mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
51
drivers/fmc/Kconfig
Normal file
51
drivers/fmc/Kconfig
Normal file
|
@ -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
|
13
drivers/fmc/Makefile
Normal file
13
drivers/fmc/Makefile
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
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
|
||||
|
||||
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
|
201
drivers/fmc/fmc-chardev.c
Normal file
201
drivers/fmc/fmc-chardev.c
Normal file
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
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);
|
||||
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");
|
304
drivers/fmc/fmc-core.c
Normal file
304
drivers/fmc/fmc-core.c
Normal file
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* Copyright (C) 2012 CERN (www.cern.ch)
|
||||
* Author: Alessandro Rubini <rubini@gnudd.com>
|
||||
*
|
||||
* Released according to the GNU GPL, version 2 or any later version.
|
||||
*
|
||||
* This work is part of the White Rabbit project, a research effort led
|
||||
* by CERN, the European Institute for Nuclear Research.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fmc.h>
|
||||
|
||||
static int fmc_check_version(unsigned long version, const char *name)
|
||||
{
|
||||
if (__FMC_MAJOR(version) != FMC_MAJOR) {
|
||||
pr_err("%s: \"%s\" has wrong major (has %li, expected %i)\n",
|
||||
__func__, name, __FMC_MAJOR(version), FMC_MAJOR);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (__FMC_MINOR(version) != FMC_MINOR)
|
||||
pr_info("%s: \"%s\" has wrong minor (has %li, expected %i)\n",
|
||||
__func__, name, __FMC_MINOR(version), FMC_MINOR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fmc_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
/* struct fmc_device *fdev = to_fmc_device(dev); */
|
||||
|
||||
/* FIXME: The MODALIAS */
|
||||
add_uevent_var(env, "MODALIAS=%s", "fmc");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fmc_probe(struct device *dev)
|
||||
{
|
||||
struct fmc_driver *fdrv = to_fmc_driver(dev->driver);
|
||||
struct fmc_device *fdev = to_fmc_device(dev);
|
||||
|
||||
return fdrv->probe(fdev);
|
||||
}
|
||||
|
||||
static int fmc_remove(struct device *dev)
|
||||
{
|
||||
struct fmc_driver *fdrv = to_fmc_driver(dev->driver);
|
||||
struct fmc_device *fdev = to_fmc_device(dev);
|
||||
|
||||
return fdrv->remove(fdev);
|
||||
}
|
||||
|
||||
static void fmc_shutdown(struct device *dev)
|
||||
{
|
||||
/* not implemented but mandatory */
|
||||
}
|
||||
|
||||
static struct bus_type fmc_bus_type = {
|
||||
.name = "fmc",
|
||||
.match = fmc_match,
|
||||
.uevent = fmc_uevent,
|
||||
.probe = fmc_probe,
|
||||
.remove = fmc_remove,
|
||||
.shutdown = fmc_shutdown,
|
||||
};
|
||||
|
||||
static void fmc_release(struct device *dev)
|
||||
{
|
||||
struct fmc_device *fmc = container_of(dev, struct fmc_device, dev);
|
||||
|
||||
kfree(fmc);
|
||||
}
|
||||
|
||||
/*
|
||||
* The eeprom is exported in sysfs, through a binary attribute
|
||||
*/
|
||||
|
||||
static ssize_t fmc_read_eeprom(struct file *file, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct fmc_device *fmc;
|
||||
int eelen;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
fmc = container_of(dev, struct fmc_device, dev);
|
||||
eelen = fmc->eeprom_len;
|
||||
if (off > eelen)
|
||||
return -ESPIPE;
|
||||
if (off == eelen)
|
||||
return 0; /* EOF */
|
||||
if (off + count > eelen)
|
||||
count = eelen - off;
|
||||
memcpy(buf, fmc->eeprom + off, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
static 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,
|
||||
};
|
||||
|
||||
/*
|
||||
* Functions for client modules follow
|
||||
*/
|
||||
|
||||
int fmc_driver_register(struct fmc_driver *drv)
|
||||
{
|
||||
if (fmc_check_version(drv->version, drv->driver.name))
|
||||
return -EINVAL;
|
||||
drv->driver.bus = &fmc_bus_type;
|
||||
return driver_register(&drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL(fmc_driver_register);
|
||||
|
||||
void fmc_driver_unregister(struct fmc_driver *drv)
|
||||
{
|
||||
driver_unregister(&drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL(fmc_driver_unregister);
|
||||
|
||||
/*
|
||||
* When a device set is registered, all eeproms must be read
|
||||
* and all FRUs must be parsed
|
||||
*/
|
||||
int fmc_device_register_n(struct fmc_device **devs, int n)
|
||||
{
|
||||
struct fmc_device *fmc, **devarray;
|
||||
uint32_t device_id;
|
||||
int i, ret = 0;
|
||||
|
||||
if (n < 1)
|
||||
return 0;
|
||||
|
||||
/* Check the version of the first data structure (function prints) */
|
||||
if (fmc_check_version(devs[0]->version, devs[0]->carrier_name))
|
||||
return -EINVAL;
|
||||
|
||||
devarray = kmemdup(devs, n * sizeof(*devs), GFP_KERNEL);
|
||||
if (!devarray)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Make all other checks before continuing, for all devices */
|
||||
for (i = 0; i < n; i++) {
|
||||
fmc = devarray[i];
|
||||
if (!fmc->hwdev) {
|
||||
pr_err("%s: device nr. %i has no hwdev pointer\n",
|
||||
__func__, i);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (fmc->flags & FMC_DEVICE_NO_MEZZANINE) {
|
||||
dev_info(fmc->hwdev, "absent mezzanine in slot %d\n",
|
||||
fmc->slot_id);
|
||||
continue;
|
||||
}
|
||||
if (!fmc->eeprom) {
|
||||
dev_err(fmc->hwdev, "no eeprom provided for slot %i\n",
|
||||
fmc->slot_id);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (!fmc->eeprom_addr) {
|
||||
dev_err(fmc->hwdev, "no eeprom_addr for slot %i\n",
|
||||
fmc->slot_id);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (!fmc->carrier_name || !fmc->carrier_data ||
|
||||
!fmc->device_id) {
|
||||
dev_err(fmc->hwdev,
|
||||
"deivce nr %i: carrier name, "
|
||||
"data or dev_id not set\n", i);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
}
|
||||
if (ret) {
|
||||
kfree(devarray);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Validation is ok. Now init and register the devices */
|
||||
for (i = 0; i < n; i++) {
|
||||
fmc = devarray[i];
|
||||
|
||||
fmc->nr_slots = n; /* each slot must know how many are there */
|
||||
fmc->devarray = devarray;
|
||||
|
||||
device_initialize(&fmc->dev);
|
||||
fmc->dev.release = fmc_release;
|
||||
fmc->dev.parent = fmc->hwdev;
|
||||
|
||||
/* Fill the identification stuff (may fail) */
|
||||
fmc_fill_id_info(fmc);
|
||||
|
||||
fmc->dev.bus = &fmc_bus_type;
|
||||
|
||||
/* Name from mezzanine info or carrier info. Or 0,1,2.. */
|
||||
device_id = fmc->device_id;
|
||||
if (!fmc->mezzanine_name)
|
||||
dev_set_name(&fmc->dev, "fmc-%04x", device_id);
|
||||
else
|
||||
dev_set_name(&fmc->dev, "%s-%04x", fmc->mezzanine_name,
|
||||
device_id);
|
||||
ret = device_add(&fmc->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(fmc->hwdev, "Slot %i: Failed in registering "
|
||||
"\"%s\"\n", fmc->slot_id, fmc->dev.kobj.name);
|
||||
goto out;
|
||||
}
|
||||
ret = sysfs_create_bin_file(&fmc->dev.kobj, &fmc_eeprom_attr);
|
||||
if (ret < 0) {
|
||||
dev_err(&fmc->dev, "Failed in registering eeprom\n");
|
||||
goto out1;
|
||||
}
|
||||
/* This device went well, give information to the user */
|
||||
fmc_dump_eeprom(fmc);
|
||||
fmc_dump_sdb(fmc);
|
||||
}
|
||||
return 0;
|
||||
|
||||
out1:
|
||||
device_del(&fmc->dev);
|
||||
out:
|
||||
fmc_free_id_info(fmc);
|
||||
put_device(&fmc->dev);
|
||||
|
||||
kfree(devarray);
|
||||
for (i--; i >= 0; i--) {
|
||||
sysfs_remove_bin_file(&devs[i]->dev.kobj, &fmc_eeprom_attr);
|
||||
device_del(&devs[i]->dev);
|
||||
fmc_free_id_info(devs[i]);
|
||||
put_device(&devs[i]->dev);
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL(fmc_device_register_n);
|
||||
|
||||
int fmc_device_register(struct fmc_device *fmc)
|
||||
{
|
||||
return fmc_device_register_n(&fmc, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(fmc_device_register);
|
||||
|
||||
void fmc_device_unregister_n(struct fmc_device **devs, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (n < 1)
|
||||
return;
|
||||
|
||||
/* Free devarray first, not used by the later loop */
|
||||
kfree(devs[0]->devarray);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
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");
|
100
drivers/fmc/fmc-dump.c
Normal file
100
drivers/fmc/fmc-dump.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2013 CERN (www.cern.ch)
|
||||
* Author: Alessandro Rubini <rubini@gnudd.com>
|
||||
*
|
||||
* Released according to the GNU GPL, version 2 or any later version.
|
||||
*
|
||||
* This work is part of the White Rabbit project, a research effort led
|
||||
* by CERN, the European Institute for Nuclear Research.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fmc.h>
|
||||
#include <linux/fmc-sdb.h>
|
||||
|
||||
static int fmc_must_dump_eeprom;
|
||||
module_param_named(dump_eeprom, fmc_must_dump_eeprom, int, 0644);
|
||||
static int fmc_must_dump_sdb;
|
||||
module_param_named(dump_sdb, fmc_must_dump_sdb, int, 0644);
|
||||
|
||||
#define LINELEN 16
|
||||
|
||||
/* Dumping 8k takes oh so much: avoid duplicate lines */
|
||||
static const uint8_t *dump_line(int addr, const uint8_t *line,
|
||||
const uint8_t *prev)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!prev || memcmp(line, prev, LINELEN)) {
|
||||
pr_info("%04x: ", addr);
|
||||
for (i = 0; i < LINELEN; ) {
|
||||
printk(KERN_CONT "%02x", line[i]);
|
||||
i++;
|
||||
printk(i & 3 ? " " : i & (LINELEN - 1) ? " " : "\n");
|
||||
}
|
||||
return line;
|
||||
}
|
||||
/* repeated line */
|
||||
if (line == prev + LINELEN)
|
||||
pr_info("[...]\n");
|
||||
return prev;
|
||||
}
|
||||
|
||||
void fmc_dump_eeprom(const struct fmc_device *fmc)
|
||||
{
|
||||
const uint8_t *line, *prev;
|
||||
int i;
|
||||
|
||||
if (!fmc_must_dump_eeprom)
|
||||
return;
|
||||
|
||||
pr_info("FMC: %s (%s), slot %i, device %s\n", dev_name(fmc->hwdev),
|
||||
fmc->carrier_name, fmc->slot_id, dev_name(&fmc->dev));
|
||||
pr_info("FMC: dumping eeprom 0x%x (%i) bytes\n", fmc->eeprom_len,
|
||||
fmc->eeprom_len);
|
||||
|
||||
line = fmc->eeprom;
|
||||
prev = NULL;
|
||||
for (i = 0; i < fmc->eeprom_len; i += LINELEN, line += LINELEN)
|
||||
prev = dump_line(i, line, prev);
|
||||
}
|
||||
|
||||
void fmc_dump_sdb(const struct fmc_device *fmc)
|
||||
{
|
||||
const uint8_t *line, *prev;
|
||||
int i, len;
|
||||
|
||||
if (!fmc->sdb)
|
||||
return;
|
||||
if (!fmc_must_dump_sdb)
|
||||
return;
|
||||
|
||||
/* If the argument is not-zero, do simple dump (== show) */
|
||||
if (fmc_must_dump_sdb > 0)
|
||||
fmc_show_sdb_tree(fmc);
|
||||
|
||||
if (fmc_must_dump_sdb == 1)
|
||||
return;
|
||||
|
||||
/* If bigger than 1, dump it seriously, to help debugging */
|
||||
|
||||
/*
|
||||
* Here we should really use libsdbfs (which is designed to
|
||||
* work in kernel space as well) , but it doesn't support
|
||||
* directories yet, and it requires better intergration (it
|
||||
* should be used instead of fmc-specific code).
|
||||
*
|
||||
* So, lazily, just dump the top-level array
|
||||
*/
|
||||
pr_info("FMC: %s (%s), slot %i, device %s\n", dev_name(fmc->hwdev),
|
||||
fmc->carrier_name, fmc->slot_id, dev_name(&fmc->dev));
|
||||
pr_info("FMC: poor dump of sdb first level:\n");
|
||||
|
||||
len = fmc->sdb->len * sizeof(union sdb_record);
|
||||
line = (void *)fmc->sdb->record;
|
||||
prev = NULL;
|
||||
for (i = 0; i < len; i += LINELEN, line += LINELEN)
|
||||
prev = dump_line(i, line, prev);
|
||||
return;
|
||||
}
|
355
drivers/fmc/fmc-fakedev.c
Normal file
355
drivers/fmc/fmc-fakedev.c
Normal file
|
@ -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");
|
114
drivers/fmc/fmc-match.c
Normal file
114
drivers/fmc/fmc-match.c
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (C) 2012 CERN (www.cern.ch)
|
||||
* Author: Alessandro Rubini <rubini@gnudd.com>
|
||||
*
|
||||
* Released according to the GNU GPL, version 2 or any later version.
|
||||
*
|
||||
* This work is part of the White Rabbit project, a research effort led
|
||||
* by CERN, the European Institute for Nuclear Research.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fmc.h>
|
||||
#include <linux/ipmi-fru.h>
|
||||
|
||||
/* The fru parser is both user and kernel capable: it needs alloc */
|
||||
void *fru_alloc(size_t size)
|
||||
{
|
||||
return kzalloc(size, GFP_KERNEL);
|
||||
}
|
||||
|
||||
/* The actual match function */
|
||||
int fmc_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct fmc_driver *fdrv = to_fmc_driver(drv);
|
||||
struct fmc_device *fdev = to_fmc_device(dev);
|
||||
struct fmc_fru_id *fid;
|
||||
int i, matched = 0;
|
||||
|
||||
/* This currently only matches the EEPROM (FRU id) */
|
||||
fid = fdrv->id_table.fru_id;
|
||||
if (!fid) {
|
||||
dev_warn(&fdev->dev, "Driver has no ID: matches all\n");
|
||||
matched = 1;
|
||||
} else {
|
||||
if (!fdev->id.manufacturer || !fdev->id.product_name)
|
||||
return 0; /* the device has no FRU information */
|
||||
for (i = 0; i < fdrv->id_table.fru_id_nr; i++, fid++) {
|
||||
if (fid->manufacturer &&
|
||||
strcmp(fid->manufacturer, fdev->id.manufacturer))
|
||||
continue;
|
||||
if (fid->product_name &&
|
||||
strcmp(fid->product_name, fdev->id.product_name))
|
||||
continue;
|
||||
matched = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: match SDB contents */
|
||||
return matched;
|
||||
}
|
||||
|
||||
/* This function creates ID info for a newly registered device */
|
||||
int fmc_fill_id_info(struct fmc_device *fmc)
|
||||
{
|
||||
struct fru_common_header *h;
|
||||
struct fru_board_info_area *bia;
|
||||
int ret, allocated = 0;
|
||||
|
||||
/* If we know the eeprom length, try to read it off the device */
|
||||
if (fmc->eeprom_len && !fmc->eeprom) {
|
||||
fmc->eeprom = kzalloc(fmc->eeprom_len, GFP_KERNEL);
|
||||
if (!fmc->eeprom)
|
||||
return -ENOMEM;
|
||||
allocated = 1;
|
||||
ret = fmc->op->read_ee(fmc, 0, fmc->eeprom, fmc->eeprom_len);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If no eeprom, continue with other matches */
|
||||
if (!fmc->eeprom)
|
||||
return 0;
|
||||
|
||||
dev_info(fmc->hwdev, "mezzanine %i\n", fmc->slot_id); /* header */
|
||||
|
||||
/* So we have the eeprom: parse the FRU part (if any) */
|
||||
h = (void *)fmc->eeprom;
|
||||
if (h->format != 1) {
|
||||
pr_info(" EEPROM has no FRU information\n");
|
||||
goto out;
|
||||
}
|
||||
if (!fru_header_cksum_ok(h)) {
|
||||
pr_info(" FRU: wrong header checksum\n");
|
||||
goto out;
|
||||
}
|
||||
bia = fru_get_board_area(h);
|
||||
if (!fru_bia_cksum_ok(bia)) {
|
||||
pr_info(" FRU: wrong board area checksum\n");
|
||||
goto out;
|
||||
}
|
||||
fmc->id.manufacturer = fru_get_board_manufacturer(h);
|
||||
fmc->id.product_name = fru_get_product_name(h);
|
||||
pr_info(" Manufacturer: %s\n", fmc->id.manufacturer);
|
||||
pr_info(" Product name: %s\n", fmc->id.product_name);
|
||||
|
||||
/* Create the short name (FIXME: look in sdb as well) */
|
||||
fmc->mezzanine_name = kstrdup(fmc->id.product_name, GFP_KERNEL);
|
||||
|
||||
out:
|
||||
if (allocated) {
|
||||
kfree(fmc->eeprom);
|
||||
fmc->eeprom = NULL;
|
||||
}
|
||||
return 0; /* no error: let other identification work */
|
||||
}
|
||||
|
||||
/* Some ID data is allocated using fru_alloc() above, so release it */
|
||||
void fmc_free_id_info(struct fmc_device *fmc)
|
||||
{
|
||||
kfree(fmc->mezzanine_name);
|
||||
kfree(fmc->id.manufacturer);
|
||||
kfree(fmc->id.product_name);
|
||||
}
|
289
drivers/fmc/fmc-sdb.c
Normal file
289
drivers/fmc/fmc-sdb.c
Normal file
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* 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(struct fmc_device *fmc, struct fmc_driver *d, char *gw,
|
||||
int sdb_entry)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = fmc->op->reprogram(fmc, d, gw);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (sdb_entry < 0)
|
||||
return ret;
|
||||
|
||||
/* We are required to find SDB at a given offset */
|
||||
ret = fmc_scan_sdb_tree(fmc, sdb_entry);
|
||||
if (ret < 0) {
|
||||
dev_err(&fmc->dev, "Can't find SDB at address 0x%x\n",
|
||||
sdb_entry);
|
||||
return -ENODEV;
|
||||
}
|
||||
fmc_dump_sdb(fmc);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(fmc_reprogram);
|
||||
|
||||
static char *__strip_trailing_space(char *buf, char *str, int len)
|
||||
{
|
||||
int i = len - 1;
|
||||
|
||||
memcpy(buf, str, len);
|
||||
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)); \
|
||||
})
|
||||
|
||||
static void __fmc_show_sdb_tree(const struct fmc_device *fmc,
|
||||
const struct sdb_array *arr)
|
||||
{
|
||||
unsigned long base = arr->baseaddr;
|
||||
int i, j, n = arr->len, level = arr->level;
|
||||
char buf[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;
|
||||
|
||||
dev_info(&fmc->dev, "SDB: ");
|
||||
|
||||
for (j = 0; j < level; j++)
|
||||
printk(KERN_CONT " ");
|
||||
switch (r->empty.record_type) {
|
||||
case sdb_type_interconnect:
|
||||
printk(KERN_CONT "%08llx:%08x %.19s\n",
|
||||
__be64_to_cpu(p->vendor_id),
|
||||
__be32_to_cpu(p->device_id),
|
||||
p->name);
|
||||
break;
|
||||
case sdb_type_device:
|
||||
printk(KERN_CONT "%08llx:%08x %.19s (%08llx-%08llx)\n",
|
||||
__be64_to_cpu(p->vendor_id),
|
||||
__be32_to_cpu(p->device_id),
|
||||
p->name,
|
||||
__be64_to_cpu(c->addr_first) + base,
|
||||
__be64_to_cpu(c->addr_last) + base);
|
||||
break;
|
||||
case sdb_type_bridge:
|
||||
printk(KERN_CONT "%08llx:%08x %.19s (bridge: %08llx)\n",
|
||||
__be64_to_cpu(p->vendor_id),
|
||||
__be32_to_cpu(p->device_id),
|
||||
p->name,
|
||||
__be64_to_cpu(c->addr_first) + base);
|
||||
if (IS_ERR(arr->subtree[i])) {
|
||||
dev_info(&fmc->dev, "SDB: (bridge error %li)\n",
|
||||
PTR_ERR(arr->subtree[i]));
|
||||
break;
|
||||
}
|
||||
__fmc_show_sdb_tree(fmc, arr->subtree[i]);
|
||||
break;
|
||||
case sdb_type_integration:
|
||||
printk(KERN_CONT "integration\n");
|
||||
break;
|
||||
case sdb_type_repo_url:
|
||||
printk(KERN_CONT "Synthesis repository: %s\n",
|
||||
__sdb_string(buf, r->repo_url.repo_url));
|
||||
break;
|
||||
case sdb_type_synthesis:
|
||||
printk(KERN_CONT "Bitstream '%s' ",
|
||||
__sdb_string(buf, r->synthesis.syn_name));
|
||||
printk(KERN_CONT "synthesized %08x by %s ",
|
||||
__be32_to_cpu(r->synthesis.date),
|
||||
__sdb_string(buf, r->synthesis.user_name));
|
||||
printk(KERN_CONT "(%s version %x), ",
|
||||
__sdb_string(buf, r->synthesis.tool_name),
|
||||
__be32_to_cpu(r->synthesis.tool_version));
|
||||
printk(KERN_CONT "commit %pm\n",
|
||||
r->synthesis.commit_id);
|
||||
break;
|
||||
case sdb_type_empty:
|
||||
printk(KERN_CONT "empty\n");
|
||||
break;
|
||||
default:
|
||||
printk(KERN_CONT "UNKNOWN TYPE 0x%02x\n",
|
||||
r->empty.record_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fmc_show_sdb_tree(const struct fmc_device *fmc)
|
||||
{
|
||||
if (!fmc->sdb)
|
||||
return;
|
||||
__fmc_show_sdb_tree(fmc, fmc->sdb);
|
||||
}
|
||||
EXPORT_SYMBOL(fmc_show_sdb_tree);
|
||||
|
||||
signed long fmc_find_sdb_device(struct sdb_array *tree,
|
||||
uint64_t vid, uint32_t did, unsigned long *sz)
|
||||
{
|
||||
signed long res = -ENODEV;
|
||||
union sdb_record *r;
|
||||
struct sdb_product *p;
|
||||
struct sdb_component *c;
|
||||
int i, n = tree->len;
|
||||
uint64_t last, first;
|
||||
|
||||
/* FIXME: what if the first interconnect is not at zero? */
|
||||
for (i = 0; i < n; i++) {
|
||||
r = &tree->record[i];
|
||||
c = &r->dev.sdb_component;
|
||||
p = &c->product;
|
||||
|
||||
if (!IS_ERR(tree->subtree[i]))
|
||||
res = fmc_find_sdb_device(tree->subtree[i],
|
||||
vid, did, sz);
|
||||
if (res >= 0)
|
||||
return res + tree->baseaddr;
|
||||
if (r->empty.record_type != sdb_type_device)
|
||||
continue;
|
||||
if (__be64_to_cpu(p->vendor_id) != vid)
|
||||
continue;
|
||||
if (__be32_to_cpu(p->device_id) != did)
|
||||
continue;
|
||||
/* found */
|
||||
last = __be64_to_cpu(c->addr_last);
|
||||
first = __be64_to_cpu(c->addr_first);
|
||||
if (sz)
|
||||
*sz = (typeof(*sz))(last + 1 - first);
|
||||
return first + tree->baseaddr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL(fmc_find_sdb_device);
|
107
drivers/fmc/fmc-trivial.c
Normal file
107
drivers/fmc/fmc-trivial.c
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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->op->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;
|
||||
|
||||
if (fmc->op->validate)
|
||||
index = fmc->op->validate(fmc, &t_drv);
|
||||
if (index < 0)
|
||||
return -EINVAL; /* not our device: invalid */
|
||||
|
||||
ret = fmc->op->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->op->gpio_config(fmc, t_gpio, ARRAY_SIZE(t_gpio));
|
||||
|
||||
/* Reprogram, if asked to. ESRCH == no filename specified */
|
||||
ret = -ESRCH;
|
||||
if (fmc->op->reprogram)
|
||||
ret = fmc->op->reprogram(fmc, &t_drv, "");
|
||||
if (ret == -ESRCH)
|
||||
ret = 0;
|
||||
if (ret < 0)
|
||||
fmc->op->irq_free(fmc);
|
||||
|
||||
/* FIXME: reprogram LM32 too */
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int t_remove(struct fmc_device *fmc)
|
||||
{
|
||||
fmc->op->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");
|
176
drivers/fmc/fmc-write-eeprom.c
Normal file
176
drivers/fmc/fmc-write-eeprom.c
Normal file
|
@ -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->op->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->op->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;
|
||||
}
|
||||
if (fmc->op->validate)
|
||||
index = fmc->op->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");
|
82
drivers/fmc/fru-parse.c
Normal file
82
drivers/fmc/fru-parse.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2012 CERN (www.cern.ch)
|
||||
* Author: Alessandro Rubini <rubini@gnudd.com>
|
||||
*
|
||||
* Released according to the GNU GPL, version 2 or any later version.
|
||||
*
|
||||
* This work is part of the White Rabbit project, a research effort led
|
||||
* by CERN, the European Institute for Nuclear Research.
|
||||
*/
|
||||
#include <linux/ipmi-fru.h>
|
||||
|
||||
/* Some internal helpers */
|
||||
static struct fru_type_length *
|
||||
__fru_get_board_tl(struct fru_common_header *header, int nr)
|
||||
{
|
||||
struct fru_board_info_area *bia;
|
||||
struct fru_type_length *tl;
|
||||
|
||||
bia = fru_get_board_area(header);
|
||||
tl = bia->tl;
|
||||
while (nr > 0 && !fru_is_eof(tl)) {
|
||||
tl = fru_next_tl(tl);
|
||||
nr--;
|
||||
}
|
||||
if (fru_is_eof(tl))
|
||||
return NULL;
|
||||
return tl;
|
||||
}
|
||||
|
||||
static char *__fru_alloc_get_tl(struct fru_common_header *header, int nr)
|
||||
{
|
||||
struct fru_type_length *tl;
|
||||
char *res;
|
||||
int len;
|
||||
|
||||
tl = __fru_get_board_tl(header, nr);
|
||||
if (!tl)
|
||||
return NULL;
|
||||
len = fru_strlen(tl);
|
||||
res = fru_alloc(fru_strlen(tl) + 1);
|
||||
if (!res)
|
||||
return NULL;
|
||||
return fru_strcpy(res, tl);
|
||||
}
|
||||
|
||||
/* Public checksum verifiers */
|
||||
int fru_header_cksum_ok(struct fru_common_header *header)
|
||||
{
|
||||
uint8_t *ptr = (void *)header;
|
||||
int i, sum;
|
||||
|
||||
for (i = sum = 0; i < sizeof(*header); i++)
|
||||
sum += ptr[i];
|
||||
return (sum & 0xff) == 0;
|
||||
}
|
||||
int fru_bia_cksum_ok(struct fru_board_info_area *bia)
|
||||
{
|
||||
uint8_t *ptr = (void *)bia;
|
||||
int i, sum;
|
||||
|
||||
for (i = sum = 0; i < 8 * bia->area_len; i++)
|
||||
sum += ptr[i];
|
||||
return (sum & 0xff) == 0;
|
||||
}
|
||||
|
||||
/* Get various stuff, trivial */
|
||||
char *fru_get_board_manufacturer(struct fru_common_header *header)
|
||||
{
|
||||
return __fru_alloc_get_tl(header, 0);
|
||||
}
|
||||
char *fru_get_product_name(struct fru_common_header *header)
|
||||
{
|
||||
return __fru_alloc_get_tl(header, 1);
|
||||
}
|
||||
char *fru_get_serial_number(struct fru_common_header *header)
|
||||
{
|
||||
return __fru_alloc_get_tl(header, 2);
|
||||
}
|
||||
char *fru_get_part_number(struct fru_common_header *header)
|
||||
{
|
||||
return __fru_alloc_get_tl(header, 3);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue