summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDeepak Kodihalli <dkodihal@in.ibm.com>2017-04-28 04:44:38 -0500
committerDeepak Kodihalli <dkodihal@in.ibm.com>2017-06-27 08:40:30 -0500
commit393821dd663f54384ea5b12cf30ab7944ceae5a6 (patch)
treec338d1ad69b3962ae3fdfd2d606bfa1e0dcf8f0d
parent90b92fe48ec72b6c15cce24d33ac983548368b98 (diff)
downloadphosphor-mboxd-393821dd663f54384ea5b12cf30ab7944ceae5a6.tar.gz
phosphor-mboxd-393821dd663f54384ea5b12cf30ab7944ceae5a6.zip
pnor : generate partition table
Implement a class that, upon construction, generates the virtual PNOR partition table. The virtual PNOR is typically a subset of the full PNOR image, by choosing partitions of interest. The generation is based on upon information read from the PNOR partition files and table of contents (toc) file. Provide an interface to the virtual PNOR partition table. Change-Id: I7a68e3833b8cf66e92eb6ca274f6a3c376ce0add Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
-rw-r--r--Makefile.am26
-rw-r--r--common.h8
-rw-r--r--configure.ac3
-rw-r--r--pnor_partition_defs.h121
-rw-r--r--pnor_partition_table.cpp242
-rw-r--r--pnor_partition_table.hpp214
-rw-r--r--test/create_pnor_partition_table.cpp76
7 files changed, 689 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 5a26916..cf1f987 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,10 +1,23 @@
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_flash.c \
+ mboxd_lpc.c \
+ mboxd_msg.c \
+ mboxd_windows.c \
+ mtd.c
mboxd_LDFLAGS = $(LIBSYSTEMD_LIBS)
mboxd_CFLAGS = $(LIBSYSTEMD_CFLAGS)
+if VIRTUAL_PNOR_ENABLED
+mboxd_SOURCES += pnor_partition_table.cpp
+mboxd_LDFLAGS += -lstdc++fs
+endif
+
mboxctl_SOURCES = mboxctl.c
mboxctl_LDFLAGS = $(LIBSYSTEMD_LIBS)
mboxctl_CFLAGS = $(LIBSYSTEMD_CFLAGS)
@@ -14,6 +27,7 @@ mboxctl_CFLAGS = $(LIBSYSTEMD_CFLAGS)
AM_LIBS = $(CODE_COVERAGE_LIBS)
AM_CPPFLAGS = $(CODE_COVERAGE_CPPFLAGS)
AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
+AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
test_sanity_SOURCES = test/sanity.c
@@ -95,6 +109,12 @@ test_sequence_numbers_SOURCES = test/sequence_numbers.c \
test_get_mbox_info_v2_timeout_SOURCES = test/get_mbox_info_v2_timeout.c \
$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
+test_create_pnor_partition_table_SOURCES = \
+ test/create_pnor_partition_table.cpp \
+ common.c \
+ pnor_partition_table.cpp
+test_create_pnor_partition_table_LDFLAGS = -lstdc++fs
+
check_PROGRAMS = test/sanity \
test/copy_flash \
test/erase_flash \
@@ -123,4 +143,8 @@ check_PROGRAMS = test/sanity \
test/sequence_numbers \
test/get_mbox_info_v2_timeout
+if VIRTUAL_PNOR_ENABLED
+check_PROGRAMS += test/create_pnor_partition_table
+endif
+
TESTS = $(check_PROGRAMS)
diff --git a/common.h b/common.h
index 83ead0c..90a0fc9 100644
--- a/common.h
+++ b/common.h
@@ -46,6 +46,10 @@ extern enum verbose verbosity;
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);
__attribute__((format(printf, 2, 3)))
@@ -100,4 +104,8 @@ static inline bool is_power_of_2(unsigned val)
char *get_dev_mtd(void);
+#ifdef __cplusplus
+}
+#endif
+
#endif /* COMMON_H */
diff --git a/configure.ac b/configure.ac
index c5ef78a..a523948 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,9 @@ 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_LOC, "/var/lib/phosphor-software-manager/pnor/ro", [The path to the directory containing PNOR partition files.])
+
# Create configured output
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
diff --git a/pnor_partition_defs.h b/pnor_partition_defs.h
new file mode 100644
index 0000000..836dac5
--- /dev/null
+++ b/pnor_partition_defs.h
@@ -0,0 +1,121 @@
+#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. */
+#define PARTITION_USER_WORDS 16
+#define PARTITION_ECC_PROTECTED 0x8000
+#define PARTITION_PRESERVED 0x80000000
+#define PARTITION_READONLY 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/pnor_partition_table.cpp b/pnor_partition_table.cpp
new file mode 100644
index 0000000..17f5011
--- /dev/null
+++ b/pnor_partition_table.cpp
@@ -0,0 +1,242 @@
+#include "pnor_partition_table.hpp"
+#include "common.h"
+#include "config.h"
+#include <syslog.h>
+#include <endian.h>
+#include <regex>
+#include <fstream>
+#include <algorithm>
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+
+namespace partition
+{
+namespace block
+{
+
+// The PNOR erase-block size is 4 KB
+constexpr size_t size = 4096;
+
+} // namespace block
+
+Table::Table():
+ Table(fs::path(PARTITION_FILES_LOC))
+{
+}
+
+Table::Table(fs::path&& directory):
+ szBlocks(0),
+ imgBlocks(0),
+ directory(std::move(directory)),
+ numParts(0)
+{
+ preparePartitions();
+ 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 = szBlocks;
+ table.data.entry_size = sizeof(pnor_partition);
+ table.data.entry_count = numParts;
+ table.data.block_size = block::size;
+ table.data.block_count = imgBlocks;
+ 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;
+ }
+ }
+
+ size_t totalSizeBytes = sizeof(pnor_partition_table) +
+ (num * sizeof(pnor_partition));
+ size_t totalSizeAligned = align_up(totalSizeBytes, block::size);
+ szBlocks = totalSizeAligned / block::size;
+ imgBlocks = szBlocks;
+ tbl.resize(totalSizeAligned);
+}
+
+inline void Table::writeSizes(pnor_partition& part, size_t start, size_t end)
+{
+ size_t size = end - start;
+ part.data.base = imgBlocks;
+ size_t sizeInBlocks = align_up(size, block::size) / block::size;
+ imgBlocks += sizeInBlocks;
+ part.data.size = sizeInBlocks;
+ part.data.actual = size;
+}
+
+inline void Table::writeUserdata(pnor_partition& part, const std::string& data)
+{
+ if (std::string::npos != data.find("ECC"))
+ {
+ part.data.user.data[0] = PARTITION_ECC_PROTECTED;
+ }
+ auto perms = 0;
+ if (std::string::npos != data.find("READONLY"))
+ {
+ perms |= PARTITION_READONLY;
+ }
+ if (std::string::npos != data.find("PRESERVED"))
+ {
+ perms |= PARTITION_PRESERVED;
+ }
+ part.data.user.data[1] = perms;
+}
+
+inline void Table::writeDefaults(pnor_partition& part)
+{
+ part.data.pid = PARENT_PATITION_ID;
+ part.data.type = PARTITION_TYPE_DATA;
+ part.data.flags = 0; // flags unused
+}
+
+inline void Table::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 Table::preparePartitions()
+{
+ fs::path tocFile = directory;
+ tocFile /= PARTITION_TOC_FILE;
+ allocateMemory(tocFile);
+
+ std::ifstream file(tocFile.c_str());
+ static constexpr auto ID_MATCH = 1;
+ static constexpr auto NAME_MATCH = 2;
+ static constexpr auto START_ADDR_MATCH = 3;
+ static constexpr auto END_ADDR_MATCH = 4;
+ // Parse PNOR toc (table of contents) file, which has lines like :
+ // partition01=HBB,00010000,000a0000,ECC,PRESERVED, to indicate partitions
+ std::regex regex
+ {
+ "^partition([0-9]+)=([A-Za-z0-9_]+),"
+ "([0-9a-fA-F]+),([0-9a-fA-F]+)",
+ std::regex::extended
+ };
+ std::smatch match;
+ std::string line;
+
+ decltype(auto) table = getNativeTable();
+
+ while (std::getline(file, line))
+ {
+ if (std::regex_search(line, match, regex))
+ {
+ fs::path partitionFile = directory;
+ partitionFile /= match[NAME_MATCH].str();
+ if (!fs::exists(partitionFile))
+ {
+ MSG_ERR("Partition file %s does not exist",
+ partitionFile.c_str());
+ continue;
+ }
+
+ writeNameAndId(table.partitions[numParts],
+ match[NAME_MATCH].str(),
+ match[ID_MATCH].str());
+ writeDefaults(table.partitions[numParts]);
+ writeSizes(table.partitions[numParts],
+ std::stoul(match[START_ADDR_MATCH].str(), nullptr, 16),
+ std::stoul(match[END_ADDR_MATCH].str(), nullptr, 16));
+ writeUserdata(table.partitions[numParts], match.suffix().str());
+ table.partitions[numParts].checksum =
+ details::checksum(table.partitions[numParts].data);
+
+ ++numParts;
+ }
+ }
+}
+
+const pnor_partition& Table::partition(size_t offset) const
+{
+ const decltype(auto) table = getNativeTable();
+ size_t offt = offset / block::size;
+
+ for (decltype(numParts) i{}; i < numParts; ++i)
+ {
+ if ((offt >= table.partitions[i].data.base) &&
+ (offt < (table.partitions[i].data.base +
+ table.partitions[i].data.size)))
+ {
+ return table.partitions[i];
+ }
+ }
+
+ static pnor_partition p{};
+ return p;
+}
+
+} // 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 = htobe32(src->checksum);
+
+ 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 = htobe32(psrc->checksum);
+ }
+
+ return out;
+}
+
+} // namespace virtual_pnor
+} // namespace openpower
diff --git a/pnor_partition_table.hpp b/pnor_partition_table.hpp
new file mode 100644
index 0000000..b6c50f4
--- /dev/null
+++ b/pnor_partition_table.hpp
@@ -0,0 +1,214 @@
+#pragma once
+
+#include <vector>
+#include <memory>
+#include <numeric>
+#include <experimental/filesystem>
+#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);
+
+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] directory - path of the directory housing PNOR partitions
+ */
+ Table(fs::path&& directory);
+
+ Table();
+ Table(const Table&) = delete;
+ Table& operator=(const Table&) = delete;
+ Table(Table&&) = delete;
+ Table& operator=(Table&&) = delete;
+ ~Table() = default;
+
+ /** @brief Return size of partition table
+ *
+ * @returns size_t - size of partition table in blocks
+ */
+ size_t size() const
+ {
+ return szBlocks;
+ }
+
+ /** @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 a
+ * reference to a zeroed out pnor_partition structure.
+ */
+ const pnor_partition& partition(size_t offset) const;
+
+ private:
+ /** @brief Prepares a vector of PNOR partition structures.
+ */
+ void preparePartitions();
+
+ /** @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 Populate fields related to sizes for the input
+ * pnor_partition structure.
+ *
+ * @param[in/out] part - pnor_partition structure
+ * @param[in] start - partition start address
+ * @param[in] end - partition end address
+ */
+ void writeSizes(pnor_partition& part, size_t start, size_t end);
+
+ /** @brief Populate userdata bits for the input
+ * pnor_partition structure.
+ *
+ * @param[in/out] part - pnor_partition structure
+ * @param[in] data - string having userdata fields in a
+ * comma-separated line.
+ */
+ void writeUserdata(pnor_partition& part, const std::string& data);
+
+ /** @brief Populate the name and id fields for the input
+ * pnor_partition structure.
+ *
+ * @param[in/out] part - pnor_partition structure
+ * @param[in] name - partition name
+ * @param[id] id - partition id
+ */
+ void writeNameAndId(pnor_partition& part, std::string&& name,
+ const std::string& id);
+
+ /** @brief Populate default/unused fields for the input
+ * pnor_partition structure.
+ *
+ * @param[in/out] part - pnor_partition structure
+ */
+ void writeDefaults(pnor_partition& part);
+
+ /** @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)),
+ * measured in erase-blocks.
+ */
+ size_t szBlocks;
+
+ /** @brief Size of virtual PNOR image, measured in erase-blocks */
+ size_t imgBlocks;
+
+ /** @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;
+};
+
+} // namespace partition
+} // namespace virtual_pnor
+} // namespace openpower
diff --git a/test/create_pnor_partition_table.cpp b/test/create_pnor_partition_table.cpp
new file mode 100644
index 0000000..bda443c
--- /dev/null
+++ b/test/create_pnor_partition_table.cpp
@@ -0,0 +1,76 @@
+#include "pnor_partition_table.hpp"
+#include "config.h"
+#include <assert.h>
+#include <string.h>
+#include <vector>
+#include <fstream>
+#include <experimental/filesystem>
+
+constexpr auto line = "partition01=HBB,00000000,00000400,ECC,PRESERVED";
+constexpr auto partitionName = "HBB";
+char tmplt[] = "/tmp/vpnor_partitions.XXXXXX";
+
+int main()
+{
+ namespace fs = std::experimental::filesystem;
+
+ char* tmpdir = mkdtemp(tmplt);
+ assert(tmpdir != nullptr);
+
+ fs::path tocFilePath{tmpdir};
+ tocFilePath /= PARTITION_TOC_FILE;
+ std::ofstream tocFile(tocFilePath.c_str());
+ tocFile.write(line, strlen(line));
+ tocFile.close();
+
+ fs::path partitionFilePath{tmpdir};
+ partitionFilePath /= partitionName;
+ std::ofstream partitionFile(partitionFilePath.c_str());
+ std::vector<char> empty(1, 0); // 1 byte file
+ partitionFile.write(empty.data(), empty.size());
+ partitionFile.close();
+
+ const openpower::virtual_pnor::partition::Table table(fs::path{tmpdir});
+
+ pnor_partition_table expectedTable{};
+ expectedTable.data.magic = PARTITION_HEADER_MAGIC;
+ expectedTable.data.version = PARTITION_VERSION_1;
+ expectedTable.data.size = 1; // 1 block
+ expectedTable.data.entry_size = sizeof(pnor_partition);
+ expectedTable.data.entry_count = 1; // 1 partition
+ expectedTable.data.block_size = 4096;
+ expectedTable.data.block_count = 2; // 1 table block and 1 partition block
+ expectedTable.checksum =
+ openpower::virtual_pnor::details::checksum(expectedTable.data);
+
+ pnor_partition expectedPartition{};
+ strcpy(expectedPartition.data.name, partitionName);
+ expectedPartition.data.base = 1; // starts after 1 block
+ expectedPartition.data.size = 1; // 1 block
+ expectedPartition.data.actual = 0x400; // 1024 bytes
+ 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.checksum =
+ openpower::virtual_pnor::details::checksum(expectedPartition.data);
+
+ const pnor_partition_table& result = table.getNativeTable();
+
+ fs::remove_all(fs::path{tmpdir});
+
+ 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(4096);
+ rc = memcmp(&first, &result.partitions[0], sizeof(pnor_partition));
+ assert(rc == 0);
+
+ return 0;
+}
OpenPOWER on IntegriCloud