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