Thread (24 messages) 24 messages, 5 authors, 2022-03-16

Re: [RFCv3 2/6] i2c: add I2C Address Translator (ATR) support

From: Andy Shevchenko <hidden>
Date: 2022-02-08 11:38:27
Also in: linux-i2c, linux-media, lkml

On Mon, Feb 7, 2022 at 7:55 PM Luca Ceresoli [off-list ref] wrote:
An ATR is a device that looks similar to an i2c-mux: it has an I2C
slave "upstream" port and N master "downstream" ports, and forwards
transactions from upstream to the appropriate downstream port. But is
is different in that the forwarded transaction has a different slave
address. The address used on the upstream bus is called the "alias"
and is (potentially) different from the physical slave address of the
downstream chip.

Add a helper file (just like i2c-mux.c for a mux or switch) to allow
implementing ATR features in a device driver. The helper takes care or
adapter creation/destruction and translates addresses at each transaction.
Why I2C mux driver can't be updated to support this feature?

...
 RFCv1 was implemented inside i2c-mux.c and added yet more complexity
 there. RFCv2 creates a new file on its own, i2c-atr.c. Since many ATR
 features are not in a MUX and vice versa, the overlapping is low. This was
 almost a complete rewrite, but for the records here are the main
 differences from the old implementation:
While this is from a code perspective, maybe i2c mux and this one can
still share some parts?

...
+config I2C_ATR
+       tristate "I2C Address Translator (ATR) support"
+       help
+         Enable support for I2C Address Translator (ATR) chips.
+
+         An ATR allows accessing multiple I2C busses from a single
+         physical bus via address translation instead of bus selection as
+         i2c-muxes do.
What would be the module name?

...
+/**
Is this a kernel doc formatted documentation?
Haven't you got a warning?
+ * I2C Address Translator
+ *
+ * Copyright (c) 2019 Luca Ceresoli [off-list ref]
2019,2022?
+ *
+ * An I2C Address Translator (ATR) is a device with an I2C slave parent
+ * ("upstream") port and N I2C master child ("downstream") ports, and
+ * forwards transactions from upstream to the appropriate downstream port
+ * with a modified slave address. The address used on the parent bus is
+ * called the "alias" and is (potentially) different from the physical
+ * slave address of the child bus. Address translation is done by the
+ * hardware.
+ *
+ * An ATR looks similar to an i2c-mux except:
+ * - the address on the parent and child busses can be different
+ * - there is normally no need to select the child port; the alias used on
+ *   the parent bus implies it
+ *
+ * The ATR functionality can be provided by a chip with many other
+ * features. This file provides a helper to implement an ATR within your
+ * driver.
+ *
+ * The ATR creates a new I2C "child" adapter on each child bus. Adding
+ * devices on the child bus ends up in invoking the driver code to select
+ * an available alias. Maintaining an appropriate pool of available aliases
+ * and picking one for each new device is up to the driver implementer. The
+ * ATR maintains an table of currently assigned alias and uses it to modify
+ * all I2C transactions directed to devices on the child buses.
+ *
+ * A typical example follows.
+ *
+ * Topology:
+ *
+ *                       Slave X @ 0x10
+ *               .-----.   |
+ *   .-----.     |     |---+---- B
+ *   | CPU |--A--| ATR |
+ *   `-----'     |     |---+---- C
+ *               `-----'   |
+ *                       Slave Y @ 0x10
+ *
+ * Alias table:
+ *
+ *   Client  Alias
+ *   -------------
+ *      X    0x20
+ *      Y    0x30
+ *
+ * Transaction:
+ *
+ *  - Slave X driver sends a transaction (on adapter B), slave address 0x10
+ *  - ATR driver rewrites messages with address 0x20, forwards to adapter A
+ *  - Physical I2C transaction on bus A, slave address 0x20
+ *  - ATR chip propagates transaction on bus B with address translated to 0x10
+ *  - Slave X chip replies on bus B
+ *  - ATR chip forwards reply on bus A
+ *  - ATR driver rewrites messages with address 0x10
+ *  - Slave X driver gets back the msgs[], with reply and address 0x10
+ *
+ * Usage:
+ *
+ *  1. In your driver (typically in the probe function) add an ATR by
+ *     calling i2c_atr_new() passing your attach/detach callbacks
+ *  2. When the attach callback is called pick an appropriate alias,
+ *     configure it in your chip and return the chosen alias in the
+ *     alias_id parameter
+ *  3. When the detach callback is called, deconfigure the alias from
+ *     your chip and put it back in the pool for later usage
+ *
+ * Originally based on i2c-mux.c
+ */
Shouldn't this comment be somewhere under Documentation/ ?

...
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+static int i2c_atr_map_msgs(struct i2c_atr_chan *chan,
+                           struct i2c_msg msgs[], int num)
foo[] makes not much sense in the function parameter. *foo is what
will be used and it's explicit.

Can this be located on one line (similar question to make compact the
rest of the function declarations)?
+
Redundant blank line.

...
+       /* Ensure we have enough room to save the original addresses */
+       if (unlikely(chan->orig_addrs_size < num)) {
+               void *new_buf = kmalloc(num * sizeof(chan->orig_addrs[0]),
+                                       GFP_KERNEL);
Use kmalloc_array()
+               if (new_buf == NULL)
+                       return -ENOMEM;
+
+               kfree(chan->orig_addrs);
Hmm... is it a reimplementation of krealloc_array()?
+               chan->orig_addrs = new_buf;
+               chan->orig_addrs_size = num;
+       }
...
+               if (c2a) {
+                       msgs[i].addr = c2a->alias;
+               } else {
+                       dev_err(atr->dev, "client 0x%02x not mapped!\n",
+                               msgs[i].addr);
+                       return -ENXIO;
+               }
'else' would be redundant if you switch to the traditional pattern,
i.e. check for errors first.

...
+/*
+ * Restore all message address aliases with the original addresses.
+ *
+ * This function is internal for use in i2c_atr_master_xfer().
+ *
+ * @see i2c_atr_map_msgs()
+ */
Too sparse formatting of the comment. Can you make it compact?

...
+       int ret = 0;
Unneeded assignment.
+       /* Switch to the right atr port */
+       if (atr->ops->select) {
+               ret = atr->ops->select(atr, chan->chan_id);
+               if (ret < 0)
+                       goto out;
+       }
+
+       /* Translate addresses */
+       mutex_lock(&chan->orig_addrs_lock);
+       ret = i2c_atr_map_msgs(chan, msgs, num);
+       if (ret < 0) {
+               mutex_unlock(&chan->orig_addrs_lock);
+               goto out;
goto out_unlock_deselect;
+       }
+
+       /* Perform the transfer */
+       ret = i2c_transfer(parent, msgs, num);
+
+       /* Restore addresses */
+       i2c_atr_unmap_msgs(chan, msgs, num);
out_unlock_deselct:
+       mutex_unlock(&chan->orig_addrs_lock);
+out:
out_deselect:
+       if (atr->ops->deselect)
+               atr->ops->deselect(atr, chan->chan_id);
+
+       return ret;
+}
...
+       int err = 0;
Be consistent with ret vs. err across the functions.
+       if (atr->ops->select)
+               err = atr->ops->select(atr, chan->chan_id);
+       if (!err)
Perhaps

       int ret;

       ret = 0;
       if (atr->ops->select)
               ret = atr->ops->select(atr, chan->chan_id);
       if (ret)
               goto out_deselect;

+               err = i2c_smbus_xfer(parent, c2a->alias, flags,
+                                    read_write, command, size, data);
out_deselect:
+       if (atr->ops->deselect)
+               atr->ops->deselect(atr, chan->chan_id);
+
+       return err;
+}
...
+       int err = 0;
Same as above: naming, useless assignment.

...
+       c2a = kzalloc(sizeof(struct i2c_atr_cli2alias_pair), GFP_KERNEL);
sizeof(*c2a)
+       if (!c2a) {
+               err = -ENOMEM;
+               goto err_alloc;
Useless label, return directly.
+       }
...
+       c2a = i2c_atr_find_mapping_by_client(&chan->alias_list, client);
+       if (c2a != NULL) {
if (c2a)
+               list_del(&c2a->node);
+               kfree(c2a);
+       }
...
+       char symlink_name[20];
Why 20? Do we have a predefined constant for that?

+       if (dev->of_node) {
This check can be dropped, also please use device property and fwnode
APIs. No good of having OF-centric generic modules nowadays.
+               struct device_node *atr_node;
+               struct device_node *child;
+               u32 reg;
+
+               atr_node = of_get_child_by_name(dev->of_node, "i2c-atr");
atr_node = device_get_named_child_node(...);

fwnode_for_each_child_node() {
}
+               for_each_child_of_node(atr_node, child) {
+                       err = of_property_read_u32(child, "reg", &reg);
+                       if (err)
+                               continue;
+                       if (chan_id == reg)
+                               break;
+               }
+
+               chan->adap.dev.of_node = child;
+               of_node_put(atr_node);
+       }
On the second thought can you utilize the parser from I2C mux?

...
+       WARN(sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device"),
+            "can't create symlink to atr device\n");
+       snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan_id);
+       WARN(sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name),
+            "can't create symlink for channel %u\n", chan_id);
Doesn't sysfs already has a warning when it's really needed?

...
+       if (atr->adapter[chan_id] == NULL) {
+               dev_err(dev, "Adapter %d does not exist\n", chan_id);
Noisy message. On freeing we usually don't issue such when we try to
free already freeed resource.
+               return;
+       }
...
+       atr = devm_kzalloc(dev, sizeof(*atr)
+                           + max_adapters * sizeof(atr->adapter[0]),
+                           GFP_KERNEL);
Check overflow.h and use respective macro here.
+       if (!atr)
+               return ERR_PTR(-ENOMEM);
...
+/**
It's not a kernel doc.
+ * drivers/i2c/i2c-atr.h -- I2C Address Translator
Please, no names of the files inside the files.
+ * Copyright (c) 2019 Luca Ceresoli [off-list ref]
2019,2022 ?
+ * Based on i2c-mux.h
+ */
...
+#ifdef __KERNEL__
Why?

...
+#include <linux/i2c.h>
+#include <linux/mutex.h>
Missed types.h

Missed struct device;

-- 
With Best Regards,
Andy Shevchenko
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help