[PATCH 8/9] discover: Expose and configure Attribute Overrides

Samuel Mendoza-Jonas sam.mj at au1.ibm.com
Tue Dec 15 14:15:29 AEDT 2015


Parse the attribute XML file and ATTR_PERM partition on startup when
running on a platform with a BMC available. If the descriptive XML file
is not available the ATTR_PERM partition is ignored.
This also adds server support for the PB_PROTOCOL_ACTION_FIRMWARE
message to send attribute details to clients, and for clients to inform
the server of updated values to those attributes.
It is defined that on a successful attribute override update the machine
must reboot immediately for the overrides to take effect.

Signed-off-by: Samuel Mendoza-Jonas <sam.mj at au1.ibm.com>
---
 discover/Makefile.am        |   3 +
 discover/device-handler.c   |  37 +++++
 discover/device-handler.h   |   2 +
 discover/discover-server.c  |  36 +++++
 discover/hostboot.c         | 337 ++++++++++++++++++++++++++++++++++++++++++++
 discover/hostboot.h         |  17 +++
 discover/platform-powerpc.c |  32 +++++
 discover/platform.c         |  23 +++
 discover/platform.h         |   6 +
 9 files changed, 493 insertions(+)

diff --git a/discover/Makefile.am b/discover/Makefile.am
index f55f1cd..7e057d9 100644
--- a/discover/Makefile.am
+++ b/discover/Makefile.am
@@ -90,9 +90,12 @@ discover_platform_ro_SOURCES += \
 	discover/hostboot.c
 
 discover_platform_ro_LDFLAGS = \
+	`xml2-config --libs` \
 	$(core_lib) \
 	$(UDEV_LIBS)
 
+discover_platform_ro_CPPFLAGS += \
+	`xml2-config --cflags`
 endif
 
 discover_platform_ro_LINK = \
diff --git a/discover/device-handler.c b/discover/device-handler.c
index bfe5fff..56e0f1b 100644
--- a/discover/device-handler.c
+++ b/discover/device-handler.c
@@ -1057,6 +1057,43 @@ void device_handler_update_config(struct device_handler *handler,
 	device_handler_reinit(handler);
 }
 
+void device_handler_update_platform_options(struct platform_options *options)
+{
+	struct process *p;
+	int rc;
+	const char *argv[] = {
+		pb_system_apps.shutdown,
+		"--reboot",
+		NULL
+	};
+
+	rc = platform_options_update(options);
+	if (rc < 0) {
+		/* There was an error updating platform attributes, avoid
+		 * rebooting so it can be investigated */
+		pb_log("WARNING: Platform attribute update failed\n");
+		return;
+	}
+	if (rc == 0) {
+		pb_log("No platform updates performed - continuing\n");
+		return;
+	}
+
+	pb_log("%d platform updates performed - REBOOTING\n", rc);
+	p = process_create(options);
+
+	p->path = pb_system_apps.shutdown;
+	p->argv = argv;
+	p->keep_stdout = true;
+
+	rc = process_run_sync(p);
+
+	if (p->exit_status != 0)
+		pb_log("shutdown returns error: %d\n", p->exit_status);
+
+	process_release(p);
+}
+
 static char *device_from_addr(void *ctx, struct pb_url *url)
 {
 	char *ipaddr, *buf, *tok, *dev = NULL;
diff --git a/discover/device-handler.h b/discover/device-handler.h
index d18910a..ce35b26 100644
--- a/discover/device-handler.h
+++ b/discover/device-handler.h
@@ -14,6 +14,7 @@ struct event;
 struct device;
 struct waitset;
 struct config;
+struct platform_options;
 
 struct discover_device {
 	struct device		*device;
@@ -132,6 +133,7 @@ void device_handler_boot(struct device_handler *handler,
 void device_handler_cancel_default(struct device_handler *handler);
 void device_handler_update_config(struct device_handler *handler,
 		struct config *config);
+void device_handler_update_platform_options(struct platform_options *options);
 void device_handler_process_url(struct device_handler *handler,
 		const char *url);
 void device_handler_reinit(struct device_handler *handler);
diff --git a/discover/discover-server.c b/discover/discover-server.c
index e4f3b67..a5fb972 100644
--- a/discover/discover-server.c
+++ b/discover/discover-server.c
@@ -210,10 +210,29 @@ static int write_config_message(struct discover_server *server,
 	return client_write_message(server, client, message);
 }
 
+static int write_platform_message(struct discover_server *server,
+		struct client *client, const struct platform_options *options)
+{
+	struct pb_protocol_message *message;
+	int len;
+
+	len = pb_protocol_platform_options_len(options);
+
+	message = pb_protocol_create_message(client,
+			PB_PROTOCOL_ACTION_FIRMWARE, len);
+	if (!message)
+		return -1;
+
+	pb_protocol_serialise_platform_options(options, message->payload, len);
+
+	return client_write_message(server, client, message);
+}
+
 static int discover_server_process_message(void *arg)
 {
 	struct pb_protocol_message *message;
 	struct boot_command *boot_command;
+	struct platform_options *options;
 	struct client *client = arg;
 	struct config *config;
 	char *url;
@@ -269,6 +288,18 @@ static int discover_server_process_message(void *arg)
 		device_handler_process_url(client->server->device_handler, url);
 		break;
 
+	case PB_PROTOCOL_ACTION_FIRMWARE:
+		options = talloc_zero(client, struct platform_options);
+
+		rc = pb_protocol_deserialise_platform_options(options, message);
+		if (rc) {
+			pb_log("%s: no options?", __func__);
+			return 0;
+		}
+
+		device_handler_update_platform_options(options);
+		break;
+
 	default:
 		pb_log("%s: invalid action %d\n", __func__, message->action);
 		return 0;
@@ -313,6 +344,11 @@ static int discover_server_process_connection(void *arg)
 	if (rc)
 		return 0;
 
+	/* send platform options to client */
+	rc = write_platform_message(server, client, platform_options_get());
+	if (rc)
+		return 0;
+
 	/* send existing devices to client */
 	n_devices = device_handler_get_device_count(server->device_handler);
 	for (i = 0; i < n_devices; i++) {
diff --git a/discover/hostboot.c b/discover/hostboot.c
index 8ea5ef3..dadb052 100644
--- a/discover/hostboot.c
+++ b/discover/hostboot.c
@@ -4,10 +4,347 @@
 
 #include <log/log.h>
 #include <talloc/talloc.h>
+#include <xml/xml.h>
 #include <flash/flash.h>
 
 #include "hostboot.h"
 
+static void parseEnumeration(struct firmware_attr *attr, xmlDocPtr doc,
+			xmlNodePtr node)
+{
+	xmlNodePtr enumerator, child = node->children;
+	char *val;
+	unsigned int i;
+
+	attr->limit = LIMIT_ENUM;
+
+	while (child) {
+		if (xmlStrcmp(child->name, (xmlChar *)"enumerator")) {
+			child = child->next;
+			continue;
+		}
+
+		i = attr->n_range++;
+		attr->enums = talloc_realloc(attr, attr->enums,
+					struct enumeration, attr->n_range);
+		enumerator = child->children;
+		while (enumerator) {
+			val = nodeContent(attr, doc, enumerator);
+			errno = 0;
+
+			if (!xmlStrcmp(enumerator->name, (xmlChar *)"display-name"))
+				attr->enums[i].display_name
+					= talloc_strdup(attr, val);
+			if (!xmlStrcmp(enumerator->name, (xmlChar *)"description"))
+				attr->enums[i].description
+					= talloc_strdup(attr, val);
+			if (!xmlStrcmp(enumerator->name, (xmlChar *)"value"))
+				attr->enums[i].value = strtoul(val, NULL, 16);
+
+			if (errno)
+				pb_log("Error parsing firmware enum '%s'"
+				       ", %s\n", val, strerror(errno));
+
+			enumerator = enumerator->next;
+		}
+		child = child->next;
+	}
+}
+
+static void parseRange(struct firmware_attr *attr, xmlDocPtr doc,
+			xmlNodePtr node)
+{
+	xmlNodePtr child = node->children;
+	char *val;
+
+	attr->limit = LIMIT_RANGE;
+	attr->n_range = 2;
+	attr->range = talloc_array(attr, int, attr->n_range);
+
+	while (child) {
+		errno = 0;
+		val = nodeContent(attr, doc, child);
+
+		if (!xmlStrcmp(child->name, (xmlChar *)"start"))
+			attr->range[0] = strtol(val, NULL, 0);
+		if (!xmlStrcmp(child->name, (xmlChar *)"end"))
+			attr->range[1] = strtol(val, NULL, 0);
+
+		if (errno)
+			pb_log("Error parsing firmware value '%s'"
+			       ", %s\n", val, strerror(errno));
+
+		child = child->next;
+	}
+}
+
+static void parseType(struct firmware_attr *attr, xmlDocPtr doc,
+			xmlNodePtr node)
+{
+	xmlNodePtr child = node->children;
+	char *val;
+
+	while (child) {
+		errno = 0;
+		val = nodeContent(attr, doc, child);
+
+		if (!xmlStrcmp(child->name, (xmlChar *)"size"))
+			attr->size = strtol(val, NULL, 0);
+
+		if (!xmlStrcmp(child->name, (xmlChar *)"encoding")) {
+			if (!strncmp(val, "signed", strlen("signed")))
+				attr->is_signed = true;
+			else if (!strncmp(val, "unsigned", strlen("unsigned")))
+				attr->is_signed = false;
+			else
+				pb_log("Unrecognised encoding: '%s'\n", val);
+		}
+
+		if (errno)
+			pb_log("Error parsing firmware value '%s'"
+			       ", %s\n", val, strerror(errno));
+
+		child = child->next;
+	}
+}
+
+static void parseTarget(struct firmware_attr *attr, xmlDocPtr doc,
+			xmlNodePtr node)
+{
+
+	xmlNodePtr child = node->children;
+	char *val;
+
+	while (child) {
+		errno = 0;
+		val = nodeContent(attr, doc, child);
+
+		if (!xmlStrcmp(child->name, (xmlChar *)"type"))
+			attr->type = strtoul(val, NULL, 16);
+
+		if (!xmlStrcmp(child->name, (xmlChar *)"node"))
+			attr->node = strtoul(val, NULL, 16);
+
+		if (!xmlStrcmp(child->name, (xmlChar *)"position"))
+			attr->pos = strtoul(val, NULL, 16);
+
+		if (!xmlStrcmp(child->name, (xmlChar *)"unit"))
+			attr->unitPos = strtoul(val, NULL, 16);
+
+		if (errno)
+			pb_log("Error parsing firmware value '%s'"
+			       ", %s\n", val, strerror(errno));
+
+		child = child->next;
+	}
+}
+
+static inline bool attribute_match(struct firmware_attr *attr,
+				   struct attribute_header *hdr)
+{
+	uint32_t id  = strtoul(attr->numeric_id, NULL, 0);
+
+	return id == hdr->iv_attrID && attr->type == hdr->iv_targetType &&
+		attr->pos == hdr->iv_pos && attr->unitPos == hdr->iv_unitPos &&
+		attr->node == hdr->iv_node;
+}
+
+static void createAttribute(void *ctx, struct firmware_attr **attr,
+				xmlDocPtr doc, xmlNodePtr root,
+				struct attribute *current_attrs, int n_attrs)
+{
+	xmlNodePtr *nodes;
+	struct firmware_attr *tmp;
+	int i, n_nodes;
+
+	n_nodes = nodeChildren(ctx, &nodes, root, XML_ELEMENT_NODE);
+	if (!n_nodes) {
+		pb_log("No attribute information found\n");
+		return;
+	}
+
+	tmp = talloc_zero(ctx, struct firmware_attr);
+
+	tmp->id = nodeAttribute(ctx, root, "id");
+
+	/* Iterate through possible elements */
+	for (i = 0; i < n_nodes; i++) {
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"numeric-id"))
+			tmp->numeric_id = nodeContent(ctx, doc, nodes[i]);
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"type"))
+			parseType(tmp, doc, nodes[i]);
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"default"))
+			tmp->default_value = nodeContent(ctx, doc, nodes[i]);
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"display-name"))
+			tmp->display_name = nodeContent(ctx, doc, nodes[i]);
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"description"))
+			tmp->description = nodeContent(ctx, doc, nodes[i]);
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"target"))
+			parseTarget(tmp, doc, nodes[i]);
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"range"))
+			parseRange(tmp, doc, nodes[i]);
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"enumeration"))
+			parseEnumeration(tmp, doc, nodes[i]);
+	}
+
+	for (i = 0; i < n_attrs; i++) {
+		if (attribute_match(tmp, current_attrs[i].hdr)) {
+			tmp->current_value = parse_attribute_value(ctx,
+							   current_attrs[i],
+							   tmp->is_signed);
+			pb_log("Existing override for %s: %s\n",
+			       tmp->id, tmp->current_value);
+			break;
+		}
+	}
+
+	if (!tmp->current_value)
+		tmp->current_value = talloc_strdup(ctx, tmp->default_value);
+	/* Init user-specified value to current value */
+	tmp->value = talloc_strdup(ctx, tmp->current_value);
+
+	talloc_free(nodes);
+	*attr = tmp;
+}
+
+static int build_config(void *ctx, struct firmware_option **opts,
+			xmlDocPtr doc, xmlNodePtr root,
+			struct attribute *current_attrs, int n_attrs)
+{
+	xmlNodePtr *nodes;
+	struct firmware_option *options;
+	int i, n_nodes, n_valid, cur;
+
+	n_valid = n_nodes = nodeChildren(ctx, &nodes, root, XML_ELEMENT_NODE);
+	if (!n_nodes) {
+		pb_log("No options found in hostboot document\n");
+		return 0;
+	}
+
+	options = talloc_zero_array(ctx, struct firmware_option, n_nodes);
+
+	for (cur = i = 0; i < n_nodes; i++) {
+		/* If unrecognised skip option but keep array consistent
+		 * and continue to parse options */
+		if (xmlStrcmp(nodes[i]->name, (xmlChar *)"attribute")
+		    && xmlStrcmp(nodes[i]->name, (xmlChar *)"group")) {
+			pb_debug("Unrecognized element: '%s'\n", nodes[i]->name);
+			n_valid--;
+			options = talloc_realloc(ctx, options,
+						struct firmware_option, n_valid);
+			continue;
+		}
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"attribute")) {
+			options[cur].type = FIRMWARE_ATTR;
+			createAttribute(ctx, &options[cur].attr, doc, nodes[i],
+					current_attrs, n_attrs);
+		}
+
+		if (!xmlStrcmp(nodes[i]->name, (xmlChar *)"group")) {
+			options[cur].type = FIRMWARE_GROUP;
+			xmlNodePtr nameptr = findChildNode(nodes[i],
+						XML_ELEMENT_NODE, "name");
+			if (nameptr)
+				options[cur].name = nodeContent(ctx,
+							doc, nameptr);
+			options[cur].n_opts = build_config(ctx,
+						&options[cur].opts,
+						doc, nodes[i],
+						current_attrs, n_attrs);
+		}
+		cur++;
+	}
+
+	*opts = options;
+	return n_valid;
+}
+
+int hostboot_load_attributes(struct platform_options *options,
+				      const char *xmlpath)
+{
+	struct attribute *current_attrs;;
+	struct firmware_option *opts;
+	int n_opts, n_attrs;
+	xmlNodePtr xmlroot;
+	xmlDocPtr xmldoc;
+
+	/* If there is no XML file available existing overrides can't be
+	 * interpreted, so exit early */
+	xmldoc = readXMLFile(xmlpath);
+	if (!xmldoc)
+		return -1;
+
+	xmlroot = findXMLRoot(xmldoc, "firmware-overrides");
+	if (!xmlroot) {
+		pb_log("Failed to find XML root\n");
+		finaliseXML(xmldoc);
+		return -1;
+	}
+
+	/* Gather existing attributes so current values can be used */
+	n_attrs = flash_read_attrs(options, &current_attrs);
+	if (n_attrs < 0) {
+		pb_log("Error retrieving current attribute overrides\n");
+		n_attrs = 0;
+	}
+
+	/* Parse all attributes from the XML file */
+	n_opts = build_config(options, &opts, xmldoc, xmlroot,
+			      current_attrs, n_attrs);
+	options->n_options = n_opts;
+	options->options = opts;
+
+	finaliseXML(xmldoc);
+
+	pb_log("%d platform option%s found, with %d existing override%s\n",
+	       n_opts, n_opts == 1 ? "" : "s",
+	       n_attrs, n_attrs == 1 ? "" : "s");
+
+	return 0;
+}
+
+int hostboot_save_attribute(void *ctx, struct firmware_option option)
+{
+	int rc = 0, updated = 0;
+	unsigned int i;
+
+	if (option.type == FIRMWARE_GROUP) {
+		for (i = 0; i < option.n_opts; i++) {
+			rc = hostboot_save_attribute(ctx, option.opts[i]);
+			if (rc < 0)
+				goto out;
+			updated += rc;
+		}
+	}
+
+	if (option.type == FIRMWARE_ATTR) {
+		if (strlen(option.attr->value) == strlen(option.attr->current_value))
+			if (!strncmp(option.attr->value, option.attr->current_value,
+				     strlen(option.attr->current_value)))
+				return 0;
+
+		pb_log("Attribute %s: '%s' updated, writing to flash...\n",
+		       option.attr->numeric_id, option.attr->id);
+		pb_log("\tNew value: %s\n", option.attr->value);
+
+		/* Process and commit the update */
+		rc = flash_write_attribute(ctx, option.attr, 1);
+		if (!rc)
+			updated = 1;
+	}
+
+out:
+	return rc < 0 ? rc : updated;
+}
+
 int hostboot_load_versions(struct system_info *info)
 {
 	return flash_read_version(info, &info->platform_versions);
diff --git a/discover/hostboot.h b/discover/hostboot.h
index 9ef5f7a..91c2819 100644
--- a/discover/hostboot.h
+++ b/discover/hostboot.h
@@ -5,8 +5,25 @@
 #include <types/types.h>
 
 #if MTD_SUPPORT
+int hostboot_load_attributes(struct platform_options *options,
+			const char *xmlpath);
+int hostboot_save_attribute(void *ctx, struct firmware_option option);
 int hostboot_load_versions(struct system_info *info);
 #else
+static inline int hostboot_load_attributes(struct platform_options *options,
+			const char *xmlpath)
+{
+	(void)options;
+	(void)xmlpath;
+	return NULL;
+}
+static inline int hostboot_save_attribute(void *ctx,
+			struct firmware_option option)
+{
+	(void)ctx;
+	(void)option;
+	return -1;
+}
 static inline int hostboot_load_versions(struct system_info *info)
 {
 	(void)info;
diff --git a/discover/platform-powerpc.c b/discover/platform-powerpc.c
index a36f451..de242a5 100644
--- a/discover/platform-powerpc.c
+++ b/discover/platform-powerpc.c
@@ -24,6 +24,7 @@
 static const char *partition = "common";
 static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
 static const char *devtree_dir = "/proc/device-tree/";
+static const char *xml_path = "/usr/share/bios_metadata.xml";
 static const int ipmi_timeout = 5000; /* milliseconds. */
 
 struct param {
@@ -992,6 +993,35 @@ static int set_ipmi_os_boot_sensor(struct platform_powerpc *platform)
 	return 0;
 }
 
+static int load_platform_options(struct platform_options *options)
+{
+	struct stat statbuf;
+	int rc;
+
+	/* Attribute overrides only supported on BMC-machines */
+	rc = stat("/proc/device-tree/bmc", &statbuf);
+	if (rc)
+		return 0;
+
+	rc = hostboot_load_attributes(options, xml_path);
+	return 0;
+}
+
+static int save_platform_options(struct platform_options *options)
+{
+	int rc, updated = 0;
+	unsigned int i;
+
+	for (i = 0; i < options->n_options; i++) {
+		rc = hostboot_save_attribute(options, options->options[i]);
+		if (rc < 0)
+			break;
+		updated += rc;
+	}
+
+	return rc < 0 ? rc : updated;
+}
+
 static int load_config(struct platform *p, struct config *config)
 {
 	struct platform_powerpc *platform = to_platform_powerpc(p);
@@ -1117,6 +1147,8 @@ static struct platform platform_powerpc = {
 	.save_config		= save_config,
 	.pre_boot		= pre_boot,
 	.get_sysinfo		= get_sysinfo,
+	.load_options		= load_platform_options,
+	.save_options		= save_platform_options,
 };
 
 register_platform(platform_powerpc);
diff --git a/discover/platform.c b/discover/platform.c
index 5f448f1..4e024d0 100644
--- a/discover/platform.c
+++ b/discover/platform.c
@@ -15,6 +15,7 @@
 void			*platform_ctx;
 static struct platform	*platform;
 static struct config	*config;
+static struct platform_options *options;
 
 static const char *kernel_cmdline_debug = "petitboot.debug";
 
@@ -155,10 +156,14 @@ int platform_init(void *ctx)
 	config = talloc(platform_ctx, struct config);
 	config_set_defaults(config);
 
+	options = talloc_zero(platform_ctx, struct platform_options);
+
 	if (platform) {
 		pb_log("Detected platform type: %s\n", platform->name);
 		if (platform->load_config)
 			platform->load_config(platform, config);
+		if (platform->load_options)
+			platform->load_options(options);
 	} else {
 		pb_log("No platform type detected, some platform-specific "
 				"functionality will be disabled\n");
@@ -212,6 +217,19 @@ int config_set(struct config *newconfig)
 	return rc;
 }
 
+int platform_options_update(struct platform_options *options)
+{
+	int rc = 0, updated = 0;
+
+	if (platform && platform->save_options) {
+		rc = updated = platform->save_options(options);
+		goto out;
+	}
+
+out:
+	return rc < 0 ? rc : updated;
+}
+
 /* A non-exported function to allow the test infrastructure to initialise
  * (and change) the configuration variables */
 struct parser_test;
@@ -229,6 +247,11 @@ const struct config *config_get(void)
 	return config;
 }
 
+const struct platform_options *platform_options_get(void)
+{
+	return options;
+}
+
 void config_set_autoboot(bool autoboot_enabled)
 {
 	config->autoboot_enabled = autoboot_enabled;
diff --git a/discover/platform.h b/discover/platform.h
index 5aa8e3f..bbe21aa 100644
--- a/discover/platform.h
+++ b/discover/platform.h
@@ -11,6 +11,8 @@ struct platform {
 	void		(*pre_boot)(struct platform *,
 				const struct config *);
 	int		(*get_sysinfo)(struct platform *, struct system_info *);
+	int		(*load_options)(struct platform_options *);
+	int		(*save_options)(struct platform_options *);
 	uint16_t	dhcp_arch_id;
 	void		*platform_data;
 };
@@ -29,6 +31,10 @@ void config_set_autoboot(bool autoboot_enabled);
 /* for use by the platform-specific storage code */
 void config_set_defaults(struct config *config);
 
+/* platform-specific options */
+const struct platform_options *platform_options_get(void);
+int platform_options_update(struct platform_options *options);
+
 #define __platform_ptrname(_n) __platform_ ## _n
 #define  _platform_ptrname(_n) __platform_ptrname(_n)
 
-- 
2.6.3



More information about the Petitboot mailing list