FunctionFS 的工作原理

概述

从内核的角度来看,FunctionFS 只是一个具有独特行为的复合功能。它只能在用户空间驱动程序通过写入描述符和字符串进行注册后才能添加到 USB 配置中(用户空间程序必须提供与内核级别复合功能添加到配置时所提供的信息相同的信息)。

这尤其意味着复合初始化函数可能不在 init 段中(即,可能无法使用 `__init` 标签)。

从用户空间的角度来看,它是一个文件系统,挂载后提供一个“ep0”文件。用户空间驱动程序需要将描述符和字符串写入该文件。它无需担心端点、接口或字符串编号,只需提供描述符,就像该功能是唯一一个一样(端点和字符串编号从一开始,接口编号从零开始)。FunctionFS 会根据需要更改它们,并处理不同配置中编号不同的情况。

有关 FunctionFS 描述符的更多信息,请参阅 FunctionFS 描述符

当描述符和字符串写入后,“ep#”文件就会出现(每个声明的端点对应一个),它们处理单个端点上的通信。FunctionFS 再次负责实际编号和配置的更改(这意味着“ep1”文件可能实际映射到(例如)端点 3(当配置更改时映射到(例如)端点 2))。“ep0”用于接收事件和处理设置请求。

当所有文件关闭时,该功能会自行禁用。

我还想提到的是,FunctionFS 的设计方式允许其多次挂载,因此最终一个 gadget 可以使用多个 FunctionFS 功能。其理念是,每个 FunctionFS 实例都由挂载时使用的设备名称标识。

可以想象一个 gadget 具有以太网、MTP 和 HID 接口,其中后两者是通过 FunctionFS 实现的。在用户空间层面,它看起来会像这样:

$ insmod g_ffs.ko idVendor=<ID> iSerialNumber=<string> functions=mtp,hid
$ mkdir /dev/ffs-mtp && mount -t functionfs mtp /dev/ffs-mtp
$ ( cd /dev/ffs-mtp && mtp-daemon ) &
$ mkdir /dev/ffs-hid && mount -t functionfs hid /dev/ffs-hid
$ ( cd /dev/ffs-hid && hid-daemon ) &

在内核层面,gadget 会检查 `ffs_data->dev_name` 来识别其 FunctionFS 是为 MTP(“mtp”)还是 HID(“hid”)设计的。

如果没有提供“functions”模块参数,驱动程序只接受一个具有任意名称的功能。

当提供了“functions”模块参数时,只接受列出的名称的功能。特别是,如果“functions”参数的值只是一个单元素列表,那么行为与完全没有“functions”时类似;但是,只接受具有指定名称的功能。

只有在所有声明的功能文件系统都被挂载并且所有功能的 USB 描述符都已写入其 ep0 后,该 gadget 才会注册。

相反,在第一个 USB 功能关闭其端点后,该 gadget 就会注销。

DMABUF 接口

FunctionFS 额外支持基于 DMABUF 的接口,用户空间可以将 DMABUF 对象(外部创建的)附加到端点,并随后使用它们进行数据传输。

用户空间应用程序随后可以使用此接口在多个接口之间共享 DMABUF 对象,从而允许以零拷贝方式传输数据,例如在 IIO 和 USB 堆栈之间。

作为此接口的一部分,新增了三个 IOCTL。这三个 IOCTL 必须在数据端点上执行(即,非 ep0)。它们是:

FUNCTIONFS_DMABUF_ATTACH(int)

将由文件描述符标识的 DMABUF 对象附加到数据端点。成功时返回零,错误时返回负的 errno 值。

FUNCTIONFS_DMABUF_DETACH(int)

将由文件描述符标识的给定 DMABUF 对象从数据端点分离。成功时返回零,错误时返回负的 errno 值。请注意,关闭端点的文件描述符将自动分离所有已附加的 DMABUF。

FUNCTIONFS_DMABUF_TRANSFER(struct usb_ffs_dmabuf_transfer_req *)

将先前附加的 DMABUF 加入传输队列。该参数是一个结构体,其中包含 DMABUF 的文件描述符、要传输的字节大小(通常应与 DMABUF 的大小对应)以及一个目前未使用的“flags”字段。成功时返回零,错误时返回负的 errno 值。