嵌入式Linux adbd实现概要梳理(基于STM32MP157D+Buildroot)
关键词:USB Gadget、dwc2、configfs、functionfs、adbd等等。
基于STM32MP157D简单记录ADB实现的过程,涉及到USB、Gadget、configfs、functionfs、adbd、ADB协议等等。
基于Buildroot 2020.02.6编译adbd运行于设备,和PC Windows交互的简要框图:
1 Linux下USB Gadget
1.1 Linux内核Gadget以及USB相关配置
Linux下Gadget以及USB配置包括:主机控制器驱动配置、设备控制器驱动配置、configfs、FunctionFS配置等等。
File systems
->Pseudo filesystems
->Userspace-driven configuration filesystem--支持configfs。
Device Drifers ->USB support
->EHCI HCD (USB 2.0) support--USB 2.0的EHCI驱动支持。
->OHCI HCD (USB 1.1) support--USB 1.1的OHCI驱动支持。
->DesignWare USB2 DRD Core Support--DesignWare HSOTG Dual Role Hi-Speed USB控制器驱动。
->DWC2 Mode Selection(Dual Role mode)--3种选择:仅支持Host,仅支持Gadget、Host/Gadget都支持。 ->USB Physical Layer drivers--USB物理层驱动。
->USB Gadget support--作为Device的驱动Gadget。
->USB Peripheral Controller
->Synopsys USB 2.0 Device controller--Synopsys USB 2.0 UDC驱动。 ->USB Gadget Drivers ->USB functions configurable through configs--通过configfs配置USB gadget功能。
->Function filesystem(FunctionFS)
->USB Role Switch Support--支持在Host控制器和Device控制器之间切换。
1.2 USB Host/Device框架
作为ADB设备时是USB Device,对应的驱动是Gadget FunctionFS驱动,FunctionFS借助configfs方便用户空间进行各种功能组合例化。
另外还涉及到UDC驱动控制USB设备控制器和USB主机控制器进行数据交互。
1.3 UDC驱动
usb_udc_init()模块创建了udc class,及其uevent函数:
usb_udc_init ->udc_class--创建udc名称的设备类。 ->usb_udc_uevent--送给用户空间的uevent格式。
由于DWC2驱动支持Host/Gadget,所以Host和Gadget驱动都位于dwc2_driver_probe()。
dwc2_platform_driver dwc2_driver_probe dwc2_gadget_init dwc2_hsotg_hw_cfg dwc2_hsotg_ep_alloc_request--分配ep0作为控制端点。 dwc2_hsotg_initep--分配in/out端点。 usb_add_gadget_udc usb_add_gadget_udc_release--增加一个UDC Gadget驱动,并加入到udc_list中。创建设备,以及一系列属性。
比如:
/sys/class/udc/49000000.usb-otg/ |-- a_alt_hnp_support |-- a_hnp_support |-- b_hnp_enable |-- current_speed |-- device -> ../../../49000000.usb-otg |-- function |-- is_a_peripheral |-- is_otg |-- is_selfpowered |-- maximum_speed |-- power | |-- autosuspend_delay_ms | |-- control | |-- runtime_active_time | |-- runtime_status | `-- runtime_suspended_time |-- soft_connect |-- srp |-- state |-- subsystem -> ../../../../../../class/udc `-- uevent
其中dwc2_hsotg_gadget_ops:
static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = { .get_frame = dwc2_hsotg_gadget_getframe, .udc_start = dwc2_hsotg_udc_start, .udc_stop = dwc2_hsotg_udc_stop, .pullup = dwc2_hsotg_pullup, .vbus_session = dwc2_hsotg_vbus_session, .vbus_draw = dwc2_hsotg_vbus_draw, };
1.4 configfs
configfs是基于RAM的虚拟文件系统。configs是一个基于文件系统的内核对象管理器(config_items)。
在用户空间通过mkdir或者rmdir来创建或者销毁一个功能。目录内的属性可以进行读写,进而对功能进行配置。
1.4.1 configfs数据结构和API
struct configfs_subsystem { struct config_group su_group; struct mutex su_mutex; };
struct config_group { struct config_item cg_item; struct list_head cg_children; struct configfs_subsystem *cg_subsys; struct list_head default_groups; struct list_head group_entry; };
struct config_item { char *ci_name; char ci_namebuf[CONFIGFS_ITEM_NAME_LEN]; struct kref ci_kref; struct list_head ci_entry; struct config_item *ci_parent; struct config_group *ci_group; const struct config_item_type *ci_type; struct dentry *ci_dentry; }; struct config_item_type { struct module *ct_owner; struct configfs_item_operations *ct_item_ops; struct configfs_group_operations *ct_group_ops; struct configfs_attribute **ct_attrs; struct configfs_bin_attribute **ct_bin_attrs; }; struct configfs_item_operations { void (*release)(struct config_item *); int (*allow_link)(struct config_item *src, struct config_item *target);--支持symlink操作。 void (*drop_link)(struct config_item *src, struct config_item *target); }; struct configfs_group_operations { struct config_item *(*make_item)(struct config_group *group, const char *name);--支持文件操作。 struct config_group *(*make_group)(struct config_group *group, const char *name);--支持mkdir操作。 int (*commit_item)(struct config_item *item); void (*disconnect_notify)(struct config_group *group, struct config_item *item); void (*drop_item)(struct config_group *group, struct config_item *item); }; struct configfs_attribute {--字符类型文件读写。 const char *ca_name; struct module *ca_owner; umode_t ca_mode; ssize_t (*show)(struct config_item *, char *); ssize_t (*store)(struct config_item *, const char *, size_t); };
struct configfs_bin_attribute {--二进制类型文件读写 struct configfs_attribute cb_attr; /* std. attribute */ void *cb_private; /* for user */ size_t cb_max_size; /* max core size */ ssize_t (*read)(struct config_item *, void *, size_t); ssize_t (*write)(struct config_item *, const void *, size_t); };
configfs对外API比较简单,初始化一个struct config_group,然后注册struct configfs_subsystem到configfs中。
void config_group_init(struct config_group *group)--初始化一个struct config_group。 int configfs_register_subsystem(struct configfs_subsystem *subsys)--注册一个struct configfs_subsystem到configfs。会在/sys/kernel/config目录下创建ci_namebuf的目录。 void configfs_unregister_subsystem(struct configfs_subsystem *subsys)--从configfs注销一个struct configfs_subsystem。
1.5 GadgetFS
GadgeFS基于configfs,使用者可以在用户空间配置和组合内核的function,灵活构成USB复合设备。
GadgeFS由gadget_cfs_init初始化:
config_group_init
configfs_register_subsystem
->configfs_attach_group
->config_attach_item
gadget_subsys是/sys/kernel/config/usb_gadget目录对应的结构体,目前仅支持创建目录。没创建一个目录就创建了一个GadgetFS实例。
static struct configfs_group_operations gadgets_ops = { .make_group = &gadgets_make,--当调用mkdir时,调用此函数。 .drop_item = &gadgets_drop,-- }; static const struct config_item_type gadgets_type = { .ct_group_ops = &gadgets_ops,--在usb_gadget下操作的函数集。 .ct_owner = THIS_MODULE, }; static struct configfs_subsystem gadget_subsys = { .su_group = { .cg_item = { .ci_namebuf = "usb_gadget",--在/sys/kernel/config下创建usb_gadget目录。 .ci_type = &gadgets_type, }, }, .su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex), };
gadgets_make()是创建GadgetFS实例的入口函数:
gadgets_make ->config_group_init_type_name--默认创建一系列目录或者文件节点。 ->config_item_set_name--设置item名称。 ->config_group_init--初始化struct config_group。
gadget_root_type--创建bDeviceClass等节点。
functions_type--仅支持functions目录下mkdir,调用function_make创建functions实例。
config_desc_type--仅支持mkdir,调用config_desc_make。
gadget_strings_strings_type--创建manufacturer/product/serialnumber等节点。
os_desc_type--创建use/b_vendor_code/qe_sign等节点。 ->configfs_add_default_group--分别默认创建functions/configs/strings/os_desc等目录。
->configfs_driver_template--当Gadget设备和驱动绑定,调用bind函数。
1.5.1 functions目录
function_make()在functions下创建目录,目录名称需按照FUNC.INSTANCE格式创建。
function_make ->usb_get_function_instance ->try_get_usb_function_instance--根据名称遍历func_list,找到后调用fd->alloc_inst()创建struct usb_function_instance。
->fd->alloc_inst()
->config_item_set_name
->fi->set_inst_name--设置INSTANCE名称。
DECLARE_USB_FUNCTION_INIT(ffs, ffs_alloc_inst, ffs_alloc)(f_fs.c)注册了ffs类型的function,其初始化一个struct usb_function_driver ffsusb_func结构体,然后定义了ffsmod_init()和ffsmod_exit()两个模块函数。
ffsmod_init ->usb_function_register--将ffsusb_func插入到func_list上。 ffsmod_exit ->usb_function_unregister--将ffsusb_func从func_list上移除。
struct usb_function_driver {
const char *name;--对应名称为ffs。
struct module *mod;
struct list_head list;--对应到func_list列表。
struct usb_function_instance *(*alloc_inst)(void);--对应函数为ffs_alloc_inst()。
struct usb_function *(*alloc_func)(struct usb_function_instance *inst);--对应函数为ffs_alloc()。
};
ffs_alloc_inst()创建一个struct usb_function_instance,并注册了functionfs类型文件系统。
ffs_alloc_inst ->_ffs_alloc_dev ->functionfs_init ->register_filesystem--注册ffs_fs_type类型文件系统,名称为functionfs。 static struct file_system_type ffs_fs_type = { .owner = THIS_MODULE, .name = "functionfs", .init_fs_context = ffs_fs_init_fs_context, .parameters = &ffs_fs_fs_parameters, .kill_sb = ffs_fs_kill_sb, };
1.5.2 configs目录
config_desc_make ->config_group_init_type_name--根据gadget_config_type创建MaxPower和bmAttributes两个节点。并且允许创建link。
->gadget_config_item_ops
->config_usb_cfg_link--ln -s创建link时调用函数。
->usb_get_function
->ffs_alloc--分配一个struct usb_function,并加入到cfg->func_list中。
->config_group_init_type_name--创建strings目录,仅支持mkdir创建目录,目录名为0x409表示language为en-us。目录创建后仅有configuration节点,供写入配置名称。
1.5.3 strings目录
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info)创建gadget_strings_strings_type,在strings下创建目录会同时创建如下节点:
GS_STRINGS_RW(gadget_strings, manufacturer); GS_STRINGS_RW(gadget_strings, product); GS_STRINGS_RW(gadget_strings, serialnumber); static struct configfs_attribute *gadget_strings_langid_attrs[] = { &gadget_strings_attr_manufacturer, &gadget_strings_attr_product, &gadget_strings_attr_serialnumber, NULL, };
目录名0x409表示string的language是en-us。
1.5.4 UDC
往UDC写空则和Gadget驱动解绑,将Gadget从Host拔出;写入/sys/clss/udc下一个名称,则将Gadget驱动绑定到UDC,Host即可识别Gadget。
gadget_dev_desc_UDC_store ->unregister_gadget--如果写入空,则注销UDC驱动。 ->usb_gadget_unregister_driver-- ->usb_gadget_remove_driver ->usb_gadget_set_state ->check_pending_gadget_drivers
->usb_gadget_probe_driver--如果当前有在使用中的UDC则退出;如果没有则将UDC和驱动绑定。
->udc_bind_to_driver
2 GadgetFS配置以及adb启动
参考《Linux USB gadget configured through configfs — The Linux Kernel documentation》进行Gadget configfs配置。
脚本对adbd使用进行配置:
#!/bin/sh gadget=gadget do_start(){ #挂载configs文件系统。 #has_mount=$(mount -l | grep /sys/kernel/config) #if [[ -z $has_mount ]];then mount -t configfs none /sys/kernel/config #fi cd /sys/kernel/config/usb_gadget #进入usb_gadget,创建gadget目录后系统自动创建usb gadget相关的内容。 if [[ ! -d ${gadget} ]]; then mkdir ${gadget} else exit 0 fi cd ${gadget} #设置EP0 Packet最大值。 echo "64" > bMaxPacketSize0 #设置USB协议版本USB2.0,Device版本号为0x0100。 echo 0x0200 > bcdUSB echo 0x0100 > bcdDevice #定义产品的VendorID和ProductID。 echo "0x09D9" > idVendor echo "0x0502" > idProduct #0x409对应en-us,表示后续string类型的语言。 mkdir strings/0x409 #将开发商、产品和序列号字符串写入内核。 echo "76543210" > strings/0x409/serialnumber echo "STM" > strings/0x409/manufacturer echo "STM32MP157" > strings/0x409/product #创建一个USB配置实例。 if [[ ! -d configs/config.1 ]]; then mkdir configs/config.1 fi #设备从总线获取的最大电流mA。 echo 120 > configs/config.1/MaxPower #定义配置描述符使用的字符串。 if [[ ! -d configs/config.1/strings/0x409 ]]; then mkdir configs/config.1/strings/0x409 fi echo "STMCfg" > configs/config.1/strings/0x409/configuration #按照FUNC.INSTANCE格式创建功能实例。需要注意的是,一个功能如果有多个实例的话,扩展名必须用数字编号。 mkdir functions/ffs.adb #分配一个usb_function并加入到func_list中。后续绑定驱动到UDC时需要。 ln -s functions/ffs.adb configs/config.1 mkdir /dev/usb-ffs mkdir /dev/usb-ffs/adb #挂载adb设备functionfs文件系统到/dev/usb-ffs/adb,出现ep0/1/2,给adbd使用。 mount -t functionfs adb /dev/usb-ffs/adb adbd & echo "sleep 3s" sleep 3s #将gadget驱动注册到UDC上,插上USB线到电脑上,电脑就会枚举USB设备。 udc_name=$(ls /sys/class/udc) echo UDC:$udc_name echo "$udc_name" > UDC } do_stop() { cd /sys/kernel/config/usb_gadget/${gadaget} echo "" > UDC } case $1 in start) echo "Start hid gadget " do_start ;; stop) echo "Stop hid gadget" do_stop ;; *) echo "Usage: $0 (stop | start)" ;; esac
当调用mkdir时,在/sys/kernel/config/usb_gadget下创建一系列文件:
/sys/kernel/config/usb_gadget/ `-- gadget |-- UDC--绑定UDC驱动。 |-- bDeviceClass--USB设备类,在ch9.h - include/uapi/linux/usb/ch9.h - Linux source code (v5.4.34) - Bootlin。 |-- bDeviceProtocol--USB协议代码。 |-- bDeviceSubClass--USB设备子类。 |-- bMaxPacketSize0--端点0的最大包大小。 |-- bcdDevice--设备出厂编号。 |-- bcdUSB--USB版本号。 |-- configs | `-- config.1--由脚本创建。创建MaxPower和bmAttributes,以及strings目录。 | |-- MaxPower--设备从总线获取的最大电流。 | |-- bmAttributes--供电模式选择,是自供电、还是电池供电、是否可以唤醒。 | |-- ffs.adb -> ../../../../usb_gadget/gadget/functions/ffs.adb | `-- strings | `-- 0x409 | `-- configuration |-- functions | `-- ffs.adb--初始化一个functions实例,注册functionfs类型文件系统,创建adb设备。 |-- idProduct--产品编号。 |-- idVendor--厂商编号。 |-- os_desc | |-- b_vendor_code | |-- qw_sign | `-- use `-- strings `-- 0x409--由脚本创建。 |-- manufacturer |-- product `-- serialnumber
3 基于Buildroot的adbd简析
Buildroot下配置adbd:
Target packages ->System tools ->adbd
adbd创建了transport层来进行数据传输,transport之间通过socketpair创建的套接字进行通信。底层通信可以是TCP或者USB,如果是USB则需要通过/dev/usb-ffs/adb的断点进行控制以及读写。
主要代码流程如下:
main ->start_device_log ->adb_main
->init_transport_registration--创建一对套接字写到transport_registration_send,从transport_registration_recv读取并进行处理。
->adb_socketpair
->socketpair--创建一对无名的,相互连接的套接字用于全双工通信,可以往sv[0]中写,从sv[1]中读;或者相反。
->fdevent_install--注册对transport_registration_recv句柄读事件的回调函数transport_registration_func。
->transport_registration_func--注册一对读写套接字,从usb(本例)读取,通过socket句柄由select调用对应的读函数transport_socket_events。
->fdevent_install
->transport_socket_events--读事件对应回调函数,属于来源于USB端口。
->read_packet--从socket读取内容。
->handle_packet--解析读取到的内容,并做出响应。
->input_thread--由adb_thread_create创建,当有写操作请求时调用usb_write()。
->output_thread--由adb_thread_create创建,从usb读取数据写到socket中。另一端套接字读取调用transport_socket_events进行处理。
->fdevent_set--设置fdevent对应的事件,为FDE_READ/FDE_WRITE/FDE_ERROR/FDE_TIMEOUT之一。 ->install_listener--注册"tcp:5037"句柄的读事件处理函数ss_listener_event_func。 ->usb_init--支持/dev/android_adb和/dev/usb-ffs/adb两种初始化。 ->usb_ffs_init--打开/dev/usb-ffs/adb下ep并创建线程,对ep0进行配置,从ep1读取,写到ep2。
->usb_ffs_open_thread
->init_functions--分别打开control(ep0)/bulk_out(ep1)/bulk_in(ep2)句柄,不通过control进行descriptr和string配置。
->register_usb_transport--注册一个基于USB的transport。
->init_usb_transport--初始化基于USB的atransport:read_from_remote对应usb_read()->usb_ffs_read()->bulk_read();write_to_remote对应usb_write()->usb_ffs_write()->bulk_write()。
->register_transport
->transport_write_action--写到transport_registration_send,通知transport_registration_recv调用transport_registration_func注册一个transport。 ->init_jdwp ->fdevent_loop ->fdevent_subproc_setup ->fdevent_process--select()等待read_fds/write_fds/error_fds任一句柄就绪。
->fdevent_plist_enqueue--将fdevent加入到list_pending中。
->fdevent_list_dqueue--从list_pending中去除fdevent。
->fdevent_call_fdfunc--调用fdevent的处理函数。
handle_packet()是adbd处理各种请求的核心函数:
handle_packet
->A_SYNC
->A_CNXN--PC和设备进行连接。
->A_OPEN--在adbd所在device上开启一个服务,比如adb shell等。
->create_local_service_socket
->service_to_fd
->create_local_socket
->create_remote_socket
->send_ready--表示Open成功。
->A_OKAY--表示接收OK。
->A_CLSE--关闭对应连接。
->A_WRTE--用来向PC端或者Device端发送数据。
参考文档:《Rockchip RK3399 - linux通过libusb读取usb数据包 》《Linux usb 4. Device 详解_usb dwc2驱动解析》《Linux: USB Gadget 驱动简介_usb_add_gadget_udc》《Linux: USB Gadget ConfigFS》。