#include "mp297x.hpp" #include "common/include/utils.hpp" #include #include 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 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(tokens, ATE::regName); if (regName == crcUserRegName) { configuration->configId = parser->getVal(tokens, ATE::configId); configuration->crcUser = parser->getVal(tokens, ATE::regDataHex); } else if (regName == crcMultiRegName) { configuration->crcMulti = parser->getVal(tokens, ATE::regDataHex); break; } } co_return true; } sdbusplus::async::task 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 MP297X::checkId(PMBusCmd idCmd, uint32_t expected) { constexpr size_t idLength = 2; std::vector tbuf; std::vector 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(idCmd)); co_return false; } auto idBytes = std::span(rbuf).subspan(statusByteLength); auto id = bytesToInt(idBytes); if (id != expected) { error("ID check failed for cmd {CMD}: got {ID}, expected {EXP}", "CMD", lg2::hex, static_cast(idCmd), "ID", lg2::hex, id, "EXP", lg2::hex, expected); co_return false; } co_return true; } sdbusplus::async::task MP297X::isPasswordUnlock() { constexpr uint8_t passwordMatchMask = 0x08; std::vector tbuf; std::vector 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 MP297X::unlockWriteProtect() { constexpr uint8_t writeProtectModeMask = 0x04; constexpr uint8_t unlockMemoryProtect = 0x00; constexpr uint8_t unlockMTPProtect = 0x63; std::vector tbuf; std::vector 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 MP297X::programPageRegisters( MPSPage page, const std::map>& groupedData) { auto pageNum = static_cast(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(page); } std::vector tbuf; std::vector 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& tbuf) -> sdbusplus::async::task { std::vector 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(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 MP297X::storeDataIntoMTP() { std::vector tbuf; std::vector 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 MP297X::enableMTPPageWriteRead() { constexpr uint8_t mtpByteRWEnable = 0x20; std::vector tbuf; std::vector 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 MP297X::enableMultiConfigCRC() { std::vector tbuf; std::vector 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 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 tbuf; std::vector 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(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(rbuf); // Combine: [byte3 byte2] = userCodeCRC, [byte1 byte0] = multiConfigCRC *checksum = (static_cast(userCodeCRC) << 16) | static_cast(multiConfigCRC); debug("Read CRC: {CRC}", "CRC", lg2::hex, *checksum); co_return true; } sdbusplus::async::task 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 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