protocol: Add get_info

Change-Id: Ie3338714813bb65f5d37fcd046dd5bebc0ba21f0
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/Makefile.am b/Makefile.am
index 97be7e0..102e7c4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,6 +8,7 @@
 	control_legacy.c \
 	control_dbus.c \
 	lpc.c \
+	protocol.c \
 	transport_mbox.c \
 	windows.c \
 	mtd.c
diff --git a/lpc.c b/lpc.c
index 2c8b00d..4b9d384 100644
--- a/lpc.c
+++ b/lpc.c
@@ -170,7 +170,7 @@
 		  &map)) {
 		MSG_ERR("Failed to point the LPC BUS to memory: %s\n",
 			strerror(errno));
-		return -MBOX_R_SYSTEM_ERROR;
+		return -errno;
 	}
 
 	/* LPC now maps memory (keep suspended state) */
diff --git a/mbox.h b/mbox.h
index 19d0a3f..3676825 100644
--- a/mbox.h
+++ b/mbox.h
@@ -8,6 +8,8 @@
 #include <systemd/sd-bus.h>
 #include <poll.h>
 #include <stdbool.h>
+
+#include "protocol.h"
 #include "vpnor/mboxd_pnor_partition_table.h"
 
 enum api_version {
@@ -136,9 +138,11 @@
 				  struct mbox_msg *);
 
 struct mbox_context {
+	enum api_version version;
+	const struct protocol_ops *protocol;
+
 /* System State */
 	enum mbox_state state;
-	enum api_version version;
 	struct pollfd fds[TOTAL_FDS];
 	sd_bus *bus;
 	bool terminate;
diff --git a/mboxd.c b/mboxd.c
index 5ed42d6..2382724 100644
--- a/mboxd.c
+++ b/mboxd.c
@@ -353,6 +353,11 @@
 		goto finish;
 	}
 
+	rc = protocol_init(context);
+	if (rc) {
+		goto finish;
+	}
+
 	rc = init_mbox_dev(context);
 	if (rc) {
 		goto finish;
@@ -403,14 +408,15 @@
 	MSG_INFO("Daemon Exiting...\n");
 	clr_bmc_events(context, BMC_EVENT_DAEMON_READY, SET_BMC_EVENT);
 
+#ifdef VIRTUAL_PNOR_ENABLED
+	destroy_vpnor(context);
+#endif
 	dbus_free(context);
 	flash_dev_free(context);
 	lpc_dev_free(context);
 	free_mbox_dev(context);
 	windows_free(context);
-#ifdef VIRTUAL_PNOR_ENABLED
-	destroy_vpnor(context);
-#endif
+	protocol_free(context);
 	free(context);
 
 	return rc;
diff --git a/protocol.c b/protocol.c
new file mode 100644
index 0000000..24a830b
--- /dev/null
+++ b/protocol.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include "config.h"
+
+#include <errno.h>
+#include <stdint.h>
+
+#include "mbox.h"
+#include "lpc.h"
+#include "transport_mbox.h" /* TODO: Remove dependency on transport_mbox.h */
+#include "windows.h"
+
+int protocol_v1_get_info(struct mbox_context *context,
+			 struct protocol_get_info *io)
+{
+	uint8_t old_version = context->version;
+	int rc;
+
+	/* Bootstrap protocol version. This may involve {up,down}grading */
+	rc = protocol_negotiate_version(context, io->req.api_version);
+	if (rc < 0)
+		return rc;
+
+	/* Do the {up,down}grade if necessary*/
+	if (rc != old_version) {
+		windows_reset_all(context, SET_BMC_EVENT);
+		return context->protocol->get_info(context, io);
+	}
+
+	/* Record the negotiated version for the response */
+	io->resp.api_version = rc;
+
+	/* Now do all required intialisation for v1 */
+	context->block_size_shift = BLOCK_SIZE_SHIFT_V1;
+	MSG_INFO("Block Size: 0x%.8x (shift: %u)\n",
+		 1 << context->block_size_shift, context->block_size_shift);
+
+	/* Knowing blocksize we can allocate the window dirty_bytemap */
+	windows_alloc_dirty_bytemap(context);
+
+	io->resp.v1.read_window_size =
+		context->windows.default_size >> context->block_size_shift;
+	io->resp.v1.write_window_size =
+		context->windows.default_size >> context->block_size_shift;
+
+	return lpc_map_memory(context);
+}
+
+/*
+ * get_suggested_timeout() - get the suggested timeout value in seconds
+ * @context:	The mbox context pointer
+ *
+ * Return:	Suggested timeout in seconds
+ */
+static uint16_t get_suggested_timeout(struct mbox_context *context)
+{
+	struct window_context *window = windows_find_largest(context);
+	uint32_t max_size_mb = window ? (window->size >> 20) : 0;
+	uint16_t ret;
+
+	ret = align_up(max_size_mb * FLASH_ACCESS_MS_PER_MB, 1000) / 1000;
+
+	MSG_DBG("Suggested Timeout: %us, max window size: %uMB, for %dms/MB\n",
+		ret, max_size_mb, FLASH_ACCESS_MS_PER_MB);
+	return ret;
+}
+
+int protocol_v2_get_info(struct mbox_context *context,
+			 struct protocol_get_info *io)
+{
+	uint8_t old_version = context->version;
+	int rc;
+
+	/* Bootstrap protocol version. This may involve {up,down}grading */
+	rc = protocol_negotiate_version(context, io->req.api_version);
+	if (rc < 0)
+		return rc;
+
+	/* Do the {up,down}grade if necessary*/
+	if (rc != old_version) {
+		windows_reset_all(context, SET_BMC_EVENT);
+		return context->protocol->get_info(context, io);
+	}
+
+	/* Record the negotiated version for the response */
+	io->resp.api_version = rc;
+
+	/* Now do all required intialisation for v2 */
+	context->block_size_shift = log_2(context->mtd_info.erasesize);
+	MSG_INFO("Block Size: 0x%.8x (shift: %u)\n",
+		 1 << context->block_size_shift, context->block_size_shift);
+
+	/* Knowing blocksize we can allocate the window dirty_bytemap */
+	windows_alloc_dirty_bytemap(context);
+
+	io->resp.v2.block_size_shift = context->block_size_shift;
+	io->resp.v2.timeout = get_suggested_timeout(context);
+
+	return lpc_map_memory(context);
+}
+
+static const struct protocol_ops protocol_ops_v1 = {
+	.get_info = protocol_v1_get_info,
+};
+
+static const struct protocol_ops protocol_ops_v2 = {
+	.get_info = protocol_v2_get_info,
+};
+
+static const struct protocol_ops *protocol_ops_map[] = {
+	[0] = NULL,
+	[1] = &protocol_ops_v1,
+	[2] = &protocol_ops_v2,
+};
+
+int protocol_negotiate_version(struct mbox_context *context,
+				   uint8_t requested)
+{
+	/* Check we support the version requested */
+	if (requested < API_MIN_VERSION)
+		return -EINVAL;
+
+	context->version = (requested > API_MAX_VERSION) ?
+				API_MAX_VERSION : requested;
+
+	context->protocol = protocol_ops_map[context->version];
+
+	return context->version;
+}
+
+int protocol_init(struct mbox_context *context)
+{
+	context->version = API_MAX_VERSION;
+	context->protocol = protocol_ops_map[context->version];
+
+	return 0;
+}
+
+void protocol_free(struct mbox_context *context)
+{
+	return;
+}
diff --git a/protocol.h b/protocol.h
new file mode 100644
index 0000000..c147139
--- /dev/null
+++ b/protocol.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
+
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+struct mbox_context;
+
+/*
+ * The GET_MBOX_INFO command is special as it can change the interface based on
+ * negotiation. As such we need to accommodate all response types
+ */
+struct protocol_get_info {
+	struct {
+		uint8_t api_version;
+	} req;
+	struct {
+		uint8_t api_version;
+		union {
+			struct {
+				uint16_t read_window_size;
+				uint16_t write_window_size;
+			} v1;
+			struct {
+				uint8_t block_size_shift;
+				uint16_t timeout;
+			} v2;
+		};
+	} resp;
+};
+
+struct protocol_ops {
+	int (*get_info)(struct mbox_context *context,
+			struct protocol_get_info *io);
+};
+
+int protocol_init(struct mbox_context *context);
+void protocol_free(struct mbox_context *context);
+
+int protocol_negotiate_version(struct mbox_context *context, uint8_t requested);
+
+/* Protocol v1 */
+int protocol_v1_get_info(struct mbox_context *context,
+			 struct protocol_get_info *io);
+
+/* Protocol v2 */
+int protocol_v2_get_info(struct mbox_context *context,
+			 struct protocol_get_info *io);
+
+#endif /* PROTOCOL_H */
diff --git a/test/Makefile.am.include b/test/Makefile.am.include
index feb1c97..71fa851 100644
--- a/test/Makefile.am.include
+++ b/test/Makefile.am.include
@@ -24,7 +24,8 @@
 	lpc.c \
 	lpc_reset.c \
 	common.c \
-	flash.c
+	flash.c \
+	protocol.c
 
 TEST_MOCK_SRCS = %reldir%/tmpf.c %reldir%/mbox.c %reldir%/system.c
 
diff --git a/test/mbox.c b/test/mbox.c
index dbf2526..a622d6e 100644
--- a/test/mbox.c
+++ b/test/mbox.c
@@ -231,6 +231,9 @@
 	test.context.windows.num = n_windows;
 	test.context.windows.default_size = len;
 
+	rc = protocol_init(&test.context);
+	assert(rc == 0);
+
 	/*
 	 * We need to call __init_mbox_dev() to initialise the handler table.
 	 * However, afterwards we need to discard the fd of the clearly useless
diff --git a/transport_mbox.c b/transport_mbox.c
index c14ab27..ec6d601 100644
--- a/transport_mbox.c
+++ b/transport_mbox.c
@@ -30,6 +30,52 @@
 #include "windows.h"
 #include "lpc.h"
 
+struct errno_map {
+	int rc;
+	int mbox_errno;
+};
+
+static const struct errno_map errno_map_v1[] = {
+	{ 0, MBOX_R_SUCCESS },
+	{ EACCES, MBOX_R_PARAM_ERROR },
+	{ EBUSY, MBOX_R_SYSTEM_ERROR },
+	{ EINVAL, MBOX_R_PARAM_ERROR },
+	{ EPERM, MBOX_R_PARAM_ERROR },
+	{ ETIMEDOUT, MBOX_R_TIMEOUT },
+	{ -1, MBOX_R_SYSTEM_ERROR },
+};
+
+static const struct errno_map errno_map_v2[] = {
+	{ 0, MBOX_R_SUCCESS },
+	{ EACCES, MBOX_R_PARAM_ERROR },
+	{ EBUSY, MBOX_R_BUSY },
+	{ EINVAL, MBOX_R_PARAM_ERROR },
+	{ EPERM, MBOX_R_WINDOW_ERROR },
+	{ ETIMEDOUT, MBOX_R_TIMEOUT },
+	{ -1, MBOX_R_SYSTEM_ERROR },
+};
+
+static const struct errno_map *errno_maps[] = {
+	[0] = NULL,
+	[1] = errno_map_v1,
+	[2] = errno_map_v2,
+};
+
+static inline int mbox_xlate_errno(struct mbox_context *context,
+					     int rc)
+{
+	const struct errno_map *entry;
+
+	rc = -rc;
+	for(entry = errno_maps[context->version]; entry->rc != -1; entry++) {
+		if (rc == entry->rc) {
+			return -entry->mbox_errno;
+		}
+	}
+
+	return -entry->mbox_errno;
+}
+
 /*
  * write_bmc_event_reg() - Write to the BMC controlled status register (reg 15)
  * @context:	The mbox context pointer
@@ -128,25 +174,6 @@
 }
 
 /*
- * get_suggested_timeout() - get the suggested timeout value in seconds
- * @context:	The mbox context pointer
- *
- * Return:	Suggested timeout in seconds
- */
-static uint16_t get_suggested_timeout(struct mbox_context *context)
-{
-	struct window_context *window = windows_find_largest(context);
-	uint32_t max_size_mb = window ? (window->size >> 20) : 0;
-	uint16_t ret;
-
-	ret = align_up(max_size_mb * FLASH_ACCESS_MS_PER_MB, 1000) / 1000;
-
-	MSG_DBG("Suggested Timeout: %us, max window size: %uMB, for %dms/MB\n",
-		ret, max_size_mb, FLASH_ACCESS_MS_PER_MB);
-	return ret;
-}
-
-/*
  * Command: GET_MBOX_INFO
  * Get the API version, default window size and block size
  * We also set the LPC mapping to point to the reserved memory region here so
@@ -172,65 +199,23 @@
 				 union mbox_regs *req, struct mbox_msg *resp)
 {
 	uint8_t mbox_api_version = req->msg.args[0];
-	uint8_t old_api_version = context->version;
+	struct protocol_get_info io = {
+		.req = { .api_version = mbox_api_version }
+	};
 	int rc;
 
-	/* Check we support the version requested */
-	if (mbox_api_version < API_MIN_VERSION)
-		return -MBOX_R_PARAM_ERROR;
-
-	if (mbox_api_version > API_MAX_VERSION)
-		mbox_api_version = API_MAX_VERSION;
-
-	context->version = mbox_api_version;
-	MSG_INFO("Using Protocol Version: %d\n", context->version);
-
-	/*
-	 * The reset state is currently to have the LPC bus point directly to
-	 * flash, since we got a mbox_info command we know the host can talk
-	 * mbox so point the LPC bus mapping to the reserved memory region now
-	 * so the host can access what we put in it.
-	 */
-	rc = lpc_map_memory(context);
+	rc = context->protocol->get_info(context, &io);
 	if (rc < 0) {
-		return rc;
+		return mbox_xlate_errno(context, rc);
 	}
 
-	switch (context->version) {
-	case API_VERSION_1:
-		context->block_size_shift = BLOCK_SIZE_SHIFT_V1;
-		break;
-	default:
-		context->block_size_shift = log_2(context->mtd_info.erasesize);
-		break;
-	}
-	MSG_INFO("Block Size: 0x%.8x (shift: %u)\n",
-		 1 << context->block_size_shift, context->block_size_shift);
-
-	/* Now we know the blocksize we can allocate the window dirty_bytemap */
-	if (mbox_api_version != old_api_version) {
-		windows_alloc_dirty_bytemap(context);
-	}
-	/* Reset if we were V1 since this required exact window mapping */
-	if (old_api_version == API_VERSION_1) {
-		/*
-		 * This will only set the BMC event if there was a current
-		 * window -> In which case we are better off notifying the
-		 * host.
-		 */
-		windows_reset_all(context, SET_BMC_EVENT);
-	}
-
-	resp->args[0] = mbox_api_version;
-	if (context->version == API_VERSION_1) {
-		put_u16(&resp->args[1], context->windows.default_size >>
-					context->block_size_shift);
-		put_u16(&resp->args[3], context->windows.default_size >>
-					context->block_size_shift);
-	}
-	if (context->version >= API_VERSION_2) {
-		resp->args[5] = context->block_size_shift;
-		put_u16(&resp->args[6], get_suggested_timeout(context));
+	resp->args[0] = io.resp.api_version;
+	if (io.resp.api_version == API_VERSION_1) {
+		put_u16(&resp->args[1], io.resp.v1.read_window_size);
+		put_u16(&resp->args[3], io.resp.v1.write_window_size);
+	} else if (io.resp.api_version >= API_VERSION_2) {
+		resp->args[5] = io.resp.v2.block_size_shift;
+		put_u16(&resp->args[6], io.resp.v2.timeout);
 	}
 
 	return 0;
diff --git a/vpnor/test/Makefile.am.include b/vpnor/test/Makefile.am.include
index 91666fd..2444606 100644
--- a/vpnor/test/Makefile.am.include
+++ b/vpnor/test/Makefile.am.include
@@ -5,6 +5,7 @@
 
 TEST_MBOX_VPNOR_INTEG_SRCS = \
 	common.c \
+	protocol.c \
 	transport_mbox.c \
 	windows.c \
 	lpc.c \