基于mdev实现adb热插拔(@STM32MP157D+fusb302)
关键词:fusb302、uevent、mdev、adbd等等。
1 fusb302关于USB插拔检测,以及增加uevent事件
fsusb302支持USB Power Delivery协议(USB Power Delivery),支持识别各种USB设备和对应的状态。
fusb302支持DRP(Dual Role Power)、DFP(Downstream Facing Port)、UFP(Upstream Facing Port)。
参考《PD快充 - fusb302驱动调试笔记》。
fusb302通过I2C进行控制,驱动主体如下:
fusb30x_driver ->fusb30x_probe ->devm_regmap_init_i2c--注册基于i2c的regmap。 ->fusb_initialize_gpio--fusb302所使用到的gpio初始化。
->fusb302_work_func--在中断处理函数中,调度work执行此处理函数。
->state_machine_typec--维护状态机,根据状态机执行操作。 ->devm_request_threaded_irq--注册中断处理函数。 ->cc_interrupt_handler--将之前创建创建的work键入workqueue进行处理。
在fusb302进行一系列初始化后,主要靠中断驱动进行状态机切换。
当驱动执行platform_fusb_notify时,在判断plugged in后发送add uevent到用户空间;当判断plugged out后发送remove uevent到用户空间。
用户空间mdev匹配i2c设备发出的uevent后,调用start_stop_adbd脚本进行adbd任务的启动和停止。
@@ -301,6 +301,11 @@ static void platform_fusb_notify(struct fusb30x_chip *chip) ufp = true; usb_ss = true; } + + if(plugged) + kobject_uevent(&chip->dev->kobj, KOBJ_ADD); + else + kobject_uevent(&chip->dev->kobj, KOBJ_REMOVE);
另外:发送uevent时间放在dwc2 otg部分处理似乎更合适。
2 mdev代码解析
之前有关于mdev的分析《Linux uevent分析、用户接收uevent以及mdev分析》,重新简单分析如下:
mdev_main
->getopt32-仅支持s/d/f三个选项。
->create_and_bind_to_netlink--读取内核的UEVENT消息。
->initial_scan--在mdev第一次启动时,主动遍历/sys/dev下的目录,调用fileAction/dirAction创建文件和目录。
->open_mdev_log--创建保存log的文件mdev.log。
->daemon_loop--mdev作为daemon的主要循环逻辑,循环读取kernel uevent,并进行处理。
->safe_read--阻塞读取kernel uevent字串。
->process_action--处理读取到的kernel uevent字串。
->keywords--仅支持add/remove两个action关键词。
->make_device--执行匹配,创建设备,执行命令的主要函数。
在mdev第一次启动或后续读取到kernel uevent字串后,调用make_device创建设备、执行命令。
static void make_device(char *device_name, char *path, int operation) { int major, minor, type, len; char *path_end = path + strlen(path); ... #if ENABLE_FEATURE_MDEV_CONF G.rule_idx = 0; /* restart from the beginning (think mdev -s) */ #endif for (;;) { const char *str_to_match; regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP]; char *command; char *alias; char aliaslink = aliaslink; /* for compiler */ char *node_name; const struct rule *rule; str_to_match = device_name; rule = next_rule();--根据/etc/mdev.conf创建匹配规则。 #if ENABLE_FEATURE_MDEV_CONF--根据device_name和rule进行匹配,然后根据operation执行命令。 ... rule_matches: dbg2("rule matched, line %d", G.parser ? G.parser->lineno : -1); #endif /* Build alias name */ alias = NULL; if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) { aliaslink = rule->ren_mov[0]; if (aliaslink == '!') {--!不创建设备。 ... } else if (aliaslink == '>' || aliaslink == '=') {--如果path是个目录(比如drivers/),则将设备节点移动到目录下;如果path是个名称,则将设备节点重命名为这个名称。 ... } } command = NULL; IF_FEATURE_MDEV_EXEC(command = rule->r_cmd;)--获取匹配后的命令,如果不为空则执行命令。 if (command) { if ((command[0] == '@' && operation == OP_add)--仅在action为add时,执行@开始的命令。 || (command[0] == '$' && operation == OP_remove)--仅在action为remove时,执行$开始的命令。 || (command[0] == '*')--其他action执行*开始的命令。 ) { command++; } else { command = NULL; } } dbg3("command:'%s'", command); /* "Execute" the line we found */ node_name = device_name; if (ENABLE_FEATURE_MDEV_RENAME && alias) { node_name = alias = build_alias(alias, device_name); dbg3("alias2:'%s'", alias); } if (operation == OP_add && major >= 0) {--在执行命令之前创建设备。 ... } if (ENABLE_FEATURE_MDEV_EXEC && command) { /* setenv will leak memory, use putenv/unsetenv/free */ char *s = xasprintf("%s=%s", "MDEV", node_name); putenv(s); dbg1("running: %s", command); if (system(command) == -1)--匹配后执行命令。 bb_perror_msg("can't run '%s'", command); bb_unsetenv_and_free(s); } if (operation == OP_remove && major >= -1) {--在执行命令之后删除文件链接。 ... } ... } /* for (;;) */ }
3 mdev.conf配置
当fusb302驱动检测到USB插拔后,启动start_stop_adbd脚本进行处理,参数为$ACTION。
$DEVPATH=/devices/platform/soc/40012000.i2c/i2c-0/0-0022 root:root 666 */usr/bin/start_stop_adbd $ACTION
4 start_stop_adbd 脚本
start_stop_adbd对add执行do_start,对remove执行do_stop。
#!/bin/sh gadget=gadget DAEMON="adbd" PIDFILE="/var/run/$DAEMON.pid" ADBD_ARGS="" 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} 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类型的语言。 if [[ ! -d strings/0x409 ]]; then mkdir strings/0x409 fi # 将开发商、产品和序列号字符串写入内核。 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格式创建功能实例。需要注意的是,一个功能如果有多个实例的话,扩展名必须用数字编号。 if [[ ! -d functions/ffs.adb ]]; then mkdir functions/ffs.adb fi # 分配一个usb_function并加入到func_list中。后续绑定驱动到UDC时需要。 if [[ ! -d configs/config.1/ffs.adb ]]; then ln -s functions/ffs.adb configs/config.1 fi if [[ ! -d /dev/usb-ffs ]]; then mkdir /dev/usb-ffs fi if [[ ! -d /dev/usb-ffs/adb ]]; then mkdir /dev/usb-ffs/adb fi # 挂载adb设备functionfs文件系统到/dev/usb-ffs/adb,出现ep0/1/2,给adbd使用。 if [[ ! -f /dev/usb-ffs/adb/ep0 ]]; then mount -t functionfs adb /dev/usb-ffs/adb else echo "Please use stop or restart." exit 0 fi #adbd & start-stop-daemon -b -m -S -q -p "$PIDFILE" -x "/usr/bin/$DAEMON" -- -n $ADBD_ARGS status=$? if [ $status -eq 0 ]; then echo "Start adbd: OK" else echo "Start adbd: FAIL" fi sleep 0.3s # 将gadget驱动注册到UDC上,插上USB线到电脑上,电脑就会枚举USB设备。 udc_name=$(ls /sys/class/udc) #echo UDC:$udc_name echo "$udc_name" > UDC } do_stop() { if [[ -d /sys/kernel/config/usb_gadget/${gadget}/UDC ]]; then echo "" > /sys/kernel/config/usb_gadget/${gadget}/UDC fi #killall adbd start-stop-daemon -K -q -p "$PIDFILE" status=$? if [ $status -eq 0 ]; then rm -rf "$PIDFILE" echo "Stop adbd: OK" else echo "Stop adbd: FAIL" fi if [[ -f /dev/usb-ffs/adb/ep0 ]]; then umount /dev/usb-ffs/adb fi } case $1 in start|add) do_start ;; stop|remove) do_stop ;; restart) do_stop do_start ;; *) echo "Usage: $0 (stop | start | restart)" ;; esac
adbd更多参考《嵌入式Linux adbd实现概要梳理(基于STM32MP157D+Buildroot)》。