Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Luca Ceresoli | 3040 | 99.84% | 1 | 50.00% |
Kees Cook | 5 | 0.16% | 1 | 50.00% |
Total | 3045 | 2 |
// SPDX-License-Identifier: GPL-2.0 /* * I2C Address Translator * * Copyright (c) 2019,2022 Luca Ceresoli <luca@lucaceresoli.net> * Copyright (c) 2022,2023 Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> * * Originally based on i2c-mux.c */ #include <linux/fwnode.h> #include <linux/i2c-atr.h> #include <linux/i2c.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/spinlock.h> #define ATR_MAX_ADAPTERS 100 /* Just a sanity limit */ #define ATR_MAX_SYMLINK_LEN 11 /* Longest name is 10 chars: "channel-99" */ /** * struct i2c_atr_alias_pair - Holds the alias assigned to a client. * @node: List node * @client: Pointer to the client on the child bus * @alias: I2C alias address assigned by the driver. * This is the address that will be used to issue I2C transactions * on the parent (physical) bus. */ struct i2c_atr_alias_pair { struct list_head node; const struct i2c_client *client; u16 alias; }; /** * struct i2c_atr_chan - Data for a channel. * @adap: The &struct i2c_adapter for the channel * @atr: The parent I2C ATR * @chan_id: The ID of this channel * @alias_list: List of @struct i2c_atr_alias_pair containing the * assigned aliases * @orig_addrs_lock: Mutex protecting @orig_addrs * @orig_addrs: Buffer used to store the original addresses during transmit * @orig_addrs_size: Size of @orig_addrs */ struct i2c_atr_chan { struct i2c_adapter adap; struct i2c_atr *atr; u32 chan_id; struct list_head alias_list; /* Lock orig_addrs during xfer */ struct mutex orig_addrs_lock; u16 *orig_addrs; unsigned int orig_addrs_size; }; /** * struct i2c_atr - The I2C ATR instance * @parent: The parent &struct i2c_adapter * @dev: The device that owns the I2C ATR instance * @ops: &struct i2c_atr_ops * @priv: Private driver data, set with i2c_atr_set_driver_data() * @algo: The &struct i2c_algorithm for adapters * @lock: Lock for the I2C bus segment (see &struct i2c_lock_operations) * @max_adapters: Maximum number of adapters this I2C ATR can have * @num_aliases: Number of aliases in the aliases array * @aliases: The aliases array * @alias_mask_lock: Lock protecting alias_use_mask * @alias_use_mask: Bitmask for used aliases in aliases array * @i2c_nb: Notifier for remote client add & del events * @adapter: Array of adapters */ struct i2c_atr { struct i2c_adapter *parent; struct device *dev; const struct i2c_atr_ops *ops; void *priv; struct i2c_algorithm algo; /* lock for the I2C bus segment (see struct i2c_lock_operations) */ struct mutex lock; int max_adapters; size_t num_aliases; const u16 *aliases; /* Protects alias_use_mask */ spinlock_t alias_mask_lock; unsigned long *alias_use_mask; struct notifier_block i2c_nb; struct i2c_adapter *adapter[] __counted_by(max_adapters); }; static struct i2c_atr_alias_pair * i2c_atr_find_mapping_by_client(const struct list_head *list, const struct i2c_client *client) { struct i2c_atr_alias_pair *c2a; list_for_each_entry(c2a, list, node) { if (c2a->client == client) return c2a; } return NULL; } static struct i2c_atr_alias_pair * i2c_atr_find_mapping_by_addr(const struct list_head *list, u16 phys_addr) { struct i2c_atr_alias_pair *c2a; list_for_each_entry(c2a, list, node) { if (c2a->client->addr == phys_addr) return c2a; } return NULL; } /* * Replace all message addresses with their aliases, saving the original * addresses. * * This function is internal for use in i2c_atr_master_xfer(). It must be * followed by i2c_atr_unmap_msgs() to restore the original addresses. */ static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, int num) { struct i2c_atr *atr = chan->atr; static struct i2c_atr_alias_pair *c2a; int i; /* Ensure we have enough room to save the original addresses */ if (unlikely(chan->orig_addrs_size < num)) { u16 *new_buf; /* We don't care about old data, hence no realloc() */ new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL); if (!new_buf) return -ENOMEM; kfree(chan->orig_addrs); chan->orig_addrs = new_buf; chan->orig_addrs_size = num; } for (i = 0; i < num; i++) { chan->orig_addrs[i] = msgs[i].addr; c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, msgs[i].addr); if (!c2a) { dev_err(atr->dev, "client 0x%02x not mapped!\n", msgs[i].addr); while (i--) msgs[i].addr = chan->orig_addrs[i]; return -ENXIO; } msgs[i].addr = c2a->alias; } return 0; } /* * Restore all message address aliases with the original addresses. This * function is internal for use in i2c_atr_master_xfer() and for this reason it * needs no null and size checks on orig_addr. * * @see i2c_atr_map_msgs() */ static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, int num) { int i; for (i = 0; i < num; i++) msgs[i].addr = chan->orig_addrs[i]; } static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct i2c_atr_chan *chan = adap->algo_data; struct i2c_atr *atr = chan->atr; struct i2c_adapter *parent = atr->parent; int ret; /* Translate addresses */ mutex_lock(&chan->orig_addrs_lock); ret = i2c_atr_map_msgs(chan, msgs, num); if (ret < 0) goto err_unlock; /* Perform the transfer */ ret = i2c_transfer(parent, msgs, num); /* Restore addresses */ i2c_atr_unmap_msgs(chan, msgs, num); err_unlock: mutex_unlock(&chan->orig_addrs_lock); return ret; } static int i2c_atr_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) { struct i2c_atr_chan *chan = adap->algo_data; struct i2c_atr *atr = chan->atr; struct i2c_adapter *parent = atr->parent; struct i2c_atr_alias_pair *c2a; c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, addr); if (!c2a) { dev_err(atr->dev, "client 0x%02x not mapped!\n", addr); return -ENXIO; } return i2c_smbus_xfer(parent, c2a->alias, flags, read_write, command, size, data); } static u32 i2c_atr_functionality(struct i2c_adapter *adap) { struct i2c_atr_chan *chan = adap->algo_data; struct i2c_adapter *parent = chan->atr->parent; return parent->algo->functionality(parent); } static void i2c_atr_lock_bus(struct i2c_adapter *adapter, unsigned int flags) { struct i2c_atr_chan *chan = adapter->algo_data; struct i2c_atr *atr = chan->atr; mutex_lock(&atr->lock); } static int i2c_atr_trylock_bus(struct i2c_adapter *adapter, unsigned int flags) { struct i2c_atr_chan *chan = adapter->algo_data; struct i2c_atr *atr = chan->atr; return mutex_trylock(&atr->lock); } static void i2c_atr_unlock_bus(struct i2c_adapter *adapter, unsigned int flags) { struct i2c_atr_chan *chan = adapter->algo_data; struct i2c_atr *atr = chan->atr; mutex_unlock(&atr->lock); } static const struct i2c_lock_operations i2c_atr_lock_ops = { .lock_bus = i2c_atr_lock_bus, .trylock_bus = i2c_atr_trylock_bus, .unlock_bus = i2c_atr_unlock_bus, }; static int i2c_atr_reserve_alias(struct i2c_atr *atr) { unsigned long idx; spin_lock(&atr->alias_mask_lock); idx = find_first_zero_bit(atr->alias_use_mask, atr->num_aliases); if (idx >= atr->num_aliases) { spin_unlock(&atr->alias_mask_lock); dev_err(atr->dev, "failed to find a free alias\n"); return -EBUSY; } set_bit(idx, atr->alias_use_mask); spin_unlock(&atr->alias_mask_lock); return atr->aliases[idx]; } static void i2c_atr_release_alias(struct i2c_atr *atr, u16 alias) { unsigned int idx; spin_lock(&atr->alias_mask_lock); for (idx = 0; idx < atr->num_aliases; ++idx) { if (atr->aliases[idx] == alias) { clear_bit(idx, atr->alias_use_mask); spin_unlock(&atr->alias_mask_lock); return; } } spin_unlock(&atr->alias_mask_lock); /* This should never happen */ dev_warn(atr->dev, "Unable to find mapped alias\n"); } static int i2c_atr_attach_client(struct i2c_adapter *adapter, const struct i2c_client *client) { struct i2c_atr_chan *chan = adapter->algo_data; struct i2c_atr *atr = chan->atr; struct i2c_atr_alias_pair *c2a; u16 alias; int ret; ret = i2c_atr_reserve_alias(atr); if (ret < 0) return ret; alias = ret; c2a = kzalloc(sizeof(*c2a), GFP_KERNEL); if (!c2a) { ret = -ENOMEM; goto err_release_alias; } ret = atr->ops->attach_client(atr, chan->chan_id, client, alias); if (ret) goto err_free; dev_dbg(atr->dev, "chan%u: client 0x%02x mapped at alias 0x%02x (%s)\n", chan->chan_id, client->addr, alias, client->name); c2a->client = client; c2a->alias = alias; list_add(&c2a->node, &chan->alias_list); return 0; err_free: kfree(c2a); err_release_alias: i2c_atr_release_alias(atr, alias); return ret; } static void i2c_atr_detach_client(struct i2c_adapter *adapter, const struct i2c_client *client) { struct i2c_atr_chan *chan = adapter->algo_data; struct i2c_atr *atr = chan->atr; struct i2c_atr_alias_pair *c2a; atr->ops->detach_client(atr, chan->chan_id, client); c2a = i2c_atr_find_mapping_by_client(&chan->alias_list, client); if (!c2a) { /* This should never happen */ dev_warn(atr->dev, "Unable to find address mapping\n"); return; } i2c_atr_release_alias(atr, c2a->alias); dev_dbg(atr->dev, "chan%u: client 0x%02x unmapped from alias 0x%02x (%s)\n", chan->chan_id, client->addr, c2a->alias, client->name); list_del(&c2a->node); kfree(c2a); } static int i2c_atr_bus_notifier_call(struct notifier_block *nb, unsigned long event, void *device) { struct i2c_atr *atr = container_of(nb, struct i2c_atr, i2c_nb); struct device *dev = device; struct i2c_client *client; u32 chan_id; int ret; client = i2c_verify_client(dev); if (!client) return NOTIFY_DONE; /* Is the client in one of our adapters? */ for (chan_id = 0; chan_id < atr->max_adapters; ++chan_id) { if (client->adapter == atr->adapter[chan_id]) break; } if (chan_id == atr->max_adapters) return NOTIFY_DONE; switch (event) { case BUS_NOTIFY_ADD_DEVICE: ret = i2c_atr_attach_client(client->adapter, client); if (ret) dev_err(atr->dev, "Failed to attach remote client '%s': %d\n", dev_name(dev), ret); break; case BUS_NOTIFY_DEL_DEVICE: i2c_atr_detach_client(client->adapter, client); break; default: break; } return NOTIFY_DONE; } static int i2c_atr_parse_alias_pool(struct i2c_atr *atr) { struct device *dev = atr->dev; unsigned long *alias_use_mask; size_t num_aliases; unsigned int i; u32 *aliases32; u16 *aliases16; int ret; ret = fwnode_property_count_u32(dev_fwnode(dev), "i2c-alias-pool"); if (ret < 0) { dev_err(dev, "Failed to count 'i2c-alias-pool' property: %d\n", ret); return ret; } num_aliases = ret; if (!num_aliases) return 0; aliases32 = kcalloc(num_aliases, sizeof(*aliases32), GFP_KERNEL); if (!aliases32) return -ENOMEM; ret = fwnode_property_read_u32_array(dev_fwnode(dev), "i2c-alias-pool", aliases32, num_aliases); if (ret < 0) { dev_err(dev, "Failed to read 'i2c-alias-pool' property: %d\n", ret); goto err_free_aliases32; } aliases16 = kcalloc(num_aliases, sizeof(*aliases16), GFP_KERNEL); if (!aliases16) { ret = -ENOMEM; goto err_free_aliases32; } for (i = 0; i < num_aliases; i++) { if (!(aliases32[i] & 0xffff0000)) { aliases16[i] = aliases32[i]; continue; } dev_err(dev, "Failed to parse 'i2c-alias-pool' property: I2C flags are not supported\n"); ret = -EINVAL; goto err_free_aliases16; } alias_use_mask = bitmap_zalloc(num_aliases, GFP_KERNEL); if (!alias_use_mask) { ret = -ENOMEM; goto err_free_aliases16; } kfree(aliases32); atr->num_aliases = num_aliases; atr->aliases = aliases16; atr->alias_use_mask = alias_use_mask; dev_dbg(dev, "i2c-alias-pool has %zu aliases", atr->num_aliases); return 0; err_free_aliases16: kfree(aliases16); err_free_aliases32: kfree(aliases32); return ret; } struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, const struct i2c_atr_ops *ops, int max_adapters) { struct i2c_atr *atr; int ret; if (max_adapters > ATR_MAX_ADAPTERS) return ERR_PTR(-EINVAL); if (!ops || !ops->attach_client || !ops->detach_client) return ERR_PTR(-EINVAL); atr = kzalloc(struct_size(atr, adapter, max_adapters), GFP_KERNEL); if (!atr) return ERR_PTR(-ENOMEM); mutex_init(&atr->lock); spin_lock_init(&atr->alias_mask_lock); atr->parent = parent; atr->dev = dev; atr->ops = ops; atr->max_adapters = max_adapters; if (parent->algo->master_xfer) atr->algo.master_xfer = i2c_atr_master_xfer; if (parent->algo->smbus_xfer) atr->algo.smbus_xfer = i2c_atr_smbus_xfer; atr->algo.functionality = i2c_atr_functionality; ret = i2c_atr_parse_alias_pool(atr); if (ret) goto err_destroy_mutex; atr->i2c_nb.notifier_call = i2c_atr_bus_notifier_call; ret = bus_register_notifier(&i2c_bus_type, &atr->i2c_nb); if (ret) goto err_free_aliases; return atr; err_free_aliases: bitmap_free(atr->alias_use_mask); kfree(atr->aliases); err_destroy_mutex: mutex_destroy(&atr->lock); kfree(atr); return ERR_PTR(ret); } EXPORT_SYMBOL_NS_GPL(i2c_atr_new, I2C_ATR); void i2c_atr_delete(struct i2c_atr *atr) { unsigned int i; for (i = 0; i < atr->max_adapters; ++i) WARN_ON(atr->adapter[i]); bus_unregister_notifier(&i2c_bus_type, &atr->i2c_nb); bitmap_free(atr->alias_use_mask); kfree(atr->aliases); mutex_destroy(&atr->lock); kfree(atr); } EXPORT_SYMBOL_NS_GPL(i2c_atr_delete, I2C_ATR); int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, struct device *adapter_parent, struct fwnode_handle *bus_handle) { struct i2c_adapter *parent = atr->parent; struct device *dev = atr->dev; struct i2c_atr_chan *chan; char symlink_name[ATR_MAX_SYMLINK_LEN]; int ret; if (chan_id >= atr->max_adapters) { dev_err(dev, "No room for more i2c-atr adapters\n"); return -EINVAL; } if (atr->adapter[chan_id]) { dev_err(dev, "Adapter %d already present\n", chan_id); return -EEXIST; } chan = kzalloc(sizeof(*chan), GFP_KERNEL); if (!chan) return -ENOMEM; if (!adapter_parent) adapter_parent = dev; chan->atr = atr; chan->chan_id = chan_id; INIT_LIST_HEAD(&chan->alias_list); mutex_init(&chan->orig_addrs_lock); snprintf(chan->adap.name, sizeof(chan->adap.name), "i2c-%d-atr-%d", i2c_adapter_id(parent), chan_id); chan->adap.owner = THIS_MODULE; chan->adap.algo = &atr->algo; chan->adap.algo_data = chan; chan->adap.dev.parent = adapter_parent; chan->adap.retries = parent->retries; chan->adap.timeout = parent->timeout; chan->adap.quirks = parent->quirks; chan->adap.lock_ops = &i2c_atr_lock_ops; if (bus_handle) { device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle)); } else { struct fwnode_handle *atr_node; struct fwnode_handle *child; u32 reg; atr_node = device_get_named_child_node(dev, "i2c-atr"); fwnode_for_each_child_node(atr_node, child) { ret = fwnode_property_read_u32(child, "reg", ®); if (ret) continue; if (chan_id == reg) break; } device_set_node(&chan->adap.dev, child); fwnode_handle_put(atr_node); } atr->adapter[chan_id] = &chan->adap; ret = i2c_add_adapter(&chan->adap); if (ret) { dev_err(dev, "failed to add atr-adapter %u (error=%d)\n", chan_id, ret); goto err_fwnode_put; } snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan->chan_id); ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device"); if (ret) dev_warn(dev, "can't create symlink to atr device\n"); ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name); if (ret) dev_warn(dev, "can't create symlink for channel %u\n", chan_id); dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap)); return 0; err_fwnode_put: fwnode_handle_put(dev_fwnode(&chan->adap.dev)); mutex_destroy(&chan->orig_addrs_lock); kfree(chan); return ret; } EXPORT_SYMBOL_NS_GPL(i2c_atr_add_adapter, I2C_ATR); void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id) { char symlink_name[ATR_MAX_SYMLINK_LEN]; struct i2c_adapter *adap; struct i2c_atr_chan *chan; struct fwnode_handle *fwnode; struct device *dev = atr->dev; adap = atr->adapter[chan_id]; if (!adap) return; chan = adap->algo_data; fwnode = dev_fwnode(&adap->dev); dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap)); snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan->chan_id); sysfs_remove_link(&dev->kobj, symlink_name); sysfs_remove_link(&chan->adap.dev.kobj, "atr_device"); i2c_del_adapter(adap); atr->adapter[chan_id] = NULL; fwnode_handle_put(fwnode); mutex_destroy(&chan->orig_addrs_lock); kfree(chan->orig_addrs); kfree(chan); } EXPORT_SYMBOL_NS_GPL(i2c_atr_del_adapter, I2C_ATR); void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data) { atr->priv = data; } EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR); void *i2c_atr_get_driver_data(struct i2c_atr *atr) { return atr->priv; } EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR); MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>"); MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>"); MODULE_DESCRIPTION("I2C Address Translator"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1