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

📄 hcd.c

📁 ReactOs中的USB驱动
💻 C
📖 第 1 页 / 共 3 页
字号:
	spin_lock_irqsave (&hcd_data_lock, flags);
	if (HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_QUIESCING) {
		usb_get_dev (urb->dev);
		list_add_tail (&urb->urb_list, &dev->urb_list);
		status = 0;
	} else {
		INIT_LIST_HEAD (&urb->urb_list);
		status = -ESHUTDOWN;
	}
	spin_unlock_irqrestore (&hcd_data_lock, flags);
	if (status)
		return status;

	/* increment urb's reference count as part of giving it to the HCD
	 * (which now controls it).  HCD guarantees that it either returns
	 * an error or calls giveback(), but not both.
	 */

	urb = usb_get_urb (urb);
	if (urb->dev == hcd->self.root_hub) {
		/* NOTE:  requirement on hub callers (usbfs and the hub
		 * driver, for now) that URBs' urb->transfer_buffer be
		 * valid and usb_buffer_{sync,unmap}() not be needed, since
		 * they could clobber root hub response data.
		 */
		urb->transfer_flags |= URB_NO_DMA_MAP;
		status = rh_urb_enqueue (hcd, urb);
		goto done;
	}

	/* lower level hcd code should use *_dma exclusively,
	 * unless it uses pio or talks to another transport.
	 */
	if (!(urb->transfer_flags & URB_NO_DMA_MAP)
			&& hcd->controller->dma_mask) {
		if (usb_pipecontrol (urb->pipe))
			urb->setup_dma = dma_map_single (
					hcd->controller,
					urb->setup_packet,
					sizeof (struct usb_ctrlrequest),
					DMA_TO_DEVICE);
		if (urb->transfer_buffer_length != 0)
			urb->transfer_dma = dma_map_single (
					hcd->controller,
					urb->transfer_buffer,
					urb->transfer_buffer_length,
					usb_pipein (urb->pipe)
					    ? DMA_FROM_DEVICE
					    : DMA_TO_DEVICE);
	}

	status = hcd->driver->urb_enqueue (hcd, urb, mem_flags);
done:

	if (status) {
		usb_put_urb (urb);
		urb_unlink (urb);
	}

	return status;
}

/*-------------------------------------------------------------------------*/

/* called in any context */
static int hcd_get_frame_number (struct usb_device *udev)
{
	struct usb_hcd	*hcd = (struct usb_hcd *)udev->bus->hcpriv;
	return hcd->driver->get_frame_number (hcd);
}

/*-------------------------------------------------------------------------*/

/* this makes the hcd giveback() the urb more quickly, by kicking it
 * off hardware queues (which may take a while) and returning it as
 * soon as practical.  we've already set up the urb's return status,
 * but we can't know if the callback completed already.
 */
static void
unlink1 (struct usb_hcd *hcd, struct urb *urb)
{
	if (urb == (struct urb *) hcd->rh_timer.data)
		usb_rh_status_dequeue (hcd, urb);
	else {
		int		value;

		/* failures "should" be harmless */
		value = hcd->driver->urb_dequeue (hcd, urb);
		if (value != 0)
			dev_dbg (hcd->controller,
				"dequeue %p --> %d\n",
				urb, value);
	}
}

struct completion_splice {		// modified urb context:
	/* did we complete? */
	struct completion	done;

	/* original urb data */
	usb_complete_t		complete;
	void			*context;
};

static void unlink_complete (struct urb *urb, struct pt_regs *regs)
{
	struct completion_splice	*splice;

	splice = (struct completion_splice *) urb->context;

	/* issue original completion call */
	urb->complete = splice->complete;
	urb->context = splice->context;
	urb->complete (urb, regs);

	/* then let the synchronous unlink call complete */
	complete (&splice->done);
}

/*
 * called in any context; note ASYNC_UNLINK restrictions
 *
 * caller guarantees urb won't be recycled till both unlink()
 * and the urb's completion function return
 */
static int hcd_unlink_urb (struct urb *urb)
{
	struct hcd_dev			*dev;
	struct usb_hcd			*hcd = 0;
	struct device			*sys = 0;
	unsigned long			flags;
	struct completion_splice	splice;
	int				retval;

	if (!urb)
		return -EINVAL;

	/*
	 * we contend for urb->status with the hcd core,
	 * which changes it while returning the urb.
	 *
	 * Caller guaranteed that the urb pointer hasn't been freed, and
	 * that it was submitted.  But as a rule it can't know whether or
	 * not it's already been unlinked ... so we respect the reversed
	 * lock sequence needed for the usb_hcd_giveback_urb() code paths
	 * (urb lock, then hcd_data_lock) in case some other CPU is now
	 * unlinking it.
	 */
	spin_lock_irqsave (&urb->lock, flags);
	spin_lock (&hcd_data_lock);

	if (!urb->dev || !urb->dev->bus) {
		retval = -ENODEV;
		goto done;
	}

	dev = urb->dev->hcpriv;
	sys = &urb->dev->dev;
	hcd = urb->dev->bus->hcpriv;
	if (!dev || !hcd) {
		retval = -ENODEV;
		goto done;
	}

	if (!urb->hcpriv) {
		retval = -EINVAL;
		goto done;
	}

	/* Any status except -EINPROGRESS means something already started to
	 * unlink this URB from the hardware.  So there's no more work to do.
	 *
	 * FIXME use better explicit urb state
	 */
	if (urb->status != -EINPROGRESS) {
		retval = -EBUSY;
		goto done;
	}

	/* maybe set up to block until the urb's completion fires.  the
	 * lower level hcd code is always async, locking on urb->status
	 * updates; an intercepted completion unblocks us.
	 */
	if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) {
		if (in_interrupt ()) {
			dev_dbg (hcd->controller, "non-async unlink in_interrupt");
			retval = -EWOULDBLOCK;
			goto done;
		}
		/* synchronous unlink: block till we see the completion */
		init_completion (&splice.done);
		splice.complete = urb->complete;
		splice.context = urb->context;
		urb->complete = unlink_complete;
		urb->context = &splice;
		urb->status = -ENOENT;
	} else {
		/* asynchronous unlink */
		urb->status = -ECONNRESET;
	}
	spin_unlock (&hcd_data_lock);
	spin_unlock_irqrestore (&urb->lock, flags);

	// FIXME remove splicing, so this becomes unlink1 (hcd, urb);
	if (urb == (struct urb *) hcd->rh_timer.data) {
		usb_rh_status_dequeue (hcd, urb);
		retval = 0;
	} else {
		retval = hcd->driver->urb_dequeue (hcd, urb);

		/* hcds shouldn't really fail these calls, but... */
		if (retval) {
			dev_dbg (sys, "dequeue %p --> %d\n", urb, retval);
			if (!(urb->transfer_flags & URB_ASYNC_UNLINK)) {
				spin_lock_irqsave (&urb->lock, flags);
				urb->complete = splice.complete;
				urb->context = splice.context;
				spin_unlock_irqrestore (&urb->lock, flags);
			}
			goto bye;
		}
	}

    	/* block till giveback, if needed */
	if (urb->transfer_flags & URB_ASYNC_UNLINK)
		return -EINPROGRESS;

	wait_for_completion (&splice.done);
	return 0;

done:
	spin_unlock (&hcd_data_lock);
	spin_unlock_irqrestore (&urb->lock, flags);
bye:
	if (retval && sys && sys->driver)
		dev_dbg (sys, "hcd_unlink_urb %p fail %d\n", urb, retval);
	return retval;
}

/*-------------------------------------------------------------------------*/

/* disables the endpoint: cancels any pending urbs, then synchronizes with
 * the hcd to make sure all endpoint state is gone from hardware. use for
 * set_configuration, set_interface, driver removal, physical disconnect.
 *
 * example:  a qh stored in hcd_dev.ep[], holding state related to endpoint
 * type, maxpacket size, toggle, halt status, and scheduling.
 */
static void hcd_endpoint_disable (struct usb_device *udev, int endpoint)
{
	unsigned long	flags;
	struct hcd_dev	*dev;
	struct usb_hcd	*hcd;
	struct urb	*urb;
	unsigned	epnum = endpoint & USB_ENDPOINT_NUMBER_MASK;

	dev = udev->hcpriv;
	hcd = udev->bus->hcpriv;

rescan:
	/* (re)block new requests, as best we can */
	if (endpoint & USB_DIR_IN) {
		usb_endpoint_halt (udev, epnum, 0);
		udev->epmaxpacketin [epnum] = 0;
	} else {
		usb_endpoint_halt (udev, epnum, 1);
		udev->epmaxpacketout [epnum] = 0;
	}

	/* then kill any current requests */
	spin_lock_irqsave (&hcd_data_lock, flags);
	list_for_each_entry (urb, &dev->urb_list, urb_list) {
		int	tmp = urb->pipe;

		/* ignore urbs for other endpoints */
		if (usb_pipeendpoint (tmp) != epnum)
			continue;
		if ((tmp ^ endpoint) & USB_DIR_IN)
			continue;

		/* another cpu may be in hcd, spinning on hcd_data_lock
		 * to giveback() this urb.  the races here should be
		 * small, but a full fix needs a new "can't submit"
		 * urb state.
		 */
		if (urb->status != -EINPROGRESS)
			continue;
		usb_get_urb (urb);
		spin_unlock_irqrestore (&hcd_data_lock, flags);

		spin_lock_irqsave (&urb->lock, flags);
		tmp = urb->status;
		if (tmp == -EINPROGRESS)
			urb->status = -ESHUTDOWN;
		spin_unlock_irqrestore (&urb->lock, flags);

		/* kick hcd unless it's already returning this */
		if (tmp == -EINPROGRESS) {
			tmp = urb->pipe;
			unlink1 (hcd, urb);
			dev_dbg (hcd->controller,
				"shutdown urb %p pipe %08x ep%d%s%s\n",
				urb, tmp, usb_pipeendpoint (tmp),
				(tmp & USB_DIR_IN) ? "in" : "out",
				({ char *s; \
				 switch (usb_pipetype (tmp)) { \
				 case PIPE_CONTROL:	s = ""; break; \
				 case PIPE_BULK:	s = "-bulk"; break; \
				 case PIPE_INTERRUPT:	s = "-intr"; break; \
				 default: 		s = "-iso"; break; \
				}; s;}));
		}
		usb_put_urb (urb);

		/* list contents may have changed */
		goto rescan;
	}
	spin_unlock_irqrestore (&hcd_data_lock, flags);

	/* synchronize with the hardware, so old configuration state
	 * clears out immediately (and will be freed).
	 */
	might_sleep ();
	if (hcd->driver->endpoint_disable)
		hcd->driver->endpoint_disable (hcd, dev, endpoint);
}

/*-------------------------------------------------------------------------*/

/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup.
 * we're guaranteed that the device is fully quiesced.  also, that each
 * endpoint has been hcd_endpoint_disabled.
 */

static int hcd_free_dev (struct usb_device *udev)
{
	struct hcd_dev		*dev;
	struct usb_hcd		*hcd;
	unsigned long		flags;

	if (!udev || !udev->hcpriv)
		return -EINVAL;

	if (!udev->bus || !udev->bus->hcpriv)
		return -ENODEV;

	// should udev->devnum == -1 ??

	dev = udev->hcpriv;
	hcd = udev->bus->hcpriv;

	/* device driver problem with refcounts? */
	if (!list_empty (&dev->urb_list)) {
		dev_dbg (hcd->controller, "free busy dev, %s devnum %d (bug!)\n",
			hcd->self.bus_name, udev->devnum);
		return -EINVAL;
	}

	spin_lock_irqsave (&hcd_data_lock, flags);
	list_del (&dev->dev_list);
	udev->hcpriv = NULL;
	spin_unlock_irqrestore (&hcd_data_lock, flags);

	kfree (dev);
	return 0;
}

/*
 * usb_hcd_operations - adapts usb_bus framework to HCD framework (bus glue)
 *
 * When registering a USB bus through the HCD framework code, use this
 * usb_operations vector.  The PCI glue layer does so automatically; only
 * bus glue for non-PCI system busses will need to use this.
 */
struct usb_operations usb_hcd_operations = {
	.allocate =		hcd_alloc_dev,
	.get_frame_number =	hcd_get_frame_number,
	.submit_urb =		hcd_submit_urb,
	.unlink_urb =		hcd_unlink_urb,
	.deallocate =		hcd_free_dev,
	.buffer_alloc =		hcd_buffer_alloc,
	.buffer_free =		hcd_buffer_free,
	.disable =		hcd_endpoint_disable,
};
EXPORT_SYMBOL (usb_hcd_operations);

/*-------------------------------------------------------------------------*/

/**
 * usb_hcd_giveback_urb - return URB from HCD to device driver
 * @hcd: host controller returning the URB
 * @urb: urb being returned to the USB device driver.
 * @regs: pt_regs, passed down to the URB completion handler
 * Context: in_interrupt()
 *
 * This hands the URB from HCD to its USB device driver, using its
 * completion function.  The HCD has freed all per-urb resources
 * (and is done using urb->hcpriv).  It also released all HCD locks;
 * the device driver won't cause problems if it frees, modifies,
 * or resubmits this URB.
 */
void STDCALL usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs)
{
	urb_unlink (urb);

	// NOTE:  a generic device/urb monitoring hook would go here.
	// hcd_monitor_hook(MONITOR_URB_FINISH, urb, dev)
	// It would catch exit/unlink paths for all urbs.

	/* lower level hcd code should use *_dma exclusively */
	if (!(urb->transfer_flags & URB_NO_DMA_MAP)) {
		if (usb_pipecontrol (urb->pipe))
			pci_unmap_single (hcd->pdev, urb->setup_dma,
					sizeof (struct usb_ctrlrequest),
					PCI_DMA_TODEVICE);
		if (urb->transfer_buffer_length != 0)
			pci_unmap_single (hcd->pdev, urb->transfer_dma,
					urb->transfer_buffer_length,
					usb_pipein (urb->pipe)
					    ? PCI_DMA_FROMDEVICE
					    : PCI_DMA_TODEVICE);
	}

	/* pass ownership to the completion handler */
	urb->complete (urb, regs);
	usb_put_urb (urb);
}

/*-------------------------------------------------------------------------*/

/**
 * usb_hcd_irq - hook IRQs to HCD framework (bus glue)
 * @irq: the IRQ being raised
 * @__hcd: pointer to the HCD whose IRQ is beinng signaled
 * @r: saved hardware registers
 *
 * When registering a USB bus through the HCD framework code, use this
 * to handle interrupts.  The PCI glue layer does so automatically; only
 * bus glue for non-PCI system busses will need to use this.
 */
irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs * r)
{
	struct usb_hcd		*hcd = __hcd;
	int			start = hcd->state;

	if (unlikely (hcd->state == USB_STATE_HALT))	/* irq sharing? */
		return IRQ_NONE;

	hcd->driver->irq (hcd, r);
	if (hcd->state != start && hcd->state == USB_STATE_HALT)
		usb_hc_died (hcd);
	return IRQ_HANDLED;
}

/*-------------------------------------------------------------------------*/

static void hcd_panic (void *_hcd)
{
	struct usb_hcd *hcd = _hcd;
	hcd->driver->stop (hcd);
}

/**
 * usb_hc_died - report abnormal shutdown of a host controller (bus glue)
 * @hcd: pointer to the HCD representing the controller
 *
 * This is called by bus glue to report a USB host controller that died
 * while operations may still have been pending.  It's called automatically
 * by the PCI glue, so only glue for non-PCI busses should need to call it. 
 */
void STDCALL usb_hc_died (struct usb_hcd *hcd)
{
	struct list_head	*devlist, *urblist;
	struct hcd_dev		*dev;
	struct urb		*urb;
	unsigned long		flags;
	
	/* flag every pending urb as done */
	spin_lock_irqsave (&hcd_data_lock, flags);
	list_for_each (devlist, &hcd->dev_list) {
		dev = list_entry (devlist, struct hcd_dev, dev_list);
		list_for_each (urblist, &dev->urb_list) {
			urb = list_entry (urblist, struct urb, urb_list);
			dev_dbg (hcd->controller, "shutdown %s urb %p pipe %x, current status %d\n",
				hcd->self.bus_name, urb, urb->pipe, urb->status);
			if (urb->status == -EINPROGRESS)
				urb->status = -ESHUTDOWN;
		}
	}
	urb = (struct urb *) hcd->rh_timer.data;
	if (urb)
		urb->status = -ESHUTDOWN;
	spin_unlock_irqrestore (&hcd_data_lock, flags);

	/* hcd->stop() needs a task context */
	INIT_WORK (&hcd->work, hcd_panic, hcd);
	(void) schedule_work (&hcd->work);
}

⌨️ 快捷键说明

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