写在前面

买了很多Linux开发板但是从来没有好好的进行过Linux开发(驱动,应用等)的系统学习。趁着暑假这几天的空闲,捡起一块来进行Linux的系统性学习,在这作为一个记录来记录学习过程和遇到的一些问题。

使用的开发板是旺仔的平地铲D1S开发板,使用芯片为全志Allwinner的F133B,提供的SDK的Linux版本为5.4。前期根据旺仔提供的教程进行开发学习。后续如果使用别的开发板也写在这里吧。

USB的RNDIS开发

RNDIS(Remote NDIS)是一种通过USB接口虚拟出以太网设备的技术,本质是TCP/IP over USB。它能让USB设备模拟成网卡,使Windows或Linux主机直接通过USB访问4G/5G网络,无需额外硬件。使用RNDIS的话就可以实现一根USB线替代网线,使用USB连接开发板和上位机就可以实现网络连接,进一步使用SSH,非常之方便。

D1S开发板的基础信息:


Linux Kernel : 5.4

UDC          : 4100000.udc-controller

ConfigFS     : /sys/kernel/config

Gadget       : g1

Config       : configs/b.1

ADB          : FunctionFS + adbd

RNDIS        : rndis.usb0

SSH Server   : Dropbear

已经确认的内核配置:


CONFIG_USB_GADGET=y

CONFIG_USB_CONFIGFS=y

CONFIG_USB_CONFIGFS_F_FS=y

CONFIG_USB_CONFIGFS_RNDIS=y

并且没有使用传统 g_ether:


# CONFIG_USB_ETH is not set

# CONFIG_USB_ETH_RNDIS is not set

上述是D1S开发板的内核固件默认配置,对于其它的开发板,需要在内核编译前确认几项配置:

Device Drivers
  -> USB support
    -> USB Gadget Support
       <*> USB Gadget functions configurable through configfs
       [*] Ethernet Control Model (CDC ECM)
	   [*] RNDIS

也就是完整的设置组合为:

CONFIG_USB_SUPPORT=y
CONFIG_USB_GADGET=y

CONFIG_USB_LIBCOMPOSITE=y
CONFIG_USB_CONFIGFS=y

CONFIG_USB_CONFIGFS_ECM=y
CONFIG_USB_CONFIGFS_RNDIS=y

#再加上:
CONFIG_NET=y
#以及SOC的USB:
CONFIG_USB_xxx_UDC=y

这样在内核上就支持了,然后就是系统上的设置。 当然,也可以先验证一下内核的部分:

zcat /proc/config.gz | grep -E \
"CONFIG_USB_GADGET|CONFIG_USB_CONFIGFS|CONFIG_USB_CONFIGFS_RNDIS|CONFIG_USB_CONFIGFS_ECM"

确保:

CONFIG_USB_GADGET=y
CONFIG_USB_CONFIGFS=y
CONFIG_USB_CONFIGFS_RNDIS=y
CONFIG_USB_CONFIGFS_ECM=y

随后:

ls /sys/class/udc/

应该有:

4100000.udc-controller

这里前面的数字不是那么重要,只要有这个控制器存在就行,然后后续开始后续的工作:

检查内核和UDC

首先查看内核配置:

zcat /proc/config.gz | grep -E \
"CONFIG_USB_GADGET|CONFIG_USB_CONFIGFS|CONFIG_USB_CONFIGFS_RNDIS|CONFIG_USB_CONFIGFS_F_FS|CONFIG_USB_ETH"

至少应该有:

CONFIG_USB_GADGET=y
CONFIG_USB_CONFIGFS=y
CONFIG_USB_CONFIGFS_RNDIS=y

如果未来还需要ADB的功能则需要:

CONFIG_USB_CONFIGFS_F_FS=y

最好不要有:

CONFIG_USB_ETH=y
CONFIG_USB_ETH_RNDIS=y

然后检查UDC:

ls /sys/class/udc/

会看到:

4100000.udc-controller

检查configfs:

mount | grep configfs

如果没有输出:

mount -t configfs none /sys/kernel/config

手动配置RNDIS

创建Gadget:

mkdir -p /sys/kernel/config/usb_gadget/g1
#创建之后
cd /sys/kernel/config/usb_gadget/g1

设置VID/PID

echo 0x1d6b > idVendor
echo 0x0105 > idProduct

保持device class默认

echo 0x00 > bDeviceClass
echo 0x00 > bDeviceSubClass
echo 0x00 > bDeviceProtocol

设置USB字符串

mkdir -p strings/0x409
echo "1587636487" > strings/0x409/serialnumber
echo "wangzai" > strings/0x409/manufacturer
echo "wangzai USB Ethernet" > strings/0x409/product

创建configuration

mkdir -p configs/b.1/strings/0x409
echo "RNDIS" > configs/b.1/strings/0x409/configuration

创建RNDIS function

mkdir -p functions/rndis.usb0

这时看到

using random self ethernet address
using random host ethernet address

是正常的,随后固定MAC

echo "02:00:00:00:00:01" > functions/rndis.usb0/dev_addr
echo "02:00:00:00:00:02" > functions/rndis.usb0/host_addr

配 Microsoft OS Descriptor

echo 1 > os_desc/use
echo 0xcd > os_desc/b_vendor_code
echo "MSFT100" > os_desc/qw_sign
echo "RNDIS" > functions/rndis.usb0/os_desc/interface.rndis/compatible_id
echo "5162001" > functions/rndis.usb0/os_desc/interface.rndis/sub_compatible_id

把 RNDIS 加入 configuration

ln -s functions/rndis.usb0 configs/b.1/rndis.usb0

再关联OS Descriptor:

ln -s configs/b.1 os_desc/b.1

最后绑定UDC(使用实际的USB)

echo 4100000.udc-controller > UDC

检查:

cat UDC

应该为:

4100000.udc-controller

随后开始配置usb0 首先查看接口:

ip link show usb0

应该有:

inet 192.168.7.2/24
link/ether 02:00:00:00:00:01

如果没有则配置:

ip addr flush dev usb0
ip addr add 192.168.7.2/24 dev usb0
ip link set usb0 up

然后回到查看接口,应该看到有ip地址

windows端操作

此时,用usb将上位机和开发板连接,设备管理器,网络适配器中应该看到:

Remote NDIS Compatible Device

打开网络设置

Win + R
ncpa.cpl

找到对应的 USB 以太网接口,IPv4 设置

IP:192.168.7.1
掩码:255.255.255.0
网关:留空
DNS:留空

然后进行Ping测试连通

ping 192.168.7.2

固化为开机自启动

首先创建

/etc/init.d/S100usrndis

执行:最好是串口执行:

cat > /etc/init.d/S100usrndis <<'EOF'
#!/bin/sh

GADGET="/sys/kernel/config/usb_gadget/g1"
CONFIG="${GADGET}/configs/b.1"
FUNC="${GADGET}/functions/rndis.usb0"

UDC_NAME="4100000.udc-controller"

start()
{
    echo "Starting USB RNDIS..."

    if ! grep -q " /sys/kernel/config configfs " /proc/mounts; then
        mount -t configfs none /sys/kernel/config || return 1
    fi

    mkdir -p "${GADGET}"

    echo 0x1d6b > "${GADGET}/idVendor"
    echo 0x0105 > "${GADGET}/idProduct"

    echo 0x00 > "${GADGET}/bDeviceClass"
    echo 0x00 > "${GADGET}/bDeviceSubClass"
    echo 0x00 > "${GADGET}/bDeviceProtocol"

    mkdir -p "${GADGET}/strings/0x409"
    echo "1587636487" > "${GADGET}/strings/0x409/serialnumber"
    echo "wangzai" > "${GADGET}/strings/0x409/manufacturer"
    echo "wangzai USB Ethernet" > "${GADGET}/strings/0x409/product"

    mkdir -p "${CONFIG}/strings/0x409"
    echo "RNDIS" > "${CONFIG}/strings/0x409/configuration"

    mkdir -p "${FUNC}"

    echo "02:00:00:00:00:01" > "${FUNC}/dev_addr"
    echo "02:00:00:00:00:02" > "${FUNC}/host_addr"

    echo 1 > "${GADGET}/os_desc/use"
    echo 0xcd > "${GADGET}/os_desc/b_vendor_code"
    echo "MSFT100" > "${GADGET}/os_desc/qw_sign"

    echo "RNDIS" > "${FUNC}/os_desc/interface.rndis/compatible_id"
    echo "5162001" > "${FUNC}/os_desc/interface.rndis/sub_compatible_id"

    if [ ! -e "${CONFIG}/rndis.usb0" ]; then
        ln -s "${FUNC}" "${CONFIG}/rndis.usb0"
    fi

    if [ ! -e "${GADGET}/os_desc/b.1" ]; then
        ln -s "${CONFIG}" "${GADGET}/os_desc/b.1"
    fi

    echo "${UDC_NAME}" > "${GADGET}/UDC

    sleep 1

    ip addr flush dev usb0 2>/dev/null
    ip addr add 192.168.7.2/24 dev usb0
    ip link set usb0 up

    echo "USB RNDIS started."
}

stop()
{
    echo "Stopping USB RNDIS..."

    if [ -e "${GADGET}/UDC" ]; then
        echo "" > "${GADGET}/UDC" 2>/dev/null
    fi

    sleep 1

    ip link set usb0 down 2>/dev/null
    ip addr flush dev usb0 2>/dev/null

    echo "USB RNDIS stopped."
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        sleep 2
        start
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac
EOF

写完后立即检查,因为串口粘贴容易丢字符:

grep -n "UDC" /etc/init.d/S100usrndis

正确的一行应该是:

echo "${UDC_NAME}" > "${GADGET}/UDC"

然后给文件以权限

chmod +x /etc/init.d/S100usrndis

检查语法

sh -n /etc/init.d/S100usrndis

手动启动脚本:

/etc/init.d/S100usrndis start

检查

cat /sys/kernel/config/usb_gadget/g1/UDC
ip addr show usb0

Windows上测试:

ping 192.168.7.2

参考资料

# 泰山派RNDIS/ADB共存与NFS文件系统

Linux内核程序的开发

Linux内核程序可以选择作为动态模块加载,这样每次编译内核程序不用替换开发板的内核固件,比较方便。

动态模块开发相关

首先要确保Linux的内核支持动态模块,主要确认的是Kernel Configuration里:

[*]Enable loadable module support
...
[*]Module unloading

将上述内容保存并编译后烧录新的固件到开发板中即可,目前使用的开发板默认配置是开启的。