Unfork phosphor-mboxd from mboxbridge

Change-Id: I1b3b9d7fd47719594c1de027389959a5a9a3ea7a
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/.clang-format-c b/.clang-format-c
new file mode 100644
index 0000000..a3ef233
--- /dev/null
+++ b/.clang-format-c
@@ -0,0 +1,6 @@
+BasedOnStyle: LLVM
+IndentWidth: 8
+UseTab: Always
+BreakBeforeBraces: Linux
+AllowShortIfStatementsOnASingleLine: false
+IndentCaseLabels: false
diff --git a/.clang-format-c++ b/.clang-format-c++
new file mode 100644
index 0000000..309a9d6
--- /dev/null
+++ b/.clang-format-c++
@@ -0,0 +1,85 @@
+---
+Language:        Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands:   true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterClass:      true
+  AfterControlStatement: true
+  AfterEnum:       true
+  AfterFunction:   true
+  AfterNamespace:  true
+  AfterObjCDeclaration: true
+  AfterStruct:     true
+  AfterUnion:      true
+  BeforeCatch:     true
+  BeforeElse:      true
+  IndentBraces:    false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit:     80
+CommentPragmas:  '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: true
+PointerAlignment: Left
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IndentCaseLabels: true
+IndentWidth:     4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+ReflowComments:  true
+SortIncludes:    false
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard:        Cpp11
+TabWidth:        4
+UseTab:          Never
+...
+
diff --git a/.gitignore b/.gitignore
index 9523617..4b8eb95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@
 *.swp
 /libtool
 /*.o
+/vpnor/*.o
 /config.h
 /config.h.in~
 /config.log
@@ -47,10 +48,11 @@
 Makefile
 .deps
 arm-openbmc-linux-gnueabi-libtool
-mboxd
+/mboxd
 mboxctl
-cscope.out
+/cscope.*
 /test/*.o
+/vpnor/test/*.o
 
 # Autotools test infrastructure
 *.log
diff --git a/Documentation/mbox_protocol.md b/Documentation/mbox_protocol.md
index 797ac7b..b60f336 100644
--- a/Documentation/mbox_protocol.md
+++ b/Documentation/mbox_protocol.md
@@ -1,4 +1,4 @@
-Copyright 2017 IBM
+Copyright 2017,2018 IBM
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/Documentation/mboxctl.md b/Documentation/mboxctl.md
index 49bf90e..27e07d6 100644
--- a/Documentation/mboxctl.md
+++ b/Documentation/mboxctl.md
@@ -1,110 +1,3 @@
-Copyright 2017 IBM
+Please reference the canonical mbox protocol documentation:
 
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-## Intro
-
-This document describes the reference mailbox control program contained in this
-repository.
-
-The mailbox control program is a program which can be used to generate dbus
-messages to control the operation of the mailbox daemon.
-
-## Files
-
-The mailbox control program is implemented entirely in the mboxctl.c file.
-
-## Operation
-
-### Invocation
-
-The mailbox control program is invoked with a command and any arguments which
-that command takes.
-
-### Sending Command
-
-The appropriate dbus message is then generated and sent on the dbus.
-
-### Receiving Commands
-
-After sending a command mboxctl then waits for a response from the daemon on
-the dbus and processes the response.
-
-A message is printed to convey the response provided by the daemon. It mboxctl
-is run in silent mode then no output is generated and the exit code reflects
-the response.
-
-## DBUS Protocol
-
-### Commands
-
-```
-0x00: Ping		- Ping the daemon
-			- Args: NONE
-			- Resp: NONE
-0x01: Daemon State	- Get the daemon status
-			- Args: NONE
-			- Resp[0]: Daemon Status:
-				0x00 - Active
-				0x01 - Suspended
-0x02: Reset		- Reset the daemon (same as the reset mbox command)
-			- Args: NONE
-			- Resp: NONE
-0x03: Suspend		- Suspend the daemon
-				- Allow the BMC to manage concurrent flash
-				  access
-				- The daemon will return BUSY to mbox window
-				  commands
-				- Will return Success if daemon successfully
-				  of already suspended
-			- Args: NONE
-			- Resp: NONE
-0x04: Resume		- Resume the daemon
-				- Will return Sucess if daemon successfully
-				  or already resumed
-			- Args[0]: Flash Modified:
-					"clean" - Not Modified (daemon won't
-						  clear its cache)
-					"modified" - Modified (daemon will clear
-						     its cache)
-			- Resp: NONE
-0x05: Clear Cache	- Tell the daemon its data source has been modified
-				- Causes the daemon to clear its cache
-			- Args: NONE
-			- Resp: NONE
-0x06: Kill		- Terminates the daemon
-			- Args: NONE
-			- Resp: NONE
-0x07: LPC State		- Query the state of the lpc mapping
-			- Args: NONE
-			- Resp[0]: LPC Bus Mapping State:
-					0x00 - Invalid (implies internal daemon
-					       error)
-					0x01 - Flash (LPC bus maps flash)
-					0x02 - Memory (LPC bus maps reserved
-					       memory)
-```
-
-### Return Values
-
-```
-0x00: Success	- Command succeeded
-0x01: Internal	- Internal DBUS Error
-0x02: Invalid	- Invalid command or parameters
-0x03: Rejected	- Daemon rejected the request
-			- If this occurs on a suspend command then the BMC must
-			  not access the flash device until a suspend command
-			  succeeds
-0x04: Hardware	- BMC Hardware Error
-0x05: Memory	- Memory Allocation Failed
-```
+    https://github.com/openbmc/mboxbridge/blob/master/Documentation/mboxctl.md
diff --git a/Documentation/mboxd.md b/Documentation/mboxd.md
index a1f4227..1f3bef1 100644
--- a/Documentation/mboxd.md
+++ b/Documentation/mboxd.md
@@ -1,4 +1,4 @@
-Copyright 2017 IBM
+Copyright 2017,2018 IBM
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/MAINTAINERS b/MAINTAINERS
index e5e2887..91a6c39 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -44,3 +44,5 @@
 
 M:  Andrew Jeffery <andrew@aj.id.au> <amboar!>
 R:  Suraj Jitindar Singh <sjitindarsingh@gmail.com>
+R:  Adriana Kobylak <anoo@us.ibm.com> <anoo!>
+R:  Deepak Kodihalli <dkodihal@in.ibm.com> <dkodihal!>
diff --git a/Makefile.am b/Makefile.am
index 5a26916..abaa980 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,126 +1,39 @@
 ACLOCAL_AMFLAGS = -I m4
 sbin_PROGRAMS = mboxd mboxctl
 
-mboxd_SOURCES = mboxd.c common.c mboxd_dbus.c mboxd_flash.c mboxd_lpc.c mboxd_msg.c mboxd_windows.c mtd.c
+mboxd_SOURCES = \
+	mboxd.c \
+	common.c \
+	mboxd_dbus.c \
+	mboxd_lpc.c \
+	mboxd_msg.c \
+	mboxd_windows.c \
+	mtd.c
 mboxd_LDFLAGS = $(LIBSYSTEMD_LIBS)
 mboxd_CFLAGS = $(LIBSYSTEMD_CFLAGS)
 
+if VIRTUAL_PNOR_ENABLED
+include vpnor/Makefile.am.include
+else
+mboxd_SOURCES += mboxd_flash.c \
+	mboxd_lpc_reset.c
+endif
+
 mboxctl_SOURCES = mboxctl.c
 mboxctl_LDFLAGS = $(LIBSYSTEMD_LIBS)
 mboxctl_CFLAGS = $(LIBSYSTEMD_CFLAGS)
 
 @CODE_COVERAGE_RULES@
 
+check_PROGRAMS =
+XFAIL_TESTS =
+
 AM_LIBS = $(CODE_COVERAGE_LIBS)
-AM_CPPFLAGS = $(CODE_COVERAGE_CPPFLAGS)
+AM_CPPFLAGS = $(CODE_COVERAGE_CPPFLAGS) -UNDEBUG
 AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
+AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
 
-test_sanity_SOURCES = test/sanity.c
-
-test_copy_flash_SOURCES = test/copy_flash.c mboxd_flash.c common.c mtd.c test/tmpf.c
-
-test_erase_flash_SOURCES = test/erase_flash.c mboxd_flash.c common.c test/tmpf.c
-
-test_write_flash_SOURCES = test/write_flash.c mboxd_flash.c common.c test/tmpf.c
-
-TEST_MBOX_SRCS = mboxd_msg.c mboxd_windows.c mboxd_lpc.c mboxd_flash.c common.c
-TEST_MOCK_SRCS = test/tmpf.c test/mbox.c test/system.c
-
-test_get_mbox_info_v2_SOURCES = test/get_mbox_info_v2.c \
-				$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_reset_state_SOURCES = test/reset_state.c \
-			   $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_get_flash_info_v2_SOURCES = test/get_flash_info_v2.c \
-				 $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_create_read_window_v2_SOURCES = test/create_read_window_v2.c \
-				     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_create_write_window_v2_SOURCES = test/create_write_window_v2.c \
-				      $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_close_window_v2_SOURCES = test/close_window_v2.c \
-			       $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_mark_write_dirty_v2_SOURCES = test/mark_write_dirty_v2.c \
-				   $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_write_flush_v2_SOURCES = test/write_flush_v2.c \
-			      $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_mark_write_erased_v2_SOURCES = test/mark_write_erased_v2.c \
-				    $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_bmc_event_ack_v2_SOURCES = test/bmc_event_ack_v2.c \
-				$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_create_oversize_window_SOURCES = test/create_oversize_window.c \
-				      $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_create_zero_size_window_SOURCES = test/create_zero_size_window.c \
-				       $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_implicit_flush_SOURCES = test/implicit_flush.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_request_high_version_SOURCES = test/request_high_version.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_request_low_version_SOURCES = test/request_low_version.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_mark_read_dirty_SOURCES = test/mark_read_dirty.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_read_window_write_flush_SOURCES = test/read_window_write_flush.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_read_window_mark_write_erased_SOURCES = test/read_window_mark_write_erased.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_write_window_dirty_erase_SOURCES = test/write_window_dirty_erase.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_invalid_command_SOURCES = test/invalid_command.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_read_window_cycle_SOURCES = test/read_window_cycle.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_sequence_numbers_SOURCES = test/sequence_numbers.c \
-			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-test_get_mbox_info_v2_timeout_SOURCES = test/get_mbox_info_v2_timeout.c \
-					$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
-
-check_PROGRAMS = test/sanity \
-		 test/copy_flash \
-		 test/erase_flash \
-		 test/write_flash \
-		 test/get_mbox_info_v2 \
-		 test/reset_state \
-		 test/get_flash_info_v2 \
-		 test/create_read_window_v2 \
-		 test/create_write_window_v2 \
-		 test/close_window_v2 \
-		 test/mark_write_dirty_v2 \
-		 test/write_flush_v2 \
-		 test/mark_write_erased_v2 \
-		 test/bmc_event_ack_v2 \
-		 test/create_oversize_window \
-		 test/create_zero_size_window \
-		 test/implicit_flush \
-		 test/request_high_version \
-		 test/request_low_version \
-		 test/mark_read_dirty \
-		 test/read_window_write_flush \
-		 test/read_window_mark_write_erased \
-		 test/write_window_dirty_erase \
-		 test/invalid_command \
-		 test/read_window_cycle \
-		 test/sequence_numbers \
-		 test/get_mbox_info_v2_timeout
+include test/Makefile.am.include
+include vpnor/test/Makefile.am.include
 
 TESTS = $(check_PROGRAMS)
diff --git a/README.md b/README.md
index 4293bbd..c986e0f 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,8 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 
-## MBOX
+MBOX
+====
 
 This repo contains the protocol definition for the host to BMC mailbox
 communication specification which can be found in
@@ -23,3 +24,96 @@
 
 Finally there is also an implementation of a mailbox daemon control program, the
 details of which can be found in Documentation/mboxctl.md.
+
+Building
+========
+
+The build system is a standard autotools setup. `bootstrap.sh` runs all the
+jobs necessary to initialise autotools.
+
+By default mboxd is configured and built _without_ the 'virtual PNOR' feature
+discussed below. The virtual PNOR functionality is written in C++, and due to
+some autotools clunkiness even if it is disabled mboxd will still be linked
+with `CXX`. Point `CXX` to `cc` at configure time if you do not have a C++
+compiler for your target (`./configure CXX=cc`).
+
+If you are hacking on the reference implementation it's recommended to run
+`bootstrap.sh` with the `dev` argument:
+
+```
+$ ./bootstrap.sh dev
+$ ./configure
+$ make
+$ make check
+```
+
+This will turn on several of the compiler's sanitizers to help find bad memory
+management and undefined behaviour in the code via the test suites.
+
+Otherwise, build with:
+
+```
+$ ./bootstrap.sh
+$ ./configure
+$ make
+$ make check
+```
+
+In addition to its role as a flash abstraction `mboxd` can also serve as a
+partition/filesystem abstraction. This feature is known as 'virtual PNOR' and
+it can be enabled at `configure` time (note that this requires a C++ compiler
+for your target):
+
+```
+$ ./bootstrap.sh
+$ ./configure --enable-virtual-pnor
+$ make
+$ make check
+```
+
+Style Guide
+===========
+
+Preamble
+--------
+
+This codebase is a mix of C (due to its heritage) and C++. This is an ugly
+split: message logging and error handling can be vastly different inside the
+same codebase. The aim is to remove the split one way or the other over time
+and have consistent approaches to solving problems.
+
+phosphor-mboxd is developed as part of the OpenBMC project, which also leads to
+integration of frameworks such as
+[phosphor-logging](https://github.com/openbmc/phosphor-logging). Specifically
+on phosphor-logging, it's noted that without care we can achieve absurd
+duplication or irritating splits in where errors are reported, as the C code is
+not capable of making use of the interfaces provided.
+
+Rules
+-----
+
+1. Message logging MUST be done to stdout or stderr, and MUST NOT be done
+   directly via journal APIs or wrappers of the journal APIs.
+
+   Rationale:
+
+   We have two scenarios where we care about output, with the important
+   restriction that the method must be consistent between C and C++:
+
+   1. Running in-context on an OpenBMC-based system
+   2. Running the test suite
+
+   In the first case it is desirable that the messages appear in the system
+   journal. To this end, systemd will by default capture stdout and stderr of
+   the launched binary and redirect it to the journal.
+
+   In the second case it is *desirable* that messages be captured by the test
+   runner (`make check`) for test failure analysis, and it is *undesirable* for
+   messages to appear in the system journal (as these are tests, not issues
+   affecting the health of the system they are being executed on).
+
+   Therefore direct calls to the journal MUST be avoided for the purpose of
+   message logging.
+
+   Note: This section specifically targets the use of phosphor-logging's
+   `log<T>()`. It does not prevent the use of `elog<T>()`.
diff --git a/bootstrap.sh b/bootstrap.sh
index eb8056f..2abd0f4 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -55,7 +55,6 @@
     dev)
         FLAGS="-fsanitize=address -fsanitize=leak -fsanitize=undefined -Wall -Werror"
         ./configure \
-            CPPFLAGS="-UNDEBUG" \
             CFLAGS="${FLAGS}" \
             CXXFLAGS="${FLAGS}" \
             --enable-code-coverage \
diff --git a/common.c b/common.c
index c9d5d85..c0497fd 100644
--- a/common.c
+++ b/common.c
@@ -1,19 +1,5 @@
-/* Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *	Unless required by applicable law or agreed to in writing, software
- *	distributed under the License is distributed on an "AS IS" BASIS,
- *	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *	See the License for the specific language governing permissions and
- *	limitations under the License.
- *
- */
-
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 #define _GNU_SOURCE
 #include <stdarg.h>
 #include <stdbool.h>
@@ -26,6 +12,10 @@
 
 #include "common.h"
 
+void (*mbox_vlog)(int p, const char *fmt, va_list args);
+
+enum verbose verbosity;
+
 void mbox_log_console(int p, const char *fmt, va_list args)
 {
 	struct timespec time;
@@ -41,8 +31,19 @@
 __attribute__((format(printf, 2, 3)))
 void mbox_log(int p, const char *fmt, ...)
 {
+	static bool warned = false;
 	va_list args;
 
+	if (!mbox_vlog) {
+		if (!warned) {
+			fprintf(stderr, "Logging backend not configured, "
+					"log output disabled\n");
+			warned = true;
+		}
+
+		return;
+	}
+
 	va_start(args, fmt);
 	mbox_vlog(p, fmt, args);
 	va_end(args);
@@ -50,21 +51,23 @@
 
 uint16_t get_u16(uint8_t *ptr)
 {
-	return *(uint16_t *)ptr;
+	return le16toh(*(uint16_t *)ptr);
 }
 
 void put_u16(uint8_t *ptr, uint16_t val)
 {
+	val = htole16(val);
 	memcpy(ptr, &val, sizeof(val));
 }
 
 uint32_t get_u32(uint8_t *ptr)
 {
-	return *(uint32_t *)ptr;
+	return le32toh(*(uint32_t *)ptr);
 }
 
 void put_u32(uint8_t *ptr, uint32_t val)
 {
+	val = htole32(val);
 	memcpy(ptr, &val, sizeof(val));
 }
 
diff --git a/common.h b/common.h
index 581cd0c..3e22dd2 100644
--- a/common.h
+++ b/common.h
@@ -1,19 +1,5 @@
-/* Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 #ifndef COMMON_H
 #define COMMON_H
 
@@ -25,24 +11,41 @@
 #define PREFIX ""
 #endif
 
-enum {
-   MBOX_LOG_NONE = 0,
-   MBOX_LOG_INFO = 1,
-   MBOX_LOG_DEBUG = 2
-} verbosity;
+enum verbose {
+	MBOX_LOG_NONE = 0,
+	MBOX_LOG_INFO = 1,
+	MBOX_LOG_DEBUG = 2
+};
+
+extern enum verbose verbosity;
 
 /* Error Messages */
-#define MSG_ERR(f_, ...)	mbox_log(LOG_ERR, f_, ##__VA_ARGS__)
-/* Informational Messages */
-#define MSG_INFO(f_, ...)	do { if (verbosity >= MBOX_LOG_INFO) { \
-					mbox_log(LOG_INFO, f_, ##__VA_ARGS__); \
-				} } while (0)
-/* Debug Messages */
-#define MSG_DBG(f_, ...)	do { if (verbosity >= MBOX_LOG_DEBUG) { \
-					mbox_log(LOG_DEBUG, f_, ##__VA_ARGS__); \
-				} } while(0)
+#define MSG_ERR(f_, ...)						\
+do {									\
+	mbox_log(LOG_ERR, f_, ##__VA_ARGS__);				\
+} while (0)
 
-void (*mbox_vlog)(int p, const char *fmt, va_list args);
+/* Informational Messages */
+#define MSG_INFO(f_, ...) 						\
+do { 									\
+	if (verbosity >= MBOX_LOG_INFO) { 				\
+		mbox_log(LOG_INFO, f_, ##__VA_ARGS__);			\
+	}								\
+} while (0)
+
+/* Debug Messages */
+#define MSG_DBG(f_, ...)						\
+do { 									\
+	if (verbosity >= MBOX_LOG_DEBUG) {				\
+		mbox_log(LOG_DEBUG, f_, ##__VA_ARGS__);			\
+	}								\
+} while(0)
+
+extern void (*mbox_vlog)(int p, const char *fmt, va_list args);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
 
 void mbox_log_console(int p, const char *fmt, va_list args);
 
@@ -98,4 +101,8 @@
 
 char *get_dev_mtd(void);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* COMMON_H */
diff --git a/configure.ac b/configure.ac
index d520a37..e05906f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,6 +8,7 @@
 
 # Checks for programs.
 AC_PROG_CC
+AC_PROG_CXX
 AM_PROG_AR
 AC_PROG_INSTALL
 AC_PROG_MAKE_SET
@@ -20,7 +21,18 @@
 )
 
 # Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_14([noext])
 AX_APPEND_COMPILE_FLAGS([-fpic -Wall], [CFLAGS])
+AX_APPEND_COMPILE_FLAGS([-fpic -Wall], [CXXFLAGS])
+
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,\
+AC_MSG_ERROR(["Requires sdbusplus package."]))
+
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],,\
+AC_MSG_ERROR(["Requires phosphor-logging package."]))
+
+AX_PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [],[phosphor-dbus-interfaces],\
+[], [AC_MSG_ERROR(["phosphor-dbus-interfaces required and not found."])])
 
 # Checks for library functions.
 LT_INIT # Removes 'unrecognized options: --with-libtool-sysroot'
@@ -43,12 +55,31 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
+AC_ARG_ENABLE([virtual-pnor],
+    AS_HELP_STRING([--enable-virtual-pnor], [Turn on virtual pnor])
+)
+AS_IF([test "x$enable_virtual_pnor" == "xyes"],
+    [
+        AM_CONDITIONAL(VIRTUAL_PNOR_ENABLED, true)
+        AX_APPEND_COMPILE_FLAGS([-DVIRTUAL_PNOR_ENABLED], [CXXFLAGS])
+        AX_APPEND_COMPILE_FLAGS([-DVIRTUAL_PNOR_ENABLED], [CFLAGS])
+    ],
+    [
+        AM_CONDITIONAL(VIRTUAL_PNOR_ENABLED, false)
+    ]
+)
+
 AX_CODE_COVERAGE
 
 PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd, , AC_MSG_ERROR([libsytemd not found]))
 AC_SUBST([LIBSYSTEMD_CFLAGS])
 AC_SUBST([LIBSYSTEMD_LIBS])
 
+AC_DEFINE(PARTITION_TOC_FILE, "pnor.toc", [The basename of the PNOR Table of contents file.])
+AC_DEFINE(PARTITION_FILES_RO_LOC, "/var/lib/phosphor-software-manager/pnor/ro", [The path to the directory containing PNOR read only partition files.])
+AC_DEFINE(PARTITION_FILES_RW_LOC, "/var/lib/phosphor-software-manager/pnor/rw", [The path to the directory containing PNOR read write partition files.])
+AC_DEFINE(PARTITION_FILES_PRSV_LOC, "/var/lib/phosphor-software-manager/pnor/prsv", [The path to the directory containing PNOR preserve partition files.])
+AC_DEFINE(PARTITION_FILES_PATCH_LOC, "/usr/local/share/pnor", [The path to the directory containing PNOR patch partition files.])
 # Create configured output
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/dbus.h b/dbus.h
index aa520c2..0be0240 100644
--- a/dbus.h
+++ b/dbus.h
@@ -1,19 +1,5 @@
-/*
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOX_DBUS_H
 #define MBOX_DBUS_H
diff --git a/format-code.sh b/format-code.sh
new file mode 100755
index 0000000..a824ad1
--- /dev/null
+++ b/format-code.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+set -euo
+
+set -x
+
+[ -f .clang-format ] && rm .clang-format
+
+CLANG_FORMAT="$(which clang-format-5.0)"
+
+# phosphor-mboxd is a fork of mboxbridge, the reference mbox daemon
+# implementation. mboxbridge is C written with the style of the Linux kernel.
+#
+# phosphor-mboxd extended the reference in C++, and used the OpenBMC C++ style.
+#
+# To remain compliant with the C++ style guide *and* preserve source
+# compatibility with the upstream reference implementation, use two separate
+# styles.
+#
+# Further, clang-format supports describing styles for multiple languages in
+# the one .clang-format file, but *doesn't* make a distinction between C and
+# C++. So we need two files. It gets worse: the -style parameter doesn't take
+# the path to a configuration file as an argument, you instead specify the
+# literal 'file' and it goes looking for a .clang-format or _clang-format file. 
+# So now we need to symlink different files in place before calling
+# ${CLANG_FORMAT}. Everything is terrible.
+#
+# ln -sf .clang-format-c .clang-format
+# git ls-files | grep '\.[ch]$' | xargs "${CLANG_FORMAT}" -i -style=file
+
+ln -sf .clang-format-c++ .clang-format
+git ls-files | grep '\.[ch]pp$' | xargs "${CLANG_FORMAT}" -i -style=file
+
+rm .clang-format
diff --git a/mbox.h b/mbox.h
index 556994f..19d0a3f 100644
--- a/mbox.h
+++ b/mbox.h
@@ -1,19 +1,5 @@
-/*
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOX_H
 #define MBOX_H
@@ -22,6 +8,7 @@
 #include <systemd/sd-bus.h>
 #include <poll.h>
 #include <stdbool.h>
+#include "vpnor/mboxd_pnor_partition_table.h"
 
 enum api_version {
 	API_VERSION_INVAL	= 0,
@@ -131,6 +118,23 @@
 	struct window_context *window;
 };
 
+struct mbox_msg {
+	uint8_t command;
+	uint8_t seq;
+	uint8_t args[MBOX_ARGS_BYTES];
+	uint8_t response;
+};
+
+union mbox_regs {
+	uint8_t raw[MBOX_REG_BYTES];
+	struct mbox_msg msg;
+};
+
+struct mbox_context;
+
+typedef int (*mboxd_mbox_handler)(struct mbox_context *, union mbox_regs *,
+				  struct mbox_msg *);
+
 struct mbox_context {
 /* System State */
 	enum mbox_state state;
@@ -141,6 +145,9 @@
 	uint8_t bmc_events;
 	uint8_t prev_seq;
 
+/* Command Dispatch */
+	const mboxd_mbox_handler *handlers;
+
 /* Window State */
 	/* The window list struct containing all current "windows" */
 	struct window_list windows;
@@ -166,6 +173,11 @@
 	uint32_t block_size_shift;
 	/* Actual Flash Info */
 	struct mtd_info_user mtd_info;
+#ifdef VIRTUAL_PNOR_ENABLED
+	/* Virtual PNOR partition table */
+	struct vpnor_partition_table *vpnor;
+	struct vpnor_partition_paths paths;
+#endif
 };
 
 #endif /* MBOX_H */
diff --git a/mboxctl.c b/mboxctl.c
index 6e8bf15..e64a9c7 100644
--- a/mboxctl.c
+++ b/mboxctl.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Control Implementation
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <assert.h>
diff --git a/mboxd.c b/mboxd.c
index 5f851df..070c8bc 100644
--- a/mboxd.c
+++ b/mboxd.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon Implementation
- *
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <assert.h>
@@ -51,6 +35,7 @@
 #include "mboxd_lpc.h"
 #include "mboxd_msg.h"
 #include "mboxd_windows.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
 
 #define USAGE \
 "\nUsage: %s [-V | --version] [-h | --help] [-v[v] | --verbose] [-s | --syslog]\n" \
@@ -103,7 +88,7 @@
 			case SIGHUP:
 				/* Host didn't request reset -> Notify it */
 				reset_all_windows(context, SET_BMC_EVENT);
-				rc = point_to_flash(context);
+				rc = reset_lpc(context);
 				if (rc < 0) {
 					MSG_ERR("WARNING: Failed to point the "
 						"LPC bus back to flash on "
@@ -138,10 +123,10 @@
 		}
 	}
 
-	/* Best to reset windows and point back to flash for safety */
+	/* Best to reset windows and the lpc mapping for safety */
 	/* Host didn't request reset -> Notify it */
 	reset_all_windows(context, SET_BMC_EVENT);
-	rc = point_to_flash(context);
+	rc = reset_lpc(context);
 	/* Not much we can do if this fails */
 	if (rc < 0) {
 		MSG_ERR("WARNING: Failed to point the LPC bus back to flash\n"
@@ -342,10 +327,14 @@
 		goto finish;
 	}
 
-	/* Set the LPC bus mapping to point to the physical flash device */
-	rc = point_to_flash(context);
+#ifdef VIRTUAL_PNOR_ENABLED
+	init_vpnor(context);
+#endif
+
+	/* Set the LPC bus mapping */
+	rc = reset_lpc(context);
 	if (rc) {
-		goto finish;
+		MSG_ERR("LPC configuration failed, RESET required: %d\n", rc);
 	}
 
 	rc = set_bmc_events(context, BMC_EVENT_DAEMON_READY, SET_BMC_EVENT);
@@ -367,6 +356,9 @@
 	free_lpc_dev(context);
 	free_mbox_dev(context);
 	free_windows(context);
+#ifdef VIRTUAL_PNOR_ENABLED
+	destroy_vpnor(context);
+#endif
 	free(context);
 
 	return rc;
diff --git a/mboxd_dbus.c b/mboxd_dbus.c
index 6493ccf..b1dc9ad 100644
--- a/mboxd_dbus.c
+++ b/mboxd_dbus.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon DBUS Helpers
- *
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <assert.h>
@@ -133,12 +117,12 @@
 	}
 
 	/*
-	 * This will close (and flush) the current window and point the lpc bus
-	 * mapping back to flash. Better set the bmc event to notify the host
-	 * of this.
+	 * This will close (and flush) the current window and reset the lpc bus
+	 * mapping back to flash, or memory in case we're using a virtual pnor.
+	 * Better set the bmc event to notify the host of this.
 	 */
 	reset_all_windows(context, SET_BMC_EVENT);
-	rc = point_to_flash(context);
+	rc = reset_lpc(context);
 	if (rc < 0) {
 		return -E_DBUS_HARDWARE;
 	}
diff --git a/mboxd_dbus.h b/mboxd_dbus.h
index 8dbac5e..beb5790 100644
--- a/mboxd_dbus.h
+++ b/mboxd_dbus.h
@@ -1,19 +1,5 @@
-/*
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOXD_DBUS_H
 #define MBOXD_DBUS_H
diff --git a/mboxd_flash.c b/mboxd_flash.c
index 1e1b7a0..5cd5868 100644
--- a/mboxd_flash.c
+++ b/mboxd_flash.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon Flash Helpers
- *
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <assert.h>
@@ -39,6 +23,7 @@
 #include <time.h>
 #include <unistd.h>
 #include <inttypes.h>
+#include <errno.h>
 #include <mtd/mtd-abi.h>
 
 #include "mbox.h"
@@ -120,46 +105,6 @@
 
 /* Flash Functions */
 
-#define CHUNKSIZE (64 * 1024)
-
-/*
- * copy_flash() - Copy data from the flash device into a provided buffer
- * @context:	The mbox context pointer
- * @offset:	The flash offset to copy from (bytes)
- * @mem:	The buffer to copy into (must be of atleast size)
- * @size:	The number of bytes to copy
- *
- * Return:	0 on success otherwise negative error code
- */
-int copy_flash(struct mbox_context *context, uint32_t offset, void *mem,
-	       uint32_t size)
-{
-	int32_t size_read;
-
-	MSG_DBG("Copy flash to %p for size 0x%.8x from offset 0x%.8x\n",
-		mem, size, offset);
-	if (lseek(context->fds[MTD_FD].fd, offset, SEEK_SET) != offset) {
-		MSG_ERR("Couldn't seek flash at pos: %u %s\n", offset,
-			strerror(errno));
-		return -MBOX_R_SYSTEM_ERROR;
-	}
-
-	do {
-		size_read = read(context->fds[MTD_FD].fd, mem,
-					  min_u32(CHUNKSIZE, size));
-		if (size_read < 0) {
-			MSG_ERR("Couldn't copy mtd into ram: %s\n",
-				strerror(errno));
-			return -MBOX_R_SYSTEM_ERROR;
-		}
-
-		size -= size_read;
-		mem += size_read;
-	} while (size && size_read);
-
-	return size ? -MBOX_R_SYSTEM_ERROR : 0;
-}
-
 /*
  * flash_is_erased() - Check if an offset into flash is erased
  * @context:	The mbox context pointer
@@ -271,6 +216,48 @@
 	return 0;
 }
 
+#define CHUNKSIZE (64 * 1024)
+
+/*
+ * copy_flash() - Copy data from the flash device into a provided buffer
+ * @context:	The mbox context pointer
+ * @offset:	The flash offset to copy from (bytes)
+ * @mem:	The buffer to copy into (must be of atleast 'size' bytes)
+ * @size:	The number of bytes to copy
+ * Return:	Number of bytes copied on success, otherwise negative error
+ *		code. copy_flash will copy at most 'size' bytes, but it may
+ *		copy less.
+ */
+int64_t copy_flash(struct mbox_context *context, uint32_t offset, void *mem,
+		   uint32_t size)
+{
+	int32_t size_read;
+	void *start = mem;
+
+	MSG_DBG("Copy flash to %p for size 0x%.8x from offset 0x%.8x\n",
+		mem, size, offset);
+	if (lseek(context->fds[MTD_FD].fd, offset, SEEK_SET) != offset) {
+		MSG_ERR("Couldn't seek flash at pos: %u %s\n", offset,
+			strerror(errno));
+		return -MBOX_R_SYSTEM_ERROR;
+	}
+
+	do {
+		size_read = read(context->fds[MTD_FD].fd, mem,
+					  min_u32(CHUNKSIZE, size));
+		if (size_read < 0) {
+			MSG_ERR("Couldn't copy mtd into ram: %s\n",
+				strerror(errno));
+			return -MBOX_R_SYSTEM_ERROR;
+		}
+
+		size -= size_read;
+		mem += size_read;
+	} while (size && size_read);
+
+	return size_read ? mem - start : -MBOX_R_SYSTEM_ERROR;
+}
+
 /*
  * write_flash() - Write the flash from a provided buffer
  * @context:	The mbox context pointer
diff --git a/mboxd_flash.h b/mboxd_flash.h
index af9757b..3cfaef9 100644
--- a/mboxd_flash.h
+++ b/mboxd_flash.h
@@ -1,19 +1,5 @@
-/*
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOXD_FLASH_H
 #define MBOXD_FLASH_H
@@ -23,14 +9,21 @@
 
 #include "mbox.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 int init_flash_dev(struct mbox_context *context);
 void free_flash_dev(struct mbox_context *context);
-int copy_flash(struct mbox_context *context, uint32_t offset, void *mem,
-	       uint32_t size);
+int64_t copy_flash(struct mbox_context *context, uint32_t offset, void *mem,
+		   uint32_t size);
 int set_flash_bytemap(struct mbox_context *context, uint32_t offset,
 		      uint32_t count, uint8_t val);
 int erase_flash(struct mbox_context *context, uint32_t offset, uint32_t count);
 int write_flash(struct mbox_context *context, uint32_t offset, void *buf,
 		uint32_t count);
 
+#ifdef __cplusplus
+}
+#endif
 #endif /* MBOXD_FLASH_H */
diff --git a/mboxd_lpc.c b/mboxd_lpc.c
index 0529b14..a9c686d 100644
--- a/mboxd_lpc.c
+++ b/mboxd_lpc.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon LPC Helpers
- *
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <assert.h>
diff --git a/mboxd_lpc.h b/mboxd_lpc.h
index bb288a9..bd3e2b1 100644
--- a/mboxd_lpc.h
+++ b/mboxd_lpc.h
@@ -1,26 +1,21 @@
-/*
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOXD_LPC_H
 #define MBOXD_LPC_H
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 int init_lpc_dev(struct mbox_context *context);
 void free_lpc_dev(struct mbox_context *context);
 int point_to_flash(struct mbox_context *context);
 int point_to_memory(struct mbox_context *context);
+int reset_lpc(struct mbox_context *context);
+
+#ifdef __cplusplus
+}
+#endif
 
 #endif /* MBOXD_LPC_H */
diff --git a/mboxd_lpc_reset.c b/mboxd_lpc_reset.c
new file mode 100644
index 0000000..5f5dc01
--- /dev/null
+++ b/mboxd_lpc_reset.c
@@ -0,0 +1,34 @@
+/*
+ * Mailbox Daemon LPC Helpers
+ *
+ * Copyright 2017 IBM
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include "mbox.h"
+#include "mboxd_lpc.h"
+
+/*
+ * reset_lpc() - Reset the lpc bus mapping
+ * @context:	The mbox context pointer
+ *
+ * Return:	0 on success otherwise negative error code
+ */
+int reset_lpc(struct mbox_context *context)
+{
+	return point_to_flash(context);
+}
diff --git a/mboxd_msg.c b/mboxd_msg.c
index 3975a20..6bf8918 100644
--- a/mboxd_msg.c
+++ b/mboxd_msg.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon MBOX Message Helpers
- *
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <assert.h>
@@ -46,12 +30,6 @@
 #include "mboxd_windows.h"
 #include "mboxd_lpc.h"
 
-static int mbox_handle_flush_window(struct mbox_context *context, union mbox_regs *req,
-			     struct mbox_msg *resp);
-
-typedef int (*mboxd_mbox_handler)(struct mbox_context *, union mbox_regs *,
-				  struct mbox_msg *);
-
 /*
  * write_bmc_event_reg() - Write to the BMC controlled status register (reg 15)
  * @context:	The mbox context pointer
@@ -138,14 +116,15 @@
 
 /*
  * Command: RESET_STATE
- * Reset the LPC mapping to point back at the flash
+ * Reset the LPC mapping to point back at the flash, or memory in case we're
+ * using a virtual pnor.
  */
-static int mbox_handle_reset(struct mbox_context *context,
+int mbox_handle_reset(struct mbox_context *context,
 			     union mbox_regs *req, struct mbox_msg *resp)
 {
 	/* Host requested it -> No BMC Event */
 	reset_all_windows(context, NO_BMC_EVENT);
-	return point_to_flash(context);
+	return reset_lpc(context);
 }
 
 /*
@@ -189,7 +168,7 @@
  * RESP[3:4]: Default write window size (number of blocks)
  * RESP[5]: Block size (as shift)
  */
-static int mbox_handle_mbox_info(struct mbox_context *context,
+int mbox_handle_mbox_info(struct mbox_context *context,
 				 union mbox_regs *req, struct mbox_msg *resp)
 {
 	uint8_t mbox_api_version = req->msg.args[0];
@@ -268,7 +247,7 @@
  * RESP[0:1]: Flash Size (number of blocks)
  * RESP[2:3]: Erase Size (number of blocks)
  */
-static int mbox_handle_flash_info(struct mbox_context *context,
+int mbox_handle_flash_info(struct mbox_context *context,
 				  union mbox_regs *req, struct mbox_msg *resp)
 {
 	switch (context->version) {
@@ -332,7 +311,7 @@
  * RESP[0:1]: LPC bus address for host to access this window (number of blocks)
  * RESP[2:3]: Actual window size that the host can access (number of blocks)
  */
-static int mbox_handle_read_window(struct mbox_context *context,
+int mbox_handle_read_window(struct mbox_context *context,
 				   union mbox_regs *req, struct mbox_msg *resp)
 {
 	uint32_t flash_offset;
@@ -405,7 +384,7 @@
  * RESP[0:1]: LPC bus address for host to access this window (number of blocks)
  * RESP[2:3]: Actual window size that was mapped/host can access (n.o. blocks)
  */
-static int mbox_handle_write_window(struct mbox_context *context,
+int mbox_handle_write_window(struct mbox_context *context,
 				    union mbox_regs *req, struct mbox_msg *resp)
 {
 	int rc;
@@ -439,7 +418,7 @@
  * ARGS[0:1]: Where within window to start (number of blocks)
  * ARGS[2:3]: Number to mark dirty (number of blocks)
  */
-static int mbox_handle_dirty_window(struct mbox_context *context,
+int mbox_handle_dirty_window(struct mbox_context *context,
 				    union mbox_regs *req, struct mbox_msg *resp)
 {
 	uint32_t offset, size;
@@ -498,7 +477,7 @@
  * ARGS[0:1]: Where within window to start (number of blocks)
  * ARGS[2:3]: Number to erase (number of blocks)
  */
-static int mbox_handle_erase_window(struct mbox_context *context,
+int mbox_handle_erase_window(struct mbox_context *context,
 				    union mbox_regs *req, struct mbox_msg *resp)
 {
 	uint32_t offset, size;
@@ -551,7 +530,7 @@
  * V2:
  * NONE
  */
-static int mbox_handle_flush_window(struct mbox_context *context,
+int mbox_handle_flush_window(struct mbox_context *context,
 				    union mbox_regs *req, struct mbox_msg *resp)
 {
 	int rc, i, offset, count;
@@ -647,7 +626,7 @@
  * V2:
  * ARGS[0]: FLAGS
  */
-static int mbox_handle_close_window(struct mbox_context *context,
+int mbox_handle_close_window(struct mbox_context *context,
 				    union mbox_regs *req, struct mbox_msg *resp)
 {
 	uint8_t flags = 0;
@@ -681,7 +660,7 @@
  *
  * ARGS[0]: Bitmap of bits to ack (by clearing)
  */
-static int mbox_handle_ack(struct mbox_context *context, union mbox_regs *req,
+int mbox_handle_ack(struct mbox_context *context, union mbox_regs *req,
 			   struct mbox_msg *resp)
 {
 	uint8_t bmc_events = req->msg.args[0];
@@ -769,7 +748,8 @@
 		resp.response = -rc;
 	} else {
 		/* Commands start at 1 so we have to subtract 1 from the cmd */
-		rc = mbox_handlers[req->msg.command - 1](context, req, &resp);
+		mboxd_mbox_handler h = context->handlers[req->msg.command - 1];
+		rc = h(context, req, &resp);
 		if (rc < 0) {
 			MSG_ERR("Error handling mbox cmd: %d\n",
 				req->msg.command);
@@ -850,6 +830,8 @@
 {
 	int fd;
 
+	context->handlers = mbox_handlers;
+
 	/* Open MBOX Device */
 	fd = open(path, O_RDWR | O_NONBLOCK);
 	if (fd < 0) {
diff --git a/mboxd_msg.h b/mboxd_msg.h
index 7d81d7a..d911b7b 100644
--- a/mboxd_msg.h
+++ b/mboxd_msg.h
@@ -1,24 +1,11 @@
-/*
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOXD_MSG_H
 #define MBOXD_MSG_H
 
 #include "common.h"
+#include "mbox.h"
 
 /* Estimate as to how long (milliseconds) it takes to access a MB from flash */
 #define FLASH_ACCESS_MS_PER_MB		8000
@@ -26,18 +13,6 @@
 #define NO_BMC_EVENT			false
 #define SET_BMC_EVENT			true
 
-struct mbox_msg {
-	uint8_t command;
-	uint8_t seq;
-	uint8_t args[MBOX_ARGS_BYTES];
-	uint8_t response;
-};
-
-union mbox_regs {
-	char raw[MBOX_REG_BYTES];
-	struct mbox_msg msg;
-};
-
 int set_bmc_events(struct mbox_context *context, uint8_t bmc_event,
 		   bool write_back);
 int clr_bmc_events(struct mbox_context *context, uint8_t bmc_event,
@@ -46,4 +21,26 @@
 int init_mbox_dev(struct mbox_context *context);
 void free_mbox_dev(struct mbox_context *context);
 
+/* Command handlers */
+int mbox_handle_reset(struct mbox_context *context,
+			     union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_mbox_info(struct mbox_context *context,
+				 union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_flash_info(struct mbox_context *context,
+				  union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_read_window(struct mbox_context *context,
+				   union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_close_window(struct mbox_context *context,
+				    union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_write_window(struct mbox_context *context,
+				    union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_dirty_window(struct mbox_context *context,
+				    union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_flush_window(struct mbox_context *context,
+				    union mbox_regs *req, struct mbox_msg *resp);
+int mbox_handle_ack(struct mbox_context *context, union mbox_regs *req,
+			   struct mbox_msg *resp);
+int mbox_handle_erase_window(struct mbox_context *context,
+				    union mbox_regs *req, struct mbox_msg *resp);
+
 #endif /* MBOXD_MSG_H */
diff --git a/mboxd_windows.c b/mboxd_windows.c
index 451ecd1..233bfdd 100644
--- a/mboxd_windows.c
+++ b/mboxd_windows.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon Window Helpers
- *
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <assert.h>
@@ -417,7 +401,6 @@
 		context->current->age = 0;
 	}
 
-	context->current->size = context->windows.default_size;
 	context->current = NULL;
 	context->current_is_write = false;
 }
@@ -585,8 +568,17 @@
 	if (!cur) {
 		MSG_DBG("No uninitialised window, evicting one\n");
 		cur = find_oldest_window(context);
+		reset_window(context, cur);
 	}
 
+/*
+ * In case of the virtual pnor, as of now it's possible that a window may
+ * have content less than it's max size. We basically copy one flash partition
+ * per window, and some partitions are smaller than the max size. An offset
+ * right after such a small partition ends should lead to new mapping. The code
+ * below prevents that.
+ */
+#ifndef VIRTUAL_PNOR_ENABLED
 	if (!exact) {
 		/*
 		 * It would be nice to align the offsets which we map to window
@@ -598,8 +590,12 @@
 		 */
 		offset &= ~(cur->size - 1);
 	}
+#endif
 
-	if ((offset + cur->size) > context->flash_size) {
+	if (offset > context->flash_size) {
+		MSG_ERR("Tried to open read window past flash limit\n");
+		return -MBOX_R_PARAM_ERROR;
+	} else if ((offset + cur->size) > context->flash_size) {
 		/*
 		 * There is V1 skiboot implementations out there which don't
 		 * mask offset with window size, meaning when we have
@@ -614,9 +610,11 @@
 			cur->size = align_down(context->flash_size - offset,
 					       1 << context->block_size_shift);
 		} else {
-			/* Trying to read past the end of flash */
-			MSG_ERR("Tried to open read window past flash limit\n");
-			return -MBOX_R_PARAM_ERROR;
+			/*
+			 * Allow requests to exceed the flash size, but limit
+			 * the response to the size of the flash.
+			 */
+			cur->size = context->flash_size - offset;
 		}
 	}
 
@@ -627,6 +625,15 @@
 		reset_window(context, cur);
 		return rc;
 	}
+	/*
+	 * rc isn't guaranteed to be aligned, so align up
+	 *
+	 * FIXME: This should only be the case for the vpnor ToC now, so handle
+	 * it there
+	 */
+	cur->size = align_up(rc, (1ULL << context->block_size_shift));
+	/* Would like a known value, pick 0xFF to it looks like erased flash */
+	memset(cur->mem + rc, 0xFF, cur->size - rc);
 
 	/*
 	 * Since for V1 windows aren't constrained to start at multiples of
diff --git a/mboxd_windows.h b/mboxd_windows.h
index 925697c..468fd12 100644
--- a/mboxd_windows.h
+++ b/mboxd_windows.h
@@ -1,19 +1,5 @@
-/*
- * Copyright 2016 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOXD_WINDOWS_H
 #define MBOXD_WINDOWS_H
diff --git a/mtd.c b/mtd.c
index 46d660e..348973e 100644
--- a/mtd.c
+++ b/mtd.c
@@ -1,3 +1,5 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 #define _GNU_SOURCE
 #include <stdbool.h>
 #include <stdio.h>
diff --git a/test/Makefile.am.include b/test/Makefile.am.include
new file mode 100644
index 0000000..264d704
--- /dev/null
+++ b/test/Makefile.am.include
@@ -0,0 +1,127 @@
+test_sanity_SOURCES = %reldir%/sanity.c
+
+test_copy_flash_SOURCES = \
+	%reldir%/copy_flash.c \
+	mboxd_flash.c \
+	common.c mtd.c \
+	%reldir%/tmpf.c
+
+test_erase_flash_SOURCES = \
+	%reldir%/erase_flash.c \
+	mboxd_flash.c \
+	common.c \
+	%reldir%/tmpf.c
+
+test_write_flash_SOURCES = \
+	%reldir%/write_flash.c \
+	mboxd_flash.c \
+	common.c \
+	%reldir%/tmpf.c
+
+TEST_MBOX_SRCS = \
+	mboxd_msg.c \
+	mboxd_windows.c \
+	mboxd_lpc.c \
+	mboxd_lpc_reset.c \
+	common.c \
+	mboxd_flash.c
+
+TEST_MOCK_SRCS = %reldir%/tmpf.c %reldir%/mbox.c %reldir%/system.c
+
+test_get_mbox_info_v2_SOURCES = %reldir%/get_mbox_info_v2.c \
+				$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_reset_state_SOURCES = %reldir%/reset_state.c \
+			   $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_get_flash_info_v2_SOURCES = %reldir%/get_flash_info_v2.c \
+				 $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_create_read_window_v2_SOURCES = %reldir%/create_read_window_v2.c \
+				     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_create_write_window_v2_SOURCES = %reldir%/create_write_window_v2.c \
+				      $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_close_window_v2_SOURCES = %reldir%/close_window_v2.c \
+			       $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_mark_write_dirty_v2_SOURCES = %reldir%/mark_write_dirty_v2.c \
+				   $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_write_flush_v2_SOURCES = %reldir%/write_flush_v2.c \
+			      $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_mark_write_erased_v2_SOURCES = %reldir%/mark_write_erased_v2.c \
+				    $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_bmc_event_ack_v2_SOURCES = %reldir%/bmc_event_ack_v2.c \
+				$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_create_oversize_window_SOURCES = %reldir%/create_oversize_window.c \
+				      $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_create_zero_size_window_SOURCES = %reldir%/create_zero_size_window.c \
+				       $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_implicit_flush_SOURCES = %reldir%/implicit_flush.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_request_high_version_SOURCES = %reldir%/request_high_version.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_request_low_version_SOURCES = %reldir%/request_low_version.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_mark_read_dirty_SOURCES = %reldir%/mark_read_dirty.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_read_window_write_flush_SOURCES = %reldir%/read_window_write_flush.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_read_window_mark_write_erased_SOURCES = %reldir%/read_window_mark_write_erased.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_write_window_dirty_erase_SOURCES = %reldir%/write_window_dirty_erase.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_invalid_command_SOURCES = %reldir%/invalid_command.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_read_window_cycle_SOURCES = %reldir%/read_window_cycle.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_sequence_numbers_SOURCES = %reldir%/sequence_numbers.c \
+			     $(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+test_get_mbox_info_v2_timeout_SOURCES = %reldir%/get_mbox_info_v2_timeout.c \
+					$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+
+check_PROGRAMS += \
+		%reldir%/sanity \
+		%reldir%/copy_flash \
+		%reldir%/erase_flash \
+		%reldir%/write_flash \
+		%reldir%/get_mbox_info_v2 \
+		%reldir%/reset_state \
+		%reldir%/get_flash_info_v2 \
+		%reldir%/create_read_window_v2 \
+		%reldir%/create_write_window_v2 \
+		%reldir%/close_window_v2 \
+		%reldir%/mark_write_dirty_v2 \
+		%reldir%/write_flush_v2 \
+		%reldir%/mark_write_erased_v2 \
+		%reldir%/bmc_event_ack_v2 \
+		%reldir%/create_oversize_window \
+		%reldir%/create_zero_size_window \
+		%reldir%/implicit_flush \
+		%reldir%/request_high_version \
+		%reldir%/request_low_version \
+		%reldir%/mark_read_dirty \
+		%reldir%/read_window_write_flush \
+		%reldir%/read_window_mark_write_erased \
+		%reldir%/write_window_dirty_erase \
+		%reldir%/invalid_command \
+		%reldir%/read_window_cycle \
+		%reldir%/sequence_numbers \
+		%reldir%/get_mbox_info_v2_timeout
diff --git a/test/bmc_event_ack_v2.c b/test/bmc_event_ack_v2.c
index 7bfae6d..3d4cffd 100644
--- a/test/bmc_event_ack_v2.c
+++ b/test/bmc_event_ack_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <sys/mman.h>
diff --git a/test/close_window_v2.c b/test/close_window_v2.c
index 40d71ac..0e3dd55 100644
--- a/test/close_window_v2.c
+++ b/test/close_window_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/copy_flash.c b/test/copy_flash.c
index 5c40b53..b8beb21 100644
--- a/test/copy_flash.c
+++ b/test/copy_flash.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <fcntl.h>
@@ -66,7 +50,7 @@
 		goto free;
 	}
 
-	rc = tmpf_init(&tmp, "flashXXXXXX");
+	rc = tmpf_init(&tmp, "flash-store.XXXXXX");
 	if (rc < 0)
 		goto free;
 
diff --git a/test/create_oversize_window.c b/test/create_oversize_window.c
index 713c62b..c60b19c 100644
--- a/test/create_oversize_window.c
+++ b/test/create_oversize_window.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/create_read_window_v2.c b/test/create_read_window_v2.c
index 34d6727..6085f96 100644
--- a/test/create_read_window_v2.c
+++ b/test/create_read_window_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/create_write_window_v2.c b/test/create_write_window_v2.c
index 3682b72..2ec59d6 100644
--- a/test/create_write_window_v2.c
+++ b/test/create_write_window_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/create_zero_size_window.c b/test/create_zero_size_window.c
index d867d46..4f26cd7 100644
--- a/test/create_zero_size_window.c
+++ b/test/create_zero_size_window.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/erase_flash.c b/test/erase_flash.c
index 71e87b5..376b7d5 100644
--- a/test/erase_flash.c
+++ b/test/erase_flash.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <stdarg.h>
@@ -42,7 +26,7 @@
 {
 	int rc;
 
-	rc = tmpf_init(&mtd, "flashXXXXXX");
+	rc = tmpf_init(&mtd, "flash-store.XXXXXX");
 	if (rc < 0)
 		return NULL;
 
diff --git a/test/get_flash_info_v2.c b/test/get_flash_info_v2.c
index 38af007..ccb1473 100644
--- a/test/get_flash_info_v2.c
+++ b/test/get_flash_info_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/get_mbox_info_v2.c b/test/get_mbox_info_v2.c
index 3a86f94..2149737 100644
--- a/test/get_mbox_info_v2.c
+++ b/test/get_mbox_info_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/get_mbox_info_v2_timeout.c b/test/get_mbox_info_v2_timeout.c
index 49cdfb8..8587dfe 100644
--- a/test/get_mbox_info_v2_timeout.c
+++ b/test/get_mbox_info_v2_timeout.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/implicit_flush.c b/test/implicit_flush.c
index 57676b8..a09b034 100644
--- a/test/implicit_flush.c
+++ b/test/implicit_flush.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <sys/mman.h>
diff --git a/test/invalid_command.c b/test/invalid_command.c
index 80ee9e2..d277551 100644
--- a/test/invalid_command.c
+++ b/test/invalid_command.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/mark_read_dirty.c b/test/mark_read_dirty.c
index 9be35a4..36e14bd 100644
--- a/test/mark_read_dirty.c
+++ b/test/mark_read_dirty.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/mark_write_dirty_v2.c b/test/mark_write_dirty_v2.c
index 9993667..2ea12ae 100644
--- a/test/mark_write_dirty_v2.c
+++ b/test/mark_write_dirty_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/mark_write_erased_v2.c b/test/mark_write_erased_v2.c
index 46027e6..09efc2a 100644
--- a/test/mark_write_erased_v2.c
+++ b/test/mark_write_erased_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <sys/mman.h>
diff --git a/test/mbox.c b/test/mbox.c
index dc049ce..3daf493 100644
--- a/test/mbox.c
+++ b/test/mbox.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE /* fallocate */
 #include <assert.h>
@@ -39,8 +23,9 @@
 
 #define STEP 16
 
-void dump_buf(const uint8_t *buf, size_t len)
+void dump_buf(const void *buf, size_t len)
 {
+	const uint8_t *buf8 = buf;
 	int i;
 
 	for (i = 0; i < len; i += STEP) {
@@ -53,7 +38,7 @@
 
 		printf("0x%08x:\t", i);
 		for (j = 0; j < max; j++)
-			printf("0x%02x, ", buf[i + j]);
+			printf("0x%02x, ", buf8[i + j]);
 
 		printf("\n");
 	}
@@ -102,6 +87,24 @@
 	return rc;
 }
 
+void mbox_rspcpy(struct mbox_context *context, struct mbox_msg *msg)
+{
+	struct stat details;
+	uint8_t *map;
+	int fd;
+
+	fd = context->fds[MBOX_FD].fd;
+	fstat(fd, &details);
+
+	map = mmap(NULL, details.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	assert(map != MAP_FAILED);
+	assert(details.st_size >= (RESPONSE_OFFSET + RESPONSE_SIZE));
+
+	memcpy(msg, &map[RESPONSE_OFFSET], RESPONSE_SIZE);
+
+	munmap(map, details.st_size);
+}
+
 int mbox_command_write(struct mbox_context *context, const uint8_t *command,
 		size_t len)
 {
@@ -216,22 +219,27 @@
 
 	atexit(cleanup);
 
-	rc = tmpf_init(&test.mbox, "mboxXXXXXX");
+	rc = tmpf_init(&test.mbox, "mbox-store.XXXXXX");
 	assert(rc == 0);
 
-	rc = tmpf_init(&test.flash, "flashXXXXXX");
+	rc = tmpf_init(&test.flash, "flash-store.XXXXXX");
 	assert(rc == 0);
 
-	rc = tmpf_init(&test.lpc, "lpcXXXXXX");
+	rc = tmpf_init(&test.lpc, "lpc-store.XXXXXX");
 	assert(rc == 0);
 
 	test.context.windows.num = n_windows;
 	test.context.windows.default_size = len;
 
 	/*
-	 * We need to control MBOX_FD, so don't call __init_mbox_dev().
-	 * Instead, insert our temporary file's fd directly into the context
+	 * We need to call __init_mbox_dev() to initialise the handler table.
+	 * However, afterwards we need to discard the fd of the clearly useless
+	 * /dev/null and replace it with our own fd for mbox device emulation
+	 * by the test framework.
 	 */
+	__init_mbox_dev(&test.context, "/dev/null");
+	rc = close(test.context.fds[MBOX_FD].fd);
+	assert(rc == 0);
 	test.context.fds[MBOX_FD].fd = test.mbox.fd;
 
 	rc = init_flash_dev(&test.context);
@@ -240,10 +248,10 @@
 	rc = fallocate(test.flash.fd, 0, 0, test.context.mtd_info.size);
 	assert(rc == 0);
 
-	rc = fallocate(test.lpc.fd, 0, 0, test.context.mtd_info.size);
+	rc = __init_lpc_dev(&test.context, test.lpc.path);
 	assert(rc == 0);
 
-	rc = __init_lpc_dev(&test.context, test.lpc.path);
+	rc = fallocate(test.lpc.fd, 0, 0, test.context.mem_size);
 	assert(rc == 0);
 
 	rc = init_windows(&test.context);
diff --git a/test/mbox.h b/test/mbox.h
index 0eef28c..4344622 100644
--- a/test/mbox.h
+++ b/test/mbox.h
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef TEST_MBOX_H
 #define TEST_MBOX_H
@@ -23,8 +7,9 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include "../common.h"
-#include "../mbox.h"
+#include "common.h"
+#include "mbox.h"
+#include "mboxd_msg.h"
 
 #include "tmpf.h"
 
@@ -35,6 +20,8 @@
 
 void mbox_dump(struct mbox_context *context);
 
+void mbox_rspcpy(struct mbox_context *context, struct mbox_msg *msg);
+
 int mbox_cmp(struct mbox_context *context, const uint8_t *expected, size_t len);
 
 int mbox_command_write(struct mbox_context *context, const uint8_t *command,
@@ -44,6 +31,6 @@
 	size_t len);
 
 /* Helpers */
-void dump_buf(const uint8_t *buf, size_t len);
+void dump_buf(const void *buf, size_t len);
 
 #endif /* TEST_MBOX_H */
diff --git a/test/read_window_cycle.c b/test/read_window_cycle.c
index e1d1b48..c2f8920 100644
--- a/test/read_window_cycle.c
+++ b/test/read_window_cycle.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/read_window_mark_write_erased.c b/test/read_window_mark_write_erased.c
index 49cb4b4..7daf9a1 100644
--- a/test/read_window_mark_write_erased.c
+++ b/test/read_window_mark_write_erased.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/read_window_write_flush.c b/test/read_window_write_flush.c
index 357a132..01bbba4 100644
--- a/test/read_window_write_flush.c
+++ b/test/read_window_write_flush.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/request_high_version.c b/test/request_high_version.c
index 679211c..b85e63f 100644
--- a/test/request_high_version.c
+++ b/test/request_high_version.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/request_low_version.c b/test/request_low_version.c
index 9f26e72..5f003f4 100644
--- a/test/request_low_version.c
+++ b/test/request_low_version.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/reset_state.c b/test/reset_state.c
index 3b2c109..b110f44 100644
--- a/test/reset_state.c
+++ b/test/reset_state.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/sanity.c b/test/sanity.c
index b4989f8..3c573ec 100644
--- a/test/sanity.c
+++ b/test/sanity.c
@@ -1,3 +1,5 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 int main(void)
 {
 #ifdef NDEBUG
diff --git a/test/sequence_numbers.c b/test/sequence_numbers.c
index be95d87..3d7d6e9 100644
--- a/test/sequence_numbers.c
+++ b/test/sequence_numbers.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 
diff --git a/test/system.c b/test/system.c
index 3b5a9bd..24fbcdc 100644
--- a/test/system.c
+++ b/test/system.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <linux/types.h>
 #include <mtd/mtd-user.h>
@@ -90,7 +74,7 @@
 
 		va_start(ap, request);
 		info = va_arg(ap, struct aspeed_lpc_ctrl_mapping *);
-		info->size = mtd.size;
+		info->size = ctrl.size;
 		va_end(ap);
 		break;
 	}
diff --git a/test/system.h b/test/system.h
index a489e51..a72fc3c 100644
--- a/test/system.h
+++ b/test/system.h
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef TEST_SYSTEM_H
 #define TEST_SYSTEM_H
diff --git a/test/tmpf.c b/test/tmpf.c
index 285f5d5..483231b 100644
--- a/test/tmpf.c
+++ b/test/tmpf.c
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon Temporary File helpers
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #define _GNU_SOURCE
 #include <stdio.h>
@@ -25,9 +9,12 @@
 
 #include "test/tmpf.h"
 
+static const char *tmpf_dir = "/tmp/";
+
 int tmpf_init(struct tmpf *tmpf, const char *template)
 {
-	strncpy(tmpf->path, template, sizeof(tmpf->path) - 1);
+	strcpy(tmpf->path, tmpf_dir);
+	strncat(tmpf->path, template, sizeof(tmpf->path) - sizeof(tmpf_dir));
 
 	tmpf->fd = mkstemp(tmpf->path);
 	if (tmpf->fd < 0) {
diff --git a/test/tmpf.h b/test/tmpf.h
index 61db73d..9232e3e 100644
--- a/test/tmpf.h
+++ b/test/tmpf.h
@@ -1,21 +1,5 @@
-/*
- * Mailbox Daemon Temporary File helpers
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
 
 #ifndef MBOX_TEST_UTILS_H
 #define MBOX_TEST_UTILS_H
@@ -27,8 +11,20 @@
 	char path[PATH_MAX];
 };
 
-int tmpf_init(struct tmpf *tmpf, const char *template);
+/**
+ * Initialise a tmpf instance for use, creating a temporary file.
+ *
+ * @tmpf: A context to initialise with the provided template
+ * @template_str: A file basename in mkstemp(3) template form
+ *
+ * Returns 0 on success, or -1 on error with errno set appropriately
+ */
+int tmpf_init(struct tmpf *tmpf, const char *template_str);
 
+/**
+ * Destroy a tmpf instance, closing the file descriptor and removing the
+ * temporary file.
+ */
 void tmpf_destroy(struct tmpf *tmpf);
 
 #endif /* MBOX_TEST_UTILS_H */
diff --git a/test/write_flash.c b/test/write_flash.c
index ebd0fb7..4828cdd 100644
--- a/test/write_flash.c
+++ b/test/write_flash.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <stdio.h>
@@ -40,7 +24,7 @@
 {
 	int rc;
 
-	rc = tmpf_init(tmp, "flashXXXXXX");
+	rc = tmpf_init(tmp, "flash-store.XXXXXX");
 	if (rc < 0)
 		return NULL;
 
diff --git a/test/write_flush_v2.c b/test/write_flush_v2.c
index 63cc666..c762a76 100644
--- a/test/write_flush_v2.c
+++ b/test/write_flush_v2.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <sys/mman.h>
diff --git a/test/write_window_dirty_erase.c b/test/write_window_dirty_erase.c
index c29c095..03e7d38 100644
--- a/test/write_window_dirty_erase.c
+++ b/test/write_window_dirty_erase.c
@@ -1,21 +1,5 @@
-/*
- * MBox Daemon Test File
- *
- * Copyright 2017 IBM
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
 
 #include <assert.h>
 #include <sys/mman.h>
diff --git a/vpnor/Makefile.am.include b/vpnor/Makefile.am.include
new file mode 100644
index 0000000..0f3de00
--- /dev/null
+++ b/vpnor/Makefile.am.include
@@ -0,0 +1,11 @@
+mboxd_SOURCES += %reldir%/pnor_partition_table.cpp \
+	%reldir%/mboxd_pnor_partition_table.cpp \
+	%reldir%/mboxd_flash.cpp \
+	%reldir%/pnor_partition.cpp \
+	%reldir%/mboxd_lpc_reset.cpp \
+	%reldir%/mboxd_msg.cpp
+
+mboxd_LDFLAGS += -lstdc++fs \
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS)
diff --git a/vpnor/README.md b/vpnor/README.md
new file mode 100644
index 0000000..12e1911
--- /dev/null
+++ b/vpnor/README.md
@@ -0,0 +1,59 @@
+Virtual PNOR Functionality
+==========================
+
+In the abstract, the virtual PNOR function shifts mboxd away from accessing raw
+flash to dynamically presenting a raw flash image to the host from a set of
+backing files.
+
+Enabling the feature virtualises both the host's access to the flash (the mbox
+protocol), and the BMC's access to the flash (via some filesystem on top of the
+flash device).
+
+Do I want to use this feature in my platform?
+---------------------------------------------
+
+Maybe. It depends on how the image construction is managed, particularly the
+behaviour around writes from the host. It is likely the scheme will prevent
+firmware updates from being correctly applied when using flash tools on the
+host.
+
+Use-case Requirements
+---------------------
+
+Currently, the virtual PNOR implementation requires that:
+
+* The host expect an FFS layout (OpenPOWER systems)
+* The BMC provide a directory tree presenting the backing files in a hierarchy
+  that reflects the partition properties in the FFS table of contents.
+
+Implementation Behavioural Properties
+-------------------------------------
+
+1.  The FFS ToC defines the set of valid access ranges in terms of partitions
+2.  The read-only property of partitions is enforced
+3.  The ToC is considered read-only
+4.  Read access to valid ranges must be granted
+5.  Write access to valid ranges may be granted
+6.  Access ranges that are valid may map into a backing file associated with
+    the partition
+7.  A read of a valid access range that maps into the backing file will render
+    the data held in the backing file at the appropriate offset
+8.  A read of a valid access range that does not map into the backing file will
+    appear erased
+9.  A read of an invalid access range will appear erased
+10. A write to a valid access range that maps into the backing file will update
+    the data in the file at the appropriate offset
+11. A write to a valid access range that does not map into the backing file
+    will expand the backing file to accommodate the write.
+12. A write to a valid access range may fail if the range is not marked as
+    writeable. The error should be returned in response to the request to open
+    the write window intersecting the read-only range.
+13. A write of an invalid access range will return an error. The error should
+    be returned in response to the request to open the write window covering
+    the invalid range.
+
+The clarification on when the failure occurs in points 11 and 12 is useful for
+host-side error handling. Opening a write window gives the indication that
+future writes are expected to succeed, but in both cases we define them as
+always failing. Therefore we should not give the impression to the host that
+what it is asking for can be satisfied.
diff --git a/vpnor/mboxd_flash.cpp b/vpnor/mboxd_flash.cpp
new file mode 100644
index 0000000..a774ec1
--- /dev/null
+++ b/vpnor/mboxd_flash.cpp
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <algorithm>
+
+extern "C" {
+#include "common.h"
+}
+
+#include "config.h"
+#include "mboxd_flash.h"
+#include "mboxd_pnor_partition_table.h"
+#include "pnor_partition.hpp"
+#include "pnor_partition_table.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+#include <memory>
+#include <string>
+#include <exception>
+#include <stdexcept>
+
+namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
+namespace fs = std::experimental::filesystem;
+namespace vpnor = openpower::virtual_pnor;
+
+/** @brief unique_ptr functor to release a char* reference. */
+struct StringDeleter
+{
+    void operator()(char* ptr) const
+    {
+        free(ptr);
+    }
+};
+using StringPtr = std::unique_ptr<char, StringDeleter>;
+
+int init_flash_dev(struct mbox_context* context)
+{
+    StringPtr filename(get_dev_mtd());
+    int fd = 0;
+    int rc = 0;
+
+    if (!filename)
+    {
+        MSG_ERR("Couldn't find the flash /dev/mtd partition\n");
+        return -1;
+    }
+
+    MSG_DBG("Opening %s\n", filename.get());
+
+    fd = open(filename.get(), O_RDWR);
+    if (fd < 0)
+    {
+        MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", filename.get(),
+                strerror(errno));
+        return -errno;
+    }
+
+    // Read the Flash Info
+    if (ioctl(fd, MEMGETINFO, &context->mtd_info) == -1)
+    {
+        MSG_ERR("Couldn't get information about MTD: %s\n", strerror(errno));
+        close(fd);
+        return -errno;
+    }
+
+    if (context->flash_size == 0)
+    {
+        // See comment in mboxd_flash_physical.c on why
+        // this is needed.
+        context->flash_size = context->mtd_info.size;
+    }
+
+    // Hostboot requires a 4K block-size to be used in the FFS flash structure
+    context->mtd_info.erasesize = 4096;
+    context->erase_size_shift = log_2(context->mtd_info.erasesize);
+    context->flash_bmap = NULL;
+    context->fds[MTD_FD].fd = -1;
+
+    close(fd);
+    return rc;
+}
+
+void free_flash_dev(struct mbox_context* context)
+{
+    // No-op
+}
+
+int set_flash_bytemap(struct mbox_context* context, uint32_t offset,
+                      uint32_t count, uint8_t val)
+{
+    // No-op
+    return 0;
+}
+
+int erase_flash(struct mbox_context* context, uint32_t offset, uint32_t count)
+{
+    // No-op
+    return 0;
+}
+
+/*
+ * copy_flash() - Copy data from the virtual pnor into a provided buffer
+ * @context:    The mbox context pointer
+ * @offset:     The pnor offset to copy from (bytes)
+ * @mem:        The buffer to copy into (must be of atleast 'size' bytes)
+ * @size:       The number of bytes to copy
+ * Return:      Number of bytes copied on success, otherwise negative error
+ *              code. copy_flash will copy at most 'size' bytes, but it may
+ *              copy less.
+ */
+int64_t copy_flash(struct mbox_context* context, uint32_t offset, void* mem,
+                   uint32_t size)
+{
+    vpnor::partition::Table* table;
+    int rc = size;
+
+    if (!(context && context->vpnor && context->vpnor->table))
+    {
+        MSG_ERR("Trying to copy data with uninitialised context!\n");
+        return -MBOX_R_SYSTEM_ERROR;
+    }
+
+    table = context->vpnor->table;
+
+    MSG_DBG("Copy virtual pnor to %p for size 0x%.8x from offset 0x%.8x\n", mem,
+            size, offset);
+
+    /* The virtual PNOR partition table starts at offset 0 in the virtual
+     * pnor image. Check if host asked for an offset that lies within the
+     * partition table.
+     */
+    size_t sz = table->size();
+    if (offset < sz)
+    {
+        const pnor_partition_table& toc = table->getHostTable();
+        rc = std::min(sz - offset, static_cast<size_t>(size));
+        memcpy(mem, ((uint8_t*)&toc) + offset, rc);
+        return rc;
+    }
+
+    try
+    {
+        vpnor::Request req(context, offset);
+        rc = req.read(mem, size);
+    }
+    catch (vpnor::UnmappedOffset& e)
+    {
+        /*
+         * Hooo boy. Pretend that this is valid flash so we don't have
+         * discontiguous regions presented to the host. Instead, fill a window
+         * with 0xff so the 'flash' looks erased. Writes to such regions are
+         * dropped on the floor, see the implementation of write_flash() below.
+         */
+        MSG_INFO("Host requested unmapped region of %" PRId32
+                 " bytes at offset 0x%" PRIx32 "\n",
+                 size, offset);
+        uint32_t span = e.next - e.base;
+        rc = std::min(size, span);
+        memset(mem, 0xff, rc);
+    }
+    catch (std::exception& e)
+    {
+        MSG_ERR("%s\n", e.what());
+        phosphor::logging::commit<err::InternalFailure>();
+        rc = -MBOX_R_SYSTEM_ERROR;
+    }
+    return rc;
+}
+
+/*
+ * write_flash() - Write to the virtual pnor from a provided buffer
+ * @context: The mbox context pointer
+ * @offset:  The flash offset to write to (bytes)
+ * @buf:     The buffer to write from (must be of atleast size)
+ * @size:    The number of bytes to write
+ *
+ * Return:  0 on success otherwise negative error code
+ */
+
+int write_flash(struct mbox_context* context, uint32_t offset, void* buf,
+                uint32_t count)
+{
+
+    if (!(context && context->vpnor && context->vpnor->table))
+    {
+        MSG_ERR("Trying to write data with uninitialised context!\n");
+        return -MBOX_R_SYSTEM_ERROR;
+    }
+
+    vpnor::partition::Table* table = context->vpnor->table;
+
+    try
+    {
+        const struct pnor_partition& part = table->partition(offset);
+        if (part.data.user.data[1] & PARTITION_READONLY)
+        {
+            MSG_ERR("Unreachable: Host attempted to write to read-only "
+                    "partition %s\n",
+                    part.data.name);
+            return -MBOX_R_WRITE_ERROR;
+        }
+
+        MSG_DBG("Write flash @ 0x%.8x for 0x%.8x from %p\n", offset, count,
+                buf);
+        vpnor::Request req(context, offset);
+        req.write(buf, count);
+    }
+    catch (vpnor::UnmappedOffset& e)
+    {
+        MSG_ERR("Unreachable: Host attempted to write %" PRIu32
+                " bytes to unmapped offset 0x%" PRIx32 "\n",
+                count, offset);
+        return -MBOX_R_WRITE_ERROR;
+    }
+    catch (const vpnor::OutOfBoundsOffset& e)
+    {
+        MSG_ERR("%s\n", e.what());
+        return -MBOX_R_PARAM_ERROR;
+    }
+    catch (const std::exception& e)
+    {
+        MSG_ERR("%s\n", e.what());
+        phosphor::logging::commit<err::InternalFailure>();
+        return -MBOX_R_SYSTEM_ERROR;
+    }
+    return 0;
+}
diff --git a/vpnor/mboxd_lpc_reset.cpp b/vpnor/mboxd_lpc_reset.cpp
new file mode 100644
index 0000000..9bcbb89
--- /dev/null
+++ b/vpnor/mboxd_lpc_reset.cpp
@@ -0,0 +1,45 @@
+/*
+ * Mailbox Daemon LPC Helpers
+ *
+ * Copyright 2017 IBM
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "mbox.h"
+#include "mboxd_lpc.h"
+#include "mboxd_pnor_partition_table.h"
+
+/*
+ * reset_lpc() - Reset the lpc bus mapping
+ * @context:     The mbox context pointer
+ *
+ * Return        0 on success otherwise negative error code
+ */
+int reset_lpc(struct mbox_context *context)
+{
+    int rc;
+
+    destroy_vpnor(context);
+
+    rc = init_vpnor(context);
+    if (rc < 0)
+        return rc;
+
+    rc = vpnor_copy_bootloader_partition(context);
+    if (rc < 0)
+        return rc;
+
+    return point_to_memory(context);
+}
diff --git a/vpnor/mboxd_msg.cpp b/vpnor/mboxd_msg.cpp
new file mode 100644
index 0000000..dd9e64a
--- /dev/null
+++ b/vpnor/mboxd_msg.cpp
@@ -0,0 +1,58 @@
+#include "config.h"
+
+extern "C" {
+#include "mbox.h"
+#include "mboxd_msg.h"
+};
+
+#include "vpnor/mboxd_msg.hpp"
+#include "vpnor/pnor_partition_table.hpp"
+
+// clang-format off
+const mboxd_mbox_handler vpnor_mbox_handlers[NUM_MBOX_CMDS] =
+{
+	mbox_handle_reset,
+	mbox_handle_mbox_info,
+	mbox_handle_flash_info,
+	mbox_handle_read_window,
+	mbox_handle_close_window,
+	vpnor_handle_write_window,
+	mbox_handle_dirty_window,
+	mbox_handle_flush_window,
+	mbox_handle_ack,
+	mbox_handle_erase_window
+};
+// clang-format on
+
+/* XXX: Maybe this should be a method on a class? */
+static bool vpnor_partition_is_readonly(const pnor_partition& part)
+{
+    return part.data.user.data[1] & PARTITION_READONLY;
+}
+
+int vpnor_handle_write_window(struct mbox_context* context,
+                              union mbox_regs* req, struct mbox_msg* resp)
+{
+    size_t offset = get_u16(&req->msg.args[0]);
+    offset <<= context->block_size_shift;
+    try
+    {
+        const pnor_partition& part = context->vpnor->table->partition(offset);
+        if (vpnor_partition_is_readonly(part))
+        {
+            return -MBOX_R_WINDOW_ERROR;
+        }
+    }
+    catch (const openpower::virtual_pnor::UnmappedOffset& e)
+    {
+        /*
+         * Writes to unmapped areas are not meaningful, so deny the request.
+         * This removes the ability for a compromised host to abuse unused
+         * space if any data was to be persisted (which it isn't).
+         */
+        return -MBOX_R_WINDOW_ERROR;
+    }
+
+    /* Defer to the default handler */
+    return mbox_handle_write_window(context, req, resp);
+}
diff --git a/vpnor/mboxd_msg.hpp b/vpnor/mboxd_msg.hpp
new file mode 100644
index 0000000..d9a05bb
--- /dev/null
+++ b/vpnor/mboxd_msg.hpp
@@ -0,0 +1,8 @@
+extern "C" {
+#include "mbox.h"
+
+extern const mboxd_mbox_handler vpnor_mbox_handlers[NUM_MBOX_CMDS];
+
+int vpnor_handle_write_window(struct mbox_context *context,
+                              union mbox_regs *req, struct mbox_msg *resp);
+};
diff --git a/vpnor/mboxd_pnor_partition_table.cpp b/vpnor/mboxd_pnor_partition_table.cpp
new file mode 100644
index 0000000..1756516
--- /dev/null
+++ b/vpnor/mboxd_pnor_partition_table.cpp
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include "mboxd_pnor_partition_table.h"
+#include "pnor_partition_table.hpp"
+#include "common.h"
+#include "mbox.h"
+#include "mboxd_flash.h"
+#include "pnor_partition_table.hpp"
+#include "config.h"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/elog-errors.hpp>
+#include <experimental/filesystem>
+#include "vpnor/mboxd_msg.hpp"
+
+int init_vpnor(struct mbox_context *context)
+{
+    if (context && !context->vpnor)
+    {
+        int rc;
+
+        strncpy(context->paths.ro_loc, PARTITION_FILES_RO_LOC, PATH_MAX);
+        context->paths.ro_loc[PATH_MAX - 1] = '\0';
+        strncpy(context->paths.rw_loc, PARTITION_FILES_RW_LOC, PATH_MAX);
+        context->paths.rw_loc[PATH_MAX - 1] = '\0';
+        strncpy(context->paths.prsv_loc, PARTITION_FILES_PRSV_LOC, PATH_MAX);
+        context->paths.prsv_loc[PATH_MAX - 1] = '\0';
+        strncpy(context->paths.patch_loc, PARTITION_FILES_PATCH_LOC, PATH_MAX);
+        context->paths.prsv_loc[PATH_MAX - 1] = '\0';
+
+        rc = init_vpnor_from_paths(context);
+        if (rc < 0)
+        {
+            return rc;
+        }
+    }
+
+    return 0;
+}
+
+int init_vpnor_from_paths(struct mbox_context *context)
+{
+    namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
+    namespace fs = std::experimental::filesystem;
+    namespace vpnor = openpower::virtual_pnor;
+
+    if (context && !context->vpnor)
+    {
+        context->handlers = vpnor_mbox_handlers;
+
+        try
+        {
+            context->vpnor = new vpnor_partition_table;
+            context->vpnor->table =
+                new openpower::virtual_pnor::partition::Table(context);
+        }
+        catch (vpnor::TocEntryError &e)
+        {
+            MSG_ERR("%s\n", e.what());
+            phosphor::logging::commit<err::InternalFailure>();
+            return -MBOX_R_SYSTEM_ERROR;
+        }
+    }
+
+    return 0;
+}
+
+int vpnor_copy_bootloader_partition(const struct mbox_context *context)
+{
+    // The hostboot bootloader has certain size/offset assumptions, so
+    // we need a special partition table here.
+    // It assumes the PNOR is 64M, the TOC size is 32K, the erase block is
+    // 4K, the page size is 4K.
+    // It also assumes the TOC is at the 'end of pnor - toc size - 1 page size'
+    // offset, and first looks for the TOC here, before proceeding to move up
+    // page by page looking for the TOC. So it is optimal to place the TOC at
+    // this offset.
+    constexpr size_t eraseSize = 0x1000;
+    constexpr size_t pageSize = 0x1000;
+    constexpr size_t pnorSize = 0x4000000;
+    constexpr size_t tocMaxSize = 0x8000;
+    constexpr size_t tocStart = pnorSize - tocMaxSize - pageSize;
+    constexpr auto blPartitionName = "HBB";
+
+    namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
+    namespace fs = std::experimental::filesystem;
+    namespace vpnor = openpower::virtual_pnor;
+
+    try
+    {
+        vpnor_partition_table vtbl{};
+        struct mbox_context local = *context;
+        local.vpnor = &vtbl;
+        local.block_size_shift = log_2(eraseSize);
+
+        openpower::virtual_pnor::partition::Table blTable(&local);
+
+        vtbl.table = &blTable;
+
+        size_t tocOffset = 0;
+
+        // Copy TOC
+        copy_flash(&local, tocOffset,
+                   static_cast<uint8_t *>(context->mem) + tocStart,
+                   blTable.capacity());
+        const pnor_partition &partition = blTable.partition(blPartitionName);
+        size_t hbbOffset = partition.data.base * eraseSize;
+        uint32_t hbbSize = partition.data.actual;
+        // Copy HBB
+        copy_flash(&local, hbbOffset,
+                   static_cast<uint8_t *>(context->mem) + hbbOffset, hbbSize);
+    }
+    catch (err::InternalFailure &e)
+    {
+        phosphor::logging::commit<err::InternalFailure>();
+        return -MBOX_R_SYSTEM_ERROR;
+    }
+    catch (vpnor::ReasonedError &e)
+    {
+        MSG_ERR("%s\n", e.what());
+        phosphor::logging::commit<err::InternalFailure>();
+        return -MBOX_R_SYSTEM_ERROR;
+    }
+
+    return 0;
+}
+
+void destroy_vpnor(struct mbox_context *context)
+{
+    if (context && context->vpnor)
+    {
+        delete context->vpnor->table;
+        delete context->vpnor;
+        context->vpnor = nullptr;
+    }
+}
diff --git a/vpnor/mboxd_pnor_partition_table.h b/vpnor/mboxd_pnor_partition_table.h
new file mode 100644
index 0000000..d13a2d2
--- /dev/null
+++ b/vpnor/mboxd_pnor_partition_table.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
+#pragma once
+
+#ifdef VIRTUAL_PNOR_ENABLED
+
+#include <limits.h>
+#include "pnor_partition_defs.h"
+
+struct mbox_context;
+struct vpnor_partition_table;
+
+struct vpnor_partition_paths
+{
+    char ro_loc[PATH_MAX];
+    char rw_loc[PATH_MAX];
+    char prsv_loc[PATH_MAX];
+    char patch_loc[PATH_MAX];
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @brief Create a virtual PNOR partition table.
+ *
+ *  @param[in] context - mbox context pointer
+ *
+ *  This API should be called before calling any other APIs below. If a table
+ *  already exists, this function will not do anything further. This function
+ *  will not do anything if the context is NULL.
+ *
+ *  Returns 0 if the call succeeds, else a negative error code.
+ */
+int init_vpnor(struct mbox_context *context);
+
+/** @brief Create a virtual PNOR partition table.
+ *
+ *  @param[in] context - mbox context pointer
+ *
+ *  This API is same as above one but requires context->path is initialised
+ *  with all the necessary paths.
+ *
+ *  Returns 0 if the call succeeds, else a negative error code.
+ */
+
+int init_vpnor_from_paths(struct mbox_context *context);
+
+/** @brief Copy bootloader partition (alongwith TOC) to LPC memory
+ *
+ *  @param[in] context - mbox context pointer
+ *
+ *  @returns 0 on success, negative error code on failure
+ */
+int vpnor_copy_bootloader_partition(const struct mbox_context *context);
+
+/** @brief Destroy partition table, if it exists.
+ *
+ *  @param[in] context - mbox context pointer
+ */
+void destroy_vpnor(struct mbox_context *context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/vpnor/pnor_partition.cpp b/vpnor/pnor_partition.cpp
new file mode 100644
index 0000000..67a24f4
--- /dev/null
+++ b/vpnor/pnor_partition.cpp
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include "pnor_partition.hpp"
+#include "pnor_partition_table.hpp"
+#include "config.h"
+#include "mboxd_flash.h"
+#include "mboxd_pnor_partition_table.h"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "common.h"
+
+#include <string>
+#include <exception>
+#include <stdexcept>
+#include <iostream>
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+
+namespace fs = std::experimental::filesystem;
+
+fs::path Request::getPartitionFilePath(int flags)
+{
+    // Check if partition exists in patch location
+    auto dst = fs::path(ctx->paths.patch_loc) / partition.data.name;
+    if (fs::is_regular_file(dst))
+    {
+        return dst;
+    }
+
+    switch (partition.data.user.data[1] &
+            (PARTITION_PRESERVED | PARTITION_READONLY))
+    {
+        case PARTITION_PRESERVED:
+            dst = ctx->paths.prsv_loc;
+            break;
+
+        case PARTITION_READONLY:
+            dst = ctx->paths.ro_loc;
+            break;
+
+        default:
+            dst = ctx->paths.rw_loc;
+    }
+    dst /= partition.data.name;
+
+    if (fs::exists(dst))
+    {
+        return dst;
+    }
+
+    if (flags == O_RDONLY)
+    {
+        dst = fs::path(ctx->paths.ro_loc) / partition.data.name;
+        assert(fs::exists(dst));
+        return dst;
+    }
+
+    assert(flags == O_RDWR);
+    auto src = fs::path(ctx->paths.ro_loc) / partition.data.name;
+    assert(fs::exists(src));
+
+    MSG_DBG("RWRequest: Didn't find '%s' under '%s', copying from '%s'\n",
+            partition.data.name, dst.c_str(), src.c_str());
+
+    dst = ctx->paths.rw_loc;
+    if (partition.data.user.data[1] & PARTITION_PRESERVED)
+    {
+        dst = ctx->paths.prsv_loc;
+    }
+
+    dst /= partition.data.name;
+    fs::copy_file(src, dst);
+
+    return dst;
+}
+
+size_t Request::clamp(size_t len)
+{
+    size_t maxAccess = offset + len;
+    size_t partSize = partition.data.size << ctx->block_size_shift;
+    return std::min(maxAccess, partSize) - offset;
+}
+
+void Request::resize(const fs::path &path, size_t len)
+{
+    size_t maxAccess = offset + len;
+    size_t fileSize = fs::file_size(path);
+    if (maxAccess < fileSize)
+    {
+        return;
+    }
+    MSG_DBG("Resizing %s to %zu bytes\n", path.c_str(), maxAccess);
+    int rc = truncate(path.c_str(), maxAccess);
+    if (rc == -1)
+    {
+        MSG_ERR("Failed to resize %s: %d\n", path.c_str(), errno);
+        throw std::system_error(errno, std::system_category());
+    }
+}
+
+size_t Request::fulfil(const fs::path &path, int flags, void *buf, size_t len)
+{
+    if (!(flags == O_RDONLY || flags == O_RDWR))
+    {
+        std::stringstream err;
+        err << "Require O_RDONLY (0x" << std::hex << O_RDONLY << " or O_RDWR "
+            << std::hex << O_RDWR << " for flags, got: 0x" << std::hex << flags;
+        throw std::invalid_argument(err.str());
+    }
+
+    int fd = ::open(path.c_str(), flags);
+    if (fd == -1)
+    {
+        MSG_ERR("Failed to open backing file at '%s': %d\n", path.c_str(),
+                errno);
+        throw std::system_error(errno, std::system_category());
+    }
+
+    size_t fileSize = fs::file_size(path);
+    int mprot = PROT_READ | ((flags == O_RDWR) ? PROT_WRITE : 0);
+    auto map = mmap(NULL, fileSize, mprot, MAP_SHARED, fd, 0);
+    if (map == MAP_FAILED)
+    {
+        close(fd);
+        MSG_ERR("Failed to map backing file '%s' for %zd bytes: %d\n",
+                path.c_str(), fileSize, errno);
+        throw std::system_error(errno, std::system_category());
+    }
+
+    // copy to the reserved memory area
+    if (flags == O_RDONLY)
+    {
+        memset(buf, 0xff, len);
+        memcpy(buf, (char *)map + offset, std::min(len, fileSize));
+    }
+    else
+    {
+        memcpy((char *)map + offset, buf, len);
+        set_flash_bytemap(ctx, base + offset, len, FLASH_DIRTY);
+    }
+    munmap(map, fileSize);
+    close(fd);
+
+    return len;
+}
+
+} // namespace virtual_pnor
+} // namespace openpower
diff --git a/vpnor/pnor_partition.hpp b/vpnor/pnor_partition.hpp
new file mode 100644
index 0000000..eb63a39
--- /dev/null
+++ b/vpnor/pnor_partition.hpp
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
+#pragma once
+
+extern "C" {
+#include "mbox.h"
+};
+
+#include "mboxd_pnor_partition_table.h"
+#include "pnor_partition_table.hpp"
+#include <fcntl.h>
+#include <string>
+#include <unistd.h>
+#include <experimental/filesystem>
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+
+namespace fs = std::experimental::filesystem;
+
+class Request
+{
+  public:
+    /** @brief Construct a flash access request
+     *
+     *  @param[in] ctx - The mbox context used to process the request
+     *  @param[in] offset - The absolute offset into the flash device as
+     *                      provided by the mbox message associated with the
+     *                      request
+     *
+     *  The class does not take ownership of the ctx pointer. The lifetime of
+     *  the ctx pointer must strictly exceed the lifetime of the class
+     *  instance.
+     */
+    Request(struct mbox_context* ctx, size_t offset) :
+        ctx(ctx), partition(ctx->vpnor->table->partition(offset)),
+        base(partition.data.base << ctx->block_size_shift),
+        offset(offset - base)
+    {
+    }
+    Request(const Request&) = delete;
+    Request& operator=(const Request&) = delete;
+    Request(Request&&) = default;
+    Request& operator=(Request&&) = default;
+    ~Request() = default;
+
+    ssize_t read(void* dst, size_t len)
+    {
+        len = clamp(len);
+        constexpr auto flags = O_RDONLY;
+        fs::path path = getPartitionFilePath(flags);
+        return fulfil(path, flags, dst, len);
+    }
+
+    ssize_t write(void* dst, size_t len)
+    {
+        if (len != clamp(len))
+        {
+            std::stringstream err;
+            err << "Request size 0x" << std::hex << len << " from offset 0x"
+                << std::hex << offset << " exceeds the partition size 0x"
+                << std::hex << (partition.data.size << ctx->block_size_shift);
+            throw OutOfBoundsOffset(err.str());
+        }
+        constexpr auto flags = O_RDWR;
+        /* Ensure file is at least the size of the maximum access */
+        fs::path path = getPartitionFilePath(flags);
+        resize(path, len);
+        return fulfil(path, flags, dst, len);
+    }
+
+  private:
+    /** @brief Clamp the access length to the maximum supported by the ToC */
+    size_t clamp(size_t len);
+
+    /** @brief Ensure the backing file is sized appropriately for the access
+     *
+     *  We need to ensure the file is big enough to satisfy the request so that
+     *  mmap() will succeed for the required size.
+     *
+     *  @return The valid access length
+     *
+     *  Throws: std::system_error
+     */
+    void resize(const std::experimental::filesystem::path& path, size_t len);
+
+    /** @brief Returns the partition file path associated with the offset.
+     *
+     *  The search strategy for the partition file depends on the value of the
+     *  flags parameter.
+     *
+     *  For the O_RDONLY case:
+     *
+     *  1.  Depending on the partition type,tries to open the file
+     *      from the associated partition(RW/PRSV/RO).
+     *  1a. if file not found in the corresponding
+     *      partition(RW/PRSV/RO) then tries to read the file from
+     *      the read only partition.
+     *  1b. if the file not found in the read only partition then
+     *      throw exception.
+     *
+     * For the O_RDWR case:
+     *
+     *  1.  Depending on the partition type tries to open the file
+     *      from the associated partition.
+     *  1a. if file not found in the corresponding partition(RW/PRSV)
+     *      then copy the file from the read only partition to the (RW/PRSV)
+     *      partition depending on the partition type.
+     *  1b. if the file not found in the read only partition then throw
+     *      exception.
+     *
+     *  @param[in] flags - The flags that will be used to open the file. Must
+     *                     be one of O_RDONLY or O_RDWR.
+     *
+     *  Post-condition: The file described by the returned path exists
+     *
+     *  Throws: std::filesystem_error, std::bad_alloc
+     */
+    std::experimental::filesystem::path getPartitionFilePath(int flags);
+
+    /** @brief Fill dst with the content of the partition relative to offset.
+     *
+     *  @param[in] offset - The pnor offset(bytes).
+     *  @param[out] dst - The buffer to fill with partition data
+     *  @param[in] len - The length of the destination buffer
+     */
+    size_t fulfil(const std::experimental::filesystem::path& path, int flags,
+                  void* dst, size_t len);
+
+    struct mbox_context* ctx;
+    const pnor_partition& partition;
+    size_t base;
+    size_t offset;
+};
+
+} // namespace virtual_pnor
+} // namespace openpower
diff --git a/vpnor/pnor_partition_defs.h b/vpnor/pnor_partition_defs.h
new file mode 100644
index 0000000..447b11c
--- /dev/null
+++ b/vpnor/pnor_partition_defs.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+
+/* There are two structures outlined here - one that represents the PNOR
+ * partition table (or header) - this appears first in the PNOR image.
+ * The last field of the PNOR partition table structure is an array
+ * of another structure - which represents the partition.
+ *
+ * The flash structures used here have been borrowed from
+ * https://github.com/open-power/hostboot/blob/master/src/usr/pnor/ffs.h */
+
+
+/* The maximum length of a partition's name */
+#define PARTITION_NAME_MAX 15
+
+/* The version of this partition implementation. This is an
+ * incrementing value */
+#define PARTITION_VERSION_1 1
+
+/* Magic number for the partition partition_table (ASCII 'PART') */
+#define PARTITION_HEADER_MAGIC 0x50415254
+
+/* Default parent partition id */
+#define PARENT_PATITION_ID 0xFFFFFFFF
+
+/* The partition structure has 16 'user data' words, which can be used to store
+ * miscellaneous information. This is typically used to store bits that state
+ * whether a partition is ECC protected, is read-only, is preserved across
+ * updates, etc.
+ *
+ * TODO: Replace with libflash (!) or at least refactor the data structures to
+ * better match hostboot's layout[1]. The latter would avoid the headache of
+ * verifying these flags match the expected functionality (taking into account
+ * changes in endianness).
+ *
+ * [1] https://github.com/open-power/hostboot/blob/9acfce99596f12dcc60952f8506a77e542609cbf/src/usr/pnor/common/ffs_hb.H#L81
+ */
+#define PARTITION_USER_WORDS 16
+#define PARTITION_ECC_PROTECTED 0x8000
+#define PARTITION_PRESERVED 0x00800000
+#define PARTITION_READONLY 0x00400000
+#define PARTITION_REPROVISION 0x00100000
+#define PARTITION_VOLATILE 0x00080000
+#define PARTITION_CLEARECC 0x00040000
+#define PARTITION_VERSION_CHECK_SHA512 0x80000000
+#define PARTITION_VERSION_CHECK_SHA512_PER_EC 0x40000000
+
+/* Partition flags */
+enum partition_flags {
+    PARTITION_FLAGS_PROTECTED = 0x0001,
+    PARTITION_FLAGS_U_BOOT_ENV = 0x0002
+};
+
+/* Type of image contained within partition */
+enum partition_type {
+    PARTITION_TYPE_DATA = 1,
+    PARTITION_TYPE_LOGICAL = 2,
+    PARTITION_TYPE_PARTITION = 3
+};
+
+
+/**
+ * struct pnor_partition
+ *
+ * @name:       Name of the partition - a null terminated string
+ * @base:       The offset in the PNOR, in block-size (1 block = 4KB),
+ *              where this partition is placed
+ * @size:       Partition size in blocks.
+ * @pid:        Parent partition id
+ * @id:         Partition ID [1..65536]
+ * @type:       Type of partition, see the 'type' enum
+ * @flags:      Partition flags (optional), see the 'flags' enum
+ * @actual:     Actual partition size (in bytes)
+ * @resvd:      Reserved words for future use
+ * @user:       User data (optional), see user data macros above
+ * @checksum:   Partition checksum (includes all words above) - the
+ *              checksum is obtained by a XOR operation on all of the
+ *              words above. This is used for detecting a corruption
+ *              in this structure
+ */
+struct pnor_partition {
+    struct {
+        char         name[PARTITION_NAME_MAX + 1];
+        uint32_t     base;
+        uint32_t     size;
+        uint32_t     pid;
+        uint32_t     id;
+        uint32_t     type;
+        uint32_t     flags;
+        uint32_t     actual;
+        uint32_t     resvd[4];
+        struct
+        {
+            uint32_t data[PARTITION_USER_WORDS];
+        } user;
+    } __attribute__ ((packed)) data;
+    uint32_t     checksum;
+} __attribute__ ((packed));
+
+/**
+ * struct pnor_partition_table
+ *
+ * @magic:          Eye catcher/corruption detector - set to
+ *                  PARTITION_HEADER_MAGIC
+ * @version:        Version of the structure, set to
+ *                  PARTITION_VERSION_1
+ * @size:           Size of partition table (in blocks)
+ * @entry_size:     Size of struct pnor_partition element (in bytes)
+ * @entry_count:    Number of struct pnor_partition elements in partitions array
+ * @block_size:     Size of an erase-block on the PNOR (in bytes)
+ * @block_count:    Number of blocks on the PNOR
+ * @resvd:          Reserved words for future use
+ * @checksum:       Header checksum (includes all words above) - the
+ *                  checksum is obtained by a XOR operation on all of the
+ *                  words above. This is used for detecting a corruption
+ *                  in this structure
+ * @partitions:     Array of struct pnor_partition
+ */
+struct pnor_partition_table {
+    struct {
+        uint32_t         magic;
+        uint32_t         version;
+        uint32_t         size;
+        uint32_t         entry_size;
+        uint32_t         entry_count;
+        uint32_t         block_size;
+        uint32_t         block_count;
+        uint32_t         resvd[4];
+    } __attribute__ ((packed)) data;
+    uint32_t         checksum;
+    struct pnor_partition partitions[];
+} __attribute__ ((packed));
diff --git a/vpnor/pnor_partition_table.cpp b/vpnor/pnor_partition_table.cpp
new file mode 100644
index 0000000..4ad07bc
--- /dev/null
+++ b/vpnor/pnor_partition_table.cpp
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include "pnor_partition_table.hpp"
+#include "common.h"
+#include "config.h"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/elog-errors.hpp>
+#include <syslog.h>
+#include <endian.h>
+#include <regex>
+#include <fstream>
+#include <algorithm>
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+namespace partition
+{
+
+Table::Table(const struct mbox_context* ctx) :
+    szBytes(sizeof(pnor_partition_table)), numParts(0),
+    blockSize(1 << ctx->erase_size_shift), pnorSize(ctx->flash_size)
+{
+    preparePartitions(ctx);
+    prepareHeader();
+    hostTbl = endianFixup(tbl);
+}
+
+void Table::prepareHeader()
+{
+    decltype(auto) table = getNativeTable();
+    table.data.magic = PARTITION_HEADER_MAGIC;
+    table.data.version = PARTITION_VERSION_1;
+    table.data.size = blocks();
+    table.data.entry_size = sizeof(pnor_partition);
+    table.data.entry_count = numParts;
+    table.data.block_size = blockSize;
+    table.data.block_count = pnorSize / blockSize;
+    table.checksum = details::checksum(table.data);
+}
+
+inline void Table::allocateMemory(const fs::path& tocFile)
+{
+    size_t num = 0;
+    std::string line;
+    std::ifstream file(tocFile.c_str());
+
+    // Find number of lines in partition file - this will help
+    // determine the number of partitions and hence also how much
+    // memory to allocate for the partitions array.
+    // The actual number of partitions may turn out to be lesser than this,
+    // in case of errors.
+    while (std::getline(file, line))
+    {
+        // Check if line starts with "partition"
+        if (std::string::npos != line.find("partition", 0))
+        {
+            ++num;
+        }
+    }
+
+    szBytes = sizeof(pnor_partition_table) + (num * sizeof(pnor_partition));
+    tbl.resize(capacity());
+}
+
+void Table::preparePartitions(const struct mbox_context* ctx)
+{
+    const fs::path roDir = ctx->paths.ro_loc;
+    const fs::path patchDir = ctx->paths.patch_loc;
+    fs::path tocFile = roDir / PARTITION_TOC_FILE;
+    allocateMemory(tocFile);
+
+    std::ifstream file(tocFile.c_str());
+    std::string line;
+    decltype(auto) table = getNativeTable();
+
+    while (std::getline(file, line))
+    {
+        pnor_partition& part = table.partitions[numParts];
+        fs::path patch;
+        fs::path file;
+
+        // The ToC file presented in the vpnor squashfs looks like:
+        //
+        // version=IBM-witherspoon-ibm-OP9_v1.19_1.135
+        // extended_version=op-build-v1.19-571-g04f4690-dirty,buildroot-2017.11-5-g65679be,skiboot-v5.10-rc4,hostboot-4c46d66,linux-4.14.20-openpower1-p4a6b675,petitboot-v1.6.6-pe5aaec2,machine-xml-0fea226,occ-3286b6b,hostboot-binaries-3d1af8f,capp-ucode-p9-dd2-v3,sbe-99e2fe2
+        // partition00=part,0x00000000,0x00002000,00,READWRITE
+        // partition01=HBEL,0x00008000,0x0002c000,00,ECC,REPROVISION,CLEARECC,READWRITE
+        // ...
+        //
+        // As such we want to skip any lines that don't begin with 'partition'
+        if (std::string::npos == line.find("partition", 0))
+        {
+            continue;
+        }
+
+        parseTocLine(line, blockSize, part);
+
+        if (numParts > 0)
+        {
+            struct pnor_partition& prev = table.partitions[numParts - 1];
+            uint32_t prev_end = prev.data.base + prev.data.size;
+
+            if (part.data.id == prev.data.id)
+            {
+                MSG_ERR("ID for previous partition '%s' at block 0x%" PRIx32
+                        "matches current partition '%s' at block 0x%" PRIx32
+                        ": %" PRId32 "\n",
+                        prev.data.name, prev.data.base, part.data.name,
+                        part.data.base, part.data.id);
+            }
+
+            if (part.data.base < prev_end)
+            {
+                std::stringstream err;
+                err << "Partition '" << part.data.name << "' start block 0x"
+                    << std::hex << part.data.base << "is less than the end "
+                    << "block 0x" << std::hex << prev_end << " of '"
+                    << prev.data.name << "'";
+                throw InvalidTocEntry(err.str());
+            }
+        }
+
+        file = roDir / part.data.name;
+        if (!fs::exists(file))
+        {
+            std::stringstream err;
+            err << "Partition file " << file.native() << " does not exist";
+            throw InvalidTocEntry(err.str());
+        }
+
+        patch = patchDir / part.data.name;
+        if (fs::is_regular_file(patch))
+        {
+            const size_t size = part.data.size * blockSize;
+            part.data.actual =
+                std::min(size, static_cast<size_t>(fs::file_size(patch)));
+        }
+
+        ++numParts;
+    }
+}
+
+const pnor_partition& Table::partition(size_t offset) const
+{
+    const decltype(auto) table = getNativeTable();
+    size_t blockOffset = offset / blockSize;
+
+    for (decltype(numParts) i{}; i < numParts; ++i)
+    {
+        const struct pnor_partition& part = table.partitions[i];
+        size_t len = part.data.size;
+
+        if ((blockOffset >= part.data.base) &&
+            (blockOffset < (part.data.base + len)))
+        {
+            return part;
+        }
+
+        /* Are we in a hole between partitions? */
+        if (blockOffset < part.data.base)
+        {
+            throw UnmappedOffset(offset, part.data.base * blockSize);
+        }
+    }
+
+    throw UnmappedOffset(offset, pnorSize);
+}
+
+const pnor_partition& Table::partition(const std::string& name) const
+{
+    const decltype(auto) table = getNativeTable();
+
+    for (decltype(numParts) i{}; i < numParts; ++i)
+    {
+        if (name == table.partitions[i].data.name)
+        {
+            return table.partitions[i];
+        }
+    }
+
+    std::stringstream err;
+    err << "Partition " << name << " is not listed in the table of contents";
+    throw UnknownPartition(err.str());
+}
+
+} // namespace partition
+
+PartitionTable endianFixup(const PartitionTable& in)
+{
+    PartitionTable out;
+    out.resize(in.size());
+    auto src = reinterpret_cast<const pnor_partition_table*>(in.data());
+    auto dst = reinterpret_cast<pnor_partition_table*>(out.data());
+
+    dst->data.magic = htobe32(src->data.magic);
+    dst->data.version = htobe32(src->data.version);
+    dst->data.size = htobe32(src->data.size);
+    dst->data.entry_size = htobe32(src->data.entry_size);
+    dst->data.entry_count = htobe32(src->data.entry_count);
+    dst->data.block_size = htobe32(src->data.block_size);
+    dst->data.block_count = htobe32(src->data.block_count);
+    dst->checksum = details::checksum(dst->data);
+
+    for (decltype(src->data.entry_count) i{}; i < src->data.entry_count; ++i)
+    {
+        auto psrc = &src->partitions[i];
+        auto pdst = &dst->partitions[i];
+        strncpy(pdst->data.name, psrc->data.name, PARTITION_NAME_MAX);
+        // Just to be safe
+        pdst->data.name[PARTITION_NAME_MAX] = '\0';
+        pdst->data.base = htobe32(psrc->data.base);
+        pdst->data.size = htobe32(psrc->data.size);
+        pdst->data.pid = htobe32(psrc->data.pid);
+        pdst->data.id = htobe32(psrc->data.id);
+        pdst->data.type = htobe32(psrc->data.type);
+        pdst->data.flags = htobe32(psrc->data.flags);
+        pdst->data.actual = htobe32(psrc->data.actual);
+        for (size_t j = 0; j < PARTITION_USER_WORDS; ++j)
+        {
+            pdst->data.user.data[j] = htobe32(psrc->data.user.data[j]);
+        }
+        pdst->checksum = details::checksum(pdst->data);
+    }
+
+    return out;
+}
+
+static inline void writeSizes(pnor_partition& part, size_t start, size_t end,
+                              size_t blockSize)
+{
+    size_t size = end - start;
+    part.data.base = align_up(start, blockSize) / blockSize;
+    size_t sizeInBlocks = align_up(size, blockSize) / blockSize;
+    part.data.size = sizeInBlocks;
+    part.data.actual = size;
+}
+
+static inline void writeUserdata(pnor_partition& part, uint32_t version,
+                                 const std::string& data)
+{
+    std::istringstream stream(data);
+    std::string flag{};
+    auto perms = 0;
+    auto state = 0;
+
+    MSG_DBG("Parsing ToC flags '%s'\n", data.c_str());
+    while (std::getline(stream, flag, ','))
+    {
+        if (flag == "")
+            continue;
+
+        if (flag == "ECC")
+        {
+            state |= PARTITION_ECC_PROTECTED;
+        }
+        else if (flag == "READONLY")
+        {
+            perms |= PARTITION_READONLY;
+        }
+        else if (flag == "READWRITE")
+        {
+            perms &= ~PARTITION_READONLY;
+        }
+        else if (flag == "PRESERVED")
+        {
+            perms |= PARTITION_PRESERVED;
+        }
+        else if (flag == "REPROVISION")
+        {
+            perms |= PARTITION_REPROVISION;
+        }
+        else if (flag == "VOLATILE")
+        {
+            perms |= PARTITION_VOLATILE;
+        }
+        else if (flag == "CLEARECC")
+        {
+            perms |= PARTITION_CLEARECC;
+        }
+        else
+        {
+            MSG_INFO("Found unimplemented partition property: %s\n",
+                     flag.c_str());
+        }
+    }
+
+    part.data.user.data[0] = state;
+    part.data.user.data[1] = perms;
+    part.data.user.data[1] |= version;
+}
+
+static inline void writeDefaults(pnor_partition& part)
+{
+    part.data.pid = PARENT_PATITION_ID;
+    part.data.type = PARTITION_TYPE_DATA;
+    part.data.flags = 0; // flags unused
+}
+
+static inline void writeNameAndId(pnor_partition& part, std::string&& name,
+                                  const std::string& id)
+{
+    name.resize(PARTITION_NAME_MAX);
+    memcpy(part.data.name, name.c_str(), sizeof(part.data.name));
+    part.data.id = std::stoul(id);
+}
+
+void parseTocLine(const std::string& line, size_t blockSize,
+                  pnor_partition& part)
+{
+    static constexpr auto ID_MATCH = 1;
+    static constexpr auto NAME_MATCH = 2;
+    static constexpr auto START_ADDR_MATCH = 4;
+    static constexpr auto END_ADDR_MATCH = 6;
+    static constexpr auto VERSION_MATCH = 8;
+    constexpr auto versionShift = 24;
+
+    // Parse PNOR toc (table of contents) file, which has lines like :
+    // partition01=HBB,0x00010000,0x000a0000,0x80,ECC,PRESERVED, to indicate
+    // partition information
+    std::regex regex{
+        "^partition([0-9]+)=([A-Za-z0-9_]+),"
+        "(0x)?([0-9a-fA-F]+),(0x)?([0-9a-fA-F]+),(0x)?([A-Fa-f0-9]{2})",
+        std::regex::extended};
+
+    std::smatch match;
+    if (!std::regex_search(line, match, regex))
+    {
+        std::stringstream err;
+        err << "Malformed partition description: " << line.c_str() << "\n";
+        throw MalformedTocEntry(err.str());
+    }
+
+    writeNameAndId(part, match[NAME_MATCH].str(), match[ID_MATCH].str());
+    writeDefaults(part);
+
+    unsigned long start =
+        std::stoul(match[START_ADDR_MATCH].str(), nullptr, 16);
+    if (start & (blockSize - 1))
+    {
+        MSG_ERR("Start offset 0x%lx for partition '%s' is not aligned to block "
+                "size 0x%zx\n",
+                start, match[NAME_MATCH].str().c_str(), blockSize);
+    }
+
+    unsigned long end = std::stoul(match[END_ADDR_MATCH].str(), nullptr, 16);
+    if ((end - start) & (blockSize - 1))
+    {
+        MSG_ERR("Partition '%s' has a size 0x%lx that is not aligned to block "
+                "size 0x%zx\n",
+                match[NAME_MATCH].str().c_str(), (end - start), blockSize);
+    }
+
+    if (start >= end)
+    {
+        std::stringstream err;
+        err << "Partition " << match[NAME_MATCH].str()
+            << " has an invalid range: start offset (0x" << std::hex << start
+            << " is beyond open end (0x" << std::hex << end << ")\n";
+        throw InvalidTocEntry(err.str());
+    }
+    writeSizes(part, start, end, blockSize);
+
+    // Use the shift to convert "80" to 0x80000000
+    unsigned long version = std::stoul(match[VERSION_MATCH].str(), nullptr, 16);
+    writeUserdata(part, version << versionShift, match.suffix().str());
+    part.checksum = details::checksum(part.data);
+}
+
+} // namespace virtual_pnor
+} // namespace openpower
diff --git a/vpnor/pnor_partition_table.hpp b/vpnor/pnor_partition_table.hpp
new file mode 100644
index 0000000..10dccdd
--- /dev/null
+++ b/vpnor/pnor_partition_table.hpp
@@ -0,0 +1,345 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
+#pragma once
+
+#include <vector>
+#include <memory>
+#include <numeric>
+#include <experimental/filesystem>
+#include "common.h"
+#include "mbox.h"
+#include "pnor_partition_defs.h"
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+
+namespace fs = std::experimental::filesystem;
+
+using PartitionTable = std::vector<uint8_t>;
+using checksum_t = uint32_t;
+
+/** @brief Convert the input partition table to big endian.
+ *
+ *  @param[in] src - reference to the pnor partition table
+ *
+ *  @returns converted partition table
+ */
+PartitionTable endianFixup(const PartitionTable& src);
+
+/** @brief Parse a ToC line (entry) into the corresponding FFS partition
+ * object.
+ *
+ * @param[in] line - The ToC line to parse
+ * @param[in] blockSize - The flash block size in bytes
+ * @param[out] part - The partition object to populate with the information
+ *                    parsed from the provided ToC line
+ *
+ * Throws: MalformedTocEntry, InvalidTocEntry
+ */
+void parseTocLine(const std::string& line, size_t blockSize,
+                  pnor_partition& part);
+
+namespace details
+{
+
+/** @brief Compute XOR-based checksum, by XORing consecutive words
+ *         in the input data. Input must be aligned to word boundary.
+ *
+ *  @param[in] data - input data on which checksum is computed
+ *
+ *  @returns computed checksum
+ */
+template <class T> checksum_t checksum(const T& data)
+{
+    static_assert(sizeof(decltype(data)) % sizeof(checksum_t) == 0,
+                  "sizeof(data) is not aligned to sizeof(checksum_t) boundary");
+
+    auto begin = reinterpret_cast<const checksum_t*>(&data);
+    auto end = begin + (sizeof(decltype(data)) / sizeof(checksum_t));
+
+    return std::accumulate(begin, end, 0, std::bit_xor<checksum_t>());
+}
+
+} // namespace details
+
+namespace partition
+{
+
+/** @class Table
+ *  @brief Generates virtual PNOR partition table.
+ *
+ *  Generates virtual PNOR partition table upon construction. Reads
+ *  the PNOR information generated by this tool :
+ *  github.com/openbmc/openpower-pnor-code-mgmt/blob/master/generate-squashfs,
+ *  which generates a minimalistic table-of-contents (toc) file and
+ *  individual files to represent various partitions that are of interest -
+ *  these help form the "virtual" PNOR, which is typically a subset of the full
+ *  PNOR image.
+ *  These files are stored in a well-known location on the PNOR.
+ *  Based on this information, this class prepares the partition table whose
+ *  structure is as outlined in pnor_partition.h.
+ *
+ *  The virtual PNOR supports 4KB erase blocks - partitions must be aligned to
+ *  this size.
+ */
+class Table
+{
+  public:
+    /** @brief Constructor accepting the path of the directory
+     *         that houses the PNOR partition files.
+     *
+     *  @param[in] ctx - Acquire sizes and paths relevant to the table
+     *
+     * Throws MalformedTocEntry, InvalidTocEntry
+     */
+    Table(const struct mbox_context* ctx);
+
+    Table(const Table&) = delete;
+    Table& operator=(const Table&) = delete;
+    Table(Table&&) = delete;
+    Table& operator=(Table&&) = delete;
+    ~Table() = default;
+
+    /** @brief Return the exact size of partition table in bytes
+     *
+     *  @returns size_t - size of partition table in bytes
+     */
+    size_t size() const
+    {
+        return szBytes;
+    }
+
+    /** @brief Return aligned size of partition table in bytes
+     *
+     *  The value returned will be greater-than or equal to size(), and
+     *  aligned to blockSize.
+     *
+     *  @returns size_t - capacity of partition table in bytes
+     */
+    size_t capacity() const
+    {
+        return align_up(szBytes, blockSize);
+    }
+
+    /** @brief Return the size of partition table in blocks
+     *
+     *  @returns size_t - size of partition table in blocks
+     */
+    size_t blocks() const
+    {
+        return capacity() / blockSize;
+    }
+
+    /** @brief Return a partition table having byte-ordering
+     *         that the host expects.
+     *
+     *  The host needs the partion table in big-endian.
+     *
+     *  @returns const reference to host partition table.
+     */
+    const pnor_partition_table& getHostTable() const
+    {
+        return *(reinterpret_cast<const pnor_partition_table*>(hostTbl.data()));
+    }
+
+    /** @brief Return a little-endian partition table
+     *
+     *  @returns const reference to native partition table
+     */
+    const pnor_partition_table& getNativeTable() const
+    {
+        return *(reinterpret_cast<const pnor_partition_table*>(tbl.data()));
+    }
+
+    /** @brief Return partition corresponding to PNOR offset, the offset
+     *         is within returned partition.
+     *
+     *  @param[in] offset - PNOR offset in bytes
+     *
+     *  @returns const reference to pnor_partition, if found, else an
+     *           exception will be thrown.
+     *
+     *  Throws: UnmappedOffset
+     */
+    const pnor_partition& partition(size_t offset) const;
+
+    /** @brief Return partition corresponding to input partition name.
+     *
+     *  @param[in] name - PNOR partition name
+     *
+     *  @returns const reference to pnor_partition, if found, else an
+     *           exception will be thrown.
+     *
+     *  Throws: UnknownPartition
+     */
+    const pnor_partition& partition(const std::string& name) const;
+
+  private:
+    /** @brief Prepares a vector of PNOR partition structures.
+     *
+     *  @param[in] ctx - An mbox context providing partition locations
+     *
+     * Throws: MalformedTocEntry, InvalidTocEntry
+     */
+    void preparePartitions(const struct mbox_context* ctx);
+
+    /** @brief Prepares the PNOR header.
+     */
+    void prepareHeader();
+
+    /** @brief Allocate memory to hold the partion table. Determine the
+     *         amount needed based on the partition files in the toc file.
+     *
+     *  @param[in] tocFile - Table of contents file path.
+     */
+    void allocateMemory(const fs::path& tocFile);
+
+    /** @brief Return a little-endian partition table
+     *
+     *  @returns reference to native partition table
+     */
+    pnor_partition_table& getNativeTable()
+    {
+        return *(reinterpret_cast<pnor_partition_table*>(tbl.data()));
+    }
+
+    /** @brief Size of the PNOR partition table -
+     *         sizeof(pnor_partition_table) +
+     *         (no. of partitions * sizeof(pnor_partition)),
+     */
+    size_t szBytes;
+
+    /** @brief Partition table */
+    PartitionTable tbl;
+
+    /** @brief Partition table with host byte ordering */
+    PartitionTable hostTbl;
+
+    /** @brief Directory housing generated PNOR partition files */
+    fs::path directory;
+
+    /** @brief Number of partitions */
+    size_t numParts;
+
+    /** @brief PNOR block size, in bytes */
+    size_t blockSize;
+
+    /** @brief PNOR size, in bytes */
+    size_t pnorSize;
+};
+} // namespace partition
+
+/** @brief An exception type storing a reason string.
+ *
+ *  This looks a lot like how std::runtime_error might be implemented however
+ *  we want to avoid extending it, as exceptions extending ReasonedError have
+ *  an expectation of being handled (can be predicted and are inside the scope
+ *  of the program).
+ *
+ *  From std::runtime_error documentation[1]:
+ *
+ *  > Defines a type of object to be thrown as exception. It reports errors
+ *  > that are due to events beyond the scope of the program and can not be
+ *  > easily predicted.
+ *
+ *  [1] http://en.cppreference.com/w/cpp/error/runtime_error
+ *
+ *  We need to keep the inheritance hierarchy separate: This avoids the
+ *  introduction of code that overzealously catches std::runtime_error to
+ *  handle exceptions that would otherwise derive ReasonedError, and in the
+ *  process swallows genuine runtime failures.
+ */
+class ReasonedError : public std::exception
+{
+  public:
+    ReasonedError(const std::string&& what) : _what(what)
+    {
+    }
+    const char* what() const noexcept
+    {
+        return _what.c_str();
+    };
+
+  private:
+    const std::string _what;
+};
+
+/** @brief Base exception type for errors related to ToC entry parsing.
+ *
+ *  Callers of parseTocEntry() may not be concerned with the specifics and
+ *  rather just want to extract and log what().
+ */
+class TocEntryError : public ReasonedError
+{
+  public:
+    TocEntryError(const std::string&& reason) : ReasonedError(std::move(reason))
+    {
+    }
+};
+
+/** @brief The exception thrown on finding a syntax error in the ToC entry
+ *
+ *  If the syntax is wrong, or expected values are missing, the ToC entry is
+ *  malformed
+ */
+class MalformedTocEntry : public TocEntryError
+{
+  public:
+    MalformedTocEntry(const std::string&& reason) :
+        TocEntryError(std::move(reason))
+    {
+    }
+};
+
+/** @brief The exception thrown on finding a semantic error in the ToC entry
+ *
+ *  If the syntax of the ToC entry is correct but the semantics are broken,
+ *  then we have an invalid ToC entry.
+ */
+class InvalidTocEntry : public TocEntryError
+{
+  public:
+    InvalidTocEntry(const std::string&& reason) :
+        TocEntryError(std::move(reason))
+    {
+    }
+};
+
+class UnmappedOffset : public std::exception
+{
+  public:
+    UnmappedOffset(size_t base, size_t next) : base(base), next(next)
+    {
+    }
+
+    const size_t base;
+    const size_t next;
+};
+
+class OutOfBoundsOffset : public ReasonedError
+{
+  public:
+    OutOfBoundsOffset(const std::string&& reason) :
+        ReasonedError(std::move(reason))
+    {
+    }
+};
+
+class UnknownPartition : public ReasonedError
+{
+  public:
+    UnknownPartition(const std::string&& reason) :
+        ReasonedError(std::move(reason))
+    {
+    }
+};
+
+} // namespace virtual_pnor
+} // namespace openpower
+
+struct vpnor_partition_table
+{
+    openpower::virtual_pnor::partition::Table* table;
+};
diff --git a/vpnor/test/Makefile.am.include b/vpnor/test/Makefile.am.include
new file mode 100644
index 0000000..819343b
--- /dev/null
+++ b/vpnor/test/Makefile.am.include
@@ -0,0 +1,250 @@
+TEST_MBOX_VPNOR_SRCS = \
+	common.c \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/tmpd.cpp
+
+TEST_MBOX_VPNOR_INTEG_SRCS = \
+	common.c \
+	mboxd_msg.c \
+	mboxd_windows.c \
+	mboxd_lpc.c \
+	vpnor/mboxd_lpc_reset.cpp \
+	vpnor/mboxd_pnor_partition_table.cpp \
+	vpnor/mboxd_flash.cpp \
+	vpnor/mboxd_msg.cpp \
+	vpnor/pnor_partition.cpp \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/tmpd.cpp
+
+VPNOR_LDADD = -lstdc++fs \
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS)
+
+vpnor_test_create_pnor_partition_table_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_pnor_partition_table.cpp
+vpnor_test_create_pnor_partition_table_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_pnor_partition_table_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_read_window_partition_exists_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_read_window_partition_exists.cpp
+vpnor_test_create_read_window_partition_exists_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_read_window_partition_exists_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_write_patch_SOURCES = \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	mtd.c \
+	%reldir%/write_patch.cpp
+vpnor_test_write_patch_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_write_patch_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_write_prsv_SOURCES = \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	mtd.c \
+	%reldir%/write_prsv.cpp
+vpnor_test_write_prsv_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_write_prsv_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_write_ro_SOURCES = \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	mtd.c \
+	%reldir%/write_ro.cpp
+vpnor_test_write_ro_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_write_ro_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_write_rw_SOURCES = \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	mtd.c \
+	%reldir%/write_rw.cpp
+vpnor_test_write_rw_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_write_rw_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_no_name_SOURCES = \
+	common.c \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/toc_no_name.cpp
+vpnor_test_toc_no_name_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_no_name_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_start_gt_end_SOURCES = \
+	common.c \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/toc_start_gt_end.cpp
+vpnor_test_toc_start_gt_end_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_start_gt_end_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_no_start_SOURCES = \
+	common.c \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/toc_no_start.cpp
+vpnor_test_toc_no_start_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_no_start_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_no_end_SOURCES = \
+	common.c \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/toc_no_end.cpp
+vpnor_test_toc_no_end_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_no_end_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_no_version_SOURCES = \
+	common.c \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/toc_no_version.cpp
+vpnor_test_toc_no_version_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_no_version_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_flags_SOURCES = \
+	common.c \
+	vpnor/pnor_partition_table.cpp \
+	%reldir%/toc_flags.cpp
+vpnor_test_toc_flags_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_flags_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_overlap_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/toc_overlap.cpp
+vpnor_test_toc_overlap_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_overlap_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_lookup_found_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/toc_lookup_found.cpp
+vpnor_test_toc_lookup_found_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_lookup_found_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_lookup_failed_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/toc_lookup_failed.cpp
+vpnor_test_toc_lookup_failed_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_lookup_failed_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_toc_missing_file_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/toc_missing_file.cpp
+vpnor_test_toc_missing_file_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_toc_missing_file_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_read_window_oob_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_read_window_oob.cpp
+vpnor_test_create_read_window_oob_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_read_window_oob_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_read_window_toc_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_read_window_toc.cpp
+vpnor_test_create_read_window_toc_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_read_window_toc_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_read_window_straddle_partitions_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_read_window_straddle_partitions.cpp
+vpnor_test_create_read_window_straddle_partitions_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_read_window_straddle_partitions_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_read_window_partition_invalid_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_read_window_partition_invalid.cpp
+vpnor_test_create_read_window_partition_invalid_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_read_window_partition_invalid_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_read_patch_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/read_patch.cpp
+vpnor_test_read_patch_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_read_patch_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_write_patch_resize_SOURCES = \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	mtd.c \
+	%reldir%/write_patch_resize.cpp
+vpnor_test_write_patch_resize_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_write_patch_resize_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_dump_flash_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/dump_flash.cpp
+vpnor_test_dump_flash_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_dump_flash_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_read_window_size_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_read_window_size.cpp
+vpnor_test_create_read_window_size_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_read_window_size_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_read_window_remap_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_read_window_remap.cpp
+vpnor_test_create_read_window_remap_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_read_window_remap_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_write_window_ro_partition_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_write_window_ro_partition.cpp
+vpnor_test_create_write_window_ro_partition_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_write_window_ro_partition_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_write_window_rw_partition_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_write_window_rw_partition.cpp
+vpnor_test_create_write_window_rw_partition_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_write_window_rw_partition_LDADD = $(VPNOR_LDADD)
+
+vpnor_test_create_write_window_unmapped_SOURCES = \
+	$(TEST_MOCK_SRCS) \
+	$(TEST_MBOX_VPNOR_INTEG_SRCS) \
+	%reldir%/create_write_window_unmapped.cpp
+vpnor_test_create_write_window_unmapped_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
+vpnor_test_create_write_window_unmapped_LDADD = $(VPNOR_LDADD)
+
+if VIRTUAL_PNOR_ENABLED
+check_PROGRAMS += \
+	%reldir%/create_pnor_partition_table \
+	%reldir%/create_read_window_partition_exists \
+	%reldir%/write_prsv \
+	%reldir%/write_ro \
+	%reldir%/write_rw \
+	%reldir%/write_patch \
+	%reldir%/toc_no_name \
+	%reldir%/toc_start_gt_end \
+	%reldir%/toc_no_start \
+	%reldir%/toc_no_end \
+	%reldir%/toc_no_version \
+	%reldir%/toc_flags \
+	%reldir%/toc_overlap \
+	%reldir%/toc_lookup_found \
+	%reldir%/toc_lookup_failed \
+	%reldir%/toc_missing_file \
+	%reldir%/create_read_window_oob \
+	%reldir%/create_read_window_toc \
+	%reldir%/create_read_window_straddle_partitions \
+	%reldir%/create_read_window_partition_invalid \
+	%reldir%/read_patch \
+	%reldir%/write_patch_resize \
+	%reldir%/dump_flash \
+	%reldir%/create_read_window_size \
+	%reldir%/create_read_window_remap \
+	%reldir%/create_write_window_ro_partition \
+	%reldir%/create_write_window_rw_partition \
+	%reldir%/create_write_window_unmapped
+endif
diff --git a/vpnor/test/create_pnor_partition_table.cpp b/vpnor/test/create_pnor_partition_table.cpp
new file mode 100644
index 0000000..3a46e38
--- /dev/null
+++ b/vpnor/test/create_pnor_partition_table.cpp
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/pnor_partition_table.hpp"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static const auto BLOCK_SIZE = 4 * 1024;
+static const auto ERASE_SIZE = BLOCK_SIZE;
+static const auto PNOR_SIZE = 64 * 1024 * 1024;
+static const auto MEM_SIZE = PNOR_SIZE;
+static const auto N_WINDOWS = 1;
+static const auto WINDOW_SIZE = BLOCK_SIZE;
+
+const std::string toc[] = {
+    "partition01=HBB,00000000,00001000,80,ECC,PRESERVED",
+};
+constexpr auto partitionName = "HBB";
+
+namespace test = openpower::virtual_pnor::test;
+
+int main()
+{
+    struct mbox_context* ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    const openpower::virtual_pnor::partition::Table table(ctx);
+
+    pnor_partition_table expectedTable{};
+    expectedTable.data.magic = PARTITION_HEADER_MAGIC;
+    expectedTable.data.version = PARTITION_VERSION_1;
+    expectedTable.data.size = 1;
+    expectedTable.data.entry_size = sizeof(pnor_partition);
+    expectedTable.data.entry_count = 1;
+    expectedTable.data.block_size = BLOCK_SIZE;
+    expectedTable.data.block_count =
+        (PNOR_SIZE) / expectedTable.data.block_size;
+    expectedTable.checksum =
+        openpower::virtual_pnor::details::checksum(expectedTable.data);
+
+    pnor_partition expectedPartition{};
+    strcpy(expectedPartition.data.name, partitionName);
+    expectedPartition.data.base = 0;
+    expectedPartition.data.size = 1;
+    expectedPartition.data.actual = 0x1000;
+    expectedPartition.data.id = 1;
+    expectedPartition.data.pid = PARENT_PATITION_ID;
+    expectedPartition.data.type = PARTITION_TYPE_DATA;
+    expectedPartition.data.flags = 0;
+    expectedPartition.data.user.data[0] = PARTITION_ECC_PROTECTED;
+    expectedPartition.data.user.data[1] |= PARTITION_PRESERVED;
+    expectedPartition.data.user.data[1] |= PARTITION_VERSION_CHECK_SHA512;
+    expectedPartition.checksum =
+        openpower::virtual_pnor::details::checksum(expectedPartition.data);
+
+    const pnor_partition_table& result = table.getNativeTable();
+
+    auto rc = memcmp(&expectedTable, &result, sizeof(pnor_partition_table));
+    assert(rc == 0);
+
+    rc = memcmp(&expectedPartition, &result.partitions[0],
+                sizeof(pnor_partition));
+    assert(rc == 0);
+
+    const pnor_partition& first = table.partition(0);
+    rc = memcmp(&first, &result.partitions[0], sizeof(pnor_partition));
+    assert(rc == 0);
+
+    return 0;
+}
diff --git a/vpnor/test/create_read_window_oob.cpp b/vpnor/test/create_read_window_oob.cpp
new file mode 100644
index 0000000..445b0a1
--- /dev/null
+++ b/vpnor/test/create_read_window_oob.cpp
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+const std::string toc[] = {
+    "partition01=HBB,00001000,00002000,80,ECC,READONLY",
+};
+
+static constexpr auto BLOCK_SIZE = 4096;
+static constexpr auto MEM_SIZE = (BLOCK_SIZE * 2);
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE;
+static constexpr auto PNOR_SIZE = (BLOCK_SIZE * 4);
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+/* Request access beyond the last (only) partition */
+static const uint8_t create_read_window[] = {0x04, 0x01, 0x03, 0x00, 0x01, 0x00,
+                                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                             0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response[] = {0x04, 0x01, 0xfe, 0xff, 0x01, 0x00, 0x03,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+
+    struct mbox_context *ctx;
+    int rc;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(ctx);
+
+    rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    rc = mbox_command_dispatch(ctx, create_read_window,
+                               sizeof(create_read_window));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    return 0;
+}
diff --git a/vpnor/test/create_read_window_partition_exists.cpp b/vpnor/test/create_read_window_partition_exists.cpp
new file mode 100644
index 0000000..12ac19b
--- /dev/null
+++ b/vpnor/test/create_read_window_partition_exists.cpp
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <experimental/filesystem>
+#include <fstream>
+#include <string.h>
+#include <vector>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+// A read window assumes that the toc is located at offset 0,
+// so create dummy partition at arbitrary offset 0x1000.
+const std::string toc[] = {
+    "partition01=HBB,00001000,00002000,80,ECC,READONLY",
+};
+
+static const uint8_t data[8] = {0xaa, 0x55, 0xaa, 0x66, 0x77, 0x88, 0x99, 0xab};
+
+static const auto BLOCK_SIZE = 4096;
+static const auto MEM_SIZE = BLOCK_SIZE * 2;
+static const auto ERASE_SIZE = BLOCK_SIZE;
+static const auto N_WINDOWS = 1;
+static const auto WINDOW_SIZE = BLOCK_SIZE;
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+// offset 0x100 and size 6
+static const uint8_t create_read_window[] = {0x04, 0x01, 0x01, 0x00, 0x01, 0x00,
+                                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                             0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response[] = {0x04, 0x01, 0xfe, 0xff, 0x01, 0x00, 0x01,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+
+namespace test = openpower::virtual_pnor::test;
+
+int main()
+{
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    root.write("HBB", data, sizeof(data));
+
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    // send the request for partition1
+    rc = mbox_command_dispatch(ctx, create_read_window,
+                               sizeof(create_read_window));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    // Compare the reserved memory to the pnor
+    rc = memcmp(ctx->mem, data, 6);
+    assert(rc == 0);
+
+    return rc;
+}
diff --git a/vpnor/test/create_read_window_partition_invalid.cpp b/vpnor/test/create_read_window_partition_invalid.cpp
new file mode 100644
index 0000000..9ed4347
--- /dev/null
+++ b/vpnor/test/create_read_window_partition_invalid.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+const std::string toc[] = {
+    "partition01=HBB,00002000,00003000,80,ECC,READONLY",
+};
+
+static constexpr auto BLOCK_SIZE = 4096;
+static constexpr auto MEM_SIZE = BLOCK_SIZE * 2;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE;
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+/* Request access below the specified partition */
+static const uint8_t create_read_window[] = {0x04, 0x01, 0x01, 0x00, 0x01, 0x00,
+                                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                             0x00, 0x00, 0x00, 0x00};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+
+    struct mbox_context *ctx;
+    int rc;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(ctx);
+
+    rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    rc = mbox_command_dispatch(ctx, create_read_window,
+                               sizeof(create_read_window));
+    return !(rc == 1);
+}
diff --git a/vpnor/test/create_read_window_remap.cpp b/vpnor/test/create_read_window_remap.cpp
new file mode 100644
index 0000000..ea319c1
--- /dev/null
+++ b/vpnor/test/create_read_window_remap.cpp
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <experimental/filesystem>
+#include <fstream>
+#include <string.h>
+#include <vector>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static const auto BLOCK_SIZE = 4096;
+static const auto ERASE_SIZE = BLOCK_SIZE;
+static const auto WINDOW_SIZE = 16 * BLOCK_SIZE;
+static const auto N_WINDOWS = 2;
+static const auto MEM_SIZE = N_WINDOWS * WINDOW_SIZE;
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00011000,80,ECC,READONLY",
+};
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+namespace test = openpower::virtual_pnor::test;
+
+int main()
+{
+    uint8_t request[] = {0x04, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+    uint8_t response[] = {0x04, 0x01, 0xe0, 0xff, 0x10, 0x00, 0x01,
+                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    for (int i = 1; i < (0x10000 / BLOCK_SIZE); i++)
+    {
+        /* Update the requested offset */
+        put_u16(&request[2], i);
+
+        /*
+         * Reuse the offset as a sequence number, because it's unique. Request
+         * and response have the same sequence number
+         */
+        request[1] = i;
+        response[1] = i;
+
+        rc = mbox_command_dispatch(ctx, request, sizeof(request));
+        assert(rc == 1);
+
+        /* Check that it maps to the same window each time */
+        rc = mbox_cmp(ctx, response, sizeof(response));
+        assert(rc == 0);
+    }
+
+    return 0;
+}
diff --git a/vpnor/test/create_read_window_size.cpp b/vpnor/test/create_read_window_size.cpp
new file mode 100644
index 0000000..1837546
--- /dev/null
+++ b/vpnor/test/create_read_window_size.cpp
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <experimental/filesystem>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+static const auto BLOCK_SIZE = 4096;
+static const auto ERASE_SIZE = BLOCK_SIZE;
+static const auto WINDOW_SIZE = 2 * BLOCK_SIZE;
+static const auto MEM_SIZE = WINDOW_SIZE;
+static const auto N_WINDOWS = 1;
+static const auto PNOR_SIZE = 4 * BLOCK_SIZE;
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00002000,80,ECC,READONLY",
+    "partition02=TWO,00002000,00004000,80,ECC,READONLY",
+};
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t request_one[] = {0x04, 0x01, 0x01, 0x00, 0x02, 0x00,
+                                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                      0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response_one[] = {0x04, 0x01, 0xfe, 0xff, 0x01,
+                                       0x00, 0x01, 0x00, 0x00, 0x00,
+                                       0x00, 0x00, 0x00, 0x01};
+
+static const uint8_t request_two[] = {0x04, 0x02, 0x02, 0x00, 0x02, 0x00,
+                                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                      0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response_two[] = {0x04, 0x02, 0xfe, 0xff, 0x02,
+                                       0x00, 0x02, 0x00, 0x00, 0x00,
+                                       0x00, 0x00, 0x00, 0x01};
+
+namespace test = openpower::virtual_pnor::test;
+
+int main()
+{
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    rc = mbox_command_dispatch(ctx, request_one, sizeof(request_one));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response_one, sizeof(response_one));
+    assert(rc == 0);
+
+    rc = mbox_command_dispatch(ctx, request_two, sizeof(request_two));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response_two, sizeof(response_two));
+    assert(rc == 0);
+
+    return rc;
+}
diff --git a/vpnor/test/create_read_window_straddle_partitions.cpp b/vpnor/test/create_read_window_straddle_partitions.cpp
new file mode 100644
index 0000000..e396088
--- /dev/null
+++ b/vpnor/test/create_read_window_straddle_partitions.cpp
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00002000,80,ECC,READONLY",
+    "partition02=TWO,00002000,00003000,80,ECC,READONLY",
+};
+
+uint8_t data[8] = {0xaa, 0x55, 0xaa, 0x66, 0x77, 0x88, 0x99, 0xab};
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto MEM_SIZE = BLOCK_SIZE * 2;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = MEM_SIZE;
+static constexpr auto PNOR_SIZE = MEM_SIZE * 2;
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t create_read_window[] = {0x04, 0x01, 0x01, 0x00, 0x02, 0x00,
+                                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                             0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response[] = {
+    0x04, 0x01, 0xfe, 0xff, 0x01, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+    namespace fs = std::experimental::filesystem;
+
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    // Request a read window that would cover both partitions. With the current
+    // behaviour, we expect to receive a reply describing a window that covers
+    // the first partition but is limited in size to exclude the second
+    // partition.
+    rc = mbox_command_dispatch(ctx, create_read_window,
+                               sizeof(create_read_window));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    return 0;
+}
diff --git a/vpnor/test/create_read_window_toc.cpp b/vpnor/test/create_read_window_toc.cpp
new file mode 100644
index 0000000..32f6523
--- /dev/null
+++ b/vpnor/test/create_read_window_toc.cpp
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include <assert.h>
+#include <sys/mman.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/pnor_partition_table.hpp"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 4 * 1024;
+static constexpr auto PNOR_SIZE = 64 * 1024 * 1024;
+static constexpr auto MEM_SIZE = BLOCK_SIZE * 2;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE;
+static constexpr auto TOC_PART_SIZE = BLOCK_SIZE;
+
+const std::string toc[] = {
+    "partition00=part,00000000,00001000,80,READONLY",
+    "partition01=ONE,00001000,00002000,80,READWRITE",
+};
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+/* Request access to the ToC base for one block */
+static const uint8_t create_read_window[] = {0x04, 0x01, 0x00, 0x00, 0x01, 0x00,
+                                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                             0x00, 0x00, 0x00, 0x00};
+
+/* Expect a response containing the ToC in one block */
+static const uint8_t response[] = {
+    0x04, 0x01, 0xfe, 0xff, 0x01, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct mbox_context* ctx;
+    int rc;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    vpnor::partition::Table table(ctx);
+
+    /* Make sure the ToC exactly fits in the space allocated for it */
+    assert(table.capacity() == TOC_PART_SIZE);
+
+    init_vpnor_from_paths(ctx);
+
+    rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    rc = mbox_command_dispatch(ctx, create_read_window,
+                               sizeof(create_read_window));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    /* Ensure our partition table is present and as expected */
+    const pnor_partition_table& toc = table.getHostTable();
+    rc = memcmp(ctx->mem, &toc, table.size());
+    assert(rc == 0);
+
+    return 0;
+}
diff --git a/vpnor/test/create_write_window_ro_partition.cpp b/vpnor/test/create_write_window_ro_partition.cpp
new file mode 100644
index 0000000..09dbba0
--- /dev/null
+++ b/vpnor/test/create_write_window_ro_partition.cpp
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+const std::string toc[] = {
+    "partition01=HBB,00001000,00002000,80,ECC,READONLY",
+};
+
+static const auto BLOCK_SIZE = 4096;
+static const auto MEM_SIZE = BLOCK_SIZE * 2;
+static const auto ERASE_SIZE = BLOCK_SIZE;
+static const auto N_WINDOWS = 1;
+static const auto WINDOW_SIZE = BLOCK_SIZE;
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+// offset 0x100 and size 6
+static const uint8_t create_write_window[] = {
+    0x06, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response[] = {0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07};
+
+namespace test = openpower::virtual_pnor::test;
+
+int main()
+{
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    rc = mbox_command_dispatch(ctx, create_write_window,
+                               sizeof(create_write_window));
+    assert(rc == 7);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    return rc;
+}
diff --git a/vpnor/test/create_write_window_rw_partition.cpp b/vpnor/test/create_write_window_rw_partition.cpp
new file mode 100644
index 0000000..74b5831
--- /dev/null
+++ b/vpnor/test/create_write_window_rw_partition.cpp
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+const std::string toc[] = {
+    "partition01=HBB,00001000,00002000,80,ECC,READWRITE",
+};
+
+static const auto BLOCK_SIZE = 4096;
+static const auto MEM_SIZE = BLOCK_SIZE * 2;
+static const auto ERASE_SIZE = BLOCK_SIZE;
+static const auto N_WINDOWS = 1;
+static const auto WINDOW_SIZE = BLOCK_SIZE;
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+// offset 0x100 and size 6
+static const uint8_t create_write_window[] = {
+    0x06, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response[] = {0x06, 0x01, 0xfe, 0xff, 0x01, 0x00, 0x01,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+
+namespace test = openpower::virtual_pnor::test;
+
+int main()
+{
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    rc = mbox_command_dispatch(ctx, create_write_window,
+                               sizeof(create_write_window));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    return rc;
+}
diff --git a/vpnor/test/create_write_window_unmapped.cpp b/vpnor/test/create_write_window_unmapped.cpp
new file mode 100644
index 0000000..47ba57e
--- /dev/null
+++ b/vpnor/test/create_write_window_unmapped.cpp
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+
+#include "config.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE;
+static constexpr auto MEM_SIZE = WINDOW_SIZE;
+static constexpr auto PNOR_SIZE = 3 * BLOCK_SIZE;
+
+const std::string toc[] = {
+    "partition01=HBB,00001000,00002000,80,ECC,READWRITE",
+};
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+// offset 0x100 and size 6
+static const uint8_t create_write_window[] = {
+    0x06, 0x01, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response[] = {0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07};
+
+namespace test = openpower::virtual_pnor::test;
+
+int main()
+{
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == MBOX_R_SUCCESS);
+
+    rc = mbox_command_dispatch(ctx, create_write_window,
+                               sizeof(create_write_window));
+    assert(rc == MBOX_R_WINDOW_ERROR);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    return rc;
+}
diff --git a/vpnor/test/dump_flash.cpp b/vpnor/test/dump_flash.cpp
new file mode 100644
index 0000000..e811f6f
--- /dev/null
+++ b/vpnor/test/dump_flash.cpp
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "mboxd_msg.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+struct test_context
+{
+    uint8_t seq;
+    struct mbox_context *ctx;
+};
+
+// Configure the system and the paritions such that we eventually request a
+// window that covers the last section of flash, but the remaining flash is
+// smaller than the window size
+static constexpr auto BLOCK_SIZE = 4096;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto N_WINDOWS = 3;
+static constexpr auto WINDOW_SIZE = 2 * BLOCK_SIZE;
+static constexpr auto MEM_SIZE = N_WINDOWS * WINDOW_SIZE;
+static constexpr auto PNOR_SIZE = (4 * BLOCK_SIZE);
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00003000,80,ECC,READONLY",
+};
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+static constexpr auto MBOX_CREATE_READ_WINDOW = 4;
+
+static int mbox_create_read_window(struct test_context *tctx, size_t offset,
+                                   size_t len)
+{
+    union mbox_regs regs;
+
+    memset(&regs, 0, sizeof(regs));
+    regs.msg.command = MBOX_CREATE_READ_WINDOW;
+    regs.msg.seq = ++tctx->seq;
+    put_u16(&regs.msg.args[0], offset);
+    put_u16(&regs.msg.args[2], len);
+
+    return mbox_command_dispatch(tctx->ctx, regs.raw, sizeof(regs.raw));
+}
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+
+    struct test_context _tctx = {0}, *tctx = &_tctx;
+    size_t len;
+    size_t pos;
+    int rc;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+
+    tctx->ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(tctx->ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(tctx->ctx);
+
+    rc = mbox_command_dispatch(tctx->ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    pos = 0;
+    while (pos < (PNOR_SIZE / BLOCK_SIZE))
+    {
+        struct mbox_msg _msg, *msg = &_msg;
+
+        rc = mbox_create_read_window(tctx, pos, (WINDOW_SIZE / BLOCK_SIZE));
+        assert(rc == 1);
+
+        mbox_rspcpy(tctx->ctx, msg);
+
+        len = get_u16(&msg->args[2]);
+        pos = get_u16(&msg->args[4]) + len;
+    }
+
+    return 0;
+}
diff --git a/vpnor/test/read_patch.cpp b/vpnor/test/read_patch.cpp
new file mode 100644
index 0000000..31a422e
--- /dev/null
+++ b/vpnor/test/read_patch.cpp
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+#include "common.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
+
+#include <assert.h>
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto PART_SIZE = BLOCK_SIZE * 4;
+static constexpr auto PATCH_SIZE = PART_SIZE / 2;
+static constexpr auto N_WINDOWS = 2;
+static constexpr auto WINDOW_SIZE = PART_SIZE * 8;
+static constexpr auto MEM_SIZE = WINDOW_SIZE * N_WINDOWS;
+static constexpr auto PNOR_SIZE = MEM_SIZE * 2;
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00005000,80,ECC,READONLY",
+};
+
+static const uint8_t get_info[] = {0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                   0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t create_read_window[] = {0x04, 0x01, 0x01, 0x00, 0x04, 0x00,
+                                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                             0x00, 0x00, 0x00, 0x00};
+
+static const uint8_t response[] = {0x04, 0x01, 0xc0, 0xff, 0x04, 0x00, 0x01,
+                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+
+    struct mbox_context *ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(PNOR_SIZE, ERASE_SIZE);
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+
+    // PATCH_SIZE is smaller than the size of the partition we defined. This
+    // test ensures that mboxd will behave correctly when we request an offset
+    // that is beyond the size of the backing file, but is in the set of valid
+    // offsets for the partition as defined by the ToC.
+    std::vector<uint8_t> patch(PATCH_SIZE, 0xff);
+    root.patch("ONE", patch.data(), patch.size());
+
+    init_vpnor_from_paths(ctx);
+
+    int rc = mbox_command_dispatch(ctx, get_info, sizeof(get_info));
+    assert(rc == 1);
+
+    rc = mbox_command_dispatch(ctx, create_read_window,
+                               sizeof(create_read_window));
+    assert(rc == 1);
+
+    rc = mbox_cmp(ctx, response, sizeof(response));
+    assert(rc == 0);
+
+    return 0;
+}
diff --git a/vpnor/test/tmpd.cpp b/vpnor/test/tmpd.cpp
new file mode 100644
index 0000000..723bf56
--- /dev/null
+++ b/vpnor/test/tmpd.cpp
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "vpnor/test/tmpd.hpp"
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+namespace test
+{
+
+namespace fs = std::experimental::filesystem;
+
+size_t VpnorRoot::write(const std::string &name, const void *data, size_t len)
+{
+    // write() is for test environment setup - always write to ro section
+    fs::path path = root / "ro" / name;
+
+    if (!fs::exists(path))
+        /* It's not in the ToC */
+        throw std::invalid_argument(name);
+
+    std::ofstream(path).write((const char *)data, len);
+
+    return len;
+}
+
+size_t VpnorRoot::patch(const std::string &name, const void *data, size_t len)
+{
+    if (!fs::exists(root / "ro" / name))
+        /* It's not in the ToC */
+        throw std::invalid_argument(name);
+
+    std::ofstream(root / "patch" / name).write((const char *)data, len);
+
+    return len;
+}
+
+} // test
+} // virtual_pnor
+} // openpower
diff --git a/vpnor/test/tmpd.hpp b/vpnor/test/tmpd.hpp
new file mode 100644
index 0000000..45260ca
--- /dev/null
+++ b/vpnor/test/tmpd.hpp
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright (C) 2018 IBM Corp. */
+
+#include <assert.h>
+#include <string.h>
+#include <vector>
+#include <fstream>
+#include <experimental/filesystem>
+
+#include "config.h"
+#include "mbox.h"
+#include "vpnor/pnor_partition_table.hpp"
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+namespace test
+{
+
+namespace fs = std::experimental::filesystem;
+
+class VpnorRoot
+{
+  public:
+    template <std::size_t N>
+    VpnorRoot(struct mbox_context* ctx, const std::string (&toc)[N],
+              size_t blockSize)
+    {
+        char tmplt[] = "/tmp/vpnor_root.XXXXXX";
+        char* tmpdir = mkdtemp(tmplt);
+        root = fs::path{tmpdir};
+
+        for (const auto& attr : attributes)
+        {
+            fs::create_directory(root / attr);
+        }
+
+        fs::path tocFilePath = root / "ro" / PARTITION_TOC_FILE;
+
+        for (const std::string& line : toc)
+        {
+            pnor_partition part;
+
+            openpower::virtual_pnor::parseTocLine(line, blockSize, part);
+
+            /* Populate the partition in the tree */
+            std::vector<char> zeroed(part.data.actual, 0);
+            fs::path partitionFilePath = root / "ro" / part.data.name;
+            std::ofstream(partitionFilePath)
+                .write(zeroed.data(), zeroed.size());
+
+            /* Update the ToC if the partition file was created */
+            std::ofstream(tocFilePath, std::ofstream::app) << line << "\n";
+        }
+
+        strncpy(ctx->paths.ro_loc, ro().c_str(), PATH_MAX - 1);
+        ctx->paths.ro_loc[PATH_MAX - 1] = '\0';
+        strncpy(ctx->paths.rw_loc, rw().c_str(), PATH_MAX - 1);
+        ctx->paths.rw_loc[PATH_MAX - 1] = '\0';
+        strncpy(ctx->paths.prsv_loc, prsv().c_str(), PATH_MAX - 1);
+        ctx->paths.prsv_loc[PATH_MAX - 1] = '\0';
+        strncpy(ctx->paths.patch_loc, patch().c_str(), PATH_MAX - 1);
+        ctx->paths.patch_loc[PATH_MAX - 1] = '\0';
+    }
+
+    VpnorRoot(const VpnorRoot&) = delete;
+    VpnorRoot& operator=(const VpnorRoot&) = delete;
+    VpnorRoot(VpnorRoot&&) = delete;
+    VpnorRoot& operator=(VpnorRoot&&) = delete;
+
+    ~VpnorRoot()
+    {
+        fs::remove_all(root);
+    }
+    fs::path ro()
+    {
+        return fs::path{root} / "ro";
+    }
+    fs::path rw()
+    {
+        return fs::path{root} / "rw";
+    }
+    fs::path prsv()
+    {
+        return fs::path{root} / "prsv";
+    }
+    fs::path patch()
+    {
+        return fs::path{root} / "patch";
+    }
+    size_t write(const std::string& name, const void* data, size_t len);
+    size_t patch(const std::string& name, const void* data, size_t len);
+
+  private:
+    fs::path root;
+    const std::string attributes[4] = {"ro", "rw", "prsv", "patch"};
+};
+
+} // test
+} // virtual_pnor
+} // openpower
diff --git a/vpnor/test/toc_flags.cpp b/vpnor/test/toc_flags.cpp
new file mode 100644
index 0000000..c30f7ad
--- /dev/null
+++ b/vpnor/test/toc_flags.cpp
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+#include <assert.h>
+
+#include "common.h"
+#include "vpnor/pnor_partition_defs.h"
+#include "vpnor/pnor_partition_table.hpp"
+
+static constexpr auto BLOCK_SIZE = 4 * 1024;
+static constexpr auto DATA_MASK = ((1 << 24) - 1);
+
+int main()
+{
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct pnor_partition part;
+    std::string line;
+
+    mbox_vlog = mbox_log_console;
+    verbosity = MBOX_LOG_DEBUG;
+
+    line = "partition01=FOO,00001000,00002000,80,ECC";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert((part.data.user.data[0]) == PARTITION_ECC_PROTECTED);
+    assert(!(part.data.user.data[1] & DATA_MASK));
+
+    line = "partition01=FOO,00001000,00002000,80,PRESERVED";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert((part.data.user.data[1] & DATA_MASK) == PARTITION_PRESERVED);
+
+    line = "partition01=FOO,00001000,00002000,80,READONLY";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert((part.data.user.data[1] & DATA_MASK) == PARTITION_READONLY);
+
+    /* BACKUP is unimplemented */
+    line = "partition01=FOO,00001000,00002000,80,BACKUP";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert(!(part.data.user.data[1] & DATA_MASK));
+
+    line = "partition01=FOO,00001000,00002000,80,REPROVISION";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert((part.data.user.data[1] & DATA_MASK) == PARTITION_REPROVISION);
+
+    line = "partition01=FOO,00001000,00002000,80,VOLATILE";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert((part.data.user.data[1] & DATA_MASK) == PARTITION_VOLATILE);
+
+    line = "partition01=FOO,00001000,00002000,80,CLEARECC";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert((part.data.user.data[1] & DATA_MASK) == PARTITION_CLEARECC);
+
+    line = "partition01=FOO,00001000,00002000,80,READWRITE";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert(((part.data.user.data[1] & DATA_MASK) ^ PARTITION_READONLY) ==
+           PARTITION_READONLY);
+
+    line = "partition01=FOO,00001000,00002000,80,";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert(!(part.data.user.data[1] & DATA_MASK));
+
+    line = "partition01=FOO,00001000,00002000,80,junk";
+    vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    assert(!(part.data.user.data[0]));
+    assert(!(part.data.user.data[1] & DATA_MASK));
+
+    return 0;
+}
diff --git a/vpnor/test/toc_lookup_failed.cpp b/vpnor/test/toc_lookup_failed.cpp
new file mode 100644
index 0000000..199a00b
--- /dev/null
+++ b/vpnor/test/toc_lookup_failed.cpp
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/pnor_partition_table.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto PNOR_SIZE = 64 * 1024 * 1024;
+static constexpr auto MEM_SIZE = 32 * 1024 * 1024;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE * 2;
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00002000,80,",
+};
+
+int main()
+{
+    namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
+    namespace test = openpower::virtual_pnor::test;
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct mbox_context* ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    vpnor::partition::Table table(ctx);
+
+    try
+    {
+        table.partition("TWO");
+    }
+    catch (vpnor::UnknownPartition& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/toc_lookup_found.cpp b/vpnor/test/toc_lookup_found.cpp
new file mode 100644
index 0000000..411e3f8
--- /dev/null
+++ b/vpnor/test/toc_lookup_found.cpp
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/pnor_partition_table.hpp"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto PNOR_SIZE = 64 * 1024 * 1024;
+static constexpr auto MEM_SIZE = 32 * 1024 * 1024;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE * 2;
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00002000,80,",
+    "partition02=TWO,00002000,00004000,80,",
+    "partition03=THREE,00004000,00008000,80,",
+};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct mbox_context* ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    vpnor::partition::Table table(ctx);
+
+    const struct pnor_partition& part = table.partition("TWO");
+    assert(part.data.id == 2);
+    assert(part.data.base == 2);
+    assert(part.data.size == 2);
+
+    return 0;
+}
diff --git a/vpnor/test/toc_missing_file.cpp b/vpnor/test/toc_missing_file.cpp
new file mode 100644
index 0000000..821653f
--- /dev/null
+++ b/vpnor/test/toc_missing_file.cpp
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/pnor_partition_table.hpp"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto PNOR_SIZE = 64 * 1024 * 1024;
+static constexpr auto MEM_SIZE = 32 * 1024 * 1024;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE * 2;
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00002000,80,",
+    "partition02=TWO,00002000,00003000,80,",
+};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+    namespace fs = std::experimental::filesystem;
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct mbox_context* ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+
+    fs::remove(root.ro() / "TWO");
+
+    try
+    {
+        vpnor::partition::Table table(ctx);
+    }
+    catch (vpnor::InvalidTocEntry& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/toc_no_end.cpp b/vpnor/test/toc_no_end.cpp
new file mode 100644
index 0000000..f9c5187
--- /dev/null
+++ b/vpnor/test/toc_no_end.cpp
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+#include <assert.h>
+
+#include "vpnor/pnor_partition_table.hpp"
+
+static constexpr auto BLOCK_SIZE = 4 * 1024;
+
+int main()
+{
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct pnor_partition part;
+    std::string line;
+
+    line = "partition01=FOO,00001000,,80,ECC,PRESERVED";
+    try
+    {
+        openpower::virtual_pnor::parseTocLine(line, BLOCK_SIZE, part);
+    }
+    catch (vpnor::MalformedTocEntry& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/toc_no_name.cpp b/vpnor/test/toc_no_name.cpp
new file mode 100644
index 0000000..d62056f
--- /dev/null
+++ b/vpnor/test/toc_no_name.cpp
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+#include <assert.h>
+
+#include "vpnor/pnor_partition_table.hpp"
+
+static constexpr auto BLOCK_SIZE = 4 * 1024;
+
+int main()
+{
+    namespace vpnor = openpower::virtual_pnor;
+
+    const std::string line = "partition01=,00000000,00000400,80,ECC,PRESERVED";
+    struct pnor_partition part;
+
+    try
+    {
+        vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    }
+    catch (vpnor::MalformedTocEntry& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/toc_no_start.cpp b/vpnor/test/toc_no_start.cpp
new file mode 100644
index 0000000..1d20a46
--- /dev/null
+++ b/vpnor/test/toc_no_start.cpp
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+#include <assert.h>
+
+#include "vpnor/pnor_partition_table.hpp"
+
+static constexpr auto BLOCK_SIZE = 4 * 1024;
+
+int main()
+{
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct pnor_partition part;
+    std::string line;
+
+    line = "partition01=FOO,,00001000,80,ECC,PRESERVED";
+    try
+    {
+        openpower::virtual_pnor::parseTocLine(line, BLOCK_SIZE, part);
+    }
+    catch (vpnor::MalformedTocEntry& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/toc_no_version.cpp b/vpnor/test/toc_no_version.cpp
new file mode 100644
index 0000000..68cb057
--- /dev/null
+++ b/vpnor/test/toc_no_version.cpp
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+#include <assert.h>
+
+#include "vpnor/pnor_partition_table.hpp"
+
+static constexpr auto BLOCK_SIZE = 4 * 1024;
+
+int main()
+{
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct pnor_partition part;
+    std::string line;
+
+    line = "partition01=FOO,00001000,00002000,,ECC,PRESERVED";
+    try
+    {
+        openpower::virtual_pnor::parseTocLine(line, BLOCK_SIZE, part);
+    }
+    catch (vpnor::MalformedTocEntry& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/toc_overlap.cpp b/vpnor/test/toc_overlap.cpp
new file mode 100644
index 0000000..94d071c
--- /dev/null
+++ b/vpnor/test/toc_overlap.cpp
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+#include <assert.h>
+#include <string.h>
+
+#include "config.h"
+#include "vpnor/pnor_partition_table.hpp"
+
+extern "C" {
+#include "test/mbox.h"
+#include "test/system.h"
+}
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto ERASE_SIZE = BLOCK_SIZE;
+static constexpr auto PNOR_SIZE = 64 * 1024 * 1024;
+static constexpr auto MEM_SIZE = 32 * 1024 * 1024;
+static constexpr auto N_WINDOWS = 1;
+static constexpr auto WINDOW_SIZE = BLOCK_SIZE * 2;
+
+const std::string toc[] = {
+    "partition01=ONE,00001000,00003000,80,",
+    "partition02=TWO,00002000,00004000,80,",
+};
+
+int main()
+{
+    namespace test = openpower::virtual_pnor::test;
+    namespace vpnor = openpower::virtual_pnor;
+
+    struct mbox_context* ctx;
+
+    system_set_reserved_size(MEM_SIZE);
+    system_set_mtd_sizes(MEM_SIZE, ERASE_SIZE);
+
+    ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+
+    try
+    {
+        vpnor::partition::Table table(ctx);
+    }
+    catch (vpnor::InvalidTocEntry& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/toc_start_gt_end.cpp b/vpnor/test/toc_start_gt_end.cpp
new file mode 100644
index 0000000..69edc63
--- /dev/null
+++ b/vpnor/test/toc_start_gt_end.cpp
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include "config.h"
+#include <assert.h>
+
+#include "vpnor/pnor_partition_table.hpp"
+
+static constexpr auto BLOCK_SIZE = 4 * 1024;
+
+int main()
+{
+    namespace vpnor = openpower::virtual_pnor;
+
+    std::string line = "partition01=FOO,00002000,00001000,80,ECC,PRESERVED";
+    pnor_partition part;
+
+    try
+    {
+        vpnor::parseTocLine(line, BLOCK_SIZE, part);
+    }
+    catch (vpnor::InvalidTocEntry& e)
+    {
+        return 0;
+    }
+
+    assert(false);
+}
diff --git a/vpnor/test/write_patch.cpp b/vpnor/test/write_patch.cpp
new file mode 100644
index 0000000..3b2b16a
--- /dev/null
+++ b/vpnor/test/write_patch.cpp
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <experimental/filesystem>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syslog.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "mbox.h"
+#include "mboxd_flash.h"
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto DATA_SIZE = 8;
+
+const uint8_t data[DATA_SIZE] = {0xa0, 0xa1, 0xa2, 0xa3,
+                                 0xa4, 0xa5, 0xa6, 0xa7};
+
+const std::string toc[] = {
+    "partition01=TEST1,00001000,00002000,80,ECC,READWRITE",
+};
+
+int main(void)
+{
+    namespace fs = std::experimental::filesystem;
+    namespace test = openpower::virtual_pnor::test;
+
+    struct mbox_context _ctx, *ctx = &_ctx;
+    char src[DATA_SIZE]{0};
+    void *map;
+    int rc;
+    int fd;
+
+    /* Setup */
+    memset(ctx, 0, sizeof(mbox_context));
+
+    mbox_vlog = &mbox_log_console;
+    verbosity = (verbose)2;
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    root.write("TEST1", data, sizeof(data));
+    /* write_flash doesn't copy the file for us */
+    assert(fs::copy_file(root.ro() / "TEST1", root.rw() / "TEST1"));
+    fs::path patch = root.patch() / "TEST1";
+    assert(fs::copy_file(root.ro() / "TEST1", patch));
+
+    init_vpnor_from_paths(ctx);
+
+    /* Test */
+    memset(src, 0x33, sizeof(src));
+    rc = write_flash(ctx, 0x1000, src, sizeof(src));
+    assert(rc == 0);
+
+    /* Check that RW file is unmodified after the patch write */
+    fd = open((root.rw() / "TEST1").c_str(), O_RDONLY);
+    map = mmap(NULL, sizeof(src), PROT_READ, MAP_SHARED, fd, 0);
+    assert(map != MAP_FAILED);
+    rc = memcmp(data, map, sizeof(src));
+    assert(rc == 0);
+    munmap(map, sizeof(src));
+    close(fd);
+
+    /* Check that PATCH is modified with the new data */
+    fd = open(patch.c_str(), O_RDONLY);
+    map = mmap(NULL, sizeof(src), PROT_READ, MAP_SHARED, fd, 0);
+    assert(map != MAP_FAILED);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+    munmap(map, sizeof(src));
+    close(fd);
+
+    destroy_vpnor(ctx);
+    free(ctx->flash_bmap);
+
+    return rc;
+}
diff --git a/vpnor/test/write_patch_resize.cpp b/vpnor/test/write_patch_resize.cpp
new file mode 100644
index 0000000..8d67683
--- /dev/null
+++ b/vpnor/test/write_patch_resize.cpp
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <experimental/filesystem>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syslog.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "mbox.h"
+#include "mboxd_flash.h"
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+static constexpr auto PART_SIZE = BLOCK_SIZE;
+static constexpr auto PATCH_SIZE = BLOCK_SIZE / 2;
+static constexpr auto UPDATE_SIZE = BLOCK_SIZE;
+
+const std::string toc[] = {
+    "partition01=TEST1,00001000,00002000,80,ECC,READWRITE",
+};
+
+int main(void)
+{
+    namespace fs = std::experimental::filesystem;
+    namespace test = openpower::virtual_pnor::test;
+
+    struct mbox_context _ctx, *ctx = &_ctx;
+    void *map;
+    int rc;
+    int fd;
+
+    /* Setup */
+    memset(ctx, 0, sizeof(mbox_context));
+
+    mbox_vlog = &mbox_log_console;
+    verbosity = (verbose)2;
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    std::vector<uint8_t> roContent(PART_SIZE, 0xff);
+    root.write("TEST1", roContent.data(), roContent.size());
+    /* write_flash doesn't copy the file for us */
+    std::vector<uint8_t> patchContent(PATCH_SIZE, 0xaa);
+    root.patch("TEST1", patchContent.data(), patchContent.size());
+
+    init_vpnor_from_paths(ctx);
+
+    /* Test */
+    std::vector<uint8_t> update(UPDATE_SIZE, 0x55);
+    rc = write_flash(ctx, 0x1000, update.data(), update.size());
+    assert(rc == 0);
+
+    /* Check that PATCH is modified with the new data */
+    fs::path patch = root.patch() / "TEST1";
+    assert(UPDATE_SIZE == fs::file_size(patch));
+    fd = open(patch.c_str(), O_RDONLY);
+    map = mmap(NULL, UPDATE_SIZE, PROT_READ, MAP_SHARED, fd, 0);
+    assert(map != MAP_FAILED);
+    rc = memcmp(update.data(), map, update.size());
+    assert(rc == 0);
+    munmap(map, update.size());
+    close(fd);
+
+    destroy_vpnor(ctx);
+    free(ctx->flash_bmap);
+
+    return rc;
+}
diff --git a/vpnor/test/write_prsv.cpp b/vpnor/test/write_prsv.cpp
new file mode 100644
index 0000000..6e0ca0f
--- /dev/null
+++ b/vpnor/test/write_prsv.cpp
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "mbox.h"
+#include "mboxd_flash.h"
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+
+const std::string toc[] = {
+    "partition01=TEST1,00001000,00002000,80,ECC,PRESERVED",
+};
+
+namespace test = openpower::virtual_pnor::test;
+
+int main(void)
+{
+    namespace fs = std::experimental::filesystem;
+
+    struct mbox_context _ctx, *ctx = &_ctx;
+    uint8_t src[8];
+    void *map;
+    int fd;
+    int rc;
+
+    /* Setup */
+    memset(ctx, 0, sizeof(mbox_context));
+
+    mbox_vlog = &mbox_log_console;
+    verbosity = (verbose)2;
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(ctx);
+
+    /* Test */
+    memset(src, 0xaa, sizeof(src));
+    rc = write_flash(ctx, 0x1000, src, sizeof(src));
+    assert(rc == 0);
+
+    /* Verify */
+    fd = open((root.prsv() / "TEST1").c_str(), O_RDONLY);
+    assert(fd >= 0);
+    map = mmap(NULL, sizeof(src), PROT_READ, MAP_SHARED, fd, 0);
+    assert(map != MAP_FAILED);
+
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+    munmap(map, sizeof(src));
+    close(fd);
+
+    /* Cleanup */
+    destroy_vpnor(ctx);
+
+    return 0;
+}
diff --git a/vpnor/test/write_ro.cpp b/vpnor/test/write_ro.cpp
new file mode 100644
index 0000000..53eeb18
--- /dev/null
+++ b/vpnor/test/write_ro.cpp
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "mbox.h"
+#include "mboxd_flash.h"
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+
+const std::string toc[] = {
+    "partition01=TEST1,00001000,00002000,80,ECC,READONLY",
+};
+
+int main(void)
+{
+    namespace fs = std::experimental::filesystem;
+    namespace test = openpower::virtual_pnor::test;
+
+    struct mbox_context _ctx, *ctx = &_ctx;
+    uint8_t src[8] = {0};
+    int rc;
+
+    /* Setup */
+    memset(ctx, 0, sizeof(mbox_context));
+
+    mbox_vlog = &mbox_log_console;
+    verbosity = (verbose)2;
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    init_vpnor_from_paths(ctx);
+
+    /* Test */
+    rc = write_flash(ctx, 0x1000, src, sizeof(src));
+
+    /* Verify we can't write to RO partitions */
+    assert(rc != 0);
+
+    destroy_vpnor(ctx);
+
+    return 0;
+}
diff --git a/vpnor/test/write_rw.cpp b/vpnor/test/write_rw.cpp
new file mode 100644
index 0000000..1c4bd38
--- /dev/null
+++ b/vpnor/test/write_rw.cpp
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2018 IBM Corp.
+
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "mbox.h"
+#include "mboxd_flash.h"
+
+#include "vpnor/test/tmpd.hpp"
+
+static constexpr auto BLOCK_SIZE = 0x1000;
+
+const std::string toc[] = {
+    "partition01=TEST1,00001000,00002000,80,ECC,READWRITE",
+};
+
+int main(void)
+{
+    namespace fs = std::experimental::filesystem;
+    namespace test = openpower::virtual_pnor::test;
+
+    struct mbox_context _ctx, *ctx = &_ctx;
+    uint8_t src[8] = {0};
+    void *map;
+    int rc;
+    int fd;
+
+    /* Setup */
+    memset(ctx, 0, sizeof(mbox_context));
+
+    mbox_vlog = &mbox_log_console;
+    verbosity = (verbose)2;
+
+    test::VpnorRoot root(ctx, toc, BLOCK_SIZE);
+    /* write_flash() doesn't copy the file for us */
+    assert(fs::copy_file(root.ro() / "TEST1", root.rw() / "TEST1"));
+    init_vpnor_from_paths(ctx);
+
+    /* Test */
+    memset(src, 0xbb, sizeof(src));
+    rc = write_flash(ctx, 0x1000, src, sizeof(src));
+    assert(rc == 0);
+    fd = open((root.rw() / "TEST1").c_str(), O_RDONLY);
+    map = mmap(NULL, sizeof(src), PROT_READ, MAP_SHARED, fd, 0);
+    assert(map != MAP_FAILED);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+
+    /* Ensure single byte writes function */
+    memset(src, 0xcc, sizeof(src));
+    rc = write_flash(ctx, 0x1000, src, sizeof(src));
+    assert(rc == 0);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+
+    src[0] = 0xff;
+    rc = write_flash(ctx, 0x1000, src, 1);
+    assert(rc == 0);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+
+    src[1] = 0xff;
+    rc = write_flash(ctx, 0x1000 + 1, &src[1], 1);
+    assert(rc == 0);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+
+    src[2] = 0xff;
+    rc = write_flash(ctx, 0x1000 + 2, &src[2], 1);
+    assert(rc == 0);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+
+    /* Writes past the end of the partition should fail */
+    rc = write_flash(ctx, 0x1000 + 0xff9, src, sizeof(src));
+    assert(rc < 0);
+
+    /* Check that RW file is unmodified after the bad write */
+    fd = open((root.rw() / "TEST1").c_str(), O_RDONLY);
+    map = mmap(NULL, sizeof(src), PROT_READ, MAP_SHARED, fd, 0);
+    assert(map != MAP_FAILED);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+
+    munmap(map, sizeof(src));
+    close(fd);
+
+    destroy_vpnor(ctx);
+
+    return 0;
+}