Linux USB Gadget驱动 part1数据传输

       内核gadget驱动主要有两个作用,第一是实现真实usb从设备,常见的UAC、UVC、大容量存储设备等;第二是虚拟一些USB设备,例如Hub、光驱、键盘等。本文作为Linux USB Gadget驱动的第一篇,先介绍下如gadget驱动做简单的数据传输,顺便分析下udc驱动,以便于后面的上层驱动出现问题更好的定位问题。

1、注册function

       内核为了层次分明的描述一个复合设备(在同一个配置下,有多个interface,实现不同的功能即为复合设备,比如usb声卡有一个UAC接口和一个HID接口),把设备描述、配置描述放在一个模块用来表示复合设备;把接口描述、端点描述放在一起用来实现一个function,function代表一个具体的功能。

        本文只介绍简单的数据传输,因此function实现两个功能就好,第一个是向udc驱动的发送队列中queue数据,对应USB IN操作;第二个是向udc驱动的接收队列queue数据,对应了USB OUT操作。(在USB协议中,IN OUT都是相对于HOST来讲的)。这里内核提供了

usb_ep_alloc_request();
usb_ep_queue();

这两个函数,分别用来分配一个gadget请求和queue一个请求。

    in_req = usb_ep_alloc_request(f_rw_data->in_ep, GFP_ATOMIC);
	if (in_req == NULL) {
		return -ENOMEM;
		printk("usb_ep_alloc_request error\n");
	}
	in_req->buf = kzalloc(40 , GFP_ATOMIC);
	memcpy(in_req->buf, send_str, strlen(send_str + 1));
	in_req->length = usb_ep_align(f_rw_data->in_ep,64);
	in_req->complete = rw_in_complete;
	result = usb_ep_queue(f_rw_data->in_ep, in_req, GFP_ATOMIC);
	if (result) {
		ERROR(cdev, "%s queue req --> %d\n",
				f_rw_data->in_ep->name, result);
	}



    out_req = usb_ep_alloc_request(f_rw_data->out_ep, GFP_ATOMIC);
	if (out_req == NULL) {
		return -ENOMEM;
		printk("usb_ep_alloc_request error\n");
	}
	out_req->buf = kmalloc(256 , GFP_ATOMIC);
	out_req->length = usb_ep_align(f_rw_data->out_ep,256);
    
	out_req->complete = rw_out_complete;
	out_req->context  = in_req;

    result = usb_ep_queue(f_rw_data->out_ep, out_req, GFP_ATOMIC);
	if (result) {
		ERROR(cdev, "%s queue req --> %d\n",
				f_rw_data->out_ep->name, result);
	}

以上代码先用usb_ep_alloc_request来分配一个请求,该函数返回usb_request结构,usb_request结构中:

  1、buf成员是实际数据存放的地址,需要为其分配地址空间。

  2、complete成员是数据传输完成的CB函数,注意该函数是中断上下文,注意数据的操作。其余成员按需填写即可。

complete中做的事情很简单。这里的只是简单的打印了收到的数据:

static void rw_in_complete(struct usb_ep *ep, struct usb_request *req)
{
	int	status = req->status;
    printk("status is %d\n", req->status);
    if(status == 0) {
		
		printk("%s\n",(char *)req->buf);
	}
	else {

		usb_ep_free_request(ep, req);
	}
}

   这里只是做了一次数据传输,要想多次传输,在 complete  函数中继续queue就可以了。

  业务数据处理部分完成了,接下来就是按照内核gadget驱动框架填写一些结构了。首先来处理下接口描述符:

/*
 *  1、接口描述符,在f_wr配置下共有一个接口,实现数据的读写操作
 *  2、该接口有两个endpoint,一个读endpoint,一个写endpoint
*/
static struct usb_interface_descriptor rw_intf = {
	.bLength =		sizeof(rw_intf),            /*接口描述符长度*/
	.bDescriptorType =	USB_DT_INTERFACE,       /*描述符类型为接口描述符*/

	.bNumEndpoints =	2,                      /*endpoint*/
	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,  /*0xff 用户自定义接口*/
	/* .iInterface = DYNAMIC */
};

接口描述符的构造可以查阅usb协议ch9,usb_interface_descriptor是按照ch9中的描述定义的。再看下该接口下挂的两个端点,本文中数据传输是双向的,因此需要两个端点:

set_alt

到目前位置,实现了function的所有实例,还差一个入口把这些实例执行起来就可以。基本的入口是module_init,在init中调用usb_function_register函数来注册上面实现的功能实例。

usb_ep_queue

上面代码中,重点关注  

    f_rw_data->function.bind    = readwrite_bind;
    f_rw_data->function.set_alt = readwrite_set_alt;

这两个成员,bind函数是复合设备插入function以后的一个CB函数,一般在该函数中完成私有数据构造、端点配置等。set_alt是function实际数据操作的起点,在该函数中完成前面介绍的usb_ep_alloc_request,使能端点,开始数据传输。

2、复合设备驱动实现

在本文中,只是按照复合设备的框架来实现的,但是只有一个interface,因此从功能上讲只是一个单一功能的usb device。

第一步填充设备描述符的结构:

/*USB 设备描述符*/
static struct usb_device_descriptor device_desc = {
	.bLength =		sizeof device_desc,                     /*描述符长度*/
	.bDescriptorType =	USB_DT_DEVICE,                      /*描述符类型*/

	.bcdUSB =		cpu_to_le16(0x0200),                    /*版本号2.0*/           
	.bDeviceClass =		USB_CLASS_VENDOR_SPEC,              /*0xFF 未设置*/ 

	.idVendor =		cpu_to_le16(DRIVER_VENDOR_NUM),         /*VID*/
	.idProduct =		cpu_to_le16(DRIVER_PRODUCT_NUM),    /*PID*/
	.bNumConfigurations =	1,                              /*设备下的配置数*/
};

第二步实现配置描述的结构:

static struct usb_configuration wr_config  = {
	.label          = "readwrite",
	.bConfigurationValue = 1,
	.bmAttributes   = USB_CONFIG_ATT_SELFPOWER,
	/* .iConfiguration = DYNAMIC */
};

第三步使用module_usb_composite_driver注册复合设备,该函数需要传入usb_composite_driver实例:

usb_ep_queue

该结构中,dev就是第一步中的设备描述符,例外一个重要的成员是bind,该bind函数类似于平台设备驱动的probe函数,在该函数中最重要的操作是通过usb_get_function找到第一节中注册的fuction。

/*
 *	1、目前看来,这个函数在驱动加载时就会被执行,并不需要VID PID的匹配;
 *
 */
static int xfile_bind(struct usb_composite_dev *cdev)
{
	int			status;

    printk("enter xfile_bind\n");

	status = usb_string_ids_tab(cdev, strings_dev);

	device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;
	device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;
	device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id;

	func_inst_wr = usb_get_function_instance("read_write");
	if (IS_ERR(func_inst_wr)) {
		printk("usb_get_function_instance error \n");
		return PTR_ERR(func_inst_wr);
	}
	wr_config.iConfiguration = strings_dev[USB_GADGET_FIRST_AVAIL_IDX].id;

	wr_config.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP;
	wr_config.descriptors = NULL;

	/* support OTG systems */
	if (gadget_is_otg(cdev->gadget)) {
		if (!otg_desc[0]) {
			struct usb_descriptor_header *usb_desc;

			usb_desc = usb_otg_descriptor_alloc(cdev->gadget);
			if (!usb_desc) {
				status = -ENOMEM;
			}
			usb_otg_descriptor_init(cdev->gadget, usb_desc);
			otg_desc[0] = usb_desc;
			otg_desc[1] = NULL;
		}
		wr_config.descriptors = otg_desc;
		wr_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
	}
	usb_add_config_only(cdev, &wr_config);

	func_wr = usb_get_function(func_inst_wr);
	if (IS_ERR(func_wr)) {
		printk("usb_get_function error\n");
		status = PTR_ERR(func_wr);
	}
	status = usb_add_function(&wr_config, func_wr);
	if(status) {
		printk("usb_add_function error\n");
	}

	usb_ep_autoconfig_reset(cdev->gadget);

	usb_composite_overwrite_options(cdev, &coverwrite);


	return 0;
}

附:usb_ep_queue和udc驱动如何交互:

gadget驱动是对不同厂家芯片usb device模式的封装,调用usb_ep_queue最终会调用                ep->ops->queue(ep, req, gfp_flags);函数,下面一段程序来自于xilinx的udc驱动

static int xudc_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
			 gfp_t gfp_flags)
{
	struct xusb_req *req = to_xusb_req(_req);
	struct xusb_ep	*ep  = to_xusb_ep(_ep);
	struct xusb_udc *udc = ep->udc;
	int  ret;
	unsigned long flags;

	if (!ep->desc) {
		dev_dbg(udc->dev, "%s:queing request to disabled %s\n",
			__func__, ep->name);
		return -ESHUTDOWN;
	}

	if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) {
		dev_dbg(udc->dev, "%s, bogus device state\n", __func__);
		return -EINVAL;
	}

	spin_lock_irqsave(&udc->lock, flags);

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

	if (udc->dma_enabled) {
		ret = usb_gadget_map_request(&udc->gadget, &req->usb_req,
					     ep->is_in);
		if (ret) {
			dev_dbg(udc->dev, "gadget_map failed ep%d\n",
				ep->epnumber);
			spin_unlock_irqrestore(&udc->lock, flags);
			return -EAGAIN;
		}
	}

	if (list_empty(&ep->queue)) {
		if (ep->is_in) {
			dev_dbg(udc->dev, "xudc_write_fifo from ep_queue\n");
			if (!xudc_write_fifo(ep, req))
				req = NULL;
		} else {
			dev_dbg(udc->dev, "xudc_read_fifo from ep_queue\n");
			if (!xudc_read_fifo(ep, req))
				req = NULL;
		}
	}

	if (req != NULL)
		list_add_tail(&req->queue, &ep->queue);

	spin_unlock_irqrestore(&udc->lock, flags);
	return 0;
}

1、to_xusb_req 和 to_xusb_ep其实就是container_of函数,因为厂udc驱动的两个成员继承与gadget驱动的usb_ep和xusb_req。先使用container_of函数把指针调整到xusb_req和xusb_ep的位置。

2、判是否DMA操作,一般情况下,udc驱动都使用了流式DMA(因为gadget驱动的数据一般来源于上层的分配,因此只时候做映射、去映射操作,一致性DMA不能满足这种使用场景)。

3、判断队列是否为空,如果为空直接进行IO操作。如果非空,就把此次请求加入到gadget 请求队列。

这样,在自己的驱动程序中就能很轻松的使用usb_ep_queue函数了。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux USB gadget驱动编写有如下几个步骤: 1. 确定USB gadget功能:首先需要确定所需实现USB gadget功能,例如以USB设备的形式提供存储、网络、音频等服务。这样可以决定需要实现USB gadget驱动类型和功能。 2. 编写USB驱动框架:基于Linux内核框架,编写USB gadget驱动的基本框架。这包括注册USB gadget驱动和常用的函数接口等。 3. 实现USB gadget子系统:根据所需的功能,实现USB gadget子系统的模块,如存储、网络或音频子系统等。这些子系统需要封装底层的USB通信协议和数据传输,供应用程序调用。 4. 配置USB gadget驱动:根据具体需求,在系统配置文件中进行必要的配置,以启用和配置USB gadget驱动。这包括配置端点、描述符和功能等。 5. 移植和编译:将驱动程序编译成内核模块,然后将其移植到目标设备上。对于嵌入式设备,可能需要修改硬件相关的代码,以适应硬件平台。 6. 测试和调试:编写测试用例,对USB gadget驱动进行测试和调试,确保其正常工作。这包括对设备和主机之间的数据传输进行验证,以及处理异常情况和错误处理。 总之,编写Linux USB gadget驱动需要明确所需实现的功能、基于内核框架编写驱动框架、实现USB gadget子系统、配置以及移植和编译。最后进行测试和调试,确保驱动程序的正常运行。通过以上步骤,可以实现各种USB设备功能的驱动。 ### 回答2: Linux USB gadget驱动是用于实现USB设备的功能的驱动程序。它使得Linux设备可以作为一个USB设备与其他主机进行通信。在编写Linux USB gadget驱动时,需要完成以下几个步骤。 首先,需要确定设备的功能和属性。USB设备可以有多种功能,如储存设备、键盘、鼠标等。根据设备的类型和规格,确定设备的操作和数据传输方式。 其次,在驱动程序中定义设备USB描述符。USB描述符包括设备描述符、接口描述符和端点描述符等,它们是USB协议的一部分,用于描述设备的属性和功能。 然后,在驱动程序中实现设备的相关功能。根据设备的类型和规格,编写相应的功能代码。例如,如果设备是一个键盘,就需要实现按键事件的处理逻辑;如果设备是一个储存设备,就需要实现读写数据的逻辑。 最后,编译和加载驱动程序。使用Linux内核提供的工具链,将驱动程序编译为可执行文件,并将其加载到Linux内核中运行。加载驱动程序后,系统即可识别设备,并根据驱动程序中定义的功能和属性来处理设备的操作和数据传输。 总之,编写Linux USB gadget驱动需要确定设备的功能和属性、定义USB描述符、实现设备的相关功能,最后编译和加载驱动程序。通过这些步骤,我们可以在Linux系统中实现USB设备的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值