📄 hcd.c
字号:
list_del_init (&urb->urb_list); dev = urb->dev; urb->dev = NULL; usb_dec_dev_use (dev); spin_unlock_irqrestore (&hcd_data_lock, flags);}/* may be called in any context with a valid urb->dev usecount *//* caller surrenders "ownership" of urb */static int hcd_submit_urb (struct urb *urb){ int status; struct usb_hcd *hcd; struct hcd_dev *dev; unsigned long flags; int pipe, temp, max; int mem_flags; if (!urb || urb->hcpriv || !urb->complete) return -EINVAL; urb->status = -EINPROGRESS; urb->actual_length = 0; urb->bandwidth = 0; INIT_LIST_HEAD (&urb->urb_list); if (!urb->dev || !urb->dev->bus || urb->dev->devnum <= 0) return -ENODEV; hcd = urb->dev->bus->hcpriv; dev = urb->dev->hcpriv; 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; /* NOTE: 2.5 passes this value explicitly in submit() */ mem_flags = in_interrupt () ? GFP_ATOMIC : GFP_KERNEL; /* 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; if (urb->next) { warn ("use explicit queuing not urb->next"); 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. /* * 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_inc_dev_use (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; if (urb->dev == hcd->bus->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 * NOTE: ref to urb->dev is a race without (2.5) refcounting, * unless driver only returns status when it didn't giveback */ if (status && urb->dev) urb_unlink (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->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->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->bus_name, udev->devnum); return -EINVAL; } 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. * * NOTE that no urb->next processing is done, even for isochronous URBs. * ISO streaming functionality can be achieved by having completion handlers * re-queue URBs. Such explicit queuing doesn't discard error reports. */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);}EXPORT_SYMBOL (usb_hcd_giveback_urb);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -