| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Management Controller Transport Protocol (MCTP) |
| * Implements DMTF specification |
| * "DSP0237 Management Component Transport Protocol (MCTP) SMBus/I2C |
| * Transport Binding" |
| * https://www.dmtf.org/sites/default/files/standards/documents/DSP0237_1.2.0.pdf |
| * |
| * A netdev is created for each I2C bus that handles MCTP. In the case of an I2C |
| * mux topology a single I2C client is attached to the root of the mux topology, |
| * shared between all mux I2C busses underneath. For non-mux cases an I2C client |
| * is attached per netdev. |
| * |
| * mctp-i2c-controller.yml devicetree binding has further details. |
| * |
| * Copyright (c) 2022 Code Construct |
| * Copyright (c) 2022 Google |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/netdevice.h> |
| #include <linux/i2c.h> |
| #include <linux/i2c-mux.h> |
| #include <linux/if_arp.h> |
| #include <net/mctp.h> |
| #include <net/mctpdevice.h> |
| |
| /* byte_count is limited to u8 */ |
| #define MCTP_I2C_MAXBLOCK 255 |
| /* One byte is taken by source_slave */ |
| #define MCTP_I2C_MAXMTU (MCTP_I2C_MAXBLOCK - 1) |
| #define MCTP_I2C_MINMTU (64 + 4) |
| /* Allow space for dest_address, command, byte_count, data, PEC */ |
| #define MCTP_I2C_BUFSZ (3 + MCTP_I2C_MAXBLOCK + 1) |
| #define MCTP_I2C_MINLEN 8 |
| #define MCTP_I2C_COMMANDCODE 0x0f |
| #define MCTP_I2C_TX_WORK_LEN 100 |
| /* Sufficient for 64kB at min mtu */ |
| #define MCTP_I2C_TX_QUEUE_LEN 1100 |
| |
| #define MCTP_I2C_OF_PROP "mctp-controller" |
| |
| enum { |
| MCTP_I2C_FLOW_STATE_NEW = 0, |
| MCTP_I2C_FLOW_STATE_ACTIVE, |
| MCTP_I2C_FLOW_STATE_INVALID, |
| }; |
| |
| /* List of all struct mctp_i2c_client |
| * Lock protects driver_clients and also prevents adding/removing adapters |
| * during mctp_i2c_client probe/remove. |
| */ |
| static DEFINE_MUTEX(driver_clients_lock); |
| static LIST_HEAD(driver_clients); |
| |
| struct mctp_i2c_client; |
| |
| /* The netdev structure. One of these per I2C adapter. */ |
| struct mctp_i2c_dev { |
| struct net_device *ndev; |
| struct i2c_adapter *adapter; |
| struct mctp_i2c_client *client; |
| struct list_head list; /* For mctp_i2c_client.devs */ |
| |
| size_t rx_pos; |
| u8 rx_buffer[MCTP_I2C_BUFSZ]; |
| struct completion rx_done; |
| |
| struct task_struct *tx_thread; |
| wait_queue_head_t tx_wq; |
| struct sk_buff_head tx_queue; |
| u8 tx_scratch[MCTP_I2C_BUFSZ]; |
| |
| /* A fake entry in our tx queue to perform an unlock operation */ |
| struct sk_buff unlock_marker; |
| |
| /* Spinlock protects i2c_lock_count, release_count, allow_rx */ |
| spinlock_t lock; |
| int i2c_lock_count; |
| int release_count; |
| /* Indicates that the netif is ready to receive incoming packets */ |
| bool allow_rx; |
| |
| }; |
| |
| /* The i2c client structure. One per hardware i2c bus at the top of the |
| * mux tree, shared by multiple netdevs |
| */ |
| struct mctp_i2c_client { |
| struct i2c_client *client; |
| u8 lladdr; |
| |
| struct mctp_i2c_dev *sel; |
| struct list_head devs; |
| spinlock_t sel_lock; /* Protects sel and devs */ |
| |
| struct list_head list; /* For driver_clients */ |
| }; |
| |
| /* Header on the wire. */ |
| struct mctp_i2c_hdr { |
| u8 dest_slave; |
| u8 command; |
| /* Count of bytes following byte_count, excluding PEC */ |
| u8 byte_count; |
| u8 source_slave; |
| }; |
| |
| static int mctp_i2c_recv(struct mctp_i2c_dev *midev); |
| static int mctp_i2c_slave_cb(struct i2c_client *client, |
| enum i2c_slave_event event, u8 *val); |
| static void mctp_i2c_ndo_uninit(struct net_device *dev); |
| static int mctp_i2c_ndo_open(struct net_device *dev); |
| |
| static struct i2c_adapter *mux_root_adapter(struct i2c_adapter *adap) |
| { |
| #if IS_ENABLED(CONFIG_I2C_MUX) |
| return i2c_root_adapter(&adap->dev); |
| #else |
| /* In non-mux config all i2c adapters are root adapters */ |
| return adap; |
| #endif |
| } |
| |
| /* Creates a new i2c slave device attached to the root adapter. |
| * Sets up the slave callback. |
| * Must be called with a client on a root adapter. |
| */ |
| static struct mctp_i2c_client *mctp_i2c_new_client(struct i2c_client *client) |
| { |
| struct mctp_i2c_client *mcli = NULL; |
| struct i2c_adapter *root = NULL; |
| int rc; |
| |
| if (client->flags & I2C_CLIENT_TEN) { |
| dev_err(&client->dev, "failed, MCTP requires a 7-bit I2C address, addr=0x%x\n", |
| client->addr); |
| rc = -EINVAL; |
| goto err; |
| } |
| |
| root = mux_root_adapter(client->adapter); |
| if (!root) { |
| dev_err(&client->dev, "failed to find root adapter\n"); |
| rc = -ENOENT; |
| goto err; |
| } |
| if (root != client->adapter) { |
| dev_err(&client->dev, |
| "A mctp-i2c-controller client cannot be placed on an I2C mux adapter.\n" |
| " It should be placed on the mux tree root adapter\n" |
| " then set mctp-controller property on adapters to attach\n"); |
| rc = -EINVAL; |
| goto err; |
| } |
| |
| mcli = kzalloc(sizeof(*mcli), GFP_KERNEL); |
| if (!mcli) { |
| rc = -ENOMEM; |
| goto err; |
| } |
| spin_lock_init(&mcli->sel_lock); |
| INIT_LIST_HEAD(&mcli->devs); |
| INIT_LIST_HEAD(&mcli->list); |
| mcli->lladdr = client->addr & 0xff; |
| mcli->client = client; |
| i2c_set_clientdata(client, mcli); |
| |
| rc = i2c_slave_register(mcli->client, mctp_i2c_slave_cb); |
| if (rc < 0) { |
| dev_err(&client->dev, "i2c register failed %d\n", rc); |
| mcli->client = NULL; |
| i2c_set_clientdata(client, NULL); |
| goto err; |
| } |
| |
| return mcli; |
| err: |
| if (mcli) { |
| if (mcli->client) |
| i2c_unregister_device(mcli->client); |
| kfree(mcli); |
| } |
| return ERR_PTR(rc); |
| } |
| |
| static void mctp_i2c_free_client(struct mctp_i2c_client *mcli) |
| { |
| int rc; |
| |
| WARN_ON(!mutex_is_locked(&driver_clients_lock)); |
| WARN_ON(!list_empty(&mcli->devs)); |
| WARN_ON(mcli->sel); /* sanity check, no locking */ |
| |
| rc = i2c_slave_unregister(mcli->client); |
| /* Leak if it fails, we can't propagate errors upwards */ |
| if (rc < 0) |
| dev_err(&mcli->client->dev, "i2c unregister failed %d\n", rc); |
| else |
| kfree(mcli); |
| } |
| |
| /* Switch the mctp i2c device to receive responses. |
| * Call with sel_lock held |
| */ |
| static void __mctp_i2c_device_select(struct mctp_i2c_client *mcli, |
| struct mctp_i2c_dev *midev) |
| { |
| assert_spin_locked(&mcli->sel_lock); |
| if (midev) |
| dev_hold(midev->ndev); |
| if (mcli->sel) |
| dev_put(mcli->sel->ndev); |
| mcli->sel = midev; |
| } |
| |
| /* Switch the mctp i2c device to receive responses */ |
| static void mctp_i2c_device_select(struct mctp_i2c_client *mcli, |
| struct mctp_i2c_dev *midev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&mcli->sel_lock, flags); |
| __mctp_i2c_device_select(mcli, midev); |
| spin_unlock_irqrestore(&mcli->sel_lock, flags); |
| } |
| |
| static int mctp_i2c_slave_cb(struct i2c_client *client, |
| enum i2c_slave_event event, u8 *val) |
| { |
| struct mctp_i2c_client *mcli = i2c_get_clientdata(client); |
| struct mctp_i2c_dev *midev = NULL; |
| unsigned long flags; |
| int rc = 0; |
| |
| spin_lock_irqsave(&mcli->sel_lock, flags); |
| midev = mcli->sel; |
| if (midev) |
| dev_hold(midev->ndev); |
| spin_unlock_irqrestore(&mcli->sel_lock, flags); |
| |
| if (!midev) |
| return 0; |
| |
| switch (event) { |
| case I2C_SLAVE_WRITE_RECEIVED: |
| if (midev->rx_pos < MCTP_I2C_BUFSZ) { |
| midev->rx_buffer[midev->rx_pos] = *val; |
| midev->rx_pos++; |
| } else { |
| midev->ndev->stats.rx_over_errors++; |
| } |
| |
| break; |
| case I2C_SLAVE_WRITE_REQUESTED: |
| /* dest_slave as first byte */ |
| midev->rx_buffer[0] = mcli->lladdr << 1; |
| midev->rx_pos = 1; |
| break; |
| case I2C_SLAVE_STOP: |
| rc = mctp_i2c_recv(midev); |
| break; |
| default: |
| break; |
| } |
| |
| dev_put(midev->ndev); |
| return rc; |
| } |
| |
| /* Processes incoming data that has been accumulated by the slave cb */ |
| static int mctp_i2c_recv(struct mctp_i2c_dev *midev) |
| { |
| struct net_device *ndev = midev->ndev; |
| struct mctp_i2c_hdr *hdr; |
| struct mctp_skb_cb *cb; |
| struct sk_buff *skb; |
| unsigned long flags; |
| u8 pec, calc_pec; |
| size_t recvlen; |
| int status; |
| |
| /* + 1 for the PEC */ |
| if (midev->rx_pos < MCTP_I2C_MINLEN + 1) { |
| ndev->stats.rx_length_errors++; |
| return -EINVAL; |
| } |
| /* recvlen excludes PEC */ |
| recvlen = midev->rx_pos - 1; |
| |
| hdr = (void *)midev->rx_buffer; |
| if (hdr->command != MCTP_I2C_COMMANDCODE) { |
| ndev->stats.rx_dropped++; |
| return -EINVAL; |
| } |
| |
| if (hdr->byte_count + offsetof(struct mctp_i2c_hdr, source_slave) != recvlen) { |
| ndev->stats.rx_length_errors++; |
| return -EINVAL; |
| } |
| |
| pec = midev->rx_buffer[midev->rx_pos - 1]; |
| calc_pec = i2c_smbus_pec(0, midev->rx_buffer, recvlen); |
| if (pec != calc_pec) { |
| ndev->stats.rx_crc_errors++; |
| return -EINVAL; |
| } |
| |
| skb = netdev_alloc_skb(ndev, recvlen); |
| if (!skb) { |
| ndev->stats.rx_dropped++; |
| return -ENOMEM; |
| } |
| |
| skb->protocol = htons(ETH_P_MCTP); |
| skb_put_data(skb, midev->rx_buffer, recvlen); |
| skb_reset_mac_header(skb); |
| skb_pull(skb, sizeof(struct mctp_i2c_hdr)); |
| skb_reset_network_header(skb); |
| |
| cb = __mctp_cb(skb); |
| cb->halen = 1; |
| cb->haddr[0] = hdr->source_slave >> 1; |
| |
| /* We need to ensure that the netif is not used once netdev |
| * unregister occurs |
| */ |
| spin_lock_irqsave(&midev->lock, flags); |
| if (midev->allow_rx) { |
| reinit_completion(&midev->rx_done); |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| status = netif_rx(skb); |
| complete(&midev->rx_done); |
| } else { |
| status = NET_RX_DROP; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| } |
| |
| if (status == NET_RX_SUCCESS) { |
| ndev->stats.rx_packets++; |
| ndev->stats.rx_bytes += recvlen; |
| } else { |
| ndev->stats.rx_dropped++; |
| } |
| return 0; |
| } |
| |
| enum mctp_i2c_flow_state { |
| MCTP_I2C_TX_FLOW_INVALID, |
| MCTP_I2C_TX_FLOW_NONE, |
| MCTP_I2C_TX_FLOW_NEW, |
| MCTP_I2C_TX_FLOW_EXISTING, |
| }; |
| |
| static enum mctp_i2c_flow_state |
| mctp_i2c_get_tx_flow_state(struct mctp_i2c_dev *midev, struct sk_buff *skb) |
| { |
| enum mctp_i2c_flow_state state; |
| struct mctp_sk_key *key; |
| struct mctp_flow *flow; |
| unsigned long flags; |
| |
| flow = skb_ext_find(skb, SKB_EXT_MCTP); |
| if (!flow) |
| return MCTP_I2C_TX_FLOW_NONE; |
| |
| key = flow->key; |
| if (!key) |
| return MCTP_I2C_TX_FLOW_NONE; |
| |
| spin_lock_irqsave(&key->lock, flags); |
| /* If the key is present but invalid, we're unlikely to be able |
| * to handle the flow at all; just drop now |
| */ |
| if (!key->valid) { |
| state = MCTP_I2C_TX_FLOW_INVALID; |
| } else { |
| switch (key->dev_flow_state) { |
| case MCTP_I2C_FLOW_STATE_NEW: |
| key->dev_flow_state = MCTP_I2C_FLOW_STATE_ACTIVE; |
| state = MCTP_I2C_TX_FLOW_NEW; |
| break; |
| case MCTP_I2C_FLOW_STATE_ACTIVE: |
| state = MCTP_I2C_TX_FLOW_EXISTING; |
| break; |
| default: |
| state = MCTP_I2C_TX_FLOW_INVALID; |
| } |
| } |
| |
| spin_unlock_irqrestore(&key->lock, flags); |
| |
| return state; |
| } |
| |
| /* We're not contending with ourselves here; we only need to exclude other |
| * i2c clients from using the bus. refcounts are simply to prevent |
| * recursive locking. |
| */ |
| static void mctp_i2c_lock_nest(struct mctp_i2c_dev *midev) |
| { |
| unsigned long flags; |
| bool lock; |
| |
| spin_lock_irqsave(&midev->lock, flags); |
| lock = midev->i2c_lock_count == 0; |
| midev->i2c_lock_count++; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| if (lock) |
| i2c_lock_bus(midev->adapter, I2C_LOCK_SEGMENT); |
| } |
| |
| static void mctp_i2c_unlock_nest(struct mctp_i2c_dev *midev) |
| { |
| unsigned long flags; |
| bool unlock; |
| |
| spin_lock_irqsave(&midev->lock, flags); |
| if (!WARN_ONCE(midev->i2c_lock_count == 0, "lock count underflow!")) |
| midev->i2c_lock_count--; |
| unlock = midev->i2c_lock_count == 0; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| if (unlock) |
| i2c_unlock_bus(midev->adapter, I2C_LOCK_SEGMENT); |
| } |
| |
| /* Unlocks the bus if was previously locked, used for cleanup */ |
| static void mctp_i2c_unlock_reset(struct mctp_i2c_dev *midev) |
| { |
| unsigned long flags; |
| bool unlock; |
| |
| spin_lock_irqsave(&midev->lock, flags); |
| unlock = midev->i2c_lock_count > 0; |
| midev->i2c_lock_count = 0; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| if (unlock) |
| i2c_unlock_bus(midev->adapter, I2C_LOCK_SEGMENT); |
| } |
| |
| static void mctp_i2c_xmit(struct mctp_i2c_dev *midev, struct sk_buff *skb) |
| { |
| struct net_device_stats *stats = &midev->ndev->stats; |
| enum mctp_i2c_flow_state fs; |
| struct mctp_i2c_hdr *hdr; |
| struct i2c_msg msg = {0}; |
| u8 *pecp; |
| int rc; |
| |
| fs = mctp_i2c_get_tx_flow_state(midev, skb); |
| |
| hdr = (void *)skb_mac_header(skb); |
| /* Sanity check that packet contents matches skb length, |
| * and can't exceed MCTP_I2C_BUFSZ |
| */ |
| if (skb->len != hdr->byte_count + 3) { |
| dev_warn_ratelimited(&midev->adapter->dev, |
| "Bad tx length %d vs skb %u\n", |
| hdr->byte_count + 3, skb->len); |
| return; |
| } |
| |
| if (skb_tailroom(skb) >= 1) { |
| /* Linear case with space, we can just append the PEC */ |
| skb_put(skb, 1); |
| } else { |
| /* Otherwise need to copy the buffer */ |
| skb_copy_bits(skb, 0, midev->tx_scratch, skb->len); |
| hdr = (void *)midev->tx_scratch; |
| } |
| |
| pecp = (void *)&hdr->source_slave + hdr->byte_count; |
| *pecp = i2c_smbus_pec(0, (u8 *)hdr, hdr->byte_count + 3); |
| msg.buf = (void *)&hdr->command; |
| /* command, bytecount, data, pec */ |
| msg.len = 2 + hdr->byte_count + 1; |
| msg.addr = hdr->dest_slave >> 1; |
| |
| switch (fs) { |
| case MCTP_I2C_TX_FLOW_NONE: |
| /* no flow: full lock & unlock */ |
| mctp_i2c_lock_nest(midev); |
| mctp_i2c_device_select(midev->client, midev); |
| rc = __i2c_transfer(midev->adapter, &msg, 1); |
| mctp_i2c_unlock_nest(midev); |
| break; |
| |
| case MCTP_I2C_TX_FLOW_NEW: |
| /* new flow: lock, tx, but don't unlock; that will happen |
| * on flow release |
| */ |
| mctp_i2c_lock_nest(midev); |
| mctp_i2c_device_select(midev->client, midev); |
| fallthrough; |
| |
| case MCTP_I2C_TX_FLOW_EXISTING: |
| /* existing flow: we already have the lock; just tx */ |
| rc = __i2c_transfer(midev->adapter, &msg, 1); |
| break; |
| |
| case MCTP_I2C_TX_FLOW_INVALID: |
| return; |
| } |
| |
| if (rc < 0) { |
| dev_warn_ratelimited(&midev->adapter->dev, |
| "__i2c_transfer failed %d\n", rc); |
| stats->tx_errors++; |
| } else { |
| stats->tx_bytes += skb->len; |
| stats->tx_packets++; |
| } |
| } |
| |
| static void mctp_i2c_flow_release(struct mctp_i2c_dev *midev) |
| { |
| unsigned long flags; |
| bool unlock; |
| |
| spin_lock_irqsave(&midev->lock, flags); |
| if (midev->release_count > midev->i2c_lock_count) { |
| WARN_ONCE(1, "release count overflow"); |
| midev->release_count = midev->i2c_lock_count; |
| } |
| |
| midev->i2c_lock_count -= midev->release_count; |
| unlock = midev->i2c_lock_count == 0 && midev->release_count > 0; |
| midev->release_count = 0; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| if (unlock) |
| i2c_unlock_bus(midev->adapter, I2C_LOCK_SEGMENT); |
| } |
| |
| static int mctp_i2c_header_create(struct sk_buff *skb, struct net_device *dev, |
| unsigned short type, const void *daddr, |
| const void *saddr, unsigned int len) |
| { |
| struct mctp_i2c_hdr *hdr; |
| struct mctp_hdr *mhdr; |
| u8 lldst, llsrc; |
| |
| if (len > MCTP_I2C_MAXMTU) |
| return -EMSGSIZE; |
| |
| lldst = *((u8 *)daddr); |
| llsrc = *((u8 *)saddr); |
| |
| skb_push(skb, sizeof(struct mctp_i2c_hdr)); |
| skb_reset_mac_header(skb); |
| hdr = (void *)skb_mac_header(skb); |
| mhdr = mctp_hdr(skb); |
| hdr->dest_slave = (lldst << 1) & 0xff; |
| hdr->command = MCTP_I2C_COMMANDCODE; |
| hdr->byte_count = len + 1; |
| hdr->source_slave = ((llsrc << 1) & 0xff) | 0x01; |
| mhdr->ver = 0x01; |
| |
| return sizeof(struct mctp_i2c_hdr); |
| } |
| |
| static int mctp_i2c_tx_thread(void *data) |
| { |
| struct mctp_i2c_dev *midev = data; |
| struct sk_buff *skb; |
| unsigned long flags; |
| |
| for (;;) { |
| if (kthread_should_stop()) |
| break; |
| |
| spin_lock_irqsave(&midev->tx_queue.lock, flags); |
| skb = __skb_dequeue(&midev->tx_queue); |
| if (netif_queue_stopped(midev->ndev)) |
| netif_wake_queue(midev->ndev); |
| spin_unlock_irqrestore(&midev->tx_queue.lock, flags); |
| |
| if (skb == &midev->unlock_marker) { |
| mctp_i2c_flow_release(midev); |
| |
| } else if (skb) { |
| mctp_i2c_xmit(midev, skb); |
| kfree_skb(skb); |
| |
| } else { |
| wait_event_idle(midev->tx_wq, |
| !skb_queue_empty(&midev->tx_queue) || |
| kthread_should_stop()); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static netdev_tx_t mctp_i2c_start_xmit(struct sk_buff *skb, |
| struct net_device *dev) |
| { |
| struct mctp_i2c_dev *midev = netdev_priv(dev); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&midev->tx_queue.lock, flags); |
| if (skb_queue_len(&midev->tx_queue) >= MCTP_I2C_TX_WORK_LEN) { |
| netif_stop_queue(dev); |
| spin_unlock_irqrestore(&midev->tx_queue.lock, flags); |
| netdev_err(dev, "BUG! Tx Ring full when queue awake!\n"); |
| return NETDEV_TX_BUSY; |
| } |
| |
| __skb_queue_tail(&midev->tx_queue, skb); |
| if (skb_queue_len(&midev->tx_queue) == MCTP_I2C_TX_WORK_LEN) |
| netif_stop_queue(dev); |
| spin_unlock_irqrestore(&midev->tx_queue.lock, flags); |
| |
| wake_up(&midev->tx_wq); |
| return NETDEV_TX_OK; |
| } |
| |
| static void mctp_i2c_release_flow(struct mctp_dev *mdev, |
| struct mctp_sk_key *key) |
| |
| { |
| struct mctp_i2c_dev *midev = netdev_priv(mdev->dev); |
| bool queue_release = false; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&midev->lock, flags); |
| /* if we have seen the flow/key previously, we need to pair the |
| * original lock with a release |
| */ |
| if (key->dev_flow_state == MCTP_I2C_FLOW_STATE_ACTIVE) { |
| midev->release_count++; |
| queue_release = true; |
| } |
| key->dev_flow_state = MCTP_I2C_FLOW_STATE_INVALID; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| if (queue_release) { |
| /* Ensure we have a release operation queued, through the fake |
| * marker skb |
| */ |
| spin_lock(&midev->tx_queue.lock); |
| if (!midev->unlock_marker.next) |
| __skb_queue_tail(&midev->tx_queue, |
| &midev->unlock_marker); |
| spin_unlock(&midev->tx_queue.lock); |
| wake_up(&midev->tx_wq); |
| } |
| } |
| |
| static const struct net_device_ops mctp_i2c_ops = { |
| .ndo_start_xmit = mctp_i2c_start_xmit, |
| .ndo_uninit = mctp_i2c_ndo_uninit, |
| .ndo_open = mctp_i2c_ndo_open, |
| }; |
| |
| static const struct header_ops mctp_i2c_headops = { |
| .create = mctp_i2c_header_create, |
| }; |
| |
| static const struct mctp_netdev_ops mctp_i2c_mctp_ops = { |
| .release_flow = mctp_i2c_release_flow, |
| }; |
| |
| static void mctp_i2c_net_setup(struct net_device *dev) |
| { |
| dev->type = ARPHRD_MCTP; |
| |
| dev->mtu = MCTP_I2C_MAXMTU; |
| dev->min_mtu = MCTP_I2C_MINMTU; |
| dev->max_mtu = MCTP_I2C_MAXMTU; |
| dev->tx_queue_len = MCTP_I2C_TX_QUEUE_LEN; |
| |
| dev->hard_header_len = sizeof(struct mctp_i2c_hdr); |
| dev->addr_len = 1; |
| |
| dev->netdev_ops = &mctp_i2c_ops; |
| dev->header_ops = &mctp_i2c_headops; |
| } |
| |
| /* Populates the mctp_i2c_dev priv struct for a netdev. |
| * Returns an error pointer on failure. |
| */ |
| static struct mctp_i2c_dev *mctp_i2c_midev_init(struct net_device *dev, |
| struct mctp_i2c_client *mcli, |
| struct i2c_adapter *adap) |
| { |
| struct mctp_i2c_dev *midev = netdev_priv(dev); |
| unsigned long flags; |
| |
| midev->tx_thread = kthread_create(mctp_i2c_tx_thread, midev, |
| "%s/tx", dev->name); |
| if (IS_ERR(midev->tx_thread)) |
| return ERR_CAST(midev->tx_thread); |
| |
| midev->ndev = dev; |
| get_device(&adap->dev); |
| midev->adapter = adap; |
| get_device(&mcli->client->dev); |
| midev->client = mcli; |
| INIT_LIST_HEAD(&midev->list); |
| spin_lock_init(&midev->lock); |
| midev->i2c_lock_count = 0; |
| midev->release_count = 0; |
| init_completion(&midev->rx_done); |
| complete(&midev->rx_done); |
| init_waitqueue_head(&midev->tx_wq); |
| skb_queue_head_init(&midev->tx_queue); |
| |
| /* Add to the parent mcli */ |
| spin_lock_irqsave(&mcli->sel_lock, flags); |
| list_add(&midev->list, &mcli->devs); |
| /* Select a device by default */ |
| if (!mcli->sel) |
| __mctp_i2c_device_select(mcli, midev); |
| spin_unlock_irqrestore(&mcli->sel_lock, flags); |
| |
| /* Start the worker thread */ |
| wake_up_process(midev->tx_thread); |
| |
| return midev; |
| } |
| |
| /* Counterpart of mctp_i2c_midev_init */ |
| static void mctp_i2c_midev_free(struct mctp_i2c_dev *midev) |
| { |
| struct mctp_i2c_client *mcli = midev->client; |
| unsigned long flags; |
| |
| if (midev->tx_thread) { |
| kthread_stop(midev->tx_thread); |
| midev->tx_thread = NULL; |
| } |
| |
| /* Unconditionally unlock on close */ |
| mctp_i2c_unlock_reset(midev); |
| |
| /* Remove the netdev from the parent i2c client. */ |
| spin_lock_irqsave(&mcli->sel_lock, flags); |
| list_del(&midev->list); |
| if (mcli->sel == midev) { |
| struct mctp_i2c_dev *first; |
| |
| first = list_first_entry_or_null(&mcli->devs, struct mctp_i2c_dev, list); |
| __mctp_i2c_device_select(mcli, first); |
| } |
| spin_unlock_irqrestore(&mcli->sel_lock, flags); |
| |
| skb_queue_purge(&midev->tx_queue); |
| put_device(&midev->adapter->dev); |
| put_device(&mcli->client->dev); |
| } |
| |
| /* Stops, unregisters, and frees midev */ |
| static void mctp_i2c_unregister(struct mctp_i2c_dev *midev) |
| { |
| unsigned long flags; |
| |
| /* Stop tx thread prior to unregister, it uses netif_() functions */ |
| kthread_stop(midev->tx_thread); |
| midev->tx_thread = NULL; |
| |
| /* Prevent any new rx in mctp_i2c_recv(), let any pending work finish */ |
| spin_lock_irqsave(&midev->lock, flags); |
| midev->allow_rx = false; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| wait_for_completion(&midev->rx_done); |
| |
| mctp_unregister_netdev(midev->ndev); |
| /* midev has been freed now by mctp_i2c_ndo_uninit callback */ |
| |
| free_netdev(midev->ndev); |
| } |
| |
| static void mctp_i2c_ndo_uninit(struct net_device *dev) |
| { |
| struct mctp_i2c_dev *midev = netdev_priv(dev); |
| |
| /* Perform cleanup here to ensure that mcli->sel isn't holding |
| * a reference that would prevent unregister_netdevice() |
| * from completing. |
| */ |
| mctp_i2c_midev_free(midev); |
| } |
| |
| static int mctp_i2c_ndo_open(struct net_device *dev) |
| { |
| struct mctp_i2c_dev *midev = netdev_priv(dev); |
| unsigned long flags; |
| |
| /* i2c rx handler can only pass packets once the netdev is registered */ |
| spin_lock_irqsave(&midev->lock, flags); |
| midev->allow_rx = true; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| return 0; |
| } |
| |
| static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli, |
| struct i2c_adapter *adap) |
| { |
| struct mctp_i2c_dev *midev = NULL; |
| struct net_device *ndev = NULL; |
| struct i2c_adapter *root; |
| unsigned long flags; |
| char namebuf[30]; |
| int rc; |
| |
| root = mux_root_adapter(adap); |
| if (root != mcli->client->adapter) { |
| dev_err(&mcli->client->dev, |
| "I2C adapter %s is not a child bus of %s\n", |
| mcli->client->adapter->name, root->name); |
| return -EINVAL; |
| } |
| |
| WARN_ON(!mutex_is_locked(&driver_clients_lock)); |
| snprintf(namebuf, sizeof(namebuf), "mctpi2c%d", adap->nr); |
| ndev = alloc_netdev(sizeof(*midev), namebuf, NET_NAME_ENUM, mctp_i2c_net_setup); |
| if (!ndev) { |
| dev_err(&mcli->client->dev, "alloc netdev failed\n"); |
| rc = -ENOMEM; |
| goto err; |
| } |
| dev_net_set(ndev, current->nsproxy->net_ns); |
| SET_NETDEV_DEV(ndev, &adap->dev); |
| dev_addr_set(ndev, &mcli->lladdr); |
| |
| midev = mctp_i2c_midev_init(ndev, mcli, adap); |
| if (IS_ERR(midev)) { |
| rc = PTR_ERR(midev); |
| midev = NULL; |
| goto err; |
| } |
| |
| rc = mctp_register_netdev(ndev, &mctp_i2c_mctp_ops); |
| if (rc < 0) { |
| dev_err(&mcli->client->dev, |
| "register netdev \"%s\" failed %d\n", |
| ndev->name, rc); |
| goto err; |
| } |
| |
| spin_lock_irqsave(&midev->lock, flags); |
| midev->allow_rx = false; |
| spin_unlock_irqrestore(&midev->lock, flags); |
| |
| return 0; |
| err: |
| if (midev) |
| mctp_i2c_midev_free(midev); |
| if (ndev) |
| free_netdev(ndev); |
| return rc; |
| } |
| |
| /* Removes any netdev for adap. mcli is the parent root i2c client */ |
| static void mctp_i2c_remove_netdev(struct mctp_i2c_client *mcli, |
| struct i2c_adapter *adap) |
| { |
| struct mctp_i2c_dev *midev = NULL, *m = NULL; |
| unsigned long flags; |
| |
| WARN_ON(!mutex_is_locked(&driver_clients_lock)); |
| spin_lock_irqsave(&mcli->sel_lock, flags); |
| /* List size is limited by number of MCTP netdevs on a single hardware bus */ |
| list_for_each_entry(m, &mcli->devs, list) |
| if (m->adapter == adap) { |
| midev = m; |
| break; |
| } |
| spin_unlock_irqrestore(&mcli->sel_lock, flags); |
| |
| if (midev) |
| mctp_i2c_unregister(midev); |
| } |
| |
| /* Determines whether a device is an i2c adapter. |
| * Optionally returns the root i2c_adapter |
| */ |
| static struct i2c_adapter *mctp_i2c_get_adapter(struct device *dev, |
| struct i2c_adapter **ret_root) |
| { |
| struct i2c_adapter *root, *adap; |
| |
| if (dev->type != &i2c_adapter_type) |
| return NULL; |
| adap = to_i2c_adapter(dev); |
| root = mux_root_adapter(adap); |
| WARN_ONCE(!root, "MCTP I2C failed to find root adapter for %s\n", |
| dev_name(dev)); |
| if (!root) |
| return NULL; |
| if (ret_root) |
| *ret_root = root; |
| return adap; |
| } |
| |
| /* Determines whether a device is an i2c adapter with the "mctp-controller" |
| * devicetree property set. If adap is not an OF node, returns match_no_of |
| */ |
| static bool mctp_i2c_adapter_match(struct i2c_adapter *adap, bool match_no_of) |
| { |
| if (!adap->dev.of_node) |
| return match_no_of; |
| return of_property_read_bool(adap->dev.of_node, MCTP_I2C_OF_PROP); |
| } |
| |
| /* Called for each existing i2c device (adapter or client) when a |
| * new mctp-i2c client is probed. |
| */ |
| static int mctp_i2c_client_try_attach(struct device *dev, void *data) |
| { |
| struct i2c_adapter *adap = NULL, *root = NULL; |
| struct mctp_i2c_client *mcli = data; |
| |
| adap = mctp_i2c_get_adapter(dev, &root); |
| if (!adap) |
| return 0; |
| if (mcli->client->adapter != root) |
| return 0; |
| /* Must either have mctp-controller property on the adapter, or |
| * be a root adapter if it's non-devicetree |
| */ |
| if (!mctp_i2c_adapter_match(adap, adap == root)) |
| return 0; |
| |
| return mctp_i2c_add_netdev(mcli, adap); |
| } |
| |
| static void mctp_i2c_notify_add(struct device *dev) |
| { |
| struct mctp_i2c_client *mcli = NULL, *m = NULL; |
| struct i2c_adapter *root = NULL, *adap = NULL; |
| int rc; |
| |
| adap = mctp_i2c_get_adapter(dev, &root); |
| if (!adap) |
| return; |
| /* Check for mctp-controller property on the adapter */ |
| if (!mctp_i2c_adapter_match(adap, false)) |
| return; |
| |
| /* Find an existing mcli for adap's root */ |
| mutex_lock(&driver_clients_lock); |
| list_for_each_entry(m, &driver_clients, list) { |
| if (m->client->adapter == root) { |
| mcli = m; |
| break; |
| } |
| } |
| |
| if (mcli) { |
| rc = mctp_i2c_add_netdev(mcli, adap); |
| if (rc < 0) |
| dev_warn(dev, "Failed adding mctp-i2c net device\n"); |
| } |
| mutex_unlock(&driver_clients_lock); |
| } |
| |
| static void mctp_i2c_notify_del(struct device *dev) |
| { |
| struct i2c_adapter *root = NULL, *adap = NULL; |
| struct mctp_i2c_client *mcli = NULL; |
| |
| adap = mctp_i2c_get_adapter(dev, &root); |
| if (!adap) |
| return; |
| |
| mutex_lock(&driver_clients_lock); |
| list_for_each_entry(mcli, &driver_clients, list) { |
| if (mcli->client->adapter == root) { |
| mctp_i2c_remove_netdev(mcli, adap); |
| break; |
| } |
| } |
| mutex_unlock(&driver_clients_lock); |
| } |
| |
| static int mctp_i2c_probe(struct i2c_client *client) |
| { |
| struct mctp_i2c_client *mcli = NULL; |
| int rc; |
| |
| mutex_lock(&driver_clients_lock); |
| mcli = mctp_i2c_new_client(client); |
| if (IS_ERR(mcli)) { |
| rc = PTR_ERR(mcli); |
| mcli = NULL; |
| goto out; |
| } else { |
| list_add(&mcli->list, &driver_clients); |
| } |
| |
| /* Add a netdev for adapters that have a 'mctp-controller' property */ |
| i2c_for_each_dev(mcli, mctp_i2c_client_try_attach); |
| rc = 0; |
| out: |
| mutex_unlock(&driver_clients_lock); |
| return rc; |
| } |
| |
| static void mctp_i2c_remove(struct i2c_client *client) |
| { |
| struct mctp_i2c_client *mcli = i2c_get_clientdata(client); |
| struct mctp_i2c_dev *midev = NULL, *tmp = NULL; |
| |
| mutex_lock(&driver_clients_lock); |
| list_del(&mcli->list); |
| /* Remove all child adapter netdevs */ |
| list_for_each_entry_safe(midev, tmp, &mcli->devs, list) |
| mctp_i2c_unregister(midev); |
| |
| mctp_i2c_free_client(mcli); |
| mutex_unlock(&driver_clients_lock); |
| } |
| |
| /* We look for a 'mctp-controller' property on I2C busses as they are |
| * added/deleted, creating/removing netdevs as required. |
| */ |
| static int mctp_i2c_notifier_call(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct device *dev = data; |
| |
| switch (action) { |
| case BUS_NOTIFY_ADD_DEVICE: |
| mctp_i2c_notify_add(dev); |
| break; |
| case BUS_NOTIFY_DEL_DEVICE: |
| mctp_i2c_notify_del(dev); |
| break; |
| } |
| return NOTIFY_DONE; |
| } |
| |
| static struct notifier_block mctp_i2c_notifier = { |
| .notifier_call = mctp_i2c_notifier_call, |
| }; |
| |
| static const struct i2c_device_id mctp_i2c_id[] = { |
| { "mctp-i2c-interface", 0 }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, mctp_i2c_id); |
| |
| static const struct of_device_id mctp_i2c_of_match[] = { |
| { .compatible = "mctp-i2c-controller" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, mctp_i2c_of_match); |
| |
| static struct i2c_driver mctp_i2c_driver = { |
| .driver = { |
| .name = "mctp-i2c-interface", |
| .of_match_table = mctp_i2c_of_match, |
| }, |
| .probe_new = mctp_i2c_probe, |
| .remove = mctp_i2c_remove, |
| .id_table = mctp_i2c_id, |
| }; |
| |
| static __init int mctp_i2c_mod_init(void) |
| { |
| int rc; |
| |
| pr_info("MCTP I2C interface driver\n"); |
| rc = i2c_add_driver(&mctp_i2c_driver); |
| if (rc < 0) |
| return rc; |
| rc = bus_register_notifier(&i2c_bus_type, &mctp_i2c_notifier); |
| if (rc < 0) { |
| i2c_del_driver(&mctp_i2c_driver); |
| return rc; |
| } |
| return 0; |
| } |
| |
| static __exit void mctp_i2c_mod_exit(void) |
| { |
| int rc; |
| |
| rc = bus_unregister_notifier(&i2c_bus_type, &mctp_i2c_notifier); |
| if (rc < 0) |
| pr_warn("MCTP I2C could not unregister notifier, %d\n", rc); |
| i2c_del_driver(&mctp_i2c_driver); |
| } |
| |
| module_init(mctp_i2c_mod_init); |
| module_exit(mctp_i2c_mod_exit); |
| |
| MODULE_DESCRIPTION("MCTP I2C device"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_AUTHOR("Matt Johnston <matt@codeconstruct.com.au>"); |