blob: e9ae9cd8564a8dd79e0bd2143e1a8e146079eb1c [file] [log] [blame]
#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