root/drivers/net/mctp/mctp-i3c.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Implements DMTF specification
 * "DSP0233 Management Component Transport Protocol (MCTP) I3C Transport
 * Binding"
 * https://www.dmtf.org/sites/default/files/standards/documents/DSP0233_1.0.0.pdf
 *
 * Copyright (c) 2023 Code Construct
 */

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/i3c/device.h>
#include <linux/i3c/master.h>
#include <linux/if_arp.h>
#include <linux/unaligned.h>
#include <net/mctp.h>
#include <net/mctpdevice.h>

#define MCTP_I3C_MAXBUF 65536
/* 48 bit Provisioned Id */
#define PID_SIZE 6

/* 64 byte payload, 4 byte MCTP header */
static const int MCTP_I3C_MINMTU = 64 + 4;
/* One byte less to allow for the PEC */
static const int MCTP_I3C_MAXMTU = MCTP_I3C_MAXBUF - 1;
/* 4 byte MCTP header, no data, 1 byte PEC */
static const int MCTP_I3C_MINLEN = 4 + 1;

/* Sufficient for 64kB at min mtu */
static const int MCTP_I3C_TX_QUEUE_LEN = 1100;

/* Somewhat arbitrary */
static const int MCTP_I3C_IBI_SLOTS = 8;

/* Mandatory Data Byte in an IBI, from DSP0233 */
#define I3C_MDB_MCTP 0xAE
/* From MIPI Device Characteristics Register (DCR) Assignments */
#define I3C_DCR_MCTP 0xCC

static const char *MCTP_I3C_OF_PROP = "mctp-controller";

/* List of mctp_i3c_busdev */
static LIST_HEAD(busdevs);
/* Protects busdevs, as well as mctp_i3c_bus.devs lists */
static DEFINE_MUTEX(busdevs_lock);

struct mctp_i3c_bus {
        struct net_device *ndev;

        struct task_struct *tx_thread;
        wait_queue_head_t tx_wq;
        /* tx_lock protects tx_skb and devs */
        spinlock_t tx_lock;
        /* Next skb to transmit */
        struct sk_buff *tx_skb;
        /* Scratch buffer for xmit */
        u8 tx_scratch[MCTP_I3C_MAXBUF];

        /* Element of busdevs */
        struct list_head list;

        /* Provisioned ID of our controller */
        u64 pid;

        struct i3c_bus *bus;
        /* Head of mctp_i3c_device.list. Protected by busdevs_lock */
        struct list_head devs;
};

struct mctp_i3c_device {
        struct i3c_device *i3c;
        struct mctp_i3c_bus *mbus;
        struct list_head list; /* Element of mctp_i3c_bus.devs */

        /* Held while tx_thread is using this device */
        struct mutex lock;

        /* Whether BCR indicates MDB is present in IBI */
        bool have_mdb;
        /* I3C dynamic address */
        u8 addr;
        /* Maximum read length */
        u16 mrl;
        /* Maximum write length */
        u16 mwl;
        /* Provisioned ID */
        u64 pid;
};

/* We synthesise a mac header using the Provisioned ID.
 * Used to pass dest to mctp_i3c_start_xmit.
 */
struct mctp_i3c_internal_hdr {
        u8 dest[PID_SIZE];
        u8 source[PID_SIZE];
} __packed;

static int mctp_i3c_read(struct mctp_i3c_device *mi)
{
        struct i3c_xfer xfer = { .rnw = 1, .len = mi->mrl };
        struct net_device_stats *stats = &mi->mbus->ndev->stats;
        struct mctp_i3c_internal_hdr *ihdr = NULL;
        struct sk_buff *skb = NULL;
        struct mctp_skb_cb *cb;
        int net_status, rc;
        u8 pec, addr;

        skb = netdev_alloc_skb(mi->mbus->ndev,
                               mi->mrl + sizeof(struct mctp_i3c_internal_hdr));
        if (!skb) {
                stats->rx_dropped++;
                rc = -ENOMEM;
                goto err;
        }

        skb->protocol = htons(ETH_P_MCTP);
        /* Create a header for internal use */
        skb_reset_mac_header(skb);
        ihdr = skb_put(skb, sizeof(struct mctp_i3c_internal_hdr));
        put_unaligned_be48(mi->pid, ihdr->source);
        put_unaligned_be48(mi->mbus->pid, ihdr->dest);
        skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr));

        xfer.data.in = skb_put(skb, mi->mrl);

        /* Make sure netif_rx() is read in the same order as i3c. */
        mutex_lock(&mi->lock);
        rc = i3c_device_do_xfers(mi->i3c, &xfer, 1, I3C_SDR);
        if (rc < 0)
                goto err;

        if (WARN_ON_ONCE(xfer.len > mi->mrl)) {
                /* Bad i3c bus driver */
                rc = -EIO;
                goto err;
        }
        if (xfer.len < MCTP_I3C_MINLEN) {
                stats->rx_length_errors++;
                rc = -EIO;
                goto err;
        }

        /* check PEC, including address byte */
        addr = mi->addr << 1 | 1;
        pec = i2c_smbus_pec(0, &addr, 1);
        pec = i2c_smbus_pec(pec, xfer.data.in, xfer.len - 1);
        if (pec != ((u8 *)xfer.data.in)[xfer.len - 1]) {
                stats->rx_crc_errors++;
                rc = -EINVAL;
                goto err;
        }

        /* Remove PEC */
        skb_trim(skb, xfer.len - 1);

        cb = __mctp_cb(skb);
        cb->halen = PID_SIZE;
        put_unaligned_be48(mi->pid, cb->haddr);

        net_status = netif_rx(skb);

        if (net_status == NET_RX_SUCCESS) {
                stats->rx_packets++;
                stats->rx_bytes += xfer.len - 1;
        } else {
                stats->rx_dropped++;
        }

        mutex_unlock(&mi->lock);
        return 0;
err:
        mutex_unlock(&mi->lock);
        kfree_skb(skb);
        return rc;
}

static void mctp_i3c_ibi_handler(struct i3c_device *i3c,
                                 const struct i3c_ibi_payload *payload)
{
        struct mctp_i3c_device *mi = i3cdev_get_drvdata(i3c);

        if (WARN_ON_ONCE(!mi))
                return;

        if (mi->have_mdb) {
                if (payload->len > 0) {
                        if (((u8 *)payload->data)[0] != I3C_MDB_MCTP) {
                                /* Not a mctp-i3c interrupt, ignore it */
                                return;
                        }
                } else {
                        /* The BCR advertised a Mandatory Data Byte but the
                         * device didn't send one.
                         */
                        dev_warn_once(i3cdev_to_dev(i3c), "IBI with missing MDB");
                }
        }

        mctp_i3c_read(mi);
}

static int mctp_i3c_setup(struct mctp_i3c_device *mi)
{
        const struct i3c_ibi_setup ibi = {
                .max_payload_len = 1,
                .num_slots = MCTP_I3C_IBI_SLOTS,
                .handler = mctp_i3c_ibi_handler,
        };
        struct i3c_device_info info;
        int rc;

        i3c_device_get_info(mi->i3c, &info);
        mi->have_mdb = info.bcr & BIT(2);
        mi->addr = info.dyn_addr;
        mi->mwl = info.max_write_len;
        mi->mrl = info.max_read_len;
        mi->pid = info.pid;

        rc = i3c_device_request_ibi(mi->i3c, &ibi);
        if (rc == -ENOTSUPP) {
                /* This driver only supports In-Band Interrupt mode.
                 * Support for Polling Mode could be added if required.
                 * (ENOTSUPP is from the i3c layer, not EOPNOTSUPP).
                 */
                dev_warn(i3cdev_to_dev(mi->i3c),
                         "Failed, bus driver doesn't support In-Band Interrupts");
                goto err;
        } else if (rc < 0) {
                dev_err(i3cdev_to_dev(mi->i3c),
                        "Failed requesting IBI (%d)\n", rc);
                goto err;
        }

        rc = i3c_device_enable_ibi(mi->i3c);
        if (rc < 0) {
                /* Assume a driver supporting request_ibi also
                 * supports enable_ibi.
                 */
                dev_err(i3cdev_to_dev(mi->i3c), "Failed enabling IBI (%d)\n", rc);
                goto err_free_ibi;
        }

        return 0;

err_free_ibi:
        i3c_device_free_ibi(mi->i3c);

err:
        return rc;
}

/* Adds a new MCTP i3c_device to a bus */
static int mctp_i3c_add_device(struct mctp_i3c_bus *mbus,
                               struct i3c_device *i3c)
__must_hold(&busdevs_lock)
{
        struct mctp_i3c_device *mi = NULL;
        int rc;

        mi = kzalloc_obj(*mi);
        if (!mi) {
                rc = -ENOMEM;
                goto err;
        }
        mi->mbus = mbus;
        mi->i3c = i3c;
        mutex_init(&mi->lock);
        list_add(&mi->list, &mbus->devs);

        i3cdev_set_drvdata(i3c, mi);
        rc = mctp_i3c_setup(mi);
        if (rc < 0)
                goto err_free;

        return 0;

err_free:
        list_del(&mi->list);
        kfree(mi);

err:
        dev_warn(i3cdev_to_dev(i3c), "Error adding mctp-i3c device, %d\n", rc);
        return rc;
}

static int mctp_i3c_probe(struct i3c_device *i3c)
{
        struct mctp_i3c_bus *b = NULL, *mbus = NULL;

        /* Look for a known bus */
        mutex_lock(&busdevs_lock);
        list_for_each_entry(b, &busdevs, list)
                if (b->bus == i3c->bus) {
                        mbus = b;
                        break;
                }
        mutex_unlock(&busdevs_lock);

        if (!mbus) {
                /* probably no "mctp-controller" property on the i3c bus */
                return -ENODEV;
        }

        return mctp_i3c_add_device(mbus, i3c);
}

static void mctp_i3c_remove_device(struct mctp_i3c_device *mi)
__must_hold(&busdevs_lock)
{
        /* Ensure the tx thread isn't using the device */
        mutex_lock(&mi->lock);

        /* Counterpart of mctp_i3c_setup */
        i3c_device_disable_ibi(mi->i3c);
        i3c_device_free_ibi(mi->i3c);

        /* Counterpart of mctp_i3c_add_device */
        i3cdev_set_drvdata(mi->i3c, NULL);
        list_del(&mi->list);

        /* Safe to unlock after removing from the list */
        mutex_unlock(&mi->lock);
        kfree(mi);
}

static void mctp_i3c_remove(struct i3c_device *i3c)
{
        struct mctp_i3c_device *mi = i3cdev_get_drvdata(i3c);

        /* We my have received a Bus Remove notify prior to device remove,
         * so mi will already be removed.
         */
        if (!mi)
                return;

        mutex_lock(&busdevs_lock);
        mctp_i3c_remove_device(mi);
        mutex_unlock(&busdevs_lock);
}

/* Returns the device for an address, with mi->lock held */
static struct mctp_i3c_device *
mctp_i3c_lookup(struct mctp_i3c_bus *mbus, u64 pid)
{
        struct mctp_i3c_device *mi = NULL, *ret = NULL;

        mutex_lock(&busdevs_lock);
        list_for_each_entry(mi, &mbus->devs, list)
                if (mi->pid == pid) {
                        ret = mi;
                        mutex_lock(&mi->lock);
                        break;
                }
        mutex_unlock(&busdevs_lock);
        return ret;
}

static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb)
{
        struct net_device_stats *stats = &mbus->ndev->stats;
        struct i3c_xfer xfer = { .rnw = false };
        struct mctp_i3c_internal_hdr *ihdr = NULL;
        struct mctp_i3c_device *mi = NULL;
        unsigned int data_len;
        u8 *data = NULL;
        u8 addr, pec;
        int rc = 0;
        u64 pid;

        skb_pull(skb, sizeof(struct mctp_i3c_internal_hdr));
        data_len = skb->len;

        ihdr = (void *)skb_mac_header(skb);

        pid = get_unaligned_be48(ihdr->dest);
        mi = mctp_i3c_lookup(mbus, pid);
        if (!mi) {
                /* I3C endpoint went away after the packet was enqueued? */
                stats->tx_dropped++;
                goto out;
        }

        if (WARN_ON_ONCE(data_len + 1 > MCTP_I3C_MAXBUF))
                goto out;

        if (data_len + 1 > (unsigned int)mi->mwl) {
                /* Route MTU was larger than supported by the endpoint */
                stats->tx_dropped++;
                goto out;
        }

        /* Need a linear buffer with space for the PEC */
        xfer.len = data_len + 1;
        if (skb_tailroom(skb) >= 1) {
                skb_put(skb, 1);
                data = skb->data;
        } else {
                /* Otherwise need to copy the buffer */
                skb_copy_bits(skb, 0, mbus->tx_scratch, skb->len);
                data = mbus->tx_scratch;
        }

        /* PEC calculation */
        addr = mi->addr << 1;
        pec = i2c_smbus_pec(0, &addr, 1);
        pec = i2c_smbus_pec(pec, data, data_len);
        data[data_len] = pec;

        xfer.data.out = data;
        rc = i3c_device_do_xfers(mi->i3c, &xfer, 1, I3C_SDR);
        if (rc == 0) {
                stats->tx_bytes += data_len;
                stats->tx_packets++;
        } else {
                stats->tx_errors++;
        }

out:
        if (mi)
                mutex_unlock(&mi->lock);
}

static int mctp_i3c_tx_thread(void *data)
{
        struct mctp_i3c_bus *mbus = data;
        struct sk_buff *skb;

        for (;;) {
                if (kthread_should_stop())
                        break;

                spin_lock_bh(&mbus->tx_lock);
                skb = mbus->tx_skb;
                mbus->tx_skb = NULL;
                spin_unlock_bh(&mbus->tx_lock);

                if (netif_queue_stopped(mbus->ndev))
                        netif_wake_queue(mbus->ndev);

                if (skb) {
                        mctp_i3c_xmit(mbus, skb);
                        kfree_skb(skb);
                } else {
                        wait_event_idle(mbus->tx_wq,
                                        mbus->tx_skb || kthread_should_stop());
                }
        }

        return 0;
}

static netdev_tx_t mctp_i3c_start_xmit(struct sk_buff *skb,
                                       struct net_device *ndev)
{
        struct mctp_i3c_bus *mbus = netdev_priv(ndev);
        netdev_tx_t ret;

        spin_lock(&mbus->tx_lock);
        netif_stop_queue(ndev);
        if (mbus->tx_skb) {
                dev_warn_ratelimited(&ndev->dev, "TX with queue stopped");
                ret = NETDEV_TX_BUSY;
        } else {
                mbus->tx_skb = skb;
                ret = NETDEV_TX_OK;
        }
        spin_unlock(&mbus->tx_lock);

        if (ret == NETDEV_TX_OK)
                wake_up(&mbus->tx_wq);

        return ret;
}

static void mctp_i3c_bus_free(struct mctp_i3c_bus *mbus)
__must_hold(&busdevs_lock)
{
        struct mctp_i3c_device *mi = NULL, *tmp = NULL;

        if (mbus->tx_thread) {
                kthread_stop(mbus->tx_thread);
                mbus->tx_thread = NULL;
        }

        /* Remove any child devices */
        list_for_each_entry_safe(mi, tmp, &mbus->devs, list) {
                mctp_i3c_remove_device(mi);
        }

        kfree_skb(mbus->tx_skb);
        list_del(&mbus->list);
}

static void mctp_i3c_ndo_uninit(struct net_device *ndev)
{
        struct mctp_i3c_bus *mbus = netdev_priv(ndev);

        /* Perform cleanup here to ensure there are no remaining references */
        mctp_i3c_bus_free(mbus);
}

static int mctp_i3c_header_create(struct sk_buff *skb, struct net_device *dev,
                                  unsigned short type, const void *daddr,
           const void *saddr, unsigned int len)
{
        struct mctp_i3c_internal_hdr *ihdr;
        int rc;

        if (!daddr || !saddr)
                return -EINVAL;

        rc = skb_cow_head(skb, sizeof(struct mctp_i3c_internal_hdr));
        if (rc)
                return rc;

        skb_push(skb, sizeof(struct mctp_i3c_internal_hdr));
        skb_reset_mac_header(skb);
        ihdr = (void *)skb_mac_header(skb);
        memcpy(ihdr->dest, daddr, PID_SIZE);
        memcpy(ihdr->source, saddr, PID_SIZE);
        return 0;
}

static const struct net_device_ops mctp_i3c_ops = {
        .ndo_start_xmit = mctp_i3c_start_xmit,
        .ndo_uninit = mctp_i3c_ndo_uninit,
};

static const struct header_ops mctp_i3c_headops = {
        .create = mctp_i3c_header_create,
};

static void mctp_i3c_net_setup(struct net_device *dev)
{
        dev->type = ARPHRD_MCTP;

        dev->mtu = MCTP_I3C_MAXMTU;
        dev->min_mtu = MCTP_I3C_MINMTU;
        dev->max_mtu = MCTP_I3C_MAXMTU;
        dev->tx_queue_len = MCTP_I3C_TX_QUEUE_LEN;

        dev->hard_header_len = sizeof(struct mctp_i3c_internal_hdr);
        dev->addr_len = PID_SIZE;

        dev->netdev_ops = &mctp_i3c_ops;
        dev->header_ops = &mctp_i3c_headops;
}

static bool mctp_i3c_is_mctp_controller(struct i3c_bus *bus)
{
        struct i3c_dev_desc *master = bus->cur_master;

        if (!master)
                return false;

        return of_property_read_bool(master->common.master->dev.of_node,
                                     MCTP_I3C_OF_PROP);
}

/* Returns the Provisioned Id of a local bus master */
static int mctp_i3c_bus_local_pid(struct i3c_bus *bus, u64 *ret_pid)
{
        struct i3c_dev_desc *master;

        master = bus->cur_master;
        if (WARN_ON_ONCE(!master))
                return -ENOENT;
        *ret_pid = master->info.pid;

        return 0;
}

/* Returns an ERR_PTR on failure */
static struct mctp_i3c_bus *mctp_i3c_bus_add(struct i3c_bus *bus)
__must_hold(&busdevs_lock)
{
        struct mctp_i3c_bus *mbus = NULL;
        struct net_device *ndev = NULL;
        char namebuf[IFNAMSIZ];
        u8 addr[PID_SIZE];
        int rc;

        if (!mctp_i3c_is_mctp_controller(bus))
                return ERR_PTR(-ENOENT);

        snprintf(namebuf, sizeof(namebuf), "mctpi3c%d", bus->id);
        ndev = alloc_netdev(sizeof(*mbus), namebuf, NET_NAME_ENUM,
                            mctp_i3c_net_setup);
        if (!ndev) {
                rc = -ENOMEM;
                goto err;
        }

        mbus = netdev_priv(ndev);
        mbus->ndev = ndev;
        mbus->bus = bus;
        INIT_LIST_HEAD(&mbus->devs);
        list_add(&mbus->list, &busdevs);

        rc = mctp_i3c_bus_local_pid(bus, &mbus->pid);
        if (rc < 0) {
                dev_err(&ndev->dev, "No I3C PID available\n");
                goto err_free_uninit;
        }
        put_unaligned_be48(mbus->pid, addr);
        dev_addr_set(ndev, addr);

        init_waitqueue_head(&mbus->tx_wq);
        spin_lock_init(&mbus->tx_lock);
        mbus->tx_thread = kthread_run(mctp_i3c_tx_thread, mbus,
                                      "%s/tx", ndev->name);
        if (IS_ERR(mbus->tx_thread)) {
                dev_warn(&ndev->dev, "Error creating thread: %pe\n",
                         mbus->tx_thread);
                rc = PTR_ERR(mbus->tx_thread);
                mbus->tx_thread = NULL;
                goto err_free_uninit;
        }

        rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_I3C);
        if (rc < 0) {
                dev_warn(&ndev->dev, "netdev register failed: %d\n", rc);
                goto err_free_netdev;
        }
        return mbus;

err_free_uninit:
        /* uninit will not get called if a netdev has not been registered,
         * so we perform the same mbus cleanup manually.
         */
        mctp_i3c_bus_free(mbus);

err_free_netdev:
        free_netdev(ndev);

err:
        return ERR_PTR(rc);
}

static void mctp_i3c_bus_remove(struct mctp_i3c_bus *mbus)
__must_hold(&busdevs_lock)
{
        /* Unregister calls through to ndo_uninit -> mctp_i3c_bus_free() */
        mctp_unregister_netdev(mbus->ndev);

        free_netdev(mbus->ndev);
        /* mbus is deallocated */
}

/* Removes all mctp-i3c busses */
static void mctp_i3c_bus_remove_all(void)
{
        struct mctp_i3c_bus *mbus = NULL, *tmp = NULL;

        mutex_lock(&busdevs_lock);
        list_for_each_entry_safe(mbus, tmp, &busdevs, list) {
                mctp_i3c_bus_remove(mbus);
        }
        mutex_unlock(&busdevs_lock);
}

/* Adds a i3c_bus if it isn't already in the busdevs list.
 * Suitable as an i3c_for_each_bus_locked callback.
 */
static int mctp_i3c_bus_add_new(struct i3c_bus *bus, void *data)
{
        struct mctp_i3c_bus *mbus = NULL, *tmp = NULL;
        bool exists = false;

        mutex_lock(&busdevs_lock);
        list_for_each_entry_safe(mbus, tmp, &busdevs, list)
                if (mbus->bus == bus)
                        exists = true;

        /* It is OK for a bus to already exist. That can occur due to
         * the race in mod_init between notifier and for_each_bus
         */
        if (!exists)
                mctp_i3c_bus_add(bus);
        mutex_unlock(&busdevs_lock);
        return 0;
}

static void mctp_i3c_notify_bus_remove(struct i3c_bus *bus)
{
        struct mctp_i3c_bus *mbus = NULL, *tmp;

        mutex_lock(&busdevs_lock);
        list_for_each_entry_safe(mbus, tmp, &busdevs, list)
                if (mbus->bus == bus)
                        mctp_i3c_bus_remove(mbus);
        mutex_unlock(&busdevs_lock);
}

static int mctp_i3c_notifier_call(struct notifier_block *nb,
                                  unsigned long action, void *data)
{
        switch (action) {
        case I3C_NOTIFY_BUS_ADD:
                mctp_i3c_bus_add_new((struct i3c_bus *)data, NULL);
                break;
        case I3C_NOTIFY_BUS_REMOVE:
                mctp_i3c_notify_bus_remove((struct i3c_bus *)data);
                break;
        }
        return NOTIFY_DONE;
}

static struct notifier_block mctp_i3c_notifier = {
        .notifier_call = mctp_i3c_notifier_call,
};

static const struct i3c_device_id mctp_i3c_ids[] = {
        I3C_CLASS(I3C_DCR_MCTP, NULL),
        { 0 },
};

static struct i3c_driver mctp_i3c_driver = {
        .driver = {
                .name = "mctp-i3c",
        },
        .probe = mctp_i3c_probe,
        .remove = mctp_i3c_remove,
        .id_table = mctp_i3c_ids,
};

static __init int mctp_i3c_mod_init(void)
{
        int rc;

        rc = i3c_register_notifier(&mctp_i3c_notifier);
        if (rc < 0) {
                i3c_driver_unregister(&mctp_i3c_driver);
                return rc;
        }

        i3c_for_each_bus_locked(mctp_i3c_bus_add_new, NULL);

        rc = i3c_driver_register(&mctp_i3c_driver);
        if (rc < 0)
                return rc;

        return 0;
}

static __exit void mctp_i3c_mod_exit(void)
{
        int rc;

        i3c_driver_unregister(&mctp_i3c_driver);

        rc = i3c_unregister_notifier(&mctp_i3c_notifier);
        if (rc < 0)
                pr_warn("MCTP I3C could not unregister notifier, %d\n", rc);

        mctp_i3c_bus_remove_all();
}

module_init(mctp_i3c_mod_init);
module_exit(mctp_i3c_mod_exit);

MODULE_DEVICE_TABLE(i3c, mctp_i3c_ids);
MODULE_DESCRIPTION("MCTP I3C device");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Matt Johnston <matt@codeconstruct.com.au>");