1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright (C) 2018 IBM Corp.
3 #include "pnor_partition_table.hpp"
4 #include "common.h"
5 #include "config.h"
6 #include "xyz/openbmc_project/Common/error.hpp"
7 #include <phosphor-logging/elog-errors.hpp>
8 #include <syslog.h>
9 #include <endian.h>
10 #include <regex>
11 #include <fstream>
12 #include <algorithm>
13 
14 namespace openpower
15 {
16 namespace virtual_pnor
17 {
18 
19 using namespace phosphor::logging;
20 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
21 
22 namespace partition
23 {
24 
Table(const struct mbox_context * ctx)25 Table::Table(const struct mbox_context* ctx) :
26     szBytes(sizeof(pnor_partition_table)), numParts(0),
27     blockSize(1 << ctx->erase_size_shift), pnorSize(ctx->flash_size)
28 {
29     preparePartitions(ctx);
30     prepareHeader();
31     hostTbl = endianFixup(tbl);
32 }
33 
prepareHeader()34 void Table::prepareHeader()
35 {
36     decltype(auto) table = getNativeTable();
37     table.data.magic = PARTITION_HEADER_MAGIC;
38     table.data.version = PARTITION_VERSION_1;
39     table.data.size = blocks();
40     table.data.entry_size = sizeof(pnor_partition);
41     table.data.entry_count = numParts;
42     table.data.block_size = blockSize;
43     table.data.block_count = pnorSize / blockSize;
44     table.checksum = details::checksum(table.data);
45 }
46 
allocateMemory(const fs::path & tocFile)47 inline void Table::allocateMemory(const fs::path& tocFile)
48 {
49     size_t num = 0;
50     std::string line;
51     std::ifstream file(tocFile.c_str());
52 
53     // Find number of lines in partition file - this will help
54     // determine the number of partitions and hence also how much
55     // memory to allocate for the partitions array.
56     // The actual number of partitions may turn out to be lesser than this,
57     // in case of errors.
58     while (std::getline(file, line))
59     {
60         // Check if line starts with "partition"
61         if (std::string::npos != line.find("partition", 0))
62         {
63             ++num;
64         }
65     }
66 
67     szBytes = sizeof(pnor_partition_table) + (num * sizeof(pnor_partition));
68     tbl.resize(capacity());
69 }
70 
preparePartitions(const struct mbox_context * ctx)71 void Table::preparePartitions(const struct mbox_context* ctx)
72 {
73     const fs::path roDir = ctx->paths.ro_loc;
74     const fs::path patchDir = ctx->paths.patch_loc;
75     fs::path tocFile = roDir / PARTITION_TOC_FILE;
76     allocateMemory(tocFile);
77 
78     std::ifstream file(tocFile.c_str());
79     std::string line;
80     decltype(auto) table = getNativeTable();
81 
82     while (std::getline(file, line))
83     {
84         pnor_partition& part = table.partitions[numParts];
85         fs::path patch;
86         fs::path file;
87 
88         // The ToC file presented in the vpnor squashfs looks like:
89         //
90         // version=IBM-witherspoon-ibm-OP9_v1.19_1.135
91         // 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
92         // partition00=part,0x00000000,0x00002000,00,READWRITE
93         // partition01=HBEL,0x00008000,0x0002c000,00,ECC,REPROVISION,CLEARECC,READWRITE
94         // ...
95         //
96         // As such we want to skip any lines that don't begin with 'partition'
97         if (std::string::npos == line.find("partition", 0))
98         {
99             continue;
100         }
101 
102         parseTocLine(line, blockSize, part);
103 
104         if (numParts > 0)
105         {
106             struct pnor_partition& prev = table.partitions[numParts - 1];
107             uint32_t prev_end = prev.data.base + prev.data.size;
108 
109             if (part.data.id == prev.data.id)
110             {
111                 MSG_ERR("ID for previous partition '%s' at block 0x%" PRIx32
112                         "matches current partition '%s' at block 0x%" PRIx32
113                         ": %" PRId32 "\n",
114                         prev.data.name, prev.data.base, part.data.name,
115                         part.data.base, part.data.id);
116             }
117 
118             if (part.data.base < prev_end)
119             {
120                 std::stringstream err;
121                 err << "Partition '" << part.data.name << "' start block 0x"
122                     << std::hex << part.data.base << "is less than the end "
123                     << "block 0x" << std::hex << prev_end << " of '"
124                     << prev.data.name << "'";
125                 throw InvalidTocEntry(err.str());
126             }
127         }
128 
129         file = roDir / part.data.name;
130         if (!fs::exists(file))
131         {
132             std::stringstream err;
133             err << "Partition file " << file.native() << " does not exist";
134             throw InvalidTocEntry(err.str());
135         }
136 
137         patch = patchDir / part.data.name;
138         if (fs::is_regular_file(patch))
139         {
140             const size_t size = part.data.size * blockSize;
141             part.data.actual =
142                 std::min(size, static_cast<size_t>(fs::file_size(patch)));
143         }
144 
145         ++numParts;
146     }
147 }
148 
partition(size_t offset) const149 const pnor_partition& Table::partition(size_t offset) const
150 {
151     const decltype(auto) table = getNativeTable();
152     size_t blockOffset = offset / blockSize;
153 
154     for (decltype(numParts) i{}; i < numParts; ++i)
155     {
156         const struct pnor_partition& part = table.partitions[i];
157         size_t len = part.data.size;
158 
159         if ((blockOffset >= part.data.base) &&
160             (blockOffset < (part.data.base + len)))
161         {
162             return part;
163         }
164 
165         /* Are we in a hole between partitions? */
166         if (blockOffset < part.data.base)
167         {
168             throw UnmappedOffset(offset, part.data.base * blockSize);
169         }
170     }
171 
172     throw UnmappedOffset(offset, pnorSize);
173 }
174 
partition(const std::string & name) const175 const pnor_partition& Table::partition(const std::string& name) const
176 {
177     const decltype(auto) table = getNativeTable();
178 
179     for (decltype(numParts) i{}; i < numParts; ++i)
180     {
181         if (name == table.partitions[i].data.name)
182         {
183             return table.partitions[i];
184         }
185     }
186 
187     std::stringstream err;
188     err << "Partition " << name << " is not listed in the table of contents";
189     throw UnknownPartition(err.str());
190 }
191 
192 } // namespace partition
193 
endianFixup(const PartitionTable & in)194 PartitionTable endianFixup(const PartitionTable& in)
195 {
196     PartitionTable out;
197     out.resize(in.size());
198     auto src = reinterpret_cast<const pnor_partition_table*>(in.data());
199     auto dst = reinterpret_cast<pnor_partition_table*>(out.data());
200 
201     dst->data.magic = htobe32(src->data.magic);
202     dst->data.version = htobe32(src->data.version);
203     dst->data.size = htobe32(src->data.size);
204     dst->data.entry_size = htobe32(src->data.entry_size);
205     dst->data.entry_count = htobe32(src->data.entry_count);
206     dst->data.block_size = htobe32(src->data.block_size);
207     dst->data.block_count = htobe32(src->data.block_count);
208     dst->checksum = details::checksum(dst->data);
209 
210     for (decltype(src->data.entry_count) i{}; i < src->data.entry_count; ++i)
211     {
212         auto psrc = &src->partitions[i];
213         auto pdst = &dst->partitions[i];
214         strncpy(pdst->data.name, psrc->data.name, PARTITION_NAME_MAX);
215         // Just to be safe
216         pdst->data.name[PARTITION_NAME_MAX] = '\0';
217         pdst->data.base = htobe32(psrc->data.base);
218         pdst->data.size = htobe32(psrc->data.size);
219         pdst->data.pid = htobe32(psrc->data.pid);
220         pdst->data.id = htobe32(psrc->data.id);
221         pdst->data.type = htobe32(psrc->data.type);
222         pdst->data.flags = htobe32(psrc->data.flags);
223         pdst->data.actual = htobe32(psrc->data.actual);
224         for (size_t j = 0; j < PARTITION_USER_WORDS; ++j)
225         {
226             pdst->data.user.data[j] = htobe32(psrc->data.user.data[j]);
227         }
228         pdst->checksum = details::checksum(pdst->data);
229     }
230 
231     return out;
232 }
233 
writeSizes(pnor_partition & part,size_t start,size_t end,size_t blockSize)234 static inline void writeSizes(pnor_partition& part, size_t start, size_t end,
235                               size_t blockSize)
236 {
237     size_t size = end - start;
238     part.data.base = align_up(start, blockSize) / blockSize;
239     size_t sizeInBlocks = align_up(size, blockSize) / blockSize;
240     part.data.size = sizeInBlocks;
241     part.data.actual = size;
242 }
243 
writeUserdata(pnor_partition & part,uint32_t version,const std::string & data)244 static inline void writeUserdata(pnor_partition& part, uint32_t version,
245                                  const std::string& data)
246 {
247     std::istringstream stream(data);
248     std::string flag{};
249     auto perms = 0;
250     auto state = 0;
251 
252     MSG_DBG("Parsing ToC flags '%s'\n", data.c_str());
253     while (std::getline(stream, flag, ','))
254     {
255         if (flag == "")
256             continue;
257 
258         if (flag == "ECC")
259         {
260             state |= PARTITION_ECC_PROTECTED;
261         }
262         else if (flag == "READONLY")
263         {
264             perms |= PARTITION_READONLY;
265         }
266         else if (flag == "READWRITE")
267         {
268             perms &= ~PARTITION_READONLY;
269         }
270         else if (flag == "PRESERVED")
271         {
272             perms |= PARTITION_PRESERVED;
273         }
274         else if (flag == "REPROVISION")
275         {
276             perms |= PARTITION_REPROVISION;
277         }
278         else if (flag == "VOLATILE")
279         {
280             perms |= PARTITION_VOLATILE;
281         }
282         else if (flag == "CLEARECC")
283         {
284             perms |= PARTITION_CLEARECC;
285         }
286         else
287         {
288             MSG_INFO("Found unimplemented partition property: %s\n",
289                      flag.c_str());
290         }
291     }
292 
293     part.data.user.data[0] = state;
294     part.data.user.data[1] = perms;
295     part.data.user.data[1] |= version;
296 }
297 
writeDefaults(pnor_partition & part)298 static inline void writeDefaults(pnor_partition& part)
299 {
300     part.data.pid = PARENT_PATITION_ID;
301     part.data.type = PARTITION_TYPE_DATA;
302     part.data.flags = 0; // flags unused
303 }
304 
writeNameAndId(pnor_partition & part,std::string && name,const std::string & id)305 static inline void writeNameAndId(pnor_partition& part, std::string&& name,
306                                   const std::string& id)
307 {
308     name.resize(PARTITION_NAME_MAX);
309     memcpy(part.data.name, name.c_str(), sizeof(part.data.name));
310     part.data.id = std::stoul(id);
311 }
312 
parseTocLine(const std::string & line,size_t blockSize,pnor_partition & part)313 void parseTocLine(const std::string& line, size_t blockSize,
314                   pnor_partition& part)
315 {
316     static constexpr auto ID_MATCH = 1;
317     static constexpr auto NAME_MATCH = 2;
318     static constexpr auto START_ADDR_MATCH = 4;
319     static constexpr auto END_ADDR_MATCH = 6;
320     static constexpr auto VERSION_MATCH = 8;
321     constexpr auto versionShift = 24;
322 
323     // Parse PNOR toc (table of contents) file, which has lines like :
324     // partition01=HBB,0x00010000,0x000a0000,0x80,ECC,PRESERVED, to indicate
325     // partition information
326     std::regex regex{
327         "^partition([0-9]+)=([A-Za-z0-9_]+),"
328         "(0x)?([0-9a-fA-F]+),(0x)?([0-9a-fA-F]+),(0x)?([A-Fa-f0-9]{2})",
329         std::regex::extended};
330 
331     std::smatch match;
332     if (!std::regex_search(line, match, regex))
333     {
334         std::stringstream err;
335         err << "Malformed partition description: " << line.c_str() << "\n";
336         throw MalformedTocEntry(err.str());
337     }
338 
339     writeNameAndId(part, match[NAME_MATCH].str(), match[ID_MATCH].str());
340     writeDefaults(part);
341 
342     unsigned long start =
343         std::stoul(match[START_ADDR_MATCH].str(), nullptr, 16);
344     if (start & (blockSize - 1))
345     {
346         MSG_ERR("Start offset 0x%lx for partition '%s' is not aligned to block "
347                 "size 0x%zx\n",
348                 start, match[NAME_MATCH].str().c_str(), blockSize);
349     }
350 
351     unsigned long end = std::stoul(match[END_ADDR_MATCH].str(), nullptr, 16);
352     if ((end - start) & (blockSize - 1))
353     {
354         MSG_ERR("Partition '%s' has a size 0x%lx that is not aligned to block "
355                 "size 0x%zx\n",
356                 match[NAME_MATCH].str().c_str(), (end - start), blockSize);
357     }
358 
359     if (start >= end)
360     {
361         std::stringstream err;
362         err << "Partition " << match[NAME_MATCH].str()
363             << " has an invalid range: start offset (0x" << std::hex << start
364             << " is beyond open end (0x" << std::hex << end << ")\n";
365         throw InvalidTocEntry(err.str());
366     }
367     writeSizes(part, start, end, blockSize);
368 
369     // Use the shift to convert "80" to 0x80000000
370     unsigned long version = std::stoul(match[VERSION_MATCH].str(), nullptr, 16);
371     writeUserdata(part, version << versionShift, match.suffix().str());
372     part.checksum = details::checksum(part.data);
373 }
374 
375 } // namespace virtual_pnor
376 } // namespace openpower
377