[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/kernel/linux/v4.14/drivers/fmc/Kconfig b/src/kernel/linux/v4.14/drivers/fmc/Kconfig
new file mode 100644
index 0000000..3a75f42
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/Makefile b/src/kernel/linux/v4.14/drivers/fmc/Makefile
new file mode 100644
index 0000000..e3da619
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-chardev.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-chardev.c
new file mode 100644
index 0000000..5ecf409
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-core.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-core.c
new file mode 100644
index 0000000..cec3b8d
--- /dev/null
+++ b/src/kernel/linux/v4.14/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,
+				"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];
+
+		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/src/kernel/linux/v4.14/drivers/fmc/fmc-debug.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-debug.c
new file mode 100644
index 0000000..3293072
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-dump.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-dump.c
new file mode 100644
index 0000000..cd1df47
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-fakedev.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-fakedev.c
new file mode 100644
index 0000000..941d093
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-match.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-match.c
new file mode 100644
index 0000000..a0956d1
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-private.h b/src/kernel/linux/v4.14/drivers/fmc/fmc-private.h
new file mode 100644
index 0000000..1e51366
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-sdb.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-sdb.c
new file mode 100644
index 0000000..ffdc176
--- /dev/null
+++ b/src/kernel/linux/v4.14/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 = kzalloc(sizeof(arr->record[0]) * n, GFP_KERNEL);
+	arr->subtree = kzalloc(sizeof(arr->subtree[0]) * n, 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/src/kernel/linux/v4.14/drivers/fmc/fmc-trivial.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-trivial.c
new file mode 100644
index 0000000..8defdee
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fmc-write-eeprom.c b/src/kernel/linux/v4.14/drivers/fmc/fmc-write-eeprom.c
new file mode 100644
index 0000000..3eb81bb
--- /dev/null
+++ b/src/kernel/linux/v4.14/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/src/kernel/linux/v4.14/drivers/fmc/fru-parse.c b/src/kernel/linux/v4.14/drivers/fmc/fru-parse.c
new file mode 100644
index 0000000..eb21480
--- /dev/null
+++ b/src/kernel/linux/v4.14/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);
+}