📄 usb-ohci.c
字号:
urb_print (urb, "SUB", usb_pipein (pipe));#endif /* handle a request to the virtual root hub */ if (usb_pipedevice (pipe) == ohci->rh.devnum) return rh_submit_urb (urb); /* when controller's hung, permit only roothub cleanup attempts * such as powering down ports */ if (ohci->disabled) { usb_dec_dev_use (urb->dev); return -ESHUTDOWN; } /* every endpoint has a ed, locate and fill it */ if (!(ed = ep_add_ed (urb->dev, pipe, urb->interval, 1))) { usb_dec_dev_use (urb->dev); return -ENOMEM; } /* for the private part of the URB we need the number of TDs (size) */ switch (usb_pipetype (pipe)) { case PIPE_BULK: /* one TD for every 4096 Byte */ size = (urb->transfer_buffer_length - 1) / 4096 + 1; break; case PIPE_ISOCHRONOUS: /* number of packets from URB */ size = urb->number_of_packets; if (size <= 0) { usb_dec_dev_use (urb->dev); return -EINVAL; } for (i = 0; i < urb->number_of_packets; i++) { urb->iso_frame_desc[i].actual_length = 0; urb->iso_frame_desc[i].status = -EXDEV; } break; case PIPE_CONTROL: /* 1 TD for setup, 1 for ACK and 1 for every 4096 B */ size = (urb->transfer_buffer_length == 0)? 2: (urb->transfer_buffer_length - 1) / 4096 + 3; break; case PIPE_INTERRUPT: /* one TD */ size = 1; break; } /* allocate the private part of the URB */ urb_priv = kmalloc (sizeof (urb_priv_t) + size * sizeof (td_t *), in_interrupt() ? GFP_ATOMIC : GFP_KERNEL); if (!urb_priv) { usb_dec_dev_use (urb->dev); return -ENOMEM; } memset (urb_priv, 0, sizeof (urb_priv_t) + size * sizeof (td_t *)); /* fill the private part of the URB */ urb_priv->length = size; urb_priv->ed = ed; /* allocate the TDs */ for (i = 0; i < size; i++) { urb_priv->td[i] = td_alloc (ohci); if (!urb_priv->td[i]) { urb_free_priv (ohci, urb_priv); usb_dec_dev_use (urb->dev); return -ENOMEM; } } if (ed->state == ED_NEW || (ed->state & ED_DEL)) { urb_free_priv (ohci, urb_priv); usb_dec_dev_use (urb->dev); return -EINVAL; } /* allocate and claim bandwidth if needed; ISO * needs start frame index if it was't provided. */ switch (usb_pipetype (pipe)) { case PIPE_ISOCHRONOUS: if (urb->transfer_flags & USB_ISO_ASAP) { urb->start_frame = ((ed->state == ED_OPER) ? (ed->last_iso + 1) : (le16_to_cpu (ohci->hcca.frame_no) + 10)) & 0xffff; } /* FALLTHROUGH */ case PIPE_INTERRUPT: if (urb->bandwidth == 0) { bustime = usb_check_bandwidth (urb->dev, urb); } if (bustime < 0) { urb_free_priv (ohci, urb_priv); usb_dec_dev_use (urb->dev); return bustime; } usb_claim_bandwidth (urb->dev, urb, bustime, usb_pipeisoc (urb->pipe)); } spin_lock_irqsave (&usb_ed_lock, flags); urb->actual_length = 0; urb->hcpriv = urb_priv; urb->status = USB_ST_URB_PENDING; /* link the ed into a chain if is not already */ if (ed->state != ED_OPER) ep_link (ohci, ed); /* fill the TDs and link it to the ed */ td_submit_urb (urb); spin_unlock_irqrestore (&usb_ed_lock, flags); return 0; }/*-------------------------------------------------------------------------*//* deactivate all TDs and remove the private part of the URB *//* interrupt callers must use async unlink mode */static int sohci_unlink_urb (urb_t * urb){ unsigned long flags; ohci_t * ohci; if (!urb) /* just to be sure */ return -EINVAL; if (!urb->dev || !urb->dev->bus) return -ENODEV; ohci = (ohci_t *) urb->dev->bus->hcpriv; #ifdef DEBUG urb_print (urb, "UNLINK", 1);#endif /* handle a request to the virtual root hub */ if (usb_pipedevice (urb->pipe) == ohci->rh.devnum) return rh_unlink_urb (urb); if (urb->hcpriv && (urb->status == USB_ST_URB_PENDING)) { if (!ohci->disabled) { urb_priv_t * urb_priv; /* interrupt code may not sleep; it must use * async status return to unlink pending urbs. */ if (!(urb->transfer_flags & USB_ASYNC_UNLINK) && in_interrupt ()) { err ("bug in call from %p; use async!", __builtin_return_address(0)); return -EWOULDBLOCK; } /* flag the urb and its TDs for deletion in some * upcoming SF interrupt delete list processing */ spin_lock_irqsave (&usb_ed_lock, flags); urb_priv = urb->hcpriv; if (!urb_priv || (urb_priv->state == URB_DEL)) { spin_unlock_irqrestore (&usb_ed_lock, flags); return 0; } urb_priv->state = URB_DEL; ep_rm_ed (urb->dev, urb_priv->ed); urb_priv->ed->state |= ED_URB_DEL; if (!(urb->transfer_flags & USB_ASYNC_UNLINK)) { DECLARE_WAIT_QUEUE_HEAD (unlink_wakeup); DECLARE_WAITQUEUE (wait, current); int timeout = OHCI_UNLINK_TIMEOUT; add_wait_queue (&unlink_wakeup, &wait); urb_priv->wait = &unlink_wakeup; spin_unlock_irqrestore (&usb_ed_lock, flags); /* wait until all TDs are deleted */ set_current_state(TASK_UNINTERRUPTIBLE); while (timeout && (urb->status == USB_ST_URB_PENDING)) timeout = schedule_timeout (timeout); current->state = TASK_RUNNING; remove_wait_queue (&unlink_wakeup, &wait); if (urb->status == USB_ST_URB_PENDING) { err ("unlink URB timeout"); return -ETIMEDOUT; } } else { /* usb_dec_dev_use done in dl_del_list() */ urb->status = -EINPROGRESS; spin_unlock_irqrestore (&usb_ed_lock, flags); } } else { urb_rm_priv (urb); if (urb->transfer_flags & USB_ASYNC_UNLINK) { urb->status = -ECONNRESET; if (urb->complete) urb->complete (urb); } else urb->status = -ENOENT; } } return 0;}/*-------------------------------------------------------------------------*//* allocate private data space for a usb device */static int sohci_alloc_dev (struct usb_device *usb_dev){ struct ohci_device * dev; /* FIXME: ED allocation with pci_consistent memory * must know the controller ... either pass it in here, * or decouple ED allocation from dev allocation. */ dev = dev_alloc (NULL); if (!dev) return -ENOMEM; memset (dev, 0, sizeof (*dev)); usb_dev->hcpriv = dev; return 0;}/*-------------------------------------------------------------------------*//* may be called from interrupt context *//* frees private data space of usb device */ static int sohci_free_dev (struct usb_device * usb_dev){ unsigned long flags; int i, cnt = 0; ed_t * ed; struct ohci_device * dev = usb_to_ohci (usb_dev); ohci_t * ohci = usb_dev->bus->hcpriv; if (!dev) return 0; if (usb_dev->devnum >= 0) { /* driver disconnects should have unlinked all urbs * (freeing all the TDs, unlinking EDs) but we need * to defend against bugs that prevent that. */ spin_lock_irqsave (&usb_ed_lock, flags); for(i = 0; i < NUM_EDS; i++) { ed = &(dev->ed[i]); if (ed->state != ED_NEW) { if (ed->state == ED_OPER) { /* driver on that interface didn't unlink an urb */ dbg ("driver usb-%s dev %d ed 0x%x unfreed URB", ohci->ohci_dev->slot_name, usb_dev->devnum, i); ep_unlink (ohci, ed); } ep_rm_ed (usb_dev, ed); ed->state = ED_DEL; cnt++; } } spin_unlock_irqrestore (&usb_ed_lock, flags); /* if the controller is running, tds for those unlinked * urbs get freed by dl_del_list at the next SF interrupt */ if (cnt > 0) { if (ohci->disabled) { /* FIXME: Something like this should kick in, * though it's currently an exotic case ... * the controller won't ever be touching * these lists again!! dl_del_list (ohci, le16_to_cpu (ohci->hcca.frame_no) & 1); */ warn ("TD leak, %d", cnt); } else if (!in_interrupt ()) { DECLARE_WAIT_QUEUE_HEAD (freedev_wakeup); DECLARE_WAITQUEUE (wait, current); int timeout = OHCI_UNLINK_TIMEOUT; /* SF interrupt handler calls dl_del_list */ add_wait_queue (&freedev_wakeup, &wait); dev->wait = &freedev_wakeup; set_current_state(TASK_UNINTERRUPTIBLE); while (timeout && dev->ed_cnt) timeout = schedule_timeout (timeout); current->state = TASK_RUNNING; remove_wait_queue (&freedev_wakeup, &wait); if (dev->ed_cnt) { err ("free device %d timeout", usb_dev->devnum); return -ETIMEDOUT; } } else { /* likely some interface's driver has a refcount bug */ err ("bus %s devnum %d deletion in interrupt", ohci->ohci_dev->slot_name, usb_dev->devnum); BUG (); } } } /* free device, and associated EDs */ dev_free (dev); return 0;}/*-------------------------------------------------------------------------*//* tell us the current USB frame number */static int sohci_get_current_frame_number (struct usb_device *usb_dev) { ohci_t * ohci = usb_dev->bus->hcpriv; return le16_to_cpu (ohci->hcca.frame_no);}/*-------------------------------------------------------------------------*/struct usb_operations sohci_device_operations = { sohci_alloc_dev, sohci_free_dev, sohci_get_current_frame_number, sohci_submit_urb, sohci_unlink_urb};/*-------------------------------------------------------------------------* * ED handling functions *-------------------------------------------------------------------------*/ /* search for the right branch to insert an interrupt ed into the int tree * do some load ballancing; * returns the branch and * sets the interval to interval = 2^integer (ld (interval)) */static int ep_int_ballance (ohci_t * ohci, int interval, int load){ int i, branch = 0; /* search for the least loaded interrupt endpoint branch of all 32 branches */ for (i = 0; i < 32; i++) if (ohci->ohci_int_load [branch] > ohci->ohci_int_load [i]) branch = i; branch = branch % interval; for (i = branch; i < 32; i += interval) ohci->ohci_int_load [i] += load; return branch;}/*-------------------------------------------------------------------------*//* 2^int( ld (inter)) */static int ep_2_n_interval (int inter){ int i; for (i = 0; ((inter >> i) > 1 ) && (i < 5); i++); return 1 << i;}/*-------------------------------------------------------------------------*//* the int tree is a binary tree * in order to process it sequentially the indexes of the branches have to be mapped * the mapping reverses the bits of a word of num_bits length */ static int ep_rev (int num_bits, int word){ int i, wout = 0; for (i = 0; i < num_bits; i++) wout |= (((word >> i) & 1) << (num_bits - i - 1)); return wout;}/*-------------------------------------------------------------------------*//* link an ed into one of the HC chains */static int ep_link (ohci_t * ohci, ed_t * edi){ int int_branch; int i; int inter; int interval; int load; __u32 * ed_p; volatile ed_t * ed = edi; ed->state = ED_OPER; switch (ed->type) { case PIPE_CONTROL: ed->hwNextED = 0; if (ohci->ed_controltail == NULL) { writel (virt_to_bus (ed), &ohci->regs->ed_controlhead); } else { ohci->ed_controltail->hwNextED = cpu_to_le32 (virt_to_bus (ed)); } ed->ed_prev = ohci->ed_controltail; if (!ohci->ed_controltail && !ohci->ed_rm_list[0] && !ohci->ed_rm_list[1]) { ohci->hc_control |= OHCI_CTRL_CLE; writel (ohci->hc_control, &ohci->regs->control); } ohci->ed_controltail = edi; break; case PIPE_BULK: ed->hwNextED = 0; if (ohci->ed_bulktail == NULL) { writel (virt_to_bus (ed), &ohci->regs->ed_bulkhead); } else { ohci->ed_bulktail->hwNextED = cpu_to_le32 (virt_to_bus (ed)); } ed->ed_prev = ohci->ed_bulktail; if (!ohci->ed_bulktail && !ohci->ed_rm_list[0] && !ohci->ed_rm_list[1]) { ohci->hc_control |= OHCI_CTRL_BLE; writel (ohci->hc_control, &ohci->regs->control); } ohci->ed_bulktail = edi; break; case PIPE_INTERRUPT: load = ed->int_load; interval = ep_2_n_interval (ed->int_period); ed->int_interval = interval; int_branch = ep_int_ballance (ohci, interval, load); ed->int_branch = int_branch; for (i = 0; i < ep_rev (6, interval); i += inter) { inter = 1; for (ed_p = &(ohci->hcca.int_table[ep_rev (5, i) + int_branch]); (*ed_p != 0) && (((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->int_interval >= interval); ed_p = &(((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->hwNextED)) inter = ep_rev (6, ((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->int_interval); ed->hwNextED = *ed_p; *ed_p = cpu_to_le32 (virt_to_bus (ed)); }#ifdef DEBUG ep_print_int_eds (ohci, "LINK_INT");#endif break; case PIPE_ISOCHRONOUS: ed->hwNextED = 0; ed->int_interval = 1; if (ohci->ed_isotail != NULL) { ohci->ed_isotail->hwNextED = cpu_to_le32 (virt_to_bus (ed)); ed->ed_prev = ohci->ed_isotail; } else { for ( i = 0; i < 32; i += inter) { inter = 1; for (ed_p = &(ohci->hcca.int_table[ep_rev (5, i)]); *ed_p != 0; ed_p = &(((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->hwNextED)) inter = ep_rev (6, ((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->int_interval); *ed_p = cpu_to_le32 (virt_to_bus (ed)); } ed->ed_prev = NULL; } ohci->ed_isotail = edi; #ifdef DEBUG ep_print_int_eds (ohci, "LINK_ISO");#endif break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -