1 /* 2 * Copyright (c) 2022 Trackunit Corporation 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 /* 8 * This library uses CMUX to create multiple data channels, called DLCIs, on a single serial bus. 9 * Each DLCI has an address from 1 to 63. DLCI address 0 is reserved for control commands. 10 * 11 * Design overview: 12 * 13 * DLCI1 <-----------+ +-------> DLCI1 14 * v v 15 * DLCI2 <---> CMUX instance <--> Serial bus <--> Client <--> DLCI2 16 * ^ ^ 17 * DLCI3 <-----------+ +-------> DLCI3 18 * 19 * Writing to and from the CMUX instances is done using the modem_pipe API. 20 */ 21 22 #include <zephyr/kernel.h> 23 #include <zephyr/types.h> 24 #include <zephyr/sys/ring_buffer.h> 25 #include <zephyr/sys/atomic.h> 26 27 #include <zephyr/modem/pipe.h> 28 #include <zephyr/modem/stats.h> 29 30 #ifndef ZEPHYR_MODEM_CMUX_ 31 #define ZEPHYR_MODEM_CMUX_ 32 33 #ifdef __cplusplus 34 extern "C" { 35 #endif 36 37 /** 38 * @brief Modem CMUX 39 * @defgroup modem_cmux Modem CMUX 40 * @ingroup modem 41 * @{ 42 */ 43 44 struct modem_cmux; 45 46 enum modem_cmux_event { 47 MODEM_CMUX_EVENT_CONNECTED = 0, 48 MODEM_CMUX_EVENT_DISCONNECTED, 49 }; 50 51 typedef void (*modem_cmux_callback)(struct modem_cmux *cmux, enum modem_cmux_event event, 52 void *user_data); 53 54 /** 55 * @cond INTERNAL_HIDDEN 56 */ 57 58 enum modem_cmux_state { 59 MODEM_CMUX_STATE_DISCONNECTED = 0, 60 MODEM_CMUX_STATE_CONNECTING, 61 MODEM_CMUX_STATE_CONNECTED, 62 MODEM_CMUX_STATE_DISCONNECTING, 63 }; 64 65 enum modem_cmux_receive_state { 66 MODEM_CMUX_RECEIVE_STATE_SOF = 0, 67 MODEM_CMUX_RECEIVE_STATE_RESYNC, 68 MODEM_CMUX_RECEIVE_STATE_ADDRESS, 69 MODEM_CMUX_RECEIVE_STATE_ADDRESS_CONT, 70 MODEM_CMUX_RECEIVE_STATE_CONTROL, 71 MODEM_CMUX_RECEIVE_STATE_LENGTH, 72 MODEM_CMUX_RECEIVE_STATE_LENGTH_CONT, 73 MODEM_CMUX_RECEIVE_STATE_DATA, 74 MODEM_CMUX_RECEIVE_STATE_FCS, 75 MODEM_CMUX_RECEIVE_STATE_DROP, 76 MODEM_CMUX_RECEIVE_STATE_EOF, 77 }; 78 79 enum modem_cmux_dlci_state { 80 MODEM_CMUX_DLCI_STATE_CLOSED, 81 MODEM_CMUX_DLCI_STATE_OPENING, 82 MODEM_CMUX_DLCI_STATE_OPEN, 83 MODEM_CMUX_DLCI_STATE_CLOSING, 84 }; 85 86 struct modem_cmux_dlci { 87 sys_snode_t node; 88 89 /* Pipe */ 90 struct modem_pipe pipe; 91 92 /* Context */ 93 uint16_t dlci_address; 94 struct modem_cmux *cmux; 95 96 /* Receive buffer */ 97 struct ring_buf receive_rb; 98 struct k_mutex receive_rb_lock; 99 100 /* Work */ 101 struct k_work_delayable open_work; 102 struct k_work_delayable close_work; 103 104 /* State */ 105 enum modem_cmux_dlci_state state; 106 107 /* Statistics */ 108 #if CONFIG_MODEM_STATS 109 struct modem_stats_buffer receive_buf_stats; 110 #endif 111 }; 112 113 struct modem_cmux_frame { 114 uint8_t dlci_address; 115 bool cr; 116 bool pf; 117 uint8_t type; 118 const uint8_t *data; 119 uint16_t data_len; 120 }; 121 122 struct modem_cmux_work { 123 struct k_work_delayable dwork; 124 struct modem_cmux *cmux; 125 }; 126 127 struct modem_cmux { 128 /* Bus pipe */ 129 struct modem_pipe *pipe; 130 131 /* Event handler */ 132 modem_cmux_callback callback; 133 void *user_data; 134 135 /* DLCI channel contexts */ 136 sys_slist_t dlcis; 137 138 /* State */ 139 enum modem_cmux_state state; 140 bool flow_control_on; 141 142 /* Work lock */ 143 bool attached; 144 struct k_spinlock work_lock; 145 146 /* Receive state*/ 147 enum modem_cmux_receive_state receive_state; 148 149 /* Receive buffer */ 150 uint8_t *receive_buf; 151 uint16_t receive_buf_size; 152 uint16_t receive_buf_len; 153 154 uint8_t work_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE]; 155 156 /* Transmit buffer */ 157 struct ring_buf transmit_rb; 158 struct k_mutex transmit_rb_lock; 159 160 /* Received frame */ 161 struct modem_cmux_frame frame; 162 uint8_t frame_header[5]; 163 uint16_t frame_header_len; 164 165 /* Work */ 166 struct k_work_delayable receive_work; 167 struct k_work_delayable transmit_work; 168 struct k_work_delayable connect_work; 169 struct k_work_delayable disconnect_work; 170 171 /* Synchronize actions */ 172 struct k_event event; 173 174 /* Statistics */ 175 #if CONFIG_MODEM_STATS 176 struct modem_stats_buffer receive_buf_stats; 177 struct modem_stats_buffer transmit_buf_stats; 178 #endif 179 }; 180 181 /** 182 * @endcond 183 */ 184 185 /** 186 * @brief Contains CMUX instance configuration data 187 */ 188 struct modem_cmux_config { 189 /** Invoked when event occurs */ 190 modem_cmux_callback callback; 191 /** Free to use pointer passed to event handler when invoked */ 192 void *user_data; 193 /** Receive buffer */ 194 uint8_t *receive_buf; 195 /** Size of receive buffer in bytes [127, ...] */ 196 uint16_t receive_buf_size; 197 /** Transmit buffer */ 198 uint8_t *transmit_buf; 199 /** Size of transmit buffer in bytes [149, ...] */ 200 uint16_t transmit_buf_size; 201 }; 202 203 /** 204 * @brief Initialize CMUX instance 205 * @param cmux CMUX instance 206 * @param config Configuration to apply to CMUX instance 207 */ 208 void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *config); 209 210 /** 211 * @brief CMUX DLCI configuration 212 */ 213 struct modem_cmux_dlci_config { 214 /** DLCI channel address */ 215 uint8_t dlci_address; 216 /** Receive buffer used by pipe */ 217 uint8_t *receive_buf; 218 /** Size of receive buffer used by pipe [127, ...] */ 219 uint16_t receive_buf_size; 220 }; 221 222 /** 223 * @brief Initialize DLCI instance and register it with CMUX instance 224 * 225 * @param cmux CMUX instance which the DLCI will be registered to 226 * @param dlci DLCI instance which will be registered and configured 227 * @param config Configuration to apply to DLCI instance 228 */ 229 struct modem_pipe *modem_cmux_dlci_init(struct modem_cmux *cmux, struct modem_cmux_dlci *dlci, 230 const struct modem_cmux_dlci_config *config); 231 232 /** 233 * @brief Attach CMUX instance to pipe 234 * 235 * @param cmux CMUX instance 236 * @param pipe Pipe instance to attach CMUX instance to 237 */ 238 int modem_cmux_attach(struct modem_cmux *cmux, struct modem_pipe *pipe); 239 240 /** 241 * @brief Connect CMUX instance 242 * 243 * @details This will send a CMUX connect request to target on the serial bus. If successful, 244 * DLCI channels can be now be opened using modem_pipe_open() 245 * 246 * @param cmux CMUX instance 247 * 248 * @note When connected, the bus pipe must not be used directly 249 */ 250 int modem_cmux_connect(struct modem_cmux *cmux); 251 252 /** 253 * @brief Connect CMUX instance asynchronously 254 * 255 * @details This will send a CMUX connect request to target on the serial bus. If successful, 256 * DLCI channels can be now be opened using modem_pipe_open(). 257 * 258 * @param cmux CMUX instance 259 * 260 * @note When connected, the bus pipe must not be used directly 261 */ 262 int modem_cmux_connect_async(struct modem_cmux *cmux); 263 264 /** 265 * @brief Close down and disconnect CMUX instance 266 * 267 * @details This will close all open DLCI channels, and close down the CMUX connection. 268 * 269 * @param cmux CMUX instance 270 * 271 * @note The bus pipe must be released using modem_cmux_release() after disconnecting 272 * before being reused. 273 */ 274 int modem_cmux_disconnect(struct modem_cmux *cmux); 275 276 /** 277 * @brief Close down and disconnect CMUX instance asynchronously 278 * 279 * @details This will close all open DLCI channels, and close down the CMUX connection. 280 * 281 * @param cmux CMUX instance 282 * 283 * @note The bus pipe must be released using modem_cmux_release() after disconnecting 284 * before being reused. 285 */ 286 int modem_cmux_disconnect_async(struct modem_cmux *cmux); 287 288 /** 289 * @brief Release CMUX instance from pipe 290 * 291 * @details Releases the pipe and hard resets the CMUX instance internally. CMUX should 292 * be disconnected using modem_cmux_disconnect(). 293 * 294 * @param cmux CMUX instance 295 * 296 * @note The bus pipe can be used directly again after CMUX instance is released. 297 */ 298 void modem_cmux_release(struct modem_cmux *cmux); 299 300 /** 301 * @} 302 */ 303 304 #ifdef __cplusplus 305 } 306 #endif 307 308 #endif /* ZEPHYR_MODEM_CMUX_ */ 309