diff options
Diffstat (limited to 'drivers/net/mctp/mctp-i2c.c')
| -rw-r--r-- | drivers/net/mctp/mctp-i2c.c | 1082 | 
1 files changed, 1082 insertions, 0 deletions
diff --git a/drivers/net/mctp/mctp-i2c.c b/drivers/net/mctp/mctp-i2c.c new file mode 100644 index 000000000000..baf7afac7857 --- /dev/null +++ b/drivers/net/mctp/mctp-i2c.c @@ -0,0 +1,1082 @@ +// 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, +}; + +/* 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 if (key->dev_flow_state == MCTP_I2C_FLOW_STATE_NEW) { +		key->dev_flow_state = MCTP_I2C_FLOW_STATE_ACTIVE; +		state = MCTP_I2C_TX_FLOW_NEW; +	} else { +		state = MCTP_I2C_TX_FLOW_EXISTING; +	} + +	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 0; +} + +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); +	unsigned long flags; + +	spin_lock_irqsave(&midev->lock, flags); +	midev->release_count++; +	spin_unlock_irqrestore(&midev->lock, flags); + +	/* 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 int 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); +	/* Callers ignore return code */ +	return 0; +} + +/* 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 <[email protected]>");  |