| #include "mp297x.hpp" |
| |
| #include "common/include/utils.hpp" |
| |
| #include <phosphor-logging/lg2.hpp> |
| |
| #include <fstream> |
| |
| PHOSPHOR_LOG2_USING; |
| |
| namespace phosphor::software::VR |
| { |
| |
| static constexpr size_t statusByteLength = 1; |
| |
| static constexpr std::string_view crcUserRegName = "CRC_USER"; |
| static constexpr std::string_view crcMultiRegName = "CRC_MULTI"; |
| |
| enum class MP297XCmd : uint8_t |
| { |
| // Page 0 commands |
| storeDataIntoMTP = 0xF1, |
| |
| // Page 1 commands |
| writeProtectMode = 0x35, |
| enableMTPPageWR = 0x4F, |
| |
| // Page 2 commands |
| enableMultiConfigCRC = 0xF3, |
| |
| // Page 0x29 commands |
| readUserCodeCRC = 0xFF, |
| |
| // Page 0x2A commands |
| readMultiConfigCRC = 0xBF, |
| }; |
| |
| sdbusplus::async::task<bool> MP297X::parseDeviceConfiguration() |
| { |
| if (!configuration) |
| { |
| error("Device configuration not initialized"); |
| co_return false; |
| } |
| |
| configuration->vendorId = 0x0025; |
| configuration->productId = 0x0071; |
| |
| for (const auto& tokens : parser->lineTokens) |
| { |
| if (!parser->isValidDataTokens(tokens)) |
| { |
| continue; |
| } |
| |
| auto regName = parser->getVal<std::string>(tokens, ATE::regName); |
| |
| if (regName == crcUserRegName) |
| { |
| configuration->configId = |
| parser->getVal<uint32_t>(tokens, ATE::configId); |
| configuration->crcUser = |
| parser->getVal<uint32_t>(tokens, ATE::regDataHex); |
| } |
| else if (regName == crcMultiRegName) |
| { |
| configuration->crcMulti = |
| parser->getVal<uint32_t>(tokens, ATE::regDataHex); |
| break; |
| } |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::verifyImage(const uint8_t* image, |
| size_t imageSize) |
| { |
| if (!co_await parseImage(image, imageSize)) |
| { |
| error("Image verification failed: image parsing failed"); |
| co_return false; |
| } |
| |
| if (configuration->registersData.empty()) |
| { |
| error("Image verification failed - no data found"); |
| co_return false; |
| } |
| |
| if (configuration->configId == 0) |
| { |
| error("Image verification failed - missing config ID"); |
| co_return false; |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::checkId(PMBusCmd idCmd, uint32_t expected) |
| { |
| constexpr size_t idLength = 2; |
| |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 0 for ID check"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(idCmd); |
| rbuf.resize(statusByteLength + idLength); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to read ID, cmd={CMD}", "CMD", lg2::hex, |
| static_cast<uint8_t>(idCmd)); |
| co_return false; |
| } |
| |
| auto idBytes = std::span(rbuf).subspan(statusByteLength); |
| auto id = bytesToInt<uint32_t>(idBytes); |
| |
| if (id != expected) |
| { |
| error("ID check failed for cmd {CMD}: got {ID}, expected {EXP}", "CMD", |
| lg2::hex, static_cast<uint8_t>(idCmd), "ID", lg2::hex, id, "EXP", |
| lg2::hex, expected); |
| co_return false; |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::isPasswordUnlock() |
| { |
| constexpr uint8_t passwordMatchMask = 0x08; |
| |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 0 for password unlock check"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(PMBusCmd::statusCML); |
| rbuf.resize(statusByteLength); |
| |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to check password unlock status"); |
| co_return false; |
| } |
| |
| auto unlocked = (rbuf[0] & passwordMatchMask) != 0; |
| |
| debug("Password unlock status: {STATUS}", "STATUS", |
| unlocked ? "Unlocked" : "Locked"); |
| |
| co_return unlocked; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::unlockWriteProtect() |
| { |
| constexpr uint8_t writeProtectModeMask = 0x04; |
| constexpr uint8_t unlockMemoryProtect = 0x00; |
| constexpr uint8_t unlockMTPProtect = 0x63; |
| |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf(statusByteLength); |
| |
| // Get write protection mode |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page1); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 1 to check write protection mode"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP297XCmd::writeProtectMode); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to get write protect mode"); |
| co_return false; |
| } |
| |
| auto isMTPMode = (rbuf[0] & writeProtectModeMask) == 0; |
| auto unlockData = isMTPMode ? unlockMTPProtect : unlockMemoryProtect; |
| |
| // Unlock write protection |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 0 to unlock write protection"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(PMBusCmd::writeProtect, unlockData); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to unlock write protection"); |
| co_return false; |
| } |
| |
| debug("Unlocked {MODE} write protection", "MODE", |
| isMTPMode ? "MTP" : "memory"); |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::programPageRegisters( |
| MPSPage page, const std::map<uint8_t, std::vector<MPSData>>& groupedData) |
| { |
| auto pageNum = static_cast<uint8_t>(page); |
| |
| if (groupedData.find(pageNum) == groupedData.end()) |
| { |
| debug("No data found for page {PAGE}", "PAGE", pageNum); |
| co_return true; |
| } |
| |
| const auto& data = groupedData.at(pageNum); |
| |
| // Page 2 is the multi-config page. Enter page2A to write config value |
| // to MTP space directly during multi-config page programming. |
| if (page == MPSPage::page2) |
| { |
| page = MPSPage::page2A; |
| pageNum = static_cast<uint8_t>(page); |
| } |
| |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| tbuf = buildByteVector(PMBusCmd::page, page); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page {PAGE} to program registers", "PAGE", |
| pageNum); |
| co_return false; |
| } |
| |
| auto i2cWriteWithRetry = |
| [&](const std::vector<uint8_t>& tbuf) -> sdbusplus::async::task<bool> { |
| std::vector<uint8_t> rbuf; |
| constexpr size_t maxRetries = 3; |
| constexpr auto retryDelay = std::chrono::milliseconds(10); |
| |
| for (size_t i = 1; i <= maxRetries; ++i) |
| { |
| if (i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| co_return true; |
| } |
| error("I2C write failed, retry {RETRY}", "RETRY", i); |
| co_await sdbusplus::async::sleep_for(ctx, retryDelay); |
| } |
| co_return false; |
| }; |
| |
| for (const auto& regData : data) |
| { |
| tbuf = {regData.addr}; |
| tbuf.insert(tbuf.end(), regData.data.begin(), |
| regData.data.begin() + regData.length); |
| |
| if (!(co_await i2cWriteWithRetry(tbuf))) |
| { |
| error( |
| "Failed to write data {DATA} to register {REG} on page {PAGE}", |
| "DATA", lg2::hex, bytesToInt<uint32_t>(regData.data), "REG", |
| lg2::hex, regData.addr, "PAGE", pageNum); |
| co_return false; |
| } |
| |
| if (page == MPSPage::page2A) |
| { |
| // Page 2A requires a delay after each register write |
| co_await sdbusplus::async::sleep_for(ctx, |
| std::chrono::milliseconds(2)); |
| } |
| } |
| |
| debug("Programmed {N} registers on page {PAGE}", "N", data.size(), "PAGE", |
| pageNum); |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::storeDataIntoMTP() |
| { |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 0 for storing data into MTP"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP297XCmd::storeDataIntoMTP); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to store data into MTP"); |
| co_return false; |
| } |
| |
| // Wait store data into MTP |
| co_await sdbusplus::async::sleep_for(ctx, std::chrono::milliseconds(500)); |
| |
| debug("Stored data into MTP"); |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::enableMTPPageWriteRead() |
| { |
| constexpr uint8_t mtpByteRWEnable = 0x20; |
| |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page1); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 1 to enable MTP page write/read"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP297XCmd::enableMTPPageWR); |
| rbuf.resize(statusByteLength); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to read MTP page write/read status"); |
| co_return false; |
| } |
| |
| uint8_t enableMTPPageWRData = rbuf[0] | mtpByteRWEnable; |
| tbuf = buildByteVector(MP297XCmd::enableMTPPageWR, enableMTPPageWRData); |
| rbuf.resize(0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to enable MTP page write/read"); |
| co_return false; |
| } |
| |
| debug("Enabled MTP page write/read"); |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::enableMultiConfigCRC() |
| { |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 2 to enable multi-config CRC"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP297XCmd::enableMultiConfigCRC); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to enable multi-config CRC"); |
| co_return false; |
| } |
| |
| debug("Enabled multi-config CRC"); |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::getCRC(uint32_t* checksum) |
| { |
| if (checksum == nullptr) |
| { |
| error("getCRC() called with null checksum pointer"); |
| co_return false; |
| } |
| |
| constexpr size_t crcLength = 2; |
| |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| uint16_t userCodeCRC = 0; |
| uint16_t multiConfigCRC = 0; |
| |
| // Read User Code CRC |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page29); |
| rbuf.resize(0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 29 for User Code CRC read"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP297XCmd::readUserCodeCRC); |
| rbuf.resize(crcLength); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to read User Code CRC from device"); |
| co_return false; |
| } |
| |
| userCodeCRC = bytesToInt<uint16_t>(rbuf); |
| |
| // Read Multi Config CRC |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2A); |
| rbuf.resize(0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 2A for Multi Config CRC read"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP297XCmd::readMultiConfigCRC); |
| rbuf.resize(crcLength); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to read Multi Config CRC from device"); |
| co_return false; |
| } |
| |
| multiConfigCRC = bytesToInt<uint16_t>(rbuf); |
| |
| // Combine: [byte3 byte2] = userCodeCRC, [byte1 byte0] = multiConfigCRC |
| *checksum = (static_cast<uint32_t>(userCodeCRC) << 16) | |
| static_cast<uint32_t>(multiConfigCRC); |
| |
| debug("Read CRC: {CRC}", "CRC", lg2::hex, *checksum); |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::checkMTPCRC() |
| { |
| uint32_t crc = 0; |
| auto expectedCRC = (configuration->crcUser << 16) | configuration->crcMulti; |
| |
| // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch) |
| if (!co_await getCRC(&crc)) |
| // NOLINTEND(clang-analyzer-core.uninitialized.Branch) |
| { |
| error("Failed to get CRC for MTP check"); |
| co_return false; |
| } |
| |
| debug("MTP CRC: {CRC}, Expected: {EXP}", "CRC", lg2::hex, crc, "EXP", |
| lg2::hex, expectedCRC); |
| |
| co_return crc == expectedCRC; |
| } |
| |
| bool MP297X::forcedUpdateAllowed() |
| { |
| return true; |
| } |
| |
| sdbusplus::async::task<bool> MP297X::updateFirmware(bool force) |
| { |
| (void)force; |
| |
| auto groupedConfigData = getGroupedConfigData(); |
| |
| if (!co_await checkId(PMBusCmd::mfrId, configuration->vendorId)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await checkId(PMBusCmd::mfrModel, configuration->productId)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await isPasswordUnlock()) |
| { |
| co_return false; |
| } |
| |
| if (!co_await unlockWriteProtect()) |
| { |
| co_return false; |
| } |
| |
| if (!co_await programPageRegisters(MPSPage::page0, groupedConfigData)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await programPageRegisters(MPSPage::page1, groupedConfigData)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await storeDataIntoMTP()) |
| { |
| co_return false; |
| } |
| |
| if (!co_await enableMTPPageWriteRead()) |
| { |
| co_return false; |
| } |
| |
| if (!co_await enableMultiConfigCRC()) |
| { |
| co_return false; |
| } |
| |
| if (!co_await programPageRegisters(MPSPage::page2, groupedConfigData)) |
| { |
| co_return false; |
| } |
| |
| if (!(co_await checkMTPCRC())) |
| { |
| co_return false; |
| } |
| |
| co_return true; |
| } |
| |
| } // namespace phosphor::software::VR |