2021-11-23 15:50:46 +08:00
// 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
2022-12-16 11:44:09 +08:00
# define FCS_INIT 0xffff
2021-11-23 15:50:46 +08:00
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 )
2022-12-16 11:44:09 +08:00
dev - > txfcs = crc_ccitt ( FCS_INIT , buf + 1 , 2 ) ;
2021-11-23 15:50:46 +08:00
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 ;
2022-03-05 23:12:47 +01:00
netif_rx ( skb ) ;
2021-11-23 15:50:46 +08:00
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 + + ;
2022-12-16 11:44:09 +08:00
dev - > rxfcs = crc_ccitt_byte ( FCS_INIT , c ) ;
2021-11-23 15:50:46 +08:00
} 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 ] ) ;
}
2022-02-11 09:15:52 +08:00
static void mctp_serial_uninit ( struct net_device * ndev )
{
struct mctp_serial * dev = netdev_priv ( ndev ) ;
cancel_work_sync ( & dev - > tx_work ) ;
}
2021-11-23 15:50:46 +08:00
static const struct net_device_ops mctp_serial_netdev_ops = {
. ndo_start_xmit = mctp_serial_tx ,
2022-02-11 09:15:52 +08:00
. ndo_uninit = mctp_serial_uninit ,
2021-11-23 15:50:46 +08:00
} ;
static void mctp_serial_setup ( struct net_device * ndev )
{
ndev - > type = ARPHRD_MCTP ;
2021-11-25 14:07:38 +08:00
/* we limit at the fixed MTU, which is also the MCTP-standard
* baseline MTU , so is also our minimum
*/
2021-11-23 15:50:46 +08:00
ndev - > mtu = MCTP_SERIAL_MTU ;
2021-11-25 14:07:38 +08:00
ndev - > max_mtu = MCTP_SERIAL_MTU ;
ndev - > min_mtu = MCTP_SERIAL_MTU ;
2021-11-23 15:50:46 +08:00
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 " ) ;