📄 hcd.c
字号:
if (!hcd || !dev)
return -ENODEV;
/* can't submit new urbs when quiescing, halted, ... */
if (hcd->state == USB_STATE_QUIESCING || !HCD_IS_RUNNING (hcd->state))
return -ESHUTDOWN;
pipe = urb->pipe;
temp = usb_pipetype (urb->pipe);
if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe),
usb_pipeout (pipe)))
return -EPIPE;
/* FIXME there should be a sharable lock protecting us against
* config/altsetting changes and disconnects, kicking in here.
*/
/* Sanity check, so HCDs can rely on clean data */
max = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe));
if (max <= 0) {
err ("bogus endpoint (bad maxpacket)");
return -EINVAL;
}
/* "high bandwidth" mode, 1-3 packets/uframe? */
if (urb->dev->speed == USB_SPEED_HIGH) {
int mult;
switch (temp) {
case PIPE_ISOCHRONOUS:
case PIPE_INTERRUPT:
mult = 1 + ((max >> 11) & 0x03);
max &= 0x03ff;
max *= mult;
}
}
/* periodic transfers limit size per frame/uframe */
switch (temp) {
case PIPE_ISOCHRONOUS: {
int n, len;
if (urb->number_of_packets <= 0)
return -EINVAL;
for (n = 0; n < urb->number_of_packets; n++) {
len = urb->iso_frame_desc [n].length;
if (len < 0 || len > max)
return -EINVAL;
}
}
break;
case PIPE_INTERRUPT:
if (urb->transfer_buffer_length > max)
return -EINVAL;
}
/* the I/O buffer must usually be mapped/unmapped */
if (urb->transfer_buffer_length < 0)
return -EINVAL;
#ifdef DEBUG
/* stuff that drivers shouldn't do, but which shouldn't
* cause problems in HCDs if they get it wrong.
*/
{
unsigned int orig_flags = urb->transfer_flags;
unsigned int allowed;
/* enforce simple/standard policy */
allowed = USB_ASYNC_UNLINK; // affects later unlinks
allowed |= USB_NO_FSBR; // only affects UHCI
switch (temp) {
case PIPE_CONTROL:
allowed |= USB_DISABLE_SPD;
break;
case PIPE_BULK:
allowed |= USB_DISABLE_SPD | USB_QUEUE_BULK
| USB_ZERO_PACKET | URB_NO_INTERRUPT;
break;
case PIPE_INTERRUPT:
allowed |= USB_DISABLE_SPD;
break;
case PIPE_ISOCHRONOUS:
allowed |= USB_ISO_ASAP;
break;
}
urb->transfer_flags &= allowed;
/* fail if submitter gave bogus flags */
if (urb->transfer_flags != orig_flags) {
err ("BOGUS urb flags, %x --> %x",
orig_flags, urb->transfer_flags);
return -EINVAL;
}
}
#endif
/*
* Force periodic transfer intervals to be legal values that are
* a power of two (so HCDs don't need to).
*
* FIXME want bus->{intr,iso}_sched_horizon values here. Each HC
* supports different values... this uses EHCI/UHCI defaults (and
* EHCI can use smaller non-default values).
*/
switch (temp) {
case PIPE_ISOCHRONOUS:
case PIPE_INTERRUPT:
/* too small? */
if (urb->interval <= 0)
return -EINVAL;
/* too big? */
switch (urb->dev->speed) {
case USB_SPEED_HIGH: /* units are microframes */
// NOTE usb handles 2^15
if (urb->interval > (1024 * 8))
urb->interval = 1024 * 8;
temp = 1024 * 8;
break;
case USB_SPEED_FULL: /* units are frames/msec */
case USB_SPEED_LOW:
if (temp == PIPE_INTERRUPT) {
if (urb->interval > 255)
return -EINVAL;
// NOTE ohci only handles up to 32
temp = 128;
} else {
if (urb->interval > 1024)
urb->interval = 1024;
// NOTE usb and ohci handle up to 2^15
temp = 1024;
}
break;
default:
return -EINVAL;
}
/* power of two? */
while (temp > urb->interval)
temp >>= 1;
urb->interval = temp;
}
/*
* FIXME: make urb timeouts be generic, keeping the HCD cores
* as simple as possible.
*/
// NOTE: a generic device/urb monitoring hook would go here.
// hcd_monitor_hook(MONITOR_URB_SUBMIT, urb)
// It would catch submission paths for all urbs.
/* increment urb's reference count, we now control it. */
urb = usb_get_urb(urb);
/*
* Atomically queue the urb, first to our records, then to the HCD.
* Access to urb->status is controlled by urb->lock ... changes on
* i/o completion (normal or fault) or unlinking.
*/
// FIXME: verify that quiescing hc works right (RH cleans up)
spin_lock_irqsave (&hcd_data_lock, flags);
if (HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_QUIESCING) {
usb_get_dev (urb->dev);
list_add (&urb->urb_list, &dev->urb_list);
status = 0;
} else {
INIT_LIST_HEAD (&urb->urb_list);
status = -ESHUTDOWN;
}
spin_unlock_irqrestore (&hcd_data_lock, flags);
if (status)
return status;
/* temporarily up refcount while queueing it in the HCD,
* since we report some queuing/setup errors ourselves
*/
urb = usb_get_urb (urb);
if (urb->dev == hcd->self.root_hub)
status = rh_urb_enqueue (hcd, urb);
else
status = hcd->driver->urb_enqueue (hcd, urb, mem_flags);
/* urb->dev got nulled if hcd called giveback for us */
if (status && urb->dev)
urb_unlink (urb);
usb_put_urb (urb);
return status;
}
/*-------------------------------------------------------------------------*/
/* called in any context */
static int hcd_get_frame_number (struct usb_device *udev)
{
struct usb_hcd *hcd = (struct usb_hcd *)udev->bus->hcpriv;
return hcd->driver->get_frame_number (hcd);
}
/*-------------------------------------------------------------------------*/
struct completion_splice { // modified urb context:
/* did we complete? */
struct completion done;
/* original urb data */
void (*complete)(struct urb *);
void *context;
};
static void unlink_complete (struct urb *urb)
{
struct completion_splice *splice;
splice = (struct completion_splice *) urb->context;
/* issue original completion call */
urb->complete = splice->complete;
urb->context = splice->context;
urb->complete (urb);
/* then let the synchronous unlink call complete */
complete (&splice->done);
}
/*
* called in any context; note ASYNC_UNLINK restrictions
*
* caller guarantees urb won't be recycled till both unlink()
* and the urb's completion function return
*/
static int hcd_unlink_urb (struct urb *urb)
{
struct hcd_dev *dev;
struct usb_hcd *hcd = 0;
unsigned long flags;
struct completion_splice splice;
int retval;
if (!urb)
return -EINVAL;
/*
* we contend for urb->status with the hcd core,
* which changes it while returning the urb.
*
* Caller guaranteed that the urb pointer hasn't been freed, and
* that it was submitted. But as a rule it can't know whether or
* not it's already been unlinked ... so we respect the reversed
* lock sequence needed for the usb_hcd_giveback_urb() code paths
* (urb lock, then hcd_data_lock) in case some other CPU is now
* unlinking it.
*/
spin_lock_irqsave (&urb->lock, flags);
spin_lock (&hcd_data_lock);
if (!urb->hcpriv || urb->transfer_flags & USB_TIMEOUT_KILLED) {
retval = -EINVAL;
goto done;
}
if (!urb->dev || !urb->dev->bus) {
retval = -ENODEV;
goto done;
}
/* giveback clears dev; non-null means it's linked at this level */
dev = urb->dev->hcpriv;
hcd = urb->dev->bus->hcpriv;
if (!dev || !hcd) {
retval = -ENODEV;
goto done;
}
/* For non-periodic transfers, any status except -EINPROGRESS means
* the HCD has already started to unlink this URB from the hardware.
* In that case, there's no more work to do.
*
* For periodic transfers, this is the only way to trigger unlinking
* from the hardware. Since we (currently) overload urb->status to
* tell the driver to unlink, error status might get clobbered ...
* unless that transfer hasn't yet restarted. One such case is when
* the URB gets unlinked from its completion handler.
*
* FIXME use an URB_UNLINKED flag to match URB_TIMEOUT_KILLED
*/
switch (usb_pipetype (urb->pipe)) {
case PIPE_CONTROL:
case PIPE_BULK:
if (urb->status != -EINPROGRESS) {
retval = -EINVAL;
goto done;
}
}
/* maybe set up to block on completion notification */
if ((urb->transfer_flags & USB_TIMEOUT_KILLED))
urb->status = -ETIMEDOUT;
else if (!(urb->transfer_flags & USB_ASYNC_UNLINK)) {
if (in_interrupt ()) {
dbg ("non-async unlink in_interrupt");
retval = -EWOULDBLOCK;
goto done;
}
/* synchronous unlink: block till we see the completion */
init_completion (&splice.done);
splice.complete = urb->complete;
splice.context = urb->context;
urb->complete = unlink_complete;
urb->context = &splice;
urb->status = -ENOENT;
} else {
/* asynchronous unlink */
urb->status = -ECONNRESET;
}
spin_unlock (&hcd_data_lock);
spin_unlock_irqrestore (&urb->lock, flags);
if (urb == (struct urb *) hcd->rh_timer.data) {
rh_status_dequeue (hcd, urb);
retval = 0;
} else {
retval = hcd->driver->urb_dequeue (hcd, urb);
// FIXME: if retval and we tried to splice, whoa!!
if (retval && urb->status == -ENOENT) err ("whoa! retval %d", retval);
}
/* block till giveback, if needed */
if (!(urb->transfer_flags & (USB_ASYNC_UNLINK|USB_TIMEOUT_KILLED))
&& HCD_IS_RUNNING (hcd->state)
&& !retval) {
dbg ("%s: wait for giveback urb %p",
hcd->self.bus_name, urb);
wait_for_completion (&splice.done);
} else if ((urb->transfer_flags & USB_ASYNC_UNLINK) && retval == 0) {
return -EINPROGRESS;
}
goto bye;
done:
spin_unlock (&hcd_data_lock);
spin_unlock_irqrestore (&urb->lock, flags);
bye:
if (retval)
dbg ("%s: hcd_unlink_urb fail %d",
hcd ? hcd->self.bus_name : "(no bus?)",
retval);
return retval;
}
/*-------------------------------------------------------------------------*/
/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup */
// FIXME: likely best to have explicit per-setting (config+alt)
// setup primitives in the usbcore-to-hcd driver API, so nothing
// is implicit. kernel 2.5 needs a bunch of config cleanup...
static int hcd_free_dev (struct usb_device *udev)
{
struct hcd_dev *dev;
struct usb_hcd *hcd;
unsigned long flags;
if (!udev || !udev->hcpriv)
return -EINVAL;
if (!udev->bus || !udev->bus->hcpriv)
return -ENODEV;
// should udev->devnum == -1 ??
dev = udev->hcpriv;
hcd = udev->bus->hcpriv;
/* device driver problem with refcounts? */
if (!list_empty (&dev->urb_list)) {
dbg ("free busy dev, %s devnum %d (bug!)",
hcd->self.bus_name, udev->devnum);
return -EINVAL;
}
if (hcd->driver->free_config)
hcd->driver->free_config (hcd, udev);
spin_lock_irqsave (&hcd_data_lock, flags);
list_del (&dev->dev_list);
udev->hcpriv = NULL;
spin_unlock_irqrestore (&hcd_data_lock, flags);
kfree (dev);
return 0;
}
static struct usb_operations hcd_operations = {
allocate: hcd_alloc_dev,
get_frame_number: hcd_get_frame_number,
submit_urb: hcd_submit_urb,
unlink_urb: hcd_unlink_urb,
deallocate: hcd_free_dev,
};
/*-------------------------------------------------------------------------*/
static void hcd_irq (int irq, void *__hcd, struct pt_regs * r)
{
struct usb_hcd *hcd = __hcd;
int start = hcd->state;
if (unlikely (hcd->state == USB_STATE_HALT)) /* irq sharing? */
return;
hcd->driver->irq (hcd);
if (hcd->state != start && hcd->state == USB_STATE_HALT)
hc_died (hcd);
}
/*-------------------------------------------------------------------------*/
/**
* usb_hcd_giveback_urb - return URB from HCD to device driver
* @hcd: host controller returning the URB
* @urb: urb being returned to the USB device driver.
* Context: in_interrupt()
*
* This hands the URB from HCD to its USB device driver, using its
* completion function. The HCD has freed all per-urb resources
* (and is done using urb->hcpriv). It also released all HCD locks;
* the device driver won't cause deadlocks if it resubmits this URB,
* and won't confuse things by modifying and resubmitting this one.
* Bandwidth and other resources will be deallocated.
*
* HCDs must not use this for periodic URBs that are still scheduled
* and will be reissued. They should just call their completion handlers
* until the urb is returned to the device driver by unlinking.
*/
void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb)
{
urb_unlink (urb);
// NOTE: a generic device/urb monitoring hook would go here.
// hcd_monitor_hook(MONITOR_URB_FINISH, urb, dev)
// It would catch exit/unlink paths for all urbs, but non-exit
// completions for periodic urbs need hooks inside the HCD.
// hcd_monitor_hook(MONITOR_URB_UPDATE, urb, dev)
if (urb->status)
dbg ("giveback urb %p status %d len %d",
urb, urb->status, urb->actual_length);
/* pass ownership to the completion handler */
urb->complete (urb);
usb_put_urb (urb);
}
EXPORT_SYMBOL (usb_hcd_giveback_urb);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -