diff options
-rw-r--r-- | Makefile.am | 26 | ||||
-rw-r--r-- | common.h | 8 | ||||
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | pnor_partition_defs.h | 121 | ||||
-rw-r--r-- | pnor_partition_table.cpp | 242 | ||||
-rw-r--r-- | pnor_partition_table.hpp | 214 | ||||
-rw-r--r-- | test/create_pnor_partition_table.cpp | 76 |
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) @@ -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; +} |