Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 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");
|