mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	This makes all those 'char's an explicit 'u8'. This is part of the continuing unification of chars and flags to be consistent u8. This approaches tty_port_default_receive_buf(). Note that we do not change signedness as we compile with -funsigned-char. Signed-off-by: "Jiri Slaby (SUSE)" <jirislaby@kernel.org> Cc: William Hubbs <w.d.hubbs@gmail.com> Cc: Chris Brannon <chris@the-brannons.com> Cc: Kirk Reiser <kirk@reisers.ca> Cc: Samuel Thibault <samuel.thibault@ens-lyon.org> Cc: Marcel Holtmann <marcel@holtmann.org> Cc: Johan Hedberg <johan.hedberg@gmail.com> Cc: Luiz Augusto von Dentz <luiz.dentz@gmail.com> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com> Cc: Arnd Bergmann <arnd@arndb.de> Cc: "David S. Miller" <davem@davemloft.net> Cc: Eric Dumazet <edumazet@google.com> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Paolo Abeni <pabeni@redhat.com> Cc: Max Staudt <max@enpas.org> Cc: Wolfgang Grandegger <wg@grandegger.com> Cc: Marc Kleine-Budde <mkl@pengutronix.de> Cc: Dario Binacchi <dario.binacchi@amarulasolutions.com> Cc: Andreas Koensgen <ajk@comnets.uni-bremen.de> Cc: Jeremy Kerr <jk@codeconstruct.com.au> Cc: Matt Johnston <matt@codeconstruct.com.au> Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> Cc: Liam Girdwood <lgirdwood@gmail.com> Cc: Mark Brown <broonie@kernel.org> Cc: Jaroslav Kysela <perex@perex.cz> Cc: Takashi Iwai <tiwai@suse.com> Acked-by: Mark Brown <broonie@kernel.org> Link: https://lore.kernel.org/r/20230810091510.13006-18-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
		
			
				
	
	
		
			523 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			523 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 u8 *c,
 | 
						|
					const u8 *f, size_t 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");
 |