[10/10] discover/platform-powerpc: Platform Firmware Updates

Message ID 20170825055940.24134-11-sam@mendozajonas.com
State New
Headers show

Commit Message

Samuel Mendoza-Jonas Aug. 25, 2017, 5:59 a.m.
Provide an interface to update the current firmware on POWER platforms.
Currently this supports updating only on BMC-based platforms, ie. those
where platform firmware is stored in a PNOR device accessible via MTD.
The pflash utility is used to safely access MTD and provide progress
updates to the user.

Platform updates are currently only intended for development use, and
explicitly not recommended in production environments. This is primarily
because there is no authentication or verification of the update
metadata or update source due to the lack of HTTPS and certificate
management in existing Petitboot environments (eg. op-build).
Platform updates are only enabled if the "petitboot,platform_update?"
parameter is set to true, and are by default disabled.

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
---
 discover/platform-powerpc.c | 213 ++++++++++++++++++++++++++++++++++++++++++++
 discover/platform.c         |  18 ++++
 discover/platform.h         |  10 +++
 3 files changed, 241 insertions(+)

Patch

diff --git a/discover/platform-powerpc.c b/discover/platform-powerpc.c
index e659919..2ec08bd 100644
--- a/discover/platform-powerpc.c
+++ b/discover/platform-powerpc.c
@@ -16,11 +16,16 @@ 
 #include <log/log.h>
 #include <process/process.h>
 #include <types/types.h>
+#include <i18n/i18n.h>
+#include <system/system.h>
+#include <device-tree/device-tree.h>
+#include <pb-config/pb-config.h>
 
 #include "hostboot.h"
 #include "platform.h"
 #include "ipmi.h"
 #include "dt.h"
+#include "device-handler.h"
 
 static const char *partition = "common";
 static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
@@ -34,6 +39,18 @@  struct param {
 	struct list_item	list;
 };
 
+struct update_data {
+	void		(*status_fn)(void *arg, struct status *status);
+	void		*status_arg;
+	void		(*update_cb)(enum update_status status);
+	char		*nvram_file;
+	enum {
+				PFLASH_UNKNOWN = 0,
+				PFLASH_ERASE,
+				PFLASH_WRITE,
+	} state;
+};
+
 struct platform_powerpc {
 	struct list	params;
 	struct ipmi	*ipmi;
@@ -67,6 +84,8 @@  static const char *known_params[] = {
 	"petitboot,console",
 	"petitboot,http_proxy",
 	"petitboot,https_proxy",
+	"petitboot,metadata_source",
+	"petitboot,platform_update?",
 	NULL,
 };
 
@@ -567,6 +586,15 @@  static void populate_config(struct platform_powerpc *platform,
 	if (val)
 		config->https_proxy = talloc_strdup(config, val);
 	set_proxy_variables(config);
+
+	val = get_param(platform, "petitboot,metadata_source");
+	if (val)
+		config->metadata_source = talloc_strdup(config, val);
+
+	val = get_param(platform, "petitboot,platform_update?");
+	if (val)
+		config->platform_update = !strcmp(val, "true");
+
 }
 
 static char *iface_config_str(void *ctx, struct interface_config *config)
@@ -748,6 +776,16 @@  static int update_config(struct platform_powerpc *platform,
 	update_string_config(platform, "petitboot,https_proxy", val);
 	set_proxy_variables(config);
 
+	val = config->metadata_source ?: "";
+	update_string_config(platform, "petitboot,metadata_source", val);
+
+	if (config->platform_update == defaults->platform_update)
+		val = "";
+	else
+		val = config->platform_update ? "true" : "false";
+	update_string_config(platform, "petitboot,platform_update?", val);
+
+
 	update_network_config(platform, config);
 
 	update_bootdev_config(platform, config);
@@ -1345,9 +1383,183 @@  static int get_sysinfo(struct platform *p, struct system_info *sysinfo)
 		if (platform->get_platform_versions(sysinfo))
 			pb_log("Failed to read platform versions\n");
 
+	sysinfo->update_support = platform->type == POWERPC_PLATFORM_BMC;
+
+	return 0;
+}
+
+/* Restore NVRAM contents after update */
+static void update_platform_cb(struct process *process)
+{
+	struct update_data *update_data = process->data;
+	struct status status;
+	int rc;
+
+	pb_log("Update complete!\n");
+
+	if (!process || !process->data)
+		return;
+
+	rc = process_run_simple(process, pb_system_apps.pflash,
+			"-e", "-f", "-p", update_data->nvram_file, "-P", "NVRAM",
+			NULL);
+	if (rc) {
+		pb_log("Failed to restore NVRAM!\n");
+		update_data->update_cb(UPDATE_ERROR);
+	} else {
+		update_data->update_cb(UPDATE_COMPLETED);
+	}
+
+	status.type = STATUS_INFO;
+	status.backlog = false;
+	status.message = talloc_asprintf(update_data, _("Update complete! You may now reboot"));
+	update_data->status_fn(update_data->status_arg, &status);
+
+	talloc_free(update_data);
+}
+
+static int pflash_progress_cb(void *arg)
+{
+
+	const char *pflash_fmt = "\r[%*[= ]] %u%% ETA:%8s\n";
+	struct process_info *procinfo = arg;
+	char *line = NULL;
+	unsigned int percentage;
+	struct update_data *update_data;
+	struct status status;
+	struct process *p;
+	int rc;
+	char eta[9];
+
+	if (!arg)
+		return -1;
+
+	p = procinfo_get_process(procinfo);
+	update_data = p->stdout_data;
+
+	rc = process_stdout_custom(procinfo, &line);
+
+	if (rc || !line)
+		return rc;
+
+	switch (update_data->state) {
+	case PFLASH_UNKNOWN:
+		if (strstr(line, "Erasing"))
+			update_data->state = PFLASH_ERASE;
+		else if (strstr(line, "Programming"))
+			update_data->state = PFLASH_WRITE;
+		break;
+	case PFLASH_ERASE:
+		if (strstr(line, "Programming"))
+			update_data->state = PFLASH_WRITE;
+		break;
+	case PFLASH_WRITE:
+		break;
+	}
+
+	rc = sscanf(line, pflash_fmt, &percentage, eta);
+	if (rc != 2) {
+		pb_debug("pflash ??: %s\n", line);
+		talloc_free(line);
+		return 0;
+	}
+
+	status.type = STATUS_INFO;
+	status.message = talloc_asprintf(line,
+			_("Update Progress: %s %u%%, ETA %s\n"),
+			update_data->state == PFLASH_ERASE ? _("erasing") :
+			update_data->state == PFLASH_WRITE ? _("writing") : "",
+			percentage, eta);
+	status.backlog = false;
+	update_data->status_fn(update_data->status_arg, &status);
+	pb_debug("pflash cb: %u%%, eta %s\n", percentage, eta);
+
+	talloc_free(line);
 	return 0;
 }
 
+static int update_platform(struct platform *p, struct pb_url *url,
+		const char *local_file,
+		void (* status_fn)(void *arg, struct status *status),
+		void *status_arg,
+		void (* update_cb)(enum update_status status))
+{
+	struct platform_powerpc *platform = to_platform_powerpc(p);
+	const struct config *config = config_get();
+	struct process *process;
+	struct update_data *update_data;
+	char *nvram_file;
+	int rc;
+
+	if (!config || !platform)
+		return -1;
+
+	if (platform->type != POWERPC_PLATFORM_BMC) {
+		pb_log("This platform does not support firmware updates\n");
+		return -1;
+	}
+
+	/* Only complete PNOR images accepted */
+	if (!pb_url_check_extension(url, ".pnor")) {
+		pb_log("update: Only .pnor files supported\n");
+		return -1;
+	}
+
+	/* Preserve NVRAM partition - will be written back in callback */
+	nvram_file = talloc_asprintf(platform, "/tmp/pb-nvram");
+	if (!nvram_file)
+		return -1;
+
+	rc = process_run_simple(platform, pb_system_apps.pflash,
+			"-r", nvram_file, "-P", "NVRAM", NULL);
+	if (rc) {
+		pb_log("Failed to preserve NVRAM\n");
+		return rc;
+	}
+
+	/* Perform Update */
+	const char *argv[] = {
+		pb_system_apps.pflash,
+		"-e",
+		"-p",
+		local_file,
+		"-f",
+		NULL
+	};
+
+	process = process_create(platform);
+	if (!process) {
+		pb_log("update: Failed to create process\n");
+		return -1;
+	}
+
+	update_data = talloc_zero(process, struct update_data);
+	if (update_data) {
+		update_data->status_fn = status_fn;
+		update_data->status_arg = status_arg;
+		update_data->update_cb = update_cb;
+		talloc_steal(update_data, nvram_file);
+		update_data->nvram_file = nvram_file;
+	}
+
+	process->path = pb_system_apps.pflash;
+	process->argv = argv;
+	process->keep_stdout = true;
+	process->stdout_cb = pflash_progress_cb;
+	process->stdout_data = update_data;
+	process->exit_cb = update_platform_cb;
+	process->data = update_data;
+	rc = process_run_async(process);
+
+	if (rc || process->exit_status) {
+		pb_log("Error updating firmware\n");
+	}
+
+	talloc_free(process);
+
+	return rc;
+}
+
 static bool probe(struct platform *p, void *ctx)
 {
 	struct platform_powerpc *platform;
@@ -1408,6 +1620,7 @@  static struct platform platform_powerpc = {
 	.save_config		= save_config,
 	.pre_boot		= pre_boot,
 	.get_sysinfo		= get_sysinfo,
+	.update_platform	= update_platform,
 };
 
 register_platform(platform_powerpc);
diff --git a/discover/platform.c b/discover/platform.c
index cc6306f..84fc5c1 100644
--- a/discover/platform.c
+++ b/discover/platform.c
@@ -94,6 +94,11 @@  static void dump_config(struct config *config)
 
 
 	pb_log(" language: %s\n", config->lang ?: "");
+
+	pb_log(" metadata source: %s\n", config->metadata_source ?: "");
+	pb_log(" update source: %s\n", config->update_source ?: "");
+	pb_log("  Platform updates allowed: %s\n",
+			config->platform_update ? "yes" : "no");
 }
 
 static bool config_debug_on_cmdline(void)
@@ -131,6 +136,8 @@  void config_set_defaults(struct config *config)
 	config->safe_mode = false;
 	config->allow_writes = true;
 	config->disable_snapshots = false;
+	config->metadata_source = NULL;
+	config->platform_update = false;
 
 	config->n_consoles = 0;
 	config->consoles = NULL;
@@ -202,6 +209,17 @@  void platform_pre_boot(void)
 		platform->pre_boot(platform, config);
 }
 
+int platform_update(struct pb_url *url, const char *local_file,
+		void (* status_fn)(void *arg, struct status *status),
+		void *status_arg,
+		void (* update_cb)(enum update_status status))
+{
+	if (platform && platform->update_platform)
+		return platform->update_platform(platform, url, local_file,
+				status_fn, status_arg, update_cb);
+	return -1;
+}
+
 int platform_get_sysinfo(struct system_info *info)
 {
 	if (platform && platform->get_sysinfo)
diff --git a/discover/platform.h b/discover/platform.h
index 5aa8e3f..423332c 100644
--- a/discover/platform.h
+++ b/discover/platform.h
@@ -2,6 +2,7 @@ 
 #define PLATFORM_H
 
 #include <types/types.h>
+#include "paths.h"
 
 struct platform {
 	const char	*name;
@@ -11,6 +12,11 @@  struct platform {
 	void		(*pre_boot)(struct platform *,
 				const struct config *);
 	int		(*get_sysinfo)(struct platform *, struct system_info *);
+	int		(*update_platform)(struct platform *, struct pb_url *,
+				const char *local_file,
+				void (* status_fn)(void *arg, struct status *status),
+				void *status_arg,
+				void (* update_cb)(enum update_status status));
 	uint16_t	dhcp_arch_id;
 	void		*platform_data;
 };
@@ -20,6 +26,10 @@  int platform_fini(void);
 const struct platform *platform_get(void);
 int platform_get_sysinfo(struct system_info *info);
 void platform_pre_boot(void);
+int platform_update(struct pb_url *url, const char *local_file,
+		void (* status_fn)(void *arg, struct status *status),
+		void *status_arg,
+		void (* update_cb)(enum update_status status));
 
 /* configuration interface */
 const struct config *config_get(void);