usb gadget driver 之一UDC driver

linux内核版本是2.6.32.2

1、platform_driver_register(&udc_driver_24x0);

UDC驱动是作为platform driver向platform子系统注册的,因此需要实现platform driver。

其中以s3c2410_udc_probe和s3c2410_udc_remove最为重要

static int s3c2410_udc_probe(struct platform_device *pdev)
{
	struct s3c2410_udc *udc = &memory;
//s3c2410的UDC设备,在其中对usb gadget设备对象和端点进行了初始化
        struct device *dev = &pdev->dev;
	int retval;
	int irq;

	dev_dbg(dev, "%s()\n", __func__);
//获取总线时钟并使能
	usb_bus_clock = clk_get(NULL, "usb-bus-gadget");
	if (IS_ERR(usb_bus_clock)) {
		dev_err(dev, "failed to get usb bus clock source\n");
		return PTR_ERR(usb_bus_clock);
	}
	clk_enable(usb_bus_clock);

//获取设备时钟并使能
	udc_clock = clk_get(NULL, "usb-device");
	if (IS_ERR(udc_clock)) {
		dev_err(dev, "failed to get udc clock source\n");
		return PTR_ERR(udc_clock);
	}
	clk_enable(udc_clock);

	mdelay(10);

	dev_dbg(dev, "got and enabled clocks\n");

	if (strncmp(pdev->name, "s3c2440", 7) == 0) {
		dev_info(dev, "S3C2440: increasing FIFO to 128 bytes\n");
		memory.ep[1].fifo_size = S3C2440_EP_FIFO_SIZE;
		memory.ep[2].fifo_size = S3C2440_EP_FIFO_SIZE;
		memory.ep[3].fifo_size = S3C2440_EP_FIFO_SIZE;
		memory.ep[4].fifo_size = S3C2440_EP_FIFO_SIZE;
	}

	spin_lock_init (&udc->lock);//初始化设备的自旋锁
	udc_info = pdev->dev.platform_data;

	rsrc_start = S3C2410_PA_USBDEV;//s3c2410 UDC端口的起始地址
	rsrc_len   = S3C24XX_SZ_USBDEV;//s3c2410 端口地址长度

	if (!request_mem_region(rsrc_start, rsrc_len, gadget_name))
		return -EBUSY;

	base_addr = ioremap(rsrc_start, rsrc_len);
	if (!base_addr) {
		retval = -ENOMEM;
		goto err_mem;
	}

	device_initialize(&udc->gadget.dev);//初始化设备对象
	udc->gadget.dev.parent = &pdev->dev;//当前UDC设备的父设备对象是platform device
	udc->gadget.dev.dma_mask = pdev->dev.dma_mask;

	the_controller = udc;
	platform_set_drvdata(pdev, udc);
//驱动和设备绑定,在platform_device结构中保存udc 设备对象

	s3c2410_udc_disable(udc);
	s3c2410_udc_reinit(udc);

	/* irq setup after old hardware state is cleaned up */
	retval = request_irq(IRQ_USBD, s3c2410_udc_irq, IRQF_DISABLED, gadget_name, udc);
	if (retval != 0) {
		dev_err(dev, "cannot get irq %i, err %d\n", IRQ_USBD, retval);
		retval = -EBUSY;
		goto err_map;
	}

	dev_dbg(dev, "got irq %i\n", IRQ_USBD);

	if (udc_info && udc_info->vbus_pin > 0) {
		retval = gpio_request(udc_info->vbus_pin, "udc vbus");
		if (retval < 0) {
			dev_err(dev, "cannot claim vbus pin\n");
			goto err_int;
		}

		irq = gpio_to_irq(udc_info->vbus_pin);
		if (irq < 0) {
			dev_err(dev, "no irq for gpio vbus pin\n");
			goto err_gpio_claim;
		}

		retval = request_irq(irq, s3c2410_udc_vbus_irq, IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, gadget_name, udc);
		if (retval != 0) {
			dev_err(dev, "can't get vbus irq %d, err %d\n",irq, retval);
			retval = -EBUSY;
			goto err_gpio_claim;
		}

		dev_dbg(dev, "got irq %i\n", irq);
	} else {
		udc->vbus = 1;
	}

	if (s3c2410_udc_debugfs_root) {
		udc->regs_info = debugfs_create_file("registers", S_IRUGO,
				s3c2410_udc_debugfs_root,
				udc, &s3c2410_udc_debugfs_fops);
		if (!udc->regs_info)
			dev_warn(dev, "debugfs file creation failed\n");
	}

	dev_dbg(dev, "probe ok\n");

	return 0;

err_gpio_claim:
	if (udc_info && udc_info->vbus_pin > 0)
		gpio_free(udc_info->vbus_pin);
err_int:
	free_irq(IRQ_USBD, udc);
err_map:
	iounmap(base_addr);
err_mem:
	release_mem_region(rsrc_start, rsrc_len);

	return retval;
}

从s3c2410_udc_probe函数可以看出,probe函数主要完成的就是将platform_device设备对象和UDC 设备对象建立关系,UDC 设备和驱动的一些初始化工作,并申请驱动所需要的资源,如端口区间,中断号等,并将中断函数和中断号绑定。这个中断处理函数很重要,所有的操作都将从中断函数入口。

static int s3c2410_udc_remove(struct platform_device *pdev)
{
	struct s3c2410_udc *udc = platform_get_drvdata(pdev);
	unsigned int irq;

	dev_dbg(&pdev->dev, "%s()\n", __func__);
	if (udc->driver)//设备驱动的usb_gadget_driver对象,说明对象正在使用
		return -EBUSY;

	debugfs_remove(udc->regs_info);//移除debugfs文件系统中建立的文件

	if (udc_info && udc_info->vbus_pin > 0) {
		irq = gpio_to_irq(udc_info->vbus_pin);
		free_irq(irq, udc);
	}
//释放中断
	free_irq(IRQ_USBD, udc);
//释放端口资源
	iounmap(base_addr);
	release_mem_region(rsrc_start, rsrc_len);
//解除绑定
	platform_set_drvdata(pdev, NULL);
//释放时钟
	if (!IS_ERR(udc_clock) && udc_clock != NULL) {
		clk_disable(udc_clock);
		clk_put(udc_clock);
		udc_clock = NULL;
	}
	if (!IS_ERR(usb_bus_clock) && usb_bus_clock != NULL) {
		clk_disable(usb_bus_clock);
		clk_put(usb_bus_clock);
		usb_bus_clock = NULL;
	}

	dev_dbg(&pdev->dev, "%s: remove ok\n", __func__);
	return 0;
}
可以看出,remove函数基本上是probe函数的逆操作,将probe函数中申请的资源释放掉。


2、UDC驱动程序还需要为上层实现usb_gadget_register_driver和usb_gadget_unregister_driver两个gadget driver注册接口,这两个函数将实现gadget driver和udc driver绑定。

int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
	struct s3c2410_udc *udc = the_controller;
	int		retval;

	dprintk(DEBUG_NORMAL, "usb_gadget_register_driver() '%s'\n", driver->driver.name);

	/* Sanity checks */
	if (!udc)
		return -ENODEV;
//UDC设备只能绑定一个gadget driver对象
	if (udc->driver)
		return -EBUSY;

//检查gadget driver是否实现了绑定函数、setup函数
//同时低速设备也不支持gadget drivers
	if (!driver->bind || !driver->setup || driver->speed < USB_SPEED_FULL) {
		printk(KERN_ERR "Invalid driver: bind %p setup %p speed %d\n", driver->bind, driver->setup, driver->speed);
		return -EINVAL;
	}

//支持卸载的话还要实现unbind函数
#if defined(MODULE)
	if (!driver->unbind) {
		printk(KERN_ERR "Invalid driver: no unbind method\n");
		return -EINVAL;
	}
#endif

	/* Hook the driver */
	udc->driver = driver;
	udc->gadget.dev.driver = &driver->driver;
//这里赋值的driver是struct device_driver结构,供linux设备模型使用  

	/* Bind the driver */
	if ((retval = device_add(&udc->gadget.dev)) != 0) {
		printk(KERN_ERR "Error in device_add() : %d\n",retval);
		goto register_error;
	}
//udc->gadget.dev是struct device 结构,这是向linux设备模型核心注册设备 

	dprintk(DEBUG_NORMAL, "binding gadget driver '%s'\n", driver->driver.name);

	if ((retval = driver->bind (&udc->gadget)) != 0) {
		device_del(&udc->gadget.dev);
		goto register_error;
	}
//s3c2410_udc层向设备层传递usb_gadget

	/* Enable udc */
	s3c2410_udc_enable(udc);

	return 0;

register_error:
	udc->driver = NULL;
	udc->gadget.dev.driver = NULL;
	return retval;
}

int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
{
	struct s3c2410_udc *udc = the_controller;

	if (!udc)
		return -ENODEV;
//驱动必须和注册时的驱动是一致的,同时实现了unbind函数
	if (!driver || driver != udc->driver || !driver->unbind)
		return -EINVAL;

	dprintk(DEBUG_NORMAL,"usb_gadget_register_driver() '%s'\n", driver->driver.name);

	driver->unbind(&udc->gadget);

	device_del(&udc->gadget.dev);
	udc->driver = NULL;

	/* Disable udc */
	s3c2410_udc_disable(udc);

	return 0;
}

3、中断处理函数是UDC驱动层的核心函数,由于UDC是从设备,主机端是控制端,所有的操作都是主机端发起的,所以中断处理函数是UDC驱动层的核心函数。

static irqreturn_t s3c2410_udc_irq(int dummy, void *_dev)
{
	struct s3c2410_udc *dev = _dev;
	int usb_status;
	int usbd_status;
	int pwr_reg;
	int ep0csr;
	int i;
	u32 idx;
	unsigned long flags;

//自旋锁,保护dev这个结构避免并发引起的竞态,
//因为是单处理器。这里的自旋锁退化成了一个禁止内核抢占的开关,上锁就是禁止内核抢占
	spin_lock_irqsave(&dev->lock, flags);

	/* Driver connected ? */
//当没有初始化好USB设备而发生中断时,清除中断标志  
	if (!dev->driver) {
		/* Clear interrupts */
		udc_write(udc_read(S3C2410_UDC_USB_INT_REG), S3C2410_UDC_USB_INT_REG);
		udc_write(udc_read(S3C2410_UDC_EP_INT_REG), S3C2410_UDC_EP_INT_REG);
	}

	/* Save index */
	idx = udc_read(S3C2410_UDC_INDEX_REG);

	/* Read status registers */
	usb_status = udc_read(S3C2410_UDC_USB_INT_REG);
	usbd_status = udc_read(S3C2410_UDC_EP_INT_REG);
	pwr_reg = udc_read(S3C2410_UDC_PWR_REG);

	udc_writeb(base_addr, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG);
	ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG);

	dprintk(DEBUG_NORMAL, "usbs=%02x, usbds=%02x, pwr=%02x ep0csr=%02x\n",
		usb_status, usbd_status, pwr_reg, ep0csr);

	/*
	 * Now, handle interrupts. There's two types :
	 * - Reset, Resume, Suspend coming -> usb_int_reg
	 * - EP -> ep_int_reg
	 */

	/* RESET */
	if (usb_status & S3C2410_UDC_USBINT_RESET) {
		/* two kind of reset :
		 * - reset start -> pwr reg = 8
		 * - reset end   -> pwr reg = 0
		 **/
		dprintk(DEBUG_NORMAL, "USB reset csr %x pwr %x\n",
			ep0csr, pwr_reg);

		dev->gadget.speed = USB_SPEED_UNKNOWN;
		udc_write(0x00, S3C2410_UDC_INDEX_REG);
		udc_write((dev->ep[0].ep.maxpacket & 0x7ff) >> 3,
				S3C2410_UDC_MAXP_REG);
		dev->address = 0;

		dev->ep0state = EP0_IDLE;
		dev->gadget.speed = USB_SPEED_FULL;

		/* clear interrupt */
		udc_write(S3C2410_UDC_USBINT_RESET,
				S3C2410_UDC_USB_INT_REG);

		udc_write(idx, S3C2410_UDC_INDEX_REG);
		spin_unlock_irqrestore(&dev->lock, flags);
		return IRQ_HANDLED;
	}

	/* RESUME */
	if (usb_status & S3C2410_UDC_USBINT_RESUME) {
		dprintk(DEBUG_NORMAL, "USB resume\n");

		/* clear interrupt */
		udc_write(S3C2410_UDC_USBINT_RESUME,
				S3C2410_UDC_USB_INT_REG);

		if (dev->gadget.speed != USB_SPEED_UNKNOWN
				&& dev->driver
				&& dev->driver->resume)
			dev->driver->resume(&dev->gadget);
	}

	/* SUSPEND */
	if (usb_status & S3C2410_UDC_USBINT_SUSPEND) {
		dprintk(DEBUG_NORMAL, "USB suspend\n");

		/* clear interrupt */
		udc_write(S3C2410_UDC_USBINT_SUSPEND,
				S3C2410_UDC_USB_INT_REG);

		if (dev->gadget.speed != USB_SPEED_UNKNOWN
				&& dev->driver
				&& dev->driver->suspend)
			dev->driver->suspend(&dev->gadget);

		dev->ep0state = EP0_IDLE;
	}

	/* EP */
	/* control traffic */
	/* check on ep0csr != 0 is not a good idea as clearing in_pkt_ready
	 * generate an interrupt
	 */
	if (usbd_status & S3C2410_UDC_INT_EP0) {
		dprintk(DEBUG_VERBOSE, "USB ep0 irq\n");
		/* Clear the interrupt bit by setting it to 1 */
		udc_write(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_REG);
		s3c2410_udc_handle_ep0(dev);
	}

	/* endpoint data transfers */
	for (i = 1; i < S3C2410_ENDPOINTS; i++) {
		u32 tmp = 1 << i;
		if (usbd_status & tmp) {
			dprintk(DEBUG_VERBOSE, "USB ep%d irq\n", i);

			/* Clear the interrupt bit by setting it to 1 */
			udc_write(tmp, S3C2410_UDC_EP_INT_REG);
			s3c2410_udc_handle_ep(&dev->ep[i]);
		}
	}

	dprintk(DEBUG_VERBOSE, "irq: %d s3c2410_udc_done.\n", IRQ_USBD);

	/* Restore old index */
	udc_write(idx, S3C2410_UDC_INDEX_REG);

	spin_unlock_irqrestore(&dev->lock, flags);

	return IRQ_HANDLED;
}

4、端点操作函数

首先来看中断函数中涉及的两个函数,一个是端点0的处理函数,一个是其他端点的处理函数。

static void s3c2410_udc_handle_ep0(struct s3c2410_udc *dev)
{
	u32			ep0csr;
	struct s3c2410_ep	*ep = &dev->ep[0];
	struct s3c2410_request	*req;
	struct usb_ctrlrequest	crq;

	if (list_empty(&ep->queue))
		req = NULL;
	else
		req = list_entry(ep->queue.next, struct s3c2410_request, queue);

	/* We make the assumption that S3C2410_UDC_IN_CSR1_REG equal to
	 * S3C2410_UDC_EP0_CSR_REG when index is zero */

	udc_write(0, S3C2410_UDC_INDEX_REG);
	ep0csr = udc_read(S3C2410_UDC_IN_CSR1_REG);

	dprintk(DEBUG_NORMAL, "ep0csr %x ep0state %s\n", ep0csr, ep0states[dev->ep0state]);

	/* clear stall status */
	if (ep0csr & S3C2410_UDC_EP0_CSR_SENTSTL) {
		s3c2410_udc_nuke(dev, ep, -EPIPE);
		dprintk(DEBUG_NORMAL, "... clear SENT_STALL ...\n");
		s3c2410_udc_clear_ep0_sst(base_addr);
		dev->ep0state = EP0_IDLE;
		return;
	}

	/* clear setup end */
	if (ep0csr & S3C2410_UDC_EP0_CSR_SE) {
		dprintk(DEBUG_NORMAL, "... serviced SETUP_END ...\n");
		s3c2410_udc_nuke(dev, ep, 0);
		s3c2410_udc_clear_ep0_se(base_addr);
		dev->ep0state = EP0_IDLE;
	}

	switch (dev->ep0state) {
	case EP0_IDLE:
		s3c2410_udc_handle_ep0_idle(dev, ep, &crq, ep0csr);
		break;

	case EP0_IN_DATA_PHASE:			/* GET_DESCRIPTOR etc */
		dprintk(DEBUG_NORMAL, "EP0_IN_DATA_PHASE ... what now?\n");
		if (!(ep0csr & S3C2410_UDC_EP0_CSR_IPKRDY) && req) {
			s3c2410_udc_write_fifo(ep, req);
		}
		break;

	case EP0_OUT_DATA_PHASE:		/* SET_DESCRIPTOR etc */
		dprintk(DEBUG_NORMAL, "EP0_OUT_DATA_PHASE ... what now?\n");
		if ((ep0csr & S3C2410_UDC_EP0_CSR_OPKRDY) && req ) {
			s3c2410_udc_read_fifo(ep,req);
		}
		break;

	case EP0_END_XFER:
		dprintk(DEBUG_NORMAL, "EP0_END_XFER ... what now?\n");
		dev->ep0state = EP0_IDLE;
		break;

	case EP0_STALL:
		dprintk(DEBUG_NORMAL, "EP0_STALL ... what now?\n");
		dev->ep0state = EP0_IDLE;
		break;
	}
}

static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep)
{
	struct s3c2410_request	*req;
	int			is_in = ep->bEndpointAddress & USB_DIR_IN;
	u32			ep_csr1;
	u32			idx;

	if (likely (!list_empty(&ep->queue)))
		req = list_entry(ep->queue.next, struct s3c2410_request, queue);
	else
		req = NULL;

	idx = ep->bEndpointAddress & 0x7F;

	if (is_in) {//需要向主机发送数据
		udc_write(idx, S3C2410_UDC_INDEX_REG);
		ep_csr1 = udc_read(S3C2410_UDC_IN_CSR1_REG);
		dprintk(DEBUG_VERBOSE, "ep%01d write csr:%02x %d\n", idx, ep_csr1, req ? 1 : 0);

		if (ep_csr1 & S3C2410_UDC_ICSR1_SENTSTL) {
			dprintk(DEBUG_VERBOSE, "st\n");
			udc_write(idx, S3C2410_UDC_INDEX_REG);
			udc_write(ep_csr1 & ~S3C2410_UDC_ICSR1_SENTSTL, S3C2410_UDC_IN_CSR1_REG);
			return;
		}

		if (!(ep_csr1 & S3C2410_UDC_ICSR1_PKTRDY) && req) {
			s3c2410_udc_write_fifo(ep,req);
		}
	} else {//从主机接收数据
		udc_write(idx, S3C2410_UDC_INDEX_REG);
		ep_csr1 = udc_read(S3C2410_UDC_OUT_CSR1_REG);
		dprintk(DEBUG_VERBOSE, "ep%01d rd csr:%02x\n", idx, ep_csr1);

		if (ep_csr1 & S3C2410_UDC_OCSR1_SENTSTL) {
			udc_write(idx, S3C2410_UDC_INDEX_REG);
			udc_write(ep_csr1 & ~S3C2410_UDC_OCSR1_SENTSTL, S3C2410_UDC_OUT_CSR1_REG);
			return;
		}

		if ((ep_csr1 & S3C2410_UDC_OCSR1_PKTRDY) && req) {
			s3c2410_udc_read_fifo(ep,req);
		}
	}
}

端点操作函数集:端点的基本操作函数

主要分析queue这个函数,因为上层主要和这个函数打交道,接收或发送数据都需要对应的端点队列提交请求

static int s3c2410_udc_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
{
	struct s3c2410_request	*req = to_s3c2410_req(_req);
	struct s3c2410_ep	*ep = to_s3c2410_ep(_ep);
	struct s3c2410_udc	*dev;
	u32			ep_csr = 0;
	int			fifo_count = 0;
	unsigned long		flags;

	if (unlikely (!_ep || (!ep->desc && ep->ep.name != ep0name))) {
		dprintk(DEBUG_NORMAL, "%s: invalid args\n", __func__);
		return -EINVAL;
	}

	dev = ep->dev;
	if (unlikely (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) {
		return -ESHUTDOWN;
	}

	local_irq_save (flags);

	if (unlikely(!_req || !_req->complete || !_req->buf || !list_empty(&req->queue))) {
		if (!_req)
			dprintk(DEBUG_NORMAL, "%s: 1 X X X\n", __func__);
		else {
			dprintk(DEBUG_NORMAL, "%s: 0 %01d %01d %01d\n", __func__, !_req->complete,!_req->buf, !list_empty(&req->queue));
		}

		local_irq_restore(flags);
		return -EINVAL;
	}

	_req->status = -EINPROGRESS;
	_req->actual = 0;

	dprintk(DEBUG_VERBOSE, "%s: ep%x len %d\n", __func__, ep->bEndpointAddress, _req->length);

	if (ep->bEndpointAddress) {
		udc_write(ep->bEndpointAddress & 0x7F, S3C2410_UDC_INDEX_REG);
		ep_csr = udc_read((ep->bEndpointAddress & USB_DIR_IN) ? S3C2410_UDC_IN_CSR1_REG : S3C2410_UDC_OUT_CSR1_REG);
		fifo_count = s3c2410_udc_fifo_count_out();
	} else {
		udc_write(0, S3C2410_UDC_INDEX_REG);
		ep_csr = udc_read(S3C2410_UDC_IN_CSR1_REG);
		fifo_count = s3c2410_udc_fifo_count_out();
	}

	/* kickstart this i/o queue? */
	if (list_empty(&ep->queue) && !ep->halted) {
		if (ep->bEndpointAddress == 0 /* ep0 */) {
			switch (dev->ep0state) {
			case EP0_IN_DATA_PHASE:
				if (!(ep_csr&S3C2410_UDC_EP0_CSR_IPKRDY) && s3c2410_udc_write_fifo(ep, req)) {
					dev->ep0state = EP0_IDLE;
					req = NULL;
				}
				break;

			case EP0_OUT_DATA_PHASE:
				if ((!_req->length) || ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY)	&& s3c2410_udc_read_fifo(ep, req))) {
					dev->ep0state = EP0_IDLE;
					req = NULL;
				}
				break;

			default:
				local_irq_restore(flags);
				return -EL2HLT;
			}
		} 
		else if ((ep->bEndpointAddress & USB_DIR_IN) != 0 && (!(ep_csr&S3C2410_UDC_OCSR1_PKTRDY)) && s3c2410_udc_write_fifo(ep, req)) {
			req = NULL;
		} else if ((ep_csr & S3C2410_UDC_OCSR1_PKTRDY) && fifo_count && s3c2410_udc_read_fifo(ep, req)) {
			req = NULL;
		}
	}

	/* pio or dma irq handler advances the queue. */
	if (likely (req != 0))
		list_add_tail(&req->queue, &ep->queue);

	local_irq_restore(flags);

	dprintk(DEBUG_VERBOSE, "%s ok\n", __func__);
	return 0;
}

UDC驱动中还有一个重要函数,在数据传输或接收完成,即一个请求完成之后,会调用这个请求的完成函数来通知上层驱动。

static void s3c2410_udc_done(struct s3c2410_ep *ep, struct s3c2410_request *req, int status)
{
	unsigned halted = ep->halted;

	list_del_init(&req->queue);//将这个请求从端点请求队列中删除

	if (likely (req->req.status == -EINPROGRESS))
		req->req.status = status;
	else
		status = req->req.status;

	ep->halted = 1;
	req->req.complete(&ep->ep, &req->req);
	ep->halted = halted;
}

总结:

       UDC设备驱动层的源码就分析得差不多了,其他很多函数都是操作寄存器,与UDC设备密切相关,但总的来说完成的功能都是一致的。可以发现,在UDC设备驱动层主要需要做以下几个工作:

1. 对usb_gadget、usb_ep、usb_request三个标准数据结构进行封装,根据自己UDC的一些设备特性,设计对应的自己的数据结构;

2. 实现platform_driver数据结构中的函数,将UDC设备驱动向platform系统进行注册;

3. 实现usb_gadget_ops函数集,这些函数主要是操作UDC设备的一些特性(针对设备);

4. 实现usb_ep_ops函数集,这些函数主要是操作端点的功能,如请求分配和提交等(针对端点);

5. 实现UDC设备的中断处理函数,这个函数基本上就是UDC设备驱动的核心;

6.实现上层功能驱动注册接口函数:

     int usb_gadget_register_driver(struct usb_gadget_driver *driver)

     int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)


  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值