1.簡介
configfs是基於ram的文件系統,與sysfs的功能有所不同。sysfs是基於文件系統的kernel對象視圖,雖然某些屬性允許用戶讀寫,但對象是在kernel中創建、註冊、銷燬,由kernel控制其生命週期。而configfs是一個基於文件系統的內核對象管理器(或稱爲config_items),config_items在用戶空間通過mkdir顯式的創建,使用rmdir銷燬,在mkdir之後會出現對應的屬性,可以在用戶空間對這些屬性進行讀寫,與sysfs不同的是,這些對象的生命週期完全由用戶空間控制,kernel只需要響應用戶空間的操作即可。configfs和sysfs兩者可以共存,但不能相互取代。
早期的USB只支持單一的gadget設備,使用場景較爲簡單,隨後加入了composite framework,用來支持多個function的gadget設備,多個function的綁定在內核中完成,若需要修改,則需要修改內核,不靈活也不方便。Linux3.11版本引入了基於configfs的usb gadget configfs。usb gadget configfs重新實現了複合設備層,使用者可以在用戶空間配置和組合內核的function,靈活的構成USB複合設備,極大了提高了工作效率。
2.使用方法
下面的腳本通過usb gadget configfs配置uac2.0。在使用之前,內核需要開啓UAC2.0相關配置。uac2.0的參數在/sys/kernel/config/usb_gadget/g1/configs/c.1/uac2.0文件中,可配置通道、格式、採樣率等,下面的腳本使用默認配置。
#!/bin/bash
function start_uac2()
{
# usb_gadget依賴於libcomposite模塊
modprobe libcomposite
# 掛載config文件系統
mount -t configfs none /sys/kernel/config
# 創建g1目錄,實例化一個新的gadget模板
echo "mkdir /sys/kernel/config/usb_gadget/g1"
mkdir -m 0770 /sys/kernel/config/usb_gadget/g1
# 設置產品的VendorID、ProductID及USB規範版本號
echo "Setting Vendor Product ID's and bcdDevice"
echo 0x2207 > /sys/kernel/config/usb_gadget/g1/idVendor
echo 0x0019 > /sys/kernel/config/usb_gadget/g1/idProduct
# 設備版本號
echo 0x0200 > /sys/kernel/config/usb_gadget/g1/bcdDevice
# USB 1.1: 0x0110
# USB 2.0: 0x0200, USB 2.1: 0x0210, USB 2.5: 0x0250
# USB 3.0: 0x0300, USB 3.1: 0x0310, USB 3.2: 0x0320
# echo 0x0210 > /sys/kernel/config/usb_gadget/g1/bcdUSB
# 實例化英語ID,開發商、產品和序列號字符串寫入內核
echo "Setting English strings"
mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/strings/0x409
echo "0123456789ABCDEF" > /sys/kernel/config/usb_gadget/g1/strings/0x409/serialnumber
echo "rockchip" > /sys/kernel/config/usb_gadget/g1/strings/0x409/manufacturer
echo "USB Audio Device" > /sys/kernel/config/usb_gadget/g1/strings/0x409/product
# Creating Config
echo "Creating Config"
mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1
mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409
echo "uac2" > /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409/configuration
echo 500 > /sys/kernel/config/usb_gadget/g1/configs/c.1/MaxPower
# bind functions
# uac2 need to put before uvc, otherwise uvc + uac2 enumerate failed in win10
echo "Creating UAC2 gadget functionality : uac2.0"
mkdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0
ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.0 /sys/kernel/config/usb_gadget/g1/configs/c.1
# Binding USB Device Controller
echo "Binding USB Device Controller"
echo fe800000.dwc3 > /sys/kernel/config/usb_gadget/g1/UDC
}
function stop_uac2()
{
# Disabling the gadget
echo "Disabling the USB gadget"
echo "" > /sys/kernel/config/usb_gadget/g1/UDC
# Remove functions from configurations
rm /sys/kernel/config/usb_gadget/g1/configs/c.1/uac2.0
# Remove strings directories in configurations
rmdir /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409
# remove the configurations
rmdir /sys/kernel/config/usb_gadget/g1/configs/c.1
# Remove functions (function modules are not unloaded, though)
rmdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0
# Remove strings directories in the gadget
rmdir /sys/kernel/config/usb_gadget/g1/strings/0x409
# finally remove the gadget
rmdir /sys/kernel/config/usb_gadget/g1
}
usage()
{
echo "Usage: ./usb-gadget-uac2.sh start|stop"
echo " options:"
echo " start start uac2.0 gadget"
echo " stop stop uac2.0 gadget"
}
case $1 in
start)
start_uac2
;;
stop)
stop_uac2
;;
*)
usage
exit 1
;;
esac
exit 0
執行完上述腳本後,/sys/kernel/config/usb_gadget/g1/目錄下的文件組成如下所示。b開頭的文件用來配置設備描述符。functions目錄下的文件用於配置function驅動的參數。
├── bcdDevice
├── bcdUSB
├── bDeviceClass
├── bDeviceProtocol
├── bDeviceSubClass
├── bMaxPacketSize0
├── configs
│ └── c.1
│ ├── bmAttributes
│ ├── MaxPower
│ ├── strings
│ │ └── 0x409
│ │ └── configuration
│ └── uac2.0 -> ../../../../usb_gadget/g1/functions/uac2.0
├── functions
│ └── uac2.0
│ ├── c_chmask # 錄音通道掩碼,默認0x3
│ ├── c_srate # 錄音採樣率,默認64000
│ ├── c_ssize # 錄音一幀數據所佔bit位,默認16bit
│ ├── p_chmask # 播放通道掩碼,默認0x3
│ ├── p_srate # 播放採樣率,默認48000
│ ├── p_ssize # 播放一幀數據所佔bit位,默認16bit
│ └── req_number # 分配usb請求數量,默認2個
├── idProduct
├── idVendor
├── os_desc
│ ├── b_vendor_code
│ ├── qw_sign
│ └── use
├── strings
│ └── 0x409
│ ├── manufacturer
│ ├── product
│ └── serialnumber
└── UDC # 用於設置綁定USB控制器的名稱
3.代碼分析
3.1.初始化
usb gadget configfs模塊的初始化函數爲gadget_cfs_init。該函數調用後,會向configfs註冊一個子系統,子系統使用configfs_subsystem結構體描述。子系統中又可分爲組,使用config_group描述,組內又有成員,使用config_item描述。usb gadget configfs就是configfs子系統中的一個成員,成員的名稱爲"usb_gadget",成員的類型使用config_item_type描述,成員類型中包含了初始化函數gadgets_ops。因此usb gadget configfs子系統最終通過調用gadgets_make進行初始化。當加載libcomposite.ko模塊後,會在/sys/kernel/config/目錄下生成一個usb_gadget目錄。
[drivers/usb/gadget/configfs.c]
static struct configfs_group_operations gadgets_ops = {
.make_group = &gadgets_make,
.drop_item = &gadgets_drop,
};
static struct config_item_type gadgets_type = {
.ct_group_ops = &gadgets_ops,
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem gadget_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "usb_gadget",
.ci_type = &gadgets_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex),
};
static int __init gadget_cfs_init(void)
{
int ret;
config_group_init(&gadget_subsys.su_group);
ret = configfs_register_subsystem(&gadget_subsys);
#ifdef CONFIG_USB_CONFIGFS_UEVENT
android_class = class_create(THIS_MODULE, "android_usb");
if (IS_ERR(android_class))
return PTR_ERR(android_class);
#endif
return ret;
}
module_init(gadget_cfs_init);
gadgets_make函數的主要工作內容是設置複合設備數據結構usb_composite_dev和複合設備驅動數據結構usb_composite_driver。 工作流程如下:
[drivers/usb/gadget/configfs.c]
static struct configfs_group_operations gadgets_ops = {
.make_group = &gadgets_make,
.drop_item = &gadgets_drop,
};
static struct config_item_type gadgets_type = {
.ct_group_ops = &gadgets_ops,
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem gadget_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "usb_gadget",
.ci_type = &gadgets_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex),
};
static int __init gadget_cfs_init(void)
{
int ret;
config_group_init(&gadget_subsys.su_group);
ret = configfs_register_subsystem(&gadget_subsys);
#ifdef CONFIG_USB_CONFIGFS_UEVENT
android_class = class_create(THIS_MODULE, "android_usb");
if (IS_ERR(android_class))
return PTR_ERR(android_class);
#endif
return ret;
}
module_init(gadget_cfs_init);
gadgets_make函數的主要工作內容是設置複合設備數據結構usb_composite_dev和複合設備驅動數據結構usb_composite_driver。 工作流程如下:
- 分配gadget_info結構體,gi->group.default_groups是一個二級指針,第一級指向了gi->default_groups,gi->default_groups是一個指針數組,保存了functions_group、configs_group、strings_group、os_desc_group的地址。這樣就可以通過``gi->group找到所有的config_group`。
- 初始化functions_group、configs_group、strings_group、os_desc_group,其config_item_type分別指向functions_type、config_desc_type、gadget_strings_strings_type、os_desc_type,gadget_strings_strings_type使用USB_CONFIG_STRINGS_LANG宏定義。
- 初始化複合設備驅動數據結構usb_composite_driver,使用usb gadget configfs時,使用者可以直接在用戶空間綁定function驅動,不需要bind和unbind函數,因此configfs_do_nothing和configfs_do_nothing實現爲空。複合設備驅動的 usb_gadget_driver指向configfs_driver_template。usb_gadget_driver是function驅動和UDC驅動溝通的橋樑,非常重要。
- 初始化複合設備數據結構usb_composite_dev,設置USB設備描述符。
- 設置gi->group的config_item_type指向gadget_root_type,usb gadget configfs初始化的時候首先調用gadget_root_type。
- 最後向configfs系統返回gi->group。當在/sys/kernel/config目錄下創建usb_gadget時,configfs系統會調用gi->group和gi->group.default_groups保存的config_item_type。也即調用gadgets_make函數設置的gadget_root_type、functions_type、config_desc_type、gadget_strings_strings_type、os_desc_type。
[drivers/usb/gadget/configfs.c]
static struct config_group *gadgets_make(
struct config_group *group, const char *name)
{
......
/* 分配struct gadget_info結構體 */
gi = kzalloc(sizeof(*gi), GFP_KERNEL);
......
/* 設置group,gadgets_make最終返回的是gi->group */
gi->group.default_groups = gi->default_groups;
gi->group.default_groups[0] = &gi->functions_group;
gi->group.default_groups[1] = &gi->configs_group;
gi->group.default_groups[2] = &gi->strings_group;
gi->group.default_groups[3] = &gi->os_desc_group;
/* 設置functions_group,可配置function驅動的參數 */
config_group_init_type_name(&gi->functions_group, "functions",
&functions_type);
/* 設置configs_group,可配置USB設備參數 */
config_group_init_type_name(&gi->configs_group, "configs",
&config_desc_type);
/* 設置strings_group,可配置字符串參數 */
config_group_init_type_name(&gi->strings_group, "strings",
&gadget_strings_strings_type);
/* 設置os_desc_group,可配置操作系統描述符 */
config_group_init_type_name(&gi->os_desc_group, "os_desc",
&os_desc_type);
/* 初始化複合設備驅動-usb_composite_driver */
gi->composite.bind = configfs_do_nothing; // 實現爲空
gi->composite.unbind = configfs_do_nothing; // 實現爲空
gi->composite.suspend = NULL;
gi->composite.resume = NULL;
gi->composite.max_speed = USB_SPEED_SUPER; // 支持USB3.0
/* 初始化複合設備-usb_composite_dev */
composite_init_dev(&gi->cdev);
/* 設置複合設備描述符 */
gi->cdev.desc.bLength = USB_DT_DEVICE_SIZE;
gi->cdev.desc.bDescriptorType = USB_DT_DEVICE;
gi->cdev.desc.bcdDevice = cpu_to_le16(get_default_bcdDevice());
/* 設置configfs的usb_gadget_driver */
gi->composite.gadget_driver = configfs_driver_template;
gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);
gi->composite.name = gi->composite.gadget_driver.function;
/* 設置config_group */
config_group_init_type_name(&gi->group, name,
&gadget_root_type);
/* 向configfs系統返回&gi->group */
return &gi->group;
}
/* usb gadget configfs定義的usb_gadget_driver */
static const struct usb_gadget_driver configfs_driver_template = {
.bind = configfs_composite_bind,
.unbind = configfs_composite_unbind,
#ifdef CONFIG_USB_CONFIGFS_UEVENT
.setup = android_setup,
.reset = android_disconnect,
.disconnect = android_disconnect,
#else
.setup = composite_setup,
.reset = composite_disconnect,
.disconnect = composite_disconnect,
#endif
.suspend = composite_suspend,
.resume = composite_resume,
.max_speed = USB_SPEED_SUPER,
.driver = {
.owner = THIS_MODULE,
.name = "configfs-gadget",
},
};
下面是usb gadget configfs定義的幾種config_item_type和configfs_group_operations。當在/sys/kernel/config/usb_gadget/目錄下實例化一個新的gadget實例(g1)時,首先調用gadget_root_type,在g1目錄下生成bDeviceClass、bDeviceSubClass、bDeviceProtocol、bMaxPacketSize0、idVendor、idProduct、bcdDevice、bcdUSB、UDC屬性文件,使用者可以在用戶空間進行配置;接着調用functions_type,在g1目錄下生成functions目錄,綁定function驅動後,會在該目錄下導出function驅動的屬性文件,供使用者修改;然後調用config_desc_type,在g1目錄下生成configs目錄;隨後調用gadget_strings_strings_type,在g1目錄下生成strings目錄,包含了使用字符串表示的英語ID,開發商、產品和序列號等信息。最後調用os_desc_type,在g1目錄下生成os_desc目錄,包含了操作系統信息,一般不需要設置。
[drivers/usb/gadget/configfs.c]
static struct configfs_attribute *gadget_root_attrs[] = {
&gadget_dev_desc_attr_bDeviceClass,
&gadget_dev_desc_attr_bDeviceSubClass,
&gadget_dev_desc_attr_bDeviceProtocol,
&gadget_dev_desc_attr_bMaxPacketSize0,
&gadget_dev_desc_attr_idVendor,
&gadget_dev_desc_attr_idProduct,
&gadget_dev_desc_attr_bcdDevice,
&gadget_dev_desc_attr_bcdUSB,
&gadget_dev_desc_attr_UDC,
NULL,
};
static struct config_item_type gadget_root_type = {
.ct_item_ops = &gadget_root_item_ops,
.ct_attrs = gadget_root_attrs,
.ct_owner = THIS_MODULE,
};
static struct configfs_group_operations functions_ops = {
.make_group = &function_make,
.drop_item = &function_drop,
};
static struct config_item_type functions_type = {
.ct_group_ops = &functions_ops,
.ct_owner = THIS_MODULE,
};
static struct configfs_group_operations config_desc_ops = {
.make_group = &config_desc_make,
.drop_item = &config_desc_drop,
};
static struct config_item_type config_desc_type = {
.ct_group_ops = &config_desc_ops,
.ct_owner = THIS_MODULE,
};
/* 定義gadget_strings_strings_type的宏 */
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
[include/linux/usb/gadget_configfs.h]
#define USB_CONFIG_STRINGS_LANG(struct_in, struct_member) \
...... \
static struct configfs_group_operations struct_in##_strings_ops = { \
.make_group = &struct_in##_strings_make, \
.drop_item = &struct_in##_strings_drop, \
}; \
\
static struct config_item_type struct_in##_strings_type = { \
.ct_group_ops = &struct_in##_strings_ops, \
.ct_owner = THIS_MODULE, \
}
static struct configfs_item_operations os_desc_ops = {
.release = os_desc_attr_release,
.allow_link = os_desc_link,
.drop_link = os_desc_unlink,
};
static struct config_item_type os_desc_type = {
.ct_item_ops = &os_desc_ops,
.ct_attrs = os_desc_attrs,
.ct_owner = THIS_MODULE,
};
3.2.主要調用流程分析
下面結合uac2.0用戶空間配置腳本和內核中的USB gadget configfs代碼,分析一下當用戶空間配置時,內核中做了什麼。重點關注驅動相關的執行流程。
3.2.1.創建配置
當在用戶空間執行mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1命令時,會調用到configfs的config_desc_make函數。主要的工作流程爲:
- 分配一個config_usb_cfg1結構體,該結構體包含了usb_configuration結構體,保存了該USB設備的配置信息。
- 設置USB設備的配置描述符的某些選項。如bConfigurationValue(根據創建配置目錄的名稱設置,如c.1,則設置爲1)、MaxPower、bmAttributes。
- 向用戶空間導出該配置屬性文件,便於使用者設置。
- 調用usb_add_config_only函數將該配置掛到usb_composite_dev的configs鏈表。
3.2.2.獲取function實例
前面提到過,function驅動有兩個重要的數據結構usb_function_instance和usb_function。下面是獲取usb_function_instance的過程。如下圖所示,當用戶空間執行mkdir /sys/kernel/config/usb_gadget/g1/functions/uac2.0時,內核會調用USB gadget configfs的function_make函數,function_make函數的主要工作是獲取function驅動的usb_function_instance數據結構,執行流程如下:
-
調用gadget funcation API usb_get_function_instance函數,遍歷func_list鏈表,根據名稱進行匹配。用戶空間輸入的是uac2,則匹配uac2驅動。其定義在f_uac2.c文件中。
-
匹配成功回調function驅動定義的alloc_inst函數,對於uac2,則回調afunc_alloc_inst函數。
- 首先分配f_uac2_opts數據結構,內部包含了usb_function_instance。
- 設置uac2的config_item_type爲f_uac2_func_type,f_uac2_func_type會在/sys/kernel/config/usb_gadget/g1/functions/uac2.0目錄下導出設置uac2音頻參數的屬性文件,便於用戶空間設置。
- 設置音頻參數的默認值,使用者可在用戶空間修改。
-
將獲取到的uac2的usb_function_instance掛到gadget_info的available_func鏈表。
3.2.3.獲取function
下面是獲取usb_function的過程。如下圖所示,當用戶空間執行ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.0 /sys/kernel/config/usb_gadget/g1/configs/c.1時,內核會調用USB gadget configfs的config_usb_cfg_link函數。config_usb_cfg_link函數的主要工作是獲取function驅動的usb_function數據結構,執行流程如下:
-
通過gadget funcation API,調用uac2驅動的afunc_alloc函數。
- 分配f_uac2數據結構,內部包含了usb_function。
- 填充usb_function數據結構,重要的是設置的回調函數,usb_gadget_driver在適當的時候會回調這些函數。如驅動綁定的時候會回調afunc_bind函數,解除綁定的時候會回調afunc_unbind函數,設置配置的時候會回調afunc_set_alt函數等等。
-
將獲取到的uac2的usb_function掛到config_usb_cfg的func_list鏈表。
3.2.4.驅動綁定
如下圖所示,當用戶空間執行echo fe800000.dwc3 > /sys/kernel/config/usb_gadget/g1/UDC時,內核會調用USB gadget configfs的gadget_dev_desc_UDC_store函數。gadget_dev_desc_UDC_store函數的主要工作是將usb_gadget_driver和底層的USB控制器綁定,usb_gadget_driver相當於一個橋樑,橋的兩端分別是function驅動和UDC驅動。執行流程如下:
- 判斷輸入的USB控制器名稱,若輸出爲空或者是none,則解除usb_gadget_driver和底層USB控制器的綁定。反之調用usb_udc_attach_driver函數進行匹配USB設備控制器。
- 遍歷udc_list鏈表,查找fe800000.dwc3 USB設備控制器。
- 找到對應的USB設備控制器,則保存綁定的usb_gadget_driver,即configfs_driver_template。
- 回調configfs_composite_bind函數。其主要的工作內容如下:
- 分配端點的usb_request、分配緩衝區、設置usb_request的回調函數、復位所有端點,並將gadget的端點數量清零。
- function驅動保存對應的配置,並回調function驅動的bind函數,對於uac2則回調 afunc_bind 函數。
- 如果使用os_string,則需要分配os_string requset。
- 調用UDC驅動接口usb_gadget_udc_start使能USB設備控制器。
- 調用UDC驅動接口usb_udc_connect_control連接USB主機控制器,這樣USB主機就能識別並枚舉USB設備。
4.總結
本節以uac2爲例,介紹了USB gadget configfs用戶空間的使用方法及內核中的工作流程。USB gadget configfs提供了一個便捷的配置方法,用戶可以靈活的組織USB function驅動,以組成不同功能的USB設備複合設備,當配置完成後,USB gadget configfs並不參與USB設備複合設備的工作過程。usb_gadget_driver數據結構相當於一個橋樑,連同了function驅動和UDC驅動,USB gadget configfs提供usb_gadget_driver實現爲configfs_driver_template,對於legacy驅動,其實現又不同。本節沒有涉及uac2驅動的工作流程及數據的收發過程,這些將再uac2驅動章節進行詳細介紹。