📄 usb-uhci.c
字号:
uhci->rh.send = 0; del_timer (&uhci->rh.rh_int_timer); } return 0;}/*-------------------------------------------------------------------*//* * Map status to standard result codes * * <status> is (td->status & 0xFE0000) [a.k.a. uhci_status_bits(td->status) * <dir_out> is True for output TDs and False for input TDs. */_static int uhci_map_status (int status, int dir_out){ if (!status) return 0; if (status & TD_CTRL_BITSTUFF) /* Bitstuff error */ return -EPROTO; if (status & TD_CTRL_CRCTIMEO) { /* CRC/Timeout */ if (dir_out) return -ETIMEDOUT; else return -EILSEQ; } if (status & TD_CTRL_NAK) /* NAK */ return -ETIMEDOUT; if (status & TD_CTRL_BABBLE) /* Babble */ return -EPIPE; if (status & TD_CTRL_DBUFERR) /* Buffer error */ return -ENOSR; if (status & TD_CTRL_STALLED) /* Stalled */ return -EPIPE; if (status & TD_CTRL_ACTIVE) /* Active */ return 0; return -EPROTO;}/* * Only the USB core should call uhci_alloc_dev and uhci_free_dev */_static int uhci_alloc_dev (struct usb_device *usb_dev){ return 0;}_static void uhci_unlink_urbs(uhci_t *s, struct usb_device *usb_dev, int remove_all){ unsigned long flags; struct list_head *p; struct list_head *p2; urb_t *urb; spin_lock_irqsave (&s->urb_list_lock, flags); p = s->urb_list.prev; while (p != &s->urb_list) { p2 = p; p = p->prev ; urb = list_entry (p2, urb_t, urb_list); dbg("urb: %p, dev %p, %p", urb, usb_dev,urb->dev); //urb->transfer_flags |=USB_ASYNC_UNLINK; if (remove_all || (usb_dev == urb->dev)) { spin_unlock_irqrestore (&s->urb_list_lock, flags); warn("forced removing of queued URB %p due to disconnect",urb); uhci_unlink_urb(urb); urb->dev = NULL; // avoid further processing of this UR spin_lock_irqsave (&s->urb_list_lock, flags); p = s->urb_list.prev; } } spin_unlock_irqrestore (&s->urb_list_lock, flags);}_static int uhci_free_dev (struct usb_device *usb_dev){ uhci_t *s; if(!usb_dev || !usb_dev->bus || !usb_dev->bus->hcpriv) return -EINVAL; s=(uhci_t*) usb_dev->bus->hcpriv; uhci_unlink_urbs(s, usb_dev, 0); 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 usb_device *usb_dev){ return UHCI_GET_CURRENT_FRAME ((uhci_t*) usb_dev->bus->hcpriv);}struct usb_operations uhci_device_operations ={ uhci_alloc_dev, uhci_free_dev, uhci_get_current_frame_number, uhci_submit_urb, uhci_unlink_urb};/* * For IN-control transfers, process_transfer gets a bit more complicated, * since there are devices that return less data (eg. strings) than they * have announced. This leads to a queue abort due to the short packet, * the status stage is not executed. If this happens, the status stage * is manually re-executed. * mode: 0: QHs already unlinked */_static int process_transfer (uhci_t *s, urb_t *urb, int mode){ int ret = 0; urb_priv_t *urb_priv = urb->hcpriv; struct list_head *qhl = urb_priv->desc_list.next; uhci_desc_t *qh = list_entry (qhl, uhci_desc_t, desc_list); struct list_head *p = qh->vertical.next; uhci_desc_t *desc= list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list); uhci_desc_t *last_desc = list_entry (desc->vertical.prev, uhci_desc_t, vertical); int data_toggle = usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); // save initial data_toggle int maxlength; // extracted and remapped info from TD int actual_length; int status = 0; //dbg("process_transfer: urb contains bulk/control request"); /* if the status phase has been retriggered and the queue is empty or the last status-TD is inactive, the retriggered status stage is completed */ if (urb_priv->short_control_packet && ((qh->hw.qh.element == UHCI_PTR_TERM) ||(!(last_desc->hw.td.status & TD_CTRL_ACTIVE)))) goto transfer_finished; urb->actual_length=0; for (; p != &qh->vertical; p = p->next) { desc = list_entry (p, uhci_desc_t, vertical); if (desc->hw.td.status & TD_CTRL_ACTIVE) // do not process active TDs return ret; actual_length = (desc->hw.td.status + 1) & 0x7ff; // extract transfer parameters from TD maxlength = (((desc->hw.td.info >> 21) & 0x7ff) + 1) & 0x7ff; status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (urb->pipe)); if (status == -EPIPE) { // see if EP is stalled // set up stalled condition usb_endpoint_halt (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); } if (status != 0) { // if any error occured stop processing of further TDs // only set ret if status returned an error if (status != -EPIPE) uhci_show_td (desc); ret = status; urb->error_count++; break; } else if ((desc->hw.td.info & 0xff) != USB_PID_SETUP) urb->actual_length += actual_length; // got less data than requested if ( (actual_length < maxlength)) { if (urb->transfer_flags & USB_DISABLE_SPD) { status = -EREMOTEIO; // treat as real error dbg("process_transfer: SPD!!"); break; // exit after this TD because SP was detected } // short read during control-IN: re-start status stage if ((usb_pipetype (urb->pipe) == PIPE_CONTROL)) { if (uhci_packetid(last_desc->hw.td.info) == USB_PID_OUT) { qh->hw.qh.element = virt_to_bus (last_desc); // re-trigger status stage dbg("short packet during control transfer, retrigger status stage @ %p",last_desc); //uhci_show_td (desc); //uhci_show_td (last_desc); urb_priv->short_control_packet=1; return 0; } } // all other cases: short read is OK data_toggle = uhci_toggle (desc->hw.td.info); break; } data_toggle = uhci_toggle (desc->hw.td.info); queue_dbg("process_transfer: len:%d status:%x mapped:%x toggle:%d", actual_length, desc->hw.td.status,status, data_toggle); } usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe), !data_toggle); transfer_finished: uhci_clean_transfer(s, urb, qh, (mode==0?2:1)); urb->status = status;#ifdef CONFIG_USB_UHCI_HIGH_BANDWIDTH disable_desc_loop(s,urb);#endif queue_dbg("process_transfer: (end) urb %p, wanted len %d, len %d status %x err %d", urb,urb->transfer_buffer_length,urb->actual_length, urb->status, urb->error_count); return ret;}_static int process_interrupt (uhci_t *s, urb_t *urb){ int i, ret = -EINPROGRESS; urb_priv_t *urb_priv = urb->hcpriv; struct list_head *p = urb_priv->desc_list.next; uhci_desc_t *desc = list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list); int actual_length; int status = 0; //dbg("urb contains interrupt request"); for (i = 0; p != &urb_priv->desc_list; p = p->next, i++) // Maybe we allow more than one TD later ;-) { desc = list_entry (p, uhci_desc_t, desc_list); if (desc->hw.td.status & TD_CTRL_ACTIVE) { // do not process active TDs //dbg("TD ACT Status @%p %08x",desc,desc->hw.td.status); break; } if (!desc->hw.td.status & TD_CTRL_IOC) { // do not process one-shot TDs, no recycling break; } // extract transfer parameters from TD actual_length = (desc->hw.td.status + 1) & 0x7ff; status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (urb->pipe)); // see if EP is stalled if (status == -EPIPE) { // set up stalled condition usb_endpoint_halt (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); } // if any error occured: ignore this td, and continue if (status != 0) { //uhci_show_td (desc); urb->error_count++; goto recycle; } else urb->actual_length = actual_length; recycle: if (urb->complete) { //dbg("process_interrupt: calling completion, status %i",status); urb->status = status; spin_unlock(&s->urb_list_lock); urb->complete ((struct urb *) urb); spin_lock(&s->urb_list_lock); urb->status = -EINPROGRESS; } // Recycle INT-TD if interval!=0, else mark TD as one-shot if (urb->interval) { desc->hw.td.info &= ~(1 << TD_TOKEN_TOGGLE); if (status==0) { ((urb_priv_t*)urb->hcpriv)->started=jiffies; desc->hw.td.info |= (usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)) << TD_TOKEN_TOGGLE); usb_dotoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); } else { desc->hw.td.info |= (!usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)) << TD_TOKEN_TOGGLE); } desc->hw.td.status= (urb->pipe & TD_CTRL_LS) | TD_CTRL_ACTIVE | TD_CTRL_IOC | (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD) | (3 << 27); mb(); } else { desc->hw.td.status &= ~TD_CTRL_IOC; // inactivate TD } } return ret;}// mode: 1: force processing, don't unlink tds (already unlinked)_static int process_iso (uhci_t *s, urb_t *urb, int mode){ int i; int ret = 0; urb_priv_t *urb_priv = urb->hcpriv; struct list_head *p = urb_priv->desc_list.next; uhci_desc_t *desc = list_entry (urb_priv->desc_list.prev, uhci_desc_t, desc_list); dbg("urb contains iso request"); if ((desc->hw.td.status & TD_CTRL_ACTIVE) && !mode) return -EXDEV; // last TD not finished urb->error_count = 0; urb->actual_length = 0; urb->status = 0; dbg("process iso urb %p, %li, %i, %i, %i %08x",urb,jiffies,UHCI_GET_CURRENT_FRAME(s), urb->number_of_packets,mode,desc->hw.td.status); for (i = 0; p != &urb_priv->desc_list; p = p->next, i++) { desc = list_entry (p, uhci_desc_t, desc_list); //uhci_show_td(desc); if (desc->hw.td.status & TD_CTRL_ACTIVE) { // means we have completed the last TD, but not the TDs before desc->hw.td.status &= ~TD_CTRL_ACTIVE; dbg("TD still active (%x)- grrr. paranoia!", desc->hw.td.status); ret = -EXDEV; urb->iso_frame_desc[i].status = ret; unlink_td (s, desc, 1); // FIXME: immediate deletion may be dangerous goto err; } if (!mode) unlink_td (s, desc, 1); if (urb->number_of_packets <= i) { dbg("urb->number_of_packets (%d)<=(%d)", urb->number_of_packets, i); ret = -EINVAL; goto err; } if (urb->iso_frame_desc[i].offset + urb->transfer_buffer != bus_to_virt (desc->hw.td.buffer)) { // Hm, something really weird is going on dbg("Pointer Paranoia: %p!=%p", urb->iso_frame_desc[i].offset + urb->transfer_buffer, bus_to_virt (desc->hw.td.buffer)); ret = -EINVAL; urb->iso_frame_desc[i].status = ret; goto err; } urb->iso_frame_desc[i].actual_length = (desc->hw.td.status + 1) & 0x7ff; urb->iso_frame_desc[i].status = uhci_map_status (uhci_status_bits (desc->hw.td.status), usb_pipeout (urb->pipe)); urb->actual_length += urb->iso_frame_desc[i].actual_length; err: if (urb->iso_frame_desc[i].status != 0) { urb->error_count++; urb->status = urb->iso_frame_desc[i].status; } dbg("process_iso: %i: len:%d %08x status:%x", i, urb->iso_frame_desc[i].actual_length, desc->hw.td.status,urb->iso_frame_desc[i].status); delete_desc (desc); list_del (p); } dbg("process_iso: exit %i (%d), actual_len %i", i, ret,urb->actual_length); return ret;}_static int process_urb (uhci_t *s, struct list_head *p){ int ret = 0; urb_t *urb; urb=list_entry (p, urb_t, urb_list); //dbg("process_urb: found queued urb: %p", urb); switch (usb_pipetype (urb->pipe)) { case PIPE_CONTROL: ret = process_transfer (s, urb, 1); break; case PIPE_BULK: if (!s->avoid_bulk.counter) ret = process_transfer (s, urb, 1); else return 0; break; case PIPE_ISOCHRONOUS: ret = process_iso (s, urb, 0); break; case PIPE_INTERRUPT: ret = process_interrupt (s, urb); break; } if (urb->status != -EINPROGRESS) { int proceed = 0; dbg("dequeued urb: %p", urb); dequeue_urb (s, urb);#ifdef DEBUG_SLAB kmem_cache_free(urb_priv_kmem, urb->hcpriv);#else kfree (urb->hcpriv);#endif if ((usb_pipetype (urb->pipe) != PIPE_INTERRUPT)) { urb_t *tmp = urb->next; // pointer to first urb int is_ring = 0; if (urb->next) { do { if (tmp->status != -EINPROGRESS) { proceed = 1; break; } tmp = tmp->next; } while (tmp != NULL && tmp != urb->next); if (tmp == urb->next) is_ring = 1; } spin_unlock(&s->urb_list_lock); // In case you need the current URB status for your completion handler if (urb->complete && (!proceed || (urb->transfer_flags & USB_URB_EARLY_COMPLETE))) { dbg("process_transfer: calling early completion"); urb->complete ((struct urb *) urb); if (!proceed && is_ring && (urb->status != -ENOENT)) uhci_submit_urb (urb); } if (proceed && urb->next) { // if there are linked urbs - handle submitting of them right now. tmp = urb->next; // pointer to first urb do { if ((tmp->status != -EINPROGRESS) && (tmp->status != -ENOENT) && uhci_submit_urb (tmp) != 0) break; tmp = tmp->next; } while (tmp != NULL && tmp != urb->next); // submit until we reach NULL or our own pointer or submit fails if (urb->complete && !(urb->transfer_flags & USB_URB_EARLY_COMPLETE)) { dbg("process_transfer: calling completion"); urb->complete ((struct urb *) urb); } } spin_lock(&s->urb_list_lock); usb_dec_dev_use (urb->dev); } } return ret;}_static void uhci_interrupt (int irq, void *__uhci, struct pt_regs *regs){ uhci_t *s = __uhci; unsigned int io_addr = s->io_addr; unsigned short status; struct list_head *p, *p2; /* * 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; dbg("interrupt"); if (s
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -