freebsd-skq/sys/boot/kshim/bsd_kernel.c
hselasky 0c88dabe9d Fix device delete child function.
When detaching device trees parent devices must be detached prior to
detaching its children. This is because parent devices can have
pointers to the child devices in their softcs which are not
invalidated by device_delete_child(). This can cause use after free
issues and panic().

Device drivers implementing trees, must ensure its detach function
detaches or deletes all its children before returning.

While at it remove now redundant device_detach() calls before
device_delete_child() and device_delete_children(), mostly in
the USB controller drivers.

Tested by:		Jan Henrik Sylvester <me@janh.de>
Reviewed by:		jhb
Differential Revision:	https://reviews.freebsd.org/D8070
MFC after:		2 weeks
2016-10-17 10:20:38 +00:00

1460 lines
27 KiB
C

/* $FreeBSD$ */
/*-
* Copyright (c) 2013 Hans Petter Selasky. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <bsd_global.h>
struct usb_process usb_process[USB_PROC_MAX];
static device_t usb_pci_root;
/*------------------------------------------------------------------------*
* Implementation of mutex API
*------------------------------------------------------------------------*/
struct mtx Giant;
int (*bus_alloc_resource_any_cb)(struct resource *res, device_t dev,
int type, int *rid, unsigned int flags);
int (*ofw_bus_status_ok_cb)(device_t dev);
int (*ofw_bus_is_compatible_cb)(device_t dev, char *name);
static void
mtx_system_init(void *arg)
{
mtx_init(&Giant, "Giant", NULL, MTX_DEF | MTX_RECURSE);
}
SYSINIT(mtx_system_init, SI_SUB_LOCK, SI_ORDER_MIDDLE, mtx_system_init, NULL);
int
bus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment,
bus_size_t boundary, bus_addr_t lowaddr,
bus_addr_t highaddr, bus_dma_filter_t *filter,
void *filterarg, bus_size_t maxsize, int nsegments,
bus_size_t maxsegsz, int flags, bus_dma_lock_t *lockfunc,
void *lockfuncarg, bus_dma_tag_t *dmat)
{
struct bus_dma_tag *ret;
ret = malloc(sizeof(struct bus_dma_tag), XXX, XXX);
if (*dmat == NULL)
return (ENOMEM);
ret->alignment = alignment;
ret->maxsize = maxsize;
*dmat = ret;
return (0);
}
int
bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags,
bus_dmamap_t *mapp)
{
void *addr;
addr = malloc(dmat->maxsize + dmat->alignment, XXX, XXX);
if (addr == 0)
return (ENOMEM);
*mapp = addr;
addr = (void*)(((uintptr_t)addr + dmat->alignment - 1) & ~(dmat->alignment - 1));
*vaddr = addr;
return (0);
}
int
bus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf,
bus_size_t buflen, bus_dmamap_callback_t *callback,
void *callback_arg, int flags)
{
bus_dma_segment_t segs[1];
segs[0].ds_addr = (uintptr_t)buf;
segs[0].ds_len = buflen;
(*callback)(callback_arg, segs, 1, 0);
return (0);
}
void
bus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map)
{
free(map, XXX);
}
int
bus_dma_tag_destroy(bus_dma_tag_t dmat)
{
free(dmat, XXX);
return (0);
}
struct resource *
bus_alloc_resource_any(device_t dev, int type, int *rid, unsigned int flags)
{
struct resource *res;
int ret = EINVAL;
res = malloc(sizeof(*res), XXX, XXX);
if (res == NULL)
return (NULL);
res->__r_i = malloc(sizeof(struct resource_i), XXX, XXX);
if (res->__r_i == NULL) {
free(res, XXX);
return (NULL);
}
if (bus_alloc_resource_any_cb != NULL)
ret = (*bus_alloc_resource_any_cb)(res, dev, type, rid, flags);
if (ret == 0)
return (res);
free(res->__r_i, XXX);
free(res, XXX);
return (NULL);
}
int
bus_alloc_resources(device_t dev, struct resource_spec *rs,
struct resource **res)
{
int i;
for (i = 0; rs[i].type != -1; i++)
res[i] = NULL;
for (i = 0; rs[i].type != -1; i++) {
res[i] = bus_alloc_resource_any(dev,
rs[i].type, &rs[i].rid, rs[i].flags);
if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) {
bus_release_resources(dev, rs, res);
return (ENXIO);
}
}
return (0);
}
void
bus_release_resources(device_t dev, const struct resource_spec *rs,
struct resource **res)
{
int i;
for (i = 0; rs[i].type != -1; i++)
if (res[i] != NULL) {
bus_release_resource(
dev, rs[i].type, rs[i].rid, res[i]);
res[i] = NULL;
}
}
int
bus_setup_intr(device_t dev, struct resource *r, int flags,
driver_filter_t filter, driver_intr_t handler, void *arg, void **cookiep)
{
dev->dev_irq_filter = filter;
dev->dev_irq_fn = handler;
dev->dev_irq_arg = arg;
return (0);
}
int
bus_teardown_intr(device_t dev, struct resource *r, void *cookie)
{
dev->dev_irq_filter = NULL;
dev->dev_irq_fn = NULL;
dev->dev_irq_arg = NULL;
return (0);
}
int
bus_release_resource(device_t dev, int type, int rid, struct resource *r)
{
/* Resource releasing is not supported */
return (EINVAL);
}
int
bus_generic_attach(device_t dev)
{
device_t child;
TAILQ_FOREACH(child, &dev->dev_children, dev_link) {
device_probe_and_attach(child);
}
return (0);
}
bus_space_tag_t
rman_get_bustag(struct resource *r)
{
return (r->r_bustag);
}
bus_space_handle_t
rman_get_bushandle(struct resource *r)
{
return (r->r_bushandle);
}
u_long
rman_get_size(struct resource *r)
{
return (r->__r_i->r_end - r->__r_i->r_start + 1);
}
int
ofw_bus_status_okay(device_t dev)
{
if (ofw_bus_status_ok_cb == NULL)
return (0);
return ((*ofw_bus_status_ok_cb)(dev));
}
int
ofw_bus_is_compatible(device_t dev, char *name)
{
if (ofw_bus_is_compatible_cb == NULL)
return (0);
return ((*ofw_bus_is_compatible_cb)(dev, name));
}
void
mtx_init(struct mtx *mtx, const char *name, const char *type, int opt)
{
mtx->owned = 0;
mtx->parent = mtx;
}
void
mtx_lock(struct mtx *mtx)
{
mtx = mtx->parent;
mtx->owned++;
}
void
mtx_unlock(struct mtx *mtx)
{
mtx = mtx->parent;
mtx->owned--;
}
int
mtx_owned(struct mtx *mtx)
{
mtx = mtx->parent;
return (mtx->owned != 0);
}
void
mtx_destroy(struct mtx *mtx)
{
/* NOP */
}
/*------------------------------------------------------------------------*
* Implementation of shared/exclusive mutex API
*------------------------------------------------------------------------*/
void
sx_init_flags(struct sx *sx, const char *name, int flags)
{
sx->owned = 0;
}
void
sx_destroy(struct sx *sx)
{
/* NOP */
}
void
sx_xlock(struct sx *sx)
{
sx->owned++;
}
void
sx_xunlock(struct sx *sx)
{
sx->owned--;
}
int
sx_xlocked(struct sx *sx)
{
return (sx->owned != 0);
}
/*------------------------------------------------------------------------*
* Implementaiton of condition variable API
*------------------------------------------------------------------------*/
void
cv_init(struct cv *cv, const char *desc)
{
cv->sleeping = 0;
}
void
cv_destroy(struct cv *cv)
{
/* NOP */
}
void
cv_wait(struct cv *cv, struct mtx *mtx)
{
cv_timedwait(cv, mtx, -1);
}
int
cv_timedwait(struct cv *cv, struct mtx *mtx, int timo)
{
int start = ticks;
int delta;
int time = 0;
if (cv->sleeping)
return (EWOULDBLOCK); /* not allowed */
cv->sleeping = 1;
while (cv->sleeping) {
if (timo >= 0) {
delta = ticks - start;
if (delta >= timo || delta < 0)
break;
}
mtx_unlock(mtx);
usb_idle();
if (++time >= (1000000 / hz)) {
time = 0;
callout_process(1);
}
/* Sleep for 1 us */
delay(1);
mtx_lock(mtx);
}
if (cv->sleeping) {
cv->sleeping = 0;
return (EWOULDBLOCK); /* not allowed */
}
return (0);
}
void
cv_signal(struct cv *cv)
{
cv->sleeping = 0;
}
void
cv_broadcast(struct cv *cv)
{
cv->sleeping = 0;
}
/*------------------------------------------------------------------------*
* Implementation of callout API
*------------------------------------------------------------------------*/
static void callout_proc_msg(struct usb_proc_msg *);
volatile int ticks = 0;
static LIST_HEAD(, callout) head_callout = LIST_HEAD_INITIALIZER(&head_callout);
static struct mtx mtx_callout;
static struct usb_proc_msg callout_msg[2];
static void
callout_system_init(void *arg)
{
mtx_init(&mtx_callout, "callout-mtx", NULL, MTX_DEF | MTX_RECURSE);
callout_msg[0].pm_callback = &callout_proc_msg;
callout_msg[1].pm_callback = &callout_proc_msg;
}
SYSINIT(callout_system_init, SI_SUB_LOCK, SI_ORDER_MIDDLE, callout_system_init, NULL);
static void
callout_callback(struct callout *c)
{
mtx_lock(c->mtx);
mtx_lock(&mtx_callout);
if (c->entry.le_prev != NULL) {
LIST_REMOVE(c, entry);
c->entry.le_prev = NULL;
}
mtx_unlock(&mtx_callout);
if (c->c_func != NULL)
(c->c_func) (c->c_arg);
if (!(c->flags & CALLOUT_RETURNUNLOCKED))
mtx_unlock(c->mtx);
}
void
callout_process(int timeout)
{
ticks += timeout;
usb_proc_msignal(usb_process + 2, &callout_msg[0], &callout_msg[1]);
}
static void
callout_proc_msg(struct usb_proc_msg *pmsg)
{
struct callout *c;
int delta;
repeat:
mtx_lock(&mtx_callout);
LIST_FOREACH(c, &head_callout, entry) {
delta = c->timeout - ticks;
if (delta < 0) {
mtx_unlock(&mtx_callout);
callout_callback(c);
goto repeat;
}
}
mtx_unlock(&mtx_callout);
}
void
callout_init_mtx(struct callout *c, struct mtx *mtx, int flags)
{
memset(c, 0, sizeof(*c));
if (mtx == NULL)
mtx = &Giant;
c->mtx = mtx;
c->flags = (flags & CALLOUT_RETURNUNLOCKED);
}
void
callout_reset(struct callout *c, int to_ticks,
void (*func) (void *), void *arg)
{
callout_stop(c);
c->c_func = func;
c->c_arg = arg;
c->timeout = ticks + to_ticks;
mtx_lock(&mtx_callout);
LIST_INSERT_HEAD(&head_callout, c, entry);
mtx_unlock(&mtx_callout);
}
void
callout_stop(struct callout *c)
{
mtx_lock(&mtx_callout);
if (c->entry.le_prev != NULL) {
LIST_REMOVE(c, entry);
c->entry.le_prev = NULL;
}
mtx_unlock(&mtx_callout);
c->c_func = NULL;
c->c_arg = NULL;
}
void
callout_drain(struct callout *c)
{
if (c->mtx == NULL)
return; /* not initialised */
mtx_lock(c->mtx);
callout_stop(c);
mtx_unlock(c->mtx);
}
int
callout_pending(struct callout *c)
{
int retval;
mtx_lock(&mtx_callout);
retval = (c->entry.le_prev != NULL);
mtx_unlock(&mtx_callout);
return (retval);
}
/*------------------------------------------------------------------------*
* Implementation of device API
*------------------------------------------------------------------------*/
static const char unknown_string[] = { "unknown" };
static TAILQ_HEAD(, module_data) module_head =
TAILQ_HEAD_INITIALIZER(module_head);
static uint8_t
devclass_equal(const char *a, const char *b)
{
char ta, tb;
if (a == b)
return (1);
while (1) {
ta = *a;
tb = *b;
if (ta != tb)
return (0);
if (ta == 0)
break;
a++;
b++;
}
return (1);
}
int
bus_generic_resume(device_t dev)
{
return (0);
}
int
bus_generic_shutdown(device_t dev)
{
return (0);
}
int
bus_generic_suspend(device_t dev)
{
return (0);
}
int
bus_generic_print_child(device_t dev, device_t child)
{
return (0);
}
void
bus_generic_driver_added(device_t dev, driver_t *driver)
{
return;
}
device_t
device_get_parent(device_t dev)
{
return (dev ? dev->dev_parent : NULL);
}
void
device_set_interrupt(device_t dev, driver_filter_t *filter,
driver_intr_t *fn, void *arg)
{
dev->dev_irq_filter = filter;
dev->dev_irq_fn = fn;
dev->dev_irq_arg = arg;
}
void
device_run_interrupts(device_t parent)
{
device_t child;
if (parent == NULL)
return;
TAILQ_FOREACH(child, &parent->dev_children, dev_link) {
int status;
if (child->dev_irq_filter != NULL)
status = child->dev_irq_filter(child->dev_irq_arg);
else
status = FILTER_SCHEDULE_THREAD;
if (status == FILTER_SCHEDULE_THREAD) {
if (child->dev_irq_fn != NULL)
(child->dev_irq_fn) (child->dev_irq_arg);
}
}
}
void
device_set_ivars(device_t dev, void *ivars)
{
dev->dev_aux = ivars;
}
void *
device_get_ivars(device_t dev)
{
return (dev ? dev->dev_aux : NULL);
}
int
device_get_unit(device_t dev)
{
return (dev ? dev->dev_unit : 0);
}
int
bus_generic_detach(device_t dev)
{
device_t child;
int error;
if (!dev->dev_attached)
return (EBUSY);
TAILQ_FOREACH(child, &dev->dev_children, dev_link) {
if ((error = device_detach(child)) != 0)
return (error);
}
return (0);
}
const char *
device_get_nameunit(device_t dev)
{
if (dev && dev->dev_nameunit[0])
return (dev->dev_nameunit);
return (unknown_string);
}
static uint8_t
devclass_create(devclass_t *dc_pp)
{
if (dc_pp == NULL) {
return (1);
}
if (dc_pp[0] == NULL) {
dc_pp[0] = malloc(sizeof(**(dc_pp)),
M_DEVBUF, M_WAITOK | M_ZERO);
if (dc_pp[0] == NULL) {
return (1);
}
}
return (0);
}
static const struct module_data *
devclass_find_create(const char *classname)
{
const struct module_data *mod;
TAILQ_FOREACH(mod, &module_head, entry) {
if (devclass_equal(mod->mod_name, classname)) {
if (devclass_create(mod->devclass_pp)) {
continue;
}
return (mod);
}
}
return (NULL);
}
static uint8_t
devclass_add_device(const struct module_data *mod, device_t dev)
{
device_t *pp_dev;
device_t *end;
uint8_t unit;
pp_dev = mod->devclass_pp[0]->dev_list;
end = pp_dev + DEVCLASS_MAXUNIT;
unit = 0;
while (pp_dev != end) {
if (*pp_dev == NULL) {
*pp_dev = dev;
dev->dev_unit = unit;
dev->dev_module = mod;
snprintf(dev->dev_nameunit,
sizeof(dev->dev_nameunit),
"%s%d", device_get_name(dev), unit);
return (0);
}
pp_dev++;
unit++;
}
DPRINTF("Could not add device to devclass.\n");
return (1);
}
static void
devclass_delete_device(const struct module_data *mod, device_t dev)
{
if (mod == NULL) {
return;
}
mod->devclass_pp[0]->dev_list[dev->dev_unit] = NULL;
dev->dev_module = NULL;
}
static device_t
make_device(device_t parent, const char *name)
{
device_t dev = NULL;
const struct module_data *mod = NULL;
if (name) {
mod = devclass_find_create(name);
if (!mod) {
DPRINTF("%s:%d:%s: can't find device "
"class %s\n", __FILE__, __LINE__,
__FUNCTION__, name);
goto done;
}
}
dev = malloc(sizeof(*dev),
M_DEVBUF, M_WAITOK | M_ZERO);
if (dev == NULL)
goto done;
dev->dev_parent = parent;
TAILQ_INIT(&dev->dev_children);
if (name) {
dev->dev_fixed_class = 1;
if (devclass_add_device(mod, dev)) {
goto error;
}
}
done:
return (dev);
error:
if (dev) {
free(dev, M_DEVBUF);
}
return (NULL);
}
device_t
device_add_child(device_t dev, const char *name, int unit)
{
device_t child;
if (unit != -1) {
device_printf(dev, "Unit is not -1\n");
}
child = make_device(dev, name);
if (child == NULL) {
device_printf(dev, "Could not add child '%s'\n", name);
goto done;
}
if (dev == NULL) {
/* no parent */
goto done;
}
TAILQ_INSERT_TAIL(&dev->dev_children, child, dev_link);
done:
return (child);
}
int
device_delete_child(device_t dev, device_t child)
{
int error = 0;
device_t grandchild;
/* detach parent before deleting children, if any */
error = device_detach(child);
if (error)
goto done;
/* remove children second */
while ((grandchild = TAILQ_FIRST(&child->dev_children))) {
error = device_delete_child(child, grandchild);
if (error) {
device_printf(dev, "Error deleting child!\n");
goto done;
}
}
devclass_delete_device(child->dev_module, child);
if (dev != NULL) {
/* remove child from parent */
TAILQ_REMOVE(&dev->dev_children, child, dev_link);
}
free(child, M_DEVBUF);
done:
return (error);
}
int
device_delete_children(device_t dev)
{
device_t child;
int error = 0;
while ((child = TAILQ_FIRST(&dev->dev_children))) {
error = device_delete_child(dev, child);
if (error) {
device_printf(dev, "Error deleting child!\n");
break;
}
}
return (error);
}
void
device_quiet(device_t dev)
{
dev->dev_quiet = 1;
}
const char *
device_get_desc(device_t dev)
{
if (dev)
return &(dev->dev_desc[0]);
return (unknown_string);
}
static int
default_method(void)
{
/* do nothing */
DPRINTF("Default method called\n");
return (0);
}
void *
device_get_method(device_t dev, const char *what)
{
const struct device_method *mtod;
mtod = dev->dev_module->driver->methods;
while (mtod->func != NULL) {
if (devclass_equal(mtod->desc, what)) {
return (mtod->func);
}
mtod++;
}
return ((void *)&default_method);
}
const char *
device_get_name(device_t dev)
{
if (dev == NULL)
return (unknown_string);
return (dev->dev_module->driver->name);
}
static int
device_allocate_softc(device_t dev)
{
const struct module_data *mod;
mod = dev->dev_module;
if ((dev->dev_softc_alloc == 0) &&
(mod->driver->size != 0)) {
dev->dev_sc = malloc(mod->driver->size,
M_DEVBUF, M_WAITOK | M_ZERO);
if (dev->dev_sc == NULL)
return (ENOMEM);
dev->dev_softc_alloc = 1;
}
return (0);
}
int
device_probe_and_attach(device_t dev)
{
const struct module_data *mod;
const char *bus_name_parent;
bus_name_parent = device_get_name(device_get_parent(dev));
if (dev->dev_attached)
return (0); /* fail-safe */
if (dev->dev_fixed_class) {
mod = dev->dev_module;
if (DEVICE_PROBE(dev) <= 0) {
if (device_allocate_softc(dev) == 0) {
if (DEVICE_ATTACH(dev) == 0) {
/* success */
dev->dev_attached = 1;
return (0);
}
}
}
device_detach(dev);
goto error;
}
/*
* Else find a module for our device, if any
*/
TAILQ_FOREACH(mod, &module_head, entry) {
if (devclass_equal(mod->bus_name, bus_name_parent)) {
if (devclass_create(mod->devclass_pp)) {
continue;
}
if (devclass_add_device(mod, dev)) {
continue;
}
if (DEVICE_PROBE(dev) <= 0) {
if (device_allocate_softc(dev) == 0) {
if (DEVICE_ATTACH(dev) == 0) {
/* success */
dev->dev_attached = 1;
return (0);
}
}
}
/* else try next driver */
device_detach(dev);
}
}
error:
return (ENODEV);
}
int
device_detach(device_t dev)
{
const struct module_data *mod = dev->dev_module;
int error;
if (dev->dev_attached) {
error = DEVICE_DETACH(dev);
if (error) {
return error;
}
dev->dev_attached = 0;
}
device_set_softc(dev, NULL);
if (dev->dev_fixed_class == 0)
devclass_delete_device(mod, dev);
return (0);
}
void
device_set_softc(device_t dev, void *softc)
{
if (dev->dev_softc_alloc) {
free(dev->dev_sc, M_DEVBUF);
dev->dev_sc = NULL;
}
dev->dev_sc = softc;
dev->dev_softc_alloc = 0;
}
void *
device_get_softc(device_t dev)
{
if (dev == NULL)
return (NULL);
return (dev->dev_sc);
}
int
device_is_attached(device_t dev)
{
return (dev->dev_attached);
}
void
device_set_desc(device_t dev, const char *desc)
{
snprintf(dev->dev_desc, sizeof(dev->dev_desc), "%s", desc);
}
void
device_set_desc_copy(device_t dev, const char *desc)
{
device_set_desc(dev, desc);
}
void *
devclass_get_softc(devclass_t dc, int unit)
{
return (device_get_softc(devclass_get_device(dc, unit)));
}
int
devclass_get_maxunit(devclass_t dc)
{
int max_unit = 0;
if (dc) {
max_unit = DEVCLASS_MAXUNIT;
while (max_unit--) {
if (dc->dev_list[max_unit]) {
break;
}
}
max_unit++;
}
return (max_unit);
}
device_t
devclass_get_device(devclass_t dc, int unit)
{
return (((unit < 0) || (unit >= DEVCLASS_MAXUNIT) || (dc == NULL)) ?
NULL : dc->dev_list[unit]);
}
devclass_t
devclass_find(const char *classname)
{
const struct module_data *mod;
TAILQ_FOREACH(mod, &module_head, entry) {
if (devclass_equal(mod->driver->name, classname))
return (mod->devclass_pp[0]);
}
return (NULL);
}
void
module_register(void *data)
{
struct module_data *mdata = data;
TAILQ_INSERT_TAIL(&module_head, mdata, entry);
}
/*------------------------------------------------------------------------*
* System startup
*------------------------------------------------------------------------*/
static void
sysinit_run(const void **ppdata)
{
const struct sysinit *psys;
while ((psys = *ppdata) != NULL) {
(psys->func) (psys->data);
ppdata++;
}
}
/*------------------------------------------------------------------------*
* USB process API
*------------------------------------------------------------------------*/
static int usb_do_process(struct usb_process *);
static int usb_proc_level = -1;
static struct mtx usb_proc_mtx;
void
usb_idle(void)
{
int old_level = usb_proc_level;
int old_giant = Giant.owned;
int worked;
device_run_interrupts(usb_pci_root);
do {
worked = 0;
Giant.owned = 0;
while (++usb_proc_level < USB_PROC_MAX)
worked |= usb_do_process(usb_process + usb_proc_level);
usb_proc_level = old_level;
Giant.owned = old_giant;
} while (worked);
}
void
usb_init(void)
{
sysinit_run(sysinit_data);
}
void
usb_uninit(void)
{
sysinit_run(sysuninit_data);
}
static void
usb_process_init_sub(struct usb_process *up)
{
TAILQ_INIT(&up->up_qhead);
cv_init(&up->up_cv, "-");
cv_init(&up->up_drain, "usbdrain");
up->up_mtx = &usb_proc_mtx;
}
static void
usb_process_init(void *arg)
{
uint8_t x;
mtx_init(&usb_proc_mtx, "usb-proc-mtx", NULL, MTX_DEF | MTX_RECURSE);
for (x = 0; x != USB_PROC_MAX; x++)
usb_process_init_sub(&usb_process[x]);
}
SYSINIT(usb_process_init, SI_SUB_LOCK, SI_ORDER_MIDDLE, usb_process_init, NULL);
static int
usb_do_process(struct usb_process *up)
{
struct usb_proc_msg *pm;
int worked = 0;
mtx_lock(&usb_proc_mtx);
repeat:
pm = TAILQ_FIRST(&up->up_qhead);
if (pm != NULL) {
worked = 1;
(pm->pm_callback) (pm);
if (pm == TAILQ_FIRST(&up->up_qhead)) {
/* nothing changed */
TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry);
pm->pm_qentry.tqe_prev = NULL;
}
goto repeat;
}
mtx_unlock(&usb_proc_mtx);
return (worked);
}
void *
usb_proc_msignal(struct usb_process *up, void *_pm0, void *_pm1)
{
struct usb_proc_msg *pm0 = _pm0;
struct usb_proc_msg *pm1 = _pm1;
struct usb_proc_msg *pm2;
usb_size_t d;
uint8_t t;
t = 0;
if (pm0->pm_qentry.tqe_prev) {
t |= 1;
}
if (pm1->pm_qentry.tqe_prev) {
t |= 2;
}
if (t == 0) {
/*
* No entries are queued. Queue "pm0" and use the existing
* message number.
*/
pm2 = pm0;
} else if (t == 1) {
/* Check if we need to increment the message number. */
if (pm0->pm_num == up->up_msg_num) {
up->up_msg_num++;
}
pm2 = pm1;
} else if (t == 2) {
/* Check if we need to increment the message number. */
if (pm1->pm_num == up->up_msg_num) {
up->up_msg_num++;
}
pm2 = pm0;
} else if (t == 3) {
/*
* Both entries are queued. Re-queue the entry closest to
* the end.
*/
d = (pm1->pm_num - pm0->pm_num);
/* Check sign after subtraction */
if (d & 0x80000000) {
pm2 = pm0;
} else {
pm2 = pm1;
}
TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry);
} else {
pm2 = NULL; /* panic - should not happen */
}
/* Put message last on queue */
pm2->pm_num = up->up_msg_num;
TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry);
return (pm2);
}
/*------------------------------------------------------------------------*
* usb_proc_is_gone
*
* Return values:
* 0: USB process is running
* Else: USB process is tearing down
*------------------------------------------------------------------------*/
uint8_t
usb_proc_is_gone(struct usb_process *up)
{
return (0);
}
/*------------------------------------------------------------------------*
* usb_proc_mwait
*
* This function will return when the USB process message pointed to
* by "pm" is no longer on a queue. This function must be called
* having "usb_proc_mtx" locked.
*------------------------------------------------------------------------*/
void
usb_proc_mwait(struct usb_process *up, void *_pm0, void *_pm1)
{
struct usb_proc_msg *pm0 = _pm0;
struct usb_proc_msg *pm1 = _pm1;
/* Just remove the messages from the queue. */
if (pm0->pm_qentry.tqe_prev) {
TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry);
pm0->pm_qentry.tqe_prev = NULL;
}
if (pm1->pm_qentry.tqe_prev) {
TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry);
pm1->pm_qentry.tqe_prev = NULL;
}
}
/*------------------------------------------------------------------------*
* SYSTEM attach
*------------------------------------------------------------------------*/
#ifdef USB_PCI_PROBE_LIST
static device_method_t pci_methods[] = {
DEVMETHOD_END
};
static driver_t pci_driver = {
.name = "pci",
.methods = pci_methods,
};
static devclass_t pci_devclass;
DRIVER_MODULE(pci, pci, pci_driver, pci_devclass, 0, 0);
static const char *usb_pci_devices[] = {
USB_PCI_PROBE_LIST
};
#define USB_PCI_USB_MAX (sizeof(usb_pci_devices) / sizeof(void *))
static device_t usb_pci_dev[USB_PCI_USB_MAX];
static void
usb_pci_mod_load(void *arg)
{
uint32_t x;
usb_pci_root = device_add_child(NULL, "pci", -1);
if (usb_pci_root == NULL)
return;
for (x = 0; x != USB_PCI_USB_MAX; x++) {
usb_pci_dev[x] = device_add_child(usb_pci_root, usb_pci_devices[x], -1);
if (usb_pci_dev[x] == NULL)
continue;
if (device_probe_and_attach(usb_pci_dev[x])) {
device_printf(usb_pci_dev[x],
"WARNING: Probe and attach failed!\n");
}
}
}
SYSINIT(usb_pci_mod_load, SI_SUB_RUN_SCHEDULER, SI_ORDER_MIDDLE, usb_pci_mod_load, 0);
static void
usb_pci_mod_unload(void *arg)
{
uint32_t x;
for (x = 0; x != USB_PCI_USB_MAX; x++) {
if (usb_pci_dev[x]) {
device_detach(usb_pci_dev[x]);
device_delete_child(usb_pci_root, usb_pci_dev[x]);
}
}
if (usb_pci_root)
device_delete_child(NULL, usb_pci_root);
}
SYSUNINIT(usb_pci_mod_unload, SI_SUB_RUN_SCHEDULER, SI_ORDER_MIDDLE, usb_pci_mod_unload, 0);
#endif
/*------------------------------------------------------------------------*
* MALLOC API
*------------------------------------------------------------------------*/
#ifndef HAVE_MALLOC
#define USB_POOL_ALIGN 8
static uint8_t usb_pool[USB_POOL_SIZE] __aligned(USB_POOL_ALIGN);
static uint32_t usb_pool_rem = USB_POOL_SIZE;
static uint32_t usb_pool_entries;
struct malloc_hdr {
TAILQ_ENTRY(malloc_hdr) entry;
uint32_t size;
} __aligned(USB_POOL_ALIGN);
static TAILQ_HEAD(, malloc_hdr) malloc_head =
TAILQ_HEAD_INITIALIZER(malloc_head);
void *
usb_malloc(unsigned long size)
{
struct malloc_hdr *hdr;
size = (size + USB_POOL_ALIGN - 1) & ~(USB_POOL_ALIGN - 1);
size += sizeof(struct malloc_hdr);
TAILQ_FOREACH(hdr, &malloc_head, entry) {
if (hdr->size == size)
break;
}
if (hdr) {
DPRINTF("MALLOC: Entries = %d; Remainder = %d; Size = %d\n",
(int)usb_pool_entries, (int)usb_pool_rem, (int)size);
TAILQ_REMOVE(&malloc_head, hdr, entry);
memset(hdr + 1, 0, hdr->size - sizeof(*hdr));
return (hdr + 1);
}
if (usb_pool_rem >= size) {
hdr = (void *)(usb_pool + USB_POOL_SIZE - usb_pool_rem);
hdr->size = size;
usb_pool_rem -= size;
usb_pool_entries++;
DPRINTF("MALLOC: Entries = %d; Remainder = %d; Size = %d\n",
(int)usb_pool_entries, (int)usb_pool_rem, (int)size);
memset(hdr + 1, 0, hdr->size - sizeof(*hdr));
return (hdr + 1);
}
return (NULL);
}
void
usb_free(void *arg)
{
struct malloc_hdr *hdr;
if (arg == NULL)
return;
hdr = arg;
hdr--;
TAILQ_INSERT_TAIL(&malloc_head, hdr, entry);
}
#endif
char *
usb_strdup(const char *str)
{
char *tmp;
int len;
len = 1 + strlen(str);
tmp = malloc(len,XXX,XXX);
if (tmp == NULL)
return (NULL);
memcpy(tmp, str, len);
return (tmp);
}