コード例 #1
0
static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
					struct net_device *net)
{
	struct eth_dev		*dev = netdev_priv(net);
	int			length = skb->len;
	int			retval;
	struct usb_request	*req = NULL;
	unsigned long		flags;
	struct usb_ep		*in;
	u16			cdc_filter;

	spin_lock_irqsave(&dev->lock, flags);
	if (dev->port_usb) {
		in = dev->port_usb->in_ep;
		cdc_filter = dev->port_usb->cdc_filter;
	} else {
		in = NULL;
		cdc_filter = 0;
	}
	spin_unlock_irqrestore(&dev->lock, flags);

	if (!in) {
		dev_kfree_skb_any(skb);
		return NETDEV_TX_OK;
	}

	/* apply outgoing CDC or RNDIS filters */
	if (!is_promisc(cdc_filter)) {
		u8		*dest = skb->data;

		if (is_multicast_ether_addr(dest)) {
			u16	type;

			/* ignores USB_CDC_PACKET_TYPE_MULTICAST and host
			 * SET_ETHERNET_MULTICAST_FILTERS requests
			 */
			if (is_broadcast_ether_addr(dest))
				type = USB_CDC_PACKET_TYPE_BROADCAST;
			else
				type = USB_CDC_PACKET_TYPE_ALL_MULTICAST;
			if (!(cdc_filter & type)) {
				dev_kfree_skb_any(skb);
				return NETDEV_TX_OK;
			}
		}
		/* ignores USB_CDC_PACKET_TYPE_DIRECTED */
	}

	spin_lock_irqsave(&dev->req_lock, flags);
	/*
	 * this freelist can be empty if an interrupt triggered disconnect()
	 * and reconfigured the gadget (shutting down this queue) after the
	 * network stack decided to xmit but before we got the spinlock.
	 */
	if (list_empty(&dev->tx_reqs)) {
		spin_unlock_irqrestore(&dev->req_lock, flags);
		return NETDEV_TX_BUSY;
	}

	req = container_of(dev->tx_reqs.next, struct usb_request, list);
	list_del(&req->list);

	/* temporarily stop TX queue when the freelist empties */
	if (list_empty(&dev->tx_reqs))
		netif_stop_queue(net);
	spin_unlock_irqrestore(&dev->req_lock, flags);

	/* no buffer copies needed, unless the network stack did it
	 * or the hardware can't use skb buffers.
	 * or there's not enough space for extra headers we need
	 */
	if (dev->wrap) {
		unsigned long	flags;

		spin_lock_irqsave(&dev->lock, flags);
		if (dev->port_usb)
			skb = dev->wrap(dev->port_usb, skb);
		spin_unlock_irqrestore(&dev->lock, flags);
		if (!skb)
			goto drop;

		length = skb->len;
	}
	req->buf = skb->data;
	req->context = skb;
	req->complete = tx_complete;

	/* use zlp framing on tx for strict CDC-Ether conformance,
	 * though any robust network rx path ignores extra padding.
	 * and some hardware doesn't like to write zlps.
	 */
	req->zero = 1;
	if (!dev->zlp && (length % in->maxpacket) == 0)
		length++;

	req->length = length;

	/* throttle highspeed IRQ rate back slightly */
	if (gadget_is_dualspeed(dev->gadget))
		req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH)
			? ((atomic_read(&dev->tx_qlen) % qmult) != 0)
			: 0;

	retval = usb_ep_queue(in, req, GFP_ATOMIC);
	switch (retval) {
	default:
		DBG(dev, "tx queue err %d\n", retval);
		break;
	case 0:
		net->trans_start = jiffies;
		atomic_inc(&dev->tx_qlen);
	}

	if (retval) {
		dev_kfree_skb_any(skb);
drop:
		dev->net->stats.tx_dropped++;
		spin_lock_irqsave(&dev->req_lock, flags);
		if (list_empty(&dev->tx_reqs))
			netif_start_queue(net);
		list_add(&req->list, &dev->tx_reqs);
		spin_unlock_irqrestore(&dev->req_lock, flags);
	}
	return NETDEV_TX_OK;
}
コード例 #2
0
static int dtf_allocate_endpoints(struct usb_configuration *c, struct usb_function *f)
{
	struct usb_composite_dev *cdev = c->cdev;
	struct dtf_dev *dev = func_to_dtf(f);
	struct usb_request *req;
	struct usb_ep *ep;

	_dbgmsg( "IN\n" );
	
	/* allocate endpoints: PipeGroup1 intrrupt */
	_dbgmsg_gadget( "usb_ep_autoconfig\n" );
	ep = usb_ep_autoconfig(cdev->gadget, &vPg1_epintr_desc);
	if( !ep ) {
		_dbgmsg( "usb_ep_autoconfig for PG1 ep_intr failed\n" );
		return -ENODEV;
	}
	_dbgmsg("usb_ep_autoconfig for PG1 ep_intr got %s\n", ep->name);

	ep->driver_data = dev;
	dev->pg.ep_intr = ep;

	/* allocate endpoints: PipeGroup1 bulk(in) */
	_dbgmsg_gadget( "usb_ep_autoconfig\n" );
	ep = usb_ep_autoconfig(cdev->gadget, &vPg1_epin_desc);
	if( !ep ) {
		_dbgmsg( "usb_ep_autoconfig for PG1 ep_in failed\n" );
		return -ENODEV;
	}
	_dbgmsg("usb_ep_autoconfig for PG1 ep_in got %s\n", ep->name);

	ep->driver_data = dev;
	dev->pg.ep_in = ep;

	/* allocate endpoints: PipeGroup1 bulk(out) */
	_dbgmsg_gadget( "usb_ep_autoconfig\n" );
	ep = usb_ep_autoconfig(cdev->gadget, &vPg1_epout_desc);
	if( !ep ) {
		_dbgmsg( "usb_ep_autoconfig for PG1 ep_out failed\n" );
		return -ENODEV;
	}
	_dbgmsg("usb_ep_autoconfig for PG1 ep_out got %s\n", ep->name);
	ep->driver_data = dev;
	dev->pg.ep_out = ep;

	/* support high speed hardware */
	if (gadget_is_dualspeed(cdev->gadget)) {
		vPg1_epintr_desc_hs.bEndpointAddress = vPg1_epintr_desc.bEndpointAddress;
		vPg1_epin_desc_hs.bEndpointAddress = vPg1_epin_desc.bEndpointAddress;
		vPg1_epout_desc_hs.bEndpointAddress = vPg1_epout_desc.bEndpointAddress;
	}

	_dbgmsg("%s speed %s: PG1[INTR/%s, IN/%s, OUT/%s]\n",
		gadget_is_dualspeed(cdev->gadget) ? "dual" : "full",
		f->name,
		dev->pg.ep_intr->name, dev->pg.ep_in->name, dev->pg.ep_out->name);

	/* allocate request for endpoints */
	req = dtf_request_new( dev->pg.ep_intr, 16 );
	if(!req) {
		_dbgmsg( "create request error\n" );
		return -ENODEV;
	}
	req->complete = dtf_complete_intr;
	dev->pg.mReq_intr = req;

	req = dtf_request_new( dev->pg.ep_in, 512 );
	if(!req) {
		_dbgmsg( "create request error\n" );
		return -ENODEV;
	}
	req->complete = dtf_complete_in;
	dev->pg.mReq_in = req;

	req = dtf_request_new( dev->pg.ep_out, 512 );
	if(!req) {
		_dbgmsg( "create request error\n" );
		return -ENODEV;
	}
	req->complete = dtf_complete_out;
	dev->pg.mReq_out = req;

	_dbgmsg( "OUT\n" );
	return 0;
}
コード例 #3
0
ファイル: f_thor.c プロジェクト: CheezeCake/edison-u-boot
static int thor_func_bind(struct usb_configuration *c, struct usb_function *f)
{
	struct usb_gadget *gadget = c->cdev->gadget;
	struct f_thor *f_thor = func_to_thor(f);
	struct thor_dev *dev;
	struct usb_ep *ep;
	int status;

	thor_func = f_thor;
	dev = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*dev));
	if (!dev)
		return -ENOMEM;

	memset(dev, 0, sizeof(*dev));
	dev->gadget = gadget;
	f_thor->dev = dev;

	debug("%s: usb_configuration: 0x%p usb_function: 0x%p\n",
	      __func__, c, f);
	debug("f_thor: 0x%p thor: 0x%p\n", f_thor, dev);

	/* EP0  */
	/* preallocate control response and buffer */
	dev->req = usb_ep_alloc_request(gadget->ep0, 0);
	if (!dev->req) {
		status = -ENOMEM;
		goto fail;
	}
	dev->req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE,
				 THOR_PACKET_SIZE);
	if (!dev->req->buf) {
		status = -ENOMEM;
		goto fail;
	}

	dev->req->complete = thor_setup_complete;

	/* DYNAMIC interface numbers assignments */
	status = usb_interface_id(c, f);

	if (status < 0)
		goto fail;

	thor_downloader_intf_int.bInterfaceNumber = status;
	thor_downloader_cdc_union.bMasterInterface0 = status;

	status = usb_interface_id(c, f);

	if (status < 0)
		goto fail;

	thor_downloader_intf_data.bInterfaceNumber = status;
	thor_downloader_cdc_union.bSlaveInterface0 = status;

	/* allocate instance-specific endpoints */
	ep = usb_ep_autoconfig(gadget, &fs_in_desc);
	if (!ep) {
		status = -ENODEV;
		goto fail;
	}

	if (gadget_is_dualspeed(gadget)) {
		hs_in_desc.bEndpointAddress =
				fs_in_desc.bEndpointAddress;
	}

	dev->in_ep = ep; /* Store IN EP for enabling @ setup */
	ep->driver_data = dev;

	ep = usb_ep_autoconfig(gadget, &fs_out_desc);
	if (!ep) {
		status = -ENODEV;
		goto fail;
	}

	if (gadget_is_dualspeed(gadget))
		hs_out_desc.bEndpointAddress =
				fs_out_desc.bEndpointAddress;

	dev->out_ep = ep; /* Store OUT EP for enabling @ setup */
	ep->driver_data = dev;

	ep = usb_ep_autoconfig(gadget, &fs_int_desc);
	if (!ep) {
		status = -ENODEV;
		goto fail;
	}

	dev->int_ep = ep;
	ep->driver_data = dev;

	if (gadget_is_dualspeed(gadget)) {
		hs_int_desc.bEndpointAddress =
				fs_int_desc.bEndpointAddress;

		f->hs_descriptors = (struct usb_descriptor_header **)
			&hs_thor_downloader_function;

		if (!f->hs_descriptors)
			goto fail;
	}

	debug("%s: out_ep:%p out_req:%p\n", __func__,
	      dev->out_ep, dev->out_req);

	return 0;

 fail:
	free(dev);
	return status;
}
コード例 #4
0
ファイル: f_fastboot.c プロジェクト: sky8336/mn201307
static int fastboot_bind(struct usb_gadget *gadget)
{
	struct fastboot_dev *dev = &l_fbdev;
	u8 cdc = 1, zlp = 1;
	struct usb_ep *in_ep, *out_ep;
	int gcnum;
	u8 tmp[7];

	debug("%s controller :%s recognized\n", __func__, gadget->name);
	gcnum = usb_gadget_controller_number(gadget);
	if (gcnum >= 0)
		device_desc.bcdDevice = cpu_to_le16(0x0300 + gcnum);
	else {
		/*
		 * can't assume CDC works.  don't want to default to
		 * anything less functional on CDC-capable hardware,
		 * so we fail in this case.
		 */
		error("controller '%s' not recognized", gadget->name);
		return -ENODEV;
	}

	if (bcdDevice)
		device_desc.bcdDevice = cpu_to_le16(bcdDevice);
	if (iManufacturer)
		strlcpy(manufacturer, iManufacturer, sizeof manufacturer);
	if (iProduct)
		strlcpy(product_desc, iProduct, sizeof product_desc);

	iSerialNumber = get_product_sn();
	device_desc.iSerialNumber = STRING_SERIALNUMBER,
	strlcpy(serial_number, iSerialNumber, sizeof serial_number);

	/* all we really need is bulk IN/OUT */
	usb_ep_autoconfig_reset(gadget);
	in_ep = usb_ep_autoconfig(gadget, &fs_source_desc);
	if (!in_ep) {
autoconf_fail:
		error("can't autoconfigure on %s\n", gadget->name);
		return -ENODEV;
	}
	in_ep->driver_data = in_ep;	/* claim */

	out_ep = usb_ep_autoconfig(gadget, &fs_sink_desc);
	if (!out_ep)
		goto autoconf_fail;
	out_ep->driver_data = out_ep;	/* claim */

	device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
	usb_gadget_set_selfpowered(gadget);

	if (gadget_is_dualspeed(gadget)) {

		/* assumes ep0 uses the same value for both speeds ... */
		dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0;

		/* and that all endpoints are dual-speed */
		hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress;
		hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress;
	}

	dev->network_started = 0;
	dev->in_ep = in_ep;
	dev->out_ep = out_ep;

	/* preallocate control message data and buffer */
	dev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
	if (!dev->req)
		goto fail;
	dev->req->buf = control_req;
	dev->req->complete = fastboot_setup_complete;

	/* ... and maybe likewise for status transfer */

	/* finish hookup to lower layer ... */
	dev->gadget = gadget;
	set_gadget_data(gadget, dev);
	gadget->ep0->driver_data = dev;

	debug("bind controller with the driver\n");
	/*
	 * two kinds of host-initiated state changes:
	 *  - iff DATA transfer is active, carrier is "on"
	 *  - tx queueing enabled if open *and* carrier is "on"
	 */
	return 0;

fail:
	error("%s failed", __func__);
	fastboot_unbind(gadget);
	return -ENOMEM;
}
コード例 #5
0
ファイル: f_fastboot.c プロジェクト: sky8336/mn201307
/*
 * The setup() callback implements all the ep0 functionality that's not
 * handled lower down.  CDC has a number of less-common features:
 *
 *  - two interfaces:  control, and ethernet data
 *  - Ethernet data interface has two altsettings:  default, and active
 *  - class-specific descriptors for the control interface
 *  - class-specific control requests
 */
static int fastboot_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{
	struct fastboot_dev *dev = get_gadget_data(gadget);
	struct usb_request *req = dev->req;
	int value = -EOPNOTSUPP;
	u16 wIndex = le16_to_cpu(ctrl->wIndex);
	u16 wValue = le16_to_cpu(ctrl->wValue);
	u16 wLength = le16_to_cpu(ctrl->wLength);

	/*
	 * descriptors just go into the pre-allocated ep0 buffer,
	 * while config change events may enable network traffic.
	 */

	debug("%s\n", __func__);

	req->complete = fastboot_setup_complete;
	switch (ctrl->bRequest) {

	case USB_REQ_GET_DESCRIPTOR:
		if (ctrl->bRequestType != USB_DIR_IN)
			break;
		switch (wValue >> 8) {

		case USB_DT_DEVICE:
			value = min(wLength, (u16) sizeof device_desc);
			memcpy(req->buf, &device_desc, value);
			break;
		case USB_DT_DEVICE_QUALIFIER:
			if (!gadget_is_dualspeed(gadget))
				break;
			value = min(wLength, (u16) sizeof dev_qualifier);
			memcpy(req->buf, &dev_qualifier, value);
			break;

		case USB_DT_OTHER_SPEED_CONFIG:
			if (!gadget_is_dualspeed(gadget))
				break;
			/* FALLTHROUGH */
		case USB_DT_CONFIG:
			value = config_buf(gadget, req->buf, wValue >> 8, wValue & 0xff, gadget_is_otg(gadget));
			if (value >= 0)
				value = min(wLength, (u16) value);
			break;

		case USB_DT_STRING:
			value = usb_gadget_get_string(&stringtab, wValue & 0xff, req->buf);

			if (value >= 0)
				value = min(wLength, (u16) value);

			break;
		}
		break;

	case USB_REQ_SET_CONFIGURATION:
		if (ctrl->bRequestType != 0)
			break;
		value = fastboot_set_config(dev, wValue, GFP_ATOMIC);
		l_fbdev.network_started = 1;
		break;
	case USB_REQ_GET_CONFIGURATION:
		if (ctrl->bRequestType != USB_DIR_IN)
			break;
		*(u8 *) req->buf = dev->config;
		value = min(wLength, (u16) 1);
		break;

	case USB_REQ_SET_INTERFACE:
		if (ctrl->bRequestType != USB_RECIP_INTERFACE || !dev->config || wIndex > 1)
			break;
		/*
		 * FIXME this is wrong, as is the assumption that
		 * all non-PXA hardware talks real CDC ...
		 */
		debug("set_interface ignored!\n");

done_set_intf:
		break;
	case USB_REQ_GET_INTERFACE:
		if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE)
		    || !dev->config || wIndex > 1)
			break;

		/* for CDC, iff carrier is on, data interface is active. */
		if (wIndex != 1)
			*(u8 *) req->buf = 0;
		else {
			/* *(u8 *)req->buf = netif_carrier_ok (dev->net) ? 1 : 0; */
			/* carrier always ok ... */
			*(u8 *) req->buf = 1;
		}
		value = min(wLength, (u16) 1);
		break;
	default:
		debug("unknown control req%02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, wValue, wIndex, wLength);
	}

	/* respond with data transfer before status phase? */
	if (value >= 0) {
		debug("respond with data transfer before status phase\n");
		req->length = value;
		req->zero = value < wLength && (value % gadget->ep0->maxpacket) == 0;
		value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);
		if (value < 0) {
			debug("ep_queue --> %d\n", value);
			req->status = 0;
			fastboot_setup_complete(gadget->ep0, req);
		}
	}

	/* host either stalls (value < 0) or reports success */
	return value;
}
コード例 #6
0
ファイル: f_fastboot.c プロジェクト: sky8336/mn201307
/* maxpacket and other transfer characteristics vary by speed. */
static inline struct usb_endpoint_descriptor *ep_choose(struct usb_gadget *g, struct usb_endpoint_descriptor *hs, struct usb_endpoint_descriptor *fs)
{
	if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
		return hs;
	return fs;
}
コード例 #7
0
static int
gser_bind(struct usb_configuration *c, struct usb_function *f)
{
	struct usb_composite_dev *cdev = c->cdev;
	struct f_gser		*gser = func_to_gser(f);
	int			status;
	struct usb_ep		*ep;
#ifdef CONFIG_PANTECH_ANDROID_USB
	ushort vid;
#endif
	/* allocate instance-specific interface IDs */
#ifdef CONFIG_PANTECH_ANDROID_USB
	vid = getVendorID();
	if(vid == 0x05C6){
		//printk("^^^^ It's Qualcomm gser\n");
	status = usb_interface_id(c, f);
	if (status < 0)
		goto fail;
	gser->data_id = status;
	gser_interface_desc.bInterfaceNumber = status;
	}else{
		//printk("^^^^ It's SKY gser\n");
	status = usb_interface_id(c, f);
	if (status < 0)
		goto fail;
	gser->data_id = status; //data_id : cdc interface number
	gser_acm_cdc_interface_desc.bInterfaceNumber = status;

	//acm interface
	status = usb_interface_id(c, f);
	if (status < 0)
		goto fail;
	gser_acm_data_interface_desc.bInterfaceNumber = status;
	}
#else
	status = usb_interface_id(c, f);
	if (status < 0)
		goto fail;
	gser->data_id = status;
	gser_interface_desc.bInterfaceNumber = status;
#endif

	status = -ENODEV;

	/* allocate instance-specific endpoints */
	ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc);
	if (!ep)
		goto fail;
	gser->port.in = ep;
	ep->driver_data = cdev;	/* claim */

	ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc);
	if (!ep)
		goto fail;
	gser->port.out = ep;
	ep->driver_data = cdev;	/* claim */

#ifdef CONFIG_MODEM_SUPPORT
	ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_notify_desc);
	if (!ep)
		goto fail;
	gser->notify = ep;
	ep->driver_data = cdev;	/* claim */
	/* allocate notification */
	gser->notify_req = gs_alloc_req(ep,
			sizeof(struct usb_cdc_notification) + 2,
			GFP_KERNEL);
	if (!gser->notify_req)
		goto fail;

	gser->notify_req->complete = gser_notify_complete;
	gser->notify_req->context = gser;
#endif

	/* copy descriptors, and track endpoint copies */
#ifdef CONFIG_PANTECH_ANDROID_USB
	vid = getVendorID();
	if( vid == 0x05C6){
		f->descriptors = usb_copy_descriptors(gser_fs_function);
	}else{
		f->descriptors = usb_copy_descriptors(pantech_gser_fs_function);
	}
#else
	f->descriptors = usb_copy_descriptors(gser_fs_function);
#endif

	if (!f->descriptors)
		goto fail;

	/* support all relevant hardware speeds... we expect that when
	 * hardware is dual speed, all bulk-capable endpoints work at
	 * both speeds
	 */
	if (gadget_is_dualspeed(c->cdev->gadget)) {
		gser_hs_in_desc.bEndpointAddress =
				gser_fs_in_desc.bEndpointAddress;
		gser_hs_out_desc.bEndpointAddress =
				gser_fs_out_desc.bEndpointAddress;
#ifdef CONFIG_MODEM_SUPPORT
		gser_hs_notify_desc.bEndpointAddress =
				gser_fs_notify_desc.bEndpointAddress;
#endif

		/* copy descriptors, and track endpoint copies */
#ifdef CONFIG_PANTECH_ANDROID_USB
	if( vid == 0x05C6){
		f->hs_descriptors = usb_copy_descriptors(gser_hs_function);
	}else{
		f->hs_descriptors = usb_copy_descriptors(pantech_gser_hs_function);
	}
#else
		f->hs_descriptors = usb_copy_descriptors(gser_hs_function);
#endif

		if (!f->hs_descriptors)
			goto fail;

	}
	if (gadget_is_superspeed(c->cdev->gadget)) {
		gser_ss_in_desc.bEndpointAddress =
			gser_fs_in_desc.bEndpointAddress;
		gser_ss_out_desc.bEndpointAddress =
			gser_fs_out_desc.bEndpointAddress;

		/* copy descriptors, and track endpoint copies */
		f->ss_descriptors = usb_copy_descriptors(gser_ss_function);
		if (!f->ss_descriptors)
			goto fail;
	}

	DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
			gser->port_num,
			gadget_is_superspeed(c->cdev->gadget) ? "super" :
			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
			gser->port.in->name, gser->port.out->name);
	return 0;

fail:
	if (f->descriptors)
		usb_free_descriptors(f->descriptors);
#ifdef CONFIG_MODEM_SUPPORT
	if (gser->notify_req)
		gs_free_req(gser->notify, gser->notify_req);

	/* we might as well release our claims on endpoints */
	if (gser->notify)
		gser->notify->driver_data = NULL;
#endif
	/* we might as well release our claims on endpoints */
	if (gser->port.out)
		gser->port.out->driver_data = NULL;
	if (gser->port.in)
		gser->port.in->driver_data = NULL;

	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);

	return status;
}
コード例 #8
0
ファイル: f_uvc.c プロジェクト: flwh/Alcatel_OT_985_kernel
int __init
uvc_bind_config(struct usb_configuration *c,
		const struct uvc_descriptor_header * const *control,
		const struct uvc_descriptor_header * const *fs_streaming,
		const struct uvc_descriptor_header * const *hs_streaming)
{
	struct uvc_device *uvc;
	int ret = 0;

	/* TODO Check if the USB device controller supports the required
	 * features.
	 */
	if (!gadget_is_dualspeed(c->cdev->gadget))
		return -EINVAL;

	uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
	if (uvc == NULL)
		return -ENOMEM;

	uvc->state = UVC_STATE_DISCONNECTED;

	/* Validate the descriptors. */
	if (control == NULL || control[0] == NULL ||
	    control[0]->bDescriptorSubType != UVC_DT_HEADER)
		goto error;

	if (fs_streaming == NULL || fs_streaming[0] == NULL ||
	    fs_streaming[0]->bDescriptorSubType != UVC_DT_INPUT_HEADER)
		goto error;

	if (hs_streaming == NULL || hs_streaming[0] == NULL ||
	    hs_streaming[0]->bDescriptorSubType != UVC_DT_INPUT_HEADER)
		goto error;

	uvc->desc.control = control;
	uvc->desc.fs_streaming = fs_streaming;
	uvc->desc.hs_streaming = hs_streaming;

	/* Allocate string descriptor numbers. */
	if ((ret = usb_string_id(c->cdev)) < 0)
		goto error;
	uvc_en_us_strings[UVC_STRING_ASSOCIATION_IDX].id = ret;
	uvc_iad.iFunction = ret;

	if ((ret = usb_string_id(c->cdev)) < 0)
		goto error;
	uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id = ret;
	uvc_control_intf.iInterface = ret;

	if ((ret = usb_string_id(c->cdev)) < 0)
		goto error;
	uvc_en_us_strings[UVC_STRING_STREAMING_IDX].id = ret;
	uvc_streaming_intf_alt0.iInterface = ret;
	uvc_streaming_intf_alt1.iInterface = ret;

	/* Register the function. */
	uvc->func.name = "uvc";
	uvc->func.strings = uvc_function_strings;
	uvc->func.bind = uvc_function_bind;
	uvc->func.unbind = uvc_function_unbind;
	uvc->func.get_alt = uvc_function_get_alt;
	uvc->func.set_alt = uvc_function_set_alt;
	uvc->func.disable = uvc_function_disable;
	uvc->func.setup = uvc_function_setup;

	ret = usb_add_function(c, &uvc->func);
	if (ret)
		kfree(uvc);

	return 0;

error:
	kfree(uvc);
	return ret;
}