[[RFC] v2 12/14] discover/platform-powerpc: Platform Firmware Updates

Message ID 20180118050517.2442-13-sam@mendozajonas.com
State RFC
Headers show
Series
  • [[RFC] v2 12/14] discover/platform-powerpc: Platform Firmware Updates
Related show

Commit Message

Samuel Mendoza-Jonas Jan. 18, 2018, 5:05 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/Makefile.am               |   2 +
 discover/platform-powerpc-update.c | 210 +++++++++++++++++++++++++++++++++++++
 discover/platform-powerpc-update.h |  13 +++
 discover/platform-powerpc.c        |  60 +++++++++++
 discover/platform.c                |  19 ++++
 discover/platform.h                |  12 +++
 6 files changed, 316 insertions(+)
 create mode 100644 discover/platform-powerpc-update.c
 create mode 100644 discover/platform-powerpc-update.h

Patch

diff --git a/discover/Makefile.am b/discover/Makefile.am
index 4a6cbd0..a25622c 100644
--- a/discover/Makefile.am
+++ b/discover/Makefile.am
@@ -76,6 +76,8 @@  discover_pb_discover_CPPFLAGS = \
 discover_platform_ro_SOURCES = \
 	discover/platform.c \
 	discover/platform.h \
+	discover/platform-powerpc-update.c \
+	discover/platform-powerpc-update.h \
 	discover/ipmi.c \
 	discover/ipmi.h \
 	discover/dt.c \
diff --git a/discover/platform-powerpc-update.c b/discover/platform-powerpc-update.c
new file mode 100644
index 0000000..d257bdd
--- /dev/null
+++ b/discover/platform-powerpc-update.c
@@ -0,0 +1,210 @@ 
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/fcntl.h>
+
+#include <talloc/talloc.h>
+#include <log/log.h>
+#include <process/process.h>
+#include <types/types.h>
+#include <i18n/i18n.h>
+#include <system/system.h>
+#include <url/url.h>
+
+#include "platform-powerpc-update.h"
+
+struct update_data {
+	void		(*status_fn)(void *arg, struct status *status,
+				enum update_status state);
+	void		*status_arg;
+	const char	*nvram_file;
+	enum {
+				PFLASH_UNKNOWN = 0,
+				PFLASH_ERASE,
+				PFLASH_WRITE,
+	} flash_state;
+};
+
+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 *data;
+	struct status status;
+	struct process *p;
+	int rc;
+	char eta[9];
+
+	if (!arg)
+		return -1;
+
+	p = procinfo_get_process(procinfo);
+	data = p->stdout_data;
+
+	rc = process_stdout_custom(procinfo, &line);
+
+	if (rc || !line)
+		return rc;
+
+	switch (data->flash_state) {
+	case PFLASH_UNKNOWN:
+		if (strstr(line, "Erasing"))
+			data->flash_state = PFLASH_ERASE;
+		else if (strstr(line, "Programming"))
+			data->flash_state = PFLASH_WRITE;
+		break;
+	case PFLASH_ERASE:
+		if (strstr(line, "Programming"))
+			data->flash_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"),
+			data->flash_state == PFLASH_ERASE ? _("erasing") :
+			data->flash_state == PFLASH_WRITE ? _("writing") : "",
+			percentage, eta);
+	status.backlog = false;
+	data->status_fn(data->status_arg, &status, UPDATE_RUNNING);
+	pb_debug("pflash cb: %u%%, eta %s\n", percentage, eta);
+
+	talloc_free(line);
+	return 0;
+}
+
+/* Restore NVRAM contents after update for flash based update */
+static void nvram_restore_cb(struct process *process)
+{
+	struct update_data *data = process->data;
+	struct status status;
+	int rc;
+
+	pb_log("Update complete!\n");
+
+	if (!process || !process->data)
+		return;
+
+	status.backlog = false;
+
+	if (process->exit_status) {
+		pb_log("pflash returned an error - leaving NVRAM alone\n");
+		status.type = STATUS_ERROR;
+		status.message = talloc_asprintf(data,
+				_("Update failed - check logs for details"));
+		data->status_fn(data->status_arg, &status,
+				UPDATE_ERROR);
+		goto out;
+	}
+
+	rc = process_run_simple(process, pb_system_apps.pflash,
+			"-e", "-f", "-p", data->nvram_file, "-P", "NVRAM",
+			NULL);
+	if (rc) {
+		pb_log("Failed to restore NVRAM!\n");
+		status.type = STATUS_ERROR;
+		status.message = talloc_asprintf(data,
+				_("Failed to restore NVRAM!"));
+		data->status_fn(data->status_arg, &status,
+				UPDATE_ERROR);
+	} else {
+		status.type = STATUS_INFO;
+		status.message = talloc_asprintf(data,
+				_("Update complete! You may now reboot"));
+		data->status_fn(data->status_arg, &status,
+				UPDATE_COMPLETED);
+	}
+
+out:
+	talloc_free(process);
+	talloc_free(data);
+}
+
+int platform_update_flash(void *ctx, struct pb_url *url, const char *file,
+		void (*status_fn)(void *arg, struct status *status,
+			enum update_status state),
+		void *status_arg,
+		const struct system_info *info)
+{
+	struct update_data *data;
+	struct process *process;
+	(void)info;
+	int rc;
+
+	/* Only complete PNOR images accepted */
+	if (!pb_url_check_extension(url, ".pnor")) {
+		pb_log("update: Only .pnor files supported\n");
+		return -1;
+	}
+
+	data = talloc_zero(ctx, struct update_data);
+	if (!data)
+		return -1;
+
+	data->status_fn = status_fn;
+	data->status_arg = status_arg;
+
+	/* Preserve NVRAM partition - will be restored in callback */
+	data->nvram_file = talloc_asprintf(data, "/tmp/pb-nvram");
+	if (!data->nvram_file) {
+		talloc_free(data);
+		return -1;
+	}
+
+	rc = process_run_simple(ctx, pb_system_apps.pflash,
+			"-r", data->nvram_file, "-P", "NVRAM", NULL);
+	if (rc) {
+		pb_log("Failed to preserve NVRAM\n");
+		talloc_free(data);
+		return rc;
+	}
+
+	/* Perform Update */
+	const char *argv[] = {
+		pb_system_apps.pflash,
+		"-e",
+		"-p",
+		file,
+		"-f",
+		NULL
+	};
+
+	process = process_create(ctx);
+	if (!process) {
+		pb_log("update: Failed to create process\n");
+		talloc_free(data);
+		return -1;
+	}
+
+	process->path = pb_system_apps.pflash;
+	process->argv = argv;
+	process->keep_stdout = true;
+	process->stdout_cb = pflash_progress_cb;
+	process->stdout_data = data;
+	process->exit_cb = nvram_restore_cb;
+	process->data = data;
+	rc = process_run_async(process);
+
+	if (rc || process->exit_status) {
+		pb_log("Error updating firmware\n");
+		talloc_free(process);
+		talloc_free(data);
+	}
+
+	return rc;
+}
diff --git a/discover/platform-powerpc-update.h b/discover/platform-powerpc-update.h
new file mode 100644
index 0000000..e629cd5
--- /dev/null
+++ b/discover/platform-powerpc-update.h
@@ -0,0 +1,13 @@ 
+#ifndef PLATFORM_UPDATE_H
+#define PLATFORM_UPDATE_H
+
+#include "config.h"
+#include <types/types.h>
+
+int platform_update_flash(void *ctx, struct pb_url *url, const char *file,
+		void (*status_fn)(void *arg, struct status *status,
+			enum update_status state),
+		void *status_arg,
+		const struct system_info *info);
+
+#endif /* PLATFORM_UPDATE_H */
diff --git a/discover/platform-powerpc.c b/discover/platform-powerpc.c
index 0e2f87d..4b7ee60 100644
--- a/discover/platform-powerpc.c
+++ b/discover/platform-powerpc.c
@@ -16,11 +16,17 @@ 
 #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"
+#include "platform-powerpc-update.h"
 
 static const char *partition = "common";
 static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
@@ -47,6 +53,13 @@  struct platform_powerpc {
 	int 		(*set_os_boot_sensor)(
 				struct platform_powerpc *platform);
 	int		(*get_platform_versions)(struct system_info *info);
+	int		(*update)(
+				void *ctx, struct pb_url *url, const char *file,
+				void (*status_fn)(
+					void *arg, struct status *status,
+					enum update_status state),
+				void *status_arg,
+				const struct system_info *info);
 
 	enum {
 			POWERPC_PLATFORM_FSP,
@@ -67,6 +80,8 @@  static const char *known_params[] = {
 	"petitboot,console",
 	"petitboot,http_proxy",
 	"petitboot,https_proxy",
+	"petitboot,metadata_source",
+	"petitboot,platform_update?",
 	NULL,
 };
 
@@ -553,6 +568,14 @@  static void populate_config(struct platform_powerpc *platform,
 	val = get_param(platform, "petitboot,https_proxy");
 	if (val)
 		config->https_proxy = talloc_strdup(config, val);
+
+	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)
@@ -733,6 +756,15 @@  static int update_config(struct platform_powerpc *platform,
 	val = config->https_proxy ?: "";
 	update_string_config(platform, "petitboot,https_proxy", val);
 
+	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);
@@ -1327,8 +1359,15 @@  static void pre_boot(struct platform *p, const struct config *config)
 static int get_sysinfo(struct platform *p, struct system_info *sysinfo)
 {
 	struct platform_powerpc *platform = p->platform_data;
+	struct process *process;
 	char *buf, *filename;
 	int len, rc;
+	const char *argv[] = {
+		pb_system_apps.obmc_update,
+		"check",
+		sysinfo->bmc_ip,
+		NULL,
+	};
 
 	filename = talloc_asprintf(platform, "%smodel", devtree_dir);
 	rc = read_file(platform, filename, &buf, &len);
@@ -1361,9 +1400,29 @@  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");
 
+	if (platform->type == POWERPC_PLATFORM_BMC)
+		platform->update = platform_update_flash;
+	else
+		platform->update = NULL;
+
+	sysinfo->update_support = platform->type == POWERPC_PLATFORM_BMC;
+
 	return 0;
 }
 
+// we could just pass the update_data in here directly
+static int update_platform(struct platform *p, struct pb_url *url,
+		const char *file,
+		void (*status_fn)(void *arg, struct status *status,
+			enum update_status state),
+		void *status_arg, const struct system_info *info)
+{
+	struct platform_powerpc *platform = to_platform_powerpc(p);
+
+	return platform->update(platform, url, file, status_fn, status_arg,
+			info);
+}
+
 static bool probe(struct platform *p, void *ctx)
 {
 	struct platform_powerpc *platform;
@@ -1424,6 +1483,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..accc62a 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,9 @@  void config_set_defaults(struct config *config)
 	config->safe_mode = false;
 	config->allow_writes = true;
 	config->disable_snapshots = false;
+	config->metadata_source = NULL;
+	config->update_source = NULL;
+	config->platform_update = false;
 
 	config->n_consoles = 0;
 	config->consoles = NULL;
@@ -202,6 +210,17 @@  void platform_pre_boot(void)
 		platform->pre_boot(platform, config);
 }
 
+int platform_update(struct pb_url *url, const char *file,
+		void (*status_fn)(void *arg, struct status *status,
+			enum update_status state),
+		void *status_arg, const struct system_info *info)
+{
+	if (platform && platform->update_platform)
+		return platform->update_platform(platform, url, file, status_fn,
+				status_arg, info);
+	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..6a3e108 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,13 @@  struct platform {
 	void		(*pre_boot)(struct platform *,
 				const struct config *);
 	int		(*get_sysinfo)(struct platform *, struct system_info *);
+	int		(*update_platform)(struct platform *p,
+				struct pb_url *url, const char *file,
+				void (*status_fn)(void *arg,
+					struct status *status,
+					enum update_status state),
+				void *status_arg,
+				const struct system_info *info);
 	uint16_t	dhcp_arch_id;
 	void		*platform_data;
 };
@@ -20,6 +28,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 *file,
+		void (*status_fn)(void *arg, struct status *status,
+			enum update_status state),
+		void *status_arg, const struct system_info *info);
 
 /* configuration interface */
 const struct config *config_get(void);