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