内核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函数了。