通过 configfs 配置的 Linux USB 设备¶
2013年4月25日
概述¶
USB Linux 设备是一种具有 UDC(USB 设备控制器)的设备,可以连接到 USB 主机以扩展其附加功能,例如串口或大容量存储功能。
主机将设备视为一组配置,每个配置包含多个接口,从设备角度来看,这些接口被称为功能,每个功能代表一个串行连接或 SCSI 磁盘。
Linux 为设备提供了许多功能可供使用。
创建设备意味着决定将有哪些配置,以及每个配置将提供哪些功能。
Configfs(请参阅 Configfs - 用户空间驱动的内核对象配置)非常适合用于告知内核上述决策。本文档介绍如何执行此操作。
它还描述了 configfs 集成到设备的设计。
要求¶
为了使其工作,configfs 必须可用,因此在 .config 中 CONFIGFS_FS 必须为 'y' 或 'm'。在撰写本文时,USB_LIBCOMPOSITE 选择 CONFIGFS_FS。
用法¶
(描述通过 configfs 提供的第一个功能的原始帖子可以在这里看到:http://www.spinics.net/lists/linux-usb/msg76388.html)
$ modprobe libcomposite
$ mount none $CONFIGFS_HOME -t configfs
其中 CONFIGFS_HOME 是 configfs 的挂载点
1. 创建设备¶
对于要创建的每个设备,必须创建其对应的目录
$ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
例如
$ mkdir $CONFIGFS_HOME/usb_gadget/g1
...
...
...
$ cd $CONFIGFS_HOME/usb_gadget/g1
每个设备都需要指定其供应商 ID <VID> 和产品 ID <PID>
$ echo <VID> > idVendor
$ echo <PID> > idProduct
设备还需要其序列号、制造商和产品字符串。为了有一个地方存储它们,必须为每种语言创建一个字符串子目录,例如
$ mkdir strings/0x409
然后可以指定字符串
$ echo <serial number> > strings/0x409/serialnumber
$ echo <manufacturer> > strings/0x409/manufacturer
$ echo <product> > strings/0x409/product
进一步的自定义字符串描述符可以创建为语言目录中的目录,字符串文本被写入字符串目录中的“s”属性
$ mkdir strings/0x409/xu.0 $ echo <字符串文本> > strings/0x409/xu.0/s
如果函数驱动程序支持,函数可能允许指向这些自定义字符串描述符的符号链接,以将这些字符串与类描述符关联。
2. 创建配置¶
每个设备将包含多个配置,必须创建其对应的目录
$ mkdir configs/<名称>.<数字>
其中 <名称> 可以是文件系统中合法的任何字符串,而 <数字> 是配置的编号,例如
$ mkdir configs/c.1
...
...
...
每个配置也需要其字符串,因此必须为每种语言创建一个子目录,例如
$ mkdir configs/c.1/strings/0x409
然后可以指定配置字符串
$ echo <configuration> > configs/c.1/strings/0x409/configuration
还可以为配置设置一些属性,例如
$ echo 120 > configs/c.1/MaxPower
3. 创建函数¶
该设备将提供一些功能,对于每个功能,必须创建其对应的目录
$ mkdir functions/<name>.<instance name>
其中 <名称> 对应于允许的函数名称之一,而实例名称是文件系统中允许的任意字符串,例如
$ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
...
...
...
每个函数都提供其特定的属性集,具有只读或读写访问权限。在适用的情况下,需要适当地写入它们。有关更多信息,请参阅 Documentation/ABI/testing/configfs-usb-gadget。
4. 将函数与配置关联¶
此时创建了多个设备,每个设备都指定了多个配置和多个可用函数。剩下的就是指定哪个函数在哪个配置中可用(同一函数可以在多个配置中使用)。这是通过创建符号链接来实现的
$ ln -s functions/<name>.<instance name> configs/<name>.<number>
例如
$ ln -s functions/ncm.usb0 configs/c.1
...
...
...
5. 启用设备¶
以上所有步骤都是为了将设备组成配置和功能。
示例目录结构可能如下所示
.
./strings
./strings/0x409
./strings/0x409/serialnumber
./strings/0x409/product
./strings/0x409/manufacturer
./configs
./configs/c.1
./configs/c.1/ncm.usb0 -> ../../../../usb_gadget/g1/functions/ncm.usb0
./configs/c.1/strings
./configs/c.1/strings/0x409
./configs/c.1/strings/0x409/configuration
./configs/c.1/bmAttributes
./configs/c.1/MaxPower
./functions
./functions/ncm.usb0
./functions/ncm.usb0/ifname
./functions/ncm.usb0/qmult
./functions/ncm.usb0/host_addr
./functions/ncm.usb0/dev_addr
./UDC
./bcdUSB
./bcdDevice
./idProduct
./idVendor
./bMaxPacketSize0
./bDeviceProtocol
./bDeviceSubClass
./bDeviceClass
必须最终启用此类设备,以便 USB 主机可以枚举它。
为了启用该设备,必须将其绑定到 UDC(USB 设备控制器)
$ echo <udc name> > UDC
其中 <udc 名称> 是在 /sys/class/udc/* 中找到的名称之一,例如
$ echo s3c-hsotg > UDC
6. 禁用设备¶
$ echo "" > UDC
7. 清理¶
从配置中删除函数
$ rm configs/<config name>.<number>/<function>
其中 <配置名称>.<数字> 指定配置,<函数> 是指向要从配置中删除的函数的符号链接,例如
$ rm configs/c.1/ncm.usb0
...
...
...
删除配置中的字符串目录
$ rmdir configs/<配置名称>.<数字>/strings/<语言>
例如
$ rmdir configs/c.1/strings/0x409
...
...
...
并删除配置
$ rmdir configs/<config name>.<number>
例如
rmdir configs/c.1
...
...
...
删除函数(但不会卸载函数模块)
$ rmdir functions/<名称>.<实例名称>
例如
$ rmdir functions/ncm.usb0
...
...
...
删除设备中的字符串目录
$ rmdir strings/<lang>
例如
$ rmdir strings/0x409
最后删除该设备
$ cd ..
$ rmdir <gadget name>
例如
$ rmdir g1
实现设计¶
下面介绍了 configfs 的工作原理。在 configfs 中,有项和组,两者都表示为目录。项和组之间的区别在于组可以包含其他组。在下面的图片中,只显示了一个项。项和组都可以具有属性,这些属性表示为文件。用户可以创建和删除目录,但不能删除文件,这些文件可以是只读的或读写的,具体取决于它们代表的内容。
configfs 的文件系统部分对 config_items/groups 和 configfs_attributes 进行操作,它们是通用的,对于所有配置的元素都具有相同的类型。但是,它们嵌入在使用特定的大型结构中。在下面的图片中,有一个包含 config_item 的“cs”和一个包含 configfs_attribute 的“sa”。
文件系统视图如下所示
./
./cs (directory)
|
+--sa (file)
|
.
.
.
每当用户读取/写入“sa”文件时,都会调用一个函数,该函数接受一个 struct config_item 和一个 struct configfs_attribute。在该函数中,使用众所周知的 container_of 技术检索“cs”和“sa”,并调用适当的 sa 函数(show 或 store),并将“cs”和一个字符缓冲区传递给它。“show”用于显示文件的内容(将数据从 cs 复制到缓冲区),而“store”用于修改文件的内容(将数据从缓冲区复制到 cs),但这取决于这两个函数的实现者来决定它们实际执行的操作。
typedef struct configured_structure cs;
typedef struct specific_attribute sa;
sa
+----------------------------------+
cs | (*show)(cs *, buffer); |
+-----------------+ | (*store)(cs *, buffer, length); |
| | | |
| +-------------+ | | +------------------+ |
| | struct |-|----|------>|struct | |
| | config_item | | | |configfs_attribute| |
| +-------------+ | | +------------------+ |
| | +----------------------------------+
| data to be set | .
| | .
+-----------------+ .
文件名由配置项/组设计者决定,而目录通常可以随意命名。一个组可以自动创建一些其默认子组。
有关 configfs 的更多信息,请参阅 Configfs - 用户空间驱动的内核对象配置。
上面描述的概念转换为 USB 设备如下
1. 设备具有其配置组,该配置组具有一些属性(idVendor、idProduct 等)和默认子组(configs、functions、strings)。写入属性会导致信息存储在适当的位置。在 configs、functions 和 strings 子组中,用户可以创建其子组以表示给定语言的配置、函数和字符串组。
2. 用户创建配置和函数,并在配置中创建指向函数的符号链接。当写入设备的 UDC 属性时,会使用此信息,这意味着将设备绑定到 UDC。drivers/usb/gadget/configfs.c 中的代码会迭代所有配置,并在每个配置中迭代所有函数并绑定它们。这样就绑定了整个设备。
文件 drivers/usb/gadget/configfs.c 包含以下代码
设备的 config_group
设备的默认组(configs、functions、strings)
将函数与配置关联(符号链接)
4. 每个 USB 函数自然都有其自己希望配置的内容的视图,因此特定函数的 config_groups 在函数实现文件 drivers/usb/gadget/f_*.c 中定义。
函数的代码以这种方式编写,它使用
usb_get_function_instance(),它又会调用 request_module。因此,如果 modprobe 工作正常,则会自动加载特定函数的模块。请注意,反之则不然:在禁用和拆除设备后,模块仍保持加载状态。