Commit 279c21b6 authored by Alessandro Rubini's avatar Alessandro Rubini

Merge branch 'fmc-identifiers'

This merges a long series of commits that introduces support for FMC
identifiers in EEPROM, and related documentation.  The branch
also has some simple drivers used for debugging.

Support for identifiers is not complete at this point (most, it's not
tested because I experience gateware-related problems with eeprom
writing).  However, the code should introduce no regression at all,
so I'd better merge now, so to use the new drivers and prevent conflicts
from a later merge if I change the documentation.
parents be23bf8e 3535bb0c
*.o
*~
*.a
*.so
*.pyc
\ No newline at end of file
DIRS = kernel
DIRS = kernel tools
all clean modules install modules_install:
for d in $(DIRS); do $(MAKE) -C $$d $@ || exit 1; done
This diff is collapsed.
......@@ -4,11 +4,15 @@ LINUX ?= /lib/modules/$(shell uname -r)/build
ccflags-y += -I$M/include
obj-m = fmc.o
obj-m += fmc-fakedev.o
obj-m += fmc-trivial.o
obj-m += fmc-write-eeprom.o
obj-m += fmc-chardev.o
fmc-y = fmc-core.o
fmc-y += fmc-match.o
fmc-y += fmc-sdb.o
fmc-y += fru-parse.o
all modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) modules
......
/*
* 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>
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 struct file_operations fc_fops = {
.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;
if (fmc->op->validate)
index = fmc->op->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);
fc->fmc = fmc;
fc->misc.minor = MISC_DYNAMIC_MINOR;
fc->misc.fops = &fc_fops;
fc->misc.name = kstrdup(dev_name(&fmc->dev), GFP_KERNEL);
spin_lock(&fc_lock);
ret = misc_register(&fc->misc);
if (ret < 0) {
kfree(fc->misc.name);
kfree(fc);
} else {
list_add(&fc->list, &fc_devices);
}
spin_unlock(&fc_lock);
dev_info(fc->fmc->hwdev, "Created misc device \"%s\"\n",
fc->misc.name);
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->hwdev, "remove called but not found\n");
return -ENODEV;
}
spin_lock(&fc_lock);
list_del(&fc->list);
misc_deregister(&fc->misc);
kfree(fc->misc.name);
kfree(fc);
spin_unlock(&fc_lock);
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");
......@@ -27,16 +27,6 @@ static int fmc_check_version(unsigned long version, const char *name)
return 0;
}
static 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);
//const struct fmc_device_id *t = fdrv->id_table;
/* Currently, return 1 every time, until we define policies */
return 1;
}
static int fmc_uevent(struct device *dev, struct kobj_uevent_env *env)
{
//struct fmc_device *fdev = to_fmc_device(dev);
......@@ -85,7 +75,10 @@ struct device fmc_bus = {
.init_name = "fmc",
};
/* Functions for client modules */
/*
* Functions for client modules follow
*/
int fmc_driver_register(struct fmc_driver *drv)
{
if (fmc_check_version(drv->version, drv->driver.name))
......@@ -101,33 +94,63 @@ void fmc_driver_unregister(struct fmc_driver *drv)
}
EXPORT_SYMBOL(fmc_driver_unregister);
int fmc_device_register(struct fmc_device *fdev)
/* When a device is registered, we must read the eeprom and parse FRU */
int fmc_device_register(struct fmc_device *fmc)
{
if (fmc_check_version(fdev->version, fdev->carrier_name))
static int fmc_index; /* a "unique" name for lame devices */
uint32_t device_id;
int ret;
if (fmc_check_version(fmc->version, fmc->carrier_name))
return -EINVAL;
device_initialize(&fdev->dev);
if (!fdev->dev.release)
fdev->dev.release = __fmc_release;
if (!fdev->dev.parent)
fdev->dev.parent = &fmc_bus;
fdev->dev.bus = &fmc_bus_type;
{
static int i;
/* FIXME: the name */
dev_set_name(&fdev->dev, "fmc-%04x", i++);
/* make sure it is not initialized, or it complains */
memset(&fmc->dev.kobj, 0, sizeof(struct kobject));
device_initialize(&fmc->dev);
if (!fmc->dev.release)
fmc->dev.release = __fmc_release;
if (!fmc->dev.parent)
fmc->dev.parent = &fmc_bus;
/* Fill the identification stuff (may fail) */
fmc_fill_id_info(fmc);
fmc->dev.bus = &fmc_bus_type;
/* The name is from mezzanine info or carrier info. Or 0,1,2.. */
device_id = fmc->device_id;
if (!device_id) {
dev_warn(fmc->hwdev, "No device_id filled, using index\n");
device_id = fmc_index++;
}
return device_add(&fdev->dev);
if (!fmc->mezzanine_name) {
dev_warn(fmc->hwdev, "No mezzanine_name found\n");
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, "Failed in registering \"%s\"\n",
fmc->dev.kobj.name);
fmc_free_id_info(fmc);
return ret;
}
return 0;
}
EXPORT_SYMBOL(fmc_device_register);
void fmc_device_unregister(struct fmc_device *fdev)
void fmc_device_unregister(struct fmc_device *fmc)
{
device_del(&fdev->dev);
put_device(&fdev->dev);
device_del(&fmc->dev);
fmc_free_id_info(fmc);
put_device(&fmc->dev);
}
EXPORT_SYMBOL(fmc_device_unregister);
/* Init and exit are trivial */
static int fmc_init(void)
{
......
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released to the public domain, as example to be reused
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/workqueue.h>
#include <linux/fmc.h>
static char *ff_eeprom;
module_param_named(eeprom, ff_eeprom, charp, 0444);
/* Lazily, don't support the "standard" module parameters */
#define FF_EEPROM_SIZE 8192
/*
* 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_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;
struct device dev;
struct delayed_work work;
};
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->hwdev, "reprogramming with %s\n", gw);
ret = request_firmware(&fw, gw, fmc->hwdev);
if (ret < 0) {
dev_warn(fmc->hwdev, "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: add some fake GPIO mapping
*/
static struct fmc_device ff_template_fmc; /* defined later */
/* unregister and register again, after we changed eeprom */
static void ff_work_fn(struct work_struct *work)
{
struct delayed_work *dw = to_delayed_work(work);
struct ff_dev *ff = container_of(dw, struct ff_dev, work);
int ret;
fmc_device_unregister(&ff->fmc);
/* copy the template, so all lists are empty */
ff->fmc = ff_template_fmc;
ret = fmc_device_register(&ff->fmc);
if (ret < 0) {
/* Serious problem: we will crash at rmmod time */
pr_err("%s: can't re-register FMC device\n", __func__);
return;
}
}
/* low-level i2c */
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, ff_eeimg + offset, size);
return size;
}
int ff_eeprom_write(struct fmc_device *fmc, uint32_t offset,
const void *buf, size_t size)
{
struct ff_dev *ff = container_of(fmc, struct ff_dev, fmc);
if (offset > FF_EEPROM_SIZE)
return -EINVAL;
if (offset + size > FF_EEPROM_SIZE)
size = FF_EEPROM_SIZE - offset;
printk("size %i\n", size);
memcpy(ff_eeimg + 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" */
uint32_t ff_readl(struct fmc_device *fmc, int offset)
{
return 0;
}
void ff_writel(struct fmc_device *fmc, uint32_t value, int offset)
{
return;
}
static struct fmc_operations ff_fmc_operations = {
.readl = ff_readl,
.writel = ff_writel,
.reprogram = ff_reprogram,
.irq_request = ff_irq_request,
.read_ee = ff_read_ee,
.write_ee = ff_write_ee,
};
/* Every device must have a release method: provide a default */
static void __ff_release(struct device *dev)
{
printk("%s\n", __func__);
memset(dev, 0, sizeof(*dev));
dev->release = __ff_release;
}
static struct ff_dev ff_static_dev = {
.dev = {
.release = __ff_release,
}, .fmc = {
/* the template below will be copied herein */
}
};
static struct fmc_device ff_template_fmc = {
.version = FMC_VERSION,
.carrier_name = "fake",
.device_id = 0xf001, /* fool */
.eeprom = ff_eeimg,
.eeprom_len = sizeof(ff_eeimg),
.op = &ff_fmc_operations,
.hwdev = &ff_static_dev.dev,
.flags = FMC_DEVICE_HAS_GOLDEN,
};
/* init and exit */
int ff_init(void)
{
struct ff_dev *ff = &ff_static_dev;
const struct firmware *fw;
int len, ret = 0;
INIT_DELAYED_WORK(&ff->work, ff_work_fn);
dev_set_name(&ff->dev, "fake-fmc");
ff->fmc = ff_template_fmc;
ret = device_register(&ff->dev);
if (ret)
return ret;
/* If the user passed "eeprom=" as a parameter, fetch it */
if (ff_eeprom) {
ret = request_firmware(&fw, ff_eeprom, &ff->dev);
if (ret < 0) {
dev_err(&ff->dev, "Can't load \"%s\" (error %i)\n",
ff_eeprom, -ret);
} else {
len = min(fw->size, (size_t)FF_EEPROM_SIZE);
memcpy(ff_eeimg, fw->data, len);
release_firmware(fw);
}
}
ret = fmc_device_register(&ff->fmc);
if (ret) {
device_unregister(&ff->dev);
return ret;
}
//schedule_delayed_work(&ff->work, HZ * 2); /* remove, replug, in 2s */
return ret;
}
void ff_exit(void)
{
struct ff_dev *ff = &ff_static_dev;
cancel_delayed_work_sync(&ff->work);
fmc_device_unregister(&ff->fmc);
device_unregister(&ff->dev);
}
module_init(ff_init);
module_exit(ff_exit);
MODULE_LICENSE("GPL and additional rights");
/*
* 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(int 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->hwdev, "Driver has no ID: matches all\n");
matched = 1;
} else {
for (i = 0; i < fdrv->id_table.fru_id_nr; i++, fid++) {
if (strcmp(fid->manufacturer, fdev->id.manufacturer))
continue;
if (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 kwown 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;
/* So we have the eeprom: parse the FRU part (if any) */
h = (void *)fmc->eeprom;
if (h->format != 1) {
dev_warn(fmc->hwdev, "EEPROM has no FRU information\n");
goto out;
}
if (!fru_header_cksum_ok(h)) {
dev_warn(fmc->hwdev,"FRU: wrong header checksum\n");
goto out;
}
bia = fru_get_board_area(h);
if (!fru_bia_cksum_ok(bia)) {
dev_warn(fmc->hwdev, "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);
dev_info(fmc->hwdev, "Manufacturer: %s\n", fmc->id.manufacturer);
dev_info(fmc->hwdev, "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);
}
/*
* 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);
}
......@@ -23,15 +23,48 @@ struct fmc_driver;
* to check the version of the data structures we receive.
*/
#define FMC_MAJOR 1
#define FMC_MAJOR 2
#define FMC_MINOR 0
#define FMC_VERSION ((FMC_MAJOR << 16) | FMC_MINOR)
#define __FMC_MAJOR(x) ((x) >> 16)
#define __FMC_MINOR(x) ((x) & 0xffff)
/*
* The device identification, as defined by the IPMI FRU (Field Replaceable
* Unit) includes four different stings to describe the device. Here we
* only match the "Board Manutacturer" and the "Board Product Name",
* ignoring the "Board Serial Number" and "Board Part Number". All 4 are
* expected to be strings, so they are treated as zero-terminated C strings.
* Unspecified string (NULL) means "any", so if both are unspecified this
* is a catch-all driver. So null entries are allowed and we use array
* and length. This is unlike pci and usb that use null-terminated arrays
*/
struct fmc_fru_id {
char *manufacturer;
char *product_name;
};
/*
* If the FPGA is already programmed (think Etherbone or the second
* SVEC slot), we can match on SDB devices in the memory image. This
* match uses an array of devices that must all be present, and the
* match is based on vendor and device only. Further checks are expected
* to happen in the probe function. Zero meas "any" and catch-all is allowed.
*/
struct fmc_sdb_one_id {
uint64_t vendor;
uint32_t device;
};
struct fmc_sdb_id {
struct fmc_sdb_one_id *cores;
int cores_nr;
};
struct fmc_device_id {
/* FIXME: the device ID must be defined according to eeprom contents */
uint64_t unique_id;
struct fmc_fru_id *fru_id;
int fru_id_nr;
struct fmc_sdb_id *sdb_id;
int sdb_id_nr;
};
#define FMC_MAX_CARDS 16 /* That many with the same matching driver... */
......@@ -42,7 +75,7 @@ struct fmc_driver {
struct device_driver driver;
int (*probe)(struct fmc_device *);
int (*remove)(struct fmc_device *);
const struct fmc_device_id *id_table;
const struct fmc_device_id id_table;
/* What follows is for generic module parameters */
int busid_n;
int busid_val[FMC_MAX_CARDS];
......@@ -108,11 +141,19 @@ struct fmc_operations {
int (*write_ee)(struct fmc_device *fmc, int pos, const void *d, int l);
};
/* The device reports all information needed to access hw */
/*
* The device reports all information needed to access hw.
*
* If we have eeprom_len and not contents, the core reads it.
* Then, parsing of identifiers is done by the corem which fills fmc_fru_id..
* Similarly a device that must be matched based on SDB cores must
* fill the entry point and the core will scan the bus (FIXME: sdb match)
*/
struct fmc_device {
unsigned long version;
unsigned long flags;
struct fmc_device_id id; /* for the match function */
struct module *owner; /* char device must pin it */
struct fmc_fru_id id; /* for EEPROM-based match */
struct fmc_operations *op; /* carrier-provided */
int irq; /* according to host bus. 0 == none */
int eeprom_len; /* Usually 8kB, may be less */
......@@ -120,9 +161,13 @@ struct fmc_device {
char *carrier_name; /* "SPEC" or similar, for special use */
void *carrier_data; /* "struct spec *" or equivalent */
__iomem void *base; /* May be NULL (Etherbone) */
unsigned long memlen; /* Used for the char device */
struct device dev; /* For Linux use */
struct device *hwdev; /* The underlying hardware device */
unsigned long sdbfs_entry;
struct sdb_array *sdb;
uint32_t device_id; /* Filled by the device */
char *mezzanine_name; /* Built by fmc-core (allocated) */
void *mezzanine_data;
};
#define to_fmc_device(x) container_of((x), struct fmc_device, dev)
......@@ -130,6 +175,7 @@ struct fmc_device {
#define FMC_DEVICE_HAS_GOLDEN 1
#define FMC_DEVICE_HAS_CUSTOM 2
#define FMC_DEVICE_NO_MEZZANINE 4
#define FMC_DEVICE_MATCH_SDB 8 /* fmc-core must scan sdb in fpga */
/* If the carrier offers no readl/writel, use base address */
static inline uint32_t fmc_readl(struct fmc_device *fmc, int offset)
......@@ -163,4 +209,10 @@ extern void fmc_driver_unregister(struct fmc_driver *drv);
extern int fmc_device_register(struct fmc_device *tdev);
extern void fmc_device_unregister(struct fmc_device *tdev);
/* Internal cross-calls between files; not exported to otther modules */
extern int fmc_match(struct device *dev, struct device_driver *drv);
extern int fmc_fill_id_info(struct fmc_device *fmc);
extern void fmc_free_id_info(struct fmc_device *fmc);
#endif /* __LINUX_FMC_H__ */
/*
* 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.
*/
#ifndef __LINUX_IPMI_FRU_H__
#define __LINUX_IPMI_FRU_H__
#ifdef __KERNEL__
# include <linux/types.h>
# include <linux/string.h>
#else
# include <stdint.h>
# include <string.h>
#endif
/*
* These structures match the unaligned crap we have in FRU1011.pdf
* (http://download.intel.com/design/servers/ipmi/FRU1011)
*/
/* chapter 8, page 5 */
struct fru_common_header {
uint8_t format; /* 0x01 */
uint8_t internal_use_off; /* multiple of 8 bytes */
uint8_t chassis_info_off; /* multiple of 8 bytes */
uint8_t board_area_off; /* multiple of 8 bytes */
uint8_t product_area_off; /* multiple of 8 bytes */
uint8_t multirecord_off; /* multiple of 8 bytes */
uint8_t pad; /* must be 0 */
uint8_t checksum; /* sum modulo 256 must be 0 */
};
/* chapter 9, page 5 -- internal_use: not used by us */
/* chapter 10, page 6 -- chassis info: not used by us */
/* chapter 13, page 9 -- used by board_info_area below */
struct fru_type_length {
uint8_t type_length;
uint8_t data[0];
};
/* chapter 11, page 7 */
struct fru_board_info_area {
uint8_t format; /* 0x01 */
uint8_t area_len; /* multiple of 8 bytes */
uint8_t language; /* I hope it's 0 */
uint8_t mfg_date[3]; /* LSB, minutes since 1996-01-01 */
struct fru_type_length tl[0]; /* type-length stuff follows */
/*
* the TL there are in order:
* Board Manufacturer
* Board Product Name
* Board Serial Number
* Board Part Number
* FRU File ID (may be null)
* more manufacturer-specific stuff
* 0xc1 as a terminator
* 0x00 pad to a multiple of 8 bytes - 1
* checksum (sum of all stuff module 256 must be zero)
*/
};
enum fru_type {
FRU_TYPE_BINARY = 0x00,
FRU_TYPE_BCDPLUS = 0x40,
FRU_TYPE_ASCII6 = 0x80,
FRU_TYPE_ASCII = 0xc0, /* not ascii: depends on language */
};
/*
* some helpers
*/
static inline struct fru_board_info_area *fru_get_board_area(
struct fru_common_header *header)
{
/* we know for sure that the header is 8 bytes in size */
return (struct fru_board_info_area *)(header + header->board_area_off);
}
static inline int fru_type(struct fru_type_length *tl)
{
return tl->type_length & 0xc0;
}
static inline int fru_length(struct fru_type_length *tl)
{
return (tl->type_length & 0x3f) + 1; /* len of whole record */
}
/* assume ascii-latin1 encoding */
static inline int fru_strlen(struct fru_type_length *tl)
{
return fru_length(tl) - 1;
}
static inline char *fru_strcpy(char *dest, struct fru_type_length *tl)
{
int len = fru_strlen(tl);
memcpy(dest, tl->data, len);
dest[len + 1] = '\0';
return dest;
}
static inline struct fru_type_length *fru_next_tl(struct fru_type_length *tl)
{
return tl + fru_length(tl);
}
static inline int fru_is_eof(struct fru_type_length *tl)
{
return tl->type_length == 0xc1;
}
/*
* External functions defined in fru-parse.c.
*/
extern int fru_header_cksum_ok(struct fru_common_header *header);
extern int fru_bia_cksum_ok(struct fru_board_info_area *bia);
/*
* FIXME: is the following needed?
*
* extern int fru_dump_info(struct fru_common_header *header, char *prefix,
* int(*p)(char *fmt, ...));
*/
/* All these 4 return allocated strings by calling fru_alloc() */
extern char *fru_get_board_manufacturer(struct fru_common_header *header);
extern char *fru_get_product_name(struct fru_common_header *header);
extern char *fru_get_serial_number(struct fru_common_header *header);
extern char *fru_get_part_number(struct fru_common_header *header);
/* This must be defined by the caller of the above functions */
extern void *fru_alloc(int size);
#endif /* __LINUX_IMPI_FRU_H__ */
fru-dump
fmc-mem
\ No newline at end of file
CFLAGS = -Wall -ggdb -O2 -I../kernel/include
all: fru-dump fmc-mem
all clean:
$(MAKE) -C libipmi $@
# placeholders, so ../Makefile is happy
modules install modules_install:
#!/bin/sh
export FRU_VENDOR="CERN"
export FRU_NAME="FmcAdc100m14b4cha"
export FRU_PART="EDA-02063-V5-0"
serial="HCCFFIA___-CR"
for number in $(seq 1 50); do
# build number-string "ns"
ns="$(printf %06d $number)"
./fru-generator -s "${serial}${ns}" > eeprom-${ns}.bin
done
/*
* 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
static void help(char *prgname)
{
fprintf(stderr, "%s: use "
"\"%s <device> <addr> [<value>] [+<nbytes>]\"\n"
" <device> is a file name, all others are hex numbers\n"
" bursts of \"nbytes\" use stdin/stdout (value ignored)\n",
prgname, prgname);
exit(1);
}
static void w_loop(int fd, char **argv, int nbytes)
{
uint32_t reg;
while (nbytes >= sizeof(reg)) {
if (read(0, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: <stdin>: short read\n", argv[0]);
exit(1);
}
if (write(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: write(%s): %s\n", argv[0],
argv[1], strerror(errno));
exit(1);
}
nbytes -= sizeof(reg);
}
exit(0);
}
static void r_loop(int fd, char **argv, int nbytes)
{
uint32_t reg;
while (nbytes >= sizeof(reg)) {
if (read(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: read(%s): %s\n", argv[0],
argv[1], strerror(errno));
exit(1);
}
if (write(1, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: <stdout>: %s\n", argv[0],
strerror(errno));
exit(1);
}
nbytes -= sizeof(reg);
}
exit(0);
}
int main(int argc, char **argv)
{
int fd;
unsigned long addr, value, nbytes = 0;
uint32_t reg;
int dowrite = 0, doloop = 0;
char c;
if (argc < 3 || argv[1][0] == '-') /* -h, --help, whatever */
help(argv[0]);
/* boring scanning of arguments */
if (sscanf(argv[2], "%lx%c", &addr, &c) != 1) {
fprintf(stderr, "%s: Not an hex address \"%s\"\n",
argv[0], argv[2]);
exit(1);
}
if (argv[argc - 1][0] == '+') {
if (sscanf(argv[argc - 1], "+%lx%c", &nbytes, &c) != 1) {
fprintf(stderr, "%s: Not +<hex-number> \"%s\"\n",
argv[0], argv[argc - 1]);
exit(1);
}
doloop = 1;
argc--;
}
if (argc > 4)
help(argv[0]);
if (argc == 4) {
if (sscanf(argv[3], "%lx%c", &value, &c) != 1) {
fprintf(stderr, "%s: Not an hex value \"%s\"\n",
argv[0], argv[3]);
exit(1);
}
dowrite = 1;
}
if (0) { /* want to verify? */
if (dowrite)
printf("write %lx to %s:%lx (loop %i %lx)\n",
value, argv[1], addr, doloop, nbytes);
else
printf("read from %s:%lx (loop %i %lx)\n",
argv[1], addr, doloop, nbytes);
exit(0);
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
if (lseek(fd, addr, SEEK_SET) < 0) {
fprintf(stderr, "%s: %s: lseek: %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
/* each loop function here exits when it's done */
if (doloop && dowrite)
w_loop(fd, argv, nbytes);
if (doloop)
r_loop(fd, argv, nbytes);
/* one write only */
if (dowrite) {
reg = value;
if (write(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: write(): %s\n", argv[0],
strerror(errno));
exit(1);
}
exit(0);
}
/* one read only */
if (read(fd, &reg, sizeof(reg)) != sizeof(reg)) {
fprintf(stderr, "%s: read(): %s\n", argv[0],
strerror(errno));
exit(1);
}
printf("%08x\n", reg);
exit(0);
}
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/ipmi-fru.h>
#include "../kernel/fru-parse.c" /* Aaaargh!!!!! horrible hack... */
void *fru_alloc(int size)
{
return malloc(size);
}
#define EEPROM_SIZE 8192
int main(int argc, char **argv)
{
struct fru_board_info_area *bia;
struct fru_common_header *h;
struct stat stbuf;
void *eeprom;
char *fname;
FILE *f = NULL;
int i, err = 0;
if (argc < 2) {
fprintf(stderr, "%s: Use \"%s <fru-image> [...]\"\n",
argv[0], argv[0]);
exit(1);
}
eeprom = malloc(EEPROM_SIZE);
if (!eeprom) {
fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
exit(1);
}
h = eeprom;
for (i = 1; i < argc; i++) {
fname = argv[i];
if (f) /* second or later loop */
fclose(f);
f = fopen(fname, "r");
memset(eeprom, 0, EEPROM_SIZE);
if (!f) {
fprintf(stderr, "%s: %s: %s\n", argv[0], fname,
strerror(errno));
err++;
continue;
}
if (fstat(fileno(f), &stbuf) < 0) { /* never, I hope */
fprintf(stderr, "%s: %s: %s\n", argv[0], fname,
strerror(errno));
err++;
continue;
}
if (stbuf.st_size > EEPROM_SIZE)
stbuf.st_size = EEPROM_SIZE;
if (fread(eeprom, 1, stbuf.st_size, f) != stbuf.st_size) {
fprintf(stderr, "%s: %s: read error\n", argv[0],
fname);
err++;
continue;
}
/* some boring check */
if (h->format != 1) {
fprintf(stderr, "%s: not a FRU file\n", fname);
continue;
}
if (!fru_header_cksum_ok(h)) {
fprintf(stderr, "%s: wrong header checksum\n", fname);
}
bia = fru_get_board_area(h);
if (!fru_bia_cksum_ok(bia)) {
fprintf(stderr, "%s: wrong board area checksum\n",
fname);
}
/* FIXME: dump the stupid date -- which expires in 2027... */
/* The following may have nulls and segfault: who cares... */
printf("%s: manufacturer: %s\n", fname,
fru_get_board_manufacturer(h));
printf("%s: product-name: %s\n", fname,
fru_get_product_name(h));
printf("%s: serial-number: %s\n", fname,
fru_get_serial_number(h));
printf("%s: part-number: %s\n", fname,
fru_get_part_number(h));
}
return err != 0;
}
#! /usr/bin/env python
#-*-python-*-
# Copyright CERN, 2011, 2012
# Author: Matthieu Cattin <matthieu.cattin@cern.ch>
# Modified and broken by Alessandro Rubini, still learning python
# Import system modules
import sys
import getopt
import time
import datetime
import os
from libipmi.fmc_eeprom import *
"""
Creates a FRU binary file to be written into FMC EEPROM
"""
def main (argv0, argv):
# Defaults
FRU_VENDOR = "fmc-example"
FRU_NAME = "mezzanine"
FRU_SERIAL = "0001"
FRU_PART = "sample-part"
FRU_OUTPUT = "/dev/stdout"
verbose = 0
# Override defaults with environment variables
try:
FRU_VENDOR = os.environ['FRU_VENDOR']
except:
pass
try:
FRU_NAME = os.environ['FRU_NAME']
except:
pass
try:
FRU_SERIAL = os.environ['FRU_SERIAL']
except:
pass
try:
FRU_PART = os.environ['FRU_PART']
except:
pass
try:
FRU_OUTPUT = os.environ['FRU_OUTPUT']
except:
pass
if os.getenv("FRU_VERBOSE") is not None:
verbose = 1
# Override defaults with command line arguments
try:
opts, args = getopt.getopt(argv,"v:n:s:p:o:",["--help"])
except getopt.GetoptError:
print "fru-generator: wrong arguments"
sys.exit(2)
for opt, arg in opts:
if opt == "--help":
print "fru-generator: no help yet"
sys.exit(1)
if opt == '-v':
FRU_VENDOR = arg
if opt == '-n':
FRU_NAME = arg
if opt == '-s':
FRU_SERIAL = arg
if opt == '-p':
FRU_PART = arg
if opt == '-o':
FRU_OUTPUT = arg
if verbose:
print "VENDOR = " + FRU_VENDOR
print "NAME = " + FRU_NAME
print "SERIAL = " + FRU_SERIAL
print "PART = " + FRU_PART
print "OUTPUT = " + FRU_OUTPUT
#==================================================
# Calculate number of minutes since 0:00 1/1/96
now_date = datetime.datetime.now()
ref_date = datetime.datetime(1996, 1, 1)
diff_date = now_date - ref_date
total_seconds = diff_date.days * 86400 + diff_date.seconds
current_date = int(total_seconds//60)
mfg_date = current_date
#==================================================
# Create Board Info Area
# FRU field is used to store the date of generation of the eeprom content
# This could be used later to determine if the content has to be udated (bug fix, ...)
fru = "%s" % now_date
bia = BoardInfoArea(mfg_date, FRU_VENDOR, FRU_NAME, FRU_SERIAL, FRU_PART, fru)
#==================================================
# Multirecords Area
# output number, vnom, vmin, vmax, ripple, imin, imax
dcload0 = DCLoadRecord(0, 2.5, 2.375, 2.625, 0.0, 0, 4000) # VADJ
dcload1 = DCLoadRecord(1, 3.3, 3.135, 3.465, 0.0, 0, 3000) # P3V3
dcload2 = DCLoadRecord(2, 12.0, 11.4, 12.6, 0.0, 0, 1000) # P12V
dcload = [ dcload0, dcload1, dcload2 ]
# output number, vnom, vmin, vmax, ripple, imin, imax
dcout0 = DCOutputRecord(3, 0.0, 0.0, 0.0, 0.0, 0, 0) # VIO_B_M2C
dcout1 = DCOutputRecord(4, 0.0, 0.0, 0.0, 0.0, 0, 0) # VREF_A_M2C
dcout2 = DCOutputRecord(5, 0.0, 0.0, 0.0, 0.0, 0, 0) # VREF_B_M2C
dcout = [ dcout0, dcout1, dcout2 ]
# module size : 0=single width, 1=double width
# P1 size : 0=LPC, 1=HPC
# P2 size : 0=LPC, 1=HPC, 3=not fitted
# clock dir : 0=M2C, 1=C2M
# nb sig P1 A : number
# nb sig P1 B : number
# nb sig P2 A : number
# nb sig P2 B : number
# nb GBT P1 : number
# nb GBT P2 : number
# max TCK freq : frequency in MHz
oem = OEMRecord(0, 1, 3, 1, 68, 0, 0, 0, 0, 0, 0)
#==================================================
# Write eeprom content to a binary file
ipmi_open_file(FRU_OUTPUT)
#ipmi_set(bia, dcload, dcout, oem, iua)
ipmi_set(bia, dcload, dcout, oem)
ipmi_write()
ipmi_close_file()
if __name__ == '__main__' :
main(sys.argv[0], sys.argv[1:])
OBJ=ipmi.o
OUT=libipmi.a
OUT_SO=libipmi.so
CFLAGS+=-fPIC -shared -Wall -Wextra -ggdb
all: $(OUT) $(OUT_SO)
$(OUT): $(OBJ)
ar rcs $(OUT) $(OBJ)
$(OUT_SO): $(OBJ)
$(CC) $< $(CFLAGS) -shared -fPIC -L. -Wl,-soname,$@ -o $@
clean:
rm -rf $(OBJ) $(OUT) $(OUT_SO)
This diff is collapsed.
#include "ipmi.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static FILE *f = NULL;
struct common_header *ch = NULL;
struct board_info_area *bia = NULL;
struct oem_record *oem = NULL;
struct internal_use_area *iua = NULL;
struct dc_load_list *dcll = NULL;
struct dc_output_list *dcol = NULL;
int ipmi_file_open(const char *name)
{
if (f)
fclose(f);
f = fopen(name, "w");
if (!f)
return -1;
return 0;
}
void ipmi_file_close(void)
{
if (f)
fclose(f);
}
uint8_t checksum(uint8_t *data, int len)
{
int i;
int sum = 0;
for (i = 0; i < len; i++)
sum += data[i];
return (-sum)&0xff;
}
int board_info_area_get_size(uint8_t *pad)
{
int size = 13 +
(bia->mfgr_typelen & 0x3f) +
(bia->product_typelen & 0x3f) +
(bia->serial_typelen & 0x3f) +
(bia->partnum_typelen & 0x3f) +
(bia->fru_fid_typelen & 0x3f);
if (size & 0x7) {
if (pad) {
*pad = 8 - (size & 0x7);
}
size -= size % 8;
size += 8;
}
return size;
}
int internal_use_area_get_size(void)
{
return 1 + iua->len;
}
int ipmi_common_header_write(void)
{
int ret;
if (!ch || !f)
return -1;
ch->checksum = checksum((uint8_t *)ch, sizeof(struct common_header) - 1);
ret = fwrite(ch, 1, sizeof(struct common_header), f);
return 0;
}
void ipmi_set_board_info_area(struct board_info_area *d)
{
bia = d;
}
void ipmi_add_dc_load_record(struct dc_load_record *d)
{
struct dc_load_list *l = malloc(sizeof(struct dc_load_list));
l->rec = d;
l->next = NULL;
if (!dcll) {
dcll = l;
} else {
l->next = dcll;
dcll = l;
}
}
void ipmi_add_dc_output_record(struct dc_output_record *d)
{
struct dc_output_list *l = malloc(sizeof(struct dc_output_list));
l->rec = d;
l->next = NULL;
if (!dcol) {
dcol = l;
} else {
l->next = dcol;
dcol = l;
}
}
void ipmi_set_oem_record(struct oem_record *d)
{
oem = d;
}
int ipmi_board_info_area_write(void)
{
int i;
int len;
int ret;
uint8_t pad = 0;
uint8_t checksum;
if (!bia || !f)
return -1;
/* Write upto the mfgr_data */
ret = fwrite(bia, 6, 1, f);
len = bia->mfgr_typelen & 0x3f;
ret = fwrite(&bia->mfgr_typelen, 1, sizeof(uint8_t), f);
ret = fwrite(bia->mfgr_data, len, 1, f);
len = bia->product_typelen & 0x3f;
ret = fwrite(&bia->product_typelen, 1, sizeof(uint8_t), f);
ret = fwrite(bia->product_data, len, 1, f);
len = bia->serial_typelen & 0x3f;
ret = fwrite(&bia->serial_typelen, 1, sizeof(uint8_t), f);
ret = fwrite(bia->serial_data, len, 1, f);
len = bia->partnum_typelen & 0x3f;
ret = fwrite(&bia->partnum_typelen, 1, sizeof(uint8_t), f);
ret = fwrite(bia->partnum_data, len, 1, f);
len = bia->fru_fid_typelen & 0x3f;
ret = fwrite(&bia->fru_fid_typelen, 1, sizeof(uint8_t), f);
ret = fwrite(bia->fru_fid_data, len, 1, f);
bia->typelen_end = 0xc1;
ret = fwrite(&bia->typelen_end, 1, sizeof(uint8_t), f);
/* calculate checksum here */
checksum = 0;
checksum +=
bia->format +
bia->area_len +
bia->language +
bia->mfg_date0 +
bia->mfg_date1 +
bia->mfg_date2 +
bia->mfgr_typelen +
bia->product_typelen +
bia->serial_typelen +
bia->partnum_typelen +
bia->fru_fid_typelen +
bia->typelen_end;
for (i = 0; i < (bia->mfgr_typelen & 0x3f); i++)
checksum += bia->mfgr_data[i];
for (i = 0; i < (bia->product_typelen & 0x3f); i++)
checksum += bia->product_data[i];
for (i = 0; i < (bia->serial_typelen & 0x3f); i++)
checksum += bia->serial_data[i];
for (i = 0; i < (bia->partnum_typelen & 0x3f); i++)
checksum += bia->partnum_data[i];
for (i = 0; i < (bia->fru_fid_typelen & 0x3f); i++)
checksum += bia->fru_fid_data[i];
checksum = -checksum;
checksum &= 0xff;
bia->checksum = checksum;
uint8_t nul = 0;
board_info_area_get_size(&pad);
for (i = 0; i < pad; i++)
ret = fwrite(&nul, 1, sizeof(uint8_t), f);
ret = fwrite(&bia->checksum, 1, sizeof(uint8_t), f);
return 0;
}
int ipmi_dc_load_record_write(int end)
{
int ret;
struct dc_load_list *t;
if (!dcll || !f)
return -1;
t = dcll;
while (t) {
struct multirecord_header head;
head.record_typeid = 0x2; /* DC load type */
head.extra = 0x2;
if (end)
head.extra |= (1 << 7);
head.record_len = 13;
head.record_checksum = checksum((uint8_t *)t->rec,
sizeof(struct dc_load_record));
head.header_checksum = checksum((uint8_t *)&head,
sizeof(struct multirecord_header) - 1);
ret = fwrite(&head, 1, sizeof(struct multirecord_header), f);
ret = fwrite(&t->rec->voltage_required, 1, 1, f);
ret = fwrite(&t->rec->nominal_voltage, 1, 12, f);
t = t->next;
}
return 0;
}
int ipmi_dc_output_record_write(int end)
{
int ret;
struct dc_output_list *t;
if (!dcol || !f)
return -1;
t = dcol;
while (t) {
struct multirecord_header head;
head.record_typeid = 0x1; /* DC output type */
head.extra = 0x2;
if (end)
head.extra |= (1 << 7);
head.record_len = 13;
head.record_checksum = checksum((uint8_t *)t->rec,
sizeof(struct dc_output_record));
head.header_checksum = checksum((uint8_t *)&head,
sizeof(struct multirecord_header) - 1);
ret = fwrite(&head, 1, sizeof(struct multirecord_header), f);
ret = fwrite(&t->rec->output_info, 1, 1, f);
ret = fwrite(&t->rec->nominal_voltage, 1, 12, f);
t = t->next;
}
return 0;
}
int ipmi_oem_record_write(int end)
{
int ret;
struct multirecord_header head;
if (!oem || !f)
return -1;
/* VITA ID: 0x0012a2 (LS Byte first) */
oem->mfg_id0 = 0xa2;
oem->mfg_id1 = 0x12;
oem->mfg_id2 = 0x00;
head.record_typeid = 0xfa; /* OEM record type */
head.extra = 0x2;
if (end)
head.extra |= (1 << 7);
head.record_len = sizeof(struct oem_record);
head.record_checksum = checksum((uint8_t *)oem,
sizeof(struct oem_record));
head.header_checksum = checksum((uint8_t *)&head,
sizeof(struct multirecord_header) - 1);
ret = fwrite(&head, 1, sizeof(struct multirecord_header), f);
ret = fwrite(oem, 1, sizeof(struct oem_record), f);
return 0;
}
int multirecord_area_get_size(int *diff)
{
struct dc_load_list *l1 = dcll;
struct dc_output_list *l2 = dcol;
int sum = 0;
while (l1) {
sum += sizeof(struct multirecord_header);
sum += 13;
l1 = l1->next;
}
while (l2) {
sum += sizeof(struct multirecord_header);
sum += 13;
l2 = l2->next;
}
sum += sizeof(struct multirecord_header) + sizeof(struct oem_record);
if (sum % 8) {
if (diff) {
*diff = 8 - (sum % 8);
}
sum += 8;
sum &= ~7;
}
return sum;
}
int ipmi_write(void)
{
int pad = 0;
int padlen = 0;
ch = malloc(sizeof(struct common_header));
memset(ch, 0, sizeof(struct common_header));
ch->format = 1; // Format version
/*
* IPMI areas arrangement in memory
*
* +------------------------------+
* | Common header |
* +------------------------------+
* | Board area |
* +------------------------------+
* | Multi-record area |
* | +------------------------+
* | | 3x DC load records |
* | +------------------------+
* | | 3x DC output records |
* | +------------------------+
* | | OEM record |
* +-----+------------------------+
* | Internal use area (optional) |
* +------------------------------+
*/
// Compute area offsets
ch->board_area_off = sizeof(struct common_header)/8; // always 1
ch->multirecord_off = (sizeof(struct common_header) + board_info_area_get_size(NULL))/8;
if (iua)
ch->internal_use_off = (sizeof(struct common_header) + board_info_area_get_size(NULL) + multirecord_area_get_size(NULL))/8;
else
ch->internal_use_off = 0;
// Write common heade
ipmi_common_header_write();
// Write board info area, padding (to 8 byte multiple) is done inside the write function
bia->area_len = board_info_area_get_size(NULL)/8;
ipmi_board_info_area_write();
// Write multi-record area
ipmi_dc_load_record_write(0);
ipmi_dc_output_record_write(0);
ipmi_oem_record_write(1);
// Padding after multi-record area
multirecord_area_get_size(&padlen);
if (padlen) {
int i;
for (i = 0; i < padlen; i++)
fwrite(&pad, 1, 1, f);
}
// Write Internal Use area, if exists
if (iua)
ipmi_internal_use_area_write();
return 0;
}
void ipmi_set_internal_use_area(struct internal_use_area *d)
{
iua = d;
}
int ipmi_internal_use_area_write(void)
{
if (!iua || !f)
return -1;
fwrite(&iua->format, 1, 1, f);
fwrite(&iua->len, 1, 4, f);
fwrite(iua->data, 1, iua->len, f);
return 0;
}
unsigned char *ipmi_get_internal_use_data(char *data, int *l)
{
unsigned char *buf;
struct common_header *ch = (struct common_header *)data;
unsigned char *d = (unsigned char *)data + ch->internal_use_off*8;
int len = (int)d[1];
buf = malloc(sizeof(uint8_t) * (len + 1));
memcpy(buf, d+5, len);
buf[len] = 0;
*l = len;
return buf;
}
int ipmi_get_mfg_date(char *data)
{
int i;
int ret = 0;
struct common_header *ch = (struct common_header *)data;
unsigned char *date = (unsigned char *)data + ch->board_area_off*8 + 3;
for (i = 0; i < 3; i++)
ret |= (date[i] << (i*8));
return ret;
}
#ifndef IPMI_H
#define IPMI_H
#include <stdint.h>
#include <stdio.h>
/* 8 bytes */
struct common_header {
uint8_t format;
uint8_t internal_use_off;
uint8_t chassis_info_off;
uint8_t board_area_off;
uint8_t product_area_off;
uint8_t multirecord_off;
uint8_t pad;
uint8_t checksum;
};
struct board_info_area {
uint8_t format;
uint8_t area_len;
uint8_t language;
uint8_t mfg_date0;
uint8_t mfg_date1;
uint8_t mfg_date2;
uint8_t mfgr_typelen;
uint8_t *mfgr_data;
uint8_t product_typelen;
uint8_t *product_data;
uint8_t serial_typelen;
uint8_t *serial_data;
uint8_t partnum_typelen;
uint8_t *partnum_data;
uint8_t fru_fid_typelen;
uint8_t *fru_fid_data;
/* uint8_t *custom; */
uint8_t typelen_end;
uint8_t pad_len;
uint8_t checksum;
};
/* 5 bytes */
struct multirecord_header {
uint8_t record_typeid;
uint8_t extra;
uint8_t record_len;
uint8_t record_checksum;
uint8_t header_checksum;
};
struct dc_output_list {
struct dc_output_record *rec;
struct dc_output_list *next;
};
/* 13 bytes */
struct dc_load_record {
uint8_t voltage_required;
uint16_t nominal_voltage;
uint16_t min_voltage;
uint16_t max_voltage;
uint16_t spec_ripple;
uint16_t min_current;
uint16_t max_current;
};
struct dc_load_list {
struct dc_load_record *rec;
struct dc_load_list *next;
};
/* 13 bytes */
struct dc_output_record {
uint8_t output_info;
uint16_t nominal_voltage;
uint16_t max_neg_voltage_dev;
uint16_t max_pos_voltage_dev;
uint16_t ripple;
uint16_t min_current_draw;
uint16_t max_current_draw;
};
struct fmc_oem_data {
uint8_t subtype_version;
uint8_t other;
uint8_t p1_a_nsig;
uint8_t p1_b_nsig;
uint8_t p2_a_nsig;
uint8_t p2_b_nsig;
uint8_t p1_p2_gbt_ntran;
uint8_t max_clock;
};
/* 12 bytes */
struct oem_record {
uint8_t mfg_id0;
uint8_t mfg_id1;
uint8_t mfg_id2;
struct fmc_oem_data data;
};
struct internal_use_area {
uint8_t format;
int len;
char *data;
};
int ipmi_file_open(const char *name);
void ipmi_file_close(void);
int ipmi_write(void);
int ipmi_common_header_write(void);
void ipmi_set_board_info_area(struct board_info_area *);
int ipmi_board_info_area_write(void);
void ipmi_set_internal_use_area(struct internal_use_area *);
int ipmi_internal_use_area_write(void);
void ipmi_add_dc_load_record(struct dc_load_record *);
int ipmi_dc_load_record_write(int);
void ipmi_add_dc_output_record(struct dc_output_record *);
int ipmi_dc_output_record_write(int);
void ipmi_set_oem_record(struct oem_record *);
int ipmi_oem_record_write(int);
unsigned char *ipmi_get_internal_use_data(char *data, int *l);
int ipmi_get_mfg_date(char *data);
#endif
# This is an example configuration file for FMC eeprom generation.
# The contents reflect what I used to program my specimen of the
# "fine delay" card (official name: FmcDelay1ns4cha).
# SDB records live at offset 256 (the bus looks there and at other
# powers of two, but our FRU file is known to be small)
.
position = 256
# The IPMI-FRU material is in the "IPMI-FRU" file. It has been
# generated using ../fru-generator like this:
#
# ../fru-generator -v CERN -n FmcDelay1ns4cha -s proto-0 \
# -p EDA-02267-V3 > IPMI-FRU
IPMI-FRU
position = 0
# we have a "name" file, that includes "fdelay". But no configuration is
# needed.
# Finally, the fine-delay wants configuration at offset 0x1800, because
# SDB was added to the drivers later, and initially a known address was
# used. So prepare a writable file in there. The driver looks for "fd-c".
# The size of calibration information is around 100 bytes.
fd-calib
position = 0x1800
maxsize = 256
write = 1
placeholder
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment