From 7d355b0fec964ad84ecaf88eb946121d39486070 Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Fri, 1 Sep 2017 11:05:52 +0200 Subject: [PATCH] zdev: Add support for reading firmware configuration files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for reading firmware-provided I/O configuration data files. Such configuration files are generated by the Dynamic Partition Manager and made available via a kernel interface for consumption by Linux. To read a firmware configuration file, use the existing --import option: # chzdev --import /sys/firmware/sclp_sd/config/data This will apply all I/O configuration data found in the specified file to the persistent configuration. Signed-off-by: Peter Oberparleiter Signed-off-by: Jan Höppner --- zdev/include/firmware.h | 25 ++ zdev/man/chzdev.8 | 16 +- zdev/src/Makefile | 2 +- zdev/src/chzdev.c | 21 +- zdev/src/firmware.c | 676 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 730 insertions(+), 10 deletions(-) create mode 100644 zdev/include/firmware.h create mode 100644 zdev/src/firmware.c diff --git a/zdev/include/firmware.h b/zdev/include/firmware.h new file mode 100644 index 00000000..b022bcd3 --- /dev/null +++ b/zdev/include/firmware.h @@ -0,0 +1,25 @@ +/* + * zdev - Modify and display the persistent configuration of devices + * + * Copyright IBM Corp. 2017 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef FIRMWARE_H +#define FIRMWARE_H + +#include +#include + +#include "exit_code.h" +#include "misc.h" + +struct util_list; + +bool firmware_detect(FILE *fd); +exit_code_t firmware_read(FILE *fd, const char *filename, long skip, + config_t config, struct util_list *objects); + +#endif /* FIRMWARE_H */ diff --git a/zdev/man/chzdev.8 b/zdev/man/chzdev.8 index fda54911..ae99600c 100644 --- a/zdev/man/chzdev.8 +++ b/zdev/man/chzdev.8 @@ -409,13 +409,23 @@ default value. .PP . .OD import "" "FILENAME" "|-" -Import configuration data from a text file. +Import configuration data from a text or machine-provided file. Reads configuration data from FILENAME and applies it. If a single hyphen ("-") is specified as FILENAME data is read from the standard input stream. The -input format of the data read must be the same format as produced by the -chzdev \-\-export action. +input format must be either in the format as produced by the chzdev \-\-export +action, or in the format of a machine-provided I/O configuration data file. +.B Machine-provided data: +Some machine models provide I/O configuration data which is made available +by the Linux kernel via a sysfs interface. While this data is intended for +automatic consumption during the boot phase, you can also apply it manually +using the \-\-import action like in the following example + +.B Example: +.CL chzdev --import /sys/firmware/sclp_sd/config/data + +.B Note: By default all configuration data that is read is also applied. To reduce the scope of imported configuration data, you can select specific devices, a device type, or define whether only data for the active or persistent configuration diff --git a/zdev/src/Makefile b/zdev/src/Makefile index 9e2c7a92..6056a8c3 100644 --- a/zdev/src/Makefile +++ b/zdev/src/Makefile @@ -8,7 +8,7 @@ ALL_CPPFLAGS += -I ../include -std=gnu99 -Wno-unused-parameter \ chzdev_objects += attrib.o chzdev.o device.o devnode.o devtype.o exit_code.o \ export.o hash.o inuse.o misc.o namespace.o opts.o path.o \ root.o select.o setting.o subtype.o table.o table_attribs.o \ - table_types.o net.o + table_types.o net.o firmware.o # Devtype Helpers chzdev_objects += blkinfo.o ccw.o ccwgroup.o findmnt.o modprobe.o module.o \ diff --git a/zdev/src/chzdev.c b/zdev/src/chzdev.c index f7fe3858..561de66d 100644 --- a/zdev/src/chzdev.c +++ b/zdev/src/chzdev.c @@ -27,6 +27,7 @@ #include "devnode.h" #include "devtype.h" #include "export.h" +#include "firmware.h" #include "inuse.h" #include "misc.h" #include "module.h" @@ -2500,9 +2501,9 @@ static exit_code_t do_export(struct options *opts) /* Open output stream. */ if (strcmp(opts->export, "-") == 0) { fd = stdout; - info("Exporting configuration data to standard output\n"); + info("Exporting data to standard output\n"); } else { - info("Exporting configuration data to %s\n", opts->export); + info("Exporting data to %s\n", opts->export); if (!util_path_exists(opts->export)) { rc = path_create(opts->export); if (rc) @@ -2735,6 +2736,7 @@ static exit_code_t do_import(struct options *opts) exit_code_t drc = EXIT_OK; const char *filename; int found; + bool is_firmware; /* Open input stream. */ if (strcmp(opts->import, "-") == 0) { @@ -2744,16 +2746,23 @@ static exit_code_t do_import(struct options *opts) fd = fopen(opts->import, "r"); filename = opts->import; } - info("Importing configuration data from %s\n", filename); + if (!fd) { error("Could not open file %s: %s\n", opts->import, strerror(errno)); return EXIT_RUNTIME_ERROR; } + is_firmware = firmware_detect(fd); + info("Importing data from %s%s\n", filename, + is_firmware ? " (firmware format)" : ""); + /* Read data. */ objects = ptrlist_new(); - rc = export_read(fd, filename, objects); + if (is_firmware) + rc = firmware_read(fd, filename, -1, opts->config, objects); + else + rc = export_read(fd, filename, objects); if (rc) goto out; @@ -2766,8 +2775,8 @@ static exit_code_t do_import(struct options *opts) "selection\n", filename); rc = EXIT_EMPTY_SELECTION; } else { - error("%s: No settings found to import\n", filename); - rc = EXIT_NO_DATA; + info("%s: No settings found to import\n", filename); + rc = EXIT_OK; } goto out; } diff --git a/zdev/src/firmware.c b/zdev/src/firmware.c new file mode 100644 index 00000000..7179e63b --- /dev/null +++ b/zdev/src/firmware.c @@ -0,0 +1,676 @@ +/* + * zdev - Modify and display the persistent configuration of devices + * + * Copyright IBM Corp. 2017 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "attrib.h" +#include "ccw.h" +#include "ccwgroup.h" +#include "dasd.h" +#include "device.h" +#include "export.h" +#include "firmware.h" +#include "misc.h" +#include "qeth.h" +#include "subtype.h" +#include "zfcp_host.h" +#include "zfcp_lun.h" + +/* In-memory firmware file representation. */ +struct fw_file { + const char *name; + char *buffer; + size_t size; + char *last_access; + size_t last_size; +}; + +/* Record access to fields of the buffered file for use in warning messages. */ +#define fwacc(f, x) ((f)->last_access = (char *) &(x), \ + (f)->last_size = sizeof(x), x) + +/* + * Firmware file format definitions. + */ + +/* Firmware file header. */ +struct fw_filehdr { + uint32_t magic; + uint16_t ver; + uint16_t hdr_len; + uint32_t file_len; + uint32_t seq; + uint32_t zeroes; + uint16_t de_count; + char unused[10]; +} __packed; + +#define FW_HDR_MAGIC 0x7a646576 /* ASCII "zdev" */ +#define FW_HDR_VER_Z14 0x0000 + +/* I/O device ID. */ +struct fw_iodevid { + uint8_t cssid; + uint8_t ssid; + uint16_t devno; +} __packed; + +#define FW_IODEVID_FLAG_MCSS 0x01 + +/* Device setting. */ +struct fw_setting { + uint16_t len; + uint8_t key_type; + uint8_t key_len; + uint8_t val_type; + uint8_t val_len; + char data[]; +} __packed; + +#define FW_SETTING_KEYTYPE_ASCII 0x00 +#define FW_SETTING_VALTYPE_ASCII 0x00 +#define FW_SETTING_VALTYPE_UINT 0x01 + +/* Device settings list. */ +struct fw_setlist { + uint16_t len; + char data[]; +} __packed; + +/* Device entry header. */ +struct fw_dehdr { + uint16_t type; + uint16_t len; + uint32_t seq; +} __packed; + +#define FW_DE_HDR_TYPE_DASD 0x0001 +#define FW_DE_HDR_TYPE_ZFCP_HOST 0x0002 +#define FW_DE_HDR_TYPE_ZFCP_LUN 0x0003 +#define FW_DE_HDR_TYPE_QETH 0x0004 + +/* DASD device entry. */ +struct fw_dasd { + struct fw_dehdr hdr; + uint8_t id_flags; + struct fw_iodevid id; + char settings[]; +} __packed; + +/* zFCP host device entry. */ +struct fw_zfcp_host { + struct fw_dehdr hdr; + uint8_t id_flags; + struct fw_iodevid id; + char settings[]; +} __packed; + +/* zFCP LUN device entry. */ +struct fw_zfcp_lun { + struct fw_dehdr hdr; + uint8_t id_flags; + struct fw_iodevid id; + uint64_t wwpn; + uint64_t fcp_lun; + char settings[]; +} __packed; + +/* QETH device entry. */ +struct fw_qeth { + struct fw_dehdr hdr; + uint8_t id_flags; + struct fw_iodevid read_id; + struct fw_iodevid write_id; + struct fw_iodevid data_id; + char settings[]; +} __packed; + +/* Emit a warning that refers to a position in a firmware file. */ +static void fwwarn(struct fw_file *f, const char *fmt, ...) +{ + va_list args; + off_t start = (off_t) (f->last_access - f->buffer), + end = start + f->last_size - 1; + + fprintf(stderr, "%s: ", f->name); + if (start == end) + fprintf(stderr, "Byte 0x%zx: ", start); + else + fprintf(stderr, "Bytes 0x%zx-0x%zx: ", start, end); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +/* Basic file format header sanity check. */ +static bool check_header(struct fw_file *f, struct fw_filehdr *hdr) +{ + if (fwacc(f, hdr->magic) != FW_HDR_MAGIC) + fwwarn(f, "Invalid file magic (0x%08x)", hdr->magic); + else if (fwacc(f, hdr->ver) != FW_HDR_VER_Z14) + fwwarn(f, "Unsupported file version (0x%04x)", hdr->ver); + else + return true; + + return false; +} + +#define READ_RETRY 3 + +/* Read a firmware configuration file. */ +static exit_code_t read_fw(struct fw_file *file, FILE *fd, const char *filename) +{ + struct fw_file f = { NULL }; + struct fw_filehdr *hdr; + char *buffer, *buffer2; + size_t size, size2; + int retry; + exit_code_t rc = EXIT_OK; + + for (retry = 0; retry < READ_RETRY; retry++) { + /* Read complete file once */ + rc = misc_read_fd(fd, (void **) &buffer, &size); + if (rc) { + warn("%s: Could not read file", filename); + return rc; + } + if (!buffer) { + /* Empty file - skip silently as this is the default + * on machines without firmware support. */ + return rc; + } + + /* Re-read complete file to detect in-flight modifications. */ + if (fseek(fd, 0, SEEK_SET) == -1) { + /* Could be a pipe, socket, or FIFO - accept v1. */ + break; + } + rc = misc_read_fd(fd, (void **) &buffer2, &size2); + if (rc || !buffer2) { + /* Could not get second version - accept v1. */ + break; + } + + if (size == size2 && memcmp(buffer, buffer2, size) == 0) { + /* No change */ + free(buffer2); + break; + } + + free(buffer); + free(buffer2); + } + + if (retry >= READ_RETRY) { + warnx("%s: File changed %d times while reading - aborting", + filename, retry); + return EXIT_RUNTIME_ERROR; + } + + /* Perform basic checks */ + f.name = filename; + f.buffer = buffer; + f.size = size; + hdr = (void *) buffer; + if (!check_header(&f, hdr)) { + free(buffer); + return EXIT_FORMAT_ERROR; + } + if (fwacc(&f, hdr->file_len) > size) { + fwwarn(&f, "File length mismatch (expect %zu) - adjusting", + size); + hdr->file_len = size; + } + + *file = f; + + return EXIT_OK; +} + +/* Return textual representation of a device entry type. */ +static const char *type_to_str(uint16_t type) +{ + switch (type) { + case FW_DE_HDR_TYPE_DASD: + return "dasd"; + case FW_DE_HDR_TYPE_ZFCP_HOST: + return "zfcp-host"; + case FW_DE_HDR_TYPE_ZFCP_LUN: + return "zfcp-lun"; + case FW_DE_HDR_TYPE_QETH: + return "qeth"; + default: + return ""; + } +} + +/* Convert a binary format device setting of the specified length to integer. */ +static unsigned long parse_value(char *data, uint8_t len) +{ + switch (len) { + case 1: + return (unsigned long) *((uint8_t *) data); + case 2: + return (unsigned long) *((uint16_t *) data); + case 4: + return (unsigned long) *((uint32_t *) data); + case 8: + return (unsigned long) *((uint64_t *) data); + default: + return 0; + } +} + +/* Perform sanity checks on device setting. */ +static bool check_setting(struct fw_file *f, struct fw_setting *set) +{ + /* Key sanity checks */ + if (fwacc(f, set->key_type) != FW_SETTING_KEYTYPE_ASCII) { + fwwarn(f, "Unsupported key type: %d", set->key_type); + return false; + } + if (fwacc(f, set->key_len) < 1) { + fwwarn(f, "Unsupported key length: %d", set->key_len); + return false; + } + if (sizeof(struct fw_setting) + fwacc(f, set->key_len) > set->len) { + fwwarn(f, "Key length exceeds setting"); + return false; + } + if (fwacc(f, set->data[set->key_len - 1])) { + fwwarn(f, "Key not null-terminated"); + return false; + } + + /* Value sanity checks */ + if (fwacc(f, set->val_type) != FW_SETTING_VALTYPE_UINT && + fwacc(f, set->val_type) != FW_SETTING_VALTYPE_ASCII) { + fwwarn(f, "Unsupported value type: %d", set->val_type); + return false; + } + if (fwacc(f, set->val_len) < 1) { + fwwarn(f, "Unsupported value length: %d", set->val_len); + return false; + } + if (sizeof(struct fw_setting) + set->key_len + + fwacc(f, set->val_len) > set->len) { + fwwarn(f, "Value length exceeds setting"); + return false; + } + if ((set->val_type == FW_SETTING_VALTYPE_ASCII) && + fwacc(f, set->data[set->key_len + set->val_len - 1])) { + fwwarn(f, "Value not null-terminated"); + return false; + } + if (set->val_type == FW_SETTING_VALTYPE_UINT) { + switch (fwacc(f, set->val_len)) { + case 1: + case 2: + case 4: + case 8: + break; + default: + fwwarn(f, "Unsupported integer value length: %d", + set->val_len); + return false; + } + } + + return true; +} + +/* Add a setting to the device. Emit a warning if the setting is not known. */ +static void _add_setting(const char *filename, struct device *dev, + config_t config, const char *key, const char *value) +{ + struct attrib *a; + struct setting_list *list; + + list = device_get_setting_list(dev, config); + a = attrib_find(dev->subtype->dev_attribs, key); + if (!a) { + warnx("%s: Applying unknown device setting %s=%s", filename, + key, value); + } + setting_list_apply(list, a, key, value); +} + +static void add_setting(const char *filename, struct device *dev, + config_t config, const char *key, const char *value) +{ + if (SCOPE_ACTIVE(config)) + _add_setting(filename, dev, config_active, key, value); + if (SCOPE_PERSISTENT(config)) + _add_setting(filename, dev, config_persistent, key, value); +} + +/* Parse a single device setting in firmware format and apply it to the + * specified device. */ +static void parse_setting(struct fw_file *f, struct fw_setting *set, + struct device *dev, config_t config) +{ + char *ascii_key, *ascii_val; + unsigned long ulong_val; + + if (!check_setting(f, set)) + return; + + ascii_key = &set->data[0]; + if (set->val_type == FW_SETTING_VALTYPE_UINT) { + ulong_val = parse_value(&set->data[set->key_len], set->val_len); + ascii_val = misc_asprintf("%lu", ulong_val); + add_setting(f->name, dev, config, ascii_key, ascii_val); + free(ascii_val); + } else { + ascii_val = &set->data[set->key_len]; + add_setting(f->name, dev, config, ascii_key, ascii_val); + } +} + +/* Parse a device settings list in firmware format and apply the resulting + * settings to the specified device. */ +static void parse_settings(struct fw_file *f, char *data, struct device *dev, + config_t config) +{ + struct fw_setlist *list = (struct fw_setlist *) data; + struct fw_setting *set; + uint16_t off; + + for (off = sizeof(struct fw_setlist); off < list->len; + off += set->len) { + set = (struct fw_setting *) &data[off]; + if (fwacc(f, set->len) < sizeof(struct fw_setting)) { + fwwarn(f, "Setting too short"); + break; + } + if (off + fwacc(f, set->len) > list->len) { + fwwarn(f, "Setting too long"); + break; + } + parse_setting(f, set, dev, config); + } +} + +/* Perform sanity checks on an I/O device ID. */ +static bool check_iodevid(struct fw_file *f, uint8_t *flags, + struct fw_iodevid *id) +{ + + if (fwacc(f, *flags) & FW_IODEVID_FLAG_MCSS) { + fwwarn(f, "Unsupported entry in non-default CSS"); + return false; + } + if (fwacc(f, id->cssid) != 0) { + fwwarn(f, "Non-zero CSS-ID"); + return false; + } + return true; +} + +/* Perform sanity checks on a device entry. */ +static bool check_de_size(struct fw_file *f, struct fw_dehdr *de, size_t size) +{ + if (fwacc(f, de->len) < size) { + fwwarn(f, "Device entry too short (expect %zu)", size); + return false; + } + return true; +} + +/* Convert an I/O device ID to CCW device ID format. */ +static void io_to_ccw(struct ccw_devid *c, struct fw_iodevid *i) +{ + c->cssid = i->cssid; + c->ssid = i->ssid; + c->devno = i->devno; +} + +/* Register a new device configuration. */ +static struct device *add_device(struct fw_file *f, struct subtype *st, + const char *id, config_t config, + struct util_list *objects) +{ + struct device *dev; + + if (!st->devices) + st->devices = device_list_new(st); + + dev = device_list_find(st->devices, id, NULL); + if (!dev) { + dev = device_new(st, id); + if (!dev) { + warnx("%s: Skipping invalid %s device ID %s", f->name, + st->name, id); + return NULL; + } + device_list_add(st->devices, dev); + } + ptrlist_add(objects, object_new(export_device, dev)); + + /* Prepare device for new settings. */ + if (SCOPE_ACTIVE(config)) { + setting_list_clear(dev->active.settings); + if (dev->subtype->support_definable) + dev->active.definable = 1; + else + dev->active.exists = 1; + } + if (SCOPE_PERSISTENT(config)) { + setting_list_clear(dev->persistent.settings); + dev->persistent.exists = 1; + } + + return dev; +} + +/* Parse a DASD device entry. */ +static void parse_dasd(struct fw_file *f, struct fw_dehdr *de, config_t config, + struct util_list *objects) +{ + struct fw_dasd *dasd = (struct fw_dasd *) de; + struct ccw_devid devid; + struct device *dev_eckd, *dev_fba; + char *id; + + if (!check_de_size(f, de, sizeof(struct fw_dasd))) + return; + if (!check_iodevid(f, &dasd->id_flags, &dasd->id)) + return; + + /* Could be either dasd_eckd or dasd_fba - add both entries */ + io_to_ccw(&devid, &dasd->id); + id = ccw_devid_to_str(&devid); + dev_eckd = add_device(f, &dasd_subtype_eckd, id, config, objects); + dev_fba = add_device(f, &dasd_subtype_fba, id, config, objects); + free(id); + + if (dasd->hdr.len > sizeof(struct fw_dasd)) { + if (dev_eckd) + parse_settings(f, dasd->settings, dev_eckd, config); + if (dev_fba) + parse_settings(f, dasd->settings, dev_fba, config); + } +} + +/* Parse a zFCP host device entry. */ +static void parse_zfcp_host(struct fw_file *f, struct fw_dehdr *de, + config_t config, struct util_list *objects) +{ + struct fw_zfcp_host *zfcp_host = (struct fw_zfcp_host *) de; + struct ccw_devid devid; + struct device *dev; + char *id; + + if (!check_de_size(f, de, sizeof(struct fw_zfcp_host))) + return; + if (!check_iodevid(f, &zfcp_host->id_flags, &zfcp_host->id)) + return; + + /* Add zfcp_host entry */ + io_to_ccw(&devid, &zfcp_host->id); + id = ccw_devid_to_str(&devid); + dev = add_device(f, &zfcp_host_subtype, id, config, objects); + free(id); + + if (dev && zfcp_host->hdr.len > sizeof(struct fw_zfcp_host)) + parse_settings(f, zfcp_host->settings, dev, config); +} + +/* Parse a zFCP LUN device entry. */ +static void parse_zfcp_lun(struct fw_file *f, struct fw_dehdr *de, + config_t config, struct util_list *objects) +{ + struct fw_zfcp_lun *zfcp_lun = (struct fw_zfcp_lun *) de; + struct zfcp_lun_devid devid; + struct device *dev; + char *id; + + if (!check_de_size(f, de, sizeof(struct fw_zfcp_lun))) + return; + if (!check_iodevid(f, &zfcp_lun->id_flags, &zfcp_lun->id)) + return; + + /* Add zfcp_lun entry */ + io_to_ccw(&devid.fcp_dev, &zfcp_lun->id); + devid.wwpn = zfcp_lun->wwpn; + devid.lun = zfcp_lun->fcp_lun; + id = zfcp_lun_devid_to_str(&devid); + dev = add_device(f, &zfcp_lun_subtype, id, config, objects); + free(id); + + if (dev && zfcp_lun->hdr.len > sizeof(struct fw_zfcp_lun)) + parse_settings(f, zfcp_lun->settings, dev, config); + +} + +/* Parse a QETH device entry. */ +static void parse_qeth(struct fw_file *f, struct fw_dehdr *de, config_t config, + struct util_list *objects) +{ + struct fw_qeth *qeth = (struct fw_qeth *) de; + struct ccwgroup_devid devid; + struct device *dev; + char *id; + + if (!check_de_size(f, de, sizeof(struct fw_qeth))) + return; + if (!check_iodevid(f, &qeth->id_flags, &qeth->read_id) || + !check_iodevid(f, &qeth->id_flags, &qeth->write_id) || + !check_iodevid(f, &qeth->id_flags, &qeth->data_id)) + return; + + /* Add qeth entry */ + devid.num = 3; + io_to_ccw(&devid.devid[0], &qeth->read_id); + io_to_ccw(&devid.devid[1], &qeth->write_id); + io_to_ccw(&devid.devid[2], &qeth->data_id); + id = ccwgroup_devid_to_str(&devid); + dev = add_device(f, &qeth_subtype_qeth, id, config, objects); + free(id); + + if (dev && qeth->hdr.len > sizeof(struct fw_qeth)) + parse_settings(f, qeth->settings, dev, config); +} + +/* Parse a firmware file. */ +static void parse_fw(struct fw_file *f, long skip, config_t config, + struct util_list *objects) +{ + char *data = f->buffer; + struct fw_filehdr *hdr = (struct fw_filehdr *) data; + struct fw_dehdr *de; + uint16_t count = 0; + uint32_t off; + + for (off = hdr->hdr_len; off < hdr->file_len; off += de->len) { + count++; + de = (struct fw_dehdr *) &data[off]; + if (fwacc(f, de->len) == 0) { + fwwarn(f, "Empty device entry"); + break; + } + if (off + fwacc(f, de->len) > hdr->file_len) { + fwwarn(f, "Device entry too long"); + break; + } + if (skip >= 0 && de->seq <= skip) { + debug("Skipping %s entry due to sequence (%08x)\n", + type_to_str(de->type), de->seq); + continue; + } + switch (fwacc(f, de->type)) { + case FW_DE_HDR_TYPE_DASD: + parse_dasd(f, de, config, objects); + break; + case FW_DE_HDR_TYPE_ZFCP_HOST: + parse_zfcp_host(f, de, config, objects); + break; + case FW_DE_HDR_TYPE_ZFCP_LUN: + parse_zfcp_lun(f, de, config, objects); + break; + case FW_DE_HDR_TYPE_QETH: + parse_qeth(f, de, config, objects); + break; + default: + fwwarn(f, "Unknown entry (type=%04x)", de->type); + break; + } + } + + if (count != fwacc(f, hdr->de_count)) + fwwarn(f, "Device entry count mismatch"); +} + +/* Read configuration objects from @fd in firmware file format. Add pointers to + * newly allocated struct export_objects to ptrlist @objects. If @skip is a + * positive number, skip over entries with a sequence number equal to or + * greater than @skip. */ +exit_code_t firmware_read(FILE *fd, const char *filename, long skip, + config_t config, struct util_list *objects) +{ + struct fw_file file; + exit_code_t rc; + + rc = read_fw(&file, fd, filename); + if (rc) + return rc; + + parse_fw(&file, skip, config, objects); + free(file.buffer); + + return rc; +} + +/* Check if @fd refers to a file in binary firmware format. */ +bool firmware_detect(FILE *fd) +{ + int c; + + c = fgetc(fd); + ungetc(c, fd); + + /* Note: A full check would require looking at least at the first 4 + * bytes, but fd might be non-seekable (e.g. pipe). Since there is no + * way that a textual import file can start with a 'z', looking at + * the first char should be enough. */ + + return c == 'z'; +}