From: Matt Johnston <matt@codeconstruct.com.au>
To: Kenny Hsieh <kenny_hsieh@jabil.com>,
netdev@vger.kernel.org, Jeremy Kerr <jk@codeconstruct.com.au>,
Andi Shyti <andi.shyti@kernel.org>,
Peter Rosin <peda@lysator.liu.se>
Cc: Andrew Lunn <andrew+netdev@lunn.ch>,
davem@davemloft.net, Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>,
linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next] mctp i2c: add sysfs control for mux child netdevs
Date: Tue, 06 Oct 2026 18:31:33 +0800 [thread overview]
Message-ID: <a1e2d7c0644234bd66b806134f7b803af741aa74.camel@codeconstruct.com.au> (raw)
In-Reply-To: <20261002073156.759005-1-kenny_hsieh@jabil.com>
Hi Kenny,
Thanks for this patch. The sashiko concerns look plausible. I've added a few
other thoughts.
(Disclosure, I suggested this sysfs approach to Kenny earlier).
On Fri, 2026-10-02 at 15:31 +0800, Kenny Hsieh wrote:
> An MCTP netdev is created for an I2C bus that carries an
> "mctp-controller" property in the devicetree. A bus instantiated at
> runtime through the I2C sysfs interface has no devicetree node on which
> to set that property, so it can never get a netdev.
>
> This matters for a mux topology that is only known at runtime. A BMC
> that ships one firmware image across several chassis cannot describe
> every chassis's mux tree in its devicetree. Userspace creates the muxes
> it finds over i2c sysfs instead, and the channels below them have no
> node of their own.
>
> Add a writable "mctp_controller" attribute on mux child adapters below
> a bus that has an mctp-i2c-controller device bound to it. A mux tree
> with no such device is untouched. Writing 1 creates the netdev for that
> bus, writing 0 removes it, and reading reports whether one exists.
> Userspace can then enable MCTP per channel once it has created the
> muxes.
>
> The root adapter is excluded: its netdev follows the bound
> mctp-i2c-controller device rather than this attribute, so a write there
> returns -EINVAL. The devicetree property keeps working, and both
> mechanisms may be used on the same mux tree.
It might make sense to create the mctp_controller sysfs attribute for root
adapters too, so the root bus mctpi2c netdev can be removed at runtime. It
would probably remove some special-casing for the root node too.
> The netdev is placed in the network namespace the mctp-i2c-controller
> device was probed in, not the one that writes the attribute, since the
> attribute is visible in every namespace.
Perhaps creating/removing a netdev should only be permitted when the current
process matches the mctp-i2c-controller network namespace. There could be
unforseen results creating a netdev in a different namespace? The restriction
could be relaxed later if needed and is deemed safe.
> Only one mctp-i2c-controller client is allowed per mux tree. A second
> one would add the same attribute to every child adapter again, and
> either client's removal would delete the attribute the other still
> needs.
This is a bit redundant given mctp-i2c-controller client will already only
bind to root adapters?
> Tested on an AST2700 BMC with a nested mux tree by writing 1 to
> mctp_controller on a channel that has no "mctp-controller" property,
> which brings up the interface, then writing 0, which removes it. The
> attribute also survives an unbind and rebind of the
> mctp-i2c-controller driver.
>
> Signed-off-by: Kenny Hsieh <kenny_hsieh@jabil.com>
> ---
> .../ABI/testing/sysfs-bus-i2c-devices-mctp | 26 ++
> MAINTAINERS | 1 +
> drivers/net/mctp/mctp-i2c.c | 256 +++++++++++++++---
> 3 files changed, 242 insertions(+), 41 deletions(-)
> create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-mctp
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-mctp b/Documentation/ABI/testing/sysfs-bus-i2c-devices-mctp
> new file mode 100644
> index 000000000000..a04bc30aa7b5
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-mctp
> @@ -0,0 +1,26 @@
> +What: /sys/bus/i2c/devices/i2c-<n>/mctp_controller
> +Date: October 2026
> +KernelVersion: 7.4
> +Contact: Kenny Hsieh <kenny_hsieh@jabil.com>
> +Description:
> + Controls whether an MCTP network device exists for this I2C
> + bus. Writing 1 creates it, writing 0 removes it, and reading
> + returns 1 while it exists. Writing a value that matches the
> + current state is a no-op.
> +
> + The attribute appears only on mux child busses whose mux tree
> + root has an mctp-i2c-controller device bound to it. The root
> + bus itself is excluded, because its network device follows
> + that mctp-i2c-controller device rather than this attribute.
> +
> + A bus described in the devicetree can carry an
> + "mctp-controller" property instead, which creates the network
> + device when the mctp-i2c-controller device probes. This
> + attribute covers busses instantiated at runtime through the
> + I2C sysfs interface, which have no devicetree node on which to
> + set that property. Both mechanisms may be used on the same mux
> + tree.
> +
> + Removal is synchronous and does not drain traffic: queued
> + packets are discarded and in-flight requests fail, so a
> + requester sees a timeout rather than an error.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3011f995437f..1ba32f61a21a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15718,6 +15718,7 @@ M: Jeremy Kerr <jk@codeconstruct.com.au>
> M: Matt Johnston <matt@codeconstruct.com.au>
> L: netdev@vger.kernel.org
> S: Maintained
> +F: Documentation/ABI/testing/sysfs-bus-i2c-devices-mctp
> F: Documentation/networking/mctp.rst
> F: drivers/net/mctp/
> F: include/linux/usb/mctp-usb.h
> diff --git a/drivers/net/mctp/mctp-i2c.c b/drivers/net/mctp/mctp-i2c.c
> index b02476d338bb..d4da32656744 100644
> --- a/drivers/net/mctp/mctp-i2c.c
> +++ b/drivers/net/mctp/mctp-i2c.c
> @@ -22,6 +22,8 @@
> #include <linux/i2c.h>
> #include <linux/i2c-mux.h>
> #include <linux/if_arp.h>
> +#include <linux/sysfs.h>
> +#include <linux/nsproxy.h>
> #include <net/mctp.h>
> #include <net/mctpdevice.h>
>
> @@ -89,6 +91,10 @@ struct mctp_i2c_dev {
> struct mctp_i2c_client {
> struct i2c_client *client;
> u8 lladdr;
> + /* Namespace the controller was probed in. Netdevs are created here
> + * regardless of which namespace writes the mctp_controller attribute.
> + */
> + possible_net_t net;
>
> struct mctp_i2c_dev *sel;
> struct list_head devs;
> @@ -128,7 +134,7 @@ static struct i2c_adapter *mux_root_adapter(struct i2c_adapter *adap)
> */
> static struct mctp_i2c_client *mctp_i2c_new_client(struct i2c_client *client)
> {
> - struct mctp_i2c_client *mcli = NULL;
> + struct mctp_i2c_client *mcli = NULL, *m = NULL;
> struct i2c_adapter *root = NULL;
> int rc;
>
> @@ -154,6 +160,21 @@ static struct mctp_i2c_client *mctp_i2c_new_client(struct i2c_client *client)
> goto err;
> }
>
> + /* One client per mux tree. A second one would add the same
> + * mctp_controller attribute to every child adapter again, and either
> + * client's removal would delete the attribute the other still needs.
> + */
> + WARN_ON(!mutex_is_locked(&driver_clients_lock));
> + list_for_each_entry(m, &driver_clients, list) {
> + if (m->client->adapter == root) {
> + dev_err(&client->dev,
> + "An mctp-i2c-controller client is already attached to adapter %s\n",
> + root->name);
> + rc = -EBUSY;
> + goto err;
> + }
> + }
> +
> mcli = kzalloc_obj(*mcli);
> if (!mcli) {
> rc = -ENOMEM;
> @@ -163,6 +184,7 @@ static struct mctp_i2c_client *mctp_i2c_new_client(struct i2c_client *client)
> INIT_LIST_HEAD(&mcli->devs);
> INIT_LIST_HEAD(&mcli->list);
> mcli->lladdr = client->addr & 0xff;
> + write_pnet(&mcli->net, current->nsproxy->net_ns);
> mcli->client = client;
> i2c_set_clientdata(client, mcli);
>
> @@ -857,6 +879,25 @@ static int mctp_i2c_ndo_open(struct net_device *dev)
> return 0;
> }
>
> +/* Returns the netdev for adap, or NULL if there is none */
> +static struct mctp_i2c_dev *mctp_i2c_find_dev(struct mctp_i2c_client *mcli,
> + struct i2c_adapter *adap)
> +{
> + struct mctp_i2c_dev *midev = NULL, *m = NULL;
> + unsigned long flags;
> +
> + 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);
> +
> + return midev;
> +}
> +
> static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli,
> struct i2c_adapter *adap)
> {
> @@ -883,7 +924,12 @@ static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli,
> rc = -ENOMEM;
> goto err;
> }
> - dev_net_set(ndev, current->nsproxy->net_ns);
> + /* Tie the netdev to the namespace the mctp-i2c-controller device was
> + * probed in, not to whoever happens to write mctp_controller. A write
> + * from another namespace must not place the netdev there, since the
> + * attribute is visible in all of them.
> + */
> + dev_net_set(ndev, read_pnet(&mcli->net));
> SET_NETDEV_DEV(ndev, &adap->dev);
> dev_addr_set(ndev, &mcli->lladdr);
>
> @@ -920,18 +966,10 @@ static int mctp_i2c_add_netdev(struct mctp_i2c_client *mcli,
> 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;
> + struct mctp_i2c_dev *midev = NULL;
>
> 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);
> + midev = mctp_i2c_find_dev(mcli, adap);
>
> if (midev)
> mctp_i2c_unregister(midev);
> @@ -968,6 +1006,121 @@ static bool mctp_i2c_adapter_match(struct i2c_adapter *adap, bool match_no_of)
> return of_property_read_bool(adap->dev.of_node, MCTP_I2C_OF_PROP);
> }
>
> +/* Returns the client owning @root's mux tree, or NULL.
> + * Call with driver_clients_lock held. The returned pointer is only valid
> + * while that lock is held.
> + */
> +static struct mctp_i2c_client *mctp_i2c_find_client(struct i2c_adapter *root)
> +{
> + struct mctp_i2c_client *mcli = NULL;
> +
> + WARN_ON(!mutex_is_locked(&driver_clients_lock));
> + list_for_each_entry(mcli, &driver_clients, list)
> + if (mcli->client->adapter == root)
> + return mcli;
> +
> + return NULL;
> +}
> +
> +/* A bus instantiated at runtime has no devicetree node on which to set the
> + * "mctp-controller" property, so this attribute is how userspace asks for its
> + * netdev once the adapter exists.
> + */
> +static ssize_t mctp_controller_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct i2c_adapter *root = NULL, *adap = NULL;
> + struct mctp_i2c_client *mcli;
> + bool present = false;
> +
> + adap = mctp_i2c_get_adapter(dev, &root);
> + if (!adap)
> + return -ENODEV;
> +
> + mutex_lock(&driver_clients_lock);
> + mcli = mctp_i2c_find_client(root);
> + if (mcli)
> + present = mctp_i2c_find_dev(mcli, adap);
> + mutex_unlock(&driver_clients_lock);
> +
> + return sysfs_emit(buf, "%d\n", present);
> +}
> +
> +static ssize_t mctp_controller_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct i2c_adapter *root = NULL, *adap = NULL;
> + struct mctp_i2c_client *mcli;
> + bool enable;
> + int rc;
> +
> + rc = kstrtobool(buf, &enable);
> + if (rc < 0)
> + return rc;
> +
> + adap = mctp_i2c_get_adapter(dev, &root);
> + if (!adap)
> + return -ENODEV;
> +
> + /* The root adapter's netdev follows the mctp-i2c-controller client
> + * itself, so it is not ours to add or remove here.
> + */
> + if (adap == root)
> + return -EINVAL;
> +
> + mutex_lock(&driver_clients_lock);
> + mcli = mctp_i2c_find_client(root);
> + if (!mcli) {
> + /* No mctp-i2c-controller on this mux tree's root */
> + rc = -ENODEV;
> + goto out;
> + }
> +
> + if (enable) {
> + if (mctp_i2c_find_dev(mcli, adap))
> + rc = 0; /* Already present, nothing to do */
> + else
> + rc = mctp_i2c_add_netdev(mcli, adap);
> + } else {
> + mctp_i2c_remove_netdev(mcli, adap);
> + rc = 0;
> + }
> +out:
> + mutex_unlock(&driver_clients_lock);
> +
> + return rc < 0 ? rc : count;
> +}
> +
> +static DEVICE_ATTR_RW(mctp_controller);
> +
> +static struct attribute *mctp_i2c_adapter_attrs[] = {
> + &dev_attr_mctp_controller.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group mctp_i2c_adapter_group = {
> + .attrs = mctp_i2c_adapter_attrs,
> +};
> +
> +/* The root adapter is skipped: its netdev follows the bound client. The group
> + * is never removed, since kernfs drops it when the adapter is deleted, and
> + * updating rather than creating lets a rebind accept an existing group.
Is there a use case for the sysfs attributes to be persistent even if the the
mctp-i2c-controller client is removed and re-added? To me it would be less
surprising if they started with their initial settings when it's re-added.
> + */
> +static void mctp_i2c_add_sysfs(struct i2c_adapter *adap,
> + struct i2c_adapter *root)
> +{
> + int rc;
> +
> + if (adap == root)
> + return;
> +
> + rc = sysfs_update_group(&adap->dev.kobj, &mctp_i2c_adapter_group);
> + if (rc < 0)
> + dev_warn(&adap->dev,
> + "Failed adding mctp_controller attribute, %d\n", rc);
> +}
> +
> /* Called for each existing i2c device (adapter or client) when a
> * new mctp-i2c client is probed.
> */
> @@ -981,37 +1134,62 @@ static int mctp_i2c_client_try_attach(struct device *dev, void *data)
> return 0;
> if (mcli->client->adapter != root)
> return 0;
> +
> + /* Unmarked busses get the attribute too, so userspace can enable them */
> + mctp_i2c_add_sysfs(adap, root);
> +
> /* 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);
> + /* @mcli cannot be unbound here: the driver core holds the device lock
> + * across probe, and an unbind needs that same lock.
> + */
> + mutex_lock(&driver_clients_lock);
> + mctp_i2c_add_netdev(mcli, adap);
> + mutex_unlock(&driver_clients_lock);
> +
> + return 0;
> }
>
> static void mctp_i2c_notify_add(struct device *dev)
> {
> - struct mctp_i2c_client *mcli = NULL, *m = NULL;
> + struct mctp_i2c_client *mcli = NULL;
> struct i2c_adapter *root = NULL, *adap = NULL;
> + bool have_client;
> + bool match;
> 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;
> + match = mctp_i2c_adapter_match(adap, false);
>
> - /* 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;
> - }
> - }
> + have_client = mctp_i2c_find_client(root);
> + mutex_unlock(&driver_clients_lock);
> +
> + if (!have_client)
> + return;
> +
> + /* Outside the lock: updating the group waits for a kernfs drain, and a
> + * concurrent mctp_controller_store() holds that reference while it
> + * waits for driver_clients_lock.
> + */
> + mctp_i2c_add_sysfs(adap, root);
> +
> + if (!match)
> + return;
>
> + mutex_lock(&driver_clients_lock);
> + /* The pointer from the first lookup does not outlive the dropped
> + * lock, so look the client up again.
> + */
> + mcli = mctp_i2c_find_client(root);
> if (mcli) {
> rc = mctp_i2c_add_netdev(mcli, adap);
> if (rc < 0)
> @@ -1030,36 +1208,32 @@ static void mctp_i2c_notify_del(struct device *dev)
> 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;
> - }
> - }
> + mcli = mctp_i2c_find_client(root);
> + if (mcli)
> + mctp_i2c_remove_netdev(mcli, adap);
> mutex_unlock(&driver_clients_lock);
> }
>
> static int mctp_i2c_probe(struct i2c_client *client)
> {
> - struct mctp_i2c_client *mcli = NULL;
> - int rc;
> + struct mctp_i2c_client *mcli;
>
> 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);
> + mutex_unlock(&driver_clients_lock);
> + return PTR_ERR(mcli);
> }
> + list_add(&mcli->list, &driver_clients);
> + mutex_unlock(&driver_clients_lock);
>
> - /* Add a netdev for adapters that have a 'mctp-controller' property */
> + /* Add a netdev for adapters that have a 'mctp-controller' property.
> + * Runs with the lock dropped, for the reason mctp_i2c_notify_add()
> + * gives.
> + */
> i2c_for_each_dev(mcli, mctp_i2c_client_try_attach);
> - rc = 0;
> -out:
> - mutex_unlock(&driver_clients_lock);
> - return rc;
> +
> + return 0;
> }
>
> static void mctp_i2c_remove(struct i2c_client *client)
Cheers,
Matt
prev parent reply other threads:[~2026-10-06 10:36 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-02 7:31 Kenny Hsieh
2026-10-06 8:01 ` netdev-bot+sashiko
2026-10-06 10:31 ` Matt Johnston [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=a1e2d7c0644234bd66b806134f7b803af741aa74.camel@codeconstruct.com.au \
--to=matt@codeconstruct.com.au \
--cc=andi.shyti@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=jk@codeconstruct.com.au \
--cc=kenny_hsieh@jabil.com \
--cc=kuba@kernel.org \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=peda@lysator.liu.se \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®