// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2019 CERN * Author: Federico Vaga <federico.vaga@cern.ch> */ #include <linux/fmc.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/device.h> #include <linux/export.h> #include <linux/module.h> #include "fmc-internal.h" #include "fmc-compat.h" /* * @todo perhaps use the IDR so that we can get pointers as well */ static DEFINE_IDA(fmc_carrier_ida); static DEFINE_IDA(fmc_slot_ida); static struct class fmc_class; /** * fmc_slot_present() - Check if an FMC slot is present in a carrier slot * @slot: FMC slot to verify * * Return: 1 if the slot is present, 0 if it is not present, otherwise a * negative error number */ int fmc_slot_present(struct fmc_slot *slot) { struct fmc_carrier *carrier = to_fmc_carrier(slot->dev.parent); int ret; if (!try_module_get(carrier->ops->owner)) return -ENODEV; ret = !!carrier->ops->is_present(carrier, slot); module_put(carrier->ops->owner); return ret; } EXPORT_SYMBOL(fmc_slot_present); static ssize_t fmc_ga_show(struct device *dev, struct device_attribute *attr, char *buf) { struct fmc_slot *slot = to_fmc_slot(dev); return sprintf(buf, "0x%x\n", slot->ga); } static DEVICE_ATTR_RO(fmc_ga); static ssize_t present_show(struct device *dev, struct device_attribute *attr, char *buf) { struct fmc_slot *slot = to_fmc_slot(dev); return sprintf(buf, "%d\n", fmc_slot_present(slot)); } static DEVICE_ATTR_RO(present); static struct attribute *fmc_slot_attrs[] = { &dev_attr_fmc_ga.attr, &dev_attr_present.attr, NULL, }; static const struct attribute_group fmc_slot_group = { .attrs = fmc_slot_attrs, }; static const struct attribute_group *fmc_slot_groups[] = { &fmc_slot_group, &fmc_slot_eeprom_group, NULL, }; static void fmc_slot_release(struct device *dev) { struct fmc_slot *slot = to_fmc_slot(dev); i2c_put_adapter(slot->adapter); slot->adapter = NULL; ida_simple_remove(&fmc_slot_ida, slot->dev.id); slot->dev.id = -1; } static const struct device_type fmc_slot_type = { .groups = fmc_slot_groups, .name = "fmc-slot", .release = fmc_slot_release, }; static struct attribute *fmc_carrier_attrs[] = { NULL, }; static const struct attribute_group fmc_carrier_group = { .attrs = fmc_carrier_attrs, }; static const struct attribute_group *fmc_carrier_groups[] = { &fmc_carrier_group, NULL, }; static void fmc_carrier_release(struct device *dev) { struct fmc_carrier *carrier = to_fmc_carrier(dev); ida_simple_remove(&fmc_carrier_ida, carrier->dev.id); carrier->dev.id = -1; } static const struct device_type fmc_carrier_type = { .groups = fmc_carrier_groups, .name = "fmc-carrier", .release = fmc_carrier_release, }; /** * fmc_slot_id() - Generate a device ID for slot * @carrier: an FMC carrier instance (already configured) * @lun slot Logical Unit Number * * Return: a valid slot device ID */ static inline uint32_t fmc_slot_id(struct fmc_carrier *carrier, unsigned int lun) { return (carrier->dev.id << 16) | lun; } /** * fmc_carrier_add_slot() - Add an initialized slot to the given carrier * @carrier: the carrier instance * @info: slot information from the carrier driver * * Return: 0 on success, otherwise a negative error number */ static struct fmc_slot *fmc_carrier_add_slot(struct fmc_carrier *carrier, struct fmc_slot_info *info) { struct fmc_slot *slot; int ret, err; if (WARN(!info, "Invalid slot info")) return ERR_PTR(-EINVAL); slot = devm_kzalloc(&carrier->dev, sizeof(struct fmc_slot), GFP_KERNEL); if (!slot) { err = -ENOMEM; goto err_alloc; } slot->ga = info->ga; slot->lun = info->lun; slot->dev.class = &fmc_class; slot->dev.type = &fmc_slot_type; slot->dev.parent = &carrier->dev; slot->dev.id = fmc_slot_id(carrier, info->lun); slot->dev.id = ida_simple_get(&fmc_slot_ida, slot->dev.id, slot->dev.id + 1, GFP_KERNEL); if (slot->dev.id < 0) { dev_err(&carrier->dev, "can't assign ID to slot (LUN: %dGA: 0x%02x)\n", info->lun, info->ga); err = slot->dev.id; goto err_ida; } slot->adapter = i2c_get_adapter(info->i2c_bus_nr); if (!slot->adapter) { err = -ENODEV; goto err_i2c; } err = dev_set_name(&slot->dev, "fmc-slot-%d.%d", carrier->dev.id, slot->lun); if (err) goto err_name; err = device_register(&slot->dev); if (err) { put_device(&slot->dev); goto err_reg; } ret = fmc_slot_present(slot); if (ret <= 0) return slot; err = fmc_slot_eeprom_add(slot); if (err) goto err_eeprom; return slot; err_eeprom: device_unregister(&slot->dev); err_reg: err_name: i2c_put_adapter(slot->adapter); err_i2c: ida_simple_remove(&fmc_slot_ida, slot->dev.id); err_ida: devm_kfree(&carrier->dev, slot); err_alloc: return ERR_PTR(err); } /** * fmc_carrier_del_slot() - Remove the given slot from its carrier * @slot: a previously added slot * * NOTE: previously added with fmc_carrier_add_slot() */ static void fmc_carrier_del_slot(struct fmc_slot *slot) { if (IS_ERR_OR_NULL(slot)) return; fmc_slot_eeprom_del(slot); device_unregister(&slot->dev); } /** * fmc_carrier_register() - Register an FMC carrier device * @parent: the parent device for the FMC carrier * @ops: carrier operations * @nr_slot: number of available slots * @slot_info: information from the carrier about the slots * @priv: private data * * Return: 0 on success, otherwise a negative error code */ int fmc_carrier_register(struct device *parent, const struct fmc_carrier_operations *ops, unsigned int nr_slot, struct fmc_slot_info *slot_info, void *priv) { struct fmc_carrier *carrier; int err, i; ssize_t carrier_mem_size; void *tmp_p; if (!parent) { pr_err("FMC: the FMC carrier must have a parent device\n"); return -ENODEV; } if (!ops) { pr_err("FMC: the FMC carrier operations are missing\n"); return -EINVAL; } if (!ops->is_present) { pr_err("FMC: the FMC carrier operation `is_present` is missing\n"); return -EINVAL; } if (!slot_info) { pr_err("FMC: now slot information provided\n"); return -EINVAL; } carrier_mem_size = sizeof(struct fmc_carrier); carrier_mem_size += sizeof(struct fmc_slot *) * nr_slot; carrier = devm_kzalloc(parent, carrier_mem_size, GFP_KERNEL); if (!carrier) { err = -ENOMEM; goto err_alloc; } carrier->priv = priv; carrier->slot_nr = nr_slot; tmp_p = ((char *)carrier) + sizeof(struct fmc_carrier); carrier->slot = tmp_p; carrier->ops = ops; carrier->dev.class = &fmc_class; carrier->dev.type = &fmc_carrier_type; carrier->dev.parent = parent; carrier->dev.id = ida_simple_get(&fmc_carrier_ida, 0, 0, GFP_KERNEL); if (carrier->dev.id < 0) { dev_err(parent, "FMC can't assign ID to carrier\n"); err = carrier->dev.id; goto err_ida; } err = dev_set_name(&carrier->dev, "fmc-carrier-%x", carrier->dev.id); if (err) goto err_name; err = device_register(&carrier->dev); if (err) { put_device(&carrier->dev); goto err_reg; } for (i = 0; i < carrier->slot_nr; ++i) { carrier->slot[i] = fmc_carrier_add_slot(carrier, &slot_info[i]); if (IS_ERR_OR_NULL(carrier->slot[i])) { dev_err(&carrier->dev, "Failed to add slot %d\n", i); err = -EINVAL; goto err_slot; } } return 0; err_slot: while (--i >= 0) fmc_carrier_del_slot(carrier->slot[i]); device_unregister(&carrier->dev); err_reg: err_name: ida_simple_remove(&fmc_carrier_ida, carrier->dev.id); err_ida: devm_kfree(parent, carrier); err_alloc: dev_err(parent, "FMC carrier registration: failed %d\n", err); return err; } EXPORT_SYMBOL(fmc_carrier_register); /** * fmc_carrier_unregister() - Unregister an FMC carrier instance * @parent: parent device * * Return: 0 on success, otherwise a negative error code */ int fmc_carrier_unregister(struct device *parent) { struct fmc_carrier *carrier; int i; carrier = fmc_carrier_get(parent); if (IS_ERR_OR_NULL(carrier)) return PTR_ERR(carrier); for (i = 0; i < carrier->slot_nr; ++i) fmc_carrier_del_slot(carrier->slot[i]); device_unregister(&carrier->dev); fmc_carrier_put(carrier); return 0; } EXPORT_SYMBOL(fmc_carrier_unregister); /** * __fmc_class_find_carrier_match() - match device with carrier * @dev: fmc carrier from the loop * @data: fmc slot looking for its parent * * A device matches only when the device instance is a carrier and it has * the parent we asked. */ static int __fmc_class_find_carrier_match(struct device *dev, const void *data) { const struct device *parent = data; return (dev->type == &fmc_carrier_type && dev->parent == parent); } /** * fmc_carrier_get() - Retrieve, and pin, the FMC carrier of a device * @parent: parent device * * This pointer must be released with a call to fmc_carrier_put() * * Return: It returns a pointer to an fmc_carrier, otherwise ERR_PTR(-ENODEV) */ struct fmc_carrier *fmc_carrier_get(struct device *parent) { struct device *dev; dev = class_find_device(&fmc_class, NULL, parent, __fmc_class_find_carrier_match); return dev ? to_fmc_carrier(dev) : ERR_PTR(-ENODEV); } EXPORT_SYMBOL(fmc_carrier_get); /** * fmc_carrier_put() - Release the given FMC carrier * @carrier: FMC carrier to release */ void fmc_carrier_put(struct fmc_carrier *carrier) { put_device(&carrier->dev); } EXPORT_SYMBOL(fmc_carrier_put); /** * fmc_slot_get() - Retrieve, and pin, the FMC slot of a slot * @parent: parent device * @lun: Slot Logical Unit Number * * This pointer must be released with a call to fmc_slot_put() * * Return: It returns a pointer to an fmc_carrier, otherwise ERR_PTR(-ENODEV) */ struct fmc_slot *fmc_slot_get(struct device *parent, unsigned int lun) { struct fmc_carrier *carrier = fmc_carrier_get(parent); struct fmc_slot *slot; int i; if (IS_ERR(carrier)) return (struct fmc_slot *)carrier; for (i = 0; i < carrier->slot_nr; ++i) { slot = carrier->slot[i]; if (slot->dev.id == fmc_slot_id(carrier, lun)) break; } if (i == carrier->slot_nr) { fmc_carrier_put(carrier); return ERR_PTR(-ENODEV); } get_device(&slot->dev); return slot; } EXPORT_SYMBOL(fmc_slot_get); /** * fmc_slot_put() - Releases the given FMC slot * @slot: FMC slot to release */ void fmc_slot_put(struct fmc_slot *slot) { struct fmc_carrier *carrier = to_fmc_carrier(slot->dev.parent); put_device(&slot->dev); fmc_carrier_put(carrier); } EXPORT_SYMBOL(fmc_slot_put); static void fmc_class_release(struct class *cls) { ida_destroy(&fmc_carrier_ida); ida_destroy(&fmc_slot_ida); } static struct class fmc_class = { .name = "fmc", .owner = THIS_MODULE, .class_release = fmc_class_release, /* we use the device release from device_type */ }; static int __init fmc_init(void) { int ret; ret = request_module("at24"); if (ret) pr_warn("fmc: Could not load module 'at24' required for eeprom."); return class_register(&fmc_class); } static void __exit fmc_exit(void) { class_unregister(&fmc_class); } MODULE_AUTHOR("Federico Vaga <federico.vaga@cern.ch>"); MODULE_DESCRIPTION("FMC framework"); MODULE_LICENSE("GPL v2"); subsys_initcall(fmc_init); module_exit(fmc_exit); ADDITIONAL_VERSIONS;