转载:http://blog.csdn.net/aleon_liao/article/details/8573254
http://blog.csdn.net/lushengchu_luis/article/details/17628937
利用Linux USB gadget设备驱动可以实现一些比较有意思的功能,举两个例子: 1、一个嵌入式产品中的某个存储设备,或是一个存储设备的某个分区,可以作为一个U盘被PC;设别,从而非常方便的完成文件交互,这个功能被广泛的应用于手机、数码相机等产品中。2、一个嵌入式设备通过USB连接到你的PC后,在你的PC端会出现一个新的网络连接,在嵌入式设备上也会有一个网卡设备,你可以配置它们的IP地址,并进行网络通讯,俗称USBNET。
所有USB通讯的设备端都有usb device程序,通常称它们为usb固件。在一些功能简单的设备里,用一些专用的可编程USB控制器就可以了。而在一些运行了类似linux操作系统的复杂的嵌入式系统中,要完成usb device程序,就会要求你不仅熟悉usb device控制器的操作,还要熟悉操作系统的驱动架构。
我想通过 “功能体验”、“驱动调试”、“gadget驱动结构分析”、“编写一个自己的gadget驱动”这4个方面解析linux usb gadget设备驱动的编写方法。
一、linux模拟U盘功能的实现
在硬件环境为华清远见的fs2410平台,软件环境为linux-2.6.26的linux系统上,实现模拟U盘的功能。
向内核添加代码
#include
#include
#include
修改arch/arm/mach-s3c2410/mach-smdk2410.c
/*USB device上拉电阻处理 */
static void smdk2410_udc_pullup(enum s3c2410_udc_cmd_e cmd)
{
u8 *s3c2410_pullup_info[] = {
" ",
"Pull-up enable",
"Pull-up disable",
"UDC reset, in case of"
};
printk("smdk2410_udc: %s/n",s3c2410_pullup_info[cmd]);
s3c2410_gpio_cfgpin(S3C2410_GPG9, S3C2410_GPG9_OUTP);
switch (cmd)
{
case S3C2410_UDC_P_ENABLE :
s3c2410_gpio_setpin(S3C2410_GPG9, 1); //set gpg9 output HIGH
break;
case S3C2410_UDC_P_DISABLE :
s3c2410_gpio_setpin(S3C2410_GPG9, 0); //set gpg9 output LOW
break;
case S3C2410_UDC_P_RESET :
//FIXME!!!
break;
default:
break;
}
}
static struct s3c2410_udc_mach_info smdk2410_udc_cfg __initdata = {
.udc_command = smdk2410_udc_pullup,
};
static struct platform_device *smdk2410_devices[] __initdata = {
…,
&s3c_device_usbgadget, /*USB gadget device设备登记*/
};
static void __init sdmk2410_init(void)
{
u32 upll_value;
set_s3c2410fb_info(&smdk2410_lcdcfg);
s3c24xx_udc_set_platdata(&smdk2410_udc_cfg); /* 初始化*/
s3c_device_sdi.dev.platform_data = &smdk2410_mmc_cfg;
/* Turn off suspend on both USB ports, and switch the
* selectable USB port to USB device mode. */
s3c2410_modify_misccr(S3C2410_MISCCR_USBHOST |
S3C2410_MISCCR_USBSUSPND0 |
S3C2410_MISCCR_USBSUSPND1, 0x0);
/* 设置USB时钟 */
upll_value = (
0x78 << S3C2410_PLLCON_MDIVSHIFT)
| (0x02 << S3C2410_PLLCON_PDIVSHIFT)
| (0x03 << S3C2410_PLLCON_SDIVSHIFT);
while (upll_value != readl(S3C2410_UPLLCON)) {
writel(upll_value, S3C2410_UPLLCON);
udelay(20);
}
}
修改drivers/usb/gadget/file_storage.c
static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
struct usb_request *req, int *pbusy,
enum fsg_buffer_state *state)
{
int rc;
udelay(800);
……
}
配置内核支持U盘模拟
<*> USB Gadget Support --->
USB Peripheral Controller (S3C2410 USB Device Controller) --->
S3C2410 USB Device Controller
[*] S3C2410 udc debug messages
USB Gadget Drivers
File-backed Storage Gadget
3、编译内核
#make zImage
#make modules
在目录drivers/usb/gadget下生成g_file_storage.ko
加载驱动,测试功能
利用前面的生成的内核,启动系统后,加载g_file_storage.ko
#insmod g_file_storage.ko
# insmod g_file_storage.ko file=/dev/mtdblock2 stall=0 removable=1
0.03 USB: usb_gadget_register_driver() 'g_file_storage'
0.04 USB: binding gadget driver 'g_file_storage'
0.05 USB: s3c2410_set_selfpowered()
g_file_storage gadget: File-backed Storage Gadget, version: 20 October 2004
g_file_storage gadget: Number of LUNs=1
g_file_storage gadget-lun0: ro=0, file: /dev/mtdblock3
0.06 USB: udc_enable called
smdk2410_udc: Pull-up enable
连接设备到windows,windows系统会自动设备到一个新的U盘加入。格式化U盘,存入文件。卸载U盘后,在目标板上执行如下操作:
# mkdir /mnt/gadget
# mount -t vfat /dev/mtdblock2 /mnt/gadget/
#ls
可以看到windows存入U盘的文件。
二、usbnet功能的实现
配置内核支持usbnet
<*> USB Gadget Support --->
USB Peripheral Controller (S3C2410 USB Device Controller) --->
S3C2410 USB Device Controller
[*] S3C2410 udc debug messages
USB Gadget Drivers
Ethernet Gadget (with CDC Ethernet support)
[*] RNDIS support
2、编译内核
#make zImage
#make modules
在目录drivers/usb/gadget下生成g_ether.ko
3、加载驱动,测试功能
利用前面的生成的内核,启动系统后,加载g_ether.ko
#insmod g_ether.ko
#ifconfig usb0 192.168.1.120
……
usb0 Link encap:Ethernet HWaddr 5E:C5:F6:D4:2B:91
inet addr:192.168.1.120 Bcast:192.168.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:253 errors:0 dropped:0 overruns:0 frame:0
TX packets:43 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:35277 (34.4 KiB) TX bytes:10152 (9.9 KiB)
连接设备到windows,windows系统会提示安装驱动,根据提示安装上RNDIS驱动。这个驱动可以在网络上找到。此时windows会新生成一个网络连接,配置它的ip地址等信息。然后就可以和目标系统通过USB实现网络通讯了。
————————————————————————————————————————————————————————————————————————————
gadget就是指一些比较杂的小设备,gadget类的一定是作为usb device用。
前面一篇提到usb作为device的情况,注册完成后,导出usb_gadget_probe_driver函数后就完了。
这个函数就是为gadget驱动准备的,这就要从gadget/Android.c开始分析,先从它的init函数看起:
- static int __init init(void)
- {
- struct android_dev *dev;
- int err;
- 。。。。。。
- android_class = class_create(THIS_MODULE, "android_usb");
- if (IS_ERR(android_class))
- return PTR_ERR(android_class);
-
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev)
- return -ENOMEM;
-
- dev->functions = supported_functions;
- INIT_LIST_HEAD(&dev->enabled_functions);
- INIT_WORK(&dev->work, android_work);
-
- err = android_create_device(dev);
- if (err) {
- class_destroy(android_class);
- kfree(dev);
- return err;
- }
-
- _android_dev = dev;
-
-
- composite_driver.setup = android_setup;
- composite_driver.disconnect = android_disconnect;
-
- return usb_composite_probe(&android_usb_driver, android_bind);
- }
supported_functions代表了gadget驱动支持的类型,如下:
- static struct android_usb_function *supported_functions[] = {
- &adb_function,
- &acm_function,
- &mtp_function,
- &ptp_function,
- &rndis_function,
- &mass_storage_function,
- &accessory_function,
- NULL
- };
他们分别代表adb设备、acm(串口设备)、mtp(ptp的扩展)、ptp(图片传输设备)、rndis(网络设备)、U盘设备、accessory(其他一些小附件)。
不管上述何种设备,都是把它们模拟成一个usb从设备来看待,所以必须要有端点0作为控制传输、还要有其它非零端点用来传输数据。
刚好A10 CPU的usb otg 口有一个控制传输端点,4个批量传输端点和一个中断传输端点,可以符合上述的要求。
所以android.c、composite.c 的作用就是依据USB协议模拟出端点0、设备描述符等一个usb设备枚举过程所要求的功能;至于这个usb设备具体做什么,那就是由接口描述符和端点的功能决定,所以f_mtp.c、f_accessory.c、f_mass_storage.c、f_adb.c、f_acm.c等文件所做的事情的就是实现具体接口和这个接口下的端点的具体功能。
android_usb_driver代表了一个设备描述符所要具备的信息和响应的动作:
- static struct usb_composite_driver android_usb_driver = {
- .name = "android_usb",
- .dev = &device_desc,
- .strings = dev_strings,
- .unbind = android_usb_unbind,
- };
device_desc类型为usb_device_descriptor,定义如下:
- static struct usb_device_descriptor device_desc = {
- .bLength = sizeof(device_desc),
- .bDescriptorType = USB_DT_DEVICE,
- .bcdUSB = __constant_cpu_to_le16(0x0200),
- .bDeviceClass = USB_CLASS_PER_INTERFACE,
- .idVendor = __constant_cpu_to_le16(VENDOR_ID),
- .idProduct = __constant_cpu_to_le16(PRODUCT_ID),
- .bcdDevice = __constant_cpu_to_le16(0xffff),
- .bNumConfigurations = 1,
- };
这就是一个设备描述符的信息。
接着usb_composite_probe注册一个usb_composite_driver到composite.c 中,如果这一步能注册成功,那么gadget驱动就已经准备好了,随时可以相应主机的请求:
- int usb_composite_probe(struct usb_composite_driver *driver,
- int (*bind)(struct usb_composite_dev *cdev))
- {
- if (!driver || !driver->dev || !bind || composite)
- return -EINVAL;
-
- if (!driver->name)
- driver->name = "composite";
- if (!driver->iProduct)
- driver->iProduct = driver->name;
- composite_driver.function = (char *) driver->name;
- composite_driver.driver.name = driver->name;
- composite = driver;
- composite_gadget_bind = bind;
-
- return usb_gadget_probe_driver(&composite_driver, composite_bind);
- }
composite_driver代表一个gadget驱动:
- static struct usb_gadget_driver composite_driver = {
- .speed = USB_SPEED_HIGH,
-
- .unbind = composite_unbind,
-
- .setup = composite_setup,
- .disconnect = composite_disconnect,
-
- .suspend = composite_suspend,
- .resume = composite_resume,
-
- .driver = {
- .owner = THIS_MODULE,
- },
- };
简单的将driver和bind的值保存下来后调用。
usb_gadget_probe_driver函数就是在具体平台中定义的。前面一篇文章说过,这是导出来给gadget驱动用的,它是在usb/sun4i_usb/udc/sw_udc.c中:
- int usb_gadget_probe_driver(struct usb_gadget_driver *driver,
- int (*bind)(struct usb_gadget *))
- {
- 。。。。。。
- if (!bind || !driver->setup || driver->speed < USB_SPEED_FULL) {
- DMSG_PANIC("ERR: Invalid driver: bind %p setup %p speed %d\n",
- bind, driver->setup, driver->speed);
- return -EINVAL;
- }
- 。。。。。。
-
- if ((retval = device_add(&udc->gadget.dev)) != 0) {
- DMSG_PANIC("ERR: Error in device_add() : %d\n",retval);
- goto register_error;
- }
-
- DMSG_INFO_UDC("[%s]: binding gadget driver '%s'\n", gadget_name, driver->driver.name);
-
- if ((retval = bind (&udc->gadget)) != 0) {
- DMSG_PANIC("ERR: Error in bind() : %d\n",retval);
- device_del(&udc->gadget.dev);
- goto register_error;
- }
这里才用device_add将设备添加到设备层,
接着回调bind方法:
- static int composite_bind(struct usb_gadget *gadget)
- {
- struct usb_composite_dev *cdev;
- int status = -ENOMEM;
-
- cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
- if (!cdev)
- return status;
-
- spin_lock_init(&cdev->lock);
- cdev->gadget = gadget;
- set_gadget_data(gadget, cdev);
- INIT_LIST_HEAD(&cdev->configs);
-
-
- cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
- if (!cdev->req)
- goto fail;
- cdev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL);
- if (!cdev->req->buf)
- goto fail;
- cdev->req->complete = composite_setup_complete;
- gadget->ep0->driver_data = cdev;
- 。。。。。。
- if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW)
- usb_gadget_set_selfpowered(gadget);
-
-
-
-
-
- usb_ep_autoconfig_reset(cdev->gadget);
-
-
-
-
-
- status = composite_gadget_bind(cdev);
- 。。。。。。
-
- if (idVendor)
- cdev->desc.idVendor = cpu_to_le16(idVendor);
- if (idProduct)
- cdev->desc.idProduct = cpu_to_le16(idProduct);
- if (bcdDevice)
- cdev->desc.bcdDevice = cpu_to_le16(bcdDevice);
-
-
- if (iManufacturer || !cdev->desc.iManufacturer) {
- if (!iManufacturer && !composite->iManufacturer &&
- !*composite_manufacturer)
- snprintf(composite_manufacturer,
- sizeof composite_manufacturer,
- "%s %s with %s",
- init_utsname()->sysname,
- init_utsname()->release,
- gadget->name);
-
- cdev->manufacturer_override =
- override_id(cdev, &cdev->desc.iManufacturer);
- }
-
- if (iProduct || (!cdev->desc.iProduct && composite->iProduct))
- cdev->product_override =
- override_id(cdev, &cdev->desc.iProduct);
-
- if (iSerialNumber)
- cdev->serial_override =
- override_id(cdev, &cdev->desc.iSerialNumber);
- 。。。。。。
- }
首先认识usb_gadget这个结构体,这是具体平台定义的gadget资源,在sun4i_usb/udc/sw_udc.c中:
- static struct sw_udc sw_udc = {
- .gadget = {
- .ops = &sw_udc_ops,
- .ep0 = &sw_udc.ep[0].ep,
- .name = gadget_name,
- .dev = {
- .init_name = "gadget",
- },
- },
-
-
- .ep[0] = {
- .num = 0,
- .ep = {
- .name = ep0name,
- .ops = &sw_udc_ep_ops,
- .maxpacket = EP0_FIFO_SIZE,
- },
- .dev = &sw_udc,
- },
-
-
- .ep[1] = {
- .num = 1,
- .ep = {
- .name = "ep1-bulk",
- .ops = &sw_udc_ep_ops,
- .maxpacket = SW_UDC_EP_FIFO_SIZE,
- },
- .dev = &sw_udc,
- .fifo_size = (SW_UDC_EP_FIFO_SIZE * (SW_UDC_FIFO_NUM + 1)),
- .bEndpointAddress = 1,
- .bmAttributes = USB_ENDPOINT_XFER_BULK,
- },
- 。。。。。。
- .ep[5] = {
- .num = 5,
- .ep = {
- .name = "ep5-int",
- .ops = &sw_udc_ep_ops,
- .maxpacket = SW_UDC_EP_FIFO_SIZE,
- },
- .dev = &sw_udc,
- .fifo_size = (SW_UDC_EP_FIFO_SIZE * (SW_UDC_FIFO_NUM + 1)),
- .bEndpointAddress = 5,
- .bmAttributes = USB_ENDPOINT_XFER_INT,
- },
- };
一个端点0,4个bulk和一个int传输全部定义在这里,他们的ops指针都指向同一个sw_udc_ep_ops:
- static const struct usb_ep_ops sw_udc_ep_ops = {
- .enable = sw_udc_ep_enable,
- .disable = sw_udc_ep_disable,
-
- .alloc_request = sw_udc_alloc_request,
- .free_request = sw_udc_free_request,
-
- .queue = sw_udc_queue,
- .dequeue = sw_udc_dequeue,
-
- .set_halt = sw_udc_set_halt,
- };
usb_ep_alloc_request是为端点0分配空间对象,这样才能使用。composite_gadget_bind指针指向了android.c的bind函数:
- static int android_bind(struct usb_composite_dev *cdev)
- {
- struct android_dev *dev = _android_dev;
- struct usb_gadget *gadget = cdev->gadget;
- int gcnum, id, ret;
-
- usb_gadget_disconnect(gadget);
-
- ret = android_init_functions(dev->functions, cdev);
- 。。。。。。
- usb_gadget_set_selfpowered(gadget);
- dev->cdev = cdev;
-
- return 0;
- }
主要是做初始化工作以及为pid、vid赋值等。
好像到这里就完成了,都是注册并填充了一些数据、方法等,那何时才相应主机的请求呢?有中断到来的时候。
在sun4i_usb/udc/sw_udc.c中,中断到来就表示主机有请求了,会调用注册的中断函数sw_udc_irq:
- static irqreturn_t sw_udc_irq(int dummy, void *_dev)
- {
- 。。。。。。
- if (tx_irq & USBC_INTTx_FLAG_EP0) {
- DMSG_DBG_UDC("USB ep0 irq\n");
-
-
- USBC_INT_ClearEpPending(g_sw_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, 0);
-
- if(dev->gadget.speed == USB_SPEED_UNKNOWN){
- if(USBC_Dev_QueryTransferMode(g_sw_udc_io.usb_bsp_hdle) == USBC_TS_MODE_HS){
- dev->gadget.speed = USB_SPEED_HIGH;
-
- }else{
- dev->gadget.speed= USB_SPEED_FULL;
- }
- }
-
- sw_udc_handle_ep0(dev);
- }
- 。。。。。。
-
- for (i = 1; i < SW_UDC_ENDPOINTS; i++) {
- u32 tmp = 1 << i;
-
- if (tx_irq & tmp) {
- DMSG_DBG_UDC("USB tx ep%d irq\n", i);
-
-
- USBC_INT_ClearEpPending(g_sw_udc_io.usb_bsp_hdle, USBC_EP_TYPE_TX, i);
-
- sw_udc_handle_ep(&dev->ep[i]);
- }
- }
-
-
- for (i = 1; i < SW_UDC_ENDPOINTS; i++) {
- u32 tmp = 1 << i;
-
- if (rx_irq & tmp) {
- DMSG_DBG_UDC("USB rx ep%d irq\n", i);
-
-
- USBC_INT_ClearEpPending(g_sw_udc_io.usb_bsp_hdle, USBC_EP_TYPE_RX, i);
-
- sw_udc_handle_ep(&dev->ep[i]);
- }
- }
- 。。。。。。
- }
主机肯定首先要和ep0通讯的。sw_udc_handle_ep0把请求类型做标记保存下来,最终调用的是dev->driver->setup(&dev->gadget, crq),也就是android.c中的android_setup函数:
- static int
- android_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c)
- {
- 。。。。。。
- list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
- if (f->ctrlrequest) {
- value = f->ctrlrequest(f, cdev, c);
- if (value >= 0)
- break;
- }
- }
- 。。。。。。
-
-
-
- if (value < 0)
- value = acc_ctrlrequest(cdev, c);
-
- if (value < 0)
- value = composite_setup(gadget, c);
-
- spin_lock_irqsave(&cdev->lock, flags);
- if (!dev->connected) {
- dev->connected = 1;
- schedule_work(&dev->work);
- }
- else if (c->bRequest == USB_REQ_SET_CONFIGURATION && cdev->config) {
- schedule_work(&dev->work);
- }
- spin_unlock_irqrestore(&cdev->lock, flags);
-
- return value;
- }
enabled_list链表的数据,是上层控制的,上层使用到那个功能才会添加到链表中去,anroid系统常使用到的是mass_storage,adb两个。ctrlrequest方法可以不定义,看具体设备而定。
如果acc_ctrlrequest方法无法处理,最终调用composite_setup来处理,它会相应主机的获取描述符,设置地址等请求。比如获取或者设置接口描述符也在这里完成:
- case USB_REQ_SET_INTERFACE:
- if (ctrl->bRequestType != USB_RECIP_INTERFACE)
- goto unknown;
- if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
- break;
- f = cdev->config->interface[intf];
- if (!f)
- break;
- if (w_value && !f->set_alt)
- break;
- value = f->set_alt(f, w_index, w_value);
- if (value == USB_GADGET_DELAYED_STATUS) {
- DBG(cdev,
- "%s: interface %d (%s) requested delayed status\n",
- __func__, intf, f->name);
- cdev->delayed_status++;
- DBG(cdev, "delayed_status count %d\n",
- cdev->delayed_status);
- }
- break;
- case USB_REQ_GET_INTERFACE:
- if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
- goto unknown;
- if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
- break;
- f = cdev->config->interface[intf];
- if (!f)
- break;
-
- value = f->get_alt ? f->get_alt(f, w_index) : 0;
- if (value < 0)
- break;
- *((u8 *)req->buf) = value;
- value = min(w_length, (u16) 1);
- break;