📄 usb-uhci-q.c
字号:
/*
UHCI HCD (Host Controller Driver) for USB, UHCI transfer processing
(c) 1999-2002
Georg Acher + Deti Fliegl + Thomas Sailer
georg@acher.org deti@fliegl.de sailer@ife.ee.ethz.ch
with the help of
David Brownell, david-b@pacbell.net
Adam Richter, adam@yggdrasil.com
Roman Weissgaerber, weissg@vienna.at
HW-initalization based on material of
Randy Dunlap + Johannes Erdfelt + Gregory P. Smith + Linus Torvalds
$Id: usb-uhci-q.c,v 1.3 2002/05/25 16:42:41 acher Exp $
*/
/*-------------------------------------------------------------------*/
static inline void finish_urb (struct uhci_hcd *uhci, struct urb *urb)
{
if (urb->hcpriv)
uhci_free_priv (uhci, urb, urb->hcpriv);
usb_hcd_giveback_urb (&uhci->hcd, urb);
}
/*###########################################################################*/
// URB SUBMISSION STUFF
// assembles QHs und TDs for control, bulk, interrupt and isochronous
/*###########################################################################*/
// returns: 0 (no transfer queued), urb* (this urb already queued)
static struct urb* search_dev_ep (struct uhci_hcd *uhci, struct urb *urb)
{
struct list_head *p;
struct urb *tmp;
urb_priv_t *priv;
unsigned int mask = usb_pipecontrol(urb->pipe) ? (~USB_DIR_IN) : (~0);
p=uhci->urb_list.next;
for (; p != &uhci->urb_list; p = p->next) {
priv = list_entry (p, urb_priv_t, urb_list);
tmp = priv->urb;
dbg("search_dev_ep urb: %p", tmp);
// we can accept this urb if it is not queued at this time
// or if non-iso transfer requests should be scheduled for the same device and pipe
if ((!usb_pipeisoc(urb->pipe) && (tmp->dev == urb->dev) && !((tmp->pipe ^ urb->pipe) & mask)) ||
(urb == tmp))
return tmp; // found another urb already queued for processing
}
return 0;
}
/*-------------------------------------------------------------------*/
static int uhci_submit_control_urb (struct uhci_hcd *uhci, struct urb *urb)
{
uhci_desc_t *qh, *td;
urb_priv_t *urb_priv = urb->hcpriv;
unsigned long destination, status;
int maxsze = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe));
int depth_first = ctrl_depth; // UHCI descriptor chasing method
unsigned long len;
char *data;
// err("uhci_submit_control start, buf %p", urb->transfer_buffer);
if (alloc_qh (uhci, &qh)) // alloc qh for this request
return -ENOMEM;
if (alloc_td (uhci, &td, UHCI_PTR_DEPTH * depth_first)) // get td for setup stage
goto fail_unmap_enomem;
/* The "pipe" thing contains the destination in bits 8--18 */
destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;
status = TD_CTRL_ACTIVE
| (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD)
| (3 << 27); /* 3 errors */
if (urb->dev->speed == USB_SPEED_LOW)
status |= TD_CTRL_LS;
/* Build the TD for the control request, try forever, 8 bytes of data */
fill_td (td, status, destination | (7 << 21), urb_priv->setup_packet_dma);
insert_td (uhci, qh, td, 0); // queue 'setup stage'-td in qh
#if 0
{
char *sp=urb->setup_packet;
dbg("SETUP to pipe %x: %x %x %x %x %x %x %x %x", urb->pipe,
sp[0],sp[1],sp[2],sp[3],sp[4],sp[5],sp[6],sp[7]);
}
//uhci_show_td(td);
#endif
len = urb->transfer_buffer_length;
data = urb->transfer_buffer;
/* If direction is "send", change the frame from SETUP (0x2D)
to OUT (0xE1). Else change it from SETUP to IN (0x69). */
destination = (urb->pipe & PIPE_DEVEP_MASK) | (usb_pipeout (urb->pipe)?USB_PID_OUT:USB_PID_IN);
while (len > 0) {
int pktsze = len;
if (alloc_td (uhci, &td, UHCI_PTR_DEPTH * depth_first))
goto fail_unmap_enomem;
if (pktsze > maxsze)
pktsze = maxsze;
destination ^= 1 << TD_TOKEN_TOGGLE; // toggle DATA0/1
// Status, pktsze bytes of data
fill_td (td, status, destination | ((pktsze - 1) << 21),
urb_priv->transfer_buffer_dma + (data - (char *)urb->transfer_buffer));
insert_td (uhci, qh, td, UHCI_PTR_DEPTH * depth_first); // queue 'data stage'-td in qh
data += pktsze;
len -= pktsze;
}
/* Build the final TD for control status
It's only IN if the pipe is out AND we aren't expecting data */
destination &= ~UHCI_PID;
if (usb_pipeout (urb->pipe) || (urb->transfer_buffer_length == 0))
destination |= USB_PID_IN;
else
destination |= USB_PID_OUT;
destination |= 1 << TD_TOKEN_TOGGLE; /* End in Data1 */
if (alloc_td (uhci, &td, UHCI_PTR_DEPTH))
goto fail_unmap_enomem;
status &=~TD_CTRL_SPD;
/* no limit on errors on final packet, 0 bytes of data */
fill_td (td, status | TD_CTRL_IOC, destination | (UHCI_NULL_DATA_SIZE << 21), 0);
insert_td (uhci, qh, td, UHCI_PTR_DEPTH * depth_first); // queue status td
list_add (&qh->desc_list, &urb_priv->desc_list);
queue_urb (uhci, urb); // queue _before_ inserting in desc chain
qh->hw.qh.element &= cpu_to_le32(~UHCI_PTR_TERM);
/* Start it up... put low speed first */
if (urb->dev->speed == USB_SPEED_LOW)
insert_qh (uhci, uhci->control_chain, qh, 0);
else
insert_qh (uhci, uhci->bulk_chain, qh, 0);
return 0;
fail_unmap_enomem:
delete_qh(uhci, qh);
return -ENOMEM;
}
/*-------------------------------------------------------------------*/
// For queued bulk transfers, two additional QH helpers are allocated (nqh, bqh)
// Due to the linking with other bulk urbs, it has to be locked with urb_list_lock!
static int uhci_submit_bulk_urb (struct uhci_hcd *uhci, struct urb *urb, struct urb *bulk_urb)
{
urb_priv_t *urb_priv = urb->hcpriv, *upriv, *bpriv=NULL;
uhci_desc_t *qh, *td, *nqh=NULL, *bqh=NULL, *first_td=NULL;
unsigned long destination, status;
char *data;
unsigned int pipe = urb->pipe;
int maxsze = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe));
int info, len, last;
int depth_first = bulk_depth; // UHCI descriptor chasing method
if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe)))
return -EPIPE;
queue_dbg("uhci_submit_bulk_urb: urb %p, old %p, pipe %08x, len %i",
urb,bulk_urb,urb->pipe,urb->transfer_buffer_length);
upriv = (urb_priv_t*)urb->hcpriv;
if (!bulk_urb) {
if (alloc_qh (uhci, &qh)) // get qh for this request
return -ENOMEM;
if (urb->transfer_flags & USB_QUEUE_BULK) {
if (alloc_qh(uhci, &nqh)) // placeholder for clean unlink
goto fail_unmap_enomem;
upriv->next_qh = nqh;
queue_dbg("new next qh %p",nqh);
}
}
else {
bpriv = (urb_priv_t*)bulk_urb->hcpriv;
qh = bpriv->bottom_qh; // re-use bottom qh and next qh
nqh = bpriv->next_qh;
upriv->next_qh=nqh;
upriv->prev_queued_urb=bulk_urb;
}
if (urb->transfer_flags & USB_QUEUE_BULK) {
if (alloc_qh (uhci, &bqh)) // "bottom" QH
goto fail_unmap_enomem;
set_qh_element(bqh, UHCI_PTR_TERM);
set_qh_head(bqh, nqh->dma_addr | UHCI_PTR_QH); // element
upriv->bottom_qh = bqh;
}
queue_dbg("uhci_submit_bulk: qh %p bqh %p nqh %p",qh, bqh, nqh);
/* The "pipe" thing contains the destination in bits 8--18. */
destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe);
status = TD_CTRL_ACTIVE
| ((urb->transfer_flags & USB_DISABLE_SPD) ? 0 : TD_CTRL_SPD)
| (3 << 27); /* 3 errors */
if (urb->dev->speed == USB_SPEED_LOW)
status |= TD_CTRL_LS;
/* Build the TDs for the bulk request */
len = urb->transfer_buffer_length;
data = urb->transfer_buffer;
do { // TBD: Really allow zero-length packets?
int pktsze = len;
if (alloc_td (uhci, &td, UHCI_PTR_DEPTH * depth_first))
goto fail_unmap_enomem;
if (pktsze > maxsze)
pktsze = maxsze;
// pktsze bytes of data
info = destination | (((pktsze - 1)&UHCI_NULL_DATA_SIZE) << 21) |
(uhci_get_toggle (urb) << TD_TOKEN_TOGGLE);
fill_td (td, status, info,
urb_priv->transfer_buffer_dma + (data - (char *)urb->transfer_buffer));
data += pktsze;
len -= pktsze;
// Use USB_ZERO_PACKET to finish bulk OUTs always with a zero length packet
last = (len == 0 && (usb_pipein(pipe) || pktsze < maxsze || !(urb->transfer_flags & USB_ZERO_PACKET)));
if (last)
set_td_ioc(td); // last one generates INT
insert_td (uhci, qh, td, UHCI_PTR_DEPTH * depth_first);
if (!first_td)
first_td=td;
uhci_do_toggle (urb);
} while (!last);
if (bulk_urb && bpriv) // everything went OK, link with old bulk URB
bpriv->next_queued_urb=urb;
list_add (&qh->desc_list, &urb_priv->desc_list);
if (urb->transfer_flags & USB_QUEUE_BULK)
append_qh(uhci, td, bqh, UHCI_PTR_DEPTH * depth_first);
queue_urb_unlocked (uhci, urb);
if (urb->transfer_flags & USB_QUEUE_BULK)
set_qh_element(qh, first_td->dma_addr);
else
qh->hw.qh.element &= cpu_to_le32(~UHCI_PTR_TERM); // arm QH
if (!bulk_urb) { // new bulk queue
if (urb->transfer_flags & USB_QUEUE_BULK) {
spin_lock (&uhci->td_lock); // both QHs in one go
insert_qh (uhci, uhci->chain_end, qh, 0); // Main QH
insert_qh (uhci, uhci->chain_end, nqh, 0); // Helper QH
spin_unlock (&uhci->td_lock);
}
else
insert_qh (uhci, uhci->chain_end, qh, 0);
}
//dbg("uhci_submit_bulk_urb: exit\n");
return 0;
fail_unmap_enomem:
delete_qh(uhci, qh);
if (bqh)
delete_qh(uhci, bqh);
if (!bulk_urb && nqh)
delete_qh(uhci, nqh);
return -ENOMEM;
}
/*---------------------------------------------------------------------------*/
// submits USB interrupt (ie. polling ;-)
// ASAP-flag set implicitely
// if period==0, the transfer is only done once
static int uhci_submit_int_urb (struct uhci_hcd *uhci, struct urb *urb)
{
urb_priv_t *urb_priv = urb->hcpriv;
int nint;
uhci_desc_t *td;
int status, destination;
int info;
unsigned int pipe = urb->pipe;
if (urb->interval == 0)
nint = 0;
else {
// log2-function (urb->interval already 2^n)
nint = ffs(urb->interval);
if (nint>7)
nint=7;
}
dbg("INT-interval %i, chain %i", urb->interval, nint);
// remember start frame, just in case...
urb->start_frame = UHCI_GET_CURRENT_FRAME (uhci) & 1023;
urb->number_of_packets = 1; // INT allows only one packet
if (alloc_td (uhci, &td, UHCI_PTR_DEPTH))
return -ENOMEM;
status = TD_CTRL_ACTIVE | TD_CTRL_IOC
| (urb->transfer_flags & USB_DISABLE_SPD ? 0 : TD_CTRL_SPD)
| (3 << 27);
if (urb->dev->speed == USB_SPEED_LOW)
status |= TD_CTRL_LS;
destination = (pipe & PIPE_DEVEP_MASK) | usb_packetid (pipe) |
(((urb->transfer_buffer_length - 1) & 0x7ff) << 21);
info = destination | (uhci_get_toggle (urb) << TD_TOKEN_TOGGLE);
fill_td (td, status, info, urb_priv->transfer_buffer_dma);
list_add_tail (&td->desc_list, &urb_priv->desc_list);
queue_urb (uhci, urb);
insert_td_horizontal (uhci, uhci->int_chain[nint], td); // store in INT-TDs
uhci_do_toggle (urb);
return 0;
}
/*###########################################################################*/
// ISOCHRONOUS TRANSFERS
/*###########################################################################*/
// In case of ASAP iso transfer, search the URB-list for already queued URBs
// for this EP and calculate the earliest start frame for the new
// URB (easy seamless URB continuation!)
static int find_iso_limits (struct uhci_hcd *uhci, struct urb *urb, unsigned int *start, unsigned int *end)
{
struct urb *u, *last_urb = NULL;
urb_priv_t *priv;
struct list_head *p;
int ret=-1;
unsigned long flags;
spin_lock_irqsave (&uhci->urb_list_lock, flags);
p=uhci->urb_list.prev;
for (; p != &uhci->urb_list; p = p->prev) {
priv = list_entry (p, urb_priv_t, urb_list);
u = priv->urb;
// look for pending URBs with identical pipe handle
// works only because iso doesn't toggle the data bit!
if ((urb->pipe == u->pipe) && (urb->dev == u->dev) && (u->status == -EINPROGRESS)) {
if (!last_urb)
*start = u->start_frame;
last_urb = u;
}
}
if (last_urb) {
*end = (last_urb->start_frame + last_urb->number_of_packets*last_urb->interval) & 1023;
ret=0;
}
spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
return ret;
}
/*-------------------------------------------------------------------*/
// adjust start_frame according to scheduling constraints (ASAP etc)
static int iso_find_start (struct uhci_hcd *uhci, struct urb *urb)
{
unsigned int now;
unsigned int start_limit = 0, stop_limit = 0, queued_size, number_of_frames;
int limits;
now = UHCI_GET_CURRENT_FRAME (uhci) & 1023;
number_of_frames = (unsigned) (urb->number_of_packets*urb->interval);
if ( number_of_frames > 900)
return -EFBIG;
limits = find_iso_limits (uhci, urb, &start_limit, &stop_limit);
queued_size = (stop_limit - start_limit) & 1023;
if (urb->transfer_flags & USB_ISO_ASAP) {
// first iso
if (limits) {
// 10ms setup should be enough //FIXME!
urb->start_frame = (now + 10) & 1023;
}
else {
urb->start_frame = stop_limit; // seamless linkage
if (((now - urb->start_frame) & 1023) <= (unsigned) number_of_frames) {
info("iso_find_start: gap in seamless isochronous scheduling");
dbg("iso_find_start: now %u start_frame %u number_of_packets %u interval %u pipe 0x%08x",
now, urb->start_frame, urb->number_of_packets, urb->interval, urb->pipe);
urb->start_frame = (now + 5) & 1023; // 5ms setup should be enough
}
}
}
else {
urb->start_frame &= 1023;
if (((now - urb->start_frame) & 1023) < number_of_frames) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -