1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Management Component Transport Protocol (MCTP) - serial transport |
4 | * binding. This driver is an implementation of the DMTF specificiation |
5 | * "DSP0253 - Management Component Transport Protocol (MCTP) Serial Transport |
6 | * Binding", available at: |
7 | * |
8 | * https://www.dmtf.org/sites/default/files/standards/documents/DSP0253_1.0.0.pdf |
9 | * |
10 | * This driver provides DSP0253-type MCTP-over-serial transport using a Linux |
11 | * tty device, by setting the N_MCTP line discipline on the tty. |
12 | * |
13 | * Copyright (c) 2021 Code Construct |
14 | */ |
15 | |
16 | #include <linux/idr.h> |
17 | #include <linux/if_arp.h> |
18 | #include <linux/module.h> |
19 | #include <linux/skbuff.h> |
20 | #include <linux/tty.h> |
21 | #include <linux/workqueue.h> |
22 | #include <linux/crc-ccitt.h> |
23 | |
24 | #include <linux/mctp.h> |
25 | #include <net/mctp.h> |
26 | #include <net/pkt_sched.h> |
27 | |
28 | #define MCTP_SERIAL_MTU 68 /* base mtu (64) + mctp header */ |
29 | #define MCTP_SERIAL_FRAME_MTU (MCTP_SERIAL_MTU + 6) /* + serial framing */ |
30 | |
31 | #define MCTP_SERIAL_VERSION 0x1 /* DSP0253 defines a single version: 1 */ |
32 | |
33 | #define BUFSIZE MCTP_SERIAL_FRAME_MTU |
34 | |
35 | #define BYTE_FRAME 0x7e |
36 | #define BYTE_ESC 0x7d |
37 | |
38 | #define FCS_INIT 0xffff |
39 | |
40 | static DEFINE_IDA(mctp_serial_ida); |
41 | |
42 | enum mctp_serial_state { |
43 | STATE_IDLE, |
44 | STATE_START, |
45 | , |
46 | STATE_DATA, |
47 | STATE_ESCAPE, |
48 | STATE_TRAILER, |
49 | STATE_DONE, |
50 | STATE_ERR, |
51 | }; |
52 | |
53 | struct mctp_serial { |
54 | struct net_device *netdev; |
55 | struct tty_struct *tty; |
56 | |
57 | int idx; |
58 | |
59 | /* protects our rx & tx state machines; held during both paths */ |
60 | spinlock_t lock; |
61 | |
62 | struct work_struct tx_work; |
63 | enum mctp_serial_state txstate, rxstate; |
64 | u16 txfcs, rxfcs, rxfcs_rcvd; |
65 | unsigned int txlen, rxlen; |
66 | unsigned int txpos, rxpos; |
67 | unsigned char txbuf[BUFSIZE], |
68 | rxbuf[BUFSIZE]; |
69 | }; |
70 | |
71 | static bool needs_escape(unsigned char c) |
72 | { |
73 | return c == BYTE_ESC || c == BYTE_FRAME; |
74 | } |
75 | |
76 | static int next_chunk_len(struct mctp_serial *dev) |
77 | { |
78 | int i; |
79 | |
80 | /* either we have no bytes to send ... */ |
81 | if (dev->txpos == dev->txlen) |
82 | return 0; |
83 | |
84 | /* ... or the next byte to send is an escaped byte; requiring a |
85 | * single-byte chunk... |
86 | */ |
87 | if (needs_escape(c: dev->txbuf[dev->txpos])) |
88 | return 1; |
89 | |
90 | /* ... or we have one or more bytes up to the next escape - this chunk |
91 | * will be those non-escaped bytes, and does not include the escaped |
92 | * byte. |
93 | */ |
94 | for (i = 1; i + dev->txpos + 1 < dev->txlen; i++) { |
95 | if (needs_escape(c: dev->txbuf[dev->txpos + i + 1])) |
96 | break; |
97 | } |
98 | |
99 | return i; |
100 | } |
101 | |
102 | static int write_chunk(struct mctp_serial *dev, unsigned char *buf, int len) |
103 | { |
104 | return dev->tty->ops->write(dev->tty, buf, len); |
105 | } |
106 | |
107 | static void mctp_serial_tx_work(struct work_struct *work) |
108 | { |
109 | struct mctp_serial *dev = container_of(work, struct mctp_serial, |
110 | tx_work); |
111 | unsigned char c, buf[3]; |
112 | unsigned long flags; |
113 | int len, txlen; |
114 | |
115 | spin_lock_irqsave(&dev->lock, flags); |
116 | |
117 | /* txstate represents the next thing to send */ |
118 | switch (dev->txstate) { |
119 | case STATE_START: |
120 | dev->txpos = 0; |
121 | fallthrough; |
122 | case STATE_HEADER: |
123 | buf[0] = BYTE_FRAME; |
124 | buf[1] = MCTP_SERIAL_VERSION; |
125 | buf[2] = dev->txlen; |
126 | |
127 | if (!dev->txpos) |
128 | dev->txfcs = crc_ccitt(FCS_INIT, buffer: buf + 1, len: 2); |
129 | |
130 | txlen = write_chunk(dev, buf: buf + dev->txpos, len: 3 - dev->txpos); |
131 | if (txlen <= 0) { |
132 | dev->txstate = STATE_ERR; |
133 | } else { |
134 | dev->txpos += txlen; |
135 | if (dev->txpos == 3) { |
136 | dev->txstate = STATE_DATA; |
137 | dev->txpos = 0; |
138 | } |
139 | } |
140 | break; |
141 | |
142 | case STATE_ESCAPE: |
143 | buf[0] = dev->txbuf[dev->txpos] & ~0x20; |
144 | txlen = write_chunk(dev, buf, len: 1); |
145 | if (txlen <= 0) { |
146 | dev->txstate = STATE_ERR; |
147 | } else { |
148 | dev->txpos += txlen; |
149 | if (dev->txpos == dev->txlen) { |
150 | dev->txstate = STATE_TRAILER; |
151 | dev->txpos = 0; |
152 | } |
153 | } |
154 | |
155 | break; |
156 | |
157 | case STATE_DATA: |
158 | len = next_chunk_len(dev); |
159 | if (len) { |
160 | c = dev->txbuf[dev->txpos]; |
161 | if (len == 1 && needs_escape(c)) { |
162 | buf[0] = BYTE_ESC; |
163 | buf[1] = c & ~0x20; |
164 | dev->txfcs = crc_ccitt_byte(crc: dev->txfcs, c); |
165 | txlen = write_chunk(dev, buf, len: 2); |
166 | if (txlen == 2) |
167 | dev->txpos++; |
168 | else if (txlen == 1) |
169 | dev->txstate = STATE_ESCAPE; |
170 | else |
171 | dev->txstate = STATE_ERR; |
172 | } else { |
173 | txlen = write_chunk(dev, |
174 | buf: dev->txbuf + dev->txpos, |
175 | len); |
176 | if (txlen <= 0) { |
177 | dev->txstate = STATE_ERR; |
178 | } else { |
179 | dev->txfcs = crc_ccitt(crc: dev->txfcs, |
180 | buffer: dev->txbuf + |
181 | dev->txpos, |
182 | len: txlen); |
183 | dev->txpos += txlen; |
184 | } |
185 | } |
186 | if (dev->txstate == STATE_DATA && |
187 | dev->txpos == dev->txlen) { |
188 | dev->txstate = STATE_TRAILER; |
189 | dev->txpos = 0; |
190 | } |
191 | break; |
192 | } |
193 | dev->txstate = STATE_TRAILER; |
194 | dev->txpos = 0; |
195 | fallthrough; |
196 | |
197 | case STATE_TRAILER: |
198 | buf[0] = dev->txfcs >> 8; |
199 | buf[1] = dev->txfcs & 0xff; |
200 | buf[2] = BYTE_FRAME; |
201 | txlen = write_chunk(dev, buf: buf + dev->txpos, len: 3 - dev->txpos); |
202 | if (txlen <= 0) { |
203 | dev->txstate = STATE_ERR; |
204 | } else { |
205 | dev->txpos += txlen; |
206 | if (dev->txpos == 3) { |
207 | dev->txstate = STATE_DONE; |
208 | dev->txpos = 0; |
209 | } |
210 | } |
211 | break; |
212 | default: |
213 | netdev_err_once(dev->netdev, "invalid tx state %d\n" , |
214 | dev->txstate); |
215 | } |
216 | |
217 | if (dev->txstate == STATE_DONE) { |
218 | dev->netdev->stats.tx_packets++; |
219 | dev->netdev->stats.tx_bytes += dev->txlen; |
220 | dev->txlen = 0; |
221 | dev->txpos = 0; |
222 | clear_bit(TTY_DO_WRITE_WAKEUP, addr: &dev->tty->flags); |
223 | dev->txstate = STATE_IDLE; |
224 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
225 | |
226 | netif_wake_queue(dev: dev->netdev); |
227 | } else { |
228 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
229 | } |
230 | } |
231 | |
232 | static netdev_tx_t mctp_serial_tx(struct sk_buff *skb, struct net_device *ndev) |
233 | { |
234 | struct mctp_serial *dev = netdev_priv(dev: ndev); |
235 | unsigned long flags; |
236 | |
237 | WARN_ON(dev->txstate != STATE_IDLE); |
238 | |
239 | if (skb->len > MCTP_SERIAL_MTU) { |
240 | dev->netdev->stats.tx_dropped++; |
241 | goto out; |
242 | } |
243 | |
244 | spin_lock_irqsave(&dev->lock, flags); |
245 | netif_stop_queue(dev: dev->netdev); |
246 | skb_copy_bits(skb, offset: 0, to: dev->txbuf, len: skb->len); |
247 | dev->txpos = 0; |
248 | dev->txlen = skb->len; |
249 | dev->txstate = STATE_START; |
250 | spin_unlock_irqrestore(lock: &dev->lock, flags); |
251 | |
252 | set_bit(TTY_DO_WRITE_WAKEUP, addr: &dev->tty->flags); |
253 | schedule_work(work: &dev->tx_work); |
254 | |
255 | out: |
256 | kfree_skb(skb); |
257 | return NETDEV_TX_OK; |
258 | } |
259 | |
260 | static void mctp_serial_tty_write_wakeup(struct tty_struct *tty) |
261 | { |
262 | struct mctp_serial *dev = tty->disc_data; |
263 | |
264 | schedule_work(work: &dev->tx_work); |
265 | } |
266 | |
267 | static void mctp_serial_rx(struct mctp_serial *dev) |
268 | { |
269 | struct mctp_skb_cb *cb; |
270 | struct sk_buff *skb; |
271 | |
272 | if (dev->rxfcs != dev->rxfcs_rcvd) { |
273 | dev->netdev->stats.rx_dropped++; |
274 | dev->netdev->stats.rx_crc_errors++; |
275 | return; |
276 | } |
277 | |
278 | skb = netdev_alloc_skb(dev: dev->netdev, length: dev->rxlen); |
279 | if (!skb) { |
280 | dev->netdev->stats.rx_dropped++; |
281 | return; |
282 | } |
283 | |
284 | skb->protocol = htons(ETH_P_MCTP); |
285 | skb_put_data(skb, data: dev->rxbuf, len: dev->rxlen); |
286 | skb_reset_network_header(skb); |
287 | |
288 | cb = __mctp_cb(skb); |
289 | cb->halen = 0; |
290 | |
291 | netif_rx(skb); |
292 | dev->netdev->stats.rx_packets++; |
293 | dev->netdev->stats.rx_bytes += dev->rxlen; |
294 | } |
295 | |
296 | static void (struct mctp_serial *dev, unsigned char c) |
297 | { |
298 | switch (dev->rxpos) { |
299 | case 0: |
300 | if (c == BYTE_FRAME) |
301 | dev->rxpos++; |
302 | else |
303 | dev->rxstate = STATE_ERR; |
304 | break; |
305 | case 1: |
306 | if (c == MCTP_SERIAL_VERSION) { |
307 | dev->rxpos++; |
308 | dev->rxfcs = crc_ccitt_byte(FCS_INIT, c); |
309 | } else { |
310 | dev->rxstate = STATE_ERR; |
311 | } |
312 | break; |
313 | case 2: |
314 | if (c > MCTP_SERIAL_FRAME_MTU) { |
315 | dev->rxstate = STATE_ERR; |
316 | } else { |
317 | dev->rxlen = c; |
318 | dev->rxpos = 0; |
319 | dev->rxstate = STATE_DATA; |
320 | dev->rxfcs = crc_ccitt_byte(crc: dev->rxfcs, c); |
321 | } |
322 | break; |
323 | } |
324 | } |
325 | |
326 | static void mctp_serial_push_trailer(struct mctp_serial *dev, unsigned char c) |
327 | { |
328 | switch (dev->rxpos) { |
329 | case 0: |
330 | dev->rxfcs_rcvd = c << 8; |
331 | dev->rxpos++; |
332 | break; |
333 | case 1: |
334 | dev->rxfcs_rcvd |= c; |
335 | dev->rxpos++; |
336 | break; |
337 | case 2: |
338 | if (c != BYTE_FRAME) { |
339 | dev->rxstate = STATE_ERR; |
340 | } else { |
341 | mctp_serial_rx(dev); |
342 | dev->rxlen = 0; |
343 | dev->rxpos = 0; |
344 | dev->rxstate = STATE_IDLE; |
345 | } |
346 | break; |
347 | } |
348 | } |
349 | |
350 | static void mctp_serial_push(struct mctp_serial *dev, unsigned char c) |
351 | { |
352 | switch (dev->rxstate) { |
353 | case STATE_IDLE: |
354 | dev->rxstate = STATE_HEADER; |
355 | fallthrough; |
356 | case STATE_HEADER: |
357 | mctp_serial_push_header(dev, c); |
358 | break; |
359 | |
360 | case STATE_ESCAPE: |
361 | c |= 0x20; |
362 | fallthrough; |
363 | case STATE_DATA: |
364 | if (dev->rxstate != STATE_ESCAPE && c == BYTE_ESC) { |
365 | dev->rxstate = STATE_ESCAPE; |
366 | } else { |
367 | dev->rxfcs = crc_ccitt_byte(crc: dev->rxfcs, c); |
368 | dev->rxbuf[dev->rxpos] = c; |
369 | dev->rxpos++; |
370 | dev->rxstate = STATE_DATA; |
371 | if (dev->rxpos == dev->rxlen) { |
372 | dev->rxpos = 0; |
373 | dev->rxstate = STATE_TRAILER; |
374 | } |
375 | } |
376 | break; |
377 | |
378 | case STATE_TRAILER: |
379 | mctp_serial_push_trailer(dev, c); |
380 | break; |
381 | |
382 | case STATE_ERR: |
383 | if (c == BYTE_FRAME) |
384 | dev->rxstate = STATE_IDLE; |
385 | break; |
386 | |
387 | default: |
388 | netdev_err_once(dev->netdev, "invalid rx state %d\n" , |
389 | dev->rxstate); |
390 | } |
391 | } |
392 | |
393 | static void mctp_serial_tty_receive_buf(struct tty_struct *tty, const u8 *c, |
394 | const u8 *f, size_t len) |
395 | { |
396 | struct mctp_serial *dev = tty->disc_data; |
397 | int i; |
398 | |
399 | if (!netif_running(dev: dev->netdev)) |
400 | return; |
401 | |
402 | /* we don't (currently) use the flag bytes, just data. */ |
403 | for (i = 0; i < len; i++) |
404 | mctp_serial_push(dev, c: c[i]); |
405 | } |
406 | |
407 | static void mctp_serial_uninit(struct net_device *ndev) |
408 | { |
409 | struct mctp_serial *dev = netdev_priv(dev: ndev); |
410 | |
411 | cancel_work_sync(work: &dev->tx_work); |
412 | } |
413 | |
414 | static const struct net_device_ops mctp_serial_netdev_ops = { |
415 | .ndo_start_xmit = mctp_serial_tx, |
416 | .ndo_uninit = mctp_serial_uninit, |
417 | }; |
418 | |
419 | static void mctp_serial_setup(struct net_device *ndev) |
420 | { |
421 | ndev->type = ARPHRD_MCTP; |
422 | |
423 | /* we limit at the fixed MTU, which is also the MCTP-standard |
424 | * baseline MTU, so is also our minimum |
425 | */ |
426 | ndev->mtu = MCTP_SERIAL_MTU; |
427 | ndev->max_mtu = MCTP_SERIAL_MTU; |
428 | ndev->min_mtu = MCTP_SERIAL_MTU; |
429 | |
430 | ndev->hard_header_len = 0; |
431 | ndev->addr_len = 0; |
432 | ndev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; |
433 | ndev->flags = IFF_NOARP; |
434 | ndev->netdev_ops = &mctp_serial_netdev_ops; |
435 | ndev->needs_free_netdev = true; |
436 | } |
437 | |
438 | static int mctp_serial_open(struct tty_struct *tty) |
439 | { |
440 | struct mctp_serial *dev; |
441 | struct net_device *ndev; |
442 | char name[32]; |
443 | int idx, rc; |
444 | |
445 | if (!capable(CAP_NET_ADMIN)) |
446 | return -EPERM; |
447 | |
448 | if (!tty->ops->write) |
449 | return -EOPNOTSUPP; |
450 | |
451 | idx = ida_alloc(ida: &mctp_serial_ida, GFP_KERNEL); |
452 | if (idx < 0) |
453 | return idx; |
454 | |
455 | snprintf(buf: name, size: sizeof(name), fmt: "mctpserial%d" , idx); |
456 | ndev = alloc_netdev(sizeof(*dev), name, NET_NAME_ENUM, |
457 | mctp_serial_setup); |
458 | if (!ndev) { |
459 | rc = -ENOMEM; |
460 | goto free_ida; |
461 | } |
462 | |
463 | dev = netdev_priv(dev: ndev); |
464 | dev->idx = idx; |
465 | dev->tty = tty; |
466 | dev->netdev = ndev; |
467 | dev->txstate = STATE_IDLE; |
468 | dev->rxstate = STATE_IDLE; |
469 | spin_lock_init(&dev->lock); |
470 | INIT_WORK(&dev->tx_work, mctp_serial_tx_work); |
471 | |
472 | rc = register_netdev(dev: ndev); |
473 | if (rc) |
474 | goto free_netdev; |
475 | |
476 | tty->receive_room = 64 * 1024; |
477 | tty->disc_data = dev; |
478 | |
479 | return 0; |
480 | |
481 | free_netdev: |
482 | free_netdev(dev: ndev); |
483 | |
484 | free_ida: |
485 | ida_free(&mctp_serial_ida, id: idx); |
486 | return rc; |
487 | } |
488 | |
489 | static void mctp_serial_close(struct tty_struct *tty) |
490 | { |
491 | struct mctp_serial *dev = tty->disc_data; |
492 | int idx = dev->idx; |
493 | |
494 | unregister_netdev(dev: dev->netdev); |
495 | ida_free(&mctp_serial_ida, id: idx); |
496 | } |
497 | |
498 | static struct tty_ldisc_ops mctp_ldisc = { |
499 | .owner = THIS_MODULE, |
500 | .num = N_MCTP, |
501 | .name = "mctp" , |
502 | .open = mctp_serial_open, |
503 | .close = mctp_serial_close, |
504 | .receive_buf = mctp_serial_tty_receive_buf, |
505 | .write_wakeup = mctp_serial_tty_write_wakeup, |
506 | }; |
507 | |
508 | static int __init mctp_serial_init(void) |
509 | { |
510 | return tty_register_ldisc(new_ldisc: &mctp_ldisc); |
511 | } |
512 | |
513 | static void __exit mctp_serial_exit(void) |
514 | { |
515 | tty_unregister_ldisc(ldisc: &mctp_ldisc); |
516 | } |
517 | |
518 | module_init(mctp_serial_init); |
519 | module_exit(mctp_serial_exit); |
520 | |
521 | MODULE_LICENSE("GPL v2" ); |
522 | MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>" ); |
523 | MODULE_DESCRIPTION("MCTP Serial transport" ); |
524 | |