Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/mctp/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 13 kB image not shown  

Quelle  mctp-serial.c   Sprache: 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/mctpdevice.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;
 u8   txbuf[BUFSIZE],
    rxbuf[BUFSIZE];
};

static bool needs_escape(u8 c)
{
 return c == BYTE_ESC || c == BYTE_FRAME;
}

static unsigned int next_chunk_len(struct mctp_serial *dev)
{
 unsigned 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 < dev->txlen; i++) {
  if (needs_escape(dev->txbuf[dev->txpos + i]))
   break;
 }

 return i;
}

static ssize_t write_chunk(struct mctp_serial *dev, u8 *buf, size_t 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 long flags;
 ssize_t txlen;
 unsigned int len;
 u8 c, buf[3];

 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, u8 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, u8 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, u8 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 u8 *c,
     const u8 *f, size_t len)
{
 struct mctp_serial *dev = tty->disc_data;
 size_t 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 = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_SERIAL);
 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;

 mctp_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 ");
MODULE_DESCRIPTION("MCTP Serial transport");

#if IS_ENABLED(CONFIG_MCTP_SERIAL_TEST)
#include <kunit/test.h>

#define MAX_CHUNKS 6
struct test_chunk_tx {
 u8 input_len;
 u8 input[MCTP_SERIAL_MTU];
 u8 chunks[MAX_CHUNKS];
};

static void test_next_chunk_len(struct kunit *test)
{
 struct mctp_serial devx;
 struct mctp_serial *dev = &devx;
 int next;

 const struct test_chunk_tx *params = test->param_value;

 memset(dev, 0x0, sizeof(*dev));
 memcpy(dev->txbuf, params->input, params->input_len);
 dev->txlen = params->input_len;

 for (size_t i = 0; i < MAX_CHUNKS; i++) {
  next = next_chunk_len(dev);
  dev->txpos += next;
  KUNIT_EXPECT_EQ(test, next, params->chunks[i]);

  if (next == 0) {
   KUNIT_EXPECT_EQ(test, dev->txpos, dev->txlen);
   return;
  }
 }

 KUNIT_FAIL_AND_ABORT(test, "Ran out of chunks");
}

static struct test_chunk_tx chunk_tx_tests[] = {
 {
  .input_len = 5,
  .input = { 0x00, 0x11, 0x22, 0x7e, 0x80 },
  .chunks = { 3, 1, 1, 0},
 },
 {
  .input_len = 5,
  .input = { 0x00, 0x11, 0x22, 0x7e, 0x7d },
  .chunks = { 3, 1, 1, 0},
 },
 {
  .input_len = 3,
  .input = { 0x7e, 0x11, 0x22, },
  .chunks = { 1, 2, 0},
 },
 {
  .input_len = 3,
  .input = { 0x7e, 0x7e, 0x7d, },
  .chunks = { 1, 1, 1, 0},
 },
 {
  .input_len = 4,
  .input = { 0x7e, 0x7e, 0x00, 0x7d, },
  .chunks = { 1, 1, 1, 1, 0},
 },
 {
  .input_len = 6,
  .input = { 0x7e, 0x7e, 0x00, 0x7d, 0x10, 0x10},
  .chunks = { 1, 1, 1, 1, 2, 0},
 },
 {
  .input_len = 1,
  .input = { 0x7e },
  .chunks = { 1, 0 },
 },
 {
  .input_len = 1,
  .input = { 0x80 },
  .chunks = { 1, 0 },
 },
 {
  .input_len = 3,
  .input = { 0x80, 0x80, 0x00 },
  .chunks = { 3, 0 },
 },
 {
  .input_len = 7,
  .input = { 0x01, 0x00, 0x08, 0xc8, 0x00, 0x80, 0x02 },
  .chunks = { 7, 0 },
 },
 {
  .input_len = 7,
  .input = { 0x01, 0x00, 0x08, 0xc8, 0x7e, 0x80, 0x02 },
  .chunks = { 4, 1, 2, 0 },
 },
};

KUNIT_ARRAY_PARAM(chunk_tx, chunk_tx_tests, NULL);

static struct kunit_case mctp_serial_test_cases[] = {
 KUNIT_CASE_PARAM(test_next_chunk_len, chunk_tx_gen_params),
};

static struct kunit_suite mctp_serial_test_suite = {
 .name = "mctp_serial",
 .test_cases = mctp_serial_test_cases,
};

kunit_test_suite(mctp_serial_test_suite);

#endif /* CONFIG_MCTP_SERIAL_TEST */

Messung V0.5
C=95 H=95 G=94

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.