Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Matt Johnston | 3145 | 100.00% | 1 | 100.00% |
Total | 3145 | 1 |
// 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 <asm/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_priv_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); rc = i3c_device_do_priv_xfers(mi->i3c, &xfer, 1); 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++; } return 0; err: 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(sizeof(*mi), GFP_KERNEL); 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_priv_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_priv_xfers(mi->i3c, &xfer, 1); 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; 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); 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>");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1