📄 ohci-q.c
字号:
/*
* OHCI HCD (Host Controller Driver) for USB.
*
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
*
* This file is licenced under the GPL.
* $Id: ohci-q.c,v 1.8 2002/03/27 20:57:01 dbrownell Exp $
*/
static void urb_free_priv (struct ohci_hcd *hc, urb_priv_t *urb_priv)
{
int last = urb_priv->length - 1;
if (last >= 0) {
int i;
struct td *td = urb_priv->td [0];
#ifdef CONFIG_PCI
int len = td->urb->transfer_buffer_length;
int dir = usb_pipeout (td->urb->pipe)
? PCI_DMA_TODEVICE
: PCI_DMA_FROMDEVICE;
/* unmap CTRL URB setup buffer (always td 0) */
if (usb_pipecontrol (td->urb->pipe)) {
pci_unmap_single (hc->hcd.pdev,
td->data_dma, 8, PCI_DMA_TODEVICE);
/* CTRL data buffer starts at td 1 if len > 0 */
if (len && last > 0)
td = urb_priv->td [1];
}
/* else: ISOC, BULK, INTR data buffer starts at td 0 */
/* unmap data buffer */
if (len && td->data_dma)
pci_unmap_single (hc->hcd.pdev,
td->data_dma, len, dir);
#else
# warning "assuming no buffer unmapping is needed"
#endif
for (i = 0; i <= last; i++) {
td = urb_priv->td [i];
if (td)
td_free (hc, td);
}
}
kfree (urb_priv);
}
/*-------------------------------------------------------------------------*/
/*
* URB goes back to driver, and isn't reissued.
* It's completely gone from HC data structures.
* PRECONDITION: no locks held (Giveback can call into HCD.)
*/
static void finish_urb (struct ohci_hcd *ohci, struct urb *urb)
{
unsigned long flags;
#ifdef DEBUG
if (!urb->hcpriv) {
err ("already unlinked!");
BUG ();
}
#endif
urb_free_priv (ohci, urb->hcpriv);
urb->hcpriv = NULL;
spin_lock_irqsave (&urb->lock, flags);
if (likely (urb->status == -EINPROGRESS))
urb->status = 0;
spin_unlock_irqrestore (&urb->lock, flags);
#ifdef OHCI_VERBOSE_DEBUG
urb_print (urb, "RET", usb_pipeout (urb->pipe));
#endif
usb_hcd_giveback_urb (&ohci->hcd, urb);
}
static void td_submit_urb (struct urb *urb);
/* Report interrupt transfer completion, maybe reissue */
static void intr_resub (struct ohci_hcd *hc, struct urb *urb)
{
urb_priv_t *urb_priv = urb->hcpriv;
unsigned long flags;
#ifdef CONFIG_PCI
// FIXME rewrite this resubmit path. use pci_dma_sync_single()
// and requeue more cheaply, and only if needed.
// Better yet ... abolish the notion of automagic resubmission.
pci_unmap_single (hc->hcd.pdev,
urb_priv->td [0]->data_dma,
urb->transfer_buffer_length,
usb_pipeout (urb->pipe)
? PCI_DMA_TODEVICE
: PCI_DMA_FROMDEVICE);
#endif
/* FIXME: MP race. If another CPU partially unlinks
* this URB (urb->status was updated, hasn't yet told
* us to dequeue) before we call complete() here, an
* extra "unlinked" completion will be reported...
*/
spin_lock_irqsave (&urb->lock, flags);
if (likely (urb->status == -EINPROGRESS))
urb->status = 0;
spin_unlock_irqrestore (&urb->lock, flags);
#ifdef OHCI_VERBOSE_DEBUG
urb_print (urb, "INTR", usb_pipeout (urb->pipe));
#endif
urb->complete (urb);
/* always requeued, but ED_SKIP if complete() unlinks.
* EDs are removed from periodic table only at SOF intr.
*/
urb->actual_length = 0;
spin_lock_irqsave (&urb->lock, flags);
if (urb_priv->state != URB_DEL)
urb->status = -EINPROGRESS;
spin_unlock (&urb->lock);
spin_lock (&hc->lock);
td_submit_urb (urb);
spin_unlock_irqrestore (&hc->lock, flags);
}
/*-------------------------------------------------------------------------*
* ED handling functions
*-------------------------------------------------------------------------*/
/* search for the right branch to insert an interrupt ed into the int tree
* do some load balancing;
* returns the branch and
* sets the interval to interval = 2^integer (ld (interval))
*/
static int ep_int_balance (struct ohci_hcd *ohci, int interval, int load)
{
int i, branch = 0;
/* search for the least loaded interrupt endpoint branch */
for (i = 0; i < NUM_INTS ; i++)
if (ohci->ohci_int_load [branch] > ohci->ohci_int_load [i])
branch = i;
branch = branch % interval;
for (i = branch; i < NUM_INTS; 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++)
continue;
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 (struct ohci_hcd *ohci, struct ed *edi)
{
int int_branch, i;
int inter, interval, load;
__u32 *ed_p;
volatile struct ed *ed = edi;
ed->state = ED_OPER;
switch (ed->type) {
case PIPE_CONTROL:
ed->hwNextED = 0;
if (ohci->ed_controltail == NULL) {
writel (ed->dma, &ohci->regs->ed_controlhead);
} else {
ohci->ed_controltail->hwNextED = cpu_to_le32 (ed->dma);
}
ed->ed_prev = ohci->ed_controltail;
if (!ohci->ed_controltail
&& !ohci->ed_rm_list [0]
&& !ohci->ed_rm_list [1]
&& !ohci->sleeping
) {
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 (ed->dma, &ohci->regs->ed_bulkhead);
} else {
ohci->ed_bulktail->hwNextED = cpu_to_le32 (ed->dma);
}
ed->ed_prev = ohci->ed_bulktail;
if (!ohci->ed_bulktail
&& !ohci->ed_rm_list [0]
&& !ohci->ed_rm_list [1]
&& !ohci->sleeping
) {
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_balance (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) && ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval >= interval);
ed_p = & ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED))
inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval);
ed->hwNextED = *ed_p;
*ed_p = cpu_to_le32 (ed->dma);
}
#ifdef OHCI_VERBOSE_DEBUG
ohci_dump_periodic (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 (ed->dma);
ed->ed_prev = ohci->ed_isotail;
} else {
for ( i = 0; i < NUM_INTS; i += inter) {
inter = 1;
for (ed_p = & (ohci->hcca->int_table [ep_rev (5, i)]);
*ed_p != 0;
ed_p = & ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED))
inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval);
*ed_p = cpu_to_le32 (ed->dma);
}
ed->ed_prev = NULL;
}
ohci->ed_isotail = edi;
#ifdef OHCI_VERBOSE_DEBUG
ohci_dump_periodic (ohci, "LINK_ISO");
#endif
break;
}
return 0;
}
/*-------------------------------------------------------------------------*/
/* scan the periodic table to find and unlink this ED */
static void periodic_unlink (
struct ohci_hcd *ohci,
struct ed *ed,
unsigned index,
unsigned period
) {
for (; index < NUM_INTS; index += period) {
__u32 *ed_p = &ohci->hcca->int_table [index];
while (*ed_p != 0) {
if ((dma_to_ed (ohci, le32_to_cpup (ed_p))) == ed) {
*ed_p = ed->hwNextED;
break;
}
ed_p = & ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED);
}
}
}
/* unlink an ed from one of the HC chains.
* just the link to the ed is unlinked.
* the link from the ed still points to another operational ed or 0
* so the HC can eventually finish the processing of the unlinked ed
*/
static int ep_unlink (struct ohci_hcd *ohci, struct ed *ed)
{
int i;
ed->hwINFO |= ED_SKIP;
switch (ed->type) {
case PIPE_CONTROL:
if (ed->ed_prev == NULL) {
if (!ed->hwNextED) {
ohci->hc_control &= ~OHCI_CTRL_CLE;
writel (ohci->hc_control, &ohci->regs->control);
}
writel (le32_to_cpup (&ed->hwNextED),
&ohci->regs->ed_controlhead);
} else {
ed->ed_prev->hwNextED = ed->hwNextED;
}
if (ohci->ed_controltail == ed) {
ohci->ed_controltail = ed->ed_prev;
} else {
(dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED)))
->ed_prev = ed->ed_prev;
}
break;
case PIPE_BULK:
if (ed->ed_prev == NULL) {
if (!ed->hwNextED) {
ohci->hc_control &= ~OHCI_CTRL_BLE;
writel (ohci->hc_control, &ohci->regs->control);
}
writel (le32_to_cpup (&ed->hwNextED),
&ohci->regs->ed_bulkhead);
} else {
ed->ed_prev->hwNextED = ed->hwNextED;
}
if (ohci->ed_bulktail == ed) {
ohci->ed_bulktail = ed->ed_prev;
} else {
(dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED)))
->ed_prev = ed->ed_prev;
}
break;
case PIPE_INTERRUPT:
periodic_unlink (ohci, ed, ed->int_branch, ed->int_interval);
for (i = ed->int_branch; i < NUM_INTS; i += ed->int_interval)
ohci->ohci_int_load [i] -= ed->int_load;
#ifdef OHCI_VERBOSE_DEBUG
ohci_dump_periodic (ohci, "UNLINK_INT");
#endif
break;
case PIPE_ISOCHRONOUS:
if (ohci->ed_isotail == ed)
ohci->ed_isotail = ed->ed_prev;
if (ed->hwNextED != 0)
(dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED)))
->ed_prev = ed->ed_prev;
if (ed->ed_prev != NULL)
ed->ed_prev->hwNextED = ed->hwNextED;
else
periodic_unlink (ohci, ed, 0, 1);
#ifdef OHCI_VERBOSE_DEBUG
ohci_dump_periodic (ohci, "UNLINK_ISO");
#endif
break;
}
/* FIXME ED's "unlink" state is indeterminate;
* the HC might still be caching it (till SOF).
*/
ed->state = ED_UNLINK;
return 0;
}
/*-------------------------------------------------------------------------*/
/* (re)init an endpoint; this _should_ be done once at the
* usb_set_configuration command, but the USB stack is a bit stateless
* so we do it at every transaction.
* if the state of the ed is ED_NEW then a dummy td is added and the
* state is changed to ED_UNLINK
* in all other cases the state is left unchanged
* the ed info fields are set even though most of them should
* not change
*/
static struct ed *ep_add_ed (
struct usb_device *udev,
unsigned int pipe,
int interval,
int load,
int mem_flags
) {
struct ohci_hcd *ohci = hcd_to_ohci (udev->bus->hcpriv);
struct hcd_dev *dev = (struct hcd_dev *) udev->hcpriv;
struct td *td;
struct ed *ed;
unsigned ep;
unsigned long flags;
spin_lock_irqsave (&ohci->lock, flags);
ep = usb_pipeendpoint (pipe) << 1;
if (!usb_pipecontrol (pipe) && usb_pipeout (pipe))
ep |= 1;
if (!(ed = dev->ep [ep])) {
ed = ed_alloc (ohci, SLAB_ATOMIC);
if (!ed) {
/* out of memory */
spin_unlock_irqrestore (&ohci->lock, flags);
return NULL;
}
dev->ep [ep] = ed;
}
if (ed->state & ED_URB_DEL) {
/* pending unlink request */
spin_unlock_irqrestore (&ohci->lock, flags);
return NULL;
}
if (ed->state == ED_NEW) {
ed->hwINFO = ED_SKIP;
/* dummy td; end of td list for ed */
td = td_alloc (ohci, SLAB_ATOMIC);
if (!td) {
/* out of memory */
spin_unlock_irqrestore (&ohci->lock, flags);
return NULL;
}
ed->hwTailP = cpu_to_le32 (td->td_dma);
ed->hwHeadP = ed->hwTailP;
ed->state = ED_UNLINK;
ed->type = usb_pipetype (pipe);
}
// FIXME: don't do this if it's linked to the HC,
// we might clobber data toggle or other state ...
ed->hwINFO = cpu_to_le32 (usb_pipedevice (pipe)
| usb_pipeendpoint (pipe) << 7
| (usb_pipeisoc (pipe)? 0x8000: 0)
| (usb_pipecontrol (pipe)
? 0: (usb_pipeout (pipe)? 0x800: 0x1000))
| (udev->speed == USB_SPEED_LOW) << 13
| usb_maxpacket (udev, pipe, usb_pipeout (pipe))
<< 16);
if (ed->type == PIPE_INTERRUPT && ed->state == ED_UNLINK) {
ed->int_period = interval;
ed->int_load = load;
}
spin_unlock_irqrestore (&ohci->lock, flags);
return ed;
}
/*-------------------------------------------------------------------------*/
/* request unlinking of an endpoint from an operational HC.
* put the ep on the rm_list and stop the bulk or ctrl list
* real work is done at the next start frame (SF) hardware interrupt
*/
static void ed_unlink (struct usb_device *usb_dev, struct ed *ed)
{
unsigned int frame;
struct ohci_hcd *ohci = hcd_to_ohci (usb_dev->bus->hcpriv);
/* already pending? */
if (ed->state & ED_URB_DEL)
return;
ed->state |= ED_URB_DEL;
ed->hwINFO |= ED_SKIP;
switch (ed->type) {
case PIPE_CONTROL: /* stop control list */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -