浏览代码

[add] Initial commit, first version

Signed-off-by: liuxinaliang <liuxianliang@rt-thread.com>
liuxinaliang 5 年之前
父节点
当前提交
c6df46ed9f
共有 18 个文件被更改,包括 2219 次插入2 次删除
  1. 55 0
      .gitignore
  2. 207 2
      README.md
  3. 15 0
      SConscript
  4. 84 0
      class/cmux_air720.c
  5. 185 0
      docs/cmux_basic.md
  6. 92 0
      docs/cmux_port.md
  7. 二进制
      docs/figures/cmux_course.png
  8. 二进制
      docs/figures/cmux_data_store.png
  9. 二进制
      docs/figures/cmux_frame.png
  10. 二进制
      docs/figures/modify.png
  11. 二进制
      docs/figures/prepare_modify.png
  12. 二进制
      docs/figures/protocol_stack.png
  13. 106 0
      inc/cmux.h
  14. 40 0
      inc/cmux_chat.h
  15. 104 0
      sample/sample.c
  16. 1016 0
      src/cmux.c
  17. 258 0
      src/cmux_chat.c
  18. 57 0
      src/cmux_utils.c

+ 55 - 0
.gitignore

@@ -0,0 +1,55 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+# folder
+/.vscode

+ 207 - 2
README.md

@@ -1,2 +1,207 @@
-# cmux
-connection multiplexing protocol for RT-Thread, support GSM0710 .etc
+# CMUX
+
+## 1. 简介
+
+- cmux 软件包,是 RT-Thread 针对 GSM0707 / GSM0710 功能实现的软件包。它可是实现在一个真实物理通道上虚拟出多个通道,在一条物理串口接口上实现多个数据链路连接,使得可以同时在一个物理串口接口上存在多个会话。通过多路复用协议和蜂窝模组通讯,使得可以在拨号上网时可以同时拨打电话和收发短信。
+
+  cmux 软件包特点如下:
+
+  - 支持多种蜂窝模块;
+  
+  * 可以添加 PPP 功能
+
+  目前 CMUX 功能支持 Luat Air720模块,后续会接入更多蜂窝模块。
+
+  
+
+  对 CMUX 有疑惑,或者对 CMUX 感兴趣的开发者欢迎入群详细了解。
+
+  QQ群:749347156 [<传送门>](https://jq.qq.com/?_wv=1027&k=5KcuPGI)
+
+  CMUX 详细介绍:[<文档>](./docs/cmux_basic.md)
+
+  通过 CMUX 使用 PPP_DEVICE 文档:[<使用文档>](./docs/cmux_port.md)
+
+  ### 1.1 框架图
+
+  ![](docs/figures/cmux_frame.png)
+
+- CMUX 软件包实现得是 cmux-client 的内容
+  
+- CMUX 是一种类似于传输层的协议,用户使用时无法感知该层
+
+- CMUX 在应用场景中多用于 UART, 如有必要也可以支持 SPI 方式
+  
+  ### 1.2 目录结构
+```shell
+cmux
+├───docs 
+│   └───figures                     // 文档使用图片	
+├───class    
+│   └───cmux_air720.c               // 针对不同设备
+├───sample                          // 示例文件
+│   └─── sample.c     
+├───inc                             // 头文件
+│   └─── cmux.h       
+├───src                             // 移植文件
+│   └─── cmux.c
+├───LICENSE                         // 软件包许可证
+├───README.md                       // 软件包使用说明
+└───SConscript                      // RT-Thread 默认的构建脚本
+```
+
+### 1.3 许可证
+
+cmux 软件包遵循 Apache-2.0 许可,详见 LICENSE 文件。
+
+  ### 1.4 依赖
+
+  - RT-Thread 3.1.0+
+
+## 2. 获取方式
+
+​	**CMUX 软件包相关配置选项介绍**
+
+```c
+  [*] cmux protocol for rt-thread.  --->
+      [ ] using cmux debug feature (NEW)
+      (10) set cmux max frame list length (NEW)
+      (uart2) the real cmux serial prot (NEW)
+      (3) the number of cmux modem port (NEW)
+      (AT+CMUX=0,0,5,127,10,3,30,10,2) the command for cmux function (NEW)
+      Version (latest)  --->
+```
+
+- **using cmux debug feature:** 开启调试日志功能
+- **set cmux max frame list length:** 设置虚拟端口的 frame 链的最大长度
+- **the real cmux serial prot:** cmux 使用的真实串口名称
+- **the number of cmux modem port:** 蜂窝模块支持的虚拟串口数量
+- **the command for cmux function:** 进入 cmux 模式的命令
+- **Version:** 软件包版本号
+
+## 3. 使用方式
+
+cmux 软件包初始化函数如下所示:
+
+**cmux 功能启动函数,该函数自动调用**
+
+```c
+int cmux_sample_start(void)
+{
+    rt_err_t result;
+    sample = cmux_object_find(CMUX_DEPEND_NAME);
+    if(sample == RT_NULL)
+    {
+        result = -RT_ERROR;
+        LOG_E("Can't find %s", CMUX_DEPEND_NAME);
+        goto end;
+    }
+    result =cmux_start(sample);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux sample start error. Can't find %s", CMUX_DEPEND_NAME);
+        goto end;
+    }
+    LOG_I("cmux sample (%s) start successful.", CMUX_DEPEND_NAME);
+#ifdef CMUX_AT_NAME
+    result = cmux_attach(sample, CMUX_AT_PORT,  CMUX_AT_NAME, RT_DEVICE_FLAG_DMA_RX, RT_NULL);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux attach (%s) failed.", CMUX_AT_NAME);
+        goto end;
+    }
+    LOG_I("cmux object channel (%s) attach successful.", CMUX_AT_NAME);
+#endif
+#ifdef CMUX_PPP_NAME
+    result = cmux_attach(sample, CMUX_PPP_PORT, CMUX_PPP_NAME, RT_DEVICE_FLAG_DMA_RX, RT_NULL);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux attach %s failed.", CMUX_PPP_NAME);
+        goto end;
+    }
+    LOG_I("cmux object channel (%s) attach successful.", CMUX_PPP_NAME);
+#endif
+
+end:
+    return RT_EOK;
+}
+// 自动初始化
+INIT_APP_EXPORT(cmux_sample_start);
+// 命令导出到MSH( cmux_sample_start 变更为cmux_start )
+MSH_CMD_EXPORT_ALIAS(cmux_sample_start, cmux_start, a sample of cmux function);
+```
+
+* 模块进入 cmux 模式,注册 AT 和 PPP 接口到 CMUX 上;
+
+
+
+模块上电后,自动初始化流程如下:
+
+```shell
+ \ | /
+- RT -     Thread Operating System
+ / | \     4.0.2 build Apr 14 2020
+ 2006 - 2019 Copyright by rt-thread team
+lwIP-2.0.2 initialized!
+[I/sal.skt] Socket Abstraction Layer initialize success.
+[I/ppp.dev] ppp_device(pp) register successfully.
+[I/cmux] cmux rely on (uart3) init successful.
+[I/cmux.air720] cmux has been control uart3.
+msh >[W/chat] <tx: AT, want: OK, retries: 10, timeout: 1> timeout
+[I/cmux.sample] cmux sample (uart3) start successful.
+[I/cmux.sample] cmux object channel (cmux_at) attach successful.
+[I/cmux.sample] cmux object channel (cmux_ppp) attach successful.
+```
+
+设备上电初始化完成,模块提示模块已经进入 CMUX 模式,可以使用已经注册的虚拟串口。
+
+
+
+你也可以同时使用 PPP_DEVICE 提供 ppp 功能,效果如下:
+
+```shell
+ \ | /
+- RT -     Thread Operating System
+ / | \     4.0.2 build Apr 14 2020
+ 2006 - 2019 Copyright by rt-thread team
+lwIP-2.0.2 initialized!
+[I/sal.skt] Socket Abstraction Layer initialize success.
+[I/ppp.dev] ppp_device(pp) register successfully.
+[I/cmux] cmux rely on (uart3) init successful.
+[I/cmux.air720] cmux has been control uart3.
+msh >[I/cmux.sample] cmux sample (uart3) start successful.
+[I/cmux.sample] cmux object channel (cmux_at) attach successful.
+[I/cmux.sample] cmux object channel (cmux_ppp) attach successful.
+msh >ppp_start
+[I/ppp.dev] ppp_device connect successfully.
+ping www.baidu.com
+60 bytes from 39.156.66.14 icmp_seq=0 ttl=49 time=126 ms
+60 bytes from 39.156.66.14 icmp_seq=1 ttl=49 time=129 ms
+60 bytes from 39.156.66.14 icmp_seq=2 ttl=49 time=111 ms
+60 bytes from 39.156.66.14 icmp_seq=3 ttl=49 time=111 ms
+msh >ready
+msh >[D/main] cmux control channel has been open.
+[D/main] write data : 9
+[D/main] 15 ,Recieve  
++CSQ: 29,99
+
+[D/main] 6 ,Recieve  
+OK
+```
+在使用 PPP 功能的同时,可以使用虚拟出的 cmux_at 串口同时读取模块信号强度值,具体可以参考[<移植文档>](./docs/cmux_port.md)
+
+## 4. 注意事项
+
+* 使用 PPP 功能详情参考 [PPP_DEVICE](https://github.com/RT-Thread-packages/ppp_device)
+* 只有在虚拟串口注册到 rt_device 框架后才能通过 rt_device_find 找到虚拟串口,要注意先后顺序
+* 虚拟串口 attach 后并不能直接使用,必须通过 rt_device_open 打开后才能使用,符合 rt_device 的操作流程
+
+## 5. 联系方式
+
+联系人:xiangxistu
+
+Email: liuxianliang@rt-thread.com
+
+## 6. 致谢
+
+感谢网友 @ya-jeks 在 github 上的 gsmmux 参考代码

+ 15 - 0
SConscript

@@ -0,0 +1,15 @@
+from building import *
+
+cwd = GetCurrentDir()
+path = [cwd + '/inc']
+src  = Glob('src/*.c')
+src += Glob('sample/sample.c')
+
+src += Glob('class/cmux_air720.c')
+
+if GetDepend(['PKG_USING_PPP_DEVICE']):
+	SrcRemove(src, "src/cmux_chat.c")
+
+group = DefineGroup('cmux', src, depend = ['PKG_USING_CMUX'], CPPPATH = path)
+
+Return('group')

+ 84 - 0
class/cmux_air720.c

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2006-2020, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2020-04-15    xiangxistu      the first version
+ */
+
+#include <cmux.h>
+
+#ifdef PKG_USING_PPP_DEVICE
+#include <ppp_chat.h>
+#else
+#include <cmux_chat.h>
+#endif
+
+#define DBG_TAG    "cmux.air720"
+
+#ifdef CMUX_DEBUG
+#define DBG_LVL   DBG_LOG
+#else
+#define DBG_LVL   DBG_INFO
+#endif
+
+#include <rtdbg.h>
+
+struct cmux *air720 = RT_NULL;
+
+static const struct modem_chat_data cmd[] =
+{
+    {"AT",              MODEM_CHAT_RESP_OK,              10, 1, RT_FALSE},
+    {CMUX_CMD,          MODEM_CHAT_RESP_OK,               5, 1, RT_FALSE},
+};
+
+static rt_err_t cmux_at_command(struct rt_device *device)
+{
+    //private control
+
+    return modem_chat(device, cmd, sizeof(cmd) / sizeof(cmd[0]));
+}
+
+rt_err_t air720_cmux_start(struct cmux *obj)
+{
+    rt_err_t result = 0;
+    struct rt_device *device = RT_NULL;
+
+    device = obj->dev;
+    result = rt_device_open(device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux can't open %s.", device->parent.name);
+        goto _end;
+    }
+    LOG_I("cmux has been control %s.", device->parent.name);
+
+    result = cmux_at_command(device);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux start failed.");
+        goto _end;
+    }
+
+_end:
+    return result;
+}
+const struct cmux_ops cmux_ops =
+{
+    air720_cmux_start,
+    RT_NULL,
+    RT_NULL
+};
+
+int cmux_air720_init(void)
+{
+    air720 = rt_malloc(sizeof(struct cmux));
+    rt_memset(air720, 0, sizeof(struct cmux));
+    
+    air720->ops = &cmux_ops;
+
+    return cmux_init(air720, CMUX_DEPEND_NAME, CMUX_PORT_NUMBER, RT_NULL);
+}
+INIT_COMPONENT_EXPORT(cmux_air720_init);

+ 185 - 0
docs/cmux_basic.md

@@ -0,0 +1,185 @@
+# CMUX 功能
+
+## 1. CMUX 的实现原理
+
+GSM 07.10 协议支持
+
+<img src="./figures/cmux_course.png" alt="cmux_course" style="zoom:90%;" />
+
+​    以上是一次 CMUX 建立,通话和销毁的基本流程,因为牵扯到真实串口到虚拟串口的交互,所以可以在一个物理串口上同时实现多个功能。在 PPP 拨号启用时,仍然可以调用 AT 命令,也可以使用 Modem 功能同时支持数据通话。
+
+## 2. CMUX 的实现协议
+
+​	多路复用协议提供在单个物理通信通道之上虚拟出多个并行的逻辑通信通道的能力,一般应用于TE(Terminal Equipment)与MS(Mobile Station)之间,TE相当于智能手机的AP端,MS相当于智能手机的MODEM端。多路复用协议的实现效果如图:
+
+<img src="./figures/protocol_stack.png" alt=" " style="zoom:75%;" />
+
+>    实际使用中,TE 端的 MUX 向 MS 端的 MUX 发起通道建立请求,设置通道参数等,是主动的一方;
+>
+>   MS端的MUX等待TE端的服务请求,根据自身能力提供相应服务。
+
+1. **启动CMUX服务** :向模块发送 AT + CMUX 命令
+2. **建立DLC服务**  :建立数据连接
+3. **数据服务**
+4. 功耗控制
+5. **释放DLC服务**
+6. **关闭服务**
+7. 控制服务
+
+#### 2.1 CMUX 数据格式
+
+| Flag        | Address | Control |   Length   | Information | FCS    | Flag        |
+| ----------- | ------- | ------- | :--------: | ----------- | ------ | ----------- |
+| 0xF9(basic) | 地址域  | 控制域  | 数据域长度 | 实际数据域  | 校验域 | 0xF9(basic) |
+
+**地址域:**
+
+| Bit No. | 1    | 2    | 3    | 4    | 5    | 6    | 7    | 8    |
+| ------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
+| Signal  | EA   | C/R  | DLCI | DLCI | DLCI | DLCI | DLCI | DLCI |
+
+**控制域:**
+
+| 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    | 可选 |
+
+1. SABM SABM命令帧,异步平衡模式
+2. UA  UA回应帧,对SABM和DISC这两个命令帧的确认
+3. DM  如果在链路断开状态,收到DISC命令,就应该发一个DM作为响应
+4. DISC DISC命令用于终止通道
+5. UIH  UIH命令帧/响应帧,相对于 UI 帧只对地址域,控制域和长度域校验
+6. UI  UI命令帧/响应帧
+
+## 3. CMUX 协议的验证平台及验证方法
+
+STM32F407 + Air720H(手动拼接命令测试)
+
+​    使用 AT+CMUX 命令,进入 CMUX 配置功能;测试 CMUX 功能是否正常:
+
+1.  PPP 拨号正常
+2.  AT 命令查询模块信号强度正常
+
+手动发送的 CMUX 命令:
+
+![cmux_data_store](./figures/cmux_data_store.png)
+
+## 4. CMUX 在 RT-Thread 上的适配
+
+#### 4.1 移植的环境与目标
+
+使用 STM32F407 + Air720H 平台,实现 CMUX 功能;
+
+​    目标是做成软件包的形式,软件包的功能是打开后,将通过 cmux 软件包向上虚拟出 vcom1, vcom2, vcom3 以代表 ppp, at, modem 这些接口,向下接管真实物理串口,通过 cmux 协议解析数据以实现正常传输。使用时通过 rt_device_find 查找使用,以实现较大的通用性。
+
+#### 4.2 CMUX 框架图定义和介绍
+
+![cmux_frame](./figures/cmux_frame.png)
+
+1. CMUX 属于串口驱动之上,在通信协议 PPP / AT 之下;主要用于 MCU 和通讯模块在不同通道上面进行不同业务的通信及交互功能;
+2. CMUX 分为服务器和客户端部分,服务器与客户端部分实现并不相同;
+3. 我们在移植 CMUX 时,客户端与服务器是两个设备,只需要实现 CMUX 客户端部分即可,服务器由通讯模块提供;
+4. CMUX 是指在一个物理串口上实现多个虚拟串口设备功能,只能实现在一对一设备上,且双方必须同时支持 CMUX(GSM0707 / GSM0710) 协议; 
+
+#### 4.3 CMUX 的移植参考 
+
+基于 [Github](https://github.com/ya-jeks/gsmmux.git) 上 gsmmux 仓库,该仓库是针对 Linux 系统实现的 CMUX 协议支持;
+
+#### 4.3.1 软件包结构
+
+```shell
+cmux
+├───docs 
+│   └───figures                     // 文档使用图片	
+├───class    
+│   └───cmux_air720.c               // 针对不同设备
+├───sample                          // 示例文件
+│   └─── sample.c     
+├───inc                             // 头文件
+│   └─── cmux.h       
+├───src                             // 移植文件
+│   └─── cmux.c
+├───LICENSE                         // 软件包许可证
+├───README.md                       // 软件包使用说明
+└───SConscript                      // RT-Thread 默认的构建脚本
+```
+
+#### 4.3.2 基础介绍
+
+1. CMUX 的帧结构介绍
+
+   ```c
+   typedef struct cmux_buffer
+   {
+     rt_uint8_t data[CMUX_BUFFER_SIZE];
+     rt_uint8_t *read_point;                          // 用于读取 CMUX 数据的指针
+     rt_uint8_t *write_point;                         // 指向 CMUX 数据区的指针
+     rt_uint8_t *end_point;                           // 指向 CMUX 数据区末尾的指针
+     int flag_found;                                  // 是否找到 0xF9 帧头
+   } cmux_buffer;
+   
+   typedef struct cmux_frame {
+     rt_uint8_t channel;                             // 地址域
+     rt_uint8_t control;                             // 数据帧类型, SABM,UIH,UA; 控制域
+     int data_length;                                // 数据长度
+     rt_uint8_t *data;                               // 实际数据
+} cmux_frame;
+   ```
+   
+2. CMUX 不同帧类型介绍
+
+   ```shell
+   // bits: Poll/final, Command/Response, Extension
+   #define CMUX_CONTROL_PF 16
+   #define CMUX_ADDRESS_CR 2
+   #define CMUX_ADDRESS_EA 1
+   // the types of the frames
+   #define CMUX_FRAME_SABM 47
+   #define CMUX_FRAME_UA 99
+   #define CMUX_FRAME_DM 15
+   #define CMUX_FRAME_DISC 67
+   #define CMUX_FRAME_UIH 239
+   #define CMUX_FRAME_UI 3
+   // the types of the control channel commands
+   #define CMUX_C_CLD 193
+   #define CMUX_C_TEST 33
+   #define CMUX_C_MSC 225
+   #define CMUX_C_NSC 17
+   // basic mode flag for frame start and end
+   #define CMUX_HEAD_FLAG (unsigned char)0xF9
+   ```
+   
+
+#### 4.3.2 实现
+
+1. 需要实现的结构体
+
+   ```c
+   typedef struct cmux
+   {
+       struct rt_device *dev;                        /* device object */
+       const struct cmux_ops *ops;                   /* cmux device ops interface */
+       cmux_set_command_t cmd;                       /* CMUX start AT command */
+       struct cmux_buffer *buffer;                   /* cmux buffer */
+       rt_thread_t *recv_tid;                        /* recieve thread point */
+       rt_uint8_t vcom_num;                          /* the cmux port number array */
+       struct cmux_vcom *vcom;                       /* virual serial device */
+       rt_slist_t *list;                             /* cmux list */
+   
+       void *user_data;                              /* reserve */
+   }cmux;
+   
+   struct cmux_ops
+   {
+       rt_err_t  *(start)     (struct cmux *obj);
+       rt_err_t  *(stop)      (struct cmux *obj);
+       rt_err_t  *(control)   (struct cmux *obj, int cmd, void *arg);
+   };
+   ```
+   
+

+ 92 - 0
docs/cmux_port.md

@@ -0,0 +1,92 @@
+# PPP 功能使用
+
+在 CMUX 中使用 PPP_DEVICE 与直接使用 PPP_DEVICE 的使用差别不大;使用最新版本的 PPP_DEVICE 可以做到 cmux 的兼容效果,无论是打开 cmux 功能均可以正常使用 PPP_DEVICE。
+
+
+
+如果使用低版本的 PPP_DEVICE ,务必注意一下几点。
+
+1. 相对于只使用 PPP_DEVICE,CMUX + PPP_DEVICE 组合需要使用 NOT_NEED 设置
+
+   出现这个问题的原因是因为 chat 功能相对简单,匹配回复的字符串时容易受到超时判定的制约,导致提前退出,匹配失败。关闭匹配字符串功能,可以防止错误判断;后面会优化 chat 方法。
+
+![modify](./figures/modify.png)
+
+2. rst_cmd 是考虑到模块仍在 ppp 状态需要先退出 ppp 功能而考虑的
+
+在使用 cmux + ppp_device 组合时,ppp 拨号的前提就是模块已经进入 cmux 模式,无需发送 rst_cmd 命令
+
+![modify](./figures/prepare_modify.png)
+
+3. 示例中的 ready 功能是测试命令,代码如下
+
+   ```c
+   #define DBG_TAG "ready"
+   #define DBG_LVL DBG_LOG
+   #include <rtdbg.h>
+   
+   static rt_uint8_t flag = 0;
+   struct rt_device *device = RT_NULL;
+   rt_err_t result;
+   rt_size_t size;
+   rt_uint8_t buffer[20];
+   rt_size_t count = 0;
+   
+   static rt_err_t recieve_invkoen(rt_device_t dev, rt_size_t size)
+   {
+       RT_ASSERT(dev != RT_NULL);
+       flag = 1;
+       return RT_EOK;
+   }
+   
+   int cmux_ctl_command(void *parameter)
+   {
+       device = rt_device_find("cmux_at");
+       if(device == RT_NULL)
+       {
+           LOG_E("Sorry, can't find cmux control channel.");
+       }
+       else
+       {
+           result = rt_device_open(device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_DMA_RX);
+           if(result != RT_EOK)
+           {
+               LOG_E("Sorry, can't open cmux control channel.");
+           }
+           LOG_D("cmux control channel has been open.");
+       }
+       rt_device_set_rx_indicate(device, recieve_invkoen);
+   
+       size = rt_device_write(device, 0, "AT+CSQ\r\n", sizeof("AT+CSQ\r\n"));
+       LOG_D("write data : %d", size);
+   
+       while(count++ < 500)
+       {
+           size = rt_device_read(device, 0, buffer, 20);
+           if(size != 0)
+           {
+               buffer[size] = '\0';
+               LOG_D("%d ,Recieve  %s", size, buffer);
+               flag = 0;
+           }
+       }
+       count = 0;
+       return RT_EOK;
+   }
+   
+   int ready(void *para)
+   {
+       rt_thread_t read_thread = RT_NULL;
+   
+       read_thread = rt_thread_create("read",
+                                       (void (*)(void *parameter))cmux_ctl_command,
+                                       RT_NULL,
+                                       2048,
+                                       25,
+                                       20);
+       rt_thread_startup(read_thread);
+   }
+   MSH_CMD_EXPORT(ready, ready);
+   ```
+
+   还在考虑直接使用 at_client 功能,敬请期待。

二进制
docs/figures/cmux_course.png


二进制
docs/figures/cmux_data_store.png


二进制
docs/figures/cmux_frame.png


二进制
docs/figures/modify.png


二进制
docs/figures/prepare_modify.png


二进制
docs/figures/protocol_stack.png


+ 106 - 0
inc/cmux.h

@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2006-2020, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2020-04-15    xiangxistu      the first version
+ */
+
+#include <rtdef.h>
+
+#define CMUX_BUFFER_SIZE   2048
+
+#ifdef RT_USING_LIBC
+#include <lib.c>
+#else
+#define min(a,b) ((a)<=(b)?(a):(b))
+#endif
+
+// increases buffer pointer by one and wraps around if necessary
+#define INC_BUF_POINTER(buf,p) p++; if (p == buf->end_point) p = buf->data;
+
+/* Tells, how many chars are saved into the buffer.
+ *
+ */
+//int cmux_buffer_length(cmux_buffer *buf);
+#define cmux_buffer_length(buff) ((buff->read_point > buff->write_point) ? (CMUX_BUFFER_SIZE - (buff->read_point - buff->write_point)) : (buff->write_point - buff->read_point))
+
+/* Tells, how much free space there is in the buffer
+ */
+//int cmux_buffer_free(cmux_buffer *buf);
+#define cmux_buffer_free(buff) ((buff->read_point > buff->write_point) ? (buff->read_point - buff->write_point) : (CMUX_BUFFER_SIZE - (buff->write_point - buff->read_point)))
+
+typedef struct cmux_buffer
+{
+  rt_uint8_t data[CMUX_BUFFER_SIZE];                        /* cmux data buffer */
+  rt_uint8_t *read_point;
+  rt_uint8_t *write_point;
+  rt_uint8_t *end_point;
+  int flag_found;                                          /* the flag whether you find cmux frame */
+} cmux_buffer;
+
+typedef struct cmux_frame
+{
+    rt_uint8_t channel;                                   /* the frame channel */
+    rt_uint8_t control;                                   /* the type of frame */
+    int data_length;                                      /* frame length */
+    rt_uint8_t *data;                                     /* the point for cmux data */
+} cmux_frame;
+
+typedef struct frame
+{
+    struct cmux_frame *frame;                             /* the point for cmux frame */
+
+    rt_slist_t frame_list;                                /* slist for different virtual serial */
+}frame;
+
+typedef struct cmux_vcoms
+{
+    struct rt_device device;                              /* virtual device */
+
+    rt_slist_t flist;                                     /* head of frame_list */
+
+    rt_uint16_t frame_index;                              /* the length of flist */
+
+    rt_bool_t frame_using_status;                         /* This is designed for long frame when we read data; the flag will be "1" when long frame haven't reading done */
+}cmux_vcoms;
+
+typedef struct cmux
+{
+    struct rt_device *dev;                                /* device object */
+    const struct cmux_ops *ops;                           /* cmux device ops interface */
+    struct cmux_buffer *buffer;                           /* cmux buffer */
+    struct cmux_frame *frame;                             /* cmux frame point */
+    rt_thread_t recv_tid;                                 /* receive thread point */
+    rt_uint8_t vcom_num;                                  /* the cmux port number */
+    struct cmux_vcoms *vcoms;                             /* array */
+
+    struct rt_event *event;                               /* internal communication */
+    struct rt_messagequeue *mq;                           /* internal communication */
+
+    rt_slist_t list;                                      /* cmux list */
+
+    void *user_data;                                      /* reserve */
+}cmux;
+
+struct cmux_ops
+{
+    rt_err_t  (*start)     (struct cmux *obj);
+    rt_err_t  (*stop)      (struct cmux *obj);
+    rt_err_t  (*control)   (struct cmux *obj, int cmd, void *arg);
+};
+
+
+
+/* those function is opening for external file */
+rt_err_t cmux_init(struct cmux *object, const char *dev_name, rt_uint8_t vcom_num, void *user_data);
+rt_err_t cmux_start(struct cmux *object);
+rt_err_t cmux_stop(struct cmux *object);
+rt_err_t cmux_attach(struct cmux *object, int port, const char *alias_name, rt_uint16_t flags, void *user_data);
+rt_err_t cmux_detach(struct cmux *object, const char *alias_name);
+
+/* cmux_utils */
+rt_uint8_t cmux_frame_check(const rt_uint8_t *input, int length);
+struct cmux *cmux_object_find(const char *name);

+ 40 - 0
inc/cmux_chat.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019 xiaofan <xfan1024@live.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author          Notes
+ * 2019-09-19     xiaofan         the first version
+ * 2020-04-13     xiangxistu      transplant into cmux
+ */
+
+#ifndef __MODEM_CHAT_H__
+#define __MODEM_CHAT_H__
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define MODEM_CHAT_RESP_LIST(F) \
+    F(MODEM_CHAT_RESP_OK,         "OK"), \
+    F(MODEM_CHAT_RESP_ERROR,      "ERROR") \
+
+#define DEFINE_MODEM_RESP_ID_TABLE(id, s) id
+
+enum {
+    MODEM_CHAT_RESP_LIST(DEFINE_MODEM_RESP_ID_TABLE),
+    MODEM_CHAT_RESP_MAX,
+    MODEM_CHAT_RESP_NOT_NEED,
+};
+
+struct modem_chat_data {
+    const char* transmit;
+    rt_uint8_t expect;      // use CHAT_RESP_xxx
+    rt_uint8_t retries;
+    rt_uint8_t timeout;     // second
+    rt_bool_t  ignore_cr;   // ignore CR character if it is RT_TRUE
+};
+
+rt_err_t modem_chat(rt_device_t serial, const struct modem_chat_data *data, rt_size_t len);
+
+#endif  /* __MODEM_CHAT_H__ */

+ 104 - 0
sample/sample.c

@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2006-2020, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2020-04-15    xiangxistu      the first version
+ */
+ 
+#include <cmux.h>
+#include <rtthread.h>
+
+
+#define CMUX_PPP_NAME "cmux_ppp"
+#define  CMUX_AT_NAME "cmux_at"
+
+#define CMUX_PPP_PORT 1
+#define  CMUX_AT_PORT 2
+
+#define DBG_TAG    "cmux.sample"
+
+#ifdef CMUX_DEBUG
+#define DBG_LVL   DBG_LOG
+#else
+#define DBG_LVL   DBG_INFO
+#endif
+
+#include <rtdbg.h>
+
+struct cmux *sample = RT_NULL;
+
+int cmux_sample_start(void)
+{
+    rt_err_t result;
+    sample = cmux_object_find(CMUX_DEPEND_NAME);
+    if(sample == RT_NULL)
+    {
+        result = -RT_ERROR;
+        LOG_E("Can't find %s", CMUX_DEPEND_NAME);
+        goto end;
+    }
+    result =cmux_start(sample);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux sample start error. Can't find %s", CMUX_DEPEND_NAME);
+        goto end;
+    }
+    LOG_I("cmux sample (%s) start successful.", CMUX_DEPEND_NAME);
+#ifdef CMUX_AT_NAME
+    result = cmux_attach(sample, CMUX_AT_PORT,  CMUX_AT_NAME, RT_DEVICE_FLAG_DMA_RX, RT_NULL);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux attach (%s) failed.", CMUX_AT_NAME);
+        goto end;
+    }
+    LOG_I("cmux object channel (%s) attach successful.", CMUX_AT_NAME);
+#endif
+#ifdef CMUX_PPP_NAME
+    result = cmux_attach(sample, CMUX_PPP_PORT, CMUX_PPP_NAME, RT_DEVICE_FLAG_DMA_RX, RT_NULL);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux attach %s failed.", CMUX_PPP_NAME);
+        goto end;
+    }
+    LOG_I("cmux object channel (%s) attach successful.", CMUX_PPP_NAME);
+#endif
+
+end:
+    return RT_EOK;
+}
+INIT_APP_EXPORT(cmux_sample_start);
+MSH_CMD_EXPORT_ALIAS(cmux_sample_start, cmux_start, a sample of cmux function);
+
+int cmux_sample_stop(void)
+{
+    rt_err_t result;
+#ifdef CMUX_AT_NAME
+    result = cmux_detach(sample, CMUX_AT_NAME);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux object (%s) detach failed.", CMUX_AT_NAME);
+        goto end;
+    }
+#endif
+#ifdef CMUX_PPP_NAME
+    result = cmux_detach(sample, CMUX_PPP_NAME);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux object (%s) detach failed.", CMUX_PPP_NAME);
+        goto end;
+    }
+#endif
+    result = cmux_stop(sample);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux sample stop error.");
+        goto end;
+    }
+
+end:
+    return RT_EOK;
+}
+MSH_CMD_EXPORT_ALIAS(cmux_sample_stop, cmux_stop, a sample of cmux function);

+ 1016 - 0
src/cmux.c

@@ -0,0 +1,1016 @@
+/*
+ * Copyright (c) 2006-2020, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2020-04-15    xiangxistu      the first version
+ */
+
+#include <cmux.h>
+#include <rtthread.h>
+#include <rthw.h>
+
+// bits: Poll/final, Command/Response, Extension
+#define CMUX_CONTROL_PF 16
+#define CMUX_ADDRESS_CR 2
+#define CMUX_ADDRESS_EA 1
+// the types of the frames
+#define CMUX_FRAME_SABM 47
+#define CMUX_FRAME_UA 99
+#define CMUX_FRAME_DM 15
+#define CMUX_FRAME_DISC 67
+#define CMUX_FRAME_UIH 239
+#define CMUX_FRAME_UI 3
+// the types of the control channel commands
+#define CMUX_C_CLD 193
+#define CMUX_C_TEST 33
+#define CMUX_C_MSC 225
+#define CMUX_C_NSC 17
+// basic mode flag for frame start and end
+#define CMUX_HEAD_FLAG (unsigned char)0xF9
+
+#define CMUX_COMMAND_IS(command, type) ((type & ~CMUX_ADDRESS_CR) == command)
+#define CMUX_PF_ISSET(frame) ((frame->control & CMUX_CONTROL_PF) == CMUX_CONTROL_PF)
+#define CMUX_FRAME_IS(type, frame) ((frame->control & ~CMUX_CONTROL_PF) == type)
+
+#define CMUX_RECV_READ_MAX 32
+#define CMUX_THREAD_STACK_SIZE 2048
+#define CMUX_THREAD_PRIORITY   8
+
+#define CMUX_RECIEVE_RESET     0
+#define CMUX_RECIEVE_BEGIN     1
+#define CMUX_RECIEVE_PROCESS   2
+#define CMUX_RECIEVE_END       3
+
+#define CMUX_EVENT_RX_NOTIFY     1   // serial incoming a byte
+#define CMUX_EVENT_CHANNEL_OPEN  2
+#define CMUX_EVENT_CHANNEL_CLOSE 4
+#define CMUX_EVENT_CHANNEL_OPEN_REQ  8
+#define CMUX_EVENT_CHANNEL_CLOSE_REQ 16
+#define CMUX_EVENT_FUNCTION_EXIT 32
+
+#define DBG_TAG    "cmux"
+
+#ifdef CMUX_DEBUG
+#define DBG_LVL   DBG_LOG
+#else
+#define DBG_LVL   DBG_INFO
+#endif
+#include <rtdbg.h>
+
+#ifdef CMUX_DEBUG
+/**
+ * dump cmux data according to hex format,you can see data what you receive
+ *
+ * @param data      the data what you want to dump out
+ * @param len       the length of those data
+ */
+static void hex_dump(const void *data, rt_size_t len)
+{
+    const size_t maxlen = 16;
+    rt_uint32_t offset = 0;
+    size_t curlen = 0, i = 0;
+    char line[16 * 4 + 3] = {0};
+    char *p = RT_NULL;
+    const unsigned char *src = data;
+
+    while (len > 0)
+    {
+        curlen = len < maxlen ? len : maxlen;
+        p = line;
+        for (i = 0; i < curlen; i++)
+        {
+            rt_sprintf(p, "%02x ", (unsigned char)src[i]);
+            p += 3;
+        }
+        memset(p, ' ', (maxlen - curlen) * 3);
+        p += (maxlen - curlen) * 3;
+        *p++ = '|';
+        *p++ = ' ';
+        for (i = 0; i < curlen; i++)
+        {
+            *p++ = (0x20 < src[i] && src[i] < 0x7e) ? src[i] : '.';
+        }
+        *p++ = '\0';
+        LOG_D("[%04x] %s", offset, line);
+        len -= curlen;
+        src += curlen;
+        offset += curlen;
+    }
+}
+#endif
+
+
+static rt_size_t cmux_send_data(struct rt_device *dev, int port, rt_uint8_t type, const char *data, int length);
+
+
+static rt_slist_t cmux_list = RT_SLIST_OBJECT_INIT(cmux_list);
+
+/* only one cmux object can be created */
+static struct cmux *_g_cmux = RT_NULL;
+
+/**
+ * Get the cmux object that your used device.
+ *
+ * @param name       the name of your select device.
+ *
+ * @return  struct cmux object point
+ */
+struct cmux *cmux_object_find(const char *name)
+{
+    struct cmux *cmux = RT_NULL;
+    struct rt_slist_node *node = RT_NULL;
+    rt_base_t level;
+
+    level = rt_hw_interrupt_disable();
+    rt_slist_for_each(node, &cmux_list)
+    {
+        cmux = rt_slist_entry(node, struct cmux, list);
+        if (rt_strncmp(cmux->dev->parent.name, name, RT_NAME_MAX) == 0)
+        {
+            rt_hw_interrupt_enable(level);
+            return cmux;
+        }
+    };
+
+    rt_hw_interrupt_enable(level);
+    return RT_NULL;
+}
+
+
+/**
+ * Receive callback function , send CMUX_EVENT_RX_NOTIFY event when uart acquire data
+ *
+ * @param dev       the point of device driver structure, uart structure
+ * @param size      the indication callback function need this parameter
+ *
+ * @return  RT_EOK
+ */
+static rt_err_t cmux_rx_ind(rt_device_t dev, rt_size_t size)
+{
+    RT_ASSERT(dev != RT_NULL);
+    struct cmux *cmux = RT_NULL;
+
+    cmux = _g_cmux;
+
+    /* when receive data from uart , send event to wake up receive thread */
+    rt_event_send(cmux->event, CMUX_EVENT_RX_NOTIFY);
+
+    return RT_EOK;
+}
+
+/**
+ *  invoke callback function
+ *
+ * @param cmux          cmux object
+ * @param port          the number of virtual channel
+ * @param size          the size of we receive from cmux
+ *
+ * @return  the point of rt_device_write fucntion or RT_NULL
+ */
+void cmux_vcom_isr(struct cmux *cmux, rt_uint8_t port, rt_size_t size)
+{
+    /* when receive data, we should notify user immediately. */
+    if (cmux->vcoms[port].device.rx_indicate != RT_NULL)
+    {
+        cmux->vcoms[port].device.rx_indicate(&cmux->vcoms[port].device, size);
+    }
+}
+
+/**
+ *  allocate buffer for cmux object receive
+ *
+ * @param RT_NULL
+ *
+ * @return  the point of struct cmux_buffer
+ */
+static struct cmux_buffer *cmux_buffer_init()
+{
+    struct cmux_buffer *buff = RT_NULL;
+    buff = rt_malloc(sizeof(struct cmux_buffer));
+    if (buff == RT_NULL)
+    {
+        return RT_NULL;
+    }
+    rt_memset(buff, 0, sizeof(cmux_buffer));
+    buff->read_point = buff->data;
+    buff->write_point = buff->data;
+    buff->end_point = buff->data + CMUX_BUFFER_SIZE;
+    return buff;
+}
+
+/**
+ *  destroy buffer for cmux object receive
+ *
+ * @param frame         the point of cmux_frame
+ *
+ * @return  RT_NULL
+ */
+static void cmux_frame_destroy(struct cmux_frame *frame)
+{
+  if (frame->data_length > 0)
+    rt_free(frame->data);
+  rt_free(frame);
+}
+
+/**
+ *  initial virtual serial for different channel, initial slist for channel
+ *
+ * @param cmux          cmux object
+ * @param channel       the number of virtual serial
+ *
+ * @return  RT_NULL
+ */
+static void vcoms_cmux_frame_init(struct cmux *cmux, int channel)
+{
+    rt_base_t level;
+
+    level = rt_hw_interrupt_disable();
+    rt_slist_init(&cmux->vcoms[channel].flist);
+    rt_hw_interrupt_enable(level);
+    cmux->vcoms[channel].frame_index = 0;
+
+    LOG_D("init cmux data channel(%d) list.", channel);
+}
+
+/**
+ *  push cmux frame data into slist for different channel virtual serial
+ *
+ * @param cmux          cmux object
+ * @param channel       the number of virtual serial
+ * @param frame         the point of frame data
+ *
+ * @return  RT_EOK      successful
+ *          RT_ENOMEM   allocate memory failed
+ */
+static rt_err_t cmux_frame_push(struct cmux *cmux, int channel, struct cmux_frame *frame)
+{
+    rt_base_t level;
+    struct frame *frame_new = RT_NULL;
+    rt_uint16_t frame_len = cmux->vcoms[channel].frame_index;
+
+    if(frame_len <= CMUX_MAX_FRAME_LIST_LEN)
+    {
+        frame_new = rt_malloc(sizeof(struct frame));
+        if(frame_new == RT_NULL)
+        {
+            LOG_E("can't malloc <struct frame> to record data address.");
+            return -RT_ENOMEM;
+        }
+        else
+        {
+            rt_memset(frame_new, 0, sizeof(struct frame));
+
+            frame_new->frame = frame;
+            rt_slist_init(&frame_new->frame_list);
+        }
+
+        level = rt_hw_interrupt_disable();
+        rt_slist_append(&cmux->vcoms[channel].flist, &frame_new->frame_list);
+        rt_hw_interrupt_enable(level);
+
+        LOG_D("new message for channel(%d) is append, Message total: %d.", channel, ++cmux->vcoms[channel].frame_index);
+
+#ifdef CMUX_DEBUG
+        hex_dump(frame->data, frame->data_length);
+#endif
+        return RT_EOK;
+    }
+    LOG_E("malloc failed, the message for channel(%d) is long than CMUX_MAX_FRAME_LIST_LEN(%d).", channel, CMUX_MAX_FRAME_LIST_LEN);
+    return -RT_ENOMEM;
+}
+
+/**
+ *  pop cmux frame data from slist for different channel virtual serial
+ *
+ * @param cmux          cmux object
+ * @param channel       the number of virtual serial
+ *
+ * @return  frame_data  successful
+ *          RT_NULL     no message on the slist
+ */
+static struct cmux_frame *cmux_frame_pop(struct cmux *cmux, int channel)
+{
+    rt_base_t level;
+    struct frame *frame = RT_NULL;
+    struct cmux_frame *frame_data = RT_NULL;
+    struct rt_slist_node *frame_list_find = RT_NULL;
+    rt_slist_t *frame_list = RT_NULL;
+
+    frame_list = &cmux->vcoms[channel].flist;
+
+    frame_list_find = rt_slist_first(frame_list);
+    if(frame_list_find != RT_NULL)
+    {
+        frame = rt_container_of(frame_list_find, struct frame, frame_list);
+        frame_data = frame->frame;
+
+        level = rt_hw_interrupt_disable();
+        rt_slist_remove(frame_list, frame_list_find);
+        rt_hw_interrupt_enable(level);
+
+        LOG_D("new message for channel(%d) has been used, Message remain: %d.", channel, --cmux->vcoms[channel].frame_index);
+        rt_free(frame);
+    }
+
+    return frame_data;
+}
+
+/**
+ *  write data into cmux buffer
+ *
+ * @param buff        the buffer of cmux object
+ * @param input       the point of data
+ * @param count       the length of data
+ *
+ * @return length     the length of write into cmux buffer
+ *
+ */
+rt_size_t cmux_buffer_write(struct cmux_buffer *buff, rt_uint8_t *input, rt_size_t count)
+{
+    int c = buff->end_point - buff->write_point;
+
+    count = min(count, cmux_buffer_free(buff));
+    if (count > c)
+    {
+        rt_memcpy(buff->write_point, input, c);
+        rt_memcpy(buff->data, input + c, count - c);
+        buff->write_point = buff->data + (count - c);
+    }
+    else
+    {
+        rt_memcpy(buff->write_point, input, count);
+        buff->write_point += count;
+        if (buff->write_point == buff->end_point)
+            buff->write_point = buff->data;
+    }
+
+    return count;
+}
+
+/**
+ *  parse buffer for searching cmux frame
+ *
+ * @param buffer        the cmux buffer for cmux object
+ *
+ * @return  frame       successful
+ *          RT_NULL     no frame in the buffer
+ */
+static struct cmux_frame *cmux_frame_parse(struct cmux_buffer *buffer)
+{
+    int end;
+    int length_needed = 5;      /* channel, type, length, fcs, flag */
+    rt_uint8_t *data = RT_NULL;
+    rt_uint8_t fcs = 0xFF;
+    struct cmux_frame *frame = RT_NULL;
+    
+    extern rt_uint8_t cmux_crctable[256];
+
+    /* Find start flag */
+    while (!buffer->flag_found && cmux_buffer_length(buffer) > 0)
+    {
+        if (*buffer->read_point == CMUX_HEAD_FLAG)
+            buffer->flag_found = 1;
+        INC_BUF_POINTER(buffer, buffer->read_point);
+    }
+    if (!buffer->flag_found) /* no frame started */
+        return RT_NULL;
+
+    /* skip empty frames (this causes troubles if we're using DLC 62) */
+    while (cmux_buffer_length(buffer) > 0 && (*buffer->read_point == CMUX_HEAD_FLAG))
+    {
+        INC_BUF_POINTER(buffer, buffer->read_point);
+    }
+
+    if (cmux_buffer_length(buffer) >= length_needed)
+    {
+        data = buffer->read_point;
+        frame =(struct cmux_frame *)rt_malloc(sizeof(struct cmux_frame));
+        frame->data = RT_NULL;
+
+        frame->channel = ((*data & 252) >> 2);
+        fcs = cmux_crctable[fcs ^ *data];
+        INC_BUF_POINTER(buffer, data);
+
+        frame->control = *data;
+        fcs = cmux_crctable[fcs ^ *data];
+        INC_BUF_POINTER(buffer, data);
+
+        frame->data_length = (*data & 254) >> 1;
+        fcs = cmux_crctable[fcs ^ *data];
+        if ((*data & 1) == 0)
+        {
+            /* Current specify (version 7.1.0) states these kind of frames to be invalid
+            * Long lost of sync might be caused if we would expect a long
+            * frame because of an error in length field.
+                INC_BUF_POINTER(buf,data);
+                frame->data_length += (*data*128);
+                fcs = cmux_crctable[fcs^*data];
+                length_needed++;
+            */
+            cmux_frame_destroy(frame);
+            buffer->read_point = data;
+            buffer->flag_found = 0;
+            return cmux_frame_parse(buffer);
+        }
+        length_needed += frame->data_length;
+        if (!(cmux_buffer_length(buffer) >= length_needed))
+        {
+            cmux_frame_destroy(frame);
+            return RT_NULL;
+        }
+        INC_BUF_POINTER(buffer, data);
+        /* extract data */
+        if (frame->data_length > 0)
+        {
+            frame->data =(unsigned char *)rt_malloc(frame->data_length);
+            if ( frame->data != RT_NULL)
+            {
+                end = buffer->end_point - data;
+                if (frame->data_length > end)
+                {
+                    rt_memcpy(frame->data, data, end);
+                    rt_memcpy(frame->data + end, buffer->data, frame->data_length - end);
+                    data = buffer->data + (frame->data_length - end);
+                }
+                else
+                {
+                    rt_memcpy(frame->data, data, frame->data_length);
+                    data += frame->data_length;
+                    if (data == buffer->end_point)
+                        data = buffer->data;
+                }
+                if (CMUX_FRAME_IS(CMUX_FRAME_UI, frame))
+                {
+                    for (end = 0; end < frame->data_length; end++)
+                        fcs = cmux_crctable[fcs ^ (frame->data[end])];
+                }
+            }
+            else
+            {
+                LOG_E("Out of memory, when allocating space for frame data.");
+                frame->data_length = 0;
+            }
+        }
+        /* check FCS */
+        if (cmux_crctable[fcs ^ (*data)] != 0xCF)
+        {
+            LOG_W("Dropping frame: FCS doesn't match.");
+            cmux_frame_destroy(frame);
+            buffer->flag_found = 0;
+            buffer->read_point = data;
+            return cmux_frame_parse(buffer);
+        }
+        else
+        {
+            /* check end flag */
+            INC_BUF_POINTER(buffer, data);
+            if (*data != CMUX_HEAD_FLAG)
+            {
+                LOG_W("Dropping frame: End flag not found. Instead: %d.", *data);
+                cmux_frame_destroy(frame);
+                buffer->flag_found = 0;
+                buffer->read_point = data;
+                return cmux_frame_parse(buffer);
+            }
+            else
+            {
+
+            }
+            INC_BUF_POINTER(buffer, data);
+        }
+        buffer->read_point = data;
+    }
+    return frame;
+}
+
+/**
+ * save data from serial, push frame into slist and invoke callback function
+ *
+ * @param   device  the point of device driver structure, ppp_device structure
+ * @param   buf     the address of receive data from uart
+ * @param   len     the length of receive data
+ */
+static void cmux_recv_processdata(struct cmux *cmux, rt_uint8_t *buf, rt_size_t len)
+{
+    rt_size_t count = len;
+    struct cmux_frame *frame = RT_NULL;
+
+    cmux_buffer_write(cmux->buffer, buf, count);
+
+    frame = cmux_frame_parse(cmux->buffer);
+
+    if(frame != RT_NULL)
+    {
+        /* distribute different data */
+        if ((CMUX_FRAME_IS(CMUX_FRAME_UI, frame) || CMUX_FRAME_IS(CMUX_FRAME_UIH, frame)))
+        {
+            LOG_D("this is UI or UIH frame from channel(%d).", frame->channel);
+            if (frame->channel > 0)
+            {
+                /* receive data from logical channel, distribution them */
+                cmux_frame_push(cmux, frame->channel, frame);
+                cmux_vcom_isr(cmux, frame->channel, frame->data_length);
+            }
+            else
+            {
+                /* control channel command */
+                LOG_E("control channel command haven't support.");
+            }
+        }
+        else
+        {
+            switch ((frame->control & ~CMUX_CONTROL_PF))
+            {
+            case CMUX_FRAME_UA:
+                LOG_D("This is UA frame for channel(%d).", frame->channel);
+                rt_event_send(cmux->event, CMUX_EVENT_CHANNEL_OPEN);
+
+                break;
+            case CMUX_FRAME_DM:
+                LOG_D("This is DM frame for channel(%d).", frame->channel);
+
+                break;
+            case CMUX_FRAME_SABM:
+                LOG_D("This is SABM frame for channel(%d).", frame->channel);
+
+                break;
+            case CMUX_FRAME_DISC:
+                LOG_D("This is DISC frame for channel(%d).", frame->channel);
+
+                break;
+            }
+        }
+    }
+}
+
+/**
+ *  assemble general data in the format of cmux
+ *
+ * @param dev           actual serial device
+ * @param port          the number of virtual serial
+ * @param type          the format of cmux frame
+ * @param data          general data
+ * @param length        the length of general data
+ *
+ * @return  length
+ */
+static rt_size_t cmux_send_data(struct rt_device *dev, int port, rt_uint8_t type, const char *data, int length)
+{
+    /* flag, EA=1 C port, frame type, data_length 1-2 */
+    rt_uint8_t prefix[5] = {CMUX_HEAD_FLAG, CMUX_ADDRESS_EA | CMUX_ADDRESS_CR, 0, 0, 0};
+    rt_uint8_t postfix[2] = {0xFF, CMUX_HEAD_FLAG};
+    int c, prefix_length = 4;
+
+    /* EA=1, Command, let's add address */
+    prefix[1] = prefix[1] | ((63 & port) << 2);
+    /* cmux control field */
+    prefix[2] = type;
+
+    if (length > 127)
+    {
+        prefix_length = 5;
+        prefix[3] = ((127 & length) << 1);
+        prefix[4] = (32640 & length) >> 7;
+    }
+    else
+    {
+        prefix[3] = 1 | (length << 1);
+    }
+    /* CRC checksum */
+    postfix[0] = cmux_frame_check(prefix + 1, prefix_length - 1);
+
+    c = rt_device_write(dev, 0, prefix, prefix_length);
+    if (c != prefix_length)
+    {
+        LOG_D("Couldn't write the whole prefix to the serial port for the virtual port %d. Wrote only %d  bytes.", port, c);
+        return 0;
+    }
+    if (length > 0)
+    {
+        c = rt_device_write(dev, 0, data, length);
+        if (length != c)
+        {
+            LOG_D("Couldn't write all data to the serial port from the virtual port %d. Wrote only %d bytes.", port, c);
+            return 0;
+        }
+    }
+    c = rt_device_write(dev, 0, postfix, 2);
+    if (c != 2)
+    {
+        LOG_D("Couldn't write the whole postfix to the serial port for the virtual port %d. Wrote only %d bytes.", port, c);
+        return 0;
+    }
+
+    return length;
+}
+
+/**
+ * Receive thread , store serial data
+ *
+ * @param cmux    the point of cmux object structure
+ *
+ * @return  RT_EOK  we shouldn't let the receive thread return data, receive thread need keep alive all the time
+ */
+static int cmux_recv_thread(struct cmux *cmux)
+{
+    rt_uint32_t event;
+    rt_size_t len;
+    rt_uint8_t buffer[CMUX_RECV_READ_MAX];
+
+    rt_event_control(cmux->event, RT_IPC_CMD_RESET, RT_NULL);
+
+    while (1)
+    {
+        rt_event_recv(cmux->event, CMUX_EVENT_RX_NOTIFY, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &event);
+        if (event & CMUX_EVENT_RX_NOTIFY)
+        {
+            do
+            {
+                len = rt_device_read(cmux->dev, 0, buffer, CMUX_RECV_READ_MAX);
+                if (len)
+                {
+                    cmux_recv_processdata(cmux, buffer, len);
+                }
+
+            } while (len);
+        }
+    }
+
+    return RT_EOK;
+}
+
+/**
+ * cmux object init function
+ *
+ * @param object        the point of cmux object
+ * @param name          the actual serial name
+ * @param vcom_num      the channel of virtual channel
+ * @param user_data     private data
+ *
+ * @return  RT_EOK      successful
+ *          RT_ENOMEM   allocate memory failed
+ */
+rt_err_t cmux_init(struct cmux *object, const char *name, rt_uint8_t vcom_num, void *user_data)
+{
+    static rt_uint8_t count = 1;
+    char tid_name[RT_NAME_MAX] = {0};
+    char event_name[RT_NAME_MAX] = {0};
+    rt_base_t level;
+
+    if(_g_cmux == RT_NULL)
+    {
+        _g_cmux = object;
+    }
+    else
+    {
+        RT_ASSERT(!_g_cmux);
+    }
+
+    object->dev = rt_device_find(name);
+    if(object->dev == RT_NULL)
+    {
+        LOG_E("cmux can't find %s.", name);
+        return -RT_EOK;
+    }
+
+    object->vcom_num = vcom_num;
+    object->vcoms = rt_malloc(vcom_num *sizeof(struct cmux_vcoms));
+    rt_memset(object->vcoms, 0, vcom_num *sizeof(struct cmux_vcoms));
+    if(object->vcoms == RT_NULL)
+    {
+        LOG_E("cmux vcoms malloc failed.");
+        return -RT_ENOMEM;
+    }
+
+    object->buffer = cmux_buffer_init();
+    if(object->buffer == RT_NULL)
+    {
+        LOG_E("cmux buffer malloc failed.");
+        return -RT_ENOMEM;
+    }
+
+    rt_sprintf(event_name, "cmuxeve%d", count);
+    object->event = rt_event_create(event_name, RT_IPC_FLAG_FIFO);
+    if(object->event == RT_NULL)
+    {
+        LOG_E("cmux event malloc failed.");
+        return -RT_ENOMEM;
+    }
+
+    object->user_data = user_data;
+
+    level = rt_hw_interrupt_disable();
+
+    rt_slist_init(&object->list);
+    rt_slist_append(&cmux_list, &object->list);
+
+    rt_hw_interrupt_enable(level);
+
+    rt_sprintf(tid_name, "cmuxrec%d", count);
+    object->recv_tid = rt_thread_create(tid_name,
+                                     (void (*)(void *parameter))cmux_recv_thread,
+                                     object,
+                                     CMUX_THREAD_STACK_SIZE,
+                                     CMUX_THREAD_PRIORITY,
+                                     20);
+    if (object->recv_tid == RT_NULL)
+    {
+        LOG_E("cmux receive thread create failed.");
+    }
+
+    LOG_I("cmux rely on (%s) init successful.", name);
+    return RT_EOK;
+}
+
+/**
+ * start cmux function
+ *
+ * @param object    the point of cmux object
+ *
+ * @return  the result
+ */
+rt_err_t cmux_start(struct cmux *object)
+{
+    rt_err_t result = 0;
+    struct rt_device *device = RT_NULL;
+
+    /* uart transfer into cmux */
+    rt_device_set_rx_indicate(object->dev, cmux_rx_ind);
+
+    if(object->ops->start != RT_NULL)
+    {
+        result = object->ops->start(object);
+        if(result != RT_EOK)
+            return result;
+    }
+
+    if(object->recv_tid != RT_NULL)
+    {
+        result = rt_thread_startup(object->recv_tid);
+        if(result != RT_EOK)
+        {
+            LOG_D("cmux receive thread startup failed.");
+            return result;
+        }
+    }
+
+    /* attach cmux control channel into rt-thread device */
+    cmux_attach(object, 0, "cmux_ctl", RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_DMA_RX, RT_NULL);
+
+    device = rt_device_find("cmux_ctl");
+    result = rt_device_open(device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_DMA_RX);
+    if(result != RT_EOK)
+    {
+        LOG_E("cmux control channel open failed.");
+    }
+
+    return result;
+}
+
+/**
+ * stop cmux function
+ *
+ * @param object    the point of cmux object
+ *
+ * @return  the result
+ */
+rt_err_t cmux_stop(struct cmux *object)
+{
+    if(object->ops->stop != RT_NULL)
+    {
+        object->ops->stop(object);
+    }
+
+    /* we should send CMUX_FRAME_DM frame, close cmux control connect channel */
+    cmux_send_data(object->dev, 0, CMUX_FRAME_DISC | CMUX_CONTROL_PF, RT_NULL, 0);
+
+    return RT_EOK;
+}
+
+/**
+ * control cmux function
+ *
+ * @param object        the point of cmux object
+ *
+ * @return  RT_ENOSYS   haven't support control function
+ */
+rt_err_t cmux_control(struct cmux *object, int cmd, void *args)
+{
+    RT_ASSERT(object != RT_NULL);
+
+    return -RT_ENOSYS;
+}
+
+/**
+ * initialize virtual channel, and open it
+ *
+ * @param dev       the point of virtual device
+ * @param oflag     the open flag of rt_device
+ *
+ * @return  the result
+ */
+static rt_err_t cmux_vcom_open(rt_device_t dev, rt_uint16_t oflag)
+{
+    rt_err_t result = RT_EOK;
+    struct cmux *object = RT_NULL;
+
+    RT_ASSERT(dev != RT_NULL);
+
+    object = _g_cmux;
+
+    /* establish virtual connect channel */
+    cmux_send_data(object->dev, (int)dev->user_data, CMUX_FRAME_SABM | CMUX_CONTROL_PF, RT_NULL, 0);
+
+    return result;
+}
+
+/**
+ * close virtual channel
+ *
+ * @param dev       the point of virtual device
+ *
+ * @return  the result
+ */
+static rt_err_t cmux_vcom_close(rt_device_t dev)
+{
+    rt_err_t result = RT_EOK;
+    struct cmux *object = RT_NULL;
+
+    object = _g_cmux;
+
+    cmux_send_data(object->dev, (int)dev->user_data, CMUX_FRAME_DISC | CMUX_CONTROL_PF, RT_NULL, 0);
+
+    return result;
+}
+
+/**
+ * write data into virtual channel
+ *
+ * @param dev       the point of virtual device
+ * @param pos       offset
+ * @param buffer    the data you want to send
+ * @param size      the length of buffer
+ *
+ * @return  the result
+ */
+static rt_size_t cmux_vcom_write(struct rt_device *dev,
+                                 rt_off_t pos,
+                                 const void *buffer,
+                                 rt_size_t size)
+{
+    struct cmux *cmux = RT_NULL;
+    rt_size_t len;
+    cmux = _g_cmux;
+
+    /* use virtual serial, we can write data into actual serial directly. */
+    len = cmux_send_data(cmux->dev, (int)dev->user_data, CMUX_FRAME_UIH, buffer, size);
+    return len;
+}
+
+/**
+ * write data into virtual channel
+ *
+ * @param dev       the point of virtual device
+ * @param pos       offset
+ * @param buffer    the buffer you want to store
+ * @param size      the length of buffer
+ *
+ * @return  the result
+ */
+static rt_size_t cmux_vcom_read(struct rt_device *dev,
+                                rt_off_t pos,
+                                void *buffer,
+                                rt_size_t size)
+{
+    static struct cmux_frame *frame = RT_NULL;
+    static rt_size_t length = 0;
+    static rt_uint8_t *data = RT_NULL;
+
+    struct cmux *cmux = RT_NULL;
+    rt_bool_t using_status = 0;
+
+    cmux = _g_cmux;
+    using_status = cmux->vcoms[(int)(dev->user_data)].frame_using_status;
+
+    /* The previous frame has been transmitted finish. */
+    if(!using_status)
+    {
+        /* support fifo, we using the first frame */
+        frame = cmux_frame_pop(cmux, (int)dev->user_data);
+        length = 0;
+        data = RT_NULL;
+
+        /* can't find frame */
+        if(frame == RT_NULL)
+        {
+            return 0;
+        }
+
+        if(size >= frame->data_length)
+        {
+            rt_memcpy(buffer, frame->data, frame->data_length);
+            cmux_frame_destroy(frame);
+
+            return frame->data_length;
+        }
+        else
+        {
+            data = frame->data;
+            cmux->vcoms[(int)(dev->user_data)].frame_using_status = 1;
+            rt_memcpy(buffer, data, size);
+            data = data + size;
+            length = length + size;
+
+            return size;
+        }
+
+    }
+    else
+    {
+        /* transmit the rest of frame */
+        if(length + size >= frame->data_length)
+        {
+            rt_memcpy(buffer, data, frame->data_length - length);
+            cmux_frame_destroy(frame);
+
+            cmux->vcoms[(int)(dev->user_data)].frame_using_status = 0;
+
+            return frame->data_length - length;
+        }
+        else
+        {
+            rt_memcpy(buffer, data, size);
+            data = data + size;
+            length = length + size;
+
+            return size;
+        }
+    }
+}
+
+/**
+ * attach cmux into cmux object
+ *
+ * @param object        the point of cmux object
+ * @param link_port     the channel of virtual serial
+ * @param alias_name    the name of virtual name
+ * @param flag          the type of virtual serial
+ * @param user_data     private data
+ *
+ * @return  RT_EOK          execute successful
+ *
+ */
+rt_err_t cmux_attach(struct cmux *object, int link_port, const char *alias_name, rt_uint16_t flags, void *user_data)
+{
+    struct rt_device *device = &object->vcoms[link_port].device;
+
+    device->type        = RT_Device_Class_Char;
+    device->rx_indicate = RT_NULL;
+    device->tx_complete = RT_NULL;
+
+    device->init    = RT_NULL;
+    device->open    = cmux_vcom_open;
+    device->close   = cmux_vcom_close;
+    device->read    = cmux_vcom_read;
+    device->write   = cmux_vcom_write;
+    device->control = RT_NULL;
+
+    device->user_data = (void *)link_port;
+
+    vcoms_cmux_frame_init(object, link_port);
+
+    /* interrupt mode or dma mode is meaningless, because we don't have buffer for vcom */
+    if(flags & RT_DEVICE_FLAG_INT_RX)
+        rt_device_register(device, alias_name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_INT_RX);
+    else
+        rt_device_register(device, alias_name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_DMA_RX);
+
+    return RT_EOK;
+}
+
+/**
+ * detach cmux from object
+ *
+ * @param object            the point of cmux object
+ * @param alias_name        the name of virtual name
+ *
+ * @return  RT_EOK          execute successful
+ *         -RT_ERROR        error
+ */
+rt_err_t cmux_detach(struct cmux *object, const char *alias_name)
+{
+    rt_device_t device = RT_NULL;
+
+    device = rt_device_find(alias_name);
+    if(device->open_flag & RT_DEVICE_OFLAG_OPEN)
+    {
+        LOG_E("You should close vcom (%s) firstly.", device->parent.name);
+        return -RT_ERROR;
+    }
+    cmux_vcom_close(device);
+
+    return RT_EOK;
+}

+ 258 - 0
src/cmux_chat.c

@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2019 xiaofan <xfan1024@live.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author          Notes
+ * 2019-09-19     xiaofan         the first version
+ * 2020-04-13     xiangxistu      transplant into cmux
+ */
+
+#include "cmux_chat.h"
+#define DBG_TAG    "cmux.chat"
+
+#ifdef CMUX_DEBUG
+#define DBG_LVL   DBG_LOG
+#else
+#define DBG_LVL   DBG_INFO
+#endif
+
+#include <rtdbg.h>
+
+#define CHAT_READ_BUF_MAX 16
+
+/**
+* In order to match response, we need a string search algorithm
+* KMP and AC algorithm both are good choice, But we need the code
+* is simple, readable and use lower RAM/ROM.
+* So We use a simplified search algorithm, this alg is like the KMP.
+* Specifically we assume the failure vecotor is [-1, 0, 0, 0, ...]
+* This assuming is not work for all pattern string. Fortunately,
+* it's work  for this scene.
+*/
+
+#define DEFINE_MODEM_RESP_STRDATA_TABLE(id, str) [id] = str
+#define DEFINE_MODEM_RESP_STRLEN_TABLE(id, str)  [id] = (sizeof(str)-1)
+
+
+static char *resp_strdata[] =
+{
+    MODEM_CHAT_RESP_LIST(DEFINE_MODEM_RESP_STRDATA_TABLE)
+};
+
+static rt_uint8_t resp_strlen[] =
+{
+    MODEM_CHAT_RESP_LIST(DEFINE_MODEM_RESP_STRLEN_TABLE)
+};
+
+#define CHAT_DATA_FMT           "<tx: %s, want: %s, retries: %u, timeout: %u>"
+#define CHAT_DATA_STR(data)     (data)->transmit, resp2str((data)->expect), (data)->retries, (data)->timeout
+
+static const char* resp2str(rt_uint8_t resp_id)
+{
+    if (resp_id == MODEM_CHAT_RESP_NOT_NEED)
+        return "(not need)";
+    RT_ASSERT(resp_id < MODEM_CHAT_RESP_MAX);
+    return resp_strdata[resp_id];
+}
+
+/* only one device support */
+static struct rt_completion rx_comp_p;
+
+static rt_uint8_t resp_match(rt_uint8_t resp_id, rt_uint8_t state, char ch)
+{
+    while (1)
+    {
+        if (resp2str(resp_id)[state] == ch)
+            return state + 1;
+        if (state == 0)
+            return 0;
+        state = 0;
+    };
+}
+
+static rt_bool_t resp_matched(rt_uint8_t resp_id, rt_uint8_t state)
+{
+    return state == resp_strlen[resp_id];
+}
+
+/**
+ * chat_rx_ind, callback function if serial recieve data
+ *
+ * @param device the point of device driver structure, uart structure
+ * @param size  the indication callback function need this parameter
+ *
+ * @return RT_EOK
+ */
+static rt_err_t chat_rx_ind(rt_device_t device, rt_size_t size)
+{
+    rt_completion_done(&rx_comp_p);
+    return RT_EOK;
+}
+
+/**
+ * chat_read_until, waitting for recieve data from serial
+ *
+ * @param serial    the point of device driver structure, uart structure
+ * @param buffer    the buffer is waitting for recieve uart data from ppp
+ * @param size      the max length of CHAT_READ_BUF_MAX
+ * @param stop      the max of tick time
+ *
+ * @return  >=0:   the length of read data
+ *          <0 :   rt_device_read failed
+ */
+static rt_size_t chat_read_until(rt_device_t serial, void *buffer, rt_size_t size, rt_tick_t stop)
+{
+    rt_size_t rdlen;
+    rt_tick_t wait;
+
+    rt_completion_init(&rx_comp_p);
+    rdlen = rt_device_read(serial, 0, buffer, size);
+    if (rdlen)
+        return rdlen;
+
+    wait = stop - rt_tick_get();
+    if (wait > RT_TICK_MAX / 2)
+        return 0;
+
+    rt_completion_wait(&rx_comp_p, wait);
+    return rt_device_read(serial, 0, buffer, size);
+}
+
+/**
+ * modem_flush_rx , clear data what is in the rx buffer
+ *
+ * @param serial    the point of device driver structure, uart structure
+ */
+static void modem_flush_rx(rt_device_t serial)
+{
+    char rdbuf[CHAT_READ_BUF_MAX] = {0};
+
+    while (rt_device_read(serial, 0, rdbuf, CHAT_READ_BUF_MAX));
+}
+
+/**
+ * modem_chat_once , send an order to control modem
+ *
+ * @param serial    the point of device driver structure, uart structure
+ * @param data      the AT command
+ *
+ * @return  =0:   modem_chat_once successful
+ *          <0:   modem_chat_once failed
+ */
+static rt_err_t modem_chat_once(rt_device_t serial, const struct modem_chat_data *data)
+{
+    rt_uint8_t resp_state[MODEM_CHAT_RESP_MAX] = { 0 }, resp;
+    rt_tick_t stop = rt_tick_get() + data->timeout*RT_TICK_PER_SECOND;
+    rt_size_t rdlen, pos;
+    char rdbuf[CHAT_READ_BUF_MAX];
+
+    if (data->transmit)
+    {
+        LOG_D(CHAT_DATA_FMT " transmit --> modem", CHAT_DATA_STR(data));
+        rt_device_write(serial, 0, data->transmit, rt_strlen(data->transmit));
+        if (data->ignore_cr == RT_FALSE)
+            rt_device_write(serial, 0, "\r", 1);
+    }
+
+    if (data->expect == MODEM_CHAT_RESP_NOT_NEED)
+    {
+        rt_thread_mdelay(1000*data->timeout);
+        return RT_EOK;
+    }
+
+    do
+    {
+        rdlen = chat_read_until(serial, rdbuf, CHAT_READ_BUF_MAX, stop);
+        for (pos = 0; pos < rdlen; pos++)
+        {
+            for (resp = 0; resp < MODEM_CHAT_RESP_MAX; resp++)
+            {
+                resp_state[resp] = resp_match(resp, resp_state[resp], rdbuf[pos]);
+                if (resp_matched(resp, resp_state[resp]))
+                {
+                    if (resp == data->expect)
+                        return RT_EOK;
+
+                    LOG_W(CHAT_DATA_FMT" not matched, got: %s", CHAT_DATA_STR(data), resp2str(resp));
+#ifndef PKG_USING_CMUX
+                    return -RT_ERROR;
+#else
+                    continue;
+#endif
+                }
+            }
+        }
+    } while ( stop - rt_tick_get() < RT_TICK_MAX / 2);
+    LOG_W(CHAT_DATA_FMT" timeout", CHAT_DATA_STR(data));
+    return -RT_ETIMEOUT;
+}
+
+/**
+ * modem_chat_cmux , init modem or turn modem into cmux type
+ *
+ * @param serial    the point of device driver structure, uart structure
+ * @param data      the AT command, it is the address of chat strcuture, a collection of AT command
+ * @param len       the length of this collection of AT command
+ *
+ * @return  =0:   modem_chat_cmux successful
+ *          <0:   modem_chat_cmux failed
+ */
+static rt_err_t modem_chat_cmux(rt_device_t serial, const struct modem_chat_data *data, rt_size_t len)
+{
+    rt_err_t err = RT_EOK;
+    rt_size_t i;
+    rt_uint8_t retry_time;
+
+    for (i = 0; i < len; i++)
+    {
+        LOG_D(CHAT_DATA_FMT" running", CHAT_DATA_STR(&data[i]));
+        for (retry_time = 0; retry_time < data[i].retries; retry_time++)
+        {
+            modem_flush_rx(serial);
+            err = modem_chat_once(serial, &data[i]);
+            if (err == RT_EOK)
+                break;
+        }
+        if (err != RT_EOK)
+        {
+            LOG_E(CHAT_DATA_FMT" fail", CHAT_DATA_STR(&data[i]));
+            break;
+        }
+        LOG_D(CHAT_DATA_FMT" success", CHAT_DATA_STR(&data[i]));
+    }
+    return err;
+}
+
+/**
+ * modem_chat , a function for cmux, it will set rx_indicate
+ *
+ * @param device    the point of device driver structure, uart structure
+ * @param data      the AT command, it is the address of chat strcuture, a collection of AT command
+ * @param len       the length of this collection of AT command
+ *
+ * @return  =0:   modem_chat successful
+ *          <0:   modem_chat failed
+ */
+rt_err_t modem_chat(rt_device_t serial, const struct modem_chat_data *data, rt_size_t len)
+{
+    rt_err_t (*old_rx_ind)(rt_device_t dev, rt_size_t size) = RT_NULL;
+
+    rt_err_t err = RT_EOK;
+
+    rt_completion_init(&rx_comp_p);
+    old_rx_ind = serial->rx_indicate;
+    rt_device_set_rx_indicate(serial, chat_rx_ind);
+
+    LOG_D("(%s) has control by modem_chat.", serial->parent.name);
+    err = modem_chat_cmux(serial, data, len);
+    if (err != RT_EOK)
+    {
+        LOG_E("chat failed");
+    }
+
+    serial->rx_indicate = old_rx_ind;
+    LOG_D("(%s) has control by cmux.", serial->parent.name);
+    return err;
+}

+ 57 - 0
src/cmux_utils.c

@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2006-2020, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2020-04-15    xiangxistu      the first version
+ */
+
+#include<cmux.h>
+
+/* reversed, 8-bit, poly=0x07 */
+const rt_uint8_t cmux_crctable[256] = {
+    0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75,
+    0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B,
+    0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69,
+    0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67,
+    0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D,
+    0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43,
+    0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51,
+    0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F,
+    0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05,
+    0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B,
+    0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19,
+    0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17,
+    0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D,
+    0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33,
+    0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21,
+    0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F,
+    0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95,
+    0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B,
+    0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89,
+    0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87,
+    0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD,
+    0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3,
+    0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1,
+    0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF,
+    0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5,
+    0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB,
+    0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9,
+    0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7,
+    0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD,
+    0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3,
+    0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1,
+    0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF};
+
+rt_uint8_t cmux_frame_check(const rt_uint8_t *input, int length)
+{
+    rt_uint8_t fcs = 0xFF;
+    int i;
+    for (i = 0; i < length; i++)
+    {
+        fcs = cmux_crctable[fcs ^ input[i]];
+    }
+    return (0xFF - fcs);
+}