关于MUX源码分析可参考之前的一篇博客:点击打开链接
名词定义
MUX: multiplexer protocol 多路复用协议,将原有的一条物理通道虚拟成几条可并发的逻辑通道,同网络协议中的物理链路,逻辑链路有点类似.实现上也类似,将逻辑通道上的数据打包,加上包头包尾,再发到物理通道SABM: 即 Set Asynchronous Balanced Mode的缩写 ,叫设置异步平衡模式
GSM 0710六种包
1. SABM2. DISC
这两个包是分别来建立、断开逻辑通道
3. UA
4. DM
这两个包是分别来响应正确、错误
5. UIH
6. UI
这两个可以命令包、也可以是状态包,区别是前者不对包数据部分进行校验
GSM 0710三种传输格式
1. Basic
格式:Flags(1B) Address(1B) Control(1B) Length(1~2B) Info(Length指定的可变长度) FCS(1B) Flag(1B)
Flags: 格式固定为0xF9
Address: 逻辑通道编号,类似于MAC地址,Bit1为E/A(表示该地址域是否只有本字节,为1表示只有本字节)、Bit2为C\R(用来指示是命令还是响应),bit3~8为逻辑通道号。
Control:Frame Type 1 2 3 4 5 6 7 8 备注
SABM (Set Asynchronous Balanced Mode) 1 1 1 1 P/F 1 0 0
UA (Unnumbered Acknowledgement) 1 1 0 0 P/F 1 1 0
DM (Disconnected Mode) 1 1 1 1 P/F 0 0 0
DISC (Disconnect) 1 1 0 0 P/F 0 1 0
UIH(Unnumbered Information with Header check) 1 1 1 1 P/F 1 1 1
UI (Unnumbered Information) 1 1 0 0 P/F 0 0 0 可选
P/F是Poll/Final位,表示测试/返回。后续会介绍。比如建立DLC的时候,主机发SABM帧, 并把P置1,如果成功,对方返回UA帧,并把F置1,否则返回DM帧,并把P置1。Length: 只有在基本模式下,该域才存在。可以是1个字节,也可以是2个字节长度,EA位表示该域是否只有本字节。为1表示本字节就是长度域,为0表示后续还有一个字节。
2. Advanced without error recovery
格式:Flag(1B) Address(1B) Control(1B) Information(长度不确定) FCS(1B) Flags(1B)
Flag: 格式固定为0x7e3. Advance with error recovery(高级模式错误纠正)
n_gsm.c内部结构体定义:
1. struct gsm_dlci:数据链路链接标识每个活动的数据链路都有一个gsm_dlci结构体,它是用来绑定链路层的一个an optional layer tty,为了避免复杂性,当串口复用关闭时要立马释放gsm_dlci。
2. struct gsm_mux:多路复用结构体,通过ldisc可以绑定该线路规程。
基本模式数据帧格式分析(该格式为逻辑通道建立):
gsmld_output: 00000000: f9 03 ef 09 e3 05 07 0d fb f9
f9: 帧头,MUX普通模式
03: 地址域,bit1-EA=1,表示长度就是本字节,如果是0表示还有后续字节
bit2-C/R=1,表示命令/响应,这里是主机到GPRS模块,所以为命令,
bit3~8=0,为逻辑通道号,0表示命令通道
ef: 控制域,表示UIH
09: 信息域, bit1=1 表示信息域长度就是本字节,为0表示后续还有字节
bit2~bit8=4 表示长度为4个字节,
e3 05 07 0d: 数据域,4个字节
fb: CRC校验
f9: 结尾
n_gsm.txt流程
1.创建设备节点
MAJOR=`cat /proc/devices |grep gsmtty | awk '{print $1}`
for i in `seq 1 4`; do
mknod /dev/ttygsm$i c $MAJOR $i
done
2. 打开真实串口
fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
3. 设置线路规程为N_GSM0710
int ldisc = N_GSM0710;
ioctl(fd, TIOCSETD, &ldisc);
//---------------------------
在设置串口的线路规程时会执行如下的操作!
tty_open(...)
tty_lookup_driver(...) //通过主次设备号获取对应的索引
get_tty_driver(...) //获取主次设备号在tty_drivers链表中的索引
tty_driver_lookup_tty(...) //通过索引获取对应的tty
retval = tty->ops->open(tty, filp); //调用n_gsm.c gsmld_open(...)
gsmld_open(...)
gsm_alloc_mux(...) //多路复用协议分配一个gsm
gsmld_attach_gsm(...) //ld线路规程绑定上面分配的gsm
gsm_activate_mux(...) //gsm激活mux
gsm_dlci_alloc(...) //根据地址分配dlci链路控制接口,由于这里的流程是绑定地址0,即命令通道
4. 配置GSM
/* get n_gsm configuration */
ioctl(fd, GSMIOC_GETCONF, &c);
/* we are initiator and need encoding 0 (basic) */
c.initiator = 1;
c.encapsulation = 0;
/* our modem defaults to a maximum size of 127 bytes */
c.mru = 127;
c.mtu = 127;
/* set the new configuration */
ioctl(fd, GSMIOC_SETCONF, &c);
----------------------------------
tty_ioctl(...)
tiocsetd(...)
gsmld_ioctl(...)
gsmld_config(...)
gsm_dlci_begin_close(...) //函数内部dlci->state=DLCI_CLOSED,所以这里直接退出!
gsm_cleanup_mux(...) //关闭之前的多路复用串口
//......
if (dlci) {
gc = gsm_control_send(gsm, CMD_CLD, NULL, 0); //高级模式关闭DLCI
if (gc)
gsm_control_wait(gsm, gc); //发送成功就等待关闭DLCI的返回帧,这里会阻塞等待返回
}
//......
gsm_activate_mux(...) //
init_timer(&gsm->t2_timer); //初始化定时器
gsm->t2_timer.function = gsm_control_retransmit; //数据发送失败、或没有响应时的重发回调函数
gsm->t2_timer.data = (unsigned long)gsm; //定时器参数
init_waitqueue_head(&gsm->event); //初始化一个等待事件
spin_lock_init(&gsm->control_lock);
spin_lock_init(&gsm->tx_lock);
if (gsm->encoding == 0) //基本模式
gsm->receive = gsm0_receive;
else //高级模式
gsm->receive = gsm1_receive;
gsm->error = gsm_error;
dlci = gsm_dlci_alloc(gsm, 0); //分配一个地址为0的DLCI命令数据链路控制接口
init_timer(&dlci->t1); //初始化t1定时器
dlci->t1.function = gsm_dlci_t1; //绑定定时器的回调函数
dlci->t1.data = (unsigned long)dlci; //定时器回调函数的参数
tty_port_init(&dlci->port);
dlci->port.ops = &gsm_port_ops;
dlci->gsm = gsm;
dlci->addr = addr;
dlci->adaption = gsm->adaption;
dlci->state = DLCI_CLOSED; //置DLCI初始化状态为DLCI_CLOSED
if (addr) //地址不为0表示数据通道
dlci->data = gsm_dlci_data;
else //为命令通道
dlci->data = gsm_dlci_command;
gsm->dlci[addr] = dlci; //将相应的dlci绑定到addr对应的gsm->dlci[addr]数组中
gsm_dlci_begin_open(...) //发送SABM/PF,然后直接退出,这里有个疑问,就是M590E老模块没有及时响应,是否是这里没有阻塞或加一个延时时间???
5. 打开虚拟串口open("/dev/ttygsm1")
----------------------------------
tty_open(...)
gsmtty_install(...)
gsm_dlci_alloc(...) //分配一个dlci链路控制接口
retval = tty->ops->open(tty, filp)
gsmtty_open(...)
gsm_dlci_begin_open(...) //链路控制接口与MODEM模块建立,即发送SABM/PF到MODEM
dlci->state = DLCI_OPENING; //置dlci状态为DLCI_OPENING
gsm_command(dlci->gsm, dlci->addr, SABM|PF); //发送SABM/PF
mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); //重启重发定时器
gsmld_receive_buf(...) //接收MODEM响应数据
gsm->receive(...)=>gsm0_receive(...)/gsm1_receive(...)
gsm_queue(...) //接收到MODEM响应数据处理
gsm_dlci_open(...)
del_timer(&dlci->t1); //删除重发定时器
dlci->state = DLCI_OPEN; //置dlci状态为DLCI_OPEN
wake_up(&dlci->gsm->event); //同时唤醒dlci->gsm->event事件
tty_port_block_til_ready(...)
tty_port_raise_dtr_rts(...)
gsm_dtr_rts(...)
gsmtty_modem_update(...) //猫状态命令MSC
gsm_control_send(...) //组帧,MSC命令发送
wait_event(gsm->event, gsm->pending_cmd == NULL);
mod_timer(&gsm->t2_timer, jiffies + gsm->t2 * HZ / 100); //更新定时器
gsm_control_transmit(...)
gsm_data_queue(...)
__gsm_data_queue(...)
gsm_data_kick(...)
gsm->output(...)=>gsmld_output(...)
gsm_control_wait(...)
wait_event(gsm->event, control->done == 1); //阻塞,等待事件,即等待上面的MSC数据从MODEM返回
gsmld_receive_buf(...) //接收MODEM的MSC响应数据
gsm->receive(...)=>gsm0_receive(...)/gsm1_receive(...)
gsm_queue(...) //接收到MODEM的MSC响应数据处理
dlci->data(dlci, gsm->buf, gsm->len)//回调下面的接口函数
gsm_dlci_command(...)
gsm_control_response(...)
ctrl = gsm->pending_cmd;
command |= 1;
if (ctrl != NULL && (command == ctrl->cmd || command == CMD_NSC)) {
del_timer(&gsm->t2_timer); //删除定时器
gsm->pending_cmd = NULL;
/* Rejected by the other end */
if (command == CMD_NSC)
ctrl->error = -EOPNOTSUPP;
ctrl->done = 1;
wake_up(&gsm->event); //到这里,唤醒上面gsm_control_wait中的阻塞事件
}
6. 发送AT.
tty_write(...)
do_tty_write(...)=>ld->ops->write
gsmtty_write(...)
gsm_dlci_data_kick(...)
gsm_dlci_data_output(...)
__gsm_data_queue(...)
gsm_data_kick(...)
gsm->output(...)=>gsmld_output(...)
gsm_dlci_data_sweep(...) //如果缓冲区数据没有传输完,将继续发送,而完成该操作的就是本函数,其实这里采用了递归机制
gsm_dlci_data_output()
__gsm_data_queue(...)
gsm_data_kick(...)
gsm->output(...)=>gsmld_output(...)
//到这里发送完进行判断缓冲区中的数据是否全部传输完,否则将继续调用gsm_dlci_data_sweep(...)
//接收数据接口
gsmld_receive_buf(...)
gsm->receive(...)=>gsm0_receive() //基本模式
gsm_queue(...)
dlci->data=>gsm_dlci_data(...) //MODEM返回数据进行处理
tty_insert_flip_string(...) //MODEM返回的数据传输的tty io层
tty_flip_buffer_push(port); //通知tty io层,MODEM数据已经到达,tty io层在将数据传送到应用层
交互流程如下:
1. MUX 虚拟地址0(命令通道)通信帧(SABM/PF)
终端发送:gsmld_output: 00000000: f9 03 3f 01 1c f9
终端接收:gsmld_receive: 00000000: f9 03 73 01 d7 f9
2. MUX 虚拟地址1(数据通道)通信帧(SABM/PF)
终端发送:gsmld_output: 00000000: f9 07 3f 01 de f9
终端接收:gsmld_receive: 00000000: f9 07 73 01 15 f9
老的模块和新的模块在这一步会不相同,新的模块在SABM/PF协商时可以正常收到数据,而老的模块却会收到多帧M590E上报的MODEM猫状态,如下:
终端接收:gsmld_receive: 00000000: f9 01 ef 0b e3 07 07 0c 01 79 f9 //M590E上报的MODEM状态
f9 07 73 01 15 f9 //虚拟地址1 SABM/PF返回帧,是我们需要的
f9 01 ef 0b e3 07 07 0c 01 79 f9 //M590E上报的MODEM状态
f9 01 ef 0b e3 07 07 06 01 79 f9 //M590E上报的MODEM状态
f9 01 ef 0b e3 07 07 06 01 79 f9 //M590E上报的MODEM状态
f9 01 ef 0b e3 07 07 06 01 79 f9 //M590E上报的MODEM状态
f9 01 ef 0b e3 07 07 06 01 79 f9 //M590E上报的MODEM状态
为了解决老模块问题,修改两点:
① n_gsm.c驱动定时器时间扩大20倍,即原来100ms->现在2s,340ms->6.8s
② 过滤掉MODEM模块上报的猫状态,分别为“07 0c 01”和“07 06 01”
3. 终端设置M590E MODEM的猫状态终端发送:gsmld_output: 00000000: f9 03 ef 09 e3 05 07 0d fb f9
终端接收:gsmld_receive: 00000000: f9 01 ef 09 e1 05 07 0d 9a f9
4. 通过MUX虚拟串口1发送AT
终端发送:gsm_data_kick: 00000000: f9 07 ef 03 41 d4 f9 //发送‘A’
gsm_data_kick: 00000000: f9 07 ef 03 54 d4 f9 //发送‘T’
终端接收:gsmld_receive: 00000000: f9 05 ef 0d 0d 0a 4f 4b 0d 0a 5f f9 //接收”ok“
5. 数据通道交互