123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Management Component Transport Protocol (MCTP) - serial transport
- * binding. This driver is an implementation of the DMTF specificiation
- * "DSP0253 - Management Component Transport Protocol (MCTP) Serial Transport
- * Binding", available at:
- *
- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0253_1.0.0.pdf
- *
- * This driver provides DSP0253-type MCTP-over-serial transport using a Linux
- * tty device, by setting the N_MCTP line discipline on the tty.
- *
- * Copyright (c) 2021 Code Construct
- */
- #include <linux/idr.h>
- #include <linux/if_arp.h>
- #include <linux/module.h>
- #include <linux/skbuff.h>
- #include <linux/tty.h>
- #include <linux/workqueue.h>
- #include <linux/crc-ccitt.h>
- #include <linux/mctp.h>
- #include <net/mctp.h>
- #include <net/pkt_sched.h>
- #define MCTP_SERIAL_MTU 68 /* base mtu (64) + mctp header */
- #define MCTP_SERIAL_FRAME_MTU (MCTP_SERIAL_MTU + 6) /* + serial framing */
- #define MCTP_SERIAL_VERSION 0x1 /* DSP0253 defines a single version: 1 */
- #define BUFSIZE MCTP_SERIAL_FRAME_MTU
- #define BYTE_FRAME 0x7e
- #define BYTE_ESC 0x7d
- #define FCS_INIT 0xffff
- static DEFINE_IDA(mctp_serial_ida);
- enum mctp_serial_state {
- STATE_IDLE,
- STATE_START,
- STATE_HEADER,
- STATE_DATA,
- STATE_ESCAPE,
- STATE_TRAILER,
- STATE_DONE,
- STATE_ERR,
- };
- struct mctp_serial {
- struct net_device *netdev;
- struct tty_struct *tty;
- int idx;
- /* protects our rx & tx state machines; held during both paths */
- spinlock_t lock;
- struct work_struct tx_work;
- enum mctp_serial_state txstate, rxstate;
- u16 txfcs, rxfcs, rxfcs_rcvd;
- unsigned int txlen, rxlen;
- unsigned int txpos, rxpos;
- unsigned char txbuf[BUFSIZE],
- rxbuf[BUFSIZE];
- };
- static bool needs_escape(unsigned char c)
- {
- return c == BYTE_ESC || c == BYTE_FRAME;
- }
- static int next_chunk_len(struct mctp_serial *dev)
- {
- int i;
- /* either we have no bytes to send ... */
- if (dev->txpos == dev->txlen)
- return 0;
- /* ... or the next byte to send is an escaped byte; requiring a
- * single-byte chunk...
- */
- if (needs_escape(dev->txbuf[dev->txpos]))
- return 1;
- /* ... or we have one or more bytes up to the next escape - this chunk
- * will be those non-escaped bytes, and does not include the escaped
- * byte.
- */
- for (i = 1; i + dev->txpos + 1 < dev->txlen; i++) {
- if (needs_escape(dev->txbuf[dev->txpos + i + 1]))
- break;
- }
- return i;
- }
- static int write_chunk(struct mctp_serial *dev, unsigned char *buf, int len)
- {
- return dev->tty->ops->write(dev->tty, buf, len);
- }
- static void mctp_serial_tx_work(struct work_struct *work)
- {
- struct mctp_serial *dev = container_of(work, struct mctp_serial,
- tx_work);
- unsigned char c, buf[3];
- unsigned long flags;
- int len, txlen;
- spin_lock_irqsave(&dev->lock, flags);
- /* txstate represents the next thing to send */
- switch (dev->txstate) {
- case STATE_START:
- dev->txpos = 0;
- fallthrough;
- case STATE_HEADER:
- buf[0] = BYTE_FRAME;
- buf[1] = MCTP_SERIAL_VERSION;
- buf[2] = dev->txlen;
- if (!dev->txpos)
- dev->txfcs = crc_ccitt(FCS_INIT, buf + 1, 2);
- txlen = write_chunk(dev, buf + dev->txpos, 3 - dev->txpos);
- if (txlen <= 0) {
- dev->txstate = STATE_ERR;
- } else {
- dev->txpos += txlen;
- if (dev->txpos == 3) {
- dev->txstate = STATE_DATA;
- dev->txpos = 0;
- }
- }
- break;
- case STATE_ESCAPE:
- buf[0] = dev->txbuf[dev->txpos] & ~0x20;
- txlen = write_chunk(dev, buf, 1);
- if (txlen <= 0) {
- dev->txstate = STATE_ERR;
- } else {
- dev->txpos += txlen;
- if (dev->txpos == dev->txlen) {
- dev->txstate = STATE_TRAILER;
- dev->txpos = 0;
- }
- }
- break;
- case STATE_DATA:
- len = next_chunk_len(dev);
- if (len) {
- c = dev->txbuf[dev->txpos];
- if (len == 1 && needs_escape(c)) {
- buf[0] = BYTE_ESC;
- buf[1] = c & ~0x20;
- dev->txfcs = crc_ccitt_byte(dev->txfcs, c);
- txlen = write_chunk(dev, buf, 2);
- if (txlen == 2)
- dev->txpos++;
- else if (txlen == 1)
- dev->txstate = STATE_ESCAPE;
- else
- dev->txstate = STATE_ERR;
- } else {
- txlen = write_chunk(dev,
- dev->txbuf + dev->txpos,
- len);
- if (txlen <= 0) {
- dev->txstate = STATE_ERR;
- } else {
- dev->txfcs = crc_ccitt(dev->txfcs,
- dev->txbuf +
- dev->txpos,
- txlen);
- dev->txpos += txlen;
- }
- }
- if (dev->txstate == STATE_DATA &&
- dev->txpos == dev->txlen) {
- dev->txstate = STATE_TRAILER;
- dev->txpos = 0;
- }
- break;
- }
- dev->txstate = STATE_TRAILER;
- dev->txpos = 0;
- fallthrough;
- case STATE_TRAILER:
- buf[0] = dev->txfcs >> 8;
- buf[1] = dev->txfcs & 0xff;
- buf[2] = BYTE_FRAME;
- txlen = write_chunk(dev, buf + dev->txpos, 3 - dev->txpos);
- if (txlen <= 0) {
- dev->txstate = STATE_ERR;
- } else {
- dev->txpos += txlen;
- if (dev->txpos == 3) {
- dev->txstate = STATE_DONE;
- dev->txpos = 0;
- }
- }
- break;
- default:
- netdev_err_once(dev->netdev, "invalid tx state %d\n",
- dev->txstate);
- }
- if (dev->txstate == STATE_DONE) {
- dev->netdev->stats.tx_packets++;
- dev->netdev->stats.tx_bytes += dev->txlen;
- dev->txlen = 0;
- dev->txpos = 0;
- clear_bit(TTY_DO_WRITE_WAKEUP, &dev->tty->flags);
- dev->txstate = STATE_IDLE;
- spin_unlock_irqrestore(&dev->lock, flags);
- netif_wake_queue(dev->netdev);
- } else {
- spin_unlock_irqrestore(&dev->lock, flags);
- }
- }
- static netdev_tx_t mctp_serial_tx(struct sk_buff *skb, struct net_device *ndev)
- {
- struct mctp_serial *dev = netdev_priv(ndev);
- unsigned long flags;
- WARN_ON(dev->txstate != STATE_IDLE);
- if (skb->len > MCTP_SERIAL_MTU) {
- dev->netdev->stats.tx_dropped++;
- goto out;
- }
- spin_lock_irqsave(&dev->lock, flags);
- netif_stop_queue(dev->netdev);
- skb_copy_bits(skb, 0, dev->txbuf, skb->len);
- dev->txpos = 0;
- dev->txlen = skb->len;
- dev->txstate = STATE_START;
- spin_unlock_irqrestore(&dev->lock, flags);
- set_bit(TTY_DO_WRITE_WAKEUP, &dev->tty->flags);
- schedule_work(&dev->tx_work);
- out:
- kfree_skb(skb);
- return NETDEV_TX_OK;
- }
- static void mctp_serial_tty_write_wakeup(struct tty_struct *tty)
- {
- struct mctp_serial *dev = tty->disc_data;
- schedule_work(&dev->tx_work);
- }
- static void mctp_serial_rx(struct mctp_serial *dev)
- {
- struct mctp_skb_cb *cb;
- struct sk_buff *skb;
- if (dev->rxfcs != dev->rxfcs_rcvd) {
- dev->netdev->stats.rx_dropped++;
- dev->netdev->stats.rx_crc_errors++;
- return;
- }
- skb = netdev_alloc_skb(dev->netdev, dev->rxlen);
- if (!skb) {
- dev->netdev->stats.rx_dropped++;
- return;
- }
- skb->protocol = htons(ETH_P_MCTP);
- skb_put_data(skb, dev->rxbuf, dev->rxlen);
- skb_reset_network_header(skb);
- cb = __mctp_cb(skb);
- cb->halen = 0;
- netif_rx(skb);
- dev->netdev->stats.rx_packets++;
- dev->netdev->stats.rx_bytes += dev->rxlen;
- }
- static void mctp_serial_push_header(struct mctp_serial *dev, unsigned char c)
- {
- switch (dev->rxpos) {
- case 0:
- if (c == BYTE_FRAME)
- dev->rxpos++;
- else
- dev->rxstate = STATE_ERR;
- break;
- case 1:
- if (c == MCTP_SERIAL_VERSION) {
- dev->rxpos++;
- dev->rxfcs = crc_ccitt_byte(FCS_INIT, c);
- } else {
- dev->rxstate = STATE_ERR;
- }
- break;
- case 2:
- if (c > MCTP_SERIAL_FRAME_MTU) {
- dev->rxstate = STATE_ERR;
- } else {
- dev->rxlen = c;
- dev->rxpos = 0;
- dev->rxstate = STATE_DATA;
- dev->rxfcs = crc_ccitt_byte(dev->rxfcs, c);
- }
- break;
- }
- }
- static void mctp_serial_push_trailer(struct mctp_serial *dev, unsigned char c)
- {
- switch (dev->rxpos) {
- case 0:
- dev->rxfcs_rcvd = c << 8;
- dev->rxpos++;
- break;
- case 1:
- dev->rxfcs_rcvd |= c;
- dev->rxpos++;
- break;
- case 2:
- if (c != BYTE_FRAME) {
- dev->rxstate = STATE_ERR;
- } else {
- mctp_serial_rx(dev);
- dev->rxlen = 0;
- dev->rxpos = 0;
- dev->rxstate = STATE_IDLE;
- }
- break;
- }
- }
- static void mctp_serial_push(struct mctp_serial *dev, unsigned char c)
- {
- switch (dev->rxstate) {
- case STATE_IDLE:
- dev->rxstate = STATE_HEADER;
- fallthrough;
- case STATE_HEADER:
- mctp_serial_push_header(dev, c);
- break;
- case STATE_ESCAPE:
- c |= 0x20;
- fallthrough;
- case STATE_DATA:
- if (dev->rxstate != STATE_ESCAPE && c == BYTE_ESC) {
- dev->rxstate = STATE_ESCAPE;
- } else {
- dev->rxfcs = crc_ccitt_byte(dev->rxfcs, c);
- dev->rxbuf[dev->rxpos] = c;
- dev->rxpos++;
- dev->rxstate = STATE_DATA;
- if (dev->rxpos == dev->rxlen) {
- dev->rxpos = 0;
- dev->rxstate = STATE_TRAILER;
- }
- }
- break;
- case STATE_TRAILER:
- mctp_serial_push_trailer(dev, c);
- break;
- case STATE_ERR:
- if (c == BYTE_FRAME)
- dev->rxstate = STATE_IDLE;
- break;
- default:
- netdev_err_once(dev->netdev, "invalid rx state %d\n",
- dev->rxstate);
- }
- }
- static void mctp_serial_tty_receive_buf(struct tty_struct *tty,
- const unsigned char *c,
- const char *f, int len)
- {
- struct mctp_serial *dev = tty->disc_data;
- int i;
- if (!netif_running(dev->netdev))
- return;
- /* we don't (currently) use the flag bytes, just data. */
- for (i = 0; i < len; i++)
- mctp_serial_push(dev, c[i]);
- }
- static void mctp_serial_uninit(struct net_device *ndev)
- {
- struct mctp_serial *dev = netdev_priv(ndev);
- cancel_work_sync(&dev->tx_work);
- }
- static const struct net_device_ops mctp_serial_netdev_ops = {
- .ndo_start_xmit = mctp_serial_tx,
- .ndo_uninit = mctp_serial_uninit,
- };
- static void mctp_serial_setup(struct net_device *ndev)
- {
- ndev->type = ARPHRD_MCTP;
- /* we limit at the fixed MTU, which is also the MCTP-standard
- * baseline MTU, so is also our minimum
- */
- ndev->mtu = MCTP_SERIAL_MTU;
- ndev->max_mtu = MCTP_SERIAL_MTU;
- ndev->min_mtu = MCTP_SERIAL_MTU;
- ndev->hard_header_len = 0;
- ndev->addr_len = 0;
- ndev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
- ndev->flags = IFF_NOARP;
- ndev->netdev_ops = &mctp_serial_netdev_ops;
- ndev->needs_free_netdev = true;
- }
- static int mctp_serial_open(struct tty_struct *tty)
- {
- struct mctp_serial *dev;
- struct net_device *ndev;
- char name[32];
- int idx, rc;
- if (!capable(CAP_NET_ADMIN))
- return -EPERM;
- if (!tty->ops->write)
- return -EOPNOTSUPP;
- idx = ida_alloc(&mctp_serial_ida, GFP_KERNEL);
- if (idx < 0)
- return idx;
- snprintf(name, sizeof(name), "mctpserial%d", idx);
- ndev = alloc_netdev(sizeof(*dev), name, NET_NAME_ENUM,
- mctp_serial_setup);
- if (!ndev) {
- rc = -ENOMEM;
- goto free_ida;
- }
- dev = netdev_priv(ndev);
- dev->idx = idx;
- dev->tty = tty;
- dev->netdev = ndev;
- dev->txstate = STATE_IDLE;
- dev->rxstate = STATE_IDLE;
- spin_lock_init(&dev->lock);
- INIT_WORK(&dev->tx_work, mctp_serial_tx_work);
- rc = register_netdev(ndev);
- if (rc)
- goto free_netdev;
- tty->receive_room = 64 * 1024;
- tty->disc_data = dev;
- return 0;
- free_netdev:
- free_netdev(ndev);
- free_ida:
- ida_free(&mctp_serial_ida, idx);
- return rc;
- }
- static void mctp_serial_close(struct tty_struct *tty)
- {
- struct mctp_serial *dev = tty->disc_data;
- int idx = dev->idx;
- unregister_netdev(dev->netdev);
- ida_free(&mctp_serial_ida, idx);
- }
- static struct tty_ldisc_ops mctp_ldisc = {
- .owner = THIS_MODULE,
- .num = N_MCTP,
- .name = "mctp",
- .open = mctp_serial_open,
- .close = mctp_serial_close,
- .receive_buf = mctp_serial_tty_receive_buf,
- .write_wakeup = mctp_serial_tty_write_wakeup,
- };
- static int __init mctp_serial_init(void)
- {
- return tty_register_ldisc(&mctp_ldisc);
- }
- static void __exit mctp_serial_exit(void)
- {
- tty_unregister_ldisc(&mctp_ldisc);
- }
- module_init(mctp_serial_init);
- module_exit(mctp_serial_exit);
- MODULE_LICENSE("GPL v2");
- MODULE_AUTHOR("Jeremy Kerr <[email protected]>");
- MODULE_DESCRIPTION("MCTP Serial transport");
|