嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > USB Gadget 应用实例之 adb

USB Gadget 应用实例之 adb

本文来自百问网-韦东山驱动大全教程

韦东山老师驱动大全

https://blog.csdn.net/pwl999/article/details/121873236
https://blog.csdn.net/kunkliu/article/details/122746762
https://blog.csdn.net/baidxi/article/details/123618276
https://blog.csdn.net/baidxi/article/details/122874525

1. ADB 框架

ADB 全称为"Android Debug Bridge",Android 调试桥。在纯 Linux 系统中也可以使用。它是 client-server 架构,由三部分组成:

  • adbclient:我们运行的 adb 命令就属于 adbclient,比如我们运行以下命令adb push d:\1.txt /root时,它就是一个 adbclient,它通过 adbserver 把windows 下的文件 "d:\1.txt" 推送到开发板的 /root 目录
  • adbserver:作为一个后台程序运行运行于 PC,它负责管理 PC 和开发板之间的通信,完成 adbclient 的请求
  • adbd:运行于开发板的守护进程,它通过底下的 Gadget 跟 adbserver 通信

实际上,adbclient 和 adbserver 都是同一个应用程序:比如 Windows 下的 adb.exe,使用不同的参数来启动时就可以作为 adbclient 或者 adbserver。我们第 1 次执行 adb 命令时,它会帮我们启动一个 adb 程序作为 adbserver。

2. 体验ADB

2.1 在 Windows 安装软件

解压 GIT 仓库如下文件:

确认里面的 adb.exe 所在目录,把这个目录添加进 Windows 的 Path 环境变量里。

2.2 在 STM32MP157 上实验

STM32MP157 的出厂系统已经安装好了 adbd,可以直接连接 USB 线进行测试。

比如在 Windows 上执行命令:

adb  devices  # 列出adb设备
adb  push  d:\1.txt  /root  # 上传文件到开发板/root目录
adb  shell   # 启动adb命令行

IMX6ULL 的出厂系统还没安装 adbd,等移植 ADB 时再进行实验。

3. functionfs

我们关注的是 Gadget 部分:

使用 legacy 的方法时,我们需要在驱动程序里指定设备信息(比如设备描述符、配置描述符等等),还需要在驱动程序里实现数据的传输功能,这都在驱动程序里限定死了。

使用 configfs 时,我们可以灵活地指定设备信息、灵活地选择各种 function。但是,还不够灵活:你必须选择某个 function,这个 function 里已经实现实现了数据的传输功能,你无法更改。

我们能否把 Gadget 设备的端点暴露给用户程序?让用户程序自己操作端点来传输数据?可以!这就是 functionfs。

functionfs 是一种文件系统,它的使用分为两步:

  • 内核态:注册 functionfs
  • 用户态:挂载 functionfs

抓住这两点来分析代码。

3.1 注册 functionfs

以 legacy 的方式来分析,只要安装 g_ffs.ko 驱动程序:

insmod g_ffs.ko

就会触发以下调用过程:

# drivers\usb\gadget\legacy\g_ffs.c
gfs_init
 usb_get_function_instance("ffs");
  try_get_usb_function_instance
   fi = fd->alloc_inst();
    # drivers\usb\gadget\function\f_fs.c
    ffs_alloc_inst
     dev = _ffs_alloc_dev();
      ret = functionfs_init();
       ret = register_filesystem(&ffs_fs_type);

使用 configfs 方式的话,需要执行以下命令:

modprobe libcomposite
mount -t configfs none /sys/kernel/config

mkdir -p /sys/kernel/config/usb_gadget/g1
mkdir  -p /sys/kernel/config/usb_gadget/g1/functions/ffs.adb

可以看到提示信息:

执行命令cat /proc/filesystems可以看到 functionfs。

3.2 挂载 functionfs

这时就可以挂载 functionfs 了,执行如下命令:

# mkdir -p /dev/usb-ffs/adb
# mount -t functionfs adb /dev/usb-ffs/adb  # 上面创建了 functions/ffs.adb, 挂载时 dev 就要指定为 adb
# ls /dev/usb-ffs/adb/
ep0

有了 ep0 端点后,用户态程序就可以通过它跟主机通信了。

3.3 ep0 的驱动程序

ep0 对应的驱动程序,分析如下:

  • 挂载 functionfs 时,会导致一个函数被调用:
  • ffs_sb_fill 中,会在 functionfs 的根目录下创建名为 ep0 的文件,并给它提供file_operations 结构体:

4. ADB 实现

源码:https://www.github.com/hadess/adbd

本文关注的不是 adb 本身的实现,而是数据如何传输

分析文件:adbd-master\adb\usb_linux_client.cpp

4.1 初始化接口描述符

4.2 申请更多端点

在接口描述符里,定义了多个接口描述符,这是 APP 提出的请求。如果 Gadget 设备有足够的端点,那么就会在在 functionfs 跟目录下创建出这些端点,比如 ep1、ep2。

ADB 程序的调用关系如下:

init_functionfs

    // 设置功能描述符(接口描述符)
    v2_descriptor.fs_count = 3;
    v2_descriptor.hs_count = 3;
    v2_descriptor.ss_count = 5;
    v2_descriptor.os_count = 1;
    v2_descriptor.fs_descs = fs_descriptors;
    v2_descriptor.hs_descs = hs_descriptors;
    v2_descriptor.ss_descs = ss_descriptors;
    v2_descriptor.os_header = os_desc_header;
    v2_descriptor.os_desc = os_desc_compat;

 h->control = adb_open(USB_FFS_ADB_EP0, O_RDWR); // 打开端点 0

 // 把接口描述符发给驱动程序
 ret = adb_write(h->control, &v2_descriptor, sizeof(v2_descriptor));

 // 发送字符串描述符, 这会触发驱动程序根据接口描述符创建更多的 endpoint
 ret = adb_write(h->control, &strings, sizeof(strings));

上面的函数操作的都是 ep0,对应的驱动程序如下:

函数 ffs_epfiles_create 会根据接口描述符申请更多的 endpoint,并且在 functionfs 里创建对应的节点:

5. 移植 ADB

5.1 交叉编译 adb

如果不想自己编译,可以使用 GIT 仓库里的可执行程序:

以 IMX6ULL 为例,打开《嵌入式 Linux 应用开发完全手册 V5_IMX6ULL_Pro开发板.pdf》,找到《6.5 构建 IMX6ULL Pro 版的根文件系统》章节,执行以下命令:

make clean
make 100ask_imx6ull_pro_ddr512m_systemV_qt5_defconfig
make menuconfig

配置 ADB:-> Target packages  -> System tools

然后执行:

make android-tools-rebuild

期间会自动下载源码、编译。

成功后,可在如下目录查看到可执行程序 adb、adbd

/home/book/100ask_imx6ull-sdk/Buildroot_2020.02.x/output/target/usr/bin

把可执行程序放到开发板的 /usr/bin 目录。

5.2 脚本

IMX6ULL 上使用的简化脚本:

modprobe libcomposite
mount -t configfs none /sys/kernel/config
mkdir -p /dev/usb-ffs/adb
mkdir -p /sys/kernel/config/usb_gadget/g1 
mkdir  -p /sys/kernel/config/usb_gadget/g1/functions/ffs.adb
mkdir  -p /sys/kernel/config/usb_gadget/g1/configs/b.1
ln -s  /sys/kernel/config/usb_gadget/g1/functions/ffs.adb /sys/kernel/config/usb_gadget/g1/configs/b.1
mount -t functionfs adb /dev/usb-ffs/adb
start-stop-daemon --start --oknodo --pidfile /var/run/adbd.pid --startas /usr/bin/adbd --background
sleep 1
echo ci_hdrc.0 > /sys/kernel/config/usb_gadget/g1/UDC

可以在 /etc/init.d/ 目录下创建一个 S99adbd 文件,就可以自动使能 ADB 功能。这个文件在 GIT 仓库里:

来自 STM32MP157 的供参考的脚本:

#!/bin/bash -e
### BEGIN INIT INFO
# Provides:          adbd
# Required-Start:
# Required-Stop:
# Default-Start:
# Default-Stop:
# Short-Description:
# Description:       Linux ADB
### END INIT INFO

VENDOR_ID="0x1d6b"
PRODUCT_ID="0x0104"
UDC=`ls /sys/class/udc/ | awk '{print $1}'`

start() {
        mkdir -p /dev/usb-ffs/adb -m 0770

        mkdir -p /sys/kernel/config/usb_gadget/g1  -m 0770

        echo ${VENDOR_ID} > /sys/kernel/config/usb_gadget/g1//idVendor
        echo ${PRODUCT_ID} > /sys/kernel/config/usb_gadget/g1//idProduct

        mkdir  -p /sys/kernel/config/usb_gadget/g1/strings/0x409   -m 0770

        echo "0123456789ABCDEF" > /sys/kernel/config/usb_gadget/g1/strings/0x409/serialnumber
        echo "STMicroelectronics"  > /sys/kernel/config/usb_gadget/g1/strings/0x409/manufacturer
        echo "STM32MP1"  > /sys/kernel/config/usb_gadget/g1/strings/0x409/product

        mkdir  -p /sys/kernel/config/usb_gadget/g1/functions/ffs.adb
        mkdir  -p /sys/kernel/config/usb_gadget/g1/configs/b.1  -m 0770
        mkdir  -p /sys/kernel/config/usb_gadget/g1/configs/b.1/strings/0x409  -m 0770

        ln -s  /sys/kernel/config/usb_gadget/g1/functions/ffs.adb /sys/kernel/config/usb_gadget/g1/configs/b.1
        echo "adb" > /sys/kernel/config/usb_gadget/g1/configs/b.1/strings/0x409/configuration
        mount -t functionfs adb /dev/usb-ffs/adb

        start-stop-daemon --start --oknodo --pidfile /var/run/adbd.pid --startas /bin/adbd --background

        sleep 1

        echo $UDC > /sys/kernel/config/usb_gadget/g1/UDC
}

stop() {
        start-stop-daemon --stop --oknodo --pidfile /var/run/adbd.pid --retry 5
        umount /dev/usb-ffs/adb
}

restart() {
        echo $UDC > /sys/kernel/config/usb_gadget/g1/UDC
}

if [  "$UDC" != "" ]; then
        case $1 in
                start|stop|restart) "$1" ;;
        esac
fi

exit $?