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