xref: /openbmc/entity-manager/src/fru_device/fru_device.cpp (revision 4276d4ad645dd71b7dbc22b1c4f5871957971b57)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
3 
4 #include "../utils.hpp"
5 #include "fru_utils.hpp"
6 
7 #include <fcntl.h>
8 #include <sys/inotify.h>
9 #include <sys/ioctl.h>
10 
11 #include <boost/asio/io_context.hpp>
12 #include <boost/asio/steady_timer.hpp>
13 #include <nlohmann/json.hpp>
14 #include <phosphor-logging/lg2.hpp>
15 #include <sdbusplus/asio/connection.hpp>
16 #include <sdbusplus/asio/object_server.hpp>
17 
18 #include <array>
19 #include <cerrno>
20 #include <charconv>
21 #include <chrono>
22 #include <ctime>
23 #include <filesystem>
24 #include <flat_map>
25 #include <flat_set>
26 #include <fstream>
27 #include <functional>
28 #include <future>
29 #include <iomanip>
30 #include <limits>
31 #include <map>
32 #include <optional>
33 #include <regex>
34 #include <set>
35 #include <sstream>
36 #include <string>
37 #include <thread>
38 #include <utility>
39 #include <variant>
40 #include <vector>
41 
42 extern "C"
43 {
44 #include <i2c/smbus.h>
45 #include <linux/i2c-dev.h>
46 }
47 
48 namespace fs = std::filesystem;
49 constexpr size_t maxFruSize = 512;
50 constexpr size_t maxEepromPageIndex = 255;
51 constexpr size_t busTimeoutSeconds = 10;
52 
53 constexpr const char* blocklistPath = PACKAGE_DIR "blacklist.json";
54 
55 const static constexpr char* baseboardFruLocation =
56     "/etc/fru/baseboard.fru.bin";
57 
58 const static constexpr char* i2CDevLocation = "/dev";
59 
60 constexpr const char* fruDevice16BitDetectMode = FRU_DEVICE_16BITDETECTMODE;
61 
62 // TODO Refactor these to not be globals
63 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
64 static std::flat_map<size_t, std::optional<std::flat_set<size_t>>> busBlocklist;
65 struct FindDevicesWithCallback;
66 
67 static std::flat_map<std::pair<size_t, size_t>,
68                      std::shared_ptr<sdbusplus::asio::dbus_interface>>
69     foundDevices;
70 
71 static std::flat_map<size_t, std::flat_set<size_t>> failedAddresses;
72 static std::flat_map<size_t, std::flat_set<size_t>> fruAddresses;
73 
74 boost::asio::io_context io;
75 // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
76 
77 bool updateFruProperty(
78     const std::string& propertyValue, uint32_t bus, uint32_t address,
79     const std::string& propertyName,
80     std::flat_map<std::pair<size_t, size_t>,
81                   std::shared_ptr<sdbusplus::asio::dbus_interface>>&
82         dbusInterfaceMap,
83     size_t& unknownBusObjectCount, const bool& powerIsOn,
84     const std::set<size_t>& addressBlocklist,
85     sdbusplus::asio::object_server& objServer);
86 
87 // Given a bus/address, produce the path in sysfs for an eeprom.
getEepromPath(size_t bus,size_t address)88 static std::string getEepromPath(size_t bus, size_t address)
89 {
90     std::stringstream output;
91     output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
92            << std::setfill('0') << std::setw(4) << std::hex << address
93            << "/eeprom";
94     return output.str();
95 }
96 
hasEepromFile(size_t bus,size_t address)97 static bool hasEepromFile(size_t bus, size_t address)
98 {
99     auto path = getEepromPath(bus, address);
100     try
101     {
102         return fs::exists(path);
103     }
104     catch (...)
105     {
106         return false;
107     }
108 }
109 
readFromEeprom(int fd,off_t offset,size_t len,uint8_t * buf)110 static int64_t readFromEeprom(int fd, off_t offset, size_t len, uint8_t* buf)
111 {
112     auto result = lseek(fd, offset, SEEK_SET);
113     if (result < 0)
114     {
115         lg2::error("failed to seek");
116         return -1;
117     }
118 
119     return read(fd, buf, len);
120 }
121 
busStrToInt(const std::string_view busName)122 static int busStrToInt(const std::string_view busName)
123 {
124     auto findBus = busName.rfind('-');
125     if (findBus == std::string::npos)
126     {
127         return -1;
128     }
129     std::string_view num = busName.substr(findBus + 1);
130     int val = 0;
131     bool fullMatch = false;
132     fromCharsWrapper(num, val, fullMatch);
133     return val;
134 }
135 
getRootBus(size_t bus)136 static int getRootBus(size_t bus)
137 {
138     auto ec = std::error_code();
139     auto path = std::filesystem::read_symlink(
140         std::filesystem::path(
141             "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"),
142         ec);
143     if (ec)
144     {
145         return -1;
146     }
147 
148     std::string filename = path.filename();
149     auto findBus = filename.find('-');
150     if (findBus == std::string::npos)
151     {
152         return -1;
153     }
154     return std::stoi(filename.substr(0, findBus));
155 }
156 
isMuxBus(size_t bus)157 static bool isMuxBus(size_t bus)
158 {
159     auto ec = std::error_code();
160     auto isSymlink =
161         is_symlink(std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
162                                          std::to_string(bus) + "/mux_device"),
163                    ec);
164     return (!ec && isSymlink);
165 }
166 
makeProbeInterface(size_t bus,size_t address,sdbusplus::asio::object_server & objServer)167 static void makeProbeInterface(size_t bus, size_t address,
168                                sdbusplus::asio::object_server& objServer)
169 {
170     if (isMuxBus(bus))
171     {
172         return; // the mux buses are random, no need to publish
173     }
174     auto [it, success] = foundDevices.emplace(
175         std::make_pair(bus, address),
176         objServer.add_interface(
177             "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" +
178                 std::to_string(address),
179             "xyz.openbmc_project.Inventory.Item.I2CDevice"));
180     if (!success)
181     {
182         return; // already added
183     }
184     it->second->register_property("Bus", bus);
185     it->second->register_property("Address", address);
186     it->second->initialize();
187 }
188 
189 // Issue an I2C transaction to first write to_target_buf_len bytes,then read
190 // from_target_buf_len bytes.
i2cSmbusWriteThenRead(int file,uint16_t address,uint8_t * toTargetBuf,uint8_t toTargetBufLen,uint8_t * fromTargetBuf,uint8_t fromTargetBufLen)191 static int i2cSmbusWriteThenRead(
192     int file, uint16_t address, uint8_t* toTargetBuf, uint8_t toTargetBufLen,
193     uint8_t* fromTargetBuf, uint8_t fromTargetBufLen)
194 {
195     if (toTargetBuf == nullptr || toTargetBufLen == 0 ||
196         fromTargetBuf == nullptr || fromTargetBufLen == 0)
197     {
198         return -1;
199     }
200 
201     constexpr size_t smbusWriteThenReadMsgCount = 2;
202     std::array<struct i2c_msg, smbusWriteThenReadMsgCount> msgs{};
203     struct i2c_rdwr_ioctl_data rdwr{};
204 
205     msgs[0].addr = address;
206     msgs[0].flags = 0;
207     msgs[0].len = toTargetBufLen;
208     msgs[0].buf = toTargetBuf;
209     msgs[1].addr = address;
210     msgs[1].flags = I2C_M_RD;
211     msgs[1].len = fromTargetBufLen;
212     msgs[1].buf = fromTargetBuf;
213 
214     rdwr.msgs = msgs.data();
215     rdwr.nmsgs = msgs.size();
216 
217     int ret = ioctl(file, I2C_RDWR, &rdwr);
218 
219     return (ret == static_cast<int>(msgs.size())) ? msgs[1].len : -1;
220 }
221 
readData(bool is16bit,bool isBytewise,int file,uint16_t address,off_t offset,size_t len,uint8_t * buf)222 static int64_t readData(bool is16bit, bool isBytewise, int file,
223                         uint16_t address, off_t offset, size_t len,
224                         uint8_t* buf)
225 {
226     if (!is16bit)
227     {
228         if (!isBytewise)
229         {
230             return i2c_smbus_read_i2c_block_data(
231                 file, static_cast<uint8_t>(offset), len, buf);
232         }
233 
234         std::span<uint8_t> bufspan{buf, len};
235         for (size_t i = 0; i < len; i++)
236         {
237             int byte = i2c_smbus_read_byte_data(
238                 file, static_cast<uint8_t>(offset + i));
239             if (byte < 0)
240             {
241                 return static_cast<int64_t>(byte);
242             }
243             bufspan[i] = static_cast<uint8_t>(byte);
244         }
245         return static_cast<int64_t>(len);
246     }
247 
248     offset = htobe16(offset);
249     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
250     uint8_t* u8Offset = reinterpret_cast<uint8_t*>(&offset);
251     return i2cSmbusWriteThenRead(file, address, u8Offset, 2, buf, len);
252 }
253 
254 // Mode_1:
255 // --------
256 // Please refer to document docs/address_size_detection_modes.md for
257 // more details and explanations.
isDevice16BitMode1(int file)258 static std::optional<bool> isDevice16BitMode1(int file)
259 {
260     // Set the higher data word address bits to 0. It's safe on 8-bit
261     // addressing EEPROMs because it doesn't write any actual data.
262     int ret = i2c_smbus_write_byte(file, 0);
263     if (ret < 0)
264     {
265         return std::nullopt;
266     }
267 
268     /* Get first byte */
269     int byte1 = i2c_smbus_read_byte_data(file, 0);
270     if (byte1 < 0)
271     {
272         return std::nullopt;
273     }
274     /* Read 7 more bytes, it will read same first byte in case of
275      * 8 bit but it will read next byte in case of 16 bit
276      */
277     for (int i = 0; i < 7; i++)
278     {
279         int byte2 = i2c_smbus_read_byte_data(file, 0);
280         if (byte2 < 0)
281         {
282             return std::nullopt;
283         }
284         if (byte2 != byte1)
285         {
286             return true;
287         }
288     }
289     return false;
290 }
291 
292 // Mode_2:
293 // --------
294 // Please refer to document docs/address_size_detection_modes.md for
295 // more details and explanations.
isDevice16BitMode2(int file,uint16_t address)296 static std::optional<bool> isDevice16BitMode2(int file, uint16_t address)
297 {
298     uint8_t first = 0;
299     uint8_t cur = 0;
300     uint16_t v = 0;
301     int ret = 0;
302     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
303     uint8_t* p = reinterpret_cast<uint8_t*>(&v);
304 
305     /*
306      * Write 2 bytes byte0 = 0, byte1 = {0..7} and then subsequent read byte
307      * It will read same first byte in case of 8 bit but
308      * it will read next byte in case of 16 bit
309      */
310     for (int i = 0; i < 8; i++)
311     {
312         v = htobe16(i);
313 
314         ret = i2cSmbusWriteThenRead(file, address, p, 2, &cur, 1);
315         if (ret < 0)
316         {
317             return std::nullopt;
318         }
319 
320         if (i == 0)
321         {
322             first = cur;
323         }
324 
325         if (first != cur)
326         {
327             return true;
328         }
329     }
330     return false;
331 }
332 
isDevice16Bit(int file,uint16_t address)333 static std::optional<bool> isDevice16Bit(int file, uint16_t address)
334 {
335     std::string mode(fruDevice16BitDetectMode);
336 
337     if (mode == "MODE_2")
338     {
339         return isDevice16BitMode2(file, address);
340     }
341 
342     return isDevice16BitMode1(file);
343 }
344 
345 // TODO: This code is very similar to the non-eeprom version and can be merged
346 // with some tweaks.
processEeprom(int bus,int address)347 static std::vector<uint8_t> processEeprom(int bus, int address)
348 {
349     auto path = getEepromPath(bus, address);
350 
351     int file = open(path.c_str(), O_RDONLY);
352     if (file < 0)
353     {
354         lg2::error("Unable to open eeprom file: {PATH}", "PATH", path);
355         return {};
356     }
357 
358     std::string errorMessage = "eeprom at " + std::to_string(bus) +
359                                " address " + std::to_string(address);
360     auto readFunc = [file](off_t offset, size_t length, uint8_t* outbuf) {
361         return readFromEeprom(file, offset, length, outbuf);
362     };
363     FRUReader reader(std::move(readFunc));
364     std::pair<std::vector<uint8_t>, bool> pair =
365         readFRUContents(reader, errorMessage);
366 
367     close(file);
368     return pair.first;
369 }
370 
findI2CEeproms(int i2cBus,const std::shared_ptr<DeviceMap> & devices)371 std::set<size_t> findI2CEeproms(int i2cBus,
372                                 const std::shared_ptr<DeviceMap>& devices)
373 {
374     std::set<size_t> foundList;
375 
376     std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
377 
378     // For each file listed under the i2c device
379     // NOTE: This should be faster than just checking for each possible address
380     // path.
381     auto ec = std::error_code();
382     for (const auto& p : fs::directory_iterator(path, ec))
383     {
384         if (ec)
385         {
386             lg2::error("directory_iterator err {ERR}", "ERR", ec.message());
387             break;
388         }
389         const std::string node = p.path().string();
390         std::smatch m;
391         bool found =
392             std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
393 
394         if (!found)
395         {
396             continue;
397         }
398         if (m.size() != 2)
399         {
400             lg2::error("regex didn't capture");
401             continue;
402         }
403 
404         std::ssub_match subMatch = m[1];
405         std::string addressString = subMatch.str();
406         std::string_view addressStringView(addressString);
407 
408         size_t address = 0;
409         std::from_chars(addressStringView.begin(), addressStringView.end(),
410                         address, 16);
411 
412         const std::string eeprom = node + "/eeprom";
413 
414         try
415         {
416             if (!fs::exists(eeprom))
417             {
418                 continue;
419             }
420         }
421         catch (...)
422         {
423             continue;
424         }
425 
426         // There is an eeprom file at this address, it may have invalid
427         // contents, but we found it.
428         foundList.insert(address);
429 
430         std::vector<uint8_t> device = processEeprom(i2cBus, address);
431         if (!device.empty())
432         {
433             devices->emplace(address, device);
434         }
435     }
436 
437     return foundList;
438 }
439 
getBusFRUs(int file,int first,int last,int bus,std::shared_ptr<DeviceMap> devices,const bool & powerIsOn,const std::set<size_t> & addressBlocklist,sdbusplus::asio::object_server & objServer)440 int getBusFRUs(int file, int first, int last, int bus,
441                std::shared_ptr<DeviceMap> devices, const bool& powerIsOn,
442                const std::set<size_t>& addressBlocklist,
443                sdbusplus::asio::object_server& objServer)
444 {
445     std::future<int> future = std::async(std::launch::async, [&]() {
446         // NOTE: When reading the devices raw on the bus, it can interfere with
447         // the driver's ability to operate, therefore read eeproms first before
448         // scanning for devices without drivers. Several experiments were run
449         // and it was determined that if there were any devices on the bus
450         // before the eeprom was hit and read, the eeprom driver wouldn't open
451         // while the bus device was open. An experiment was not performed to see
452         // if this issue was resolved if the i2c bus device was closed, but
453         // hexdumps of the eeprom later were successful.
454 
455         // Scan for i2c eeproms loaded on this bus.
456         std::set<size_t> skipList = findI2CEeproms(bus, devices);
457         std::flat_set<size_t>& failedItems = failedAddresses[bus];
458         std::flat_set<size_t>& foundItems = fruAddresses[bus];
459         foundItems.clear();
460 
461         skipList.insert_range(addressBlocklist);
462 
463         auto busFind = busBlocklist.find(bus);
464         if (busFind != busBlocklist.end())
465         {
466             if (busFind->second != std::nullopt)
467             {
468                 for (const auto& address : *(busFind->second))
469                 {
470                     skipList.insert(address);
471                 }
472             }
473         }
474 
475         std::flat_set<size_t>* rootFailures = nullptr;
476         int rootBus = getRootBus(bus);
477 
478         if (rootBus >= 0)
479         {
480             auto rootBusFind = busBlocklist.find(rootBus);
481             if (rootBusFind != busBlocklist.end())
482             {
483                 if (rootBusFind->second != std::nullopt)
484                 {
485                     for (const auto& rootAddress : *(rootBusFind->second))
486                     {
487                         skipList.insert(rootAddress);
488                     }
489                 }
490             }
491             rootFailures = &(failedAddresses[rootBus]);
492             foundItems = fruAddresses[rootBus];
493         }
494 
495         constexpr int startSkipTargetAddr = 0;
496         constexpr int endSkipTargetAddr = 12;
497 
498         for (int ii = first; ii <= last; ii++)
499         {
500             if (foundItems.contains(ii))
501             {
502                 continue;
503             }
504             if (skipList.contains(ii))
505             {
506                 continue;
507             }
508             // skipping since no device is present in this range
509             if (ii >= startSkipTargetAddr && ii <= endSkipTargetAddr)
510             {
511                 continue;
512             }
513             // Set target address
514             if (ioctl(file, I2C_SLAVE, ii) < 0)
515             {
516                 lg2::error("device at bus {BUS} address {ADDR} busy", "BUS",
517                            bus, "ADDR", ii);
518                 continue;
519             }
520             // probe
521             if (i2c_smbus_read_byte(file) < 0)
522             {
523                 continue;
524             }
525 
526             lg2::debug("something at bus {BUS}, addr {ADDR}", "BUS", bus,
527                        "ADDR", ii);
528 
529             makeProbeInterface(bus, ii, objServer);
530 
531             if (failedItems.contains(ii))
532             {
533                 // if we failed to read it once, unlikely we can read it later
534                 continue;
535             }
536 
537             if (rootFailures != nullptr)
538             {
539                 if (rootFailures->contains(ii))
540                 {
541                     continue;
542                 }
543             }
544 
545             /* Check for Device type if it is 8 bit or 16 bit */
546             std::optional<bool> is16Bit = isDevice16Bit(file, ii);
547             if (!is16Bit.has_value())
548             {
549                 lg2::error("failed to read bus {BUS} address {ADDR}", "BUS",
550                            bus, "ADDR", ii);
551                 if (powerIsOn)
552                 {
553                     failedItems.insert(ii);
554                 }
555                 continue;
556             }
557             bool is16BitBool{*is16Bit};
558 
559             auto readFunc = [is16BitBool, file,
560                              ii](off_t offset, size_t length, uint8_t* outbuf) {
561                 return readData(is16BitBool, false, file, ii, offset, length,
562                                 outbuf);
563             };
564             FRUReader reader(std::move(readFunc));
565             std::string errorMessage =
566                 "bus " + std::to_string(bus) + " address " + std::to_string(ii);
567             std::pair<std::vector<uint8_t>, bool> pair =
568                 readFRUContents(reader, errorMessage);
569             const bool foundHeader = pair.second;
570 
571             if (!foundHeader && !is16BitBool)
572             {
573                 // certain FRU eeproms require bytewise reading.
574                 // otherwise garbage is read. e.g. SuperMicro PWS 920P-SQ
575 
576                 auto readFunc =
577                     [is16BitBool, file,
578                      ii](off_t offset, size_t length, uint8_t* outbuf) {
579                         return readData(is16BitBool, true, file, ii, offset,
580                                         length, outbuf);
581                     };
582                 FRUReader readerBytewise(std::move(readFunc));
583                 pair = readFRUContents(readerBytewise, errorMessage);
584             }
585 
586             if (pair.first.empty())
587             {
588                 continue;
589             }
590 
591             devices->emplace(ii, pair.first);
592             fruAddresses[bus].insert(ii);
593         }
594         return 1;
595     });
596     std::future_status status =
597         future.wait_for(std::chrono::seconds(busTimeoutSeconds));
598     if (status == std::future_status::timeout)
599     {
600         lg2::error("Error reading bus {BUS}", "BUS", bus);
601         if (powerIsOn)
602         {
603             busBlocklist[bus] = std::nullopt;
604         }
605         close(file);
606         return -1;
607     }
608 
609     close(file);
610     return future.get();
611 }
612 
613 struct AddressBlocklistResult
614 {
615     int rc;
616     std::set<size_t> list;
617 };
618 
loadAddressBlocklist(const nlohmann::json & data)619 AddressBlocklistResult loadAddressBlocklist(const nlohmann::json& data)
620 {
621     auto addrIt = data.find("addresses");
622     if (addrIt == data.end())
623     {
624         return {0, std::set<size_t>()};
625     }
626 
627     const auto* const addr =
628         data["addresses"].get_ptr<const nlohmann::json::array_t*>();
629 
630     if (addr == nullptr)
631     {
632         lg2::error("addresses must be an array");
633         return {EINVAL, std::set<size_t>()};
634     }
635 
636     std::set<size_t> addressBlocklist = {};
637 
638     for (const auto& address : *addr)
639     {
640         const auto* addrS = address.get_ptr<const std::string*>();
641         if (addrS == nullptr)
642         {
643             lg2::error("address must be a string\n");
644             return {EINVAL, std::set<size_t>()};
645         }
646 
647         if (!(addrS->starts_with("0x") || addrS->starts_with("0X")))
648         {
649             lg2::error("address must start with 0x or 0X\n");
650             return {EINVAL, std::set<size_t>()};
651         }
652 
653         // The alternative offered here relies on undefined behavior
654         // of dereferencing iterators given by .end()
655         // this pointer access is checked above by the calls to starts_with
656         // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
657         size_t addressInt = 0;
658         auto [ptr, ec] = std::from_chars(
659             addrS->data() + 2, addrS->data() + addrS->length(), addressInt, 16);
660 
661         const auto erc = std::make_error_condition(ec);
662         if (ptr != (addrS->data() + addrS->length()) || erc)
663         {
664             lg2::error("Invalid address type: {ADDR} {MSG}\n", "ADDR", *addrS,
665                        "MSG", erc.message());
666             return {EINVAL, std::set<size_t>()};
667         }
668         // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
669         if (addressInt > 0x77)
670         {
671             lg2::error("Invalid address {ADDR}\n", "ADDR", *addrS);
672             return {EINVAL, std::set<size_t>()};
673         }
674 
675         addressBlocklist.insert(addressInt);
676     }
677 
678     return {0, addressBlocklist};
679 }
680 
681 // once the bus blocklist is made non global,
682 // return it here too.
loadBlocklist(const char * path)683 std::set<size_t> loadBlocklist(const char* path)
684 {
685     std::ifstream blocklistStream(path);
686     if (!blocklistStream.good())
687     {
688         // File is optional.
689         lg2::error("Cannot open blocklist file.\n");
690         return {};
691     }
692 
693     nlohmann::json data =
694         nlohmann::json::parse(blocklistStream, nullptr, false);
695     if (data.is_discarded())
696     {
697         lg2::error(
698             "Illegal blocklist file detected, cannot validate JSON, exiting");
699         std::exit(EXIT_FAILURE);
700     }
701 
702     // It's expected to have at least one field, "buses" that is an array of the
703     // buses by integer. Allow for future options to exclude further aspects,
704     // such as specific addresses or ranges.
705     if (data.type() != nlohmann::json::value_t::object)
706     {
707         lg2::error("Illegal blocklist, expected to read dictionary");
708         std::exit(EXIT_FAILURE);
709     }
710 
711     // If buses field is missing, that's fine.
712     if (data.count("buses") == 1)
713     {
714         // Parse the buses array after a little validation.
715         auto buses = data.at("buses");
716         if (buses.type() != nlohmann::json::value_t::array)
717         {
718             // Buses field present but invalid, therefore this is an error.
719             lg2::error("Invalid contents for blocklist buses field");
720             std::exit(EXIT_FAILURE);
721         }
722 
723         // Catch exception here for type mis-match.
724         try
725         {
726             for (const auto& busIterator : buses)
727             {
728                 // If bus and addresses field are missing, that's fine.
729                 if (busIterator.contains("bus") &&
730                     busIterator.contains("addresses"))
731                 {
732                     auto busData = busIterator.at("bus");
733                     auto bus = busData.get<size_t>();
734 
735                     auto addressData = busIterator.at("addresses");
736                     auto addresses =
737                         addressData.get<std::set<std::string_view>>();
738 
739                     auto& block = busBlocklist[bus].emplace();
740                     for (const auto& address : addresses)
741                     {
742                         size_t addressInt = 0;
743                         bool fullMatch = false;
744                         fromCharsWrapper(address.substr(2), addressInt,
745                                          fullMatch, 16);
746                         block.insert(addressInt);
747                     }
748                 }
749                 else
750                 {
751                     busBlocklist[busIterator.get<size_t>()] = std::nullopt;
752                 }
753             }
754         }
755         catch (const nlohmann::detail::type_error& e)
756         {
757             // Type mis-match is a critical error.
758             lg2::error("Invalid bus type: {ERR}", "ERR", e.what());
759             std::exit(EXIT_FAILURE);
760         }
761     }
762 
763     const auto [rc, addressBlocklist] = loadAddressBlocklist(data);
764     if (rc != 0)
765     {
766         std::exit(EXIT_FAILURE);
767     }
768 
769     return addressBlocklist;
770 }
771 
findI2CDevices(const std::vector<fs::path> & i2cBuses,BusMap & busmap,const bool & powerIsOn,const std::set<size_t> & addressBlocklist,sdbusplus::asio::object_server & objServer)772 static void findI2CDevices(const std::vector<fs::path>& i2cBuses,
773                            BusMap& busmap, const bool& powerIsOn,
774                            const std::set<size_t>& addressBlocklist,
775                            sdbusplus::asio::object_server& objServer)
776 {
777     for (const auto& i2cBus : i2cBuses)
778     {
779         int bus = busStrToInt(i2cBus.string());
780 
781         if (bus < 0)
782         {
783             lg2::error("Cannot translate {BUS} to int", "BUS", i2cBus);
784             continue;
785         }
786         auto busFind = busBlocklist.find(bus);
787         if (busFind != busBlocklist.end())
788         {
789             if (busFind->second == std::nullopt)
790             {
791                 continue; // Skip blocked busses.
792             }
793         }
794         int rootBus = getRootBus(bus);
795         auto rootBusFind = busBlocklist.find(rootBus);
796         if (rootBusFind != busBlocklist.end())
797         {
798             if (rootBusFind->second == std::nullopt)
799             {
800                 continue;
801             }
802         }
803 
804         auto file = open(i2cBus.c_str(), O_RDWR);
805         if (file < 0)
806         {
807             lg2::error("unable to open i2c device {PATH}", "PATH",
808                        i2cBus.string());
809             continue;
810         }
811         unsigned long funcs = 0;
812 
813         if (ioctl(file, I2C_FUNCS, &funcs) < 0)
814         {
815             lg2::error(
816                 "Error: Could not get the adapter functionality matrix bus {BUS}",
817                 "BUS", bus);
818             close(file);
819             continue;
820         }
821         if (((funcs & I2C_FUNC_SMBUS_READ_BYTE) == 0U) ||
822             ((funcs & I2C_FUNC_SMBUS_READ_I2C_BLOCK) == 0U))
823         {
824             lg2::error("Error: Can't use SMBus Receive Byte command bus {BUS}",
825                        "BUS", bus);
826             close(file);
827             continue;
828         }
829         auto& device = busmap[bus];
830         device = std::make_shared<DeviceMap>();
831 
832         //  i2cdetect by default uses the range 0x03 to 0x77, as
833         //  this is  what we have tested with, use this range. Could be
834         //  changed in future.
835         lg2::debug("Scanning bus {BUS}", "BUS", bus);
836 
837         // fd is closed in this function in case the bus locks up
838         getBusFRUs(file, 0x03, 0x77, bus, device, powerIsOn, addressBlocklist,
839                    objServer);
840 
841         lg2::debug("Done scanning bus {BUS}", "BUS", bus);
842     }
843 }
844 
845 // this class allows an async response after all i2c devices are discovered
846 struct FindDevicesWithCallback :
847     std::enable_shared_from_this<FindDevicesWithCallback>
848 {
FindDevicesWithCallbackFindDevicesWithCallback849     FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
850                             BusMap& busmap, const bool& powerIsOn,
851                             sdbusplus::asio::object_server& objServer,
852                             const std::set<size_t>& addressBlocklist,
853                             std::function<void()>&& callback) :
854         _i2cBuses(i2cBuses), _busMap(busmap), _powerIsOn(powerIsOn),
855         _objServer(objServer), _callback(std::move(callback)),
856         _addressBlocklist{addressBlocklist}
857     {}
~FindDevicesWithCallbackFindDevicesWithCallback858     ~FindDevicesWithCallback()
859     {
860         _callback();
861     }
runFindDevicesWithCallback862     void run()
863     {
864         findI2CDevices(_i2cBuses, _busMap, _powerIsOn, _addressBlocklist,
865                        _objServer);
866     }
867 
868     const std::vector<fs::path>& _i2cBuses;
869     BusMap& _busMap;
870     const bool& _powerIsOn;
871     sdbusplus::asio::object_server& _objServer;
872     std::function<void()> _callback;
873     std::set<size_t> _addressBlocklist;
874 };
875 
addFruObjectToDbus(std::vector<uint8_t> & device,std::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,uint32_t bus,uint32_t address,size_t & unknownBusObjectCount,const bool & powerIsOn,const std::set<size_t> & addressBlocklist,sdbusplus::asio::object_server & objServer)876 void addFruObjectToDbus(
877     std::vector<uint8_t>& device,
878     std::flat_map<std::pair<size_t, size_t>,
879                   std::shared_ptr<sdbusplus::asio::dbus_interface>>&
880         dbusInterfaceMap,
881     uint32_t bus, uint32_t address, size_t& unknownBusObjectCount,
882     const bool& powerIsOn, const std::set<size_t>& addressBlocklist,
883     sdbusplus::asio::object_server& objServer)
884 {
885     std::flat_map<std::string, std::string, std::less<>> formattedFRU;
886 
887     std::optional<std::string> optionalProductName = getProductName(
888         device, formattedFRU, bus, address, unknownBusObjectCount);
889     if (!optionalProductName)
890     {
891         lg2::error("getProductName failed. product name is empty.");
892         return;
893     }
894 
895     std::string productName =
896         "/xyz/openbmc_project/FruDevice/" + optionalProductName.value();
897 
898     std::optional<int> index = findIndexForFRU(dbusInterfaceMap, productName);
899     if (index.has_value())
900     {
901         productName += "_";
902         productName += std::to_string(++(*index));
903     }
904 
905     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
906         objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
907     dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
908 
909     if (ENABLE_FRU_UPDATE_PROPERTY)
910     {
911         iface->register_method(
912             "UpdateFruField",
913             [bus, address, &dbusInterfaceMap, &unknownBusObjectCount,
914              &powerIsOn, &objServer, addressBlocklist](
915                 const std::string& fieldName, const std::string& fieldValue) {
916                 // Update the property
917                 if (!updateFruProperty(fieldValue, bus, address, fieldName,
918                                        dbusInterfaceMap, unknownBusObjectCount,
919                                        powerIsOn, addressBlocklist, objServer))
920                 {
921                     lg2::debug(
922                         "Failed to Add Field: Name = {NAME}, Value = {VALUE}",
923                         "NAME", fieldName, "VALUE", fieldValue);
924                     return false;
925                 }
926 
927                 return true;
928             });
929     }
930 
931     for (auto property : formattedFRU)
932     {
933         std::regex_replace(property.second.begin(), property.second.begin(),
934                            property.second.end(), nonAsciiRegex, "_");
935         if (property.second.empty())
936         {
937             continue;
938         }
939         std::string key =
940             std::regex_replace(property.first, nonAsciiRegex, "_");
941 
942         // Allow FRU field update if ENABLE_FRU_UPDATE_PROPERTY is set.
943         if (isFieldEditable(property.first))
944         {
945             std::string propertyName = property.first;
946             iface->register_property(
947                 key, property.second + '\0',
948                 [bus, address, propertyName, &dbusInterfaceMap,
949                  &unknownBusObjectCount, &powerIsOn, &objServer,
950                  &addressBlocklist](const std::string& req, std::string& resp) {
951                     if (strcmp(req.c_str(), resp.c_str()) != 0)
952                     {
953                         // call the method which will update
954                         if (updateFruProperty(req, bus, address, propertyName,
955                                               dbusInterfaceMap,
956                                               unknownBusObjectCount, powerIsOn,
957                                               addressBlocklist, objServer))
958                         {
959                             resp = req;
960                         }
961                         else
962                         {
963                             throw std::invalid_argument(
964                                 "FRU property update failed.");
965                         }
966                     }
967                     return 1;
968                 });
969         }
970         else if (!iface->register_property(key, property.second + '\0'))
971         {
972             lg2::error("illegal key: {KEY}", "KEY", key);
973         }
974         lg2::debug("parsed FRU property: {FIRST}: {SECOND}", "FIRST",
975                    property.first, "SECOND", property.second);
976     }
977 
978     // baseboard will be 0, 0
979     iface->register_property("BUS", bus);
980     iface->register_property("ADDRESS", address);
981 
982     iface->initialize();
983 }
984 
readBaseboardFRU(std::vector<uint8_t> & baseboardFRU)985 static bool readBaseboardFRU(std::vector<uint8_t>& baseboardFRU)
986 {
987     // try to read baseboard fru from file
988     std::ifstream baseboardFRUFile(baseboardFruLocation, std::ios::binary);
989     if (baseboardFRUFile.good())
990     {
991         baseboardFRUFile.seekg(0, std::ios_base::end);
992         size_t fileSize = static_cast<size_t>(baseboardFRUFile.tellg());
993         baseboardFRU.resize(fileSize);
994         baseboardFRUFile.seekg(0, std::ios_base::beg);
995         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
996         char* charOffset = reinterpret_cast<char*>(baseboardFRU.data());
997         baseboardFRUFile.read(charOffset, fileSize);
998     }
999     else
1000     {
1001         return false;
1002     }
1003     return true;
1004 }
1005 
writeFruByteData(bool is16Bit,int file,uint8_t address,uint16_t index,uint8_t byteData)1006 bool writeFruByteData(bool is16Bit, int file, uint8_t address, uint16_t index,
1007                       uint8_t byteData)
1008 {
1009     if (is16Bit)
1010     {
1011         // if uses 16-bit addressing we need to use ioctl as smbus commands
1012         // are not supported
1013         struct i2c_rdwr_ioctl_data messagesData = {};
1014         std::array<struct i2c_msg, 1> messages{};
1015         std::array<uint8_t, 3> writeBuffer{};
1016 
1017         uint8_t indexH = index >> 8;
1018         uint8_t indexL = index & 0xFF;
1019 
1020         writeBuffer[0] = indexH;
1021         writeBuffer[1] = indexL;
1022         writeBuffer[2] = byteData;
1023 
1024         messages[0].flags = 0;
1025         messages[0].len = writeBuffer.size();
1026         messages[0].buf = writeBuffer.data();
1027         messages[0].addr = address;
1028 
1029         messagesData.msgs = messages.data();
1030         messagesData.nmsgs = 1;
1031 
1032         return ioctl(file, I2C_RDWR, &messagesData) >= 0;
1033     }
1034     // if 8-bit addressing
1035     return i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1036                                      byteData) == 0;
1037 }
1038 
writeFRU(uint8_t bus,uint8_t address,const std::vector<uint8_t> & fru)1039 bool writeFRU(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
1040 {
1041     std::flat_map<std::string, std::string, std::less<>> tmp;
1042     if (fru.size() > maxFruSize)
1043     {
1044         lg2::error("Invalid fru.size() during writeFRU");
1045         return false;
1046     }
1047     // verify legal fru by running it through fru parsing logic
1048     if (formatIPMIFRU(fru, tmp) != resCodes::resOK)
1049     {
1050         lg2::error("Invalid fru format during writeFRU");
1051         return false;
1052     }
1053     // baseboard fru
1054     if (bus == 0 && address == 0)
1055     {
1056         std::ofstream file(baseboardFruLocation, std::ios_base::binary);
1057         if (!file.good())
1058         {
1059             lg2::error("Error opening file {PATH}", "PATH",
1060                        baseboardFruLocation);
1061             throw DBusInternalError();
1062             return false;
1063         }
1064         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1065         const char* charOffset = reinterpret_cast<const char*>(fru.data());
1066         file.write(charOffset, fru.size());
1067         return file.good();
1068     }
1069 
1070     if (hasEepromFile(bus, address))
1071     {
1072         auto path = getEepromPath(bus, address);
1073         off_t offset = 0;
1074 
1075         int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1076         if (eeprom < 0)
1077         {
1078             lg2::error("unable to open i2c device {PATH}", "PATH", path);
1079             throw DBusInternalError();
1080             return false;
1081         }
1082 
1083         std::string errorMessage = "eeprom at " + std::to_string(bus) +
1084                                    " address " + std::to_string(address);
1085         auto readFunc = [eeprom](off_t offset, size_t length, uint8_t* outbuf) {
1086             return readFromEeprom(eeprom, offset, length, outbuf);
1087         };
1088         FRUReader reader(std::move(readFunc));
1089 
1090         auto sections = findFRUHeader(reader, errorMessage, 0);
1091         if (!sections)
1092         {
1093             offset = 0;
1094         }
1095         else
1096         {
1097             offset = sections->IpmiFruOffset;
1098         }
1099 
1100         if (lseek(eeprom, offset, SEEK_SET) < 0)
1101         {
1102             lg2::error("Unable to seek to offset {OFFSET} in device: {PATH}",
1103                        "OFFSET", offset, "PATH", path);
1104             close(eeprom);
1105             throw DBusInternalError();
1106         }
1107 
1108         ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1109         if (writtenBytes < 0)
1110         {
1111             lg2::error("unable to write to i2c device {PATH}", "PATH", path);
1112             close(eeprom);
1113             throw DBusInternalError();
1114             return false;
1115         }
1116 
1117         close(eeprom);
1118         return true;
1119     }
1120 
1121     std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1122 
1123     int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1124     if (file < 0)
1125     {
1126         lg2::error("unable to open i2c device {PATH}", "PATH", i2cBus);
1127         throw DBusInternalError();
1128         return false;
1129     }
1130     if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1131     {
1132         lg2::error("unable to set device address");
1133         close(file);
1134         throw DBusInternalError();
1135         return false;
1136     }
1137 
1138     std::optional<bool> is16Bit = isDevice16Bit(file, address);
1139     if (!is16Bit.has_value())
1140     {
1141         lg2::error("failed to detect if device is 8 or 16 bits");
1142         return false;
1143     }
1144 
1145     constexpr const size_t retryMax = 2;
1146     uint16_t index = 0;
1147     size_t retries = retryMax;
1148     while (index < fru.size())
1149     {
1150         if (((index != 0U) && ((index % (maxEepromPageIndex + 1)) == 0)) &&
1151             (retries == retryMax))
1152         {
1153             // The 4K EEPROM only uses the A2 and A1 device address bits
1154             // with the third bit being a memory page address bit.
1155             if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1156             {
1157                 lg2::error("unable to set device address");
1158                 close(file);
1159                 throw DBusInternalError();
1160                 return false;
1161             }
1162         }
1163 
1164         if (writeFruByteData(*is16Bit, file, address, index, fru[index]))
1165         {
1166             retries = retryMax;
1167             index++;
1168         }
1169         else
1170         {
1171             if ((retries--) == 0U)
1172             {
1173                 lg2::error("error writing fru: {ERR}", "ERR", strerror(errno));
1174                 close(file);
1175                 throw DBusInternalError();
1176                 return false;
1177             }
1178         }
1179 
1180         // most eeproms require 5-10ms between writes
1181         std::this_thread::sleep_for(std::chrono::milliseconds(10));
1182     }
1183     close(file);
1184     return true;
1185 }
1186 
rescanOneBus(BusMap & busmap,uint16_t busNum,std::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,bool dbusCall,size_t & unknownBusObjectCount,const bool & powerIsOn,const std::set<size_t> & addressBlocklist,sdbusplus::asio::object_server & objServer)1187 void rescanOneBus(
1188     BusMap& busmap, uint16_t busNum,
1189     std::flat_map<std::pair<size_t, size_t>,
1190                   std::shared_ptr<sdbusplus::asio::dbus_interface>>&
1191         dbusInterfaceMap,
1192     bool dbusCall, size_t& unknownBusObjectCount, const bool& powerIsOn,
1193     const std::set<size_t>& addressBlocklist,
1194     sdbusplus::asio::object_server& objServer)
1195 {
1196     for (auto device = foundDevices.begin(); device != foundDevices.end();)
1197     {
1198         if (device->first.first == static_cast<size_t>(busNum))
1199         {
1200             objServer.remove_interface(device->second);
1201             device = foundDevices.erase(device);
1202         }
1203         else
1204         {
1205             device++;
1206         }
1207     }
1208 
1209     fs::path busPath = fs::path("/dev/i2c-" + std::to_string(busNum));
1210     if (!fs::exists(busPath))
1211     {
1212         if (dbusCall)
1213         {
1214             lg2::error("Unable to access i2c bus {BUS}", "BUS",
1215                        static_cast<int>(busNum));
1216             throw std::invalid_argument("Invalid Bus.");
1217         }
1218         return;
1219     }
1220 
1221     std::vector<fs::path> i2cBuses;
1222     i2cBuses.emplace_back(busPath);
1223 
1224     auto scan = std::make_shared<FindDevicesWithCallback>(
1225         i2cBuses, busmap, powerIsOn, objServer, addressBlocklist,
1226         [busNum, &busmap, &dbusInterfaceMap, &unknownBusObjectCount, &powerIsOn,
1227          &objServer, &addressBlocklist]() {
1228             for (auto busIface = dbusInterfaceMap.begin();
1229                  busIface != dbusInterfaceMap.end();)
1230             {
1231                 if (busIface->first.first == static_cast<size_t>(busNum))
1232                 {
1233                     objServer.remove_interface(busIface->second);
1234                     busIface = dbusInterfaceMap.erase(busIface);
1235                 }
1236                 else
1237                 {
1238                     busIface++;
1239                 }
1240             }
1241             auto found = busmap.find(busNum);
1242             if (found == busmap.end() || found->second == nullptr)
1243             {
1244                 return;
1245             }
1246             for (auto device : *(found->second))
1247             {
1248                 addFruObjectToDbus(device.second, dbusInterfaceMap,
1249                                    static_cast<uint32_t>(busNum), device.first,
1250                                    unknownBusObjectCount, powerIsOn,
1251                                    addressBlocklist, objServer);
1252             }
1253         });
1254     scan->run();
1255 }
1256 
rescanBusses(BusMap & busmap,std::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,size_t & unknownBusObjectCount,const bool & powerIsOn,const std::set<size_t> & addressBlocklist,sdbusplus::asio::object_server & objServer)1257 void rescanBusses(
1258     BusMap& busmap,
1259     std::flat_map<std::pair<size_t, size_t>,
1260                   std::shared_ptr<sdbusplus::asio::dbus_interface>>&
1261         dbusInterfaceMap,
1262     size_t& unknownBusObjectCount, const bool& powerIsOn,
1263     const std::set<size_t>& addressBlocklist,
1264     sdbusplus::asio::object_server& objServer)
1265 {
1266     static boost::asio::steady_timer timer(io);
1267     timer.expires_after(std::chrono::seconds(1));
1268 
1269     // setup an async wait in case we get flooded with requests
1270     timer.async_wait([&](const boost::system::error_code& ec) {
1271         if (ec == boost::asio::error::operation_aborted)
1272         {
1273             return;
1274         }
1275 
1276         if (ec)
1277         {
1278             lg2::error("Error in timer: {ERR}", "ERR", ec.message());
1279             return;
1280         }
1281 
1282         auto devDir = fs::path("/dev/");
1283         std::vector<fs::path> i2cBuses;
1284 
1285         std::flat_map<size_t, fs::path> busPaths;
1286         if (!getI2cDevicePaths(devDir, busPaths))
1287         {
1288             lg2::error("unable to find i2c devices");
1289             return;
1290         }
1291 
1292         for (const auto& busPath : busPaths)
1293         {
1294             i2cBuses.emplace_back(busPath.second);
1295         }
1296 
1297         busmap.clear();
1298         for (auto [pair, interface] : foundDevices)
1299         {
1300             objServer.remove_interface(interface);
1301         }
1302         foundDevices.clear();
1303 
1304         auto scan = std::make_shared<FindDevicesWithCallback>(
1305             i2cBuses, busmap, powerIsOn, objServer, addressBlocklist, [&]() {
1306                 for (auto busIface : dbusInterfaceMap)
1307                 {
1308                     objServer.remove_interface(busIface.second);
1309                 }
1310 
1311                 dbusInterfaceMap.clear();
1312                 unknownBusObjectCount = 0;
1313 
1314                 // todo, get this from a more sensable place
1315                 std::vector<uint8_t> baseboardFRU;
1316                 if (readBaseboardFRU(baseboardFRU))
1317                 {
1318                     // If no device on i2c bus 0, the insertion will happen.
1319                     auto bus0 =
1320                         busmap.try_emplace(0, std::make_shared<DeviceMap>());
1321                     bus0.first->second->emplace(0, baseboardFRU);
1322                 }
1323                 for (auto devicemap : busmap)
1324                 {
1325                     for (auto device : *devicemap.second)
1326                     {
1327                         addFruObjectToDbus(device.second, dbusInterfaceMap,
1328                                            devicemap.first, device.first,
1329                                            unknownBusObjectCount, powerIsOn,
1330                                            addressBlocklist, objServer);
1331                     }
1332                 }
1333             });
1334         scan->run();
1335     });
1336 }
1337 
updateFruProperty(const std::string & propertyValue,uint32_t bus,uint32_t address,const std::string & propertyName,std::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,size_t & unknownBusObjectCount,const bool & powerIsOn,const std::set<size_t> & addressBlocklist,sdbusplus::asio::object_server & objServer)1338 bool updateFruProperty(
1339     const std::string& propertyValue, uint32_t bus, uint32_t address,
1340     const std::string& propertyName,
1341     std::flat_map<std::pair<size_t, size_t>,
1342                   std::shared_ptr<sdbusplus::asio::dbus_interface>>&
1343         dbusInterfaceMap,
1344     size_t& unknownBusObjectCount, const bool& powerIsOn,
1345     const std::set<size_t>& addressBlocklist,
1346     sdbusplus::asio::object_server& objServer)
1347 {
1348     lg2::debug(
1349         "updateFruProperty called: FieldName = {NAME}, FieldValue = {VALUE}",
1350         "NAME", propertyName, "VALUE", propertyValue);
1351 
1352     std::vector<uint8_t> fruData;
1353     if (!getFruData(fruData, bus, address))
1354     {
1355         lg2::error("Failure getting FRU Data from bus {BUS}, address {ADDRESS}",
1356                    "BUS", bus, "ADDRESS", address);
1357         return false;
1358     }
1359 
1360     bool success = updateAddProperty(propertyValue, propertyName, fruData);
1361     if (!success)
1362     {
1363         lg2::error(
1364             "Failed to update the property on bus {BUS}, address {ADDRESS}",
1365             "BUS", bus, "ADDRESS", address);
1366         return false;
1367     }
1368 
1369     if (!writeFRU(static_cast<uint8_t>(bus), static_cast<uint8_t>(address),
1370                   fruData))
1371     {
1372         lg2::error("Failed to write the FRU");
1373         return false;
1374     }
1375 
1376     rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1377                  addressBlocklist, objServer);
1378     return true;
1379 }
1380 
main()1381 int main()
1382 {
1383     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1384     sdbusplus::asio::object_server objServer(systemBus);
1385 
1386     static size_t unknownBusObjectCount = 0;
1387     static bool powerIsOn = false;
1388     auto devDir = fs::path("/dev/");
1389     auto matchString = std::string(R"(i2c-\d+$)");
1390     std::vector<fs::path> i2cBuses;
1391 
1392     if (!findFiles(devDir, matchString, i2cBuses))
1393     {
1394         lg2::error("unable to find i2c devices");
1395         return 1;
1396     }
1397 
1398     // check for and load blocklist with initial buses.
1399     // once busBlocklist is moved to be non global,
1400     // add it here
1401     auto addressBlocklist = loadBlocklist(blocklistPath);
1402 
1403     systemBus->request_name("xyz.openbmc_project.FruDevice");
1404 
1405     // this is a map with keys of pair(bus number, address) and values of
1406     // the object on dbus
1407     std::flat_map<std::pair<size_t, size_t>,
1408                   std::shared_ptr<sdbusplus::asio::dbus_interface>>
1409         dbusInterfaceMap;
1410 
1411     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1412         objServer.add_interface("/xyz/openbmc_project/FruDevice",
1413                                 "xyz.openbmc_project.FruDeviceManager");
1414 
1415     iface->register_method("ReScan", [&]() {
1416         rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1417                      addressBlocklist, objServer);
1418     });
1419 
1420     iface->register_method("ReScanBus", [&](uint16_t bus) {
1421         rescanOneBus(busMap, bus, dbusInterfaceMap, true, unknownBusObjectCount,
1422                      powerIsOn, addressBlocklist, objServer);
1423     });
1424 
1425     iface->register_method("GetRawFru", getFRUInfo);
1426 
1427     iface->register_method(
1428         "WriteFru", [&](const uint16_t bus, const uint8_t address,
1429                         const std::vector<uint8_t>& data) {
1430             if (!writeFRU(bus, address, data))
1431             {
1432                 throw std::invalid_argument("Invalid Arguments.");
1433                 return;
1434             }
1435             // schedule rescan on success
1436             rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount,
1437                          powerIsOn, addressBlocklist, objServer);
1438         });
1439     iface->initialize();
1440 
1441     std::function<void(sdbusplus::message_t & message)> eventHandler =
1442         [&](sdbusplus::message_t& message) {
1443             std::string objectName;
1444             std::flat_map<std::string, std::variant<std::string, bool, int64_t,
1445                                                     uint64_t, double>>
1446                 values;
1447             message.read(objectName, values);
1448             auto findState = values.find("CurrentHostState");
1449             if (findState != values.end())
1450             {
1451                 if (std::get<std::string>(findState->second) ==
1452                     "xyz.openbmc_project.State.Host.HostState.Running")
1453                 {
1454                     powerIsOn = true;
1455                 }
1456             }
1457 
1458             if (powerIsOn)
1459             {
1460                 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount,
1461                              powerIsOn, addressBlocklist, objServer);
1462             }
1463         };
1464 
1465     sdbusplus::bus::match_t powerMatch = sdbusplus::bus::match_t(
1466         static_cast<sdbusplus::bus_t&>(*systemBus),
1467         "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
1468         "openbmc_project/state/"
1469         "host0',arg0='xyz.openbmc_project.State.Host'",
1470         eventHandler);
1471 
1472     int fd = inotify_init();
1473     inotify_add_watch(fd, i2CDevLocation, IN_CREATE | IN_MOVED_TO | IN_DELETE);
1474     std::array<char, 4096> readBuffer{};
1475     // monitor for new i2c devices
1476     boost::asio::posix::stream_descriptor dirWatch(io, fd);
1477     std::function<void(const boost::system::error_code, std::size_t)>
1478         watchI2cBusses = [&](const boost::system::error_code& ec,
1479                              std::size_t bytesTransferred) {
1480             if (ec)
1481             {
1482                 lg2::info("Callback Error {ERR}", "ERR", ec.message());
1483                 return;
1484             }
1485             size_t index = 0;
1486             while ((index + sizeof(inotify_event)) <= bytesTransferred)
1487             {
1488                 const char* p = &readBuffer[index];
1489                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1490                 const auto* iEvent = reinterpret_cast<const inotify_event*>(p);
1491                 switch (iEvent->mask)
1492                 {
1493                     case IN_CREATE:
1494                     case IN_MOVED_TO:
1495                     case IN_DELETE:
1496                     {
1497                         std::string_view name(&iEvent->name[0], iEvent->len);
1498                         if (name.starts_with("i2c"))
1499                         {
1500                             int bus = busStrToInt(name);
1501                             if (bus < 0)
1502                             {
1503                                 lg2::error("Could not parse bus {BUS}", "BUS",
1504                                            name);
1505                                 continue;
1506                             }
1507                             int rootBus = getRootBus(bus);
1508                             if (rootBus >= 0)
1509                             {
1510                                 rescanOneBus(busMap,
1511                                              static_cast<uint16_t>(rootBus),
1512                                              dbusInterfaceMap, false,
1513                                              unknownBusObjectCount, powerIsOn,
1514                                              addressBlocklist, objServer);
1515                             }
1516                             rescanOneBus(busMap, static_cast<uint16_t>(bus),
1517                                          dbusInterfaceMap, false,
1518                                          unknownBusObjectCount, powerIsOn,
1519                                          addressBlocklist, objServer);
1520                         }
1521                     }
1522                     break;
1523                     default:
1524                         break;
1525                 }
1526                 index += sizeof(inotify_event) + iEvent->len;
1527             }
1528 
1529             dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1530                                      watchI2cBusses);
1531         };
1532 
1533     dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
1534     // run the initial scan
1535     rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1536                  addressBlocklist, objServer);
1537 
1538     io.run();
1539     return 0;
1540 }
1541