嵌入式Linux adbd实现概要梳理(基于STM32MP157D+Buildroot)

发布时间 2023-11-14 21:42:34作者: ArnoldLu

关键词: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启动

脚本对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》。