📄 usb-uhci-mem.c
字号:
/*
UHCI HCD (Host Controller Driver) for USB
UHCI memory allocation and basic descriptor handling
(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-mem.c,v 1.3 2002/05/25 16:42:41 acher Exp $
*/
/*###########################################################################*/
// UHCI STRUCTURE
/*###########################################################################*/
static struct usb_hcd *uhci_hcd_alloc (void)
{
struct uhci_hcd *uhci;
int len;
len=sizeof (struct uhci_hcd);
uhci = (struct uhci_hcd *) kmalloc (len, GFP_KERNEL);
if (uhci == 0)
return NULL;
memset (uhci, 0, len);
init_dbg("uhci @ %p, hcd @ %p",uhci, &(uhci->hcd));
INIT_LIST_HEAD (&uhci->free_desc_qh);
INIT_LIST_HEAD (&uhci->free_desc_td);
INIT_LIST_HEAD (&uhci->urb_list);
INIT_LIST_HEAD (&uhci->urb_unlinked);
spin_lock_init (&uhci->urb_list_lock);
spin_lock_init (&uhci->qh_lock);
spin_lock_init (&uhci->td_lock);
atomic_set(&uhci->avoid_bulk, 0);
return &(uhci->hcd);
}
/*-------------------------------------------------------------------*/
static void uhci_hcd_free (struct usb_hcd *hcd)
{
kfree (hcd_to_uhci (hcd));
}
/*###########################################################################*/
// DMA/PCI CONSISTENCY
/*###########################################################################*/
static void uhci_urb_dma_sync(struct uhci_hcd *uhci, struct urb *urb, urb_priv_t *urb_priv)
{
if (urb_priv->setup_packet_dma)
pci_dma_sync_single(uhci->uhci_pci, urb_priv->setup_packet_dma,
sizeof(struct usb_ctrlrequest), PCI_DMA_TODEVICE);
if (urb_priv->transfer_buffer_dma)
pci_dma_sync_single(uhci->uhci_pci, urb_priv->transfer_buffer_dma,
urb->transfer_buffer_length,
usb_pipein(urb->pipe) ?
PCI_DMA_FROMDEVICE :
PCI_DMA_TODEVICE);
}
/*-------------------------------------------------------------------*/
static void uhci_urb_dma_unmap(struct uhci_hcd *uhci, struct urb *urb, urb_priv_t *urb_priv)
{
if (urb_priv->setup_packet_dma) {
pci_unmap_single(uhci->uhci_pci, urb_priv->setup_packet_dma,
sizeof(struct usb_ctrlrequest), PCI_DMA_TODEVICE);
urb_priv->setup_packet_dma = 0;
}
if (urb_priv->transfer_buffer_dma) {
pci_unmap_single(uhci->uhci_pci, urb_priv->transfer_buffer_dma,
urb->transfer_buffer_length,
usb_pipein(urb->pipe) ?
PCI_DMA_FROMDEVICE :
PCI_DMA_TODEVICE);
urb_priv->transfer_buffer_dma = 0;
}
}
/*###########################################################################*/
// TRANSFER DESCRIPTORS (TD)
/*###########################################################################*/
static void fill_td (uhci_desc_t *td, int status, int info, __u32 buffer)
{
td->hw.td.status = cpu_to_le32(status);
td->hw.td.info = cpu_to_le32(info);
td->hw.td.buffer = cpu_to_le32(buffer);
}
/*-------------------------------------------------------------------*/
static int alloc_td (struct uhci_hcd *uhci, uhci_desc_t ** new, int flags)
{
dma_addr_t dma_handle;
*new = pci_pool_alloc(uhci->desc_pool, GFP_DMA | GFP_ATOMIC, &dma_handle);
if (!*new)
return -ENOMEM;
memset (*new, 0, sizeof (uhci_desc_t));
(*new)->dma_addr = dma_handle;
set_td_link((*new), UHCI_PTR_TERM | (flags & UHCI_PTR_BITS)); // last by default
(*new)->type = TD_TYPE;
mb();
INIT_LIST_HEAD (&(*new)->vertical);
INIT_LIST_HEAD (&(*new)->horizontal);
return 0;
}
/*-------------------------------------------------------------------*/
/* insert td at last position in td-list of qh (vertical) */
static int insert_td (struct uhci_hcd *uhci, uhci_desc_t *qh, uhci_desc_t* new, int flags)
{
uhci_desc_t *prev;
unsigned long cpuflags;
spin_lock_irqsave (&uhci->td_lock, cpuflags);
list_add_tail (&new->vertical, &qh->vertical);
prev = list_entry (new->vertical.prev, uhci_desc_t, vertical);
if (qh == prev ) {
// virgin qh without any tds
set_qh_element(qh, new->dma_addr | UHCI_PTR_TERM);
}
else {
// already tds inserted, implicitely remove TERM bit of prev
set_td_link(prev, new->dma_addr | (flags & UHCI_PTR_DEPTH));
}
mb();
spin_unlock_irqrestore (&uhci->td_lock, cpuflags);
return 0;
}
/*-------------------------------------------------------------------*/
/* insert new_td after td (horizontal) */
static int insert_td_horizontal (struct uhci_hcd *uhci, uhci_desc_t *td, uhci_desc_t* new)
{
uhci_desc_t *next;
unsigned long flags;
spin_lock_irqsave (&uhci->td_lock, flags);
next = list_entry (td->horizontal.next, uhci_desc_t, horizontal);
list_add (&new->horizontal, &td->horizontal);
new->hw.td.link = td->hw.td.link;
mb();
set_td_link(td, new->dma_addr);
mb();
spin_unlock_irqrestore (&uhci->td_lock, flags);
return 0;
}
/*-------------------------------------------------------------------*/
static int unlink_td (struct uhci_hcd *uhci, uhci_desc_t *element, int phys_unlink)
{
uhci_desc_t *next, *prev;
int dir = 0;
unsigned long flags;
spin_lock_irqsave (&uhci->td_lock, flags);
next = list_entry (element->vertical.next, uhci_desc_t, vertical);
if (next == element) {
dir = 1;
prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal);
}
else
prev = list_entry (element->vertical.prev, uhci_desc_t, vertical);
if (phys_unlink) {
// really remove HW linking
if (prev->type == TD_TYPE) {
prev->hw.td.link = element->hw.td.link;
}
else
prev->hw.qh.element = element->hw.td.link;
}
mb ();
if (dir == 0)
list_del (&element->vertical);
else
list_del (&element->horizontal);
spin_unlock_irqrestore (&uhci->td_lock, flags);
return 0;
}
/*###########################################################################*/
// QUEUE HEADS (QH)
/*###########################################################################*/
// Allocates qh element
static int alloc_qh (struct uhci_hcd *uhci, uhci_desc_t ** new)
{
dma_addr_t dma_handle;
*new = pci_pool_alloc(uhci->desc_pool, GFP_DMA | GFP_ATOMIC, &dma_handle);
if (!*new)
return -ENOMEM;
memset (*new, 0, sizeof (uhci_desc_t));
(*new)->dma_addr = dma_handle;
set_qh_head(*new, UHCI_PTR_TERM);
set_qh_element(*new, UHCI_PTR_TERM);
(*new)->type = QH_TYPE;
mb();
INIT_LIST_HEAD (&(*new)->horizontal);
INIT_LIST_HEAD (&(*new)->vertical);
dbg("Allocated qh @ %p", *new);
return 0;
}
/*-------------------------------------------------------------------*/
// inserts new qh before/after the qh at pos
// flags: 0: insert before pos, 1: insert after pos (for low speed transfers)
static int insert_qh (struct uhci_hcd *uhci, uhci_desc_t *pos, uhci_desc_t *new, int order)
{
uhci_desc_t *old;
unsigned long flags;
spin_lock_irqsave (&uhci->qh_lock, flags);
if (!order) {
// (OLD) (POS) -> (OLD) (NEW) (POS)
old = list_entry (pos->horizontal.prev, uhci_desc_t, horizontal);
list_add_tail (&new->horizontal, &pos->horizontal);
set_qh_head(new, MAKE_QH_ADDR (pos)) ;
mb();
if (!(old->hw.qh.head & cpu_to_le32(UHCI_PTR_TERM)))
set_qh_head(old, MAKE_QH_ADDR (new)) ;
}
else {
// (POS) (OLD) -> (POS) (NEW) (OLD)
old = list_entry (pos->horizontal.next, uhci_desc_t, horizontal);
list_add (&new->horizontal, &pos->horizontal);
set_qh_head(new, MAKE_QH_ADDR (old));
mb();
set_qh_head(pos, MAKE_QH_ADDR (new)) ;
}
mb ();
spin_unlock_irqrestore (&uhci->qh_lock, flags);
return 0;
}
/*-------------------------------------------------------------------*/
// append a qh to td.link physically, the SW linkage is not affected
static void append_qh(struct uhci_hcd *uhci, uhci_desc_t *td, uhci_desc_t* qh, int flags)
{
unsigned long cpuflags;
spin_lock_irqsave (&uhci->td_lock, cpuflags);
set_td_link(td, qh->dma_addr | (flags & UHCI_PTR_DEPTH) | UHCI_PTR_QH);
mb();
spin_unlock_irqrestore (&uhci->td_lock, cpuflags);
}
/*-------------------------------------------------------------------*/
static int unlink_qh (struct uhci_hcd *uhci, uhci_desc_t *element)
{
uhci_desc_t *prev;
unsigned long flags;
__u32 old_head;
spin_lock_irqsave (&uhci->qh_lock, flags);
prev = list_entry (element->horizontal.prev, uhci_desc_t, horizontal);
old_head = element->hw.qh.head;
element->hw.qh.head = UHCI_PTR_TERM;
mb();
prev->hw.qh.head = old_head;
dbg("unlink qh %p, pqh %p, nxqh %p, to %08x", element, prev,
list_entry (element->horizontal.next, uhci_desc_t, horizontal),
le32_to_cpu(element->hw.qh.head) &~15);
list_del(&element->horizontal);
mb ();
spin_unlock_irqrestore (&uhci->qh_lock, flags);
return 0;
}
/*-------------------------------------------------------------------*/
static int delete_desc (struct uhci_hcd *uhci, uhci_desc_t *element)
{
pci_pool_free(uhci->desc_pool, element, element->dma_addr);
return 0;
}
/*-------------------------------------------------------------------*/
static int delete_qh (struct uhci_hcd *uhci, uhci_desc_t *qh)
{
uhci_desc_t *td;
struct list_head *p;
int n=0;
list_del (&qh->horizontal);
while ((p = qh->vertical.next) != &qh->vertical && n<10000) {
td = list_entry (p, uhci_desc_t, vertical);
dbg("unlink td @ %p",td);
unlink_td (uhci, td, 0); // no physical unlink
delete_desc (uhci, td);
n++;
}
// never trust any software, not even your own...
if (n>=10000)
err("delete_qh: Garbage in QH list, giving up");
delete_desc (uhci, qh);
return 0;
}
/*###########################################################################*/
// DESCRIPTOR CHAINING HELPERS
/*###########################################################################*/
static void clean_td_chain (struct uhci_hcd *uhci, uhci_desc_t *td)
{
struct list_head *p;
uhci_desc_t *td1;
if (!td)
return;
while ((p = td->horizontal.next) != &td->horizontal) {
td1 = list_entry (p, uhci_desc_t, horizontal);
delete_desc (uhci, td1);
}
delete_desc (uhci, td);
}
/*-------------------------------------------------------------------*/
// Cleans up collected QHs/TDs, but not more than 100 in one go
void clean_descs(struct uhci_hcd *uhci, int force)
{
struct list_head *q;
uhci_desc_t *qh,*td;
int now=UHCI_GET_CURRENT_FRAME(uhci), n=0;
q=uhci->free_desc_qh.prev;
while (q != &uhci->free_desc_qh && (force || n<100)) {
qh = list_entry (q, uhci_desc_t, horizontal);
q=qh->horizontal.prev;
if ((qh->last_used!=now) || force) {
delete_qh(uhci,qh);
}
n++;
}
q=uhci->free_desc_td.prev;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -