1 #include "lattice_xo5_cpld.hpp" 2 3 #include <phosphor-logging/lg2.hpp> 4 5 namespace phosphor::software::cpld 6 { 7 8 constexpr std::chrono::milliseconds ReadyPollInterval(10); 9 constexpr std::chrono::milliseconds ReadyTimeout(1000); 10 11 enum class xo5Cmd : uint8_t 12 { 13 sectorErase = 0xd8, 14 pageProgram = 0x02, 15 pageRead = 0x0b, 16 readUsercode = 0xc0 17 }; 18 19 enum class xo5Status : uint8_t 20 { 21 ready = 0x00, 22 notReady = 0xff 23 }; 24 25 struct xo5Cfg 26 { 27 static constexpr size_t pageSize = 256; 28 static constexpr size_t pagesPerBlock = 256; 29 static constexpr size_t blocksPerCfg = 11; 30 }; 31 32 static bool getStartBlock(uint8_t cfg, uint8_t& startBlock) 33 { 34 static constexpr std::array<uint8_t, 3> cfgStartBlocks = {0x01, 0x10, 0x1F}; 35 36 if (cfg >= cfgStartBlocks.size()) 37 { 38 return false; 39 } 40 41 startBlock = cfgStartBlocks[cfg]; 42 return true; 43 } 44 45 sdbusplus::async::task<bool> LatticeXO5CPLD::waitUntilReady( 46 std::chrono::milliseconds timeout) 47 { 48 const auto endTime = std::chrono::steady_clock::now() + timeout; 49 50 auto readDummy = [this]() -> sdbusplus::async::task<bool> { 51 std::vector<uint8_t> request = {}; 52 std::vector<uint8_t> response = {0xff}; 53 if (!i2cInterface.sendReceive(request, response)) 54 { 55 lg2::error("Failed to read."); 56 co_return false; 57 } 58 if (response.at(0) == static_cast<uint8_t>(xo5Status::ready)) 59 { 60 co_return true; 61 } 62 co_return false; 63 }; 64 65 while (std::chrono::steady_clock::now() < endTime) 66 { 67 if (co_await readDummy()) 68 { 69 co_return true; 70 } 71 co_await sdbusplus::async::sleep_for(ctx, ReadyPollInterval); 72 } 73 74 lg2::error("Timeout waiting for device ready"); 75 co_return false; 76 } 77 78 sdbusplus::async::task<bool> LatticeXO5CPLD::eraseCfg() 79 { 80 auto cfgIndex = (target == "CFG0") ? 0 : 1; 81 uint8_t startBlock; 82 if (!getStartBlock(cfgIndex, startBlock)) 83 { 84 lg2::error("Error: invalid cfg index."); 85 co_return false; 86 } 87 const auto endBlock = startBlock + xo5Cfg::blocksPerCfg; 88 89 auto eraseBlock = [this](uint8_t block) -> sdbusplus::async::task<bool> { 90 std::vector<uint8_t> request; 91 std::vector<uint8_t> response = {}; 92 request.reserve(4); 93 request.push_back(static_cast<uint8_t>(xo5Cmd::sectorErase)); 94 request.push_back(block); 95 request.push_back(0x0); 96 request.push_back(0x0); 97 if (!i2cInterface.sendReceive(request, response)) 98 { 99 lg2::error("Failed to erase block"); 100 co_return false; 101 } 102 co_return true; 103 }; 104 105 for (size_t block = startBlock; block < endBlock; ++block) 106 { 107 if (!(co_await eraseBlock(block))) 108 { 109 lg2::error("Erase failed: Block {BLOCK}", "BLOCK", block); 110 co_return false; 111 } 112 if (!(co_await waitUntilReady(ReadyTimeout))) 113 { 114 lg2::error("Failed to wait until ready"); 115 co_return false; 116 } 117 } 118 co_return true; 119 } 120 121 sdbusplus::async::task<bool> LatticeXO5CPLD::programPage( 122 uint8_t block, uint8_t page, const std::vector<uint8_t>& data) 123 { 124 std::vector<uint8_t> request; 125 std::vector<uint8_t> response = {}; 126 request.reserve(4 + data.size()); 127 request.push_back(static_cast<uint8_t>(xo5Cmd::pageProgram)); 128 request.push_back(block); 129 request.push_back(page); 130 request.push_back(0x0); 131 request.insert(request.end(), data.begin(), data.end()); 132 133 if (!i2cInterface.sendReceive(request, response)) 134 { 135 co_return false; 136 } 137 co_return true; 138 } 139 140 sdbusplus::async::task<bool> LatticeXO5CPLD::programCfg() 141 { 142 using diff_t = std::vector<uint8_t>::difference_type; 143 144 auto cfgIndex = (target == "CFG0") ? 0 : 1; 145 uint8_t startBlock; 146 if (!getStartBlock(cfgIndex, startBlock)) 147 { 148 lg2::error("Error: invalid cfg index."); 149 co_return false; 150 } 151 const auto endBlock = startBlock + xo5Cfg::blocksPerCfg; 152 const auto& cfgData = fwInfo.cfgData; 153 const auto totalBytes = cfgData.size(); 154 size_t bytesWritten = 0; 155 156 for (size_t block = startBlock; block < endBlock; ++block) 157 { 158 for (size_t page = 0; page < xo5Cfg::pagesPerBlock; ++page) 159 { 160 if (bytesWritten >= totalBytes) 161 { 162 co_return true; 163 } 164 165 auto offset = static_cast<diff_t>(bytesWritten); 166 auto remaining = static_cast<diff_t>(totalBytes - bytesWritten); 167 const auto chunkSize = 168 std::min(static_cast<diff_t>(xo5Cfg::pageSize), remaining); 169 std::vector<uint8_t> chunk( 170 std::next(cfgData.begin(), offset), 171 std::next(cfgData.begin(), offset + chunkSize)); 172 173 auto success = false; 174 success |= co_await programPage(block, page, chunk); 175 co_await sdbusplus::async::sleep_for(ctx, ReadyPollInterval); 176 success |= co_await waitUntilReady(ReadyTimeout); 177 if (!success) 178 { 179 lg2::error("Failed to program block {BLOCK} page {PAGE}", 180 "BLOCK", block, "PAGE", page); 181 co_return false; 182 } 183 bytesWritten += chunkSize; 184 } 185 } 186 187 co_return true; 188 } 189 190 sdbusplus::async::task<bool> LatticeXO5CPLD::readPage( 191 uint8_t block, uint8_t page, std::vector<uint8_t>& data) 192 { 193 if (data.empty()) 194 { 195 lg2::error("Error: data vector is empty."); 196 co_return false; 197 } 198 std::vector<uint8_t> request = {}; 199 std::vector<uint8_t> response = {}; 200 request.reserve(4); 201 request.push_back(static_cast<uint8_t>(xo5Cmd::pageRead)); 202 request.push_back(block); 203 request.push_back(page); 204 request.push_back(0x0); 205 206 if (!i2cInterface.sendReceive(request, response)) 207 { 208 co_return false; 209 } 210 lg2::debug("Read page {BLOCK} {PAGE} succeeded", "BLOCK", block, "PAGE", 211 page); 212 request.clear(); 213 214 std::this_thread::sleep_for(std::chrono::milliseconds(1)); 215 216 if (!(co_await waitUntilReady(ReadyTimeout))) 217 { 218 co_return false; 219 } 220 221 if (!i2cInterface.sendReceive(request, data)) 222 { 223 co_return false; 224 } 225 226 co_return data[0] == static_cast<uint8_t>(xo5Status::ready); 227 } 228 229 sdbusplus::async::task<bool> LatticeXO5CPLD::verifyCfg() 230 { 231 using diff_t = std::vector<uint8_t>::difference_type; 232 233 auto cfgIndex = (target == "CFG0") ? 0 : 1; 234 uint8_t startBlock; 235 if (!getStartBlock(cfgIndex, startBlock)) 236 { 237 lg2::error("Error: invalid cfg index."); 238 co_return false; 239 } 240 const auto endBlock = startBlock + xo5Cfg::blocksPerCfg; 241 const auto& cfgData = fwInfo.cfgData; 242 const auto totalBytes = cfgData.size(); 243 uint8_t readBuffer[1 + xo5Cfg::pageSize]; 244 size_t bytesVerified = 0; 245 246 for (size_t block = startBlock; block < endBlock; ++block) 247 { 248 for (size_t page = 0; page < xo5Cfg::pagesPerBlock; ++page) 249 { 250 if (bytesVerified >= totalBytes) 251 { 252 co_return true; 253 } 254 255 auto offset = static_cast<diff_t>(bytesVerified); 256 auto remaining = static_cast<diff_t>(totalBytes - bytesVerified); 257 const auto chunkSize = 258 std::min(static_cast<diff_t>(xo5Cfg::pageSize), remaining); 259 260 std::vector<uint8_t> expected( 261 std::next(cfgData.begin(), offset), 262 std::next(cfgData.begin(), offset + chunkSize)); 263 264 std::vector<uint8_t> chunk; 265 { 266 std::vector<uint8_t> readVec(readBuffer, 267 readBuffer + 1 + chunkSize); 268 269 if (co_await readPage(block, page, readVec)) 270 { 271 chunk.assign(readVec.begin() + 1, readVec.end()); 272 } 273 else 274 { 275 chunk.clear(); 276 } 277 } 278 279 if (chunk.empty()) 280 { 281 lg2::error("Failed to read Block {BLOCK} Page {PAGE}", "BLOCK", 282 block, "PAGE", page); 283 co_return false; 284 } 285 if (!std::equal(chunk.begin(), chunk.end(), expected.begin())) 286 { 287 lg2::error("VERIFY FAILED: Block {BLOCK} Page {PAGE}", "BLOCK", 288 block, "PAGE", page); 289 co_return false; 290 } 291 292 bytesVerified += chunkSize; 293 } 294 } 295 co_return true; 296 } 297 298 sdbusplus::async::task<bool> LatticeXO5CPLD::readUserCode(uint32_t& userCode) 299 { 300 constexpr size_t resSize = 5; 301 std::vector<uint8_t> request = {commandReadFwVersion, 0x0, 0x0, 0x0}; 302 std::vector<uint8_t> response(resSize, 0); 303 304 if (!i2cInterface.sendReceive(request, response)) 305 { 306 lg2::error("Failed to send read user code request."); 307 co_return false; 308 } 309 310 userCode |= response[4] << 24; 311 userCode |= response[3] << 16; 312 userCode |= response[2] << 8; 313 userCode |= response[1]; 314 315 co_return true; 316 } 317 318 sdbusplus::async::task<bool> LatticeXO5CPLD::prepareUpdate(const uint8_t* image, 319 size_t imageSize) 320 { 321 if (target.empty()) 322 { 323 target = "CFG0"; 324 } 325 else if (target != "CFG0" && target != "CFG1") 326 { 327 lg2::error("Error: unknown target."); 328 co_return false; 329 } 330 331 if (!jedFileParser(image, imageSize)) 332 { 333 lg2::error("JED file parsing failed"); 334 co_return false; 335 } 336 lg2::debug("JED file parsing success"); 337 338 if (!(co_await waitUntilReady(ReadyTimeout))) 339 { 340 lg2::error("Error: Device not ready."); 341 co_return false; 342 } 343 344 co_return true; 345 } 346 347 sdbusplus::async::task<bool> LatticeXO5CPLD::doUpdate() 348 { 349 lg2::debug("Erasing {TARGET}...", "TARGET", target); 350 if (!(co_await eraseCfg())) 351 { 352 lg2::error("Erase cfg data failed."); 353 co_return false; 354 } 355 356 lg2::debug("Programming {TARGET}...", "TARGET", target); 357 if (!(co_await programCfg())) 358 { 359 lg2::error("Program cfg data failed."); 360 co_return false; 361 } 362 363 co_return true; 364 } 365 366 sdbusplus::async::task<bool> LatticeXO5CPLD::finishUpdate() 367 { 368 lg2::debug("Verifying {TARGET}...", "TARGET", target); 369 if (!(co_await verifyCfg())) 370 { 371 lg2::error("Verify cfg data failed."); 372 co_return false; 373 } 374 co_return true; 375 } 376 377 } // namespace phosphor::software::cpld 378