前言
在此文档中已经介绍了如何适配4G模块,使其能够使用虚拟usb网卡拨号实现上网:
而udev自动拨号是基于虚拟usb网卡实现的,ppp拨号并不适用,
这里再简略复盘一下使用虚拟网卡的前置条件。在内核中需要打开以下宏,则开启自动识别usb虚拟网卡:
CONFIG_USB_SERIAL_GENERIC=y
CONFIG_USB_SERIAL_OPTION=y
CONFIG_USB_SERIAL_QT2=y
我们常用两种拨号模式,一种是ECM,需要在内核开启如下配置:
CONFIG_USB_USBNET=y
CONFIG_USB_NET_CDCETHER=y
另一种是RNDIS模式,相对少用,但拨号方法类似ECM,可以作为备选拨号模式,也要一并开启:
CONFIG_USB_USBNET=y
CONFIG_USB_NET_RNDIS_HOST=y
还需要在drivers/usb/serial/option.c源码中添加网卡可能出现的所有vid、pid,以保证驱动能生成/dev/ttyUSB*设备,使得能往模块发送AT指令拨号及网络通信:
...
static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(0x2DEE, 0x4D41) }, // MEIG SLM320-C 模式2
{ USB_DEVICE(0x2DEE, 0x4D42) }, // MEIG SLM320-C 模式3/模式5
{ USB_DEVICE(0x2DEE, 0x4D43) },
{ USB_DEVICE(0x0525, 0xA4A7) }, // MEIG SLM320-C 模式1
{ USB_DEVICE(0x2949, 0x8241) }, // MEIG SLM720
{ USB_DEVICE(0x2949, 0x8242) },
{ USB_DEVICE(0x2949, 0x8243) },
{ USB_DEVICE(0x05C6, 0xF601) }, // MEIG SLM750
{ USB_DEVICE(0x05C6, 0xF602) },
{ USB_DEVICE(0x05C6, 0xF603) },
{ USB_DEVICE(0x05C6, 0xF604) },
{ USB_DEVICE(0x05C6, 0xF610) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_LIGHT) },
...
一般情况下,4G模块在不同的工作模式或者拨号模式下,其vid、pid基本上都不相同,
我们无法确定客户手上的4G模块是工作在何种模式下,为了保证都能识别并生成/dev/ttyUSB*设备,应该将所有可能的vid、pid值都添加到option驱动中,
而这也是udev自动拨号能自动修改模块工作模式的前提条件,而且不同模式下的4G模块AT通信设备号也可能不一样,
udev工作时能根据匹配到的vid、pid号,通过脚本自动识别该模块处于什么工作模式下,从而对相应的AT指令通信设备做出相应动作,具体在下文中会详细说明。
udev规则
udev 其实本质是系统中的一个守护进程,通过侦听内核发出来的 uevent 来管理/dev 目录下的设备文件。
udev 规则文件是 udev 里最重要的部分,默认是存放在 /etc/udev/rule.d/ 下。所有的规则文件必须以 ".rules" 为后缀名。以sudo vi xxx.rules 进入文件(可自己创建) 保存,给配置文件设置下权限。
其文件开头的数字则代表规则生效的优先级,因为规则是有可能存在冲突的,冲突的规则则是以高优先级的为准,数字越小优先级越高,如文件:50 -android.rules,优先级为50。
在之前就已经实现使用udev将u盘和SD卡自动挂载到/run/media/下的目录,并以块设备文件本身的文件名命名:
root@bookworm-dev64mp:~# ll /run/media/
总计 20
drwxr-xr-x 5 root root 100 11月10日 08:25 ./
drwxr-xr-x 24 root root 720 4月 1日 16:57 ../
drwxr-xr-x 2 root root 40 11月10日 08:25 mmcblk1/
drwxrwxr-x 2 root users 16384 1970年 1月 1日 mmcblk1p1/
drwxr-xr-x 23 root root 4096 1月19日 17:48 mmcblk1p2/
所以这里就不详细展开介绍udev规则文件的语法了,具体可以百度。
回归正题,在4G模块自动拨号借助udev规则的目的很简单,一个是识别当前插入了一个什么模块,另一个是识别模块工作在什么模式下,
所以,udev规则写法很简单,当匹配到指定vid和pid后,将其传入相应脚本,并在脚本中做出相应动作,如:
# SLM320
ACTION=="add", ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7", RUN+="/usr/local/bin/usb-SLM320-inserted.sh 0525 a4a7 %k"
ACTION=="add", ATTRS{idVendor}=="2dee", ATTRS{idProduct}=="4d41", RUN+="/usr/local/bin/usb-SLM320-inserted.sh 2dee 4d41 %k"
ACTION=="add", ATTRS{idVendor}=="2dee", ATTRS{idProduct}=="4d42", RUN+="/usr/local/bin/usb-SLM320-inserted.sh 2dee 4d42 %k"
这是SLM320的udev规则,规则有三条,这里介绍第一条,其他如此类推:
ACTION=="add":表示udev要监听的行为,"add"就是设备插入时触发。
ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7":表示触发的是什么vid、pid号。
RUN+="/usr/local/bin/usb-SLM320-inserted.sh 0525 a4a7 %k":表示触发后执行的动作,这里我写的是调用了一个脚本,并将vid、pid传入脚本,以及传入的%k,%k是触发以上条件的设备名称
在调用脚本时为什么传入这三个参数,稍后会介绍。
拨号脚本
这里以SLM320为例,先贴代码:
#!/bin/bash
# 使用flock限制脚本在短时间内重复执行
lock_file="/var/lock/usb-SLM320-inserted.lock"
(
# 尝试获取锁
flock -n 320 || exit 1
# 检查参数个数
if [ "$#" -ne 3 ]; then
echo "Error: Three arguments required."
exit 2
fi
vid="$1"
pid="$2"
device_name="$3"
# 默认为ttyUSB0
ttyusb="/dev/ttyUSB0"
# 检查$3是否为网卡名称
if [ ! -d "/sys/class/net/$device_name" ]; then
if [ ! -c "/dev/$device_name" ]; then
echo "$(date) -- $device_name is not device name." >> /tmp/usb-log.txt #debug
exit 1
fi
fi
# 等待字符设备文件的出现
wait_for_device() {
local timeout=10 # 设置超时时间为10秒
local elapsed=0 # 已过时间初始化为0
local device=$1
while [ ! -c "$device" ]; do
if [ $elapsed -ge $timeout ]; then
return 1 # 设备文件没有出现
fi
sleep 1
((elapsed++)) # 增加已过时间
done
return 0
}
# 写字符设备
echo_device() {
local at=$1
local device=$2
# 如果要写入的目标文件为普通文件,而非字符设备文件,则立刻删除
if [ -f $device ]; then
rm -rf $device
fi
if wait_for_device $2; then
echo -e $at >> $device
sleep 0.5
return 0
fi
return 1 # 设备文件没有出现
}
# 创建指令接收文件,接收来自/dev/ttyUSB*中的消息
# 执行完这个函数,紧接着必须使用$!获取进程pid
# 脚本结束前必须杀死此进程
setup_listen_file() {
local file_name=$1
local device=$2
if [[ -p $file_name || -f $file_name ]]; then
rm $file_name
fi
touch $file_name
cat $device >> $file_name & disown
}
# 启动关键字监听,返回监听到的消息,并清空被监听文件的缓存
# 如监听"\"usbmode\","关键字,那么返回值有可能为"+SYSNV: "usbmode",5"
start_grep() {
local file=$1
local pattern=$2
grep -m 1 "$pattern" < "$file"
echo "" > "$file" #清空文件
}
echo "$(date) -- find: $vid:$pid" >> /tmp/usb-log.txt #debug
echo "$(date) -- device: $device_name" >> /tmp/usb-log.txt #debug
# 等待option驱动创建ttyUSB设备文件,不等的话会导致驱动报错
# 一般都会有/dev/ttyUSB0,所以以此为基准
wait_for_device $ttyusb
if [[ "$vid" == "0525" && "$pid" == "a4a7" ]]; then
stty -F $ttyusb raw -echo
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
exit 1
fi
if [[ "$vid" == "2dee" && "$pid" == "4d41" ]]; then
stty -F $ttyusb raw -echo
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
exit 1
fi
if [[ "$vid" == "2dee" && "$pid" == "4d43" ]]; then
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
exit 1
fi
# SLM320的RNDIS与ECM的vid、pid都相同,所以需要额外判断
if [[ "$vid" == "2dee" && "$pid" == "4d42" ]]; then
stty -F $ttyusb raw -echo
timeout 1 cat $ttyusb #输出剩下的字符,确保缓存为空
# 创建监听文件
file="/tmp/ATbuf_$$"
setup_listen_file $file $ttyusb
# 获取监听进程PID
listener_pid=$!
sleep 1
# 检查SIM卡状态
echo_device "AT+CPIN?\r" $ttyusb
sleep 0.5
# 查找"READY"关键字,判断SIM卡是否就绪
buf=$(start_grep "$file" '+CPIN: READY')
echo "$(date) -- $buf" >> /tmp/usb-log.txt #debug
if [ -z "$buf" ]; then
echo "$(date) -- usbmode not ready." >> /tmp/usb-log.txt #debug
exit 1
fi
# 查询当前拨号模式
echo_device "AT+SYSNV=0,\"usbmode\"\r" $ttyusb
sleep 0.5
# 查找"+SYSNV: \"usbmode\""关键字,判断拨号状态
buf=$(start_grep "$file" '+SYSNV: "usbmode",')
echo "$(date) -- $buf" >> /tmp/usb-log.txt #debug
# 先判断是否能找到关键字
if [ ! -z "$buf" ]; then
if [[ "$buf" == *"\"usbmode\",3"* || "$buf" == *"\"usbmode\",5"* ]]; then
echo_device "AT+CGACT=1\r" $ttyusb # 激活上下文
echo_device "AT+VERCTRL=0,1\r" $ttyusb # 自动激活网络
echo_device "AT^NDISDUP=1,1\r" $ttyusb # 拨号
# 对所有usb模块执行udhcpc获取ip,超时时间60秒
for interface in /sys/class/net/usb*; do
iface=$(basename $interface)
timeout 60 udhcpc -i $iface > /dev/null 2>&1 &
done
echo "$(date) -- okay." >> /tmp/usb-log.txt #debug
fi
else
echo "$(date) -- usbmode error." >> /tmp/usb-log.txt #debug
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
fi
# 清理监听进程
kill -9 "$listener_pid" &> /dev/null
# 删除监听文件
rm "$file"
# else
# echo "Command Failed or Timed Out."
# # echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
# # echo_device "AT+TRB\r" $ttyusb
# # exit 1
# fi
exit 0
fi
) 320>$lock_file
# udev生效测试:
# echo -e "AT+SYSNV=1,\"usbmode\",1\r" > /dev/ttyUSB0
# echo -e "AT+TRB\r" > /dev/ttyUSB0
首先此脚本使用了flock机制,为了使脚本在同一时间内只被执行一次,防止udev连续触发而导致脚本重复执行:
lock_file="/var/lock/usb-SLM320-inserted.lock"
(
# 尝试获取锁
flock -n 320 || exit 1
# 业务代码
...
) 320>$lock_file
初始化变量,指定SLM320默认AT通信设备为/dev/ttyUSB0:
vid="$1"
pid="$2"
device_name="$3"
# 默认为ttyUSB0
ttyusb="/dev/ttyUSB0"
然后这里还判断一下触发此脚本的是什么设备,只有设备为网卡设备usb*或者ttyUSB*设备才能继续执行脚本,其他设备触发该脚本的一律不与执行:
# 检查$3是否为网卡名称
if [ ! -d "/sys/class/net/$device_name" ]; then
if [ ! -c "/dev/$device_name" ]; then
echo "$(date) -- $device_name is not device name." >> /tmp/usb-log.txt #debug
exit 1
fi
fi
在脚本中,会多处看到如下日志代码,以便debug:
echo "$(date) -- $device_name is not device name." >> /tmp/usb-log.txt #debug
然后就是四个函数:
# 等待字符设备文件的出现
wait_for_device()
# 写字符设备
echo_device()
# 创建指令接收文件,接收来自/dev/ttyUSB*中的消息
# 执行完这个函数,紧接着必须使用$!获取进程pid
# 脚本结束前必须杀死此进程
setup_listen_file()
# 启动关键字监听,返回监听到的消息,并清空被监听文件的缓存
# 如监听"\"usbmode\","关键字,那么返回值有可能为"+SYSNV: "usbmode",5"
start_grep()
wait_for_device()函数是为了等待option驱动完成ttyUSB*设备的创建而存在的,其函数会在10秒内轮询检查指定的设备文件是否存在,找到返回0,等待超时返回1。
这个函数的最大作用是为了保证在使用echo发送AT指令前保证目标设备文件存在,
echo -e "AT+SYSNV=1,\"usbmode\",1\r" > /dev/ttyUSB0
否则在重定向时,就会在/dev下创建一个名为ttyUSB0的普通文本文件,导致option驱动无法创建字符设备文件而出错。
echo_device()函数就是对echo -e指令重新封装,在发送AT指令前调用wait_for_device()函数,保证目标字符设备被成功初始化后再进行发送,发送完成后延时0.5秒保证AT指令在4G模块中生效。
setup_listen_file()函数会创建一个文本文件,用于持续接收/dev/ttyUSB0中返回的指令,因为如果不先缓存下了的话,发送完AT指令后,返回值会因为接收不及时而被刷走,其核心就是创建一个cat /dev/ttyUSB0重定向到一个文本文件的后台进程,该文本文件在脚本中也称为监听文件。
start_grep()用于在对/dev/ttyUSB0发送指令后,获取其返回值的函数,其原理就是在setup_listen_file()函数创建的监听文件中搜索关键字,然后返回,再在外面进行逻辑判断处理,在搜索完成后,会清空监听文件中的内容,以防止干扰下一次搜索。
定义完这些通用函数后,接下来就是关键业务代码的实现,每个型号的4G模块可能略有差异,但总体逻辑框架都是一样的,最终目的也是一样的,
首先判断4G模块当前是否工作在ECM拨号模式下,如果不是,就修改4G模块为ECM模式,并重启4G模块,然后退出脚本,直到模块重启完成后再次触发udev调用该脚本,
在确保模式为ECM模式后,对模块进行简单拨号设置后直接拨号,最后使用udhcpc从4G模块中获取本地IP。
具体如下:
首先等待/dev/ttyUSB0设备的出现,以确保设备驱动完成初始化:
# 等待option驱动创建ttyUSB设备文件,不等的话会导致驱动报错
# 一般都会有/dev/ttyUSB0,所以以此为基准
wait_for_device $ttyusb
然后对所有可能的其他工作模式进行重新设置为ECM模式:
if [[ "$vid" == "0525" && "$pid" == "a4a7" ]]; then
stty -F $ttyusb raw -echo
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
exit 1
fi
if [[ "$vid" == "2dee" && "$pid" == "4d41" ]]; then
stty -F $ttyusb raw -echo
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
exit 1
fi
if [[ "$vid" == "2dee" && "$pid" == "4d43" ]]; then
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
exit 1
fi
如果当前为ECM模式对应的vid、pid,则开始拨号流程:
if [[ "$vid" == "2dee" && "$pid" == "4d42" ]]; then
# 拨号流程
...
首先清空/dev/ttyUSB0中的缓存,防止干扰接下来的操作,由于字符设备本身可能缓存为空,直接cat会导致挂起阻塞,所以添加1秒的超时限制,超时后杀死cat进程:
timeout 1 cat $ttyusb #输出剩下的字符,确保缓存为空
然后创建一个临时的监听文件,并开始监听:
# 创建监听文件
file="/tmp/ATbuf_$$"
setup_listen_file $file $ttyusb
# 获取监听进程PID
listener_pid=$!
sleep 1
"/tmp/ATbuf_"后面的表示当前进程的id,以区分开不同进程创建的监听文件。
这里要使用$!来获取setup_listen_file()函数中在后台cat进程的id,以便脚本结束时杀死该进程。
创建好监听文件和监听进程后,就可以执行业务代码发送AT指令了,
SLM320模块可以发送“AT+CPIN?”检查SIM卡是否就绪以保证模块完成初始化及SIM卡已插入:
# 检查SIM卡状态
echo_device "AT+CPIN?\r" $ttyusb
sleep 0.5
使用start_grep()函数搜索检查SIM卡指令的返回值,如果找不到就表示SIM卡异常,不予拨号并退出脚本:
# 查找"READY"关键字,判断SIM卡是否就绪
buf=$(start_grep "$file" '+CPIN: READY')
echo "$(date) -- $buf" >> /tmp/usb-log.txt #debug
if [ -z "$buf" ]; then
echo "$(date) -- usbmode not ready." >> /tmp/usb-log.txt #debug
exit 1
fi
然后这里为了确保SLM320工作模式为ECM模式或RNDIS模式,再次发送指令进行判断,由于两种模式都可以正常拨号使用,所以不做区分,如果并非这两种模式其中之一,则发送修改模式的AT指令并重启模块且退出脚本:
# 查找"+SYSNV: \"usbmode\""关键字,判断拨号状态
buf=$(start_grep "$file" '+SYSNV: "usbmode",')
echo "$(date) -- $buf" >> /tmp/usb-log.txt #debug
# 先判断是否能找到关键字
if [ ! -z "$buf" ]; then
if [[ "$buf" == *"\"usbmode\",3"* || "$buf" == *"\"usbmode\",5"* ]]; then
# 拨号
...
# 拨号完成
else
echo "$(date) -- usbmode error." >> /tmp/usb-log.txt #debug
echo_device "AT+SYSNV=1,\"usbmode\",5\r" $ttyusb
echo_device "AT+TRB\r" $ttyusb
fi
如果模式正确,则开始拨号,这里每个模块的拨号指令也有所不同:
# 拨号
echo_device "AT+CGACT=1\r" $ttyusb # 激活上下文
echo_device "AT+VERCTRL=0,1\r" $ttyusb # 自动激活网络
echo_device "AT^NDISDUP=1,1\r" $ttyusb # 拨号
# 对所有usb模块执行udhcpc获取ip,超时时间60秒
for interface in /sys/class/net/usb*; do
iface=$(basename $interface)
timeout 60 udhcpc -i $iface > /dev/null 2>&1 &
done
echo "$(date) -- okay." >> /tmp/usb-log.txt #debug
# 拨号完成
拨号后,使用for循环,对所有usb*网卡设备都使用udhcpc获取IP,因为不确定是usb0还是usb1还是其他,udhcpc会对已获取到IP的网卡设备进行忽略。
至此完成拨号,最后清理监听文件和监听进程,退出脚本:
# 清理监听进程
kill -9 "$listener_pid" &> /dev/null
# 删除监听文件
rm "$file"
exit 0
脚本拨号完成后就能在ifconfig中看到网卡已经能获取到本地IP,并且直接就能上网:
不过也有特殊情况,就像EC200模块,只有一种vid、pid,那就只能通过AT指令判断当前工作模式了:
# EC200G
ACTION=="add", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0904", RUN+="/usr/local/bin/usb-EC200G-inserted.sh 2c7c 0904 %k"
...
if [[ "$vid" == "2c7c" && "$pid" == "0904" ]]; then
stty -F $ttyusb raw -echo
timeout 1 cat $ttyusb #输出剩下的字符,确保缓存为空
# 创建监听文件
file="/tmp/ATbuf_$$"
setup_listen_file $file $ttyusb
# 获取监听进程PID
listener_pid=$!
sleep 1
# 检查拨号模式
echo_device "AT+QCFG=\"usbnet\"\r" $ttyusb
sleep 0.5
# 查找""usbnet","关键字,判断是否为ECM模式
buf=$(start_grep "$file" '+QCFG: "usbnet",')
echo "$(date) -- $buf" >> /tmp/usb-log.txt #debug
if [[ "$buf" != *"\"usbnet\",1"* ]]; then
echo_device "AT+QCFG=\"usbnet\",1\r" $ttyusb
echo_device "AT+RESET\r" $ttyusb
exit 1
fi
...
如果是ECM模式,向模块发送AT+QCFG="usbnet"指令则模块会返回+QCFG: "usbnet",1
如果是其他模式,则后面的数字就不是1了,借此可判断接下来应该修改模式还是继续拨号。
已知bug
- 对于存在多个同一或不同型号的4G模块,udev自动拨号只对第一个模块拨号,因为目前脚本无法对因存在多个模块而往后递增的/dev/ttyUSB*字符设备文件进行自动操作。
其他平台移植
首先在移植前,需要保证自身平台的内核已完成对虚拟usb网卡的适配支持,并且支持ECM、RNDIS拨号,
在插入4G模块后,存在/dev/ttyUSB*字符设备,并且在ifconfig能看到该网卡。
然后在文件系统构建中在/etc/udev/rules.d/目录下创建名为11-usb-autodial.rules的文件,并赋予足够的执行权限:
sudo touch /etc/udev/rules.d/11-usb-autodial.rules
sudo chmod a+rx /etc/udev/rules.d/11-usb-autodial.rules
在文件中加入所有4G模块的udev匹配规则:
# SLM320
ACTION=="add", ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7", RUN+="/usr/local/bin/usb-SLM320-inserted.sh 0525 a4a7 %k"
ACTION=="add", ATTRS{idVendor}=="2dee", ATTRS{idProduct}=="4d41", RUN+="/usr/local/bin/usb-SLM320-inserted.sh 2dee 4d41 %k"
ACTION=="add", ATTRS{idVendor}=="2dee", ATTRS{idProduct}=="4d42", RUN+="/usr/local/bin/usb-SLM320-inserted.sh 2dee 4d42 %k"
# SLM750
ACTION=="add", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="f601", RUN+="/usr/local/bin/usb-SLM750-inserted.sh 05c6 f601 %k"
ACTION=="add", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="f602", RUN+="/usr/local/bin/usb-SLM750-inserted.sh 05c6 f602 %k"
ACTION=="add", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="f603", RUN+="/usr/local/bin/usb-SLM750-inserted.sh 05c6 f603 %k"
ACTION=="add", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="f604", RUN+="/usr/local/bin/usb-SLM750-inserted.sh 05c6 f604 %k"
ACTION=="add", ATTRS{idVendor}=="05e3", ATTRS{idProduct}=="f610", RUN+="/usr/local/bin/usb-SLM750-inserted.sh 05e3 f610 %k"
# N720
ACTION=="add", ATTRS{idVendor}=="2949", ATTRS{idProduct}=="8241", RUN+="/usr/local/bin/usb-N720-inserted.sh 2949 8241 %k"
ACTION=="add", ATTRS{idVendor}=="2949", ATTRS{idProduct}=="8242", RUN+="/usr/local/bin/usb-N720-inserted.sh 2949 8242 %k"
ACTION=="add", ATTRS{idVendor}=="2949", ATTRS{idProduct}=="8243", RUN+="/usr/local/bin/usb-N720-inserted.sh 2949 8243 %k"
# EC200G
ACTION=="add", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0904", RUN+="/usr/local/bin/usb-EC200G-inserted.sh 2c7c 0904 %k"
# EC200U
ACTION=="add", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0901", RUN+="/usr/local/bin/usb-EC200G-inserted.sh 2c7c 0904 %k"
在/usr/local/bin/下放入以下脚本文件:
usb-SLM320-inserted.sh
然后给予这些脚本文件的执行权限:
sudo chmod a+rx /usr/local/bin/usb-*
如果是直接在开发板上完成以上步骤,则需重启开发板或输入以下指令使udev规则生效:
sudo /etc/init.d/udev restart
至此移植完成,插入模块后稍等片刻既可上网,如果出现问题,可以查看拨号脚本中生成的日志进行分析:
cat /tmp/usb-log.txt