diff options
Diffstat (limited to 'drivers/fmc')
-rw-r--r-- | drivers/fmc/Kconfig | 51 | ||||
-rw-r--r-- | drivers/fmc/Makefile | 15 | ||||
-rw-r--r-- | drivers/fmc/fmc-chardev.c | 200 | ||||
-rw-r--r-- | drivers/fmc/fmc-core.c | 389 | ||||
-rw-r--r-- | drivers/fmc/fmc-debug.c | 173 | ||||
-rw-r--r-- | drivers/fmc/fmc-dump.c | 59 | ||||
-rw-r--r-- | drivers/fmc/fmc-fakedev.c | 355 | ||||
-rw-r--r-- | drivers/fmc/fmc-match.c | 114 | ||||
-rw-r--r-- | drivers/fmc/fmc-private.h | 9 | ||||
-rw-r--r-- | drivers/fmc/fmc-sdb.c | 220 | ||||
-rw-r--r-- | drivers/fmc/fmc-trivial.c | 103 | ||||
-rw-r--r-- | drivers/fmc/fmc-write-eeprom.c | 176 | ||||
-rw-r--r-- | drivers/fmc/fru-parse.c | 81 |
13 files changed, 1945 insertions, 0 deletions
diff --git a/drivers/fmc/Kconfig b/drivers/fmc/Kconfig new file mode 100644 index 000000000..3a75f4256 --- /dev/null +++ b/drivers/fmc/Kconfig @@ -0,0 +1,51 @@ +# +# FMC (ANSI-VITA 57.1) bus support +# + +menuconfig FMC + tristate "FMC support" + help + + FMC (FPGA Mezzanine Carrier) is a mechanical and electrical + standard for mezzanine cards that plug into a carrier board. + This kernel subsystem supports the matching between carrier + and mezzanine based on identifiers stored in the internal I2C + EEPROM, as well as having carrier-independent drivers. + + The framework was born outside of the kernel and at this time + the off-tree code base is more complete. Code and documentation + is at git://ohwr.org/fmc-projects/fmc-bus.git . + +if FMC + +config FMC_FAKEDEV + tristate "FMC fake device (software testing)" + help + This is a fake carrier, bringing a default EEPROM content + that can be rewritten at run time and usef for matching + mezzanines. + +config FMC_TRIVIAL + tristate "FMC trivial mezzanine driver (software testing)" + help + This is a fake mezzanine driver, to show how FMC works and test it. + The driver also handles interrupts (we used it with a real carrier + before the mezzanines were produced) + +config FMC_WRITE_EEPROM + tristate "FMC mezzanine driver to write I2C EEPROM" + help + This driver matches every mezzanine device and can write the + internal EEPROM of the PCB, using the firmware loader to get + its binary and the function carrier->reprogram to actually do it. + It is useful when the mezzanines are produced. + +config FMC_CHARDEV + tristate "FMC mezzanine driver that registers a char device" + help + This driver matches every mezzanine device and allows user + space to read and write registers using a char device. It + can be used to write user-space drivers, or just get + acquainted with a mezzanine before writing its specific driver. + +endif # FMC diff --git a/drivers/fmc/Makefile b/drivers/fmc/Makefile new file mode 100644 index 000000000..e3da6192c --- /dev/null +++ b/drivers/fmc/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_FMC) += fmc.o + +fmc-y = fmc-core.o +fmc-y += fmc-match.o +fmc-y += fmc-sdb.o +fmc-y += fru-parse.o +fmc-y += fmc-dump.o +fmc-y += fmc-debug.o + +obj-$(CONFIG_FMC_FAKEDEV) += fmc-fakedev.o +obj-$(CONFIG_FMC_TRIVIAL) += fmc-trivial.o +obj-$(CONFIG_FMC_WRITE_EEPROM) += fmc-write-eeprom.o +obj-$(CONFIG_FMC_CHARDEV) += fmc-chardev.o diff --git a/drivers/fmc/fmc-chardev.c b/drivers/fmc/fmc-chardev.c new file mode 100644 index 000000000..5ecf4090a --- /dev/null +++ b/drivers/fmc/fmc-chardev.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Released according to the GNU GPL, version 2 or any later version. + * + * This work is part of the White Rabbit project, a research effort led + * by CERN, the European Institute for Nuclear Research. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/spinlock.h> +#include <linux/fmc.h> +#include <linux/uaccess.h> + +static LIST_HEAD(fc_devices); +static DEFINE_SPINLOCK(fc_lock); + +struct fc_instance { + struct list_head list; + struct fmc_device *fmc; + struct miscdevice misc; +}; + +/* at open time, we must identify our device */ +static int fc_open(struct inode *ino, struct file *f) +{ + struct fmc_device *fmc; + struct fc_instance *fc; + int minor = iminor(ino); + + list_for_each_entry(fc, &fc_devices, list) + if (fc->misc.minor == minor) + break; + if (fc->misc.minor != minor) + return -ENODEV; + fmc = fc->fmc; + if (try_module_get(fmc->owner) == 0) + return -ENODEV; + + f->private_data = fmc; + return 0; +} + +static int fc_release(struct inode *ino, struct file *f) +{ + struct fmc_device *fmc = f->private_data; + module_put(fmc->owner); + return 0; +} + +/* read and write are simple after the default llseek has been used */ +static ssize_t fc_read(struct file *f, char __user *buf, size_t count, + loff_t *offp) +{ + struct fmc_device *fmc = f->private_data; + unsigned long addr; + uint32_t val; + + if (count < sizeof(val)) + return -EINVAL; + count = sizeof(val); + + addr = *offp; + if (addr > fmc->memlen) + return -ESPIPE; /* Illegal seek */ + val = fmc_readl(fmc, addr); + if (copy_to_user(buf, &val, count)) + return -EFAULT; + *offp += count; + return count; +} + +static ssize_t fc_write(struct file *f, const char __user *buf, size_t count, + loff_t *offp) +{ + struct fmc_device *fmc = f->private_data; + unsigned long addr; + uint32_t val; + + if (count < sizeof(val)) + return -EINVAL; + count = sizeof(val); + + addr = *offp; + if (addr > fmc->memlen) + return -ESPIPE; /* Illegal seek */ + if (copy_from_user(&val, buf, count)) + return -EFAULT; + fmc_writel(fmc, val, addr); + *offp += count; + return count; +} + +static const struct file_operations fc_fops = { + .owner = THIS_MODULE, + .open = fc_open, + .release = fc_release, + .llseek = generic_file_llseek, + .read = fc_read, + .write = fc_write, +}; + + +/* Device part .. */ +static int fc_probe(struct fmc_device *fmc); +static int fc_remove(struct fmc_device *fmc); + +static struct fmc_driver fc_drv = { + .version = FMC_VERSION, + .driver.name = KBUILD_MODNAME, + .probe = fc_probe, + .remove = fc_remove, + /* no table: we want to match everything */ +}; + +/* We accept the generic busid parameter */ +FMC_PARAM_BUSID(fc_drv); + +/* probe and remove must allocate and release a misc device */ +static int fc_probe(struct fmc_device *fmc) +{ + int ret; + int index = 0; + + struct fc_instance *fc; + + index = fmc_validate(fmc, &fc_drv); + if (index < 0) + return -EINVAL; /* not our device: invalid */ + + /* Create a char device: we want to create it anew */ + fc = kzalloc(sizeof(*fc), GFP_KERNEL); + if (!fc) + return -ENOMEM; + fc->fmc = fmc; + fc->misc.minor = MISC_DYNAMIC_MINOR; + fc->misc.fops = &fc_fops; + fc->misc.name = kstrdup(dev_name(&fmc->dev), GFP_KERNEL); + + ret = misc_register(&fc->misc); + if (ret < 0) + goto out; + spin_lock(&fc_lock); + list_add(&fc->list, &fc_devices); + spin_unlock(&fc_lock); + dev_info(&fc->fmc->dev, "Created misc device \"%s\"\n", + fc->misc.name); + return 0; + +out: + kfree(fc->misc.name); + kfree(fc); + return ret; +} + +static int fc_remove(struct fmc_device *fmc) +{ + struct fc_instance *fc; + + list_for_each_entry(fc, &fc_devices, list) + if (fc->fmc == fmc) + break; + if (fc->fmc != fmc) { + dev_err(&fmc->dev, "remove called but not found\n"); + return -ENODEV; + } + + spin_lock(&fc_lock); + list_del(&fc->list); + spin_unlock(&fc_lock); + misc_deregister(&fc->misc); + kfree(fc->misc.name); + kfree(fc); + + return 0; +} + + +static int fc_init(void) +{ + int ret; + + ret = fmc_driver_register(&fc_drv); + return ret; +} + +static void fc_exit(void) +{ + fmc_driver_unregister(&fc_drv); +} + +module_init(fc_init); +module_exit(fc_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/fmc/fmc-core.c b/drivers/fmc/fmc-core.c new file mode 100644 index 000000000..bbcb505d1 --- /dev/null +++ b/drivers/fmc/fmc-core.c @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Released according to the GNU GPL, version 2 or any later version. + * + * This work is part of the White Rabbit project, a research effort led + * by CERN, the European Institute for Nuclear Research. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fmc.h> +#include <linux/fmc-sdb.h> + +#include "fmc-private.h" + +static int fmc_check_version(unsigned long version, const char *name) +{ + if (__FMC_MAJOR(version) != FMC_MAJOR) { + pr_err("%s: \"%s\" has wrong major (has %li, expected %i)\n", + __func__, name, __FMC_MAJOR(version), FMC_MAJOR); + return -EINVAL; + } + + if (__FMC_MINOR(version) != FMC_MINOR) + pr_info("%s: \"%s\" has wrong minor (has %li, expected %i)\n", + __func__, name, __FMC_MINOR(version), FMC_MINOR); + return 0; +} + +static int fmc_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + /* struct fmc_device *fdev = to_fmc_device(dev); */ + + /* FIXME: The MODALIAS */ + add_uevent_var(env, "MODALIAS=%s", "fmc"); + return 0; +} + +static int fmc_probe(struct device *dev) +{ + struct fmc_driver *fdrv = to_fmc_driver(dev->driver); + struct fmc_device *fdev = to_fmc_device(dev); + + return fdrv->probe(fdev); +} + +static int fmc_remove(struct device *dev) +{ + struct fmc_driver *fdrv = to_fmc_driver(dev->driver); + struct fmc_device *fdev = to_fmc_device(dev); + + return fdrv->remove(fdev); +} + +static void fmc_shutdown(struct device *dev) +{ + /* not implemented but mandatory */ +} + +static struct bus_type fmc_bus_type = { + .name = "fmc", + .match = fmc_match, + .uevent = fmc_uevent, + .probe = fmc_probe, + .remove = fmc_remove, + .shutdown = fmc_shutdown, +}; + +static void fmc_release(struct device *dev) +{ + struct fmc_device *fmc = container_of(dev, struct fmc_device, dev); + + kfree(fmc); +} + +/* + * The eeprom is exported in sysfs, through a binary attribute + */ + +static ssize_t fmc_read_eeprom(struct file *file, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev; + struct fmc_device *fmc; + int eelen; + + dev = container_of(kobj, struct device, kobj); + fmc = container_of(dev, struct fmc_device, dev); + eelen = fmc->eeprom_len; + if (off > eelen) + return -ESPIPE; + if (off == eelen) + return 0; /* EOF */ + if (off + count > eelen) + count = eelen - off; + memcpy(buf, fmc->eeprom + off, count); + return count; +} + +static ssize_t fmc_write_eeprom(struct file *file, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev; + struct fmc_device *fmc; + + dev = container_of(kobj, struct device, kobj); + fmc = container_of(dev, struct fmc_device, dev); + return fmc->op->write_ee(fmc, off, buf, count); +} + +static struct bin_attribute fmc_eeprom_attr = { + .attr = { .name = "eeprom", .mode = S_IRUGO | S_IWUSR, }, + .size = 8192, /* more or less standard */ + .read = fmc_read_eeprom, + .write = fmc_write_eeprom, +}; + +int fmc_irq_request(struct fmc_device *fmc, irq_handler_t h, + char *name, int flags) +{ + if (fmc->op->irq_request) + return fmc->op->irq_request(fmc, h, name, flags); + return -EPERM; +} +EXPORT_SYMBOL(fmc_irq_request); + +void fmc_irq_free(struct fmc_device *fmc) +{ + if (fmc->op->irq_free) + fmc->op->irq_free(fmc); +} +EXPORT_SYMBOL(fmc_irq_free); + +void fmc_irq_ack(struct fmc_device *fmc) +{ + if (likely(fmc->op->irq_ack)) + fmc->op->irq_ack(fmc); +} +EXPORT_SYMBOL(fmc_irq_ack); + +int fmc_validate(struct fmc_device *fmc, struct fmc_driver *drv) +{ + if (fmc->op->validate) + return fmc->op->validate(fmc, drv); + return -EPERM; +} +EXPORT_SYMBOL(fmc_validate); + +int fmc_gpio_config(struct fmc_device *fmc, struct fmc_gpio *gpio, int ngpio) +{ + if (fmc->op->gpio_config) + return fmc->op->gpio_config(fmc, gpio, ngpio); + return -EPERM; +} +EXPORT_SYMBOL(fmc_gpio_config); + +int fmc_read_ee(struct fmc_device *fmc, int pos, void *d, int l) +{ + if (fmc->op->read_ee) + return fmc->op->read_ee(fmc, pos, d, l); + return -EPERM; +} +EXPORT_SYMBOL(fmc_read_ee); + +int fmc_write_ee(struct fmc_device *fmc, int pos, const void *d, int l) +{ + if (fmc->op->write_ee) + return fmc->op->write_ee(fmc, pos, d, l); + return -EPERM; +} +EXPORT_SYMBOL(fmc_write_ee); + +/* + * Functions for client modules follow + */ + +int fmc_driver_register(struct fmc_driver *drv) +{ + if (fmc_check_version(drv->version, drv->driver.name)) + return -EINVAL; + drv->driver.bus = &fmc_bus_type; + return driver_register(&drv->driver); +} +EXPORT_SYMBOL(fmc_driver_register); + +void fmc_driver_unregister(struct fmc_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL(fmc_driver_unregister); + +/* + * When a device set is registered, all eeproms must be read + * and all FRUs must be parsed + */ +int fmc_device_register_n_gw(struct fmc_device **devs, int n, + struct fmc_gateware *gw) +{ + struct fmc_device *fmc, **devarray; + uint32_t device_id; + int i, ret = 0; + + if (n < 1) + return 0; + + /* Check the version of the first data structure (function prints) */ + if (fmc_check_version(devs[0]->version, devs[0]->carrier_name)) + return -EINVAL; + + devarray = kmemdup(devs, n * sizeof(*devs), GFP_KERNEL); + if (!devarray) + return -ENOMEM; + + /* Make all other checks before continuing, for all devices */ + for (i = 0; i < n; i++) { + fmc = devarray[i]; + if (!fmc->hwdev) { + pr_err("%s: device nr. %i has no hwdev pointer\n", + __func__, i); + ret = -EINVAL; + break; + } + if (fmc->flags & FMC_DEVICE_NO_MEZZANINE) { + dev_info(fmc->hwdev, "absent mezzanine in slot %d\n", + fmc->slot_id); + continue; + } + if (!fmc->eeprom) { + dev_err(fmc->hwdev, "no eeprom provided for slot %i\n", + fmc->slot_id); + ret = -EINVAL; + } + if (!fmc->eeprom_addr) { + dev_err(fmc->hwdev, "no eeprom_addr for slot %i\n", + fmc->slot_id); + ret = -EINVAL; + } + if (!fmc->carrier_name || !fmc->carrier_data || + !fmc->device_id) { + dev_err(fmc->hwdev, + "device nr %i: carrier name, " + "data or dev_id not set\n", i); + ret = -EINVAL; + } + if (ret) + break; + + } + if (ret) { + kfree(devarray); + return ret; + } + + /* Validation is ok. Now init and register the devices */ + for (i = 0; i < n; i++) { + fmc = devarray[i]; + + fmc->nr_slots = n; /* each slot must know how many are there */ + fmc->devarray = devarray; + + device_initialize(&fmc->dev); + fmc->dev.release = fmc_release; + fmc->dev.parent = fmc->hwdev; + + /* Fill the identification stuff (may fail) */ + fmc_fill_id_info(fmc); + + fmc->dev.bus = &fmc_bus_type; + + /* Name from mezzanine info or carrier info. Or 0,1,2.. */ + device_id = fmc->device_id; + if (!fmc->mezzanine_name) + dev_set_name(&fmc->dev, "fmc-%04x", device_id); + else + dev_set_name(&fmc->dev, "%s-%04x", fmc->mezzanine_name, + device_id); + + if (gw) { + /* + * The carrier already know the bitstream to load + * for this set of FMC mezzanines. + */ + ret = fmc->op->reprogram_raw(fmc, NULL, + gw->bitstream, gw->len); + if (ret) { + dev_warn(fmc->hwdev, + "Invalid gateware for FMC mezzanine\n"); + goto out; + } + } + + ret = device_add(&fmc->dev); + if (ret < 0) { + dev_err(fmc->hwdev, "Slot %i: Failed in registering " + "\"%s\"\n", fmc->slot_id, fmc->dev.kobj.name); + goto out; + } + ret = sysfs_create_bin_file(&fmc->dev.kobj, &fmc_eeprom_attr); + if (ret < 0) { + dev_err(&fmc->dev, "Failed in registering eeprom\n"); + goto out1; + } + /* This device went well, give information to the user */ + fmc_dump_eeprom(fmc); + fmc_debug_init(fmc); + } + return 0; + +out1: + device_del(&fmc->dev); +out: + kfree(devarray); + for (i--; i >= 0; i--) { + fmc_debug_exit(devs[i]); + sysfs_remove_bin_file(&devs[i]->dev.kobj, &fmc_eeprom_attr); + device_del(&devs[i]->dev); + fmc_free_id_info(devs[i]); + put_device(&devs[i]->dev); + } + return ret; + +} +EXPORT_SYMBOL(fmc_device_register_n_gw); + +int fmc_device_register_n(struct fmc_device **devs, int n) +{ + return fmc_device_register_n_gw(devs, n, NULL); +} +EXPORT_SYMBOL(fmc_device_register_n); + +int fmc_device_register_gw(struct fmc_device *fmc, struct fmc_gateware *gw) +{ + return fmc_device_register_n_gw(&fmc, 1, gw); +} +EXPORT_SYMBOL(fmc_device_register_gw); + +int fmc_device_register(struct fmc_device *fmc) +{ + return fmc_device_register_n(&fmc, 1); +} +EXPORT_SYMBOL(fmc_device_register); + +void fmc_device_unregister_n(struct fmc_device **devs, int n) +{ + int i; + + if (n < 1) + return; + + /* Free devarray first, not used by the later loop */ + kfree(devs[0]->devarray); + + for (i = 0; i < n; i++) { + fmc_debug_exit(devs[i]); + sysfs_remove_bin_file(&devs[i]->dev.kobj, &fmc_eeprom_attr); + device_del(&devs[i]->dev); + fmc_free_id_info(devs[i]); + put_device(&devs[i]->dev); + } +} +EXPORT_SYMBOL(fmc_device_unregister_n); + +void fmc_device_unregister(struct fmc_device *fmc) +{ + fmc_device_unregister_n(&fmc, 1); +} +EXPORT_SYMBOL(fmc_device_unregister); + +/* Init and exit are trivial */ +static int fmc_init(void) +{ + return bus_register(&fmc_bus_type); +} + +static void fmc_exit(void) +{ + bus_unregister(&fmc_bus_type); +} + +module_init(fmc_init); +module_exit(fmc_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/fmc/fmc-debug.c b/drivers/fmc/fmc-debug.c new file mode 100644 index 000000000..329307227 --- /dev/null +++ b/drivers/fmc/fmc-debug.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2015 CERN (www.cern.ch) + * Author: Federico Vaga <federico.vaga@cern.ch> + * + * Released according to the GNU GPL, version 2 or any later version. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <asm/byteorder.h> + +#include <linux/fmc.h> +#include <linux/sdb.h> +#include <linux/fmc-sdb.h> + +#define FMC_DBG_SDB_DUMP "dump_sdb" + +static char *__strip_trailing_space(char *buf, char *str, int len) +{ + int i = len - 1; + + memcpy(buf, str, len); + buf[len] = '\0'; + while (i >= 0 && buf[i] == ' ') + buf[i--] = '\0'; + return buf; +} + +#define __sdb_string(buf, field) ({ \ + BUILD_BUG_ON(sizeof(buf) < sizeof(field)); \ + __strip_trailing_space(buf, (void *)(field), sizeof(field)); \ + }) + +/** + * We do not check seq_printf() errors because we want to see things in any case + */ +static void fmc_sdb_dump_recursive(struct fmc_device *fmc, struct seq_file *s, + const struct sdb_array *arr) +{ + unsigned long base = arr->baseaddr; + int i, j, n = arr->len, level = arr->level; + char tmp[64]; + + for (i = 0; i < n; i++) { + union sdb_record *r; + struct sdb_product *p; + struct sdb_component *c; + + r = &arr->record[i]; + c = &r->dev.sdb_component; + p = &c->product; + + for (j = 0; j < level; j++) + seq_printf(s, " "); + switch (r->empty.record_type) { + case sdb_type_interconnect: + seq_printf(s, "%08llx:%08x %.19s\n", + __be64_to_cpu(p->vendor_id), + __be32_to_cpu(p->device_id), + p->name); + break; + case sdb_type_device: + seq_printf(s, "%08llx:%08x %.19s (%08llx-%08llx)\n", + __be64_to_cpu(p->vendor_id), + __be32_to_cpu(p->device_id), + p->name, + __be64_to_cpu(c->addr_first) + base, + __be64_to_cpu(c->addr_last) + base); + break; + case sdb_type_bridge: + seq_printf(s, "%08llx:%08x %.19s (bridge: %08llx)\n", + __be64_to_cpu(p->vendor_id), + __be32_to_cpu(p->device_id), + p->name, + __be64_to_cpu(c->addr_first) + base); + if (IS_ERR(arr->subtree[i])) { + seq_printf(s, "SDB: (bridge error %li)\n", + PTR_ERR(arr->subtree[i])); + break; + } + fmc_sdb_dump_recursive(fmc, s, arr->subtree[i]); + break; + case sdb_type_integration: + seq_printf(s, "integration\n"); + break; + case sdb_type_repo_url: + seq_printf(s, "Synthesis repository: %s\n", + __sdb_string(tmp, r->repo_url.repo_url)); + break; + case sdb_type_synthesis: + seq_printf(s, "Bitstream '%s' ", + __sdb_string(tmp, r->synthesis.syn_name)); + seq_printf(s, "synthesized %08x by %s ", + __be32_to_cpu(r->synthesis.date), + __sdb_string(tmp, r->synthesis.user_name)); + seq_printf(s, "(%s version %x), ", + __sdb_string(tmp, r->synthesis.tool_name), + __be32_to_cpu(r->synthesis.tool_version)); + seq_printf(s, "commit %pm\n", + r->synthesis.commit_id); + break; + case sdb_type_empty: + seq_printf(s, "empty\n"); + break; + default: + seq_printf(s, "UNKNOWN TYPE 0x%02x\n", + r->empty.record_type); + break; + } + } +} + +static int fmc_sdb_dump(struct seq_file *s, void *offset) +{ + struct fmc_device *fmc = s->private; + + if (!fmc->sdb) { + seq_printf(s, "no SDB information\n"); + return 0; + } + + seq_printf(s, "FMC: %s (%s), slot %i, device %s\n", dev_name(fmc->hwdev), + fmc->carrier_name, fmc->slot_id, dev_name(&fmc->dev)); + /* Dump SDB information */ + fmc_sdb_dump_recursive(fmc, s, fmc->sdb); + + return 0; +} + + +static int fmc_sdb_dump_open(struct inode *inode, struct file *file) +{ + struct fmc_device *fmc = inode->i_private; + + return single_open(file, fmc_sdb_dump, fmc); +} + + +const struct file_operations fmc_dbgfs_sdb_dump = { + .owner = THIS_MODULE, + .open = fmc_sdb_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int fmc_debug_init(struct fmc_device *fmc) +{ + fmc->dbg_dir = debugfs_create_dir(dev_name(&fmc->dev), NULL); + if (IS_ERR_OR_NULL(fmc->dbg_dir)) { + pr_err("FMC: Cannot create debugfs\n"); + return PTR_ERR(fmc->dbg_dir); + } + + fmc->dbg_sdb_dump = debugfs_create_file(FMC_DBG_SDB_DUMP, 0444, + fmc->dbg_dir, fmc, + &fmc_dbgfs_sdb_dump); + if (IS_ERR_OR_NULL(fmc->dbg_sdb_dump)) + pr_err("FMC: Cannot create debugfs file %s\n", + FMC_DBG_SDB_DUMP); + + return 0; +} + +void fmc_debug_exit(struct fmc_device *fmc) +{ + if (fmc->dbg_dir) + debugfs_remove_recursive(fmc->dbg_dir); +} diff --git a/drivers/fmc/fmc-dump.c b/drivers/fmc/fmc-dump.c new file mode 100644 index 000000000..cd1df475b --- /dev/null +++ b/drivers/fmc/fmc-dump.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Released according to the GNU GPL, version 2 or any later version. + * + * This work is part of the White Rabbit project, a research effort led + * by CERN, the European Institute for Nuclear Research. + */ +#include <linux/kernel.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/fmc.h> +#include <linux/fmc-sdb.h> + +static int fmc_must_dump_eeprom; +module_param_named(dump_eeprom, fmc_must_dump_eeprom, int, 0644); + +#define LINELEN 16 + +/* Dumping 8k takes oh so much: avoid duplicate lines */ +static const uint8_t *dump_line(int addr, const uint8_t *line, + const uint8_t *prev) +{ + int i; + + if (!prev || memcmp(line, prev, LINELEN)) { + pr_info("%04x: ", addr); + for (i = 0; i < LINELEN; ) { + printk(KERN_CONT "%02x", line[i]); + i++; + printk(i & 3 ? " " : i & (LINELEN - 1) ? " " : "\n"); + } + return line; + } + /* repeated line */ + if (line == prev + LINELEN) + pr_info("[...]\n"); + return prev; +} + +void fmc_dump_eeprom(const struct fmc_device *fmc) +{ + const uint8_t *line, *prev; + int i; + + if (!fmc_must_dump_eeprom) + return; + + pr_info("FMC: %s (%s), slot %i, device %s\n", dev_name(fmc->hwdev), + fmc->carrier_name, fmc->slot_id, dev_name(&fmc->dev)); + pr_info("FMC: dumping eeprom 0x%x (%i) bytes\n", fmc->eeprom_len, + fmc->eeprom_len); + + line = fmc->eeprom; + prev = NULL; + for (i = 0; i < fmc->eeprom_len; i += LINELEN, line += LINELEN) + prev = dump_line(i, line, prev); +} diff --git a/drivers/fmc/fmc-fakedev.c b/drivers/fmc/fmc-fakedev.c new file mode 100644 index 000000000..941d09309 --- /dev/null +++ b/drivers/fmc/fmc-fakedev.c @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * The software is provided "as is"; the copyright holders disclaim + * all warranties and liabilities, to the extent permitted by + * applicable law. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <linux/err.h> +#include <linux/fmc.h> + +#define FF_EEPROM_SIZE 8192 /* The standard eeprom size */ +#define FF_MAX_MEZZANINES 4 /* Fakes a multi-mezzanine carrier */ + +/* The user can pass up to 4 names of eeprom images to load */ +static char *ff_eeprom[FF_MAX_MEZZANINES]; +static int ff_nr_eeprom; +module_param_array_named(eeprom, ff_eeprom, charp, &ff_nr_eeprom, 0444); + +/* The user can ask for a multi-mezzanine carrier, with the default eeprom */ +static int ff_nr_dev = 1; +module_param_named(ndev, ff_nr_dev, int, 0444); + + +/* Lazily, don't support the "standard" module parameters */ + +/* + * Eeprom built from these commands: + + ../fru-generator -v fake-vendor -n fake-design-for-testing \ + -s 01234 -p none > IPMI-FRU + + gensdbfs . ../fake-eeprom.bin +*/ +static char ff_eeimg[FF_MAX_MEZZANINES][FF_EEPROM_SIZE] = { + { + 0x01, 0x00, 0x00, 0x01, 0x00, 0x0c, 0x00, 0xf2, 0x01, 0x0b, 0x00, 0xb2, + 0x86, 0x87, 0xcb, 0x66, 0x61, 0x6b, 0x65, 0x2d, 0x76, 0x65, 0x6e, 0x64, + 0x6f, 0x72, 0xd7, 0x66, 0x61, 0x6b, 0x65, 0x2d, 0x64, 0x65, 0x73, 0x69, + 0x67, 0x6e, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0xc5, 0x30, 0x31, 0x32, 0x33, 0x34, 0xc4, 0x6e, 0x6f, 0x6e, + 0x65, 0xda, 0x32, 0x30, 0x31, 0x32, 0x2d, 0x31, 0x31, 0x2d, 0x31, 0x39, + 0x20, 0x32, 0x32, 0x3a, 0x34, 0x32, 0x3a, 0x33, 0x30, 0x2e, 0x30, 0x37, + 0x34, 0x30, 0x35, 0x35, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, + 0x02, 0x02, 0x0d, 0xf7, 0xf8, 0x02, 0xb0, 0x04, 0x74, 0x04, 0xec, 0x04, + 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x02, 0x02, 0x0d, 0x5c, 0x93, 0x01, + 0x4a, 0x01, 0x39, 0x01, 0x5a, 0x01, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x0b, + 0x02, 0x02, 0x0d, 0x63, 0x8c, 0x00, 0xfa, 0x00, 0xed, 0x00, 0x06, 0x01, + 0x00, 0x00, 0x00, 0x00, 0xa0, 0x0f, 0x01, 0x02, 0x0d, 0xfb, 0xf5, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x0d, 0xfc, 0xf4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x0d, 0xfd, 0xf3, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfa, 0x82, 0x0b, 0xea, 0x8f, 0xa2, 0x12, 0x00, 0x00, 0x1e, 0x44, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x44, 0x42, 0x2d, 0x00, 0x03, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xc4, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, + 0x2e, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc4, 0x46, 0x69, 0x6c, 0x65, + 0x44, 0x61, 0x74, 0x61, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, + 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x49, 0x50, 0x4d, 0x49, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x49, 0x50, 0x4d, 0x49, + 0x2d, 0x46, 0x52, 0x55, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x01, 0x66, 0x61, 0x6b, 0x65, 0x0a, + }, +}; + +struct ff_dev { + struct fmc_device *fmc[FF_MAX_MEZZANINES]; + struct device dev; +}; + +static struct ff_dev *ff_current_dev; /* We have 1 carrier, 1 slot */ + +static int ff_reprogram(struct fmc_device *fmc, struct fmc_driver *drv, + char *gw) +{ + const struct firmware *fw; + int ret; + + if (!gw) { + /* program golden: success */ + fmc->flags &= ~FMC_DEVICE_HAS_CUSTOM; + fmc->flags |= FMC_DEVICE_HAS_GOLDEN; + return 0; + } + + dev_info(&fmc->dev, "reprogramming with %s\n", gw); + ret = request_firmware(&fw, gw, &fmc->dev); + if (ret < 0) { + dev_warn(&fmc->dev, "request firmware \"%s\": error %i\n", + gw, ret); + goto out; + } + fmc->flags &= ~FMC_DEVICE_HAS_GOLDEN; + fmc->flags |= FMC_DEVICE_HAS_CUSTOM; + +out: + release_firmware(fw); + return ret; +} + +static int ff_irq_request(struct fmc_device *fmc, irq_handler_t handler, + char *name, int flags) +{ + return -EOPNOTSUPP; +} + +/* FIXME: should also have some fake FMC GPIO mapping */ + + +/* + * This work function is called when we changed the eeprom. It removes the + * current fmc device and registers a new one, with different identifiers. + */ +static struct ff_dev *ff_dev_create(void); /* defined later */ + +static void ff_work_fn(struct work_struct *work) +{ + struct ff_dev *ff = ff_current_dev; + int ret; + + fmc_device_unregister_n(ff->fmc, ff_nr_dev); + device_unregister(&ff->dev); + ff_current_dev = NULL; + + ff = ff_dev_create(); + if (IS_ERR(ff)) { + pr_warning("%s: can't re-create FMC devices\n", __func__); + return; + } + ret = fmc_device_register_n(ff->fmc, ff_nr_dev); + if (ret < 0) { + dev_warn(&ff->dev, "can't re-register FMC devices\n"); + device_unregister(&ff->dev); + return; + } + + ff_current_dev = ff; +} + +static DECLARE_DELAYED_WORK(ff_work, ff_work_fn); + + +/* low-level i2c */ +static int ff_eeprom_read(struct fmc_device *fmc, uint32_t offset, + void *buf, size_t size) +{ + if (offset > FF_EEPROM_SIZE) + return -EINVAL; + if (offset + size > FF_EEPROM_SIZE) + size = FF_EEPROM_SIZE - offset; + memcpy(buf, fmc->eeprom + offset, size); + return size; +} + +static int ff_eeprom_write(struct fmc_device *fmc, uint32_t offset, + const void *buf, size_t size) +{ + if (offset > FF_EEPROM_SIZE) + return -EINVAL; + if (offset + size > FF_EEPROM_SIZE) + size = FF_EEPROM_SIZE - offset; + dev_info(&fmc->dev, "write_eeprom: offset %i, size %zi\n", + (int)offset, size); + memcpy(fmc->eeprom + offset, buf, size); + schedule_delayed_work(&ff_work, HZ * 2); /* remove, replug, in 2s */ + return size; +} + +/* i2c operations for fmc */ +static int ff_read_ee(struct fmc_device *fmc, int pos, void *data, int len) +{ + if (!(fmc->flags & FMC_DEVICE_HAS_GOLDEN)) + return -EOPNOTSUPP; + return ff_eeprom_read(fmc, pos, data, len); +} + +static int ff_write_ee(struct fmc_device *fmc, int pos, + const void *data, int len) +{ + if (!(fmc->flags & FMC_DEVICE_HAS_GOLDEN)) + return -EOPNOTSUPP; + return ff_eeprom_write(fmc, pos, data, len); +} + +/* readl and writel do not do anything. Don't waste RAM with "base" */ +static uint32_t ff_readl(struct fmc_device *fmc, int offset) +{ + return 0; +} + +static void ff_writel(struct fmc_device *fmc, uint32_t value, int offset) +{ + return; +} + +/* validate is useful so fmc-write-eeprom will not reprogram every 2 seconds */ +static int ff_validate(struct fmc_device *fmc, struct fmc_driver *drv) +{ + int i; + + if (!drv->busid_n) + return 0; /* everyhing is valid */ + for (i = 0; i < drv->busid_n; i++) + if (drv->busid_val[i] == fmc->device_id) + return i; + return -ENOENT; +} + + + +static struct fmc_operations ff_fmc_operations = { + .read32 = ff_readl, + .write32 = ff_writel, + .reprogram = ff_reprogram, + .irq_request = ff_irq_request, + .read_ee = ff_read_ee, + .write_ee = ff_write_ee, + .validate = ff_validate, +}; + +/* This device is kmalloced: release it */ +static void ff_dev_release(struct device *dev) +{ + struct ff_dev *ff = container_of(dev, struct ff_dev, dev); + kfree(ff); +} + +static struct fmc_device ff_template_fmc = { + .version = FMC_VERSION, + .owner = THIS_MODULE, + .carrier_name = "fake-fmc-carrier", + .device_id = 0xf001, /* fool */ + .eeprom_len = sizeof(ff_eeimg[0]), + .memlen = 0x1000, /* 4k, to show something */ + .op = &ff_fmc_operations, + .hwdev = NULL, /* filled at creation time */ + .flags = FMC_DEVICE_HAS_GOLDEN, +}; + +static struct ff_dev *ff_dev_create(void) +{ + struct ff_dev *ff; + struct fmc_device *fmc; + int i, ret; + + ff = kzalloc(sizeof(*ff), GFP_KERNEL); + if (!ff) + return ERR_PTR(-ENOMEM); + dev_set_name(&ff->dev, "fake-fmc-carrier"); + ff->dev.release = ff_dev_release; + + ret = device_register(&ff->dev); + if (ret < 0) { + put_device(&ff->dev); + return ERR_PTR(ret); + } + + /* Create fmc structures that refer to this new "hw" device */ + for (i = 0; i < ff_nr_dev; i++) { + fmc = kmemdup(&ff_template_fmc, sizeof(ff_template_fmc), + GFP_KERNEL); + fmc->hwdev = &ff->dev; + fmc->carrier_data = ff; + fmc->nr_slots = ff_nr_dev; + /* the following fields are different for each slot */ + fmc->eeprom = ff_eeimg[i]; + fmc->eeprom_addr = 0x50 + 2 * i; + fmc->slot_id = i; + ff->fmc[i] = fmc; + /* increment the identifier, each must be different */ + ff_template_fmc.device_id++; + } + return ff; +} + +/* init and exit */ +static int ff_init(void) +{ + struct ff_dev *ff; + const struct firmware *fw; + int i, len, ret = 0; + + /* Replicate the default eeprom for the max number of mezzanines */ + for (i = 1; i < FF_MAX_MEZZANINES; i++) + memcpy(ff_eeimg[i], ff_eeimg[0], sizeof(ff_eeimg[0])); + + if (ff_nr_eeprom > ff_nr_dev) + ff_nr_dev = ff_nr_eeprom; + + ff = ff_dev_create(); + if (IS_ERR(ff)) + return PTR_ERR(ff); + + /* If the user passed "eeprom=" as a parameter, fetch them */ + for (i = 0; i < ff_nr_eeprom; i++) { + if (!strlen(ff_eeprom[i])) + continue; + ret = request_firmware(&fw, ff_eeprom[i], &ff->dev); + if (ret < 0) { + dev_err(&ff->dev, "Mezzanine %i: can't load \"%s\" " + "(error %i)\n", i, ff_eeprom[i], -ret); + } else { + len = min_t(size_t, fw->size, (size_t)FF_EEPROM_SIZE); + memcpy(ff_eeimg[i], fw->data, len); + release_firmware(fw); + dev_info(&ff->dev, "Mezzanine %i: eeprom \"%s\"\n", i, + ff_eeprom[i]); + } + } + + ret = fmc_device_register_n(ff->fmc, ff_nr_dev); + if (ret) { + device_unregister(&ff->dev); + return ret; + } + ff_current_dev = ff; + return ret; +} + +static void ff_exit(void) +{ + if (ff_current_dev) { + fmc_device_unregister_n(ff_current_dev->fmc, ff_nr_dev); + device_unregister(&ff_current_dev->dev); + } + cancel_delayed_work_sync(&ff_work); +} + +module_init(ff_init); +module_exit(ff_exit); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/fmc/fmc-match.c b/drivers/fmc/fmc-match.c new file mode 100644 index 000000000..a0956d1f7 --- /dev/null +++ b/drivers/fmc/fmc-match.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Released according to the GNU GPL, version 2 or any later version. + * + * This work is part of the White Rabbit project, a research effort led + * by CERN, the European Institute for Nuclear Research. + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fmc.h> +#include <linux/ipmi-fru.h> + +/* The fru parser is both user and kernel capable: it needs alloc */ +void *fru_alloc(size_t size) +{ + return kzalloc(size, GFP_KERNEL); +} + +/* The actual match function */ +int fmc_match(struct device *dev, struct device_driver *drv) +{ + struct fmc_driver *fdrv = to_fmc_driver(drv); + struct fmc_device *fdev = to_fmc_device(dev); + struct fmc_fru_id *fid; + int i, matched = 0; + + /* This currently only matches the EEPROM (FRU id) */ + fid = fdrv->id_table.fru_id; + if (!fid) { + dev_warn(&fdev->dev, "Driver has no ID: matches all\n"); + matched = 1; + } else { + if (!fdev->id.manufacturer || !fdev->id.product_name) + return 0; /* the device has no FRU information */ + for (i = 0; i < fdrv->id_table.fru_id_nr; i++, fid++) { + if (fid->manufacturer && + strcmp(fid->manufacturer, fdev->id.manufacturer)) + continue; + if (fid->product_name && + strcmp(fid->product_name, fdev->id.product_name)) + continue; + matched = 1; + break; + } + } + + /* FIXME: match SDB contents */ + return matched; +} + +/* This function creates ID info for a newly registered device */ +int fmc_fill_id_info(struct fmc_device *fmc) +{ + struct fru_common_header *h; + struct fru_board_info_area *bia; + int ret, allocated = 0; + + /* If we know the eeprom length, try to read it off the device */ + if (fmc->eeprom_len && !fmc->eeprom) { + fmc->eeprom = kzalloc(fmc->eeprom_len, GFP_KERNEL); + if (!fmc->eeprom) + return -ENOMEM; + allocated = 1; + ret = fmc_read_ee(fmc, 0, fmc->eeprom, fmc->eeprom_len); + if (ret < 0) + goto out; + } + + /* If no eeprom, continue with other matches */ + if (!fmc->eeprom) + return 0; + + dev_info(fmc->hwdev, "mezzanine %i\n", fmc->slot_id); /* header */ + + /* So we have the eeprom: parse the FRU part (if any) */ + h = (void *)fmc->eeprom; + if (h->format != 1) { + pr_info(" EEPROM has no FRU information\n"); + goto out; + } + if (!fru_header_cksum_ok(h)) { + pr_info(" FRU: wrong header checksum\n"); + goto out; + } + bia = fru_get_board_area(h); + if (!fru_bia_cksum_ok(bia)) { + pr_info(" FRU: wrong board area checksum\n"); + goto out; + } + fmc->id.manufacturer = fru_get_board_manufacturer(h); + fmc->id.product_name = fru_get_product_name(h); + pr_info(" Manufacturer: %s\n", fmc->id.manufacturer); + pr_info(" Product name: %s\n", fmc->id.product_name); + + /* Create the short name (FIXME: look in sdb as well) */ + fmc->mezzanine_name = kstrdup(fmc->id.product_name, GFP_KERNEL); + +out: + if (allocated) { + kfree(fmc->eeprom); + fmc->eeprom = NULL; + } + return 0; /* no error: let other identification work */ +} + +/* Some ID data is allocated using fru_alloc() above, so release it */ +void fmc_free_id_info(struct fmc_device *fmc) +{ + kfree(fmc->mezzanine_name); + kfree(fmc->id.manufacturer); + kfree(fmc->id.product_name); +} diff --git a/drivers/fmc/fmc-private.h b/drivers/fmc/fmc-private.h new file mode 100644 index 000000000..1e5136643 --- /dev/null +++ b/drivers/fmc/fmc-private.h @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2015 CERN (www.cern.ch) + * Author: Federico Vaga <federico.vaga@cern.ch> + * + * Released according to the GNU GPL, version 2 or any later version. + */ + +extern int fmc_debug_init(struct fmc_device *fmc); +extern void fmc_debug_exit(struct fmc_device *fmc); diff --git a/drivers/fmc/fmc-sdb.c b/drivers/fmc/fmc-sdb.c new file mode 100644 index 000000000..d0e65b86d --- /dev/null +++ b/drivers/fmc/fmc-sdb.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Released according to the GNU GPL, version 2 or any later version. + * + * This work is part of the White Rabbit project, a research effort led + * by CERN, the European Institute for Nuclear Research. + */ +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/fmc.h> +#include <linux/sdb.h> +#include <linux/err.h> +#include <linux/fmc-sdb.h> +#include <asm/byteorder.h> + +static uint32_t __sdb_rd(struct fmc_device *fmc, unsigned long address, + int convert) +{ + uint32_t res = fmc_readl(fmc, address); + if (convert) + return __be32_to_cpu(res); + return res; +} + +static struct sdb_array *__fmc_scan_sdb_tree(struct fmc_device *fmc, + unsigned long sdb_addr, + unsigned long reg_base, int level) +{ + uint32_t onew; + int i, j, n, convert = 0; + struct sdb_array *arr, *sub; + + onew = fmc_readl(fmc, sdb_addr); + if (onew == SDB_MAGIC) { + /* Uh! If we are little-endian, we must convert */ + if (SDB_MAGIC != __be32_to_cpu(SDB_MAGIC)) + convert = 1; + } else if (onew == __be32_to_cpu(SDB_MAGIC)) { + /* ok, don't convert */ + } else { + return ERR_PTR(-ENOENT); + } + /* So, the magic was there: get the count from offset 4*/ + onew = __sdb_rd(fmc, sdb_addr + 4, convert); + n = __be16_to_cpu(*(uint16_t *)&onew); + arr = kzalloc(sizeof(*arr), GFP_KERNEL); + if (!arr) + return ERR_PTR(-ENOMEM); + arr->record = kcalloc(n, sizeof(arr->record[0]), GFP_KERNEL); + arr->subtree = kcalloc(n, sizeof(arr->subtree[0]), GFP_KERNEL); + if (!arr->record || !arr->subtree) { + kfree(arr->record); + kfree(arr->subtree); + kfree(arr); + return ERR_PTR(-ENOMEM); + } + + arr->len = n; + arr->level = level; + arr->fmc = fmc; + for (i = 0; i < n; i++) { + union sdb_record *r; + + for (j = 0; j < sizeof(arr->record[0]); j += 4) { + *(uint32_t *)((void *)(arr->record + i) + j) = + __sdb_rd(fmc, sdb_addr + (i * 64) + j, convert); + } + r = &arr->record[i]; + arr->subtree[i] = ERR_PTR(-ENODEV); + if (r->empty.record_type == sdb_type_bridge) { + struct sdb_component *c = &r->bridge.sdb_component; + uint64_t subaddr = __be64_to_cpu(r->bridge.sdb_child); + uint64_t newbase = __be64_to_cpu(c->addr_first); + + subaddr += reg_base; + newbase += reg_base; + sub = __fmc_scan_sdb_tree(fmc, subaddr, newbase, + level + 1); + arr->subtree[i] = sub; /* may be error */ + if (IS_ERR(sub)) + continue; + sub->parent = arr; + sub->baseaddr = newbase; + } + } + return arr; +} + +int fmc_scan_sdb_tree(struct fmc_device *fmc, unsigned long address) +{ + struct sdb_array *ret; + if (fmc->sdb) + return -EBUSY; + ret = __fmc_scan_sdb_tree(fmc, address, 0 /* regs */, 0); + if (IS_ERR(ret)) + return PTR_ERR(ret); + fmc->sdb = ret; + return 0; +} +EXPORT_SYMBOL(fmc_scan_sdb_tree); + +static void __fmc_sdb_free(struct sdb_array *arr) +{ + int i, n; + + if (!arr) + return; + n = arr->len; + for (i = 0; i < n; i++) { + if (IS_ERR(arr->subtree[i])) + continue; + __fmc_sdb_free(arr->subtree[i]); + } + kfree(arr->record); + kfree(arr->subtree); + kfree(arr); +} + +int fmc_free_sdb_tree(struct fmc_device *fmc) +{ + __fmc_sdb_free(fmc->sdb); + fmc->sdb = NULL; + return 0; +} +EXPORT_SYMBOL(fmc_free_sdb_tree); + +/* This helper calls reprogram and inizialized sdb as well */ +int fmc_reprogram_raw(struct fmc_device *fmc, struct fmc_driver *d, + void *gw, unsigned long len, int sdb_entry) +{ + int ret; + + ret = fmc->op->reprogram_raw(fmc, d, gw, len); + if (ret < 0) + return ret; + if (sdb_entry < 0) + return ret; + + /* We are required to find SDB at a given offset */ + ret = fmc_scan_sdb_tree(fmc, sdb_entry); + if (ret < 0) { + dev_err(&fmc->dev, "Can't find SDB at address 0x%x\n", + sdb_entry); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL(fmc_reprogram_raw); + +/* This helper calls reprogram and inizialized sdb as well */ +int fmc_reprogram(struct fmc_device *fmc, struct fmc_driver *d, char *gw, + int sdb_entry) +{ + int ret; + + ret = fmc->op->reprogram(fmc, d, gw); + if (ret < 0) + return ret; + if (sdb_entry < 0) + return ret; + + /* We are required to find SDB at a given offset */ + ret = fmc_scan_sdb_tree(fmc, sdb_entry); + if (ret < 0) { + dev_err(&fmc->dev, "Can't find SDB at address 0x%x\n", + sdb_entry); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL(fmc_reprogram); + +void fmc_show_sdb_tree(const struct fmc_device *fmc) +{ + pr_err("%s: not supported anymore, use debugfs to dump SDB\n", + __func__); +} +EXPORT_SYMBOL(fmc_show_sdb_tree); + +signed long fmc_find_sdb_device(struct sdb_array *tree, + uint64_t vid, uint32_t did, unsigned long *sz) +{ + signed long res = -ENODEV; + union sdb_record *r; + struct sdb_product *p; + struct sdb_component *c; + int i, n = tree->len; + uint64_t last, first; + + /* FIXME: what if the first interconnect is not at zero? */ + for (i = 0; i < n; i++) { + r = &tree->record[i]; + c = &r->dev.sdb_component; + p = &c->product; + + if (!IS_ERR(tree->subtree[i])) + res = fmc_find_sdb_device(tree->subtree[i], + vid, did, sz); + if (res >= 0) + return res + tree->baseaddr; + if (r->empty.record_type != sdb_type_device) + continue; + if (__be64_to_cpu(p->vendor_id) != vid) + continue; + if (__be32_to_cpu(p->device_id) != did) + continue; + /* found */ + last = __be64_to_cpu(c->addr_last); + first = __be64_to_cpu(c->addr_first); + if (sz) + *sz = (typeof(*sz))(last + 1 - first); + return first + tree->baseaddr; + } + return res; +} +EXPORT_SYMBOL(fmc_find_sdb_device); diff --git a/drivers/fmc/fmc-trivial.c b/drivers/fmc/fmc-trivial.c new file mode 100644 index 000000000..8defdee3e --- /dev/null +++ b/drivers/fmc/fmc-trivial.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * The software is provided "as is"; the copyright holders disclaim + * all warranties and liabilities, to the extent permitted by + * applicable law. + */ + +/* A trivial fmc driver that can load a gateware file and reports interrupts */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/fmc.h> + +static struct fmc_driver t_drv; /* initialized later */ + +static irqreturn_t t_handler(int irq, void *dev_id) +{ + struct fmc_device *fmc = dev_id; + + fmc_irq_ack(fmc); + dev_info(&fmc->dev, "received irq %i\n", irq); + return IRQ_HANDLED; +} + +static struct fmc_gpio t_gpio[] = { + { + .gpio = FMC_GPIO_IRQ(0), + .mode = GPIOF_DIR_IN, + .irqmode = IRQF_TRIGGER_RISING, + }, { + .gpio = FMC_GPIO_IRQ(1), + .mode = GPIOF_DIR_IN, + .irqmode = IRQF_TRIGGER_RISING, + } +}; + +static int t_probe(struct fmc_device *fmc) +{ + int ret; + int index = 0; + + index = fmc_validate(fmc, &t_drv); + if (index < 0) + return -EINVAL; /* not our device: invalid */ + + ret = fmc_irq_request(fmc, t_handler, "fmc-trivial", IRQF_SHARED); + if (ret < 0) + return ret; + /* ignore error code of call below, we really don't care */ + fmc_gpio_config(fmc, t_gpio, ARRAY_SIZE(t_gpio)); + + ret = fmc_reprogram(fmc, &t_drv, "", 0); + if (ret == -EPERM) /* programming not supported */ + ret = 0; + if (ret < 0) + fmc_irq_free(fmc); + + /* FIXME: reprogram LM32 too */ + return ret; +} + +static int t_remove(struct fmc_device *fmc) +{ + fmc_irq_free(fmc); + return 0; +} + +static struct fmc_driver t_drv = { + .version = FMC_VERSION, + .driver.name = KBUILD_MODNAME, + .probe = t_probe, + .remove = t_remove, + /* no table, as the current match just matches everything */ +}; + + /* We accept the generic parameters */ +FMC_PARAM_BUSID(t_drv); +FMC_PARAM_GATEWARE(t_drv); + +static int t_init(void) +{ + int ret; + + ret = fmc_driver_register(&t_drv); + return ret; +} + +static void t_exit(void) +{ + fmc_driver_unregister(&t_drv); +} + +module_init(t_init); +module_exit(t_exit); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/fmc/fmc-write-eeprom.c b/drivers/fmc/fmc-write-eeprom.c new file mode 100644 index 000000000..3eb81bb1f --- /dev/null +++ b/drivers/fmc/fmc-write-eeprom.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Released according to the GNU GPL, version 2 or any later version. + * + * This work is part of the White Rabbit project, a research effort led + * by CERN, the European Institute for Nuclear Research. + */ +#include <linux/module.h> +#include <linux/string.h> +#include <linux/firmware.h> +#include <linux/init.h> +#include <linux/fmc.h> +#include <asm/unaligned.h> + +/* + * This module uses the firmware loader to program the whole or part + * of the FMC eeprom. The meat is in the _run functions. However, no + * default file name is provided, to avoid accidental mishaps. Also, + * you must pass the busid argument + */ +static struct fmc_driver fwe_drv; + +FMC_PARAM_BUSID(fwe_drv); + +/* The "file=" is like the generic "gateware=" used elsewhere */ +static char *fwe_file[FMC_MAX_CARDS]; +static int fwe_file_n; +module_param_array_named(file, fwe_file, charp, &fwe_file_n, 0444); + +static int fwe_run_tlv(struct fmc_device *fmc, const struct firmware *fw, + int write) +{ + const uint8_t *p = fw->data; + int len = fw->size; + uint16_t thislen, thisaddr; + int err; + + /* format is: 'w' addr16 len16 data... */ + while (len > 5) { + thisaddr = get_unaligned_le16(p+1); + thislen = get_unaligned_le16(p+3); + if (p[0] != 'w' || thislen + 5 > len) { + dev_err(&fmc->dev, "invalid tlv at offset %ti\n", + p - fw->data); + return -EINVAL; + } + err = 0; + if (write) { + dev_info(&fmc->dev, "write %i bytes at 0x%04x\n", + thislen, thisaddr); + err = fmc_write_ee(fmc, thisaddr, p + 5, thislen); + } + if (err < 0) { + dev_err(&fmc->dev, "write failure @0x%04x\n", + thisaddr); + return err; + } + p += 5 + thislen; + len -= 5 + thislen; + } + if (write) + dev_info(&fmc->dev, "write_eeprom: success\n"); + return 0; +} + +static int fwe_run_bin(struct fmc_device *fmc, const struct firmware *fw) +{ + int ret; + + dev_info(&fmc->dev, "programming %zi bytes\n", fw->size); + ret = fmc_write_ee(fmc, 0, (void *)fw->data, fw->size); + if (ret < 0) { + dev_info(&fmc->dev, "write_eeprom: error %i\n", ret); + return ret; + } + dev_info(&fmc->dev, "write_eeprom: success\n"); + return 0; +} + +static int fwe_run(struct fmc_device *fmc, const struct firmware *fw, char *s) +{ + char *last4 = s + strlen(s) - 4; + int err; + + if (!strcmp(last4, ".bin")) + return fwe_run_bin(fmc, fw); + if (!strcmp(last4, ".tlv")) { + err = fwe_run_tlv(fmc, fw, 0); + if (!err) + err = fwe_run_tlv(fmc, fw, 1); + return err; + } + dev_err(&fmc->dev, "invalid file name \"%s\"\n", s); + return -EINVAL; +} + +/* + * Programming is done at probe time. Morever, only those listed with + * busid= are programmed. + * card is probed for, only one is programmed. Unfortunately, it's + * difficult to know in advance when probing the first card if others + * are there. + */ +static int fwe_probe(struct fmc_device *fmc) +{ + int err, index = 0; + const struct firmware *fw; + struct device *dev = &fmc->dev; + char *s; + + if (!fwe_drv.busid_n) { + dev_err(dev, "%s: no busid passed, refusing all cards\n", + KBUILD_MODNAME); + return -ENODEV; + } + + index = fmc_validate(fmc, &fwe_drv); + if (index < 0) { + pr_err("%s: refusing device \"%s\"\n", KBUILD_MODNAME, + dev_name(dev)); + return -ENODEV; + } + if (index >= fwe_file_n) { + pr_err("%s: no filename for device index %i\n", + KBUILD_MODNAME, index); + return -ENODEV; + } + s = fwe_file[index]; + if (!s) { + pr_err("%s: no filename for \"%s\" not programming\n", + KBUILD_MODNAME, dev_name(dev)); + return -ENOENT; + } + err = request_firmware(&fw, s, dev); + if (err < 0) { + dev_err(&fmc->dev, "request firmware \"%s\": error %i\n", + s, err); + return err; + } + fwe_run(fmc, fw, s); + release_firmware(fw); + return 0; +} + +static int fwe_remove(struct fmc_device *fmc) +{ + return 0; +} + +static struct fmc_driver fwe_drv = { + .version = FMC_VERSION, + .driver.name = KBUILD_MODNAME, + .probe = fwe_probe, + .remove = fwe_remove, + /* no table, as the current match just matches everything */ +}; + +static int fwe_init(void) +{ + int ret; + + ret = fmc_driver_register(&fwe_drv); + return ret; +} + +static void fwe_exit(void) +{ + fmc_driver_unregister(&fwe_drv); +} + +module_init(fwe_init); +module_exit(fwe_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/fmc/fru-parse.c b/drivers/fmc/fru-parse.c new file mode 100644 index 000000000..eb21480d3 --- /dev/null +++ b/drivers/fmc/fru-parse.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2012 CERN (www.cern.ch) + * Author: Alessandro Rubini <rubini@gnudd.com> + * + * Released according to the GNU GPL, version 2 or any later version. + * + * This work is part of the White Rabbit project, a research effort led + * by CERN, the European Institute for Nuclear Research. + */ +#include <linux/ipmi-fru.h> + +/* Some internal helpers */ +static struct fru_type_length * +__fru_get_board_tl(struct fru_common_header *header, int nr) +{ + struct fru_board_info_area *bia; + struct fru_type_length *tl; + + bia = fru_get_board_area(header); + tl = bia->tl; + while (nr > 0 && !fru_is_eof(tl)) { + tl = fru_next_tl(tl); + nr--; + } + if (fru_is_eof(tl)) + return NULL; + return tl; +} + +static char *__fru_alloc_get_tl(struct fru_common_header *header, int nr) +{ + struct fru_type_length *tl; + char *res; + + tl = __fru_get_board_tl(header, nr); + if (!tl) + return NULL; + + res = fru_alloc(fru_strlen(tl) + 1); + if (!res) + return NULL; + return fru_strcpy(res, tl); +} + +/* Public checksum verifiers */ +int fru_header_cksum_ok(struct fru_common_header *header) +{ + uint8_t *ptr = (void *)header; + int i, sum; + + for (i = sum = 0; i < sizeof(*header); i++) + sum += ptr[i]; + return (sum & 0xff) == 0; +} +int fru_bia_cksum_ok(struct fru_board_info_area *bia) +{ + uint8_t *ptr = (void *)bia; + int i, sum; + + for (i = sum = 0; i < 8 * bia->area_len; i++) + sum += ptr[i]; + return (sum & 0xff) == 0; +} + +/* Get various stuff, trivial */ +char *fru_get_board_manufacturer(struct fru_common_header *header) +{ + return __fru_alloc_get_tl(header, 0); +} +char *fru_get_product_name(struct fru_common_header *header) +{ + return __fru_alloc_get_tl(header, 1); +} +char *fru_get_serial_number(struct fru_common_header *header) +{ + return __fru_alloc_get_tl(header, 2); +} +char *fru_get_part_number(struct fru_common_header *header) +{ + return __fru_alloc_get_tl(header, 3); +} |