aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/fmc/fmc-sdb.c
diff options
context:
space:
mode:
authorAlessandro Rubini <rubini@gnudd.com>2013-06-18 23:47:13 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-06-18 15:36:56 -0700
commit77864f2e0a824a92bd93b4c9ad22c31d28ff55a6 (patch)
tree04748bc59eebcb9c127c33b897a7abb216044a3b /drivers/fmc/fmc-sdb.c
parentMAINTAINERS: add stable_kernel_rules.txt to stable maintainer information (diff)
downloadlinux-dev-77864f2e0a824a92bd93b4c9ad22c31d28ff55a6.tar.xz
linux-dev-77864f2e0a824a92bd93b4c9ad22c31d28ff55a6.zip
FMC: add core bus driver
This module offers registration services for both carriers (i.e. devices) and mezzanines (i.e. drivers). The matching for devices and drivers is performed according to the IPMI standard for FRU devices (Field Replaceable Units). The code includes support for parsing an SDB tree if present in the FPGA, and dumping it for diagnostics. SDB is not mandatory. Files in this commit correspond to commit ab23167f in the master branch of the project hosted on ohwr.org. Signed-off-by: Alessandro Rubini <rubini@gnudd.com> Acked-by: Juan David Gonzalez Cobas <dcobas@cern.ch> Acked-by: Emilio G. Cota <cota@braap.org> Acked-by: Samuel Iglesias Gonsalvez <siglesias@igalia.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/fmc/fmc-sdb.c')
-rw-r--r--drivers/fmc/fmc-sdb.c265
1 files changed, 265 insertions, 0 deletions
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);