当前位置: 代码迷 >> 综合 >> Linux USB composite framework
  详细解决方案

Linux USB composite framework

热度:46   发布时间:2024-02-06 19:44:09.0

最近在搞USB,学习并记录下来

一、Linux USB composite framework

从复合gadget的角度来看,一个设备将一些功能分组到配置中。一个函数可以出现在任意数量的配置中中。每个函数可能有几个接口和其他描述符,但这对内核模块是透明的。

在“原始的”USB描述符结构之上,USB复合功能可以看作是一组接口的抽象。这是该框架的另一个优秀特性——大多数实现细节都隐藏在“外壳”之下,在开发gadget时不需要考虑它们。我们不考虑端点和接口,而是考虑函数。因此,FSG是一个以“旧方式”开发的gadget,而MSG是一个复合gadget,它只使用一个复合函数——Mass Storage Function(即MSF)。事实上,MSF是从FSG创建的,它允许创建更复杂的驱动程序,而这些驱动程序的功能中包含了UMC。

二、整体驱动结构

在本文中,我将尝试解释如何创建一个MSG复合gadget。它已经在内核中了,但是让我们暂时忘记FSG和MSG的存在。

Linux的伟大之处在于,已经做了很多工作,人们可以用相对较少的努力获得结果。因此,我将展示如何使用MSF和一些“composite glue”创建一个可工作的驱动程序。

我将从模块的结构开始,跳过Mass Storage Function的细节。第一步是定义设备描述符。它存储了一些关于gadget的基本信息

static struct usb_device_descriptor msg_dev_desc = { .bLength = sizeof msg_dev_desc, .bDescriptorType = USB_DT_DEVICE, .bcdUSB = cpu_to_le16(0x0200), .idVendor = cpu_to_le16(FSG_VENDOR_ID), .idProduct = cpu_to_le16(FSG_PRODUCT_ID), };

 

usb_device_descriptor结构有更多的字段,但是它们对我们的模块来说不是必需的或者不重要的。需要关注的设置为:

bLength和bDescriptorType

每个描述符都有一个标准字段。

bsdUSB

设备支持的USB规范版本采用BCD编码(因此0x200表示2.00)。

idVendor和idProduct

每个设备必须有唯一的供应商和产品标识符对。为了避免冲突,公司(供应商)可以购买一个供应商ID,该ID为它们提供了一个包含65536个产品ID的名称空间来使用。NetChip已经向Linux社区捐赠了一些产品id。后来,Linux基金会获得了用于Linux的整个供应商ID。FSG_VENDOR_ID实际上是NetChip的供应商ID,以及FSG_PRODUCT_ID

 

下一步是定义将由驱动程序提供的USB配置。它由usb_configuration结构描述,该结构指向一个绑定回调函数。它的目的是将所有USB复合功能绑定到配置中。通常,它是一个简单的函数,因为大多数工作在调用它之前就完成了。

 

把它放在一起看起来如下:

static struct usb_configuration msg_config = { .label = "Linux Mass Storage", .bind = msg_do_config, .bConfigurationValue = 1, .bmAttributes = USB_CONFIG_ATT_SELFPOWER, }; static int __ref msg_do_config(struct usb_configuration *c) { return fsg_add(c->cdev, c, &msg_fsg_common); }

msg_config对象指定一个label(用于调试消息)、绑定回调、配置的编号(每个配置必须有一个惟一的、非零的编号),并指示设备是自驱动的。msg_bind所做的一切就是将MSF绑定到配置。

 

msg_bind()函数的定义如下,该函数是一个用于设置复合函数、准备描述符、添加设备支持的所有配置等的回调函数

static int __ref msg_bind(struct usb_composite_dev *cdev) { int ret; ret = msg_fsg_init(cdev); if (ret < 0) return ret; ret = usb_add_config(cdev, &msg_config); if (ret >= 0) set_bit(0, &msg_registered); fsg_common_put(&msg_fsg_common); return ret; }

msg_bind()函数执行以下操作:初始化Mass Storage Function,将前面定义的配置添加到USB设备,并(在最后)放置msg_fsg_common对象。如果一切成功,它将设置msg_registration标志,以便记录gadget已经注册和初始化。

 

通过以上所有操作,就可以定义复合设备。为此,使用了usb_composite_driver结构。除了指定名称之外,它还指向设备描述符和绑定回调

static struct usb_composite_driver msg_device = { .name = "g_my_mass_storage", .dev = &msg_dev_desc, .bind = msg_bind, };

至此,剩下的就是init和exit模块函数了:

 

static int __init msg_init(void) { return usb_composite_register(&msg_device); } static void msg_exit(void) { if (test_and_clear_bit(0, &msg_registered)) usb_composite_unregister(&msg_device); }

 

使用usb_composite_register()和usb_composite_unregister()函数来注册和注销设备。msg_registered变量用于确保设备只被取消注册一次。

 

总结一下:

1、当模块加载时,在msg_init()中注册复合设备(msg_device)。

2、设备绑定回调(msg_bind()),用于初始化MSF并向小工具添加配置。

3、配置(msg_config)有自己的绑定回调(msg_do_config()),它将MSF绑定到配置。

4、困难的工作在MSF中完成。

 

三、Mass Storage Function

考虑到全局,让我们进入更详细的细节:Mass Storage Function的内部工作方式。在处理它时需要注意几件事。

首先,因为MSF可以绑定到多个配置,所以它需要在实例之间共享一些数据,同时存储每个配置特定的信息。fsg_common结构用于共享数据。在绑定MSF之前,需要初始化该结构的一个实例。

由于公共对象由多个MSF实例使用,因此它没有单一所有者,因此需要一个引用计数器来决定何时可以销毁它。这就是在msg_bind()函数末尾调用fsg_common_put()的原因。

与fsg_common结构紧密相连的是一个工作线程,MSF使用它来处理主机的所有请求。创建fsg_common对象时,也会启动一个线程。当fsg_common对象被销毁,或者当它被INT、TERM或KILL信号杀死时,它会终止。在后一种情况下,即使在线程死亡之后,fsg_common对象仍然可能存在。无论什么原因,当线程退出时,都会调用thread_exit回调。

需要注意的是,一个信号可能会终止工作线程,但是为什么要这样做呢?原因很简单。只要MSF持有任何打开的文件,就不能卸载这些文件所属的文件系统。这对于关机脚本来说是个坏消息。

Alan Stern在FSG中提出的是,当工作线程收到INT、TERM或KILL信号时关闭所有的备份文件。由于MSF将与各种复合gadget一起使用,而不是对其进行硬编码,因此引入了回调。

最后要注意的是,MSF是可定制的。UMC规范允许单个设备具有多个逻辑单元(有时称为LUN,严格来说这是不正确的,因为LUN代表逻辑单元号)。每个逻辑单元可以是只读的或读写的,可以模拟CD-ROM或磁盘驱动器,可以是可移动的或不可移动的。

所有这些配置都必须在初始化fsg_common结构时指定。fsg_config结构正是用于此目的。在大多数情况下,模块作者不希望自己填充它,而是让模块的用户决定设置。

为了尽可能简单,MSF提供了一个fsg_module_parameters结构和一个fsg_module_parameters()宏。前者存储用户提供的参数,而后者定义几个模块参数。

有了fsg_module_parameters对象,可以使用fsg_config_from_params()和fsg_common_init()来创建fsg_common对象。另外,还可以使用fsg_common_from_params()将对其他两个函数的调用合并起来。

以下是它们组合在一起时的工作原理:

static struct fsg_module_parameters msg_mod_data = { .stall = 1 }; FSG_MODULE_PARAMETERS(/* no prefix */, msg_mod_data); static struct fsg_common msg_fsg_common; static int msg_thread_exits(struct fsg_common *common) { msg_exit(); return 0; } static int msg_fsg_init(struct usb_composite_dev *cdev) { struct fsg_config config; struct fsg_common *retp; fsg_config_from_params(&config, &msg_mod_data); config.thread_exits = msg_thread_exits; retp = fsg_common_init(&msg_fsg_common, cdev, &config); return IS_ERR(retp) ? PTR_ERR(retp) : 0; }

msg_exit()函数被选择为MSF的thread_exit回调函数。由于MSF在线程退出后是不可操作的,因此不需要保持复合设备注册,相反,gadget是未注册的。

此时,使用msg_registered标志的原因就很明显了。因为usb_composite_unregister()可以从两个不同的地方调用,所以需要一种保证只调用一次的机制——原子位操作对于这类任务来说是完美的。就是这样了。做完了。您可以获取完整的源代码并开始使用它。

复合框架的美妙之处在于,所有真正困难的东西都已经编写好了。在没有深入了解USB规范或Linux gadget API的情况下,可以编写设备并试验不同的配置。同时,它是一个完美的介绍一些更严肃的USB编程。

 

四、运行

要使用gadget,需要向USB主机提供一个磁盘映像,作为一个真正的USB设备。在设备上使用dd去创建一个:

# dd if=/dev/zero of=disk.img bs=1M count=64

磁盘映像到位,模块可以加载:

# insmod g_my_mass.ko file=$PWD/disk.img

将设备连接到主机应该在主机系统日志中产生几个消息,其中:

usb 1-4.4: new high speed USB device using ehci_hcd and address 8 usb 1-4.4: New USB device found, idVendor=0525, idProduct=a4a5 usb-storage: device scan complete sd 6:0:0:0: [sdb] Attached SCSI removable disk sd 6:0:0:0: [sdb] 131072 512-byte logical blocks: (67.1 MB/64.0 MiB) sdb: unknown partition table

剩下的就是用文件系统创建一个分区并开始使用pendrive:

# fdisk /dev/sdb ... # dmesg -c sd 6:0:0:0: [sdb] Assuming drive cache: write through sdb: sdb1 # mkfs.vfat /dev/sdb1 mkfs.vfat 3.0.9 (31 Jan 2010) # mount /dev/sdb1 /mnt/ # touch /mnt/foo # umount /mnt

 

如前所述,gadget工作起来妙不可言。

 

五、Conclusion

 

Linux USB复合框架提供了一种以相当直接的方式添加USB设备的方法。在复合框架出现之前,开发人员需要为他们想要添加到系统中的每个gadget实现所有USB请求。该框架处理基本的USB请求并分离每个USB复合功能,这允许gadget的作者从功能的角度而不是从底层接口和通信处理的角度来考虑问题。

正如人们所猜测的那样,本文仅仅触及复合框架的表面功能。所显示的驱动程序是一个单一配置、单一功能的gadget,因此与非复合gadget相比,其优势并不明显。以后的文章可能会介绍使用复合框架实现更强大小工具的驱动程序。

  相关解决方案