634 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Management Component Transport Protocol (MCTP) - serial transport4 * binding. This driver is an implementation of the DMTF specificiation5 * "DSP0253 - Management Component Transport Protocol (MCTP) Serial Transport6 * Binding", available at:7 *8 * https://www.dmtf.org/sites/default/files/standards/documents/DSP0253_1.0.0.pdf9 *10 * This driver provides DSP0253-type MCTP-over-serial transport using a Linux11 * tty device, by setting the N_MCTP line discipline on the tty.12 *13 * Copyright (c) 2021 Code Construct14 */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_MTU34 35#define BYTE_FRAME 0x7e36#define BYTE_ESC 0x7d37 38#define FCS_INIT 0xffff39 40static DEFINE_IDA(mctp_serial_ida);41 42enum mctp_serial_state {43 STATE_IDLE,44 STATE_START,45 STATE_HEADER,46 STATE_DATA,47 STATE_ESCAPE,48 STATE_TRAILER,49 STATE_DONE,50 STATE_ERR,51};52 53struct 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 u8 txbuf[BUFSIZE],68 rxbuf[BUFSIZE];69};70 71static bool needs_escape(u8 c)72{73 return c == BYTE_ESC || c == BYTE_FRAME;74}75 76static unsigned int next_chunk_len(struct mctp_serial *dev)77{78 unsigned 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 a85 * single-byte chunk...86 */87 if (needs_escape(dev->txbuf[dev->txpos]))88 return 1;89 90 /* ... or we have one or more bytes up to the next escape - this chunk91 * will be those non-escaped bytes, and does not include the escaped92 * byte.93 */94 for (i = 1; i + dev->txpos < dev->txlen; i++) {95 if (needs_escape(dev->txbuf[dev->txpos + i]))96 break;97 }98 99 return i;100}101 102static ssize_t write_chunk(struct mctp_serial *dev, u8 *buf, size_t len)103{104 return dev->tty->ops->write(dev->tty, buf, len);105}106 107static 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 long flags;112 ssize_t txlen;113 unsigned int len;114 u8 c, buf[3];115 116 spin_lock_irqsave(&dev->lock, flags);117 118 /* txstate represents the next thing to send */119 switch (dev->txstate) {120 case STATE_START:121 dev->txpos = 0;122 fallthrough;123 case STATE_HEADER:124 buf[0] = BYTE_FRAME;125 buf[1] = MCTP_SERIAL_VERSION;126 buf[2] = dev->txlen;127 128 if (!dev->txpos)129 dev->txfcs = crc_ccitt(FCS_INIT, buf + 1, 2);130 131 txlen = write_chunk(dev, buf + dev->txpos, 3 - dev->txpos);132 if (txlen <= 0) {133 dev->txstate = STATE_ERR;134 } else {135 dev->txpos += txlen;136 if (dev->txpos == 3) {137 dev->txstate = STATE_DATA;138 dev->txpos = 0;139 }140 }141 break;142 143 case STATE_ESCAPE:144 buf[0] = dev->txbuf[dev->txpos] & ~0x20;145 txlen = write_chunk(dev, buf, 1);146 if (txlen <= 0) {147 dev->txstate = STATE_ERR;148 } else {149 dev->txpos += txlen;150 if (dev->txpos == dev->txlen) {151 dev->txstate = STATE_TRAILER;152 dev->txpos = 0;153 }154 }155 156 break;157 158 case STATE_DATA:159 len = next_chunk_len(dev);160 if (len) {161 c = dev->txbuf[dev->txpos];162 if (len == 1 && needs_escape(c)) {163 buf[0] = BYTE_ESC;164 buf[1] = c & ~0x20;165 dev->txfcs = crc_ccitt_byte(dev->txfcs, c);166 txlen = write_chunk(dev, buf, 2);167 if (txlen == 2)168 dev->txpos++;169 else if (txlen == 1)170 dev->txstate = STATE_ESCAPE;171 else172 dev->txstate = STATE_ERR;173 } else {174 txlen = write_chunk(dev,175 dev->txbuf + dev->txpos,176 len);177 if (txlen <= 0) {178 dev->txstate = STATE_ERR;179 } else {180 dev->txfcs = crc_ccitt(dev->txfcs,181 dev->txbuf +182 dev->txpos,183 txlen);184 dev->txpos += txlen;185 }186 }187 if (dev->txstate == STATE_DATA &&188 dev->txpos == dev->txlen) {189 dev->txstate = STATE_TRAILER;190 dev->txpos = 0;191 }192 break;193 }194 dev->txstate = STATE_TRAILER;195 dev->txpos = 0;196 fallthrough;197 198 case STATE_TRAILER:199 buf[0] = dev->txfcs >> 8;200 buf[1] = dev->txfcs & 0xff;201 buf[2] = BYTE_FRAME;202 txlen = write_chunk(dev, buf + dev->txpos, 3 - dev->txpos);203 if (txlen <= 0) {204 dev->txstate = STATE_ERR;205 } else {206 dev->txpos += txlen;207 if (dev->txpos == 3) {208 dev->txstate = STATE_DONE;209 dev->txpos = 0;210 }211 }212 break;213 default:214 netdev_err_once(dev->netdev, "invalid tx state %d\n",215 dev->txstate);216 }217 218 if (dev->txstate == STATE_DONE) {219 dev->netdev->stats.tx_packets++;220 dev->netdev->stats.tx_bytes += dev->txlen;221 dev->txlen = 0;222 dev->txpos = 0;223 clear_bit(TTY_DO_WRITE_WAKEUP, &dev->tty->flags);224 dev->txstate = STATE_IDLE;225 spin_unlock_irqrestore(&dev->lock, flags);226 227 netif_wake_queue(dev->netdev);228 } else {229 spin_unlock_irqrestore(&dev->lock, flags);230 }231}232 233static netdev_tx_t mctp_serial_tx(struct sk_buff *skb, struct net_device *ndev)234{235 struct mctp_serial *dev = netdev_priv(ndev);236 unsigned long flags;237 238 WARN_ON(dev->txstate != STATE_IDLE);239 240 if (skb->len > MCTP_SERIAL_MTU) {241 dev->netdev->stats.tx_dropped++;242 goto out;243 }244 245 spin_lock_irqsave(&dev->lock, flags);246 netif_stop_queue(dev->netdev);247 skb_copy_bits(skb, 0, dev->txbuf, skb->len);248 dev->txpos = 0;249 dev->txlen = skb->len;250 dev->txstate = STATE_START;251 spin_unlock_irqrestore(&dev->lock, flags);252 253 set_bit(TTY_DO_WRITE_WAKEUP, &dev->tty->flags);254 schedule_work(&dev->tx_work);255 256out:257 kfree_skb(skb);258 return NETDEV_TX_OK;259}260 261static void mctp_serial_tty_write_wakeup(struct tty_struct *tty)262{263 struct mctp_serial *dev = tty->disc_data;264 265 schedule_work(&dev->tx_work);266}267 268static void mctp_serial_rx(struct mctp_serial *dev)269{270 struct mctp_skb_cb *cb;271 struct sk_buff *skb;272 273 if (dev->rxfcs != dev->rxfcs_rcvd) {274 dev->netdev->stats.rx_dropped++;275 dev->netdev->stats.rx_crc_errors++;276 return;277 }278 279 skb = netdev_alloc_skb(dev->netdev, dev->rxlen);280 if (!skb) {281 dev->netdev->stats.rx_dropped++;282 return;283 }284 285 skb->protocol = htons(ETH_P_MCTP);286 skb_put_data(skb, dev->rxbuf, dev->rxlen);287 skb_reset_network_header(skb);288 289 cb = __mctp_cb(skb);290 cb->halen = 0;291 292 netif_rx(skb);293 dev->netdev->stats.rx_packets++;294 dev->netdev->stats.rx_bytes += dev->rxlen;295}296 297static void mctp_serial_push_header(struct mctp_serial *dev, u8 c)298{299 switch (dev->rxpos) {300 case 0:301 if (c == BYTE_FRAME)302 dev->rxpos++;303 else304 dev->rxstate = STATE_ERR;305 break;306 case 1:307 if (c == MCTP_SERIAL_VERSION) {308 dev->rxpos++;309 dev->rxfcs = crc_ccitt_byte(FCS_INIT, c);310 } else {311 dev->rxstate = STATE_ERR;312 }313 break;314 case 2:315 if (c > MCTP_SERIAL_FRAME_MTU) {316 dev->rxstate = STATE_ERR;317 } else {318 dev->rxlen = c;319 dev->rxpos = 0;320 dev->rxstate = STATE_DATA;321 dev->rxfcs = crc_ccitt_byte(dev->rxfcs, c);322 }323 break;324 }325}326 327static void mctp_serial_push_trailer(struct mctp_serial *dev, u8 c)328{329 switch (dev->rxpos) {330 case 0:331 dev->rxfcs_rcvd = c << 8;332 dev->rxpos++;333 break;334 case 1:335 dev->rxfcs_rcvd |= c;336 dev->rxpos++;337 break;338 case 2:339 if (c != BYTE_FRAME) {340 dev->rxstate = STATE_ERR;341 } else {342 mctp_serial_rx(dev);343 dev->rxlen = 0;344 dev->rxpos = 0;345 dev->rxstate = STATE_IDLE;346 }347 break;348 }349}350 351static void mctp_serial_push(struct mctp_serial *dev, u8 c)352{353 switch (dev->rxstate) {354 case STATE_IDLE:355 dev->rxstate = STATE_HEADER;356 fallthrough;357 case STATE_HEADER:358 mctp_serial_push_header(dev, c);359 break;360 361 case STATE_ESCAPE:362 c |= 0x20;363 fallthrough;364 case STATE_DATA:365 if (dev->rxstate != STATE_ESCAPE && c == BYTE_ESC) {366 dev->rxstate = STATE_ESCAPE;367 } else {368 dev->rxfcs = crc_ccitt_byte(dev->rxfcs, c);369 dev->rxbuf[dev->rxpos] = c;370 dev->rxpos++;371 dev->rxstate = STATE_DATA;372 if (dev->rxpos == dev->rxlen) {373 dev->rxpos = 0;374 dev->rxstate = STATE_TRAILER;375 }376 }377 break;378 379 case STATE_TRAILER:380 mctp_serial_push_trailer(dev, c);381 break;382 383 case STATE_ERR:384 if (c == BYTE_FRAME)385 dev->rxstate = STATE_IDLE;386 break;387 388 default:389 netdev_err_once(dev->netdev, "invalid rx state %d\n",390 dev->rxstate);391 }392}393 394static void mctp_serial_tty_receive_buf(struct tty_struct *tty, const u8 *c,395 const u8 *f, size_t len)396{397 struct mctp_serial *dev = tty->disc_data;398 size_t i;399 400 if (!netif_running(dev->netdev))401 return;402 403 /* we don't (currently) use the flag bytes, just data. */404 for (i = 0; i < len; i++)405 mctp_serial_push(dev, c[i]);406}407 408static void mctp_serial_uninit(struct net_device *ndev)409{410 struct mctp_serial *dev = netdev_priv(ndev);411 412 cancel_work_sync(&dev->tx_work);413}414 415static const struct net_device_ops mctp_serial_netdev_ops = {416 .ndo_start_xmit = mctp_serial_tx,417 .ndo_uninit = mctp_serial_uninit,418};419 420static void mctp_serial_setup(struct net_device *ndev)421{422 ndev->type = ARPHRD_MCTP;423 424 /* we limit at the fixed MTU, which is also the MCTP-standard425 * baseline MTU, so is also our minimum426 */427 ndev->mtu = MCTP_SERIAL_MTU;428 ndev->max_mtu = MCTP_SERIAL_MTU;429 ndev->min_mtu = MCTP_SERIAL_MTU;430 431 ndev->hard_header_len = 0;432 ndev->addr_len = 0;433 ndev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;434 ndev->flags = IFF_NOARP;435 ndev->netdev_ops = &mctp_serial_netdev_ops;436 ndev->needs_free_netdev = true;437}438 439static int mctp_serial_open(struct tty_struct *tty)440{441 struct mctp_serial *dev;442 struct net_device *ndev;443 char name[32];444 int idx, rc;445 446 if (!capable(CAP_NET_ADMIN))447 return -EPERM;448 449 if (!tty->ops->write)450 return -EOPNOTSUPP;451 452 idx = ida_alloc(&mctp_serial_ida, GFP_KERNEL);453 if (idx < 0)454 return idx;455 456 snprintf(name, sizeof(name), "mctpserial%d", idx);457 ndev = alloc_netdev(sizeof(*dev), name, NET_NAME_ENUM,458 mctp_serial_setup);459 if (!ndev) {460 rc = -ENOMEM;461 goto free_ida;462 }463 464 dev = netdev_priv(ndev);465 dev->idx = idx;466 dev->tty = tty;467 dev->netdev = ndev;468 dev->txstate = STATE_IDLE;469 dev->rxstate = STATE_IDLE;470 spin_lock_init(&dev->lock);471 INIT_WORK(&dev->tx_work, mctp_serial_tx_work);472 473 rc = register_netdev(ndev);474 if (rc)475 goto free_netdev;476 477 tty->receive_room = 64 * 1024;478 tty->disc_data = dev;479 480 return 0;481 482free_netdev:483 free_netdev(ndev);484 485free_ida:486 ida_free(&mctp_serial_ida, idx);487 return rc;488}489 490static void mctp_serial_close(struct tty_struct *tty)491{492 struct mctp_serial *dev = tty->disc_data;493 int idx = dev->idx;494 495 unregister_netdev(dev->netdev);496 ida_free(&mctp_serial_ida, idx);497}498 499static struct tty_ldisc_ops mctp_ldisc = {500 .owner = THIS_MODULE,501 .num = N_MCTP,502 .name = "mctp",503 .open = mctp_serial_open,504 .close = mctp_serial_close,505 .receive_buf = mctp_serial_tty_receive_buf,506 .write_wakeup = mctp_serial_tty_write_wakeup,507};508 509static int __init mctp_serial_init(void)510{511 return tty_register_ldisc(&mctp_ldisc);512}513 514static void __exit mctp_serial_exit(void)515{516 tty_unregister_ldisc(&mctp_ldisc);517}518 519module_init(mctp_serial_init);520module_exit(mctp_serial_exit);521 522MODULE_LICENSE("GPL v2");523MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>");524MODULE_DESCRIPTION("MCTP Serial transport");525 526#if IS_ENABLED(CONFIG_MCTP_SERIAL_TEST)527#include <kunit/test.h>528 529#define MAX_CHUNKS 6530struct test_chunk_tx {531 u8 input_len;532 u8 input[MCTP_SERIAL_MTU];533 u8 chunks[MAX_CHUNKS];534};535 536static void test_next_chunk_len(struct kunit *test)537{538 struct mctp_serial devx;539 struct mctp_serial *dev = &devx;540 int next;541 542 const struct test_chunk_tx *params = test->param_value;543 544 memset(dev, 0x0, sizeof(*dev));545 memcpy(dev->txbuf, params->input, params->input_len);546 dev->txlen = params->input_len;547 548 for (size_t i = 0; i < MAX_CHUNKS; i++) {549 next = next_chunk_len(dev);550 dev->txpos += next;551 KUNIT_EXPECT_EQ(test, next, params->chunks[i]);552 553 if (next == 0) {554 KUNIT_EXPECT_EQ(test, dev->txpos, dev->txlen);555 return;556 }557 }558 559 KUNIT_FAIL_AND_ABORT(test, "Ran out of chunks");560}561 562static struct test_chunk_tx chunk_tx_tests[] = {563 {564 .input_len = 5,565 .input = { 0x00, 0x11, 0x22, 0x7e, 0x80 },566 .chunks = { 3, 1, 1, 0},567 },568 {569 .input_len = 5,570 .input = { 0x00, 0x11, 0x22, 0x7e, 0x7d },571 .chunks = { 3, 1, 1, 0},572 },573 {574 .input_len = 3,575 .input = { 0x7e, 0x11, 0x22, },576 .chunks = { 1, 2, 0},577 },578 {579 .input_len = 3,580 .input = { 0x7e, 0x7e, 0x7d, },581 .chunks = { 1, 1, 1, 0},582 },583 {584 .input_len = 4,585 .input = { 0x7e, 0x7e, 0x00, 0x7d, },586 .chunks = { 1, 1, 1, 1, 0},587 },588 {589 .input_len = 6,590 .input = { 0x7e, 0x7e, 0x00, 0x7d, 0x10, 0x10},591 .chunks = { 1, 1, 1, 1, 2, 0},592 },593 {594 .input_len = 1,595 .input = { 0x7e },596 .chunks = { 1, 0 },597 },598 {599 .input_len = 1,600 .input = { 0x80 },601 .chunks = { 1, 0 },602 },603 {604 .input_len = 3,605 .input = { 0x80, 0x80, 0x00 },606 .chunks = { 3, 0 },607 },608 {609 .input_len = 7,610 .input = { 0x01, 0x00, 0x08, 0xc8, 0x00, 0x80, 0x02 },611 .chunks = { 7, 0 },612 },613 {614 .input_len = 7,615 .input = { 0x01, 0x00, 0x08, 0xc8, 0x7e, 0x80, 0x02 },616 .chunks = { 4, 1, 2, 0 },617 },618};619 620KUNIT_ARRAY_PARAM(chunk_tx, chunk_tx_tests, NULL);621 622static struct kunit_case mctp_serial_test_cases[] = {623 KUNIT_CASE_PARAM(test_next_chunk_len, chunk_tx_gen_params),624};625 626static struct kunit_suite mctp_serial_test_suite = {627 .name = "mctp_serial",628 .test_cases = mctp_serial_test_cases,629};630 631kunit_test_suite(mctp_serial_test_suite);632 633#endif /* CONFIG_MCTP_SERIAL_TEST */634