diff --git a/discover/device-handler.c b/discover/device-handler.c
index 7a570f9..4d45e27 100644
--- a/discover/device-handler.c
+++ b/discover/device-handler.c
@@ -19,6 +19,8 @@
 #include <process/process.h>
 #include <url/url.h>
 #include <i18n/i18n.h>
+#include <version/version.h>
+#include <file/file.h>
 #include <pb-config/pb-config.h>
 
 #include <sys/sysmacros.h>
@@ -100,6 +102,9 @@ static void device_handler_reinit_sources(struct device_handler *handler);
 
 static void device_handler_update_lang(const char *lang);
 
+static int device_handler_load_update_data(struct device_handler *handler,
+		const char *url);
+
 void discover_context_add_boot_option(struct discover_context *ctx,
 		struct discover_boot_option *boot_option)
 {
@@ -359,6 +364,7 @@ static void set_env_variables(const struct config *config)
 struct device_handler *device_handler_init(struct discover_server *server,
 		struct waitset *waitset, int dry_run)
 {
+	const struct config *config = config_get();
 	struct device_handler *handler;
 	int rc;
 
@@ -366,7 +372,7 @@ struct device_handler *device_handler_init(struct discover_server *server,
 	handler->server = server;
 	handler->waitset = waitset;
 	handler->dry_run = dry_run;
-	handler->autoboot_enabled = config_autoboot_active(config_get());
+	handler->autoboot_enabled = config_autoboot_active(config);
 
 	list_init(&handler->unresolved_boot_options);
 
@@ -377,7 +383,7 @@ struct device_handler *device_handler_init(struct discover_server *server,
 
 	parser_init();
 
-	if (config_get()->safe_mode)
+	if (config->safe_mode)
 		return handler;
 
 	set_env_variables(config_get());
@@ -388,6 +394,9 @@ struct device_handler *device_handler_init(struct discover_server *server,
 		return NULL;
 	}
 
+	if (config->metadata_source)
+		device_handler_load_update_data(handler, config->metadata_source);
+
 	return handler;
 }
 
@@ -406,6 +415,11 @@ void device_handler_reinit(struct device_handler *handler)
 		handler->pending_boot_is_default = false;
 	}
 
+	if (system_info_update_status() == UPDATE_RUNNING) {
+		pb_log("%s: Update Pending\n", __func__);
+		return;
+	}
+
 	/* Cancel any remaining async jobs */
 	process_stop_async_all();
 	pending_network_jobs_cancel();
@@ -614,7 +628,7 @@ void device_handler_status_download(struct device_handler *handler,
 	}
 
 	if (i >= strlen(units)) {
-	    pb_log("Couldn't recognise suffix '%c'\n", suffix);
+	    pb_log("%s: Couldn't recognise suffix '%c'\n", __func__, suffix);
 	    size_bytes = 0;
 	} else {
 		while (i--)
@@ -691,6 +705,7 @@ void device_handler_status_download_remove(struct device_handler *handler,
 
 	list_for_each_entry_safe(&handler->progress, p, tmp, list)
 		if (p->procinfo == procinfo) {
+			pb_debug("progress_info member removed\n");
 			list_remove(&p->list);
 			talloc_free(p);
 			handler->n_progress--;
@@ -1207,6 +1222,11 @@ void device_handler_boot(struct device_handler *handler,
 {
 	struct discover_boot_option *opt = NULL;
 
+	if (system_info_update_status() == UPDATE_RUNNING) {
+		pb_log("%s: Update Pending\n", __func__);
+		return;
+	}
+
 	if (cmd->option_id && strlen(cmd->option_id))
 		opt = find_boot_option_by_id(handler, cmd->option_id);
 
@@ -1250,6 +1270,11 @@ void device_handler_update_config(struct device_handler *handler,
 {
 	int rc;
 
+	if (system_info_update_status() == UPDATE_RUNNING) {
+		pb_log("%s: Update Pending\n", __func__);
+		return;
+	}
+
 	rc = config_set(config);
 	if (rc)
 		return;
@@ -1257,6 +1282,8 @@ void device_handler_update_config(struct device_handler *handler,
 	discover_server_notify_config(handler->server, config);
 	device_handler_update_lang(config->lang);
 	device_handler_reinit(handler);
+	device_handler_load_update_data(handler, config->metadata_source);
+
 }
 
 static char *device_from_addr(void *ctx, struct pb_url *url)
@@ -1495,6 +1522,93 @@ void device_handler_install_plugin(struct device_handler *handler,
 
 #ifndef PETITBOOT_TEST
 
+static void platform_update_status_cb(void *arg, struct status *status,
+		enum update_status state)
+{
+	if (status && arg)
+		device_handler_status(arg, status);
+	system_info_register_update_status(state);
+}
+
+/* Check load success before passing to platform-specific code */
+static void device_handler_firmware_update_cb(struct load_url_result *result,
+		void *data)
+{
+	struct device_handler *handler = data;
+	int rc;
+
+	if (!result || result->status != LOAD_OK) {
+		device_handler_status_err(handler,
+				_("Failed to retrieve firmware update"));
+		return;
+	}
+
+	device_handler_status_info(handler,
+			_("Platform update starting - please wait"));
+
+	rc = platform_update(result->url, result->local,
+			platform_update_status_cb, handler, system_info_get());
+
+	if (rc) {
+		device_handler_status_err(handler,
+				_("Platform update failed - see log"));
+		system_info_register_update_status(UPDATE_ERROR);
+	} else {
+		device_handler_status_info(handler,
+				_("Platform update running.."));
+		system_info_register_update_status(UPDATE_RUNNING);
+	}
+
+	talloc_free(result->url);
+}
+
+void device_handler_firmware_update(struct device_handler *handler,
+		const char *url)
+{
+	const struct platform *platform = platform_get();
+	const struct config *config = config_get();
+	const struct system_info *info = system_info_get();
+	struct pb_url *pb_url;
+
+	if (!platform || !platform->update_platform) {
+		pb_log("Platform does not support firmware updates\n");
+		return;
+	}
+
+	if (!info || !info->update_support) {
+		pb_log("Platform update method not specified\n");
+		return;
+	}
+
+	if (!config->platform_update) {
+		pb_log("Platform updates not enabled\n");
+		return;
+	}
+
+	if (system_info_update_status() == UPDATE_RUNNING) {
+		pb_log("Update already running!\n");
+		device_handler_status_err(handler, _("Update already running!"));
+		return;
+	}
+
+	pb_url = pb_url_parse(handler, url);
+	if (!pb_url) {
+		device_handler_status_err(handler,
+				_("Failed to parse firmware update URL"));
+		return;
+	}
+
+	if (!pb_url->file) {
+		device_handler_status_err(handler,
+				_("URL doesn't specify firmware image"));
+		return;
+	}
+
+	load_url_async(handler, pb_url, device_handler_firmware_update_cb,
+			handler, NULL, handler);
+}
+
+
 /**
  * context_commit - Commit a temporary discovery context to the handler,
  * and notify the clients about any new options / devices
@@ -1971,6 +2085,89 @@ void device_sync_snapshots(struct device_handler *handler, const char *device)
 	}
 }
 
+static void device_handler_load_update_data_cb(struct load_url_result *result,
+		void *data)
+{
+	const struct system_info *info = system_info_get();
+	struct firmware_version *platform_new;
+	struct device_handler *handler = data;
+	char *buf, *update_url = NULL;
+	struct config *config;
+	bool update;
+	int rc, len;
+
+
+	if (result->status != LOAD_OK) {
+		pb_log("Failed to load metadata file\n");
+		return;
+	}
+
+	if (!handler)
+		return;
+
+	rc = read_file(handler, result->local, &buf, &len);
+	if (rc) {
+		pb_log("Failed to read metadata file\n");
+		device_handler_status_err(handler,
+				_("Metadata check failed\n"));
+		return;
+	}
+
+	rc = parse_metadata(handler, buf, len, &platform_new, &update_url);
+	system_info_update_platform_versions_new(platform_new, rc > 0 ? rc : 0);
+
+	if (rc <= 0) {
+		pb_log("Failed to parse metadata information\n");
+		return;
+	}
+
+	update = compare_metadata_versions(handler, info->platform_primary,
+			info->n_primary, info->platform_new, info->n_new);
+	if (update && update_url) {
+		device_handler_status_info(handler, _("Update available!"));
+		config = config_copy(handler, config_get());
+		talloc_free(config->update_source);
+		config->update_source = talloc_strdup(config, update_url);
+		rc = config_set(config);
+		if (!rc)
+			discover_server_notify_config(handler->server, config);
+		talloc_free(update_url);
+	} else if (update)
+		device_handler_status_info(handler,
+				_("Update available but no source specified"));
+	else
+		device_handler_status_info(handler, _("No updates"));
+
+	talloc_free(result->url);
+}
+
+static int device_handler_load_update_data(struct device_handler *handler,
+		const char *url)
+{
+	struct load_url_result *result;
+	struct pb_url *pb_url;
+
+	if (!url)
+		return 0;
+
+	pb_url = pb_url_parse(handler, url);
+	if (!pb_url || !pb_url->file) {
+		pb_log("Metadata url missing file: %s\n", url);
+		if (pb_url)
+			talloc_free(pb_url);
+		return -1;
+	}
+
+	result = load_url_async(handler, pb_url,
+			device_handler_load_update_data_cb,
+			handler, NULL, NULL);
+
+	if (!result)
+		pb_log("Failed to start load for metadata file\n");
+
+	return result ? 0 : 1;
+}
+
 #else
 
 void device_handler_discover_context_commit(
@@ -2024,4 +2221,17 @@ void device_sync_snapshots(
 {
 }
 
+static int device_handler_load_update_data(
+		struct device_handler *handler __attribute__((unused)),
+		const char *url __attribute__((unused)))
+{
+	return 0;
+}
+
+void device_handler_firmware_update(
+		struct device_handler *handler __attribute__((unused)),
+		const char *url __attribute__((unused)))
+{
+}
+
 #endif
diff --git a/discover/device-handler.h b/discover/device-handler.h
index 771cd06..c1b5091 100644
--- a/discover/device-handler.h
+++ b/discover/device-handler.h
@@ -162,6 +162,8 @@ void device_handler_process_url(struct device_handler *handler,
 		const char *url, const char *mac, const char *ip);
 void device_handler_install_plugin(struct device_handler *handler,
 		const char *plugin_file);
+void device_handler_firmware_update(struct device_handler *handler,
+		const char *url);
 void device_handler_reinit(struct device_handler *handler);
 
 int device_request_write(struct discover_device *dev, bool *release);
diff --git a/discover/discover-server.c b/discover/discover-server.c
index 57cf3b7..fbd742c 100644
--- a/discover/discover-server.c
+++ b/discover/discover-server.c
@@ -310,6 +310,15 @@ static int discover_server_process_message(void *arg)
 		device_handler_install_plugin(client->server->device_handler,
 				url);
 		break;
+
+	case PB_PROTOCOL_ACTION_FIRMWARE_UPDATE:
+		url = pb_protocol_deserialise_string((void *) client, message);
+
+		device_handler_firmware_update(client->server->device_handler,
+				url);
+
+		break;
+
 	default:
 		pb_log("%s: invalid action %d\n", __func__, message->action);
 		return 0;
diff --git a/discover/sysinfo.c b/discover/sysinfo.c
index 74d1eae..b72d750 100644
--- a/discover/sysinfo.c
+++ b/discover/sysinfo.c
@@ -142,6 +142,28 @@ void system_info_register_blockdev(const char *name, const char *uuid,
 	discover_server_notify_system_info(server, sysinfo);
 }
 
+enum update_status system_info_update_status(void)
+{
+	return sysinfo->update_status;
+}
+
+void system_info_register_update_status(enum update_status status)
+{
+	sysinfo->update_status = status;
+	discover_server_notify_system_info(server, sysinfo);
+}
+
+void system_info_update_platform_versions_new(
+		struct firmware_version *platform_new, unsigned int n_new)
+{
+	if (sysinfo->n_new)
+		talloc_free(sysinfo->platform_new);
+	sysinfo->platform_new = platform_new;
+	sysinfo->n_new = n_new;
+
+	discover_server_notify_system_info(server, sysinfo);
+}
+
 void system_info_init(struct discover_server *s)
 {
 	server = s;
diff --git a/discover/sysinfo.h b/discover/sysinfo.h
index 835dfbe..8208626 100644
--- a/discover/sysinfo.h
+++ b/discover/sysinfo.h
@@ -14,9 +14,14 @@ void system_info_register_interface(unsigned int hwaddr_size, uint8_t *hwaddr,
 		const char *name, bool link);
 void system_info_register_blockdev(const char *name, const char *uuid,
 		const char *mountpoint);
+void system_info_register_update_status(enum update_status status);
+void system_info_update_platform_versions_new(
+		struct firmware_version *platform_new, unsigned int n_new);
 
 void system_info_init(struct discover_server *server);
 void system_info_reinit(void);
 
+enum update_status system_info_update_status(void);
+
 #endif /* SYSINFO_H */
 
diff --git a/lib/pb-config/pb-config.c b/lib/pb-config/pb-config.c
index 7fa925c..3500eb6 100644
--- a/lib/pb-config/pb-config.c
+++ b/lib/pb-config/pb-config.c
@@ -102,5 +102,13 @@ struct config *config_copy(void *ctx, const struct config *src)
 	else
 		dest->lang = NULL;
 
+	if (src->metadata_source)
+		dest->metadata_source = talloc_strdup(dest, src->metadata_source);
+
+	if (src->update_source)
+		dest->update_source = talloc_strdup(dest, src->update_source);
+
+	dest->platform_update = src->platform_update;
+
 	return dest;
 }
diff --git a/lib/pb-protocol/pb-protocol.c b/lib/pb-protocol/pb-protocol.c
index 76a2665..cf4ddb4 100644
--- a/lib/pb-protocol/pb-protocol.c
+++ b/lib/pb-protocol/pb-protocol.c
@@ -281,6 +281,9 @@ int pb_protocol_system_info_len(const struct system_info *sysinfo)
 			4 + optional_strlen(bd_info->mountpoint);
 	}
 
+	len += 4 /* update_support */;
+	len += 4 /* update_status */;
+
 	/* BMC MAC */
 	len += HWADDR_SIZE;
 
@@ -351,6 +354,9 @@ int pb_protocol_config_len(const struct config *config)
 	len += 4; /* manual_console */
 
 	len += 4 + optional_strlen(config->lang);
+	len += 4 + optional_strlen(config->metadata_source);
+	len += 4 + optional_strlen(config->update_source);
+	len += 4; /* platform_update */
 
 	return len;
 }
@@ -551,6 +557,13 @@ int pb_protocol_serialise_system_info(const struct system_info *sysinfo,
 		memset(pos, 0, HWADDR_SIZE);
 	pos += HWADDR_SIZE;
 
+	*(bool *)pos = __cpu_to_be32(sysinfo->update_support);
+	pos += sizeof(bool);
+
+	*(enum update_status *)pos = sysinfo->update_status;
+	pos += sizeof(enum device_type);
+
+
 	assert(pos <= buf + buf_len);
 	(void)buf_len;
 
@@ -657,6 +670,12 @@ int pb_protocol_serialise_config(const struct config *config,
 
 	pos += pb_protocol_serialise_string(pos, config->lang);
 
+	pos += pb_protocol_serialise_string(pos, config->metadata_source);
+	pos += pb_protocol_serialise_string(pos, config->update_source);
+
+	*(uint32_t *)pos = config->platform_update;
+	pos += 4;
+
 	assert(pos <= buf + buf_len);
 	(void)buf_len;
 
@@ -1112,6 +1131,12 @@ int pb_protocol_deserialise_system_info(struct system_info *sysinfo,
 	pos += HWADDR_SIZE;
 	len -= HWADDR_SIZE;
 
+	sysinfo->update_support = *(bool *)pos;
+	pos += sizeof(sysinfo->update_support);
+
+	sysinfo->update_status = *(enum update_status *)(pos);
+	pos += sizeof(enum device_type);
+
 	rc = 0;
 out:
 	return rc;
@@ -1266,6 +1291,20 @@ int pb_protocol_deserialise_config(struct config *config,
 
 	config->lang = str;
 
+	if (read_string(config, &pos, &len, &str))
+		goto out;
+
+	config->metadata_source = str;
+
+	if (read_string(config, &pos, &len, &str))
+		goto out;
+
+	config->update_source = str;
+
+	if (read_u32(&pos, &len, &tmp))
+		goto out;
+	config->platform_update = !!tmp;
+
 	rc = 0;
 
 out:
diff --git a/lib/pb-protocol/pb-protocol.h b/lib/pb-protocol/pb-protocol.h
index 250c2d1..2b827d2 100644
--- a/lib/pb-protocol/pb-protocol.h
+++ b/lib/pb-protocol/pb-protocol.h
@@ -26,6 +26,8 @@ enum pb_protocol_action {
 	PB_PROTOCOL_ACTION_PLUGIN_OPTION_ADD	= 0xc,
 	PB_PROTOCOL_ACTION_PLUGINS_REMOVE	= 0xd,
 	PB_PROTOCOL_ACTION_PLUGIN_INSTALL	= 0xe,
+	PB_PROTOCOL_ACTION_FIRMWARE_UPDATE	= 0xf,
+	PB_PROTOCOL_ACTION_FIRMWARE_COMPLETE	= 0x10,
 };
 
 struct pb_protocol_message {
diff --git a/lib/types/types.c b/lib/types/types.c
index d7f4ead..b61c033 100644
--- a/lib/types/types.c
+++ b/lib/types/types.c
@@ -87,3 +87,19 @@ bool config_autoboot_active(const struct config *config)
 
 	return true;
 }
+
+const char *update_status_display_name(enum update_status status)
+{
+	switch (status) {
+	case UPDATE_NONE:
+		return _("No update running");
+	case UPDATE_RUNNING:
+		return _("Update running");
+	case UPDATE_COMPLETED:
+		return _("Update completed");
+	case UPDATE_ERROR:
+		return _("Error running update");
+	default:
+		return _("Unknown");
+	}
+}
diff --git a/lib/types/types.h b/lib/types/types.h
index f4312e6..03dad2b 100644
--- a/lib/types/types.h
+++ b/lib/types/types.h
@@ -24,10 +24,18 @@ enum ipmi_bootdev {
 	IPMI_BOOTDEV_INVALID = 0xff,
 };
 
+enum update_status {
+	UPDATE_NONE,
+	UPDATE_RUNNING,
+	UPDATE_COMPLETED,
+	UPDATE_ERROR
+};
+
 const char *ipmi_bootdev_display_name(enum ipmi_bootdev bootdev);
 const char *device_type_display_name(enum device_type type);
 const char *device_type_name(enum device_type type);
 enum device_type find_device_type(const char *str);
+const char *update_status_display_name(enum update_status status);
 
 struct device {
 	char		*id;
@@ -143,6 +151,9 @@ struct system_info {
 	unsigned int		n_interfaces;
 	struct blockdev_info	**blockdevs;
 	unsigned int		n_blockdevs;
+
+	bool			update_support;
+	enum update_status	update_status;
 };
 
 #define HWADDR_SIZE	6
@@ -204,12 +215,16 @@ struct config {
 	bool			manual_console;
 	char			*lang;
 
+	char			*metadata_source;
+
 	/* not user-settable */
 	unsigned int		n_consoles;
 	char			**consoles;
 	bool			disable_snapshots;
 	bool			safe_mode;
 	bool			debug;
+	char			*update_source;
+	bool			platform_update;
 };
 
 bool config_autoboot_active(const struct config *config);
diff --git a/test/parser/handler.c b/test/parser/handler.c
index a9856b4..a2d2c58 100644
--- a/test/parser/handler.c
+++ b/test/parser/handler.c
@@ -62,6 +62,16 @@ void system_info_register_blockdev(const char *name, const char *uuid,
 	(void)mountpoint;
 }
 
+void system_info_register_update_status(bool complete)
+{
+	(void)complete;
+}
+
+enum update_status system_info_update_status(void)
+{
+	assert(false);
+}
+
 void network_register_device(struct network *network,
 		struct discover_device *dev)
 {
