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

📄 uhci-hcd.c

📁 ReactOs中的USB驱动
💻 C
📖 第 1 页 / 共 5 页
字号:
 */
static void uhci_transfer_result(struct uhci_hcd *uhci, struct urb *urb)
{
	int ret = -EINVAL;
	unsigned long flags;
	struct urb_priv *urbp;

	spin_lock_irqsave(&urb->lock, flags);

	urbp = (struct urb_priv *)urb->hcpriv;

	if (urb->status != -EINPROGRESS) {
		info("uhci_transfer_result: called for URB %p not in flight?", urb);
		goto out;
	}

	switch (usb_pipetype(urb->pipe)) {
	case PIPE_CONTROL:
		ret = uhci_result_control(uhci, urb);
		break;
	case PIPE_INTERRUPT:
		ret = uhci_result_interrupt(uhci, urb);
		break;
	case PIPE_BULK:
		ret = uhci_result_bulk(uhci, urb);
		break;
	case PIPE_ISOCHRONOUS:
		ret = uhci_result_isochronous(uhci, urb);
		break;
	}

	urbp->status = ret;

	if (ret == -EINPROGRESS)
		goto out;

	switch (usb_pipetype(urb->pipe)) {
	case PIPE_CONTROL:
	case PIPE_BULK:
	case PIPE_ISOCHRONOUS:
		/* Release bandwidth for Interrupt or Isoc. transfers */
		/* Spinlock needed ? */
		if (urb->bandwidth)
			usb_release_bandwidth(urb->dev, urb, 1);
		uhci_unlink_generic(uhci, urb);
		break;
	case PIPE_INTERRUPT:
		/* Release bandwidth for Interrupt or Isoc. transfers */
		/* Make sure we don't release if we have a queued URB */
		spin_lock(&uhci->frame_list_lock);
		/* Spinlock needed ? */
		if (list_empty(&urbp->queue_list) && urb->bandwidth)
			usb_release_bandwidth(urb->dev, urb, 0);
		else
			/* bandwidth was passed on to queued URB, */
			/* so don't let usb_unlink_urb() release it */
			urb->bandwidth = 0;
		spin_unlock(&uhci->frame_list_lock);
		uhci_unlink_generic(uhci, urb);
		break;
	default:
		info("uhci_transfer_result: unknown pipe type %d for urb %p\n",
			usb_pipetype(urb->pipe), urb);
	}

	/* Remove it from uhci->urb_list */
	list_del_init(&urbp->urb_list);

	uhci_add_complete(uhci, urb);

out:
	spin_unlock_irqrestore(&urb->lock, flags);
}

/*
 * MUST be called with urb->lock acquired
 */
static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb)
{
	struct list_head *head, *tmp;
	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
	int prevactive = 1;

	/* We can get called when urbp allocation fails, so check */
	if (!urbp)
		return;

	uhci_dec_fsbr(uhci, urb);	/* Safe since it checks */

	/*
	 * Now we need to find out what the last successful toggle was
	 * so we can update the local data toggle for the next transfer
	 *
	 * There's 3 way's the last successful completed TD is found:
	 *
	 * 1) The TD is NOT active and the actual length < expected length
	 * 2) The TD is NOT active and it's the last TD in the chain
	 * 3) The TD is active and the previous TD is NOT active
	 *
	 * Control and Isochronous ignore the toggle, so this is safe
	 * for all types
	 */
	head = &urbp->td_list;
	tmp = head->next;
	while (tmp != head) {
		struct uhci_td *td = list_entry(tmp, struct uhci_td, list);

		tmp = tmp->next;

		if (!(td_status(td) & TD_CTRL_ACTIVE) &&
		    (uhci_actual_length(td_status(td)) < uhci_expected_length(td_token(td)) ||
		    tmp == head))
			usb_settoggle(urb->dev, uhci_endpoint(td_token(td)),
				uhci_packetout(td_token(td)),
				uhci_toggle(td_token(td)) ^ 1);
		else if ((td_status(td) & TD_CTRL_ACTIVE) && !prevactive)
			usb_settoggle(urb->dev, uhci_endpoint(td_token(td)),
				uhci_packetout(td_token(td)),
				uhci_toggle(td_token(td)));

		prevactive = td_status(td) & TD_CTRL_ACTIVE;
	}

	uhci_delete_queued_urb(uhci, urb);

	/* The interrupt loop will reclaim the QH's */
	uhci_remove_qh(uhci, urbp->qh);
	urbp->qh = NULL;
}

static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
{
	struct uhci_hcd *uhci = hcd_to_uhci(hcd);
	unsigned long flags;
	struct urb_priv *urbp = urb->hcpriv;

	/* If this is an interrupt URB that is being killed in urb->complete, */
	/* then just set its status and return */
	if (!urbp) {
	  urb->status = -ECONNRESET;
	  return 0;
	}

	spin_lock_irqsave(&uhci->urb_list_lock, flags);

	list_del_init(&urbp->urb_list);

	uhci_unlink_generic(uhci, urb);

	spin_lock(&uhci->urb_remove_list_lock);

	/* If we're the first, set the next interrupt bit */
	if (list_empty(&uhci->urb_remove_list))
		uhci_set_next_interrupt(uhci);
	list_add(&urbp->urb_list, &uhci->urb_remove_list);

	spin_unlock(&uhci->urb_remove_list_lock);
	spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
	return 0;
}

static int uhci_fsbr_timeout(struct uhci_hcd *uhci, struct urb *urb)
{
	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
	struct list_head *head, *tmp;
	int count = 0;

	uhci_dec_fsbr(uhci, urb);

	urbp->fsbr_timeout = 1;

	/*
	 * Ideally we would want to fix qh->element as well, but it's
	 * read/write by the HC, so that can introduce a race. It's not
	 * really worth the hassle
	 */

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

		tmp = tmp->next;

		/*
		 * Make sure we don't do the last one (since it'll have the
		 * TERM bit set) as well as we skip every so many TD's to
		 * make sure it doesn't hog the bandwidth
		 */
		if (tmp != head && (count % DEPTH_INTERVAL) == (DEPTH_INTERVAL - 1))
			td->link |= UHCI_PTR_DEPTH;

		count++;
	}

	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 uhci_hcd *uhci)
{
	return inw(uhci->io_addr + USBFRNUM);
}

static int init_stall_timer(struct usb_hcd *hcd);

static void stall_callback(unsigned long ptr)
{
	struct usb_hcd *hcd = (struct usb_hcd *)ptr;
	struct uhci_hcd *uhci = hcd_to_uhci(hcd);
	struct list_head list, *tmp, *head;
	unsigned long flags;

	INIT_LIST_HEAD(&list);

	spin_lock_irqsave(&uhci->urb_list_lock, flags);
	head = &uhci->urb_list;
	tmp = head->next;
	while (tmp != head) {
		struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list);
		struct urb *u = up->urb;

		tmp = tmp->next;

		spin_lock(&u->lock);

		/* Check if the FSBR timed out */
		if (up->fsbr && !up->fsbr_timeout && time_after_eq(jiffies, up->fsbrtime + IDLE_TIMEOUT))
			uhci_fsbr_timeout(uhci, u);

		/* Check if the URB timed out */
		if (u->timeout && time_after_eq(jiffies, up->inserttime + u->timeout))
			list_move_tail(&up->urb_list, &list);

		spin_unlock(&u->lock);
	}
	spin_unlock_irqrestore(&uhci->urb_list_lock, flags);

	head = &list;
	tmp = head->next;
	while (tmp != head) {
		struct urb_priv *up = list_entry(tmp, struct urb_priv, urb_list);
		struct urb *u = up->urb;

		tmp = tmp->next;

		uhci_urb_dequeue(hcd, u);
	}

	/* Really disable FSBR */
	if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies, uhci->fsbrtimeout)) {
		uhci->fsbrtimeout = 0;
		uhci->skel_term_qh->link = UHCI_PTR_TERM;
	}

	/* Poll for and perform state transitions */
	hc_state_transitions(uhci);

	init_stall_timer(hcd);
}

static int init_stall_timer(struct usb_hcd *hcd)
{
	struct uhci_hcd *uhci = hcd_to_uhci(hcd);

	init_timer(&uhci->stall_timer);
	uhci->stall_timer.function = stall_callback;
	uhci->stall_timer.data = (unsigned long)hcd;
	uhci->stall_timer.expires = jiffies + (HZ / 10);
	add_timer(&uhci->stall_timer);

	return 0;
}

static void uhci_free_pending_qhs(struct uhci_hcd *uhci)
{
	struct list_head *tmp, *head;
	unsigned long flags;

	spin_lock_irqsave(&uhci->qh_remove_list_lock, flags);
	head = &uhci->qh_remove_list;
	tmp = head->next;
	while (tmp != head) {
		struct uhci_qh *qh = list_entry(tmp, struct uhci_qh, remove_list);

		tmp = tmp->next;

		list_del_init(&qh->remove_list);

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

static void uhci_finish_urb(struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs)
{
	struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
	struct uhci_hcd *uhci = hcd_to_uhci(hcd);
	int status;
	unsigned long flags;

	spin_lock_irqsave(&urb->lock, flags);
	status = urbp->status;
	uhci_destroy_urb_priv(uhci, urb);

 	if (urb->status != -ENOENT && urb->status != -ECONNRESET)
		urb->status = status;
	spin_unlock_irqrestore(&urb->lock, flags);

	usb_hcd_giveback_urb(hcd, urb, regs);
}

static void uhci_finish_completion(struct usb_hcd *hcd, struct pt_regs *regs)
{
	struct uhci_hcd *uhci = hcd_to_uhci(hcd);
	struct list_head *tmp, *head;
	unsigned long flags;

	spin_lock_irqsave(&uhci->complete_list_lock, flags);
	head = &uhci->complete_list;
	tmp = head->next;
	while (tmp != head) {
		struct urb_priv *urbp = list_entry(tmp, struct urb_priv, complete_list);
		struct urb *urb = urbp->urb;

		list_del_init(&urbp->complete_list);
		spin_unlock_irqrestore(&uhci->complete_list_lock, flags);

		uhci_finish_urb(hcd, urb, regs);

		spin_lock_irqsave(&uhci->complete_list_lock, flags);
		head = &uhci->complete_list;
		tmp = head->next;
	}
	spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
}

static void uhci_remove_pending_qhs(struct uhci_hcd *uhci)
{
	struct list_head *tmp, *head;
	unsigned long flags;

	spin_lock_irqsave(&uhci->urb_remove_list_lock, flags);
	head = &uhci->urb_remove_list;
	tmp = head->next;
	while (tmp != head) {
		struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list);
		struct urb *urb = urbp->urb;

		tmp = tmp->next;

		list_del_init(&urbp->urb_list);

		urbp->status = urb->status = -ECONNRESET;

		uhci_add_complete(uhci, urb);
	}
	spin_unlock_irqrestore(&uhci->urb_remove_list_lock, flags);
}

static int uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs)
{
	struct uhci_hcd *uhci = hcd_to_uhci(hcd);
	unsigned int io_addr = uhci->io_addr;
	unsigned short status;
	struct list_head *tmp, *head;

	/*
	 * 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 0;
	outw(status, io_addr + USBSTS);		/* Clear it */

	if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) {
		if (status & USBSTS_HSE)
			err("%x: host system error, PCI problems?", io_addr);
		if (status & USBSTS_HCPE)
			err("%x: host controller process error. something bad happened", io_addr);
		if ((status & USBSTS_HCH) && uhci->state > 0) {
			err("%x: host controller halted. very bad", io_addr);
			/* FIXME: Reset the controller, fix the offending TD */
		}
	}

	if (status & USBSTS_RD)
		uhci->resume_detect = 1;

	uhci_free_pending_qhs(uhci);

	uhci_remove_pending_qhs(uhci);

	uhci_clear_next_interrupt(uhci);

	/* Walk the list of pending URB's to see which ones completed */
	spin_lock(&uhci->urb_list_lock);
	head = &uhci->urb_list;
	tmp = head->next;
	while (tmp != head) {
		struct urb_priv *urbp = list_entry(tmp, struct urb_priv, urb_list);
		struct urb *urb = urbp->urb;

		tmp = tmp->next;

		/* Checks the status and does all of the magic necessary */
		uhci_transfer_result(uhci, urb);
	}
	spin_unlock(&uhci->urb_list_lock);

	uhci_finish_completion(hcd, regs);
	
	return 0;
}

static void reset_hc(struct uhci_hcd *uhci)
{
	unsigned int io_addr = uhci->io_addr;

	/* Global reset for 50ms */
	uhci->state = UHCI_RESET;
	outw(USBCMD_GRESET, io_addr + USBCMD);
	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout((HZ*50+999) / 1000);
	set_current_state(TASK_RUNNING);
	outw(0, io_addr + USBCMD);

	/* Another 10ms delay */
	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout((HZ*10+999) / 1000);
	set_current_state(TASK_RUNNING);
	uhci->resume_detect = 0;
}

static void suspend_hc(struct uhci_hcd *uhci)
{
	unsigned int io_addr = uhci->io_addr;

	dbg("%x: suspend_hc", io_addr);
	uhci->state = UHCI_SUSPENDED;
	uhci->resume_detect = 0;
	outw(USBCMD_EGSM, io_addr + USBCMD);
}

static void wakeup_hc(struct uhci_hcd *uhci)
{
	unsigned int io_addr = uhci->io_addr;

	switch (uhci->state) {
		case UHCI_SUSPENDED:		/* Start the resume */
			dbg("%x: wakeup_hc", io_addr);

			/* Global resume for >= 20ms */
			outw(USBCMD_FGR | USBCMD_EGSM, io_addr + USBCMD);
			uhci->state = UHCI_RESUMING_1;
			uhci->state_end = jiffies + (20*HZ+999) / 1000;
			break;

		case UHCI_RESUMING_1:		/* End global resume */
			uhci->state = UHCI_RESUMING_2;
			outw(0, io_addr + USBCMD);
			/* Falls through */

		case UHCI_RESUMING_2:		/* Wait for EOP to be sent */
			if (inw(io_addr + USBCMD) & USBCMD_FGR)
				break;

			/* Run for at least 1 second, and
			 * mark it configured with a 64-byte max packet */
			uhci->state = UHCI_RUNNING_GRACE;
			uhci->state_end = jiffies + HZ;
			outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP,
					io_addr + USBCMD);
			break;

		case UHCI_RUNNING_GRACE:	/* Now allowed to suspend */
			uhci->state = UHCI_RUNNING;
			break;

		default:
			break;
	}
}

static int ports_active(struct uhci_hcd *uhci)
{
	unsigned int io_addr = uhci->io_addr;
	int connection = 0;
	int i;

	for (i = 0; i < uhci->rh_numports; i++)
		connection |= (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_CCS);

	return connection;
}

static int suspend_allowed(struct uhci_hcd *uhci)
{
	unsigned int io_addr = uhci->io_addr;
	int i;

⌨️ 快捷键说明

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