forked from pool/s390-tools
5500b3a5bc
- Added the following patches for Fate#326825 (bsc#1113329) I/O device pre-configuration * s390-tools-sles15sp1-01-zdev-use-libutil-provided-path-functions.patch * s390-tools-sles15sp1-02-zdev-Prepare-for-firmware-configuration-file-support.patch * s390-tools-sles15sp1-03-zdev-Add-support-for-reading-firmware-configuration-.patch * s390-tools-sles15sp1-04-zdev-Implement-no-settle.patch * s390-tools-sles15sp1-05-zdev-Write-zfcp-lun-udev-rules-to-separate-files.patch * s390-tools-sles15sp1-06-zdev-Add-support-for-handling-auto-configuration-dat.patch * s390-tools-sles15sp1-07-zdev-Integrate-firmware-auto-configuration-with-drac.patch * s390-tools-sles15sp1-08-zdev-Integrate-firmware-auto-configuration-with-init.patch * s390-tools-sles15sp1-09-zdev-Implement-internal-device-attributes.patch * s390-tools-sles15sp1-10-zdev-Implement-support-for-early-device-configuratio.patch * s390-tools-sles15sp1-11-zdev-Do-not-call-zipl-on-initrd-update.patch - Removed the obsolete customize-zdev-root-update-script.patch - Replaced s390-tools-sles15-zdev-fix-qeth-BridgePort-and-VNICC-conflict-checking.patch with s390-tools-sles15sp1-zdev-fix-qeth-BridgePort-and-VNICC-conflict-checking.patch to fit the current version. OBS-URL: https://build.opensuse.org/request/show/655900 OBS-URL: https://build.opensuse.org/package/show/Base:System/s390-tools?expand=0&rev=61
875 lines
24 KiB
Diff
875 lines
24 KiB
Diff
Subject: zdev: Add support for reading firmware configuration files
|
|
From: Peter Oberparleiter <oberpar@linux.ibm.com>
|
|
|
|
Summary: zdev: Add support for handling I/O configuration data
|
|
Description: LPARs that are running in IBM Dynamic Partition Manager (DPM) mode
|
|
can access a firmware-generated I/O configuration data file that
|
|
contains s390-specific information about available I/O devices
|
|
such as qeth device numbers and parameters, and FCP device IDs.
|
|
|
|
This data file is intended to remove the need for users to
|
|
manually enter the corresponding device data during installation.
|
|
|
|
Linux kernels with the corresponding support make the I/O
|
|
configuration data available at the following location:
|
|
|
|
/sys/firmware/sclp_sd/config/data
|
|
|
|
This patch set adds support for handling this data file using the
|
|
chzdev and lszdev tools:
|
|
|
|
- I/O configuration data can be applied using chzdev's --import
|
|
option
|
|
- Initial RAM-Disk scripts automatically apply the
|
|
I/O configuration data to the system configuration
|
|
- lszdev can be used to display the applied auto-configuration
|
|
data
|
|
- chzdev can be used to manually override the
|
|
auto-configuration data
|
|
|
|
Upstream-ID: 7d355b0fec964ad84ecaf88eb946121d39486070
|
|
Problem-ID: LS1604
|
|
|
|
Upstream-Description:
|
|
|
|
zdev: Add support for reading firmware configuration files
|
|
|
|
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 <oberpar@linux.vnet.ibm.com>
|
|
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
|
|
|
|
|
|
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
|
|
---
|
|
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(-)
|
|
|
|
--- /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 <stdio.h>
|
|
+#include <stdbool.h>
|
|
+
|
|
+#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 */
|
|
--- 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
|
|
--- a/zdev/src/Makefile
|
|
+++ b/zdev/src/Makefile
|
|
@@ -8,7 +8,7 @@ ALL_CPPFLAGS += -I ../include -std=gnu99
|
|
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 \
|
|
--- 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 opti
|
|
/* 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 opti
|
|
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 opti
|
|
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 opti
|
|
"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;
|
|
}
|
|
--- /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 <err.h>
|
|
+#include <fcntl.h>
|
|
+#include <stdarg.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdint.h>
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <sys/stat.h>
|
|
+#include <sys/types.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+#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 "<unknown>";
|
|
+ }
|
|
+}
|
|
+
|
|
+/* 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';
|
|
+}
|