xref: /openbmc/phosphor-bmc-code-mgmt/cpld/lattice/lattice_xo5_cpld.cpp (revision a3902c83e3b65c3612e419cc1e616c56be65ff41)
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