基于mdev实现adb热插拔(@STM32MP157D+fusb302)

发布时间 2023-11-19 21:55:07作者: ArnoldLu

关键词: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)》。