linux-stable/drivers/net/mctp/mctp-serial.c
Jeremy Kerr cccf14c07e mctp: serial: Fix starting value for frame check sequence
[ Upstream commit 2856a62762 ]

RFC1662 defines the start state for the crc16 FCS to be 0xffff, but
we're currently starting at zero.

This change uses the correct start state. We're only early in the
adoption for the serial binding, so there aren't yet any other users to
interface to.

Fixes: a0c2ccd9b5 ("mctp: Add MCTP-over-serial transport binding")
Reported-by: Harsh Tyagi <harshtya@google.com>
Tested-by: Harsh Tyagi <harshtya@google.com>
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
Reviewed-by: Alexander Duyck <alexanderduyck@fb.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Sasha Levin <sashal@kernel.org>
2022-12-31 13:26:39 +01:00

524 lines
11 KiB
C

// 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 <jk@codeconstruct.com.au>");
MODULE_DESCRIPTION("MCTP Serial transport");