📄 hcd.c
字号:
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_tail (&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;
/* increment urb's reference count as part of giving it to the HCD
* (which now controls it). HCD guarantees that it either returns
* an error or calls giveback(), but not both.
*/
urb = usb_get_urb (urb);
if (urb->dev == hcd->self.root_hub) {
/* NOTE: requirement on hub callers (usbfs and the hub
* driver, for now) that URBs' urb->transfer_buffer be
* valid and usb_buffer_{sync,unmap}() not be needed, since
* they could clobber root hub response data.
*/
urb->transfer_flags |= URB_NO_DMA_MAP;
status = rh_urb_enqueue (hcd, urb);
goto done;
}
/* lower level hcd code should use *_dma exclusively,
* unless it uses pio or talks to another transport.
*/
if (!(urb->transfer_flags & URB_NO_DMA_MAP)
&& hcd->controller->dma_mask) {
if (usb_pipecontrol (urb->pipe))
urb->setup_dma = dma_map_single (
hcd->controller,
urb->setup_packet,
sizeof (struct usb_ctrlrequest),
DMA_TO_DEVICE);
if (urb->transfer_buffer_length != 0)
urb->transfer_dma = dma_map_single (
hcd->controller,
urb->transfer_buffer,
urb->transfer_buffer_length,
usb_pipein (urb->pipe)
? DMA_FROM_DEVICE
: DMA_TO_DEVICE);
}
status = hcd->driver->urb_enqueue (hcd, urb, mem_flags);
done:
if (status) {
usb_put_urb (urb);
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);
}
/*-------------------------------------------------------------------------*/
/* this makes the hcd giveback() the urb more quickly, by kicking it
* off hardware queues (which may take a while) and returning it as
* soon as practical. we've already set up the urb's return status,
* but we can't know if the callback completed already.
*/
static void
unlink1 (struct usb_hcd *hcd, struct urb *urb)
{
if (urb == (struct urb *) hcd->rh_timer.data)
usb_rh_status_dequeue (hcd, urb);
else {
int value;
/* failures "should" be harmless */
value = hcd->driver->urb_dequeue (hcd, urb);
if (value != 0)
dev_dbg (hcd->controller,
"dequeue %p --> %d\n",
urb, value);
}
}
struct completion_splice { // modified urb context:
/* did we complete? */
struct completion done;
/* original urb data */
usb_complete_t complete;
void *context;
};
static void unlink_complete (struct urb *urb, struct pt_regs *regs)
{
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, regs);
/* 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;
struct device *sys = 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->dev || !urb->dev->bus) {
retval = -ENODEV;
goto done;
}
dev = urb->dev->hcpriv;
sys = &urb->dev->dev;
hcd = urb->dev->bus->hcpriv;
if (!dev || !hcd) {
retval = -ENODEV;
goto done;
}
if (!urb->hcpriv) {
retval = -EINVAL;
goto done;
}
/* Any status except -EINPROGRESS means something already started to
* unlink this URB from the hardware. So there's no more work to do.
*
* FIXME use better explicit urb state
*/
if (urb->status != -EINPROGRESS) {
retval = -EBUSY;
goto done;
}
/* maybe set up to block until the urb's completion fires. the
* lower level hcd code is always async, locking on urb->status
* updates; an intercepted completion unblocks us.
*/
if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) {
if (in_interrupt ()) {
dev_dbg (hcd->controller, "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);
// FIXME remove splicing, so this becomes unlink1 (hcd, urb);
if (urb == (struct urb *) hcd->rh_timer.data) {
usb_rh_status_dequeue (hcd, urb);
retval = 0;
} else {
retval = hcd->driver->urb_dequeue (hcd, urb);
/* hcds shouldn't really fail these calls, but... */
if (retval) {
dev_dbg (sys, "dequeue %p --> %d\n", urb, retval);
if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) {
spin_lock_irqsave (&urb->lock, flags);
urb->complete = splice.complete;
urb->context = splice.context;
spin_unlock_irqrestore (&urb->lock, flags);
}
goto bye;
}
}
/* block till giveback, if needed */
if (urb->transfer_flags & URB_ASYNC_UNLINK)
return -EINPROGRESS;
wait_for_completion (&splice.done);
return 0;
done:
spin_unlock (&hcd_data_lock);
spin_unlock_irqrestore (&urb->lock, flags);
bye:
if (retval && sys && sys->driver)
dev_dbg (sys, "hcd_unlink_urb %p fail %d\n", urb, retval);
return retval;
}
/*-------------------------------------------------------------------------*/
/* disables the endpoint: cancels any pending urbs, then synchronizes with
* the hcd to make sure all endpoint state is gone from hardware. use for
* set_configuration, set_interface, driver removal, physical disconnect.
*
* example: a qh stored in hcd_dev.ep[], holding state related to endpoint
* type, maxpacket size, toggle, halt status, and scheduling.
*/
static void hcd_endpoint_disable (struct usb_device *udev, int endpoint)
{
unsigned long flags;
struct hcd_dev *dev;
struct usb_hcd *hcd;
struct urb *urb;
unsigned epnum = endpoint & USB_ENDPOINT_NUMBER_MASK;
dev = udev->hcpriv;
hcd = udev->bus->hcpriv;
rescan:
/* (re)block new requests, as best we can */
if (endpoint & USB_DIR_IN) {
usb_endpoint_halt (udev, epnum, 0);
udev->epmaxpacketin [epnum] = 0;
} else {
usb_endpoint_halt (udev, epnum, 1);
udev->epmaxpacketout [epnum] = 0;
}
/* then kill any current requests */
spin_lock_irqsave (&hcd_data_lock, flags);
list_for_each_entry (urb, &dev->urb_list, urb_list) {
int tmp = urb->pipe;
/* ignore urbs for other endpoints */
if (usb_pipeendpoint (tmp) != epnum)
continue;
if ((tmp ^ endpoint) & USB_DIR_IN)
continue;
/* another cpu may be in hcd, spinning on hcd_data_lock
* to giveback() this urb. the races here should be
* small, but a full fix needs a new "can't submit"
* urb state.
*/
if (urb->status != -EINPROGRESS)
continue;
usb_get_urb (urb);
spin_unlock_irqrestore (&hcd_data_lock, flags);
spin_lock_irqsave (&urb->lock, flags);
tmp = urb->status;
if (tmp == -EINPROGRESS)
urb->status = -ESHUTDOWN;
spin_unlock_irqrestore (&urb->lock, flags);
/* kick hcd unless it's already returning this */
if (tmp == -EINPROGRESS) {
tmp = urb->pipe;
unlink1 (hcd, urb);
dev_dbg (hcd->controller,
"shutdown urb %p pipe %08x ep%d%s%s\n",
urb, tmp, usb_pipeendpoint (tmp),
(tmp & USB_DIR_IN) ? "in" : "out",
({ char *s; \
switch (usb_pipetype (tmp)) { \
case PIPE_CONTROL: s = ""; break; \
case PIPE_BULK: s = "-bulk"; break; \
case PIPE_INTERRUPT: s = "-intr"; break; \
default: s = "-iso"; break; \
}; s;}));
}
usb_put_urb (urb);
/* list contents may have changed */
goto rescan;
}
spin_unlock_irqrestore (&hcd_data_lock, flags);
/* synchronize with the hardware, so old configuration state
* clears out immediately (and will be freed).
*/
might_sleep ();
if (hcd->driver->endpoint_disable)
hcd->driver->endpoint_disable (hcd, dev, endpoint);
}
/*-------------------------------------------------------------------------*/
/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup.
* we're guaranteed that the device is fully quiesced. also, that each
* endpoint has been hcd_endpoint_disabled.
*/
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)) {
dev_dbg (hcd->controller, "free busy dev, %s devnum %d (bug!)\n",
hcd->self.bus_name, udev->devnum);
return -EINVAL;
}
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;
}
/*
* usb_hcd_operations - adapts usb_bus framework to HCD framework (bus glue)
*
* When registering a USB bus through the HCD framework code, use this
* usb_operations vector. The PCI glue layer does so automatically; only
* bus glue for non-PCI system busses will need to use this.
*/
struct usb_operations usb_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,
.buffer_alloc = hcd_buffer_alloc,
.buffer_free = hcd_buffer_free,
.disable = hcd_endpoint_disable,
};
EXPORT_SYMBOL (usb_hcd_operations);
/*-------------------------------------------------------------------------*/
/**
* 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.
* @regs: pt_regs, passed down to the URB completion handler
* 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 problems if it frees, modifies,
* or resubmits this URB.
*/
void STDCALL usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs)
{
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.
/* lower level hcd code should use *_dma exclusively */
if (!(urb->transfer_flags & URB_NO_DMA_MAP)) {
if (usb_pipecontrol (urb->pipe))
pci_unmap_single (hcd->pdev, urb->setup_dma,
sizeof (struct usb_ctrlrequest),
PCI_DMA_TODEVICE);
if (urb->transfer_buffer_length != 0)
pci_unmap_single (hcd->pdev, urb->transfer_dma,
urb->transfer_buffer_length,
usb_pipein (urb->pipe)
? PCI_DMA_FROMDEVICE
: PCI_DMA_TODEVICE);
}
/* pass ownership to the completion handler */
urb->complete (urb, regs);
usb_put_urb (urb);
}
/*-------------------------------------------------------------------------*/
/**
* usb_hcd_irq - hook IRQs to HCD framework (bus glue)
* @irq: the IRQ being raised
* @__hcd: pointer to the HCD whose IRQ is beinng signaled
* @r: saved hardware registers
*
* When registering a USB bus through the HCD framework code, use this
* to handle interrupts. The PCI glue layer does so automatically; only
* bus glue for non-PCI system busses will need to use this.
*/
irqreturn_t usb_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 IRQ_NONE;
hcd->driver->irq (hcd, r);
if (hcd->state != start && hcd->state == USB_STATE_HALT)
usb_hc_died (hcd);
return IRQ_HANDLED;
}
/*-------------------------------------------------------------------------*/
static void hcd_panic (void *_hcd)
{
struct usb_hcd *hcd = _hcd;
hcd->driver->stop (hcd);
}
/**
* usb_hc_died - report abnormal shutdown of a host controller (bus glue)
* @hcd: pointer to the HCD representing the controller
*
* This is called by bus glue to report a USB host controller that died
* while operations may still have been pending. It's called automatically
* by the PCI glue, so only glue for non-PCI busses should need to call it.
*/
void STDCALL usb_hc_died (struct usb_hcd *hcd)
{
struct list_head *devlist, *urblist;
struct hcd_dev *dev;
struct urb *urb;
unsigned long flags;
/* flag every pending urb as done */
spin_lock_irqsave (&hcd_data_lock, flags);
list_for_each (devlist, &hcd->dev_list) {
dev = list_entry (devlist, struct hcd_dev, dev_list);
list_for_each (urblist, &dev->urb_list) {
urb = list_entry (urblist, struct urb, urb_list);
dev_dbg (hcd->controller, "shutdown %s urb %p pipe %x, current status %d\n",
hcd->self.bus_name, urb, urb->pipe, urb->status);
if (urb->status == -EINPROGRESS)
urb->status = -ESHUTDOWN;
}
}
urb = (struct urb *) hcd->rh_timer.data;
if (urb)
urb->status = -ESHUTDOWN;
spin_unlock_irqrestore (&hcd_data_lock, flags);
/* hcd->stop() needs a task context */
INIT_WORK (&hcd->work, hcd_panic, hcd);
(void) schedule_work (&hcd->work);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -