| #include "mp2x6xx.hpp" |
| |
| #include "common/include/utils.hpp" |
| |
| #include <phosphor-logging/lg2.hpp> |
| |
| PHOSPHOR_LOG2_USING; |
| |
| namespace phosphor::software::VR |
| { |
| |
| static constexpr size_t vendorIdLength = 3; |
| static constexpr size_t deviceIdLength = 4; |
| static constexpr size_t configIdLength = 2; |
| static constexpr size_t statusByteLength = 1; |
| static constexpr size_t crcLength = 2; |
| |
| static constexpr std::string_view productIdRegName = "TRIM_MFR_PRODUCT_ID2"; |
| static constexpr std::string_view crcUserRegName = "CRC_USER"; |
| |
| static constexpr uint8_t pageMask = 0x0F; |
| static constexpr uint8_t configMask = 0xF0; |
| |
| static constexpr uint8_t disableWriteProtect = 0x00; |
| static constexpr uint16_t disablePage2WriteProtect = 0x128C; |
| static constexpr uint16_t disablePage3WriteProtect = 0x0082; |
| |
| enum class MP2X6XXCmd : uint8_t |
| { |
| // Page 0 commands |
| readCRCReg = 0xED, |
| // Page 1 commands |
| mfrMTPMemoryCtrl = 0xCC, |
| // Page 2 commands |
| selectConfigCtrl = 0x1A, |
| // Page 3 commands |
| mfrMTPMemoryCtrlPage3 = 0x81, |
| }; |
| |
| sdbusplus::async::task<bool> MP2X6XX::parseDeviceConfiguration() |
| { |
| if (!configuration) |
| { |
| error("Device configuration not initialized"); |
| co_return false; |
| } |
| |
| configuration->vendorId = 0x4D5053; |
| |
| for (const auto& tokens : parser->lineTokens) |
| { |
| if (!parser->isValidDataTokens(tokens)) |
| { |
| continue; |
| } |
| |
| auto regName = parser->getVal<std::string>(tokens, ATE::regName); |
| if (regName == productIdRegName) |
| { |
| configuration->productId = |
| parser->getVal<uint32_t>(tokens, ATE::regDataHex); |
| configuration->configId = |
| parser->getVal<uint32_t>(tokens, ATE::configId); |
| } |
| else if (regName == crcUserRegName) |
| { |
| configuration->crcUser = |
| parser->getVal<uint32_t>(tokens, ATE::regDataHex); |
| break; |
| } |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::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->productId == 0 || configuration->configId == 0) |
| { |
| error("Image verification failed - missing product or config ID"); |
| co_return false; |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::checkId(PMBusCmd pmBusCmd, |
| uint32_t expected) |
| { |
| const uint8_t cmd = static_cast<uint8_t>(pmBusCmd); |
| size_t idLen = 0; |
| bool blockRead = false; |
| |
| switch (pmBusCmd) |
| { |
| case PMBusCmd::mfrId: |
| idLen = vendorIdLength; |
| blockRead = true; |
| break; |
| case PMBusCmd::icDeviceId: |
| idLen = deviceIdLength; |
| blockRead = true; |
| break; |
| case PMBusCmd::mfrSerial: |
| idLen = configIdLength; |
| break; |
| default: |
| error("Invalid command for ID check: {CMD}", "CMD", lg2::hex, cmd); |
| co_return false; |
| } |
| |
| std::vector<uint8_t> rbuf; |
| std::vector<uint8_t> tbuf; |
| |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 0 for ID check"); |
| co_return false; |
| } |
| |
| tbuf = {cmd}; |
| rbuf.resize(idLen + (blockRead ? statusByteLength : 0)); |
| |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("I2C failure during ID check, cmd {CMD}", "CMD", lg2::hex, cmd); |
| co_return false; |
| } |
| |
| auto idBytes = std::span(rbuf).subspan(blockRead ? statusByteLength : 0); |
| uint32_t id = bytesToInt<uint32_t>(idBytes); |
| |
| if (id != expected) |
| { |
| error("ID check failed for cmd {CMD}: got {ID}, expected {EXP}", "CMD", |
| lg2::hex, cmd, "ID", lg2::hex, id, "EXP", lg2::hex, expected); |
| co_return false; |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::unlockWriteProtect() |
| { |
| 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 unlocking write protect"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(PMBusCmd::writeProtect, disableWriteProtect); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to disable write protect"); |
| co_return false; |
| } |
| |
| // unlock page 2 write protect |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page1); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 1 for unlocking write protect for page 2"); |
| co_return false; |
| } |
| |
| tbuf = |
| buildByteVector(MP2X6XXCmd::mfrMTPMemoryCtrl, disablePage2WriteProtect); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to unlock page 2 write protect"); |
| co_return false; |
| } |
| |
| // unlock page 3 write protect |
| tbuf = buildByteVector(PMBusCmd::page, MPSPage::page3); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page 3 for unlocking write protect for page 3"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP2X6XXCmd::mfrMTPMemoryCtrlPage3, |
| disablePage3WriteProtect); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to unlock page 3 write protect"); |
| co_return false; |
| } |
| |
| debug("Unlocked write protect"); |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::selectConfig(uint8_t config) |
| { |
| // MPS config select command: |
| // Writes to Page 2 @ 0x1A: value = 0x0F00 | ((config + 7) << 4) |
| // For config 1–6 → result: 0x0F80 to 0x0FD0 |
| |
| 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 for configuration switch"); |
| co_return false; |
| } |
| |
| constexpr uint8_t baseOffset = 7; |
| uint8_t encodedNibble = static_cast<uint8_t>((config + baseOffset) << 4); |
| uint16_t command = 0x0F00 | encodedNibble; |
| |
| tbuf = buildByteVector(MP2X6XXCmd::selectConfigCtrl, command); |
| |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to write config select command {CMD} for config {CONFIG}", |
| "CMD", lg2::hex, command, "CONFIG", config); |
| co_return false; |
| } |
| |
| debug("Switched to config {CONFIG}", "CONFIG", config); |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::programConfigData( |
| const std::vector<MPSData>& gdata) |
| { |
| std::vector<uint8_t> tbuf; |
| std::vector<uint8_t> rbuf; |
| |
| for (const auto& data : gdata) |
| { |
| uint8_t page = data.page & pageMask; |
| |
| tbuf = buildByteVector(PMBusCmd::page, page); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to set page {PAGE} for register {REG}", "PAGE", page, |
| "REG", lg2::hex, data.addr); |
| co_return false; |
| } |
| |
| tbuf = {data.addr}; |
| tbuf.insert(tbuf.end(), data.data.begin(), |
| data.data.begin() + data.length); |
| |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error( |
| "Failed to write data {DATA} to register {REG} on page {PAGE}", |
| "DATA", lg2::hex, bytesToInt<uint32_t>(data.data), "REG", |
| lg2::hex, data.addr, "PAGE", page); |
| co_return false; |
| } |
| } |
| |
| if (!co_await storeUserCode()) |
| { |
| error("Failed to store user code after programming config data"); |
| co_return false; |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::configAllRegisters() |
| { |
| for (const auto& [config, gdata] : getGroupedConfigData(configMask, 4)) |
| { |
| debug("Configuring registers for config {CONFIG}", "CONFIG", config); |
| |
| // Select the appropriate config before programming its registers |
| if (config > 0 && !co_await selectConfig(config)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await programConfigData(gdata)) |
| { |
| error("Failed to program configuration {CONFIG}", "CONFIG", config); |
| co_return false; |
| } |
| |
| debug("Configured {SIZE} registers for config {CONFIG}", "SIZE", |
| gdata.size(), "CONFIG", config); |
| } |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::storeUserCode() |
| { |
| 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 user code"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(PMBusCmd::storeUserCode); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to store user code"); |
| co_return false; |
| } |
| |
| // Wait store user code |
| co_await sdbusplus::async::sleep_for(ctx, std::chrono::milliseconds(500)); |
| |
| debug("Stored user code"); |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::getCRC(uint32_t* checksum) |
| { |
| if (checksum == nullptr) |
| { |
| error("getCRC() called with null checksum pointer"); |
| co_return false; |
| } |
| |
| 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 CRC read"); |
| co_return false; |
| } |
| |
| tbuf = buildByteVector(MP2X6XXCmd::readCRCReg); |
| rbuf.resize(crcLength); |
| if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| { |
| error("Failed to read CRC from device"); |
| co_return false; |
| } |
| |
| if (rbuf.size() < crcLength) |
| { |
| error("CRC read returned insufficient data"); |
| co_return false; |
| } |
| |
| *checksum = bytesToInt<uint32_t>(rbuf); |
| |
| debug("Read CRC: {CRC}", "CRC", lg2::hex, *checksum); |
| |
| co_return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::checkMTPCRC() |
| { |
| uint32_t crc = 0; |
| // 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, configuration->crcUser); |
| |
| co_return configuration->crcUser == crc; |
| } |
| |
| bool MP2X6XX::forcedUpdateAllowed() |
| { |
| return true; |
| } |
| |
| sdbusplus::async::task<bool> MP2X6XX::updateFirmware(bool force) |
| { |
| (void)force; |
| |
| if (!co_await checkId(PMBusCmd::mfrId, configuration->vendorId)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await checkId(PMBusCmd::icDeviceId, configuration->productId)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await checkId(PMBusCmd::mfrSerial, configuration->configId)) |
| { |
| co_return false; |
| } |
| |
| if (!co_await unlockWriteProtect()) |
| { |
| co_return false; |
| } |
| |
| if (!co_await configAllRegisters()) |
| { |
| co_return false; |
| } |
| |
| if (!(co_await checkMTPCRC())) |
| { |
| co_return false; |
| } |
| |
| co_return true; |
| } |
| |
| } // namespace phosphor::software::VR |