xref: /openbmc/phosphor-bmc-code-mgmt/i2c-vr/mps/mp2x6xx.cpp (revision dcf4b607bd2937a8964d68fe9dcc59daaf20c720)
1 #include "mp2x6xx.hpp"
2 
3 #include "common/include/utils.hpp"
4 
5 #include <phosphor-logging/lg2.hpp>
6 
7 PHOSPHOR_LOG2_USING;
8 
9 namespace phosphor::software::VR
10 {
11 
12 static constexpr size_t vendorIdLength = 3;
13 static constexpr size_t deviceIdLength = 4;
14 static constexpr size_t configIdLength = 2;
15 static constexpr size_t statusByteLength = 1;
16 static constexpr size_t crcLength = 2;
17 
18 static constexpr std::string_view productIdRegName = "TRIM_MFR_PRODUCT_ID2";
19 static constexpr std::string_view crcUserRegName = "CRC_USER";
20 
21 static constexpr uint8_t pageMask = 0x0F;
22 static constexpr uint8_t configMask = 0xF0;
23 
24 static constexpr uint8_t disableWriteProtect = 0x00;
25 static constexpr uint16_t disablePage2WriteProtect = 0x128C;
26 static constexpr uint16_t disablePage3WriteProtect = 0x0082;
27 
28 enum class MP2X6XXCmd : uint8_t
29 {
30     // Page 0 commands
31     readCRCReg = 0xED,
32     // Page 1 commands
33     mfrMTPMemoryCtrl = 0xCC,
34     // Page 2 commands
35     selectConfigCtrl = 0x1A,
36     // Page 3 commands
37     mfrMTPMemoryCtrlPage3 = 0x81,
38 };
39 
parseDeviceConfiguration()40 sdbusplus::async::task<bool> MP2X6XX::parseDeviceConfiguration()
41 {
42     if (!configuration)
43     {
44         error("Device configuration not initialized");
45         co_return false;
46     }
47 
48     configuration->vendorId = 0x4D5053;
49 
50     for (const auto& tokens : parser->lineTokens)
51     {
52         if (!parser->isValidDataTokens(tokens))
53         {
54             continue;
55         }
56 
57         auto regName = parser->getVal<std::string>(tokens, ATE::regName);
58         if (regName == productIdRegName)
59         {
60             configuration->productId =
61                 parser->getVal<uint32_t>(tokens, ATE::regDataHex);
62             configuration->configId =
63                 parser->getVal<uint32_t>(tokens, ATE::configId);
64         }
65         else if (regName == crcUserRegName)
66         {
67             configuration->crcUser =
68                 parser->getVal<uint32_t>(tokens, ATE::regDataHex);
69             break;
70         }
71     }
72 
73     co_return true;
74 }
75 
verifyImage(const uint8_t * image,size_t imageSize)76 sdbusplus::async::task<bool> MP2X6XX::verifyImage(const uint8_t* image,
77                                                   size_t imageSize)
78 {
79     if (!co_await parseImage(image, imageSize))
80     {
81         error("Image verification failed: image parsing failed");
82         co_return false;
83     }
84 
85     if (configuration->registersData.empty())
86     {
87         error("Image verification failed - no data found");
88         co_return false;
89     }
90 
91     if (configuration->productId == 0 || configuration->configId == 0)
92     {
93         error("Image verification failed - missing product or config ID");
94         co_return false;
95     }
96 
97     co_return true;
98 }
99 
checkId(PMBusCmd pmBusCmd,uint32_t expected)100 sdbusplus::async::task<bool> MP2X6XX::checkId(PMBusCmd pmBusCmd,
101                                               uint32_t expected)
102 {
103     const uint8_t cmd = static_cast<uint8_t>(pmBusCmd);
104     size_t idLen = 0;
105     bool blockRead = false;
106 
107     switch (pmBusCmd)
108     {
109         case PMBusCmd::mfrId:
110             idLen = vendorIdLength;
111             blockRead = true;
112             break;
113         case PMBusCmd::icDeviceId:
114             idLen = deviceIdLength;
115             blockRead = true;
116             break;
117         case PMBusCmd::mfrSerial:
118             idLen = configIdLength;
119             break;
120         default:
121             error("Invalid command for ID check: {CMD}", "CMD", lg2::hex, cmd);
122             co_return false;
123     }
124 
125     std::vector<uint8_t> rbuf;
126     std::vector<uint8_t> tbuf;
127 
128     tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
129     if (!i2cInterface.sendReceive(tbuf, rbuf))
130     {
131         error("Failed to set page 0 for ID check");
132         co_return false;
133     }
134 
135     tbuf = {cmd};
136     rbuf.resize(idLen + (blockRead ? statusByteLength : 0));
137 
138     if (!i2cInterface.sendReceive(tbuf, rbuf))
139     {
140         error("I2C failure during ID check, cmd {CMD}", "CMD", lg2::hex, cmd);
141         co_return false;
142     }
143 
144     auto idBytes = std::span(rbuf).subspan(blockRead ? statusByteLength : 0);
145     uint32_t id = bytesToInt<uint32_t>(idBytes);
146 
147     if (id != expected)
148     {
149         error("ID check failed for cmd {CMD}: got {ID}, expected {EXP}", "CMD",
150               lg2::hex, cmd, "ID", lg2::hex, id, "EXP", lg2::hex, expected);
151         co_return false;
152     }
153 
154     co_return true;
155 }
156 
unlockWriteProtect()157 sdbusplus::async::task<bool> MP2X6XX::unlockWriteProtect()
158 {
159     std::vector<uint8_t> tbuf;
160     std::vector<uint8_t> rbuf;
161 
162     tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
163     if (!i2cInterface.sendReceive(tbuf, rbuf))
164     {
165         error("Failed to set page 0 for unlocking write protect");
166         co_return false;
167     }
168 
169     tbuf = buildByteVector(PMBusCmd::writeProtect, disableWriteProtect);
170     if (!i2cInterface.sendReceive(tbuf, rbuf))
171     {
172         error("Failed to disable write protect");
173         co_return false;
174     }
175 
176     // unlock page 2 write protect
177     tbuf = buildByteVector(PMBusCmd::page, MPSPage::page1);
178     if (!i2cInterface.sendReceive(tbuf, rbuf))
179     {
180         error("Failed to set page 1 for unlocking write protect for page 2");
181         co_return false;
182     }
183 
184     tbuf =
185         buildByteVector(MP2X6XXCmd::mfrMTPMemoryCtrl, disablePage2WriteProtect);
186     if (!i2cInterface.sendReceive(tbuf, rbuf))
187     {
188         error("Failed to unlock page 2 write protect");
189         co_return false;
190     }
191 
192     // unlock page 3 write protect
193     tbuf = buildByteVector(PMBusCmd::page, MPSPage::page3);
194     if (!i2cInterface.sendReceive(tbuf, rbuf))
195     {
196         error("Failed to set page 3 for unlocking write protect for page 3");
197         co_return false;
198     }
199 
200     tbuf = buildByteVector(MP2X6XXCmd::mfrMTPMemoryCtrlPage3,
201                            disablePage3WriteProtect);
202     if (!i2cInterface.sendReceive(tbuf, rbuf))
203     {
204         error("Failed to unlock page 3 write protect");
205         co_return false;
206     }
207 
208     debug("Unlocked write protect");
209 
210     co_return true;
211 }
212 
selectConfig(uint8_t config)213 sdbusplus::async::task<bool> MP2X6XX::selectConfig(uint8_t config)
214 {
215     // MPS config select command:
216     // Writes to Page 2 @ 0x1A: value = 0x0F00 | ((config + 7) << 4)
217     // For config 1–6 → result: 0x0F80 to 0x0FD0
218 
219     std::vector<uint8_t> tbuf;
220     std::vector<uint8_t> rbuf;
221 
222     tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2);
223     if (!i2cInterface.sendReceive(tbuf, rbuf))
224     {
225         error("Failed to set page 2 for configuration switch");
226         co_return false;
227     }
228 
229     constexpr uint8_t baseOffset = 7;
230     uint8_t encodedNibble = static_cast<uint8_t>((config + baseOffset) << 4);
231     uint16_t command = 0x0F00 | encodedNibble;
232 
233     tbuf = buildByteVector(MP2X6XXCmd::selectConfigCtrl, command);
234 
235     if (!i2cInterface.sendReceive(tbuf, rbuf))
236     {
237         error("Failed to write config select command {CMD} for config {CONFIG}",
238               "CMD", lg2::hex, command, "CONFIG", config);
239         co_return false;
240     }
241 
242     debug("Switched to config {CONFIG}", "CONFIG", config);
243     co_return true;
244 }
245 
programConfigData(const std::vector<MPSData> & gdata)246 sdbusplus::async::task<bool> MP2X6XX::programConfigData(
247     const std::vector<MPSData>& gdata)
248 {
249     std::vector<uint8_t> tbuf;
250     std::vector<uint8_t> rbuf;
251 
252     for (const auto& data : gdata)
253     {
254         uint8_t page = data.page & pageMask;
255 
256         tbuf = buildByteVector(PMBusCmd::page, page);
257         if (!i2cInterface.sendReceive(tbuf, rbuf))
258         {
259             error("Failed to set page {PAGE} for register {REG}", "PAGE", page,
260                   "REG", lg2::hex, data.addr);
261             co_return false;
262         }
263 
264         tbuf = {data.addr};
265         tbuf.insert(tbuf.end(), data.data.begin(),
266                     data.data.begin() + data.length);
267 
268         if (!i2cInterface.sendReceive(tbuf, rbuf))
269         {
270             error(
271                 "Failed to write data {DATA} to register {REG} on page {PAGE}",
272                 "DATA", lg2::hex, bytesToInt<uint32_t>(data.data), "REG",
273                 lg2::hex, data.addr, "PAGE", page);
274             co_return false;
275         }
276     }
277 
278     if (!co_await storeUserCode())
279     {
280         error("Failed to store user code after programming config data");
281         co_return false;
282     }
283 
284     co_return true;
285 }
286 
configAllRegisters()287 sdbusplus::async::task<bool> MP2X6XX::configAllRegisters()
288 {
289     for (const auto& [config, gdata] : getGroupedConfigData(configMask, 4))
290     {
291         debug("Configuring registers for config {CONFIG}", "CONFIG", config);
292 
293         // Select the appropriate config before programming its registers
294         if (config > 0 && !co_await selectConfig(config))
295         {
296             co_return false;
297         }
298 
299         if (!co_await programConfigData(gdata))
300         {
301             error("Failed to program configuration {CONFIG}", "CONFIG", config);
302             co_return false;
303         }
304 
305         debug("Configured {SIZE} registers for config {CONFIG}", "SIZE",
306               gdata.size(), "CONFIG", config);
307     }
308 
309     co_return true;
310 }
311 
storeUserCode()312 sdbusplus::async::task<bool> MP2X6XX::storeUserCode()
313 {
314     std::vector<uint8_t> tbuf;
315     std::vector<uint8_t> rbuf;
316 
317     tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
318     if (!i2cInterface.sendReceive(tbuf, rbuf))
319     {
320         error("Failed to set page 0 for storing user code");
321         co_return false;
322     }
323 
324     tbuf = buildByteVector(PMBusCmd::storeUserCode);
325     if (!i2cInterface.sendReceive(tbuf, rbuf))
326     {
327         error("Failed to store user code");
328         co_return false;
329     }
330 
331     // Wait store user code
332     co_await sdbusplus::async::sleep_for(ctx, std::chrono::milliseconds(500));
333 
334     debug("Stored user code");
335 
336     co_return true;
337 }
338 
getCRC(uint32_t * checksum)339 sdbusplus::async::task<bool> MP2X6XX::getCRC(uint32_t* checksum)
340 {
341     if (checksum == nullptr)
342     {
343         error("getCRC() called with null checksum pointer");
344         co_return false;
345     }
346 
347     std::vector<uint8_t> tbuf;
348     std::vector<uint8_t> rbuf;
349 
350     tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
351     if (!i2cInterface.sendReceive(tbuf, rbuf))
352     {
353         error("Failed to set page 0 for CRC read");
354         co_return false;
355     }
356 
357     tbuf = buildByteVector(MP2X6XXCmd::readCRCReg);
358     rbuf.resize(crcLength);
359     if (!i2cInterface.sendReceive(tbuf, rbuf))
360     {
361         error("Failed to read CRC from device");
362         co_return false;
363     }
364 
365     if (rbuf.size() < crcLength)
366     {
367         error("CRC read returned insufficient data");
368         co_return false;
369     }
370 
371     *checksum = bytesToInt<uint32_t>(rbuf);
372 
373     debug("Read CRC: {CRC}", "CRC", lg2::hex, *checksum);
374 
375     co_return true;
376 }
377 
checkMTPCRC()378 sdbusplus::async::task<bool> MP2X6XX::checkMTPCRC()
379 {
380     uint32_t crc = 0;
381     // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
382     if (!co_await getCRC(&crc))
383     // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
384     {
385         error("Failed to get CRC for MTP check");
386         co_return false;
387     }
388 
389     debug("MTP CRC: {CRC}, Expected: {EXP}", "CRC", lg2::hex, crc, "EXP",
390           lg2::hex, configuration->crcUser);
391 
392     co_return configuration->crcUser == crc;
393 }
394 
forcedUpdateAllowed()395 bool MP2X6XX::forcedUpdateAllowed()
396 {
397     return true;
398 }
399 
updateFirmware(bool force)400 sdbusplus::async::task<bool> MP2X6XX::updateFirmware(bool force)
401 {
402     (void)force;
403 
404     if (!co_await checkId(PMBusCmd::mfrId, configuration->vendorId))
405     {
406         co_return false;
407     }
408 
409     if (!co_await checkId(PMBusCmd::icDeviceId, configuration->productId))
410     {
411         co_return false;
412     }
413 
414     if (!co_await checkId(PMBusCmd::mfrSerial, configuration->configId))
415     {
416         co_return false;
417     }
418 
419     if (!co_await unlockWriteProtect())
420     {
421         co_return false;
422     }
423 
424     if (!co_await configAllRegisters())
425     {
426         co_return false;
427     }
428 
429     if (!(co_await checkMTPCRC()))
430     {
431         co_return false;
432     }
433 
434     co_return true;
435 }
436 
437 } // namespace phosphor::software::VR
438