Skip to content
Snippets Groups Projects
fmc-core.c 10.9 KiB
Newer Older
// 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>
Federico Vaga's avatar
Federico Vaga committed
#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);
	ida_simple_remove(&fmc_slot_ida, slot->dev.id);
}

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);
}

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;

	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;
	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;