diff options
Diffstat (limited to 'common/firmware.c')
-rw-r--r-- | common/firmware.c | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/common/firmware.c b/common/firmware.c new file mode 100644 index 0000000000..2a62a81c78 --- /dev/null +++ b/common/firmware.c @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2013 Juergen Beisert <kernel@pengutronix.de>, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <firmware.h> +#include <common.h> +#include <malloc.h> +#include <xfuncs.h> +#include <fcntl.h> +#include <libbb.h> +#include <fs.h> +#include <linux/list.h> +#include <linux/stat.h> +#include <linux/err.h> + +#define BUFSIZ 4096 + +struct firmware_mgr { + struct list_head list; + struct firmware_handler *handler; /* the program handler */ + struct cdev cdev; + u8 buf[BUFSIZ]; + int ofs; +}; + +static LIST_HEAD(firmwaremgr_list); + +/* + * firmwaremgr_find - find a firmware device handler + * + * Find a firmware device handler based on the unique id. If @id is + * NULL this returns the single firmware device handler if only one + * is registered. If multiple handlers are registered @id is mandatory + * + */ +struct firmware_mgr *firmwaremgr_find(const char *id) +{ + struct firmware_mgr *mgr; + + if (!id) { + if (list_is_singular(&firmwaremgr_list)) + return list_first_entry(&firmwaremgr_list, + struct firmware_mgr, list); + else + return NULL; + } + + list_for_each_entry(mgr, &firmwaremgr_list, list) + if (!strcmp(mgr->handler->id, id)) + return mgr; + + return NULL; +} + +/* + * firmwaremgr_list_handlers - list registered firmware device handlers + * in pretty format + */ +void firmwaremgr_list_handlers(void) +{ + struct firmware_mgr *mgr; + + printf("firmware programming handlers:\n\n"); + + if (list_empty(&firmwaremgr_list)) { + printf("(none)\n"); + return; + } + + printf("%-11s%-11s\n", "name:", "model:"); + + list_for_each_entry(mgr, &firmwaremgr_list, list) { + printf("%-11s", mgr->handler->id); + if (mgr->handler->model) + printf(" -> %-11s", mgr->handler->model); + printf("\n"); + } +} + +static int firmware_open(struct cdev *cdev, unsigned long flags) +{ + struct firmware_mgr *mgr = cdev->priv; + int ret; + + mgr->ofs = 0; + + ret = mgr->handler->open(mgr->handler); + if (ret) + return ret; + + return 0; +} + +static ssize_t firmware_write(struct cdev *cdev, const void *buf, size_t insize, + loff_t offset, ulong flags) +{ + struct firmware_mgr *mgr = cdev->priv; + int ret; + size_t count = insize; + + /* + * We guarantee the write handler of the firmware device that only the + * last write is a short write. All others are 4k in size. + */ + + while (count) { + size_t space = BUFSIZ - mgr->ofs; + size_t now = min(count, space); + + memcpy(mgr->buf + mgr->ofs, buf, now); + + buf += now; + mgr->ofs += now; + count -= now; + + if (mgr->ofs == BUFSIZ) { + ret = mgr->handler->write(mgr->handler, mgr->buf, BUFSIZ); + if (ret < 0) + return ret; + + mgr->ofs = 0; + } + } + + return insize; +} + +static int firmware_close(struct cdev *cdev) +{ + struct firmware_mgr *mgr = cdev->priv; + int ret; + + if (mgr->ofs) { + ret = mgr->handler->write(mgr->handler, mgr->buf, mgr->ofs); + if (ret) + return ret; + } + + ret = mgr->handler->close(mgr->handler); + if (ret) + return ret; + + return 0; +} + +static struct file_operations firmware_ops = { + .open = firmware_open, + .write = firmware_write, + .close = firmware_close, +}; + +/* + * firmwaremgr_register - register a device which needs firmware + */ +int firmwaremgr_register(struct firmware_handler *fh) +{ + struct firmware_mgr *mgr; + int ret; + struct cdev *cdev; + + if (firmwaremgr_find(fh->id)) + return -EBUSY; + + mgr = xzalloc(sizeof(struct firmware_mgr)); + mgr->handler = fh; + + cdev = &mgr->cdev; + + cdev->name = xstrdup(fh->id); + cdev->size = FILE_SIZE_STREAM; + cdev->ops = &firmware_ops; + cdev->priv = mgr; + cdev->dev = fh->dev; + + ret = devfs_create(cdev); + if (ret) + goto out; + + list_add_tail(&mgr->list, &firmwaremgr_list); + + return 0; +out: + free(cdev->name); + free(mgr); + + return ret; +} + +/* + * firmware_load_file - load a firmware to a device + */ +int firmwaremgr_load_file(struct firmware_mgr *mgr, const char *firmware) +{ + int ret; + char *name = asprintf("/dev/%s", mgr->handler->id); + + ret = copy_file(firmware, name, 0); + + free(name); + + return ret; +} |