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
getStartBlock(uint8_t cfg,uint8_t & startBlock)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
waitUntilReady(std::chrono::milliseconds timeout)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
eraseCfg()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
programPage(uint8_t block,uint8_t page,const std::vector<uint8_t> & data)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
programCfg()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
readPage(uint8_t block,uint8_t page,std::vector<uint8_t> & data)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
verifyCfg()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
readUserCode(uint32_t & userCode)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
prepareUpdate(const uint8_t * image,size_t imageSize)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
doUpdate()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
finishUpdate()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