📄 uhci-hcd.c
字号:
spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
return 0;
}
/*
* Return the result of a transfer
*
* MUST be called with urb_list_lock acquired
*/
static void uhci_transfer_result(struct uhci_hcd *uhci, struct urb *urb)
{
int ret = -EINVAL;
unsigned long flags;
struct urb_priv *urbp;
spin_lock_irqsave(&urb->lock, flags);
urbp = (struct urb_priv *)urb->hcpriv;
if (urb->status != -EINPROGRESS) {
info("uhci_transfer_result: called for URB %p not in flight?", urb);
goto out;
}
switch (usb_pipetype(urb->pipe)) {
case PIPE_CONTROL:
ret = uhci_result_control(uhci, urb);
break;
case PIPE_INTERRUPT:
ret = uhci_result_interrupt(uhci, urb);
break;
case PIPE_BULK:
ret = uhci_result_bulk(uhci, urb);
break;
case PIPE_ISOCHRONOUS:
ret = uhci_result_isochronous(uhci, urb);
break;
}
urbp->status = ret;
if (ret == -EINPROGRESS)
goto out;
switch (usb_pipetype(urb->pipe)) {
case PIPE_CONTROL:
case PIPE_BULK:
case PIPE_ISOCHRONOUS:
/* Release bandwidth for Interrupt or Isoc. transfers */
/* Spinlock needed ? */
if (urb->bandwidth)
usb_release_bandwidth(urb->dev, urb, 1);
uhci_unlink_generic(uhci, urb);
break;
case PIPE_INTERRUPT:
/* Interrupts are an exception */
if (urb->interval)
goto out_complete;
/* Release bandwidth for Interrupt or Isoc. transfers */
/* Spinlock needed ? */
if (urb->bandwidth)
usb_release_bandwidth(urb->dev, urb, 0);
uhci_unlink_generic(uhci, urb);
break;
default:
info("uhci_transfer_result: unknown pipe type %d for urb %p\n",
usb_pipetype(urb->pipe), urb);
}
/* Remove it from uhci->urb_list */
list_del_init(&urbp->urb_list);
out_complete:
uhci_add_complete(uhci, urb);
out:
spin_unlock_irqrestore(&urb->lock, flags);
}
/*
* MUST be called with urb->lock acquired
*/
static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb)
{
struct list_head *head, *tmp;
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
int prevactive = 1;
/* We can get called when urbp allocation fails, so check */
if (!urbp)
return;
uhci_dec_fsbr(uhci, urb); /* Safe since it checks */
/*
* Now we need to find out what the last successful toggle was
* so we can update the local data toggle for the next transfer
*
* There's 3 way's the last successful completed TD is found:
*
* 1) The TD is NOT active and the actual length < expected length
* 2) The TD is NOT active and it's the last TD in the chain
* 3) The TD is active and the previous TD is NOT active
*
* Control and Isochronous ignore the toggle, so this is safe
* for all types
*/
head = &urbp->td_list;
tmp = head->next;
while (tmp != head) {
struct uhci_td *td = list_entry(tmp, struct uhci_td, list);
tmp = tmp->next;
if (!(td_status(td) & TD_CTRL_ACTIVE) &&
(uhci_actual_length(td_status(td)) < uhci_expected_length(td_token(td)) ||
tmp == head))
usb_settoggle(urb->dev, uhci_endpoint(td_token(td)),
uhci_packetout(td_token(td)),
uhci_toggle(td_token(td)) ^ 1);
else if ((td_status(td) & TD_CTRL_ACTIVE) && !prevactive)
usb_settoggle(urb->dev, uhci_endpoint(td_token(td)),
uhci_packetout(td_token(td)),
uhci_toggle(td_token(td)));
prevactive = td_status(td) & TD_CTRL_ACTIVE;
}
uhci_delete_queued_urb(uhci, urb);
/* The interrupt loop will reclaim the QH's */
uhci_remove_qh(uhci, urbp->qh);
urbp->qh = NULL;
}
static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned long flags;
struct urb_priv *urbp = urb->hcpriv;
spin_lock_irqsave(&uhci->urb_list_lock, flags);
list_del_init(&urbp->urb_list);
uhci_unlink_generic(uhci, urb);
if (urb->transfer_flags & USB_ASYNC_UNLINK) {
urbp->status = urb->status = -ECONNABORTED;
spin_lock(&uhci->urb_remove_list_lock);
/* If we're the first, set the next interrupt bit */
if (list_empty(&uhci->urb_remove_list))
uhci_set_next_interrupt(uhci);
list_add(&urbp->urb_list, &uhci->urb_remove_list);
spin_unlock(&uhci->urb_remove_list_lock);
spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
} else {
urb->status = -ENOENT;
spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
if (in_interrupt()) { /* wait at least 1 frame */
static int errorcount = 10;
if (errorcount--)
dbg("uhci_urb_dequeue called from interrupt for urb %p", urb);
udelay(1000);
} else
schedule_timeout(1+1*HZ/1000);
uhci_finish_urb(hcd, urb);
}
return 0;
}
static int uhci_fsbr_timeout(struct uhci_hcd *uhci, struct urb *urb)
{
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
struct list_head *head, *tmp;
int count = 0;
uhci_dec_fsbr(uhci, urb);
urbp->fsbr_timeout = 1;
/*
* Ideally we would want to fix qh->element as well, but it's
* read/write by the HC, so that can introduce a race. It's not
* really worth the hassle
*/
head = &urbp->td_list;
tmp = head->next;
while (tmp != head) {
struct uhci_td *td = list_entry(tmp, struct uhci_td, list);
tmp = tmp->next;
/*
* Make sure we don't do the last one (since it'll have the
* TERM bit set) as well as we skip every so many TD's to
* make sure it doesn't hog the bandwidth
*/
if (tmp != head && (count % DEPTH_INTERVAL) == (DEPTH_INTERVAL - 1))
td->link |= UHCI_PTR_DEPTH;
count++;
}
return 0;
}
/*
* uhci_get_current_frame_number()
*
* returns the current frame number for a USB bus/controller.
*/
static int uhci_get_current_frame_number(struct uhci_hcd *uhci)
{
return inw(uhci->io_addr + USBFRNUM);
}
static int init_stall_timer(struct usb_hcd *hcd);
static void stall_callback(unsigned long ptr)
{
struct usb_hcd *hcd = (struct usb_hcd *)ptr;
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
struct list_head list, *tmp, *head;
unsigned long flags;
INIT_LIST_HEAD(&list);
spin_lock_irqsave(&uhci->urb_list_lock, flags);
head = &uhci->urb_list;
tmp = head->next;
while (tmp != head) {
struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list);
struct urb *u = up->urb;
tmp = tmp->next;
spin_lock(&u->lock);
/* Check if the FSBR timed out */
if (up->fsbr && !up->fsbr_timeout && time_after_eq(jiffies, up->fsbrtime + IDLE_TIMEOUT))
uhci_fsbr_timeout(uhci, u);
/* Check if the URB timed out */
if (u->timeout && time_after_eq(jiffies, up->inserttime + u->timeout)) {
list_del(&up->urb_list);
list_add_tail(&up->urb_list, &list);
}
spin_unlock(&u->lock);
}
spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
head = &list;
tmp = head->next;
while (tmp != head) {
struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list);
struct urb *u = up->urb;
tmp = tmp->next;
u->transfer_flags |= USB_ASYNC_UNLINK | USB_TIMEOUT_KILLED;
uhci_urb_dequeue(hcd, u);
}
/* Really disable FSBR */
if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies, uhci->fsbrtimeout)) {
uhci->fsbrtimeout = 0;
uhci->skel_term_qh->link = UHCI_PTR_TERM;
}
/* enter global suspend if nothing connected */
if (!uhci->is_suspended && !ports_active(uhci))
suspend_hc(uhci);
init_stall_timer(hcd);
}
static int init_stall_timer(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
init_timer(&uhci->stall_timer);
uhci->stall_timer.function = stall_callback;
uhci->stall_timer.data = (unsigned long)hcd;
uhci->stall_timer.expires = jiffies + (HZ / 10);
add_timer(&uhci->stall_timer);
return 0;
}
static void uhci_free_pending_qhs(struct uhci_hcd *uhci)
{
struct list_head *tmp, *head;
unsigned long flags;
spin_lock_irqsave(&uhci->qh_remove_list_lock, flags);
head = &uhci->qh_remove_list;
tmp = head->next;
while (tmp != head) {
struct uhci_qh *qh = list_entry(tmp, struct uhci_qh, remove_list);
tmp = tmp->next;
list_del_init(&qh->remove_list);
uhci_free_qh(uhci, qh);
}
spin_unlock_irqrestore(&uhci->qh_remove_list_lock, flags);
}
static void uhci_finish_urb(struct usb_hcd *hcd, struct urb *urb)
{
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
struct usb_device *dev = urb->dev;
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int killed, resubmit_interrupt, status;
unsigned long flags;
spin_lock_irqsave(&urb->lock, flags);
killed = (urb->status == -ENOENT || urb->status == -ECONNABORTED ||
urb->status == -ECONNRESET);
resubmit_interrupt = (usb_pipetype(urb->pipe) == PIPE_INTERRUPT &&
urb->interval);
if (urbp->transfer_buffer_dma_handle)
pci_dma_sync_single(uhci->dev, urbp->transfer_buffer_dma_handle,
urb->transfer_buffer_length, usb_pipein(urb->pipe) ?
PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
if (urbp->setup_packet_dma_handle)
pci_dma_sync_single(uhci->dev, urbp->setup_packet_dma_handle,
sizeof(struct usb_ctrlrequest), PCI_DMA_TODEVICE);
status = urbp->status;
if (!resubmit_interrupt || killed)
/* We don't need urb_priv anymore */
uhci_destroy_urb_priv(uhci, urb);
if (!killed)
urb->status = status;
spin_unlock_irqrestore(&urb->lock, flags);
if (resubmit_interrupt)
urb->complete(urb);
else
usb_hcd_giveback_urb(hcd, urb);
if (resubmit_interrupt)
/* Recheck the status. The completion handler may have */
/* unlinked the resubmitting interrupt URB */
killed = (urb->status == -ENOENT ||
urb->status == -ECONNABORTED ||
urb->status == -ECONNRESET);
if (resubmit_interrupt && !killed) {
urb->dev = dev;
uhci_reset_interrupt(uhci, urb);
}
}
static void uhci_finish_completion(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
struct list_head *tmp, *head;
unsigned long flags;
spin_lock_irqsave(&uhci->complete_list_lock, flags);
head = &uhci->complete_list;
tmp = head->next;
while (tmp != head) {
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, complete_list);
struct urb *urb = urbp->urb;
list_del_init(&urbp->complete_list);
spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
uhci_finish_urb(hcd, urb);
spin_lock_irqsave(&uhci->complete_list_lock, flags);
head = &uhci->complete_list;
tmp = head->next;
}
spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
}
static void uhci_remove_pending_qhs(struct uhci_hcd *uhci)
{
struct list_head *tmp, *head;
unsigned long flags;
spin_lock_irqsave(&uhci->urb_remove_list_lock, flags);
head = &uhci->urb_remove_list;
tmp = head->next;
while (tmp != head) {
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list);
struct urb *urb = urbp->urb;
tmp = tmp->next;
list_del_init(&urbp->urb_list);
urbp->status = urb->status = -ECONNRESET;
uhci_add_complete(uhci, urb);
}
spin_unlock_irqrestore(&uhci->urb_remove_list_lock, flags);
}
static void uhci_irq(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned int io_addr = uhci->io_addr;
unsigned short status;
struct list_head *tmp, *head;
/*
* Read the interrupt status, and write it back to clear the
* interrupt cause
*/
status = inw(io_addr + USBSTS);
if (!status) /* shared interrupt, not mine */
return;
outw(status, io_addr + USBSTS); /* Clear it */
if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) {
if (status & USBSTS_HSE)
err("%x: host system error, PCI problems?", io_addr);
if (status & USBSTS_HCPE)
err("%x: host controller process error. something bad happened", io_addr);
if ((status & USBSTS_HCH) && !uhci->is_suspended) {
err("%x: host controller halted. very bad", io_addr);
/* FIXME: Reset the controller, fix the offending TD */
}
}
if (status & USBSTS_RD)
wakeup_hc(uhci);
uhci_free_pending_qhs(uhci);
uhci_remove_pending_qhs(uhci);
uhci_clear_next_interrupt(uhci);
/* Walk the list of pending URB's to see which ones completed */
spin_lock(&uhci->urb_list_lock);
head = &uhci->urb_list;
tmp = head->next;
while (tmp != head) {
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list);
struct urb *urb = urbp->urb;
tmp = tmp->next;
/* Checks the status and does all of the magic necessary */
uhci_transfer_result(uhci, urb);
}
spin_unlock(&uhci->urb_list_lock);
uhci_finish_completion(hcd);
}
static void reset_hc(struct uhci_hcd *uhci)
{
unsigned int io_addr = uhci->io_addr;
/* Global reset for 50ms */
outw(USBCMD_GRESET, io_addr + USBCMD);
wait_ms(50);
outw(0, io_addr + USBCMD);
wait_ms(10);
}
static void suspend_hc(struct uhci_hcd *uhci)
{
unsigned int io_addr = uhci->io_addr;
dbg("%x: suspend_hc", io_addr);
outw(USBCMD_EGSM, io_addr + USBCMD);
uhci->is_suspended = 1;
}
static void wakeup_hc(struct uhci_hcd *uhci)
{
unsigned int io_addr = uhci->io_addr;
unsigned int status;
dbg("%x: wakeup_hc", io_addr);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -