blob: ed213b4765a121c77fa32b0e7b4366e75a85417c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Management Controller Transport Protocol (MCTP)
*
* Copyright (c) 2021 Code Construct
* Copyright (c) 2021 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>
/* SMBus 3.0 allows 255 data bytes (plus PEC), but the
* first byte is taken for source slave address.
*/
#define MCTP_I2C_MAXBLOCK 255
#define MCTP_I2C_MAXMTU (MCTP_I2C_MAXBLOCK - 1)
#define MCTP_I2C_MINMTU (64 + 4)
/* Allow space for address, command, byte_count, databytes, PEC */
#define MCTP_I2C_RXBUFSZ (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,
};
static struct {
/* lock protects clients and also prevents adding/removing adapters
* during mctp_i2c_client probe/remove.
*/
struct mutex lock;
// list of struct mctp_i2c_client
struct list_head clients;
} mi_driver_state;
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 pos;
u8 buffer[MCTP_I2C_RXBUFSZ];
struct task_struct *tx_thread;
wait_queue_head_t tx_wq;
struct sk_buff_head tx_queue;
// a fake entry in our tx queue to perform an unlock operation
struct sk_buff unlock_marker;
spinlock_t flow_lock; // protects i2c_lock_count and release_count
int i2c_lock_count;
int release_count;
};
/* 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 curr_lock; // protects sel
struct list_head list; // for mi_driver_state.clients
};
// Header on the wire
struct mctp_i2c_hdr {
u8 dest_slave;
u8 command;
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 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
}
static ssize_t mctp_current_mux_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mctp_i2c_client *mcli = i2c_get_clientdata(to_i2c_client(dev));
struct net_device *ndev = NULL;
unsigned long flags;
ssize_t l;
spin_lock_irqsave(&mcli->curr_lock, flags);
if (mcli->sel) {
ndev = mcli->sel->ndev;
dev_hold(ndev);
}
spin_unlock_irqrestore(&mcli->curr_lock, flags);
l = scnprintf(buf, PAGE_SIZE, "%s\n", ndev ? ndev->name : "(none)");
if (ndev)
dev_put(ndev);
return l;
}
static DEVICE_ATTR_RO(mctp_current_mux);
/* 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, "%s failed, MCTP requires a 7-bit I2C address, addr=0x%x",
__func__, client->addr);
rc = -EINVAL;
goto err;
}
root = mux_root_adapter(client->adapter);
if (!root) {
dev_err(&client->dev, "%s failed to find root adapter\n", __func__);
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->curr_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) {
dev_err(&client->dev, "%s i2c register failed %d\n", __func__, rc);
mcli->client = NULL;
i2c_set_clientdata(client, NULL);
goto err;
}
rc = device_create_file(&client->dev, &dev_attr_mctp_current_mux);
if (rc) {
dev_err(&client->dev, "%s adding sysfs \"%s\" failed %d\n", __func__,
dev_attr_mctp_current_mux.attr.name, rc);
// continue anyway
}
return mcli;
err:
if (mcli) {
if (mcli->client) {
device_remove_file(&mcli->client->dev, &dev_attr_mctp_current_mux);
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(&mi_driver_state.lock));
WARN_ON(!list_empty(&mcli->devs));
WARN_ON(mcli->sel); // sanity check, no locking
device_remove_file(&mcli->client->dev, &dev_attr_mctp_current_mux);
rc = i2c_slave_unregister(mcli->client);
// leak if it fails, we can't propagate errors upwards
if (rc)
dev_err(&mcli->client->dev, "%s i2c unregister failed %d\n", __func__, rc);
else
kfree(mcli);
}
/* Switch the mctp i2c device to receive responses.
* Call with curr_lock held
*/
static void __mctp_i2c_device_select(struct mctp_i2c_client *mcli,
struct mctp_i2c_dev *midev)
{
assert_spin_locked(&mcli->curr_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->curr_lock, flags);
__mctp_i2c_device_select(mcli, midev);
spin_unlock_irqrestore(&mcli->curr_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->curr_lock, flags);
midev = mcli->sel;
if (midev)
dev_hold(midev->ndev);
spin_unlock_irqrestore(&mcli->curr_lock, flags);
if (!midev)
return 0;
switch (event) {
case I2C_SLAVE_WRITE_RECEIVED:
if (midev->pos < MCTP_I2C_RXBUFSZ) {
midev->buffer[midev->pos] = *val;
midev->pos++;
} else {
midev->ndev->stats.rx_over_errors++;
}
break;
case I2C_SLAVE_WRITE_REQUESTED:
/* dest_slave as first byte */
midev->buffer[0] = mcli->lladdr << 1;
midev->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;
u8 pec, calc_pec;
size_t recvlen;
/* + 1 for the PEC */
if (midev->pos < MCTP_I2C_MINLEN + 1) {
ndev->stats.rx_length_errors++;
return -EINVAL;
}
recvlen = midev->pos - 1;
hdr = (void *)midev->buffer;
if (hdr->command != MCTP_I2C_COMMANDCODE) {
ndev->stats.rx_dropped++;
return -EINVAL;
}
pec = midev->buffer[midev->pos - 1];
calc_pec = i2c_smbus_pec(0, midev->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->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;
if (netif_rx(skb) == NET_RX_SUCCESS) {
ndev->stats.rx_packets++;
ndev->stats.rx_bytes += skb->len;
} 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->flow_lock, flags);
lock = midev->i2c_lock_count == 0;
midev->i2c_lock_count++;
spin_unlock_irqrestore(&midev->flow_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->flow_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->flow_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;
union i2c_smbus_data *data;
struct mctp_i2c_hdr *hdr;
unsigned int len;
u16 daddr;
int rc;
fs = mctp_i2c_get_tx_flow_state(midev, skb);
len = skb->len;
hdr = (void *)skb_mac_header(skb);
data = (void *)&hdr->byte_count;
daddr = 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_smbus_xfer(midev->adapter, daddr, I2C_CLIENT_PEC,
I2C_SMBUS_WRITE, hdr->command,
I2C_SMBUS_BLOCK_DATA, data);
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_smbus_xfer(midev->adapter, daddr, I2C_CLIENT_PEC,
I2C_SMBUS_WRITE, hdr->command,
I2C_SMBUS_BLOCK_DATA, data);
break;
case MCTP_I2C_TX_FLOW_INVALID:
return;
}
if (rc) {
dev_warn_ratelimited(&midev->adapter->dev,
"%s i2c_smbus_xfer failed %d", __func__, rc);
stats->tx_errors++;
} else {
stats->tx_bytes += len;
stats->tx_packets++;
}
}
static void mctp_i2c_flow_release(struct mctp_i2c_dev *midev)
{
unsigned long flags;
bool unlock;
spin_lock_irqsave(&midev->flow_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->flow_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;
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;
if (hdr->byte_count > MCTP_I2C_MAXBLOCK)
return -EMSGSIZE;
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(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->flow_lock, flags);
midev->release_count++;
spin_unlock_irqrestore(&midev->flow_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,
};
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;
dev->needs_free_netdev = true;
}
static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli,
struct i2c_adapter *adap)
{
unsigned long flags;
struct mctp_i2c_dev *midev = NULL;
struct net_device *ndev = NULL;
struct i2c_adapter *root;
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",
mcli->client->adapter->name, root->name);
return -EINVAL;
}
WARN_ON(!mutex_is_locked(&mi_driver_state.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, "%s alloc netdev failed\n", __func__);
rc = -ENOMEM;
goto err;
}
dev_net_set(ndev, current->nsproxy->net_ns);
SET_NETDEV_DEV(ndev, &adap->dev);
ndev->dev_addr = &mcli->lladdr;
midev = netdev_priv(ndev);
skb_queue_head_init(&midev->tx_queue);
INIT_LIST_HEAD(&midev->list);
midev->adapter = adap;
midev->client = mcli;
spin_lock_init(&midev->flow_lock);
midev->i2c_lock_count = 0;
midev->release_count = 0;
/* Hold references */
get_device(&midev->adapter->dev);
get_device(&midev->client->client->dev);
midev->ndev = ndev;
init_waitqueue_head(&midev->tx_wq);
midev->tx_thread = kthread_create(mctp_i2c_tx_thread, midev,
"%s/tx", namebuf);
if (IS_ERR_OR_NULL(midev->tx_thread)) {
rc = -ENOMEM;
goto err_free;
}
rc = mctp_register_netdev(ndev, &mctp_i2c_mctp_ops);
if (rc) {
dev_err(&mcli->client->dev,
"%s register netdev \"%s\" failed %d\n", __func__,
ndev->name, rc);
goto err_stop_kthread;
}
spin_lock_irqsave(&mcli->curr_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->curr_lock, flags);
wake_up_process(midev->tx_thread);
return 0;
err_stop_kthread:
kthread_stop(midev->tx_thread);
err_free:
free_netdev(ndev);
err:
return rc;
}
// Removes and unregisters a mctp-i2c netdev
static void mctp_i2c_free_netdev(struct mctp_i2c_dev *midev)
{
struct mctp_i2c_client *mcli = midev->client;
unsigned long flags;
netif_stop_queue(midev->ndev);
kthread_stop(midev->tx_thread);
skb_queue_purge(&midev->tx_queue);
/* Release references, used only for TX which has stopped */
put_device(&midev->adapter->dev);
put_device(&mcli->client->dev);
/* Remove it from the parent mcli */
spin_lock_irqsave(&mcli->curr_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->curr_lock, flags);
/* Remove netdev. mctp_i2c_slave_cb() takes a dev_hold() so removing
* it now is safe. unregister_netdev() frees ndev and midev.
*/
mctp_unregister_netdev(midev->ndev);
}
// 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)
{
unsigned long flags;
struct mctp_i2c_dev *midev = NULL, *m = NULL;
WARN_ON(!mutex_is_locked(&mi_driver_state.lock));
spin_lock_irqsave(&mcli->curr_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->curr_lock, flags);
if (midev)
mctp_i2c_free_netdev(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, "%s failed to find root adapter for %s\n",
__func__, 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(&mi_driver_state.lock);
list_for_each_entry(m, &mi_driver_state.clients, list) {
if (m->client->adapter == root) {
mcli = m;
break;
}
}
if (mcli) {
rc = mctp_i2c_add_netdev(mcli, adap);
if (rc)
dev_warn(dev, "%s Failed adding mctp-i2c device",
__func__);
}
mutex_unlock(&mi_driver_state.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(&mi_driver_state.lock);
list_for_each_entry(mcli, &mi_driver_state.clients, list) {
if (mcli->client->adapter == root) {
mctp_i2c_remove_netdev(mcli, adap);
break;
}
}
mutex_unlock(&mi_driver_state.lock);
}
static int mctp_i2c_probe(struct i2c_client *client)
{
struct mctp_i2c_client *mcli = NULL;
int rc;
/* Check for >32 byte block support required for MCTP */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_V3_BLOCK)) {
dev_err(&client->dev,
"%s failed, I2C bus driver does not support 255 byte block transfer\n",
__func__);
return -EOPNOTSUPP;
}
mutex_lock(&mi_driver_state.lock);
mcli = mctp_i2c_new_client(client);
if (IS_ERR(mcli)) {
rc = PTR_ERR(mcli);
mcli = NULL;
goto out;
} else {
list_add(&mcli->list, &mi_driver_state.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(&mi_driver_state.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(&mi_driver_state.lock);
list_del(&mcli->list);
// Remove all child adapter netdevs
list_for_each_entry_safe(midev, tmp, &mcli->devs, list)
mctp_i2c_free_netdev(midev);
mctp_i2c_free_client(mcli);
mutex_unlock(&mi_driver_state.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", 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",
.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_init(void)
{
int rc;
INIT_LIST_HEAD(&mi_driver_state.clients);
mutex_init(&mi_driver_state.lock);
pr_info("MCTP SMBus/I2C transport driver\n");
rc = i2c_add_driver(&mctp_i2c_driver);
if (rc)
return rc;
rc = bus_register_notifier(&i2c_bus_type, &mctp_i2c_notifier);
if (rc) {
i2c_del_driver(&mctp_i2c_driver);
return rc;
}
return 0;
}
static __exit void mctp_i2c_exit(void)
{
int rc;
rc = bus_unregister_notifier(&i2c_bus_type, &mctp_i2c_notifier);
if (rc)
pr_warn("%s Could not unregister notifier, %d", __func__, rc);
i2c_del_driver(&mctp_i2c_driver);
}
module_init(mctp_i2c_init);
module_exit(mctp_i2c_exit);
MODULE_DESCRIPTION("MCTP SMBus/I2C device");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Matt Johnston <matt@codeconstruct.com.au>");