diff options
Diffstat (limited to 'vpnor/pnor_partition_table.cpp')
-rw-r--r-- | vpnor/pnor_partition_table.cpp | 376 |
1 files changed, 376 insertions, 0 deletions
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 |