⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 uhci-hcd.c

📁 ReactOs中的USB驱动
💻 C
📖 第 1 页 / 共 5 页
字号:
/*
 * Universal Host Controller Interface driver for USB.
 *
 * Maintainer: Johannes Erdfelt <johannes@erdfelt.com>
 *
 * (C) Copyright 1999 Linus Torvalds
 * (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com
 * (C) Copyright 1999 Randy Dunlap
 * (C) Copyright 1999 Georg Acher, acher@in.tum.de
 * (C) Copyright 1999 Deti Fliegl, deti@fliegl.de
 * (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch
 * (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at
 * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface
 *               support from usb-ohci.c by Adam Richter, adam@yggdrasil.com).
 * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c)
 *
 * Intel documents this fairly well, and as far as I know there
 * are no royalties or anything like that, but even so there are
 * people who decided that they want to do the same thing in a
 * completely different way.
 *
 * WARNING! The USB documentation is downright evil. Most of it
 * is just crap, written by a committee. You're better off ignoring
 * most of it, the important stuff is:
 *  - the low-level protocol (fairly simple but lots of small details)
 *  - working around the horridness of the rest
 */

#if 0
#include <linux/config.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/proc_fs.h>
#endif

//#ifdef CONFIG_USB_DEBUG
#define DEBUG
//#else
//#undef DEBUG
//#endif

#if 0
#include <linux/usb.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>
#endif

#include "uhci_config.h"
#include "../usb_wrapper.h"
#include "../../usbport/hcd.h"
#include "uhci-hcd.h"

#if 0
#include <linux/pm.h>
#endif

/*
 * Version Information
 */
#define DRIVER_VERSION "v2.1"
#define DRIVER_AUTHOR "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber"
#define DRIVER_DESC "USB Universal Host Controller Interface driver"

/*
 * debug = 0, no debugging messages
 * debug = 1, dump failed URB's except for stalls
 * debug = 2, dump all failed URB's (including stalls)
 *            show all queues in /proc/driver/uhci/[pci_addr]
 * debug = 3, show all TD's in URB's when dumping
 */
#ifdef DEBUG
static int debug = 3;
#else
static int debug = 2;
#endif
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug level");
static char *errbuf;
#define ERRBUF_LEN    (PAGE_SIZE * 8)

#include "uhci-hub.c"
#include "uhci-debug.c"

static kmem_cache_t *uhci_up_cachep;	/* urb_priv */

static int uhci_get_current_frame_number(struct uhci_hcd *uhci);
static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb);
static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb);

static void hc_state_transitions(struct uhci_hcd *uhci);

/* If a transfer is still active after this much time, turn off FSBR */
#define IDLE_TIMEOUT	(HZ / 20)	/* 50 ms */
#define FSBR_DELAY	(HZ / 20)	/* 50 ms */

/* When we timeout an idle transfer for FSBR, we'll switch it over to */
/* depth first traversal. We'll do it in groups of this number of TD's */
/* to make sure it doesn't hog all of the bandwidth */
#define DEPTH_INTERVAL 5

/*
 * Technically, updating td->status here is a race, but it's not really a
 * problem. The worst that can happen is that we set the IOC bit again
 * generating a spurious interrupt. We could fix this by creating another
 * QH and leaving the IOC bit always set, but then we would have to play
 * games with the FSBR code to make sure we get the correct order in all
 * the cases. I don't think it's worth the effort
 */
static inline void uhci_set_next_interrupt(struct uhci_hcd *uhci)
{
	unsigned long flags;

	spin_lock_irqsave(&uhci->frame_list_lock, flags);
	uhci->term_td->status |= cpu_to_le32(TD_CTRL_IOC);
	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
}

static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci)
{
	unsigned long flags;

	spin_lock_irqsave(&uhci->frame_list_lock, flags);
	uhci->term_td->status &= ~cpu_to_le32(TD_CTRL_IOC);
	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
}

static inline void uhci_add_complete(struct uhci_hcd *uhci, struct urb *urb)
{
	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
	unsigned long flags;

	spin_lock_irqsave(&uhci->complete_list_lock, flags);
	list_add_tail(&urbp->complete_list, &uhci->complete_list);
	spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
}

static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci, struct usb_device *dev)
{
	dma_addr_t dma_handle;
	struct uhci_td *td;

	td = pci_pool_alloc(uhci->td_pool, GFP_ATOMIC, &dma_handle);
	if (!td)
		return NULL;

	td->dma_handle = dma_handle;

	td->link = UHCI_PTR_TERM;
	td->buffer = 0;

	td->frame = -1;
	td->dev = dev;

	INIT_LIST_HEAD(&td->list);
	INIT_LIST_HEAD(&td->fl_list);

	usb_get_dev(dev);

	return td;
}

static inline void uhci_fill_td(struct uhci_td *td, __u32 status,
		__u32 token, __u32 buffer)
{
	td->status = cpu_to_le32(status);
	td->token = cpu_to_le32(token);
	td->buffer = cpu_to_le32(buffer);
}

/*
 * We insert Isochronous URB's directly into the frame list at the beginning
 */
static void uhci_insert_td_frame_list(struct uhci_hcd *uhci, struct uhci_td *td, unsigned framenum)
{
	unsigned long flags;

	framenum %= UHCI_NUMFRAMES;

	spin_lock_irqsave(&uhci->frame_list_lock, flags);

	td->frame = framenum;

	/* Is there a TD already mapped there? */
	if (uhci->fl->frame_cpu[framenum]) {
		struct uhci_td *ftd, *ltd;

		ftd = uhci->fl->frame_cpu[framenum];
		ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list);

		list_add_tail(&td->fl_list, &ftd->fl_list);

		td->link = ltd->link;
		mb();
		ltd->link = cpu_to_le32(td->dma_handle);
	} else {
		td->link = uhci->fl->frame[framenum];
		mb();
		uhci->fl->frame[framenum] = cpu_to_le32(td->dma_handle);
		uhci->fl->frame_cpu[framenum] = td;
	}

	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
}

static void uhci_remove_td(struct uhci_hcd *uhci, struct uhci_td *td)
{
	unsigned long flags;

	/* If it's not inserted, don't remove it */
	spin_lock_irqsave(&uhci->frame_list_lock, flags);
	if (td->frame == -1 && list_empty(&td->fl_list))
		goto out;

	if (td->frame != -1 && uhci->fl->frame_cpu[td->frame] == td) {
		if (list_empty(&td->fl_list)) {
			uhci->fl->frame[td->frame] = td->link;
			uhci->fl->frame_cpu[td->frame] = NULL;
		} else {
			struct uhci_td *ntd;

			ntd = list_entry(td->fl_list.next, struct uhci_td, fl_list);
			uhci->fl->frame[td->frame] = cpu_to_le32(ntd->dma_handle);
			uhci->fl->frame_cpu[td->frame] = ntd;
		}
	} else {
		struct uhci_td *ptd;

		ptd = list_entry(td->fl_list.prev, struct uhci_td, fl_list);
		ptd->link = td->link;
	}

	mb();
	td->link = UHCI_PTR_TERM;

	list_del_init(&td->fl_list);
	td->frame = -1;

out:
	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
}

/*
 * Inserts a td into qh list at the top.
 */
static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct urb *urb, u32 breadth)
{
	struct list_head *tmp, *head;
	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
	struct uhci_td *td, *ptd;

	if (list_empty(&urbp->td_list))
		return;

	head = &urbp->td_list;
	tmp = head->next;

	/* Ordering isn't important here yet since the QH hasn't been */
	/*  inserted into the schedule yet */
	td = list_entry(tmp, struct uhci_td, list);

	/* Add the first TD to the QH element pointer */
	qh->element = cpu_to_le32(td->dma_handle) | breadth;

	ptd = td;

	/* Then link the rest of the TD's */
	tmp = tmp->next;
	while (tmp != head) {
		td = list_entry(tmp, struct uhci_td, list);

		tmp = tmp->next;

		ptd->link = cpu_to_le32(td->dma_handle) | breadth;

		ptd = td;
	}

	ptd->link = UHCI_PTR_TERM;
}

static void uhci_free_td(struct uhci_hcd *uhci, struct uhci_td *td)
{
	if (!list_empty(&td->list))
		dbg("td %p is still in list!", td);
	if (!list_empty(&td->fl_list))
		dbg("td %p is still in fl_list!", td);

	if (td->dev)
		usb_put_dev(td->dev);

	pci_pool_free(uhci->td_pool, td, td->dma_handle);
}

static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci, struct usb_device *dev)
{
	dma_addr_t dma_handle;
	struct uhci_qh *qh;

	qh = pci_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle);
	if (!qh)
		return NULL;

	qh->dma_handle = dma_handle;

	qh->element = UHCI_PTR_TERM;
	qh->link = UHCI_PTR_TERM;

	qh->dev = dev;
	qh->urbp = NULL;

	INIT_LIST_HEAD(&qh->list);
	INIT_LIST_HEAD(&qh->remove_list);

	usb_get_dev(dev);

	return qh;
}

static void uhci_free_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
	if (!list_empty(&qh->list))
		dbg("qh %p list not empty!", qh);
	if (!list_empty(&qh->remove_list))
		dbg("qh %p still in remove_list!", qh);

	if (qh->dev)
		usb_put_dev(qh->dev);

	pci_pool_free(uhci->qh_pool, qh, qh->dma_handle);
}

/*
 * Append this urb's qh after the last qh in skelqh->list
 * MUST be called with uhci->frame_list_lock acquired
 *
 * Note that urb_priv.queue_list doesn't have a separate queue head;
 * it's a ring with every element "live".
 */
static void _uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct urb *urb)
{
	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
	struct list_head *tmp;
	struct uhci_qh *lqh;

	/* Grab the last QH */
	lqh = list_entry(skelqh->list.prev, struct uhci_qh, list);

	/*
	 * Patch this endpoint's URB's QHs to point to the next skelqh:
	 *    skelqh --> ... lqh --> newqh --> next skelqh
	 * Do this first, so the HC always sees the right QH after this one.
	 */
	list_for_each (tmp, &urbp->queue_list) {
		struct urb_priv *turbp =
			list_entry(tmp, struct urb_priv, queue_list);

		turbp->qh->link = lqh->link;
	}
	urbp->qh->link = lqh->link;
	wmb();				/* Ordering is important */

	/*
	 * Patch QHs for previous endpoint's queued URBs?  HC goes
	 * here next, not to the next skelqh it now points to.
	 *
	 *    lqh --> td ... --> qh ... --> td --> qh ... --> td
	 *     |                 |                 |
	 *     v                 v                 v
	 *     +<----------------+-----------------+
	 *     v
	 *    newqh --> td ... --> td
	 *     |
	 *     v
	 *    ...
	 *
	 * The HC could see (and use!) any of these as we write them.
	 */
	if (lqh->urbp) {
		list_for_each (tmp, &lqh->urbp->queue_list) {
			struct urb_priv *turbp =
				list_entry(tmp, struct urb_priv, queue_list);

			turbp->qh->link = cpu_to_le32(urbp->qh->dma_handle) | UHCI_PTR_QH;
		}
	}
	lqh->link = cpu_to_le32(urbp->qh->dma_handle) | UHCI_PTR_QH;

	list_add_tail(&urbp->qh->list, &skelqh->list);
}

static void uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct urb *urb)
{
	unsigned long flags;

	spin_lock_irqsave(&uhci->frame_list_lock, flags);
	_uhci_insert_qh(uhci, skelqh, urb);
	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
}

/*
 * Start removal of QH from schedule; it finishes next frame.
 * TDs should be unlinked before this is called.
 */
static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
	unsigned long flags;
	struct uhci_qh *pqh;

	if (!qh)
		return;

	qh->urbp = NULL;

	/*
	 * Only go through the hoops if it's actually linked in
	 * Queued QHs are removed in uhci_delete_queued_urb,
	 * since (for queued URBs) the pqh is pointed to the next
	 * QH in the queue, not the next endpoint's QH.
	 */
	spin_lock_irqsave(&uhci->frame_list_lock, flags);
	if (!list_empty(&qh->list)) {
		pqh = list_entry(qh->list.prev, struct uhci_qh, list);

		if (pqh->urbp) {
			struct list_head *head, *tmp;

			head = &pqh->urbp->queue_list;
			tmp = head->next;
			while (head != tmp) {
				struct urb_priv *turbp =
					list_entry(tmp, struct urb_priv, queue_list);

				tmp = tmp->next;

				turbp->qh->link = qh->link;
			}
		}

		pqh->link = qh->link;
		mb();
		/* Leave qh->link in case the HC is on the QH now, it will */
		/* continue the rest of the schedule */
		qh->element = UHCI_PTR_TERM;

		list_del_init(&qh->list);
	}
	spin_unlock_irqrestore(&uhci->frame_list_lock, flags);

	spin_lock_irqsave(&uhci->qh_remove_list_lock, flags);

	/* Check to see if the remove list is empty. Set the IOC bit */
	/* to force an interrupt so we can remove the QH */
	if (list_empty(&uhci->qh_remove_list))
		uhci_set_next_interrupt(uhci);

	list_add(&qh->remove_list, &uhci->qh_remove_list);

	spin_unlock_irqrestore(&uhci->qh_remove_list_lock, flags);
}

static int uhci_fixup_toggle(struct urb *urb, unsigned int toggle)
{
	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
	struct list_head *head, *tmp;

	head = &urbp->td_list;
	tmp = head->next;
	while (head != tmp) {
		struct uhci_td *td = list_entry(tmp, struct uhci_td, list);

		tmp = tmp->next;

		if (toggle)
			td->token |= cpu_to_le32(TD_TOKEN_TOGGLE);
		else
			td->token &= ~cpu_to_le32(TD_TOKEN_TOGGLE);


		toggle ^= 1;
	}

	return toggle;
}

/* This function will append one URB's QH to another URB's QH. This is for */
/* queuing interrupt, control or bulk transfers */
static void uhci_append_queued_urb(struct uhci_hcd *uhci, struct urb *eurb, struct urb *urb)
{
	struct urb_priv *eurbp, *urbp, *furbp, *lurbp;
	struct list_head *tmp;
	struct uhci_td *lltd;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -