📄 hcd.c
字号:
int is_iso = usb_pipeisoc (pipe); int old_alloc = dev->bus->bandwidth_allocated; int new_alloc; bustime = NS_TO_US (usb_calc_bus_time (dev->speed, is_in, is_iso, usb_maxpacket (dev, pipe, !is_in))); if (is_iso) bustime /= urb->number_of_packets; new_alloc = old_alloc + (int) bustime; if (new_alloc > FRAME_TIME_MAX_USECS_ALLOC) {#ifdef DEBUG char *mode = #ifdef CONFIG_USB_BANDWIDTH "";#else "would have ";#endif dev_dbg (&dev->dev, "usb_check_bandwidth %sFAILED: %d + %ld = %d usec\n", mode, old_alloc, bustime, new_alloc);#endif#ifdef CONFIG_USB_BANDWIDTH bustime = -ENOSPC; /* report error */#endif } return bustime;}EXPORT_SYMBOL (usb_check_bandwidth);/** * usb_claim_bandwidth - records bandwidth for a periodic transfer * @dev: source/target of request * @urb: request (urb->dev == dev) * @bustime: bandwidth consumed, in (average) microseconds per frame * @isoc: true iff the request is isochronous * * Bus bandwidth reservations are recorded purely for diagnostic purposes. * HCDs are expected not to overcommit periodic bandwidth, and to record such * reservations whenever endpoints are added to the periodic schedule. * * FIXME averaging per-frame is suboptimal. Better to sum over the HCD's * entire periodic schedule ... 32 frames for OHCI, 1024 for UHCI, settable * for EHCI (256/512/1024 frames, default 1024) and have the bus expose how * large its periodic schedule is. */void usb_claim_bandwidth (struct usb_device *dev, struct urb *urb, int bustime, int isoc){ dev->bus->bandwidth_allocated += bustime; if (isoc) dev->bus->bandwidth_isoc_reqs++; else dev->bus->bandwidth_int_reqs++; urb->bandwidth = bustime;#ifdef USB_BANDWIDTH_MESSAGES dev_dbg (&dev->dev, "bandwidth alloc increased by %d (%s) to %d for %d requesters\n", bustime, isoc ? "ISOC" : "INTR", dev->bus->bandwidth_allocated, dev->bus->bandwidth_int_reqs + dev->bus->bandwidth_isoc_reqs);#endif}EXPORT_SYMBOL (usb_claim_bandwidth);/** * usb_release_bandwidth - reverses effect of usb_claim_bandwidth() * @dev: source/target of request * @urb: request (urb->dev == dev) * @isoc: true iff the request is isochronous * * This records that previously allocated bandwidth has been released. * Bandwidth is released when endpoints are removed from the host controller's * periodic schedule. */void usb_release_bandwidth (struct usb_device *dev, struct urb *urb, int isoc){ dev->bus->bandwidth_allocated -= urb->bandwidth; if (isoc) dev->bus->bandwidth_isoc_reqs--; else dev->bus->bandwidth_int_reqs--;#ifdef USB_BANDWIDTH_MESSAGES dev_dbg (&dev->dev, "bandwidth alloc reduced by %d (%s) to %d for %d requesters\n", urb->bandwidth, isoc ? "ISOC" : "INTR", dev->bus->bandwidth_allocated, dev->bus->bandwidth_int_reqs + dev->bus->bandwidth_isoc_reqs);#endif urb->bandwidth = 0;}EXPORT_SYMBOL (usb_release_bandwidth);/*-------------------------------------------------------------------------*//* * Generic HC operations. *//*-------------------------------------------------------------------------*/static void urb_unlink (struct urb *urb){ unsigned long flags; /* Release any periodic transfer bandwidth */ if (urb->bandwidth) usb_release_bandwidth (urb->dev, urb, usb_pipeisoc (urb->pipe)); /* clear all state linking urb to this dev (and hcd) */ spin_lock_irqsave (&hcd_data_lock, flags); list_del_init (&urb->urb_list); spin_unlock_irqrestore (&hcd_data_lock, flags); usb_put_dev (urb->dev);}/* may be called in any context with a valid urb->dev usecount * caller surrenders "ownership" of urb * expects usb_submit_urb() to have sanity checked and conditioned all * inputs in the urb */static int hcd_submit_urb (struct urb *urb, gfp_t mem_flags){ int status; struct usb_hcd *hcd = urb->dev->bus->hcpriv; struct usb_host_endpoint *ep; unsigned long flags; if (!hcd) return -ENODEV; usbmon_urb_submit(&hcd->self, 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); ep = (usb_pipein(urb->pipe) ? urb->dev->ep_in : urb->dev->ep_out) [usb_pipeendpoint(urb->pipe)]; if (unlikely (!ep)) status = -ENOENT; else if (unlikely (urb->reject)) status = -EPERM; else switch (hcd->state) { case HC_STATE_RUNNING: case HC_STATE_RESUMING:doit: usb_get_dev (urb->dev); list_add_tail (&urb->urb_list, &ep->urb_list); status = 0; break; case HC_STATE_SUSPENDED: /* HC upstream links (register access, wakeup signaling) can work * even when the downstream links (and DMA etc) are quiesced; let * usbcore talk to the root hub. */ if (hcd->self.controller->power.power_state.event == PM_EVENT_ON && urb->dev->parent == NULL) goto doit; /* FALL THROUGH */ default: status = -ESHUTDOWN; break; } spin_unlock_irqrestore (&hcd_data_lock, flags); if (status) { INIT_LIST_HEAD (&urb->urb_list); usbmon_urb_submit_error(&hcd->self, urb, 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); atomic_inc (&urb->use_count); 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. */ 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 (hcd->self.controller->dma_mask) { if (usb_pipecontrol (urb->pipe) && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP)) urb->setup_dma = dma_map_single ( hcd->self.controller, urb->setup_packet, sizeof (struct usb_ctrlrequest), DMA_TO_DEVICE); if (urb->transfer_buffer_length != 0 && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) urb->transfer_dma = dma_map_single ( hcd->self.controller, urb->transfer_buffer, urb->transfer_buffer_length, usb_pipein (urb->pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); } status = hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags);done: if (unlikely (status)) { urb_unlink (urb); atomic_dec (&urb->use_count); if (urb->reject) wake_up (&usb_kill_urb_queue); usb_put_urb (urb); usbmon_urb_submit_error(&hcd->self, urb, status); } 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; if (!HC_IS_RUNNING (hcd->state)) return -ESHUTDOWN; 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 intunlink1 (struct usb_hcd *hcd, struct urb *urb){ int value; if (urb->dev == hcd->self.root_hub) value = usb_rh_urb_dequeue (hcd, urb); else { /* The only reason an HCD might fail this call is if * it has not yet fully queued the urb to begin with. * Such failures should be harmless. */ value = hcd->driver->urb_dequeue (hcd, urb); } if (value != 0) dev_dbg (hcd->self.controller, "dequeue %p --> %d\n", urb, value); return value;}/* * called in any context * * 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, int status){ struct usb_host_endpoint *ep; struct usb_hcd *hcd = NULL; struct device *sys = NULL; unsigned long flags; struct list_head *tmp; int retval; if (!urb) return -EINVAL; if (!urb->dev || !urb->dev->bus) return -ENODEV; ep = (usb_pipein(urb->pipe) ? urb->dev->ep_in : urb->dev->ep_out) [usb_pipeendpoint(urb->pipe)]; if (!ep) return -ENODEV; /* * 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); sys = &urb->dev->dev; hcd = urb->dev->bus->hcpriv; if (hcd == NULL) { retval = -ENODEV; goto done; } /* insist the urb is still queued */ list_for_each(tmp, &ep->urb_list) { if (tmp == &urb->urb_list) break; } if (tmp != &urb->urb_list) { retval = -EIDRM; 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. */ if (urb->status != -EINPROGRESS) { retval = -EBUSY; goto done; } /* IRQ setup can easily be broken so that USB controllers * never get completion IRQs ... maybe even the ones we need to * finish unlinking the initial failed usb_set_address() * or device descriptor fetch. */ if (!test_bit(HCD_FLAG_SAW_IRQ, &hcd->flags) && hcd->self.root_hub != urb->dev) { dev_warn (hcd->self.controller, "Unlink after no-IRQ? " "Controller is probably using the wrong IRQ." "\n"); set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); } urb->status = status; spin_unlock (&hcd_data_lock); spin_unlock_irqrestore (&urb->lock, flags); retval = unlink1 (hcd, urb); if (retval == 0) retval = -EINPROGRESS; return retval;done: spin_unlock (&hcd_data_lock); spin_unlock_irqrestore (&urb->lock, flags); if (retval != -EIDRM && 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 ep->hcpriv, holding state related to endpoint * type, maxpacket size, toggle, halt status, and scheduling. */static voidhcd_endpoint_disable (struct usb_device *udev, struct usb_host_endpoint *ep){ struct usb_hcd *hcd; struct urb *urb; hcd = udev->bus->hcpriv; WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT && udev->state != USB_STATE_NOTATTACHED); local_irq_disable (); /* FIXME move most of this into message.c as part of its * endpoint disable logic */ /* ep is already gone from udev->ep_{in,out}[]; no more submits */rescan: spin_lock (&hcd_data_lock); list_for_each_entry (urb, &ep->urb_list, urb_list) { int tmp; /* 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. * FIXME urb->reject should allow that... */ if (urb->status != -EINPROGRESS) continue; usb_get_urb (urb); spin_unlock (&hcd_data_lock); spin_lock (&urb->lock); tmp = urb->status; if (tmp == -EINPROGRESS) urb->status = -ESHUTDOWN; spin_unlock (&urb->lock); /* kick hcd unless it's already returning this */ if (tmp == -EINPROGRESS) { tmp = urb->pipe; unlink1 (hcd, urb); dev_dbg (hcd->self.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 (&hcd_data_lock); local_irq_enable (); /* 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, ep);}/*-------------------------------------------------------------------------*/#ifdef CONFIG_PMint hcd_bus_suspend (struct usb_bus *bus){ struct usb_hcd *hcd; int status; hcd = container_of (bus, struct usb_hcd, self); if (!hcd->driver->bus_suspend) return -ENOENT; hcd->state = HC_STATE_QUIESCING; status = hcd->driver->bus_suspend (hcd); if (status == 0) hcd->state = HC_STATE_SUSPENDED; else dev_dbg(&bus->root_hub->dev, "%s fail, err %d\n", "suspend", status); return status;}int hcd_bus_resume (struct usb_bus *bus){ struct usb_hcd *hcd; int status; hcd = container_of (bus, struct usb_hcd, self); if (!hcd->driver->bus_resume) return -ENOENT; if (hcd->state == HC_STATE_RUNNING) return 0; hcd->state = HC_STATE_RESUMING; status = hcd->driver->bus_resume (hcd); if (status == 0) hcd->state = HC_STATE_RUNNING; else {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -