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

📄 hcd.c

📁 linux core 函数例程
💻 C
📖 第 1 页 / 共 4 页
字号:
	if (!hcd || !dev)
		return -ENODEV;

	/* can't submit new urbs when quiescing, halted, ... */
	if (hcd->state == USB_STATE_QUIESCING || !HCD_IS_RUNNING (hcd->state))
		return -ESHUTDOWN;
	pipe = urb->pipe;
	temp = usb_pipetype (urb->pipe);
	if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe),
			usb_pipeout (pipe)))
		return -EPIPE;

	/* FIXME there should be a sharable lock protecting us against
	 * config/altsetting changes and disconnects, kicking in here.
	 */

	/* Sanity check, so HCDs can rely on clean data */
	max = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe));
	if (max <= 0) {
		err ("bogus endpoint (bad maxpacket)");
		return -EINVAL;
	}

	/* "high bandwidth" mode, 1-3 packets/uframe? */
	if (urb->dev->speed == USB_SPEED_HIGH) {
		int	mult;
		switch (temp) {
		case PIPE_ISOCHRONOUS:
		case PIPE_INTERRUPT:
			mult = 1 + ((max >> 11) & 0x03);
			max &= 0x03ff;
			max *= mult;
		}
	}

	/* periodic transfers limit size per frame/uframe */
	switch (temp) {
	case PIPE_ISOCHRONOUS: {
		int	n, len;

		if (urb->number_of_packets <= 0)		    
			return -EINVAL;
		for (n = 0; n < urb->number_of_packets; n++) {
			len = urb->iso_frame_desc [n].length;
			if (len < 0 || len > max) 
				return -EINVAL;
		}

		}
		break;
	case PIPE_INTERRUPT:
		if (urb->transfer_buffer_length > max)
			return -EINVAL;
	}

	/* the I/O buffer must usually be mapped/unmapped */
	if (urb->transfer_buffer_length < 0)
		return -EINVAL;

#ifdef DEBUG
	/* stuff that drivers shouldn't do, but which shouldn't
	 * cause problems in HCDs if they get it wrong.
	 */
	{
	unsigned int	orig_flags = urb->transfer_flags;
	unsigned int	allowed;

	/* enforce simple/standard policy */
	allowed = USB_ASYNC_UNLINK;	// affects later unlinks
	allowed |= USB_NO_FSBR;		// only affects UHCI
	switch (temp) {
	case PIPE_CONTROL:
		allowed |= USB_DISABLE_SPD;
		break;
	case PIPE_BULK:
		allowed |= USB_DISABLE_SPD | USB_QUEUE_BULK
				| USB_ZERO_PACKET | URB_NO_INTERRUPT;
		break;
	case PIPE_INTERRUPT:
		allowed |= USB_DISABLE_SPD;
		break;
	case PIPE_ISOCHRONOUS:
		allowed |= USB_ISO_ASAP;
		break;
	}
	urb->transfer_flags &= allowed;

	/* fail if submitter gave bogus flags */
	if (urb->transfer_flags != orig_flags) {
		err ("BOGUS urb flags, %x --> %x",
			orig_flags, urb->transfer_flags);
		return -EINVAL;
	}
	}
#endif
	/*
	 * Force periodic transfer intervals to be legal values that are
	 * a power of two (so HCDs don't need to).
	 *
	 * FIXME want bus->{intr,iso}_sched_horizon values here.  Each HC
	 * supports different values... this uses EHCI/UHCI defaults (and
	 * EHCI can use smaller non-default values).
	 */
	switch (temp) {
	case PIPE_ISOCHRONOUS:
	case PIPE_INTERRUPT:
		/* too small? */
		if (urb->interval <= 0)
			return -EINVAL;
		/* too big? */
		switch (urb->dev->speed) {
		case USB_SPEED_HIGH:	/* units are microframes */
			// NOTE usb handles 2^15
			if (urb->interval > (1024 * 8))
				urb->interval = 1024 * 8;
			temp = 1024 * 8;
			break;
		case USB_SPEED_FULL:	/* units are frames/msec */
		case USB_SPEED_LOW:
			if (temp == PIPE_INTERRUPT) {
				if (urb->interval > 255)
					return -EINVAL;
				// NOTE ohci only handles up to 32
				temp = 128;
			} else {
				if (urb->interval > 1024)
					urb->interval = 1024;
				// NOTE usb and ohci handle up to 2^15
				temp = 1024;
			}
			break;
		default:
			return -EINVAL;
		}
		/* power of two? */
		while (temp > urb->interval)
			temp >>= 1;
		urb->interval = temp;
	}


	/*
	 * FIXME:  make urb timeouts be generic, keeping the HCD cores
	 * as simple as possible.
	 */

	// NOTE:  a generic device/urb monitoring hook would go here.
	// hcd_monitor_hook(MONITOR_URB_SUBMIT, urb)
	// It would catch submission paths for all urbs.

	/* increment urb's reference count, we now control it. */
	urb = usb_get_urb(urb);

	/*
	 * Atomically queue the urb,  first to our records, then to the HCD.
	 * Access to urb->status is controlled by urb->lock ... changes on
	 * i/o completion (normal or fault) or unlinking.
	 */

	// FIXME:  verify that quiescing hc works right (RH cleans up)

	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 (&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;

	/* temporarily up refcount while queueing it in the HCD,
	 * since we report some queuing/setup errors ourselves
	 */
	urb = usb_get_urb (urb);
	if (urb->dev == hcd->self.root_hub)
		status = rh_urb_enqueue (hcd, urb);
	else
		status = hcd->driver->urb_enqueue (hcd, urb, mem_flags);
	/* urb->dev got nulled if hcd called giveback for us */
	if (status && urb->dev)
		urb_unlink (urb);
	usb_put_urb (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);
}

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

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

	/* original urb data */
	void			(*complete)(struct urb *);
	void			*context;
};

static void unlink_complete (struct urb *urb)
{
	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);

	/* 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;
	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->hcpriv || urb->transfer_flags & USB_TIMEOUT_KILLED) {
		retval = -EINVAL;
		goto done;
	}

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

	/* giveback clears dev; non-null means it's linked at this level */
	dev = urb->dev->hcpriv;
	hcd = urb->dev->bus->hcpriv;
	if (!dev || !hcd) {
		retval = -ENODEV;
		goto done;
	}

	/* For non-periodic transfers, any status except -EINPROGRESS means
	 * the HCD has already started to unlink this URB from the hardware.
	 * In that case, there's no more work to do.
	 *
	 * For periodic transfers, this is the only way to trigger unlinking
	 * from the hardware.  Since we (currently) overload urb->status to
	 * tell the driver to unlink, error status might get clobbered ...
	 * unless that transfer hasn't yet restarted.  One such case is when
	 * the URB gets unlinked from its completion handler.
	 *
	 * FIXME use an URB_UNLINKED flag to match URB_TIMEOUT_KILLED
	 */
	switch (usb_pipetype (urb->pipe)) {
	case PIPE_CONTROL:
	case PIPE_BULK:
		if (urb->status != -EINPROGRESS) {
			retval = -EINVAL;
			goto done;
		}
	}

	/* maybe set up to block on completion notification */
	if ((urb->transfer_flags & USB_TIMEOUT_KILLED))
		urb->status = -ETIMEDOUT;
	else if (!(urb->transfer_flags & USB_ASYNC_UNLINK)) {
		if (in_interrupt ()) {
			dbg ("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);

	if (urb == (struct urb *) hcd->rh_timer.data) {
		rh_status_dequeue (hcd, urb);
		retval = 0;
	} else {
		retval = hcd->driver->urb_dequeue (hcd, urb);
// FIXME:  if retval and we tried to splice, whoa!!
if (retval && urb->status == -ENOENT) err ("whoa! retval %d", retval);
	}

    	/* block till giveback, if needed */
	if (!(urb->transfer_flags & (USB_ASYNC_UNLINK|USB_TIMEOUT_KILLED))
			&& HCD_IS_RUNNING (hcd->state)
			&& !retval) {
		dbg ("%s: wait for giveback urb %p",
			hcd->self.bus_name, urb);
		wait_for_completion (&splice.done);
	} else if ((urb->transfer_flags & USB_ASYNC_UNLINK) && retval == 0) {
		return -EINPROGRESS;
	}
	goto bye;
done:
	spin_unlock (&hcd_data_lock);
	spin_unlock_irqrestore (&urb->lock, flags);
bye:
	if (retval)
		dbg ("%s: hcd_unlink_urb fail %d",
		    hcd ? hcd->self.bus_name : "(no bus?)",
		    retval);
	return retval;
}

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

/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup */

// FIXME:  likely best to have explicit per-setting (config+alt)
// setup primitives in the usbcore-to-hcd driver API, so nothing
// is implicit.  kernel 2.5 needs a bunch of config cleanup...

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)) {
		dbg ("free busy dev, %s devnum %d (bug!)",
			hcd->self.bus_name, udev->devnum);
		return -EINVAL;
	}

	if (hcd->driver->free_config)
		hcd->driver->free_config (hcd, udev);

	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;
}

static struct usb_operations 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,
};

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

static void 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;

	hcd->driver->irq (hcd);
	if (hcd->state != start && hcd->state == USB_STATE_HALT)
		hc_died (hcd);
}

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

/**
 * 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.
 * 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 deadlocks if it resubmits this URB,
 * and won't confuse things by modifying and resubmitting this one.
 * Bandwidth and other resources will be deallocated.
 *
 * HCDs must not use this for periodic URBs that are still scheduled
 * and will be reissued.  They should just call their completion handlers
 * until the urb is returned to the device driver by unlinking.
 */
void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb)
{
	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, but non-exit
	// completions for periodic urbs need hooks inside the HCD.
	// hcd_monitor_hook(MONITOR_URB_UPDATE, urb, dev)

	if (urb->status)
		dbg ("giveback urb %p status %d len %d",
			urb, urb->status, urb->actual_length);

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

⌨️ 快捷键说明

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