Commit 29e89c61 authored by Alessandro Rubini's avatar Alessandro Rubini

kernel: several changes to support FRU match

The new matching requires some changes in data structures. They are
documented in a later commit. This also splits out a new file
"fmc-match.c" because matching will become a big task when SDB is
finally added.
Signed-off-by: Alessandro Rubini's avatarAlessandro Rubini <rubini@gnudd.com>
parent 74275be1
......@@ -8,7 +8,9 @@ obj-m += fmc-trivial.o
obj-m += fmc-write-eeprom.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
......
......@@ -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 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);
}
......@@ -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__ */
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