summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/fmc/Makefile4
-rw-r--r--drivers/fmc/fmc-core.c274
-rw-r--r--drivers/fmc/fmc-dump.c100
-rw-r--r--drivers/fmc/fmc-match.c114
-rw-r--r--drivers/fmc/fmc-sdb.c265
-rw-r--r--drivers/fmc/fru-parse.c82
6 files changed, 838 insertions, 1 deletions
diff --git a/drivers/fmc/Makefile b/drivers/fmc/Makefile
index a2784d8b5306..df9893972a62 100644
--- a/drivers/fmc/Makefile
+++ b/drivers/fmc/Makefile
@@ -2,3 +2,7 @@
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
diff --git a/drivers/fmc/fmc-core.c b/drivers/fmc/fmc-core.c
index fc3547f32d5e..24d52497524d 100644
--- a/drivers/fmc/fmc-core.c
+++ b/drivers/fmc/fmc-core.c
@@ -1,13 +1,285 @@
-/* Temporary placeholder so the empty code can build */
+/*
+ * 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>
+
+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 struct bin_attribute fmc_eeprom_attr = {
+ .attr = { .name = "eeprom", .mode = S_IRUGO, },
+ .size = 8192, /* more or less standard */
+ .read = fmc_read_eeprom,
+};
+
+/*
+ * 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(struct fmc_device **devs, int n)
+{
+ 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,
+ "deivce 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];
+
+ if (fmc->flags == FMC_DEVICE_NO_MEZZANINE)
+ continue; /* dev_info already done above */
+
+ 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);
+ 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_dump_sdb(fmc);
+ }
+ return 0;
+
+out1:
+ device_del(&fmc->dev);
+out:
+ fmc_free_id_info(fmc);
+ put_device(&fmc->dev);
+
+ kfree(devarray);
+ for (i--; i >= 0; 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);
+
+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++) {
+ if (devs[i]->flags == FMC_DEVICE_NO_MEZZANINE)
+ continue;
+ 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);
diff --git a/drivers/fmc/fmc-dump.c b/drivers/fmc/fmc-dump.c
new file mode 100644
index 000000000000..c91afd6388f6
--- /dev/null
+++ b/drivers/fmc/fmc-dump.c
@@ -0,0 +1,100 @@
+/*
+ * 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);
+static int fmc_must_dump_sdb;
+module_param_named(dump_sdb, fmc_must_dump_sdb, 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);
+}
+
+void fmc_dump_sdb(const struct fmc_device *fmc)
+{
+ const uint8_t *line, *prev;
+ int i, len;
+
+ if (!fmc->sdb)
+ return;
+ if (!fmc_must_dump_sdb)
+ return;
+
+ /* If the argument is not-zero, do simple dump (== show) */
+ if (fmc_must_dump_sdb > 0)
+ fmc_show_sdb_tree(fmc);
+
+ if (fmc_must_dump_sdb == 1)
+ return;
+
+ /* If bigger than 1, dump it seriously, to help debugging */
+
+ /*
+ * Here we should really use libsdbfs (which is designed to
+ * work in kernel space as well) , but it doesn't support
+ * directories yet, and it requires better intergration (it
+ * should be used instead of fmc-specific code).
+ *
+ * So, lazily, just dump the top-level array
+ */
+ 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: poor dump of sdb first level:\n");
+
+ len = fmc->sdb->len * sizeof(union sdb_record);
+ line = (void *)fmc->sdb->record;
+ prev = NULL;
+ for (i = 0; i < len; i += LINELEN, line += LINELEN)
+ prev = dump_line(i, line, prev);
+ return;
+}
diff --git a/drivers/fmc/fmc-match.c b/drivers/fmc/fmc-match.c
new file mode 100644
index 000000000000..104a5efc2207
--- /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->op->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-sdb.c b/drivers/fmc/fmc-sdb.c
new file mode 100644
index 000000000000..74fb326f4af1
--- /dev/null
+++ b/drivers/fmc/fmc-sdb.c
@@ -0,0 +1,265 @@
+/*
+ * 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) {
+ arr->record = kzalloc(sizeof(arr->record[0]) * n, GFP_KERNEL);
+ arr->subtree = kzalloc(sizeof(arr->subtree[0]) * n, GFP_KERNEL);
+ }
+ if (!arr || !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(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;
+ }
+ fmc_dump_sdb(fmc);
+ return 0;
+}
+EXPORT_SYMBOL(fmc_reprogram);
+
+static void __fmc_show_sdb_tree(const struct fmc_device *fmc,
+ const struct sdb_array *arr)
+{
+ int i, j, n = arr->len, level = arr->level;
+ const struct sdb_array *ap;
+
+ for (i = 0; i < n; i++) {
+ unsigned long base;
+ union sdb_record *r;
+ struct sdb_product *p;
+ struct sdb_component *c;
+ r = &arr->record[i];
+ c = &r->dev.sdb_component;
+ p = &c->product;
+ base = 0;
+ for (ap = arr; ap; ap = ap->parent)
+ base += ap->baseaddr;
+ dev_info(&fmc->dev, "SDB: ");
+
+ for (j = 0; j < level; j++)
+ printk(KERN_CONT " ");
+ switch (r->empty.record_type) {
+ case sdb_type_interconnect:
+ printk(KERN_CONT "%08llx:%08x %.19s\n",
+ __be64_to_cpu(p->vendor_id),
+ __be32_to_cpu(p->device_id),
+ p->name);
+ break;
+ case sdb_type_device:
+ printk(KERN_CONT "%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:
+ printk(KERN_CONT "%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])) {
+ printk(KERN_CONT "(bridge error %li)\n",
+ PTR_ERR(arr->subtree[i]));
+ break;
+ }
+ __fmc_show_sdb_tree(fmc, arr->subtree[i]);
+ break;
+ case sdb_type_integration:
+ printk(KERN_CONT "integration\n");
+ break;
+ case sdb_type_repo_url:
+ printk(KERN_CONT "repo-url\n");
+ break;
+ case sdb_type_synthesis:
+ printk(KERN_CONT "synthesis-info\n");
+ break;
+ case sdb_type_empty:
+ printk(KERN_CONT "empty\n");
+ break;
+ default:
+ printk(KERN_CONT "UNKNOWN TYPE 0x%02x\n",
+ r->empty.record_type);
+ break;
+ }
+ }
+}
+
+void fmc_show_sdb_tree(const struct fmc_device *fmc)
+{
+ if (!fmc->sdb)
+ return;
+ __fmc_show_sdb_tree(fmc, fmc->sdb);
+}
+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/fru-parse.c b/drivers/fmc/fru-parse.c
new file mode 100644
index 000000000000..cb46263c5da2
--- /dev/null
+++ b/drivers/fmc/fru-parse.c
@@ -0,0 +1,82 @@
+/*
+ * 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;
+ int len;
+
+ tl = __fru_get_board_tl(header, nr);
+ if (!tl)
+ return NULL;
+ len = fru_strlen(tl);
+ 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);
+}