xref: /openbmc/entity-manager/src/fru_device/fru_device.cpp (revision cefe4bb6b95624a4d1691cfdba075fff4fdb39f8)
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 <boost/container/flat_map.hpp>
14 #include <nlohmann/json.hpp>
15 #include <phosphor-logging/lg2.hpp>
16 #include <sdbusplus/asio/connection.hpp>
17 #include <sdbusplus/asio/object_server.hpp>
18 
19 #include <array>
20 #include <cerrno>
21 #include <charconv>
22 #include <chrono>
23 #include <ctime>
24 #include <filesystem>
25 #include <fstream>
26 #include <functional>
27 #include <future>
28 #include <iomanip>
29 #include <limits>
30 #include <map>
31 #include <optional>
32 #include <regex>
33 #include <set>
34 #include <sstream>
35 #include <string>
36 #include <thread>
37 #include <utility>
38 #include <variant>
39 #include <vector>
40 
41 extern "C"
42 {
43 #include <i2c/smbus.h>
44 #include <linux/i2c-dev.h>
45 }
46 
47 namespace fs = std::filesystem;
48 constexpr size_t maxFruSize = 512;
49 constexpr size_t maxEepromPageIndex = 255;
50 constexpr size_t busTimeoutSeconds = 10;
51 
52 constexpr const char* blocklistPath = PACKAGE_DIR "blacklist.json";
53 
54 const static constexpr char* baseboardFruLocation =
55     "/etc/fru/baseboard.fru.bin";
56 
57 const static constexpr char* i2CDevLocation = "/dev";
58 
59 constexpr const char* fruDevice16BitDetectMode = FRU_DEVICE_16BITDETECTMODE;
60 
61 // TODO Refactor these to not be globals
62 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
63 static boost::container::flat_map<size_t, std::optional<std::set<size_t>>>
64     busBlocklist;
65 struct FindDevicesWithCallback;
66 
67 static boost::container::flat_map<
68     std::pair<size_t, size_t>, std::shared_ptr<sdbusplus::asio::dbus_interface>>
69     foundDevices;
70 
71 static boost::container::flat_map<size_t, std::set<size_t>> failedAddresses;
72 static boost::container::flat_map<size_t, std::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     boost::container::flat_map<
81         std::pair<size_t, size_t>,
82         std::shared_ptr<sdbusplus::asio::dbus_interface>>& 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::set<size_t>& failedItems = failedAddresses[bus];
458         std::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::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             ((I2C_FUNC_SMBUS_READ_I2C_BLOCK) == 0))
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,boost::container::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     boost::container::flat_map<
879         std::pair<size_t, size_t>,
880         std::shared_ptr<sdbusplus::asio::dbus_interface>>& 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     boost::container::flat_map<std::string, std::string> 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 
writeFRU(uint8_t bus,uint8_t address,const std::vector<uint8_t> & fru)1006 bool writeFRU(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
1007 {
1008     boost::container::flat_map<std::string, std::string> tmp;
1009     if (fru.size() > maxFruSize)
1010     {
1011         lg2::error("Invalid fru.size() during writeFRU");
1012         return false;
1013     }
1014     // verify legal fru by running it through fru parsing logic
1015     if (formatIPMIFRU(fru, tmp) != resCodes::resOK)
1016     {
1017         lg2::error("Invalid fru format during writeFRU");
1018         return false;
1019     }
1020     // baseboard fru
1021     if (bus == 0 && address == 0)
1022     {
1023         std::ofstream file(baseboardFruLocation, std::ios_base::binary);
1024         if (!file.good())
1025         {
1026             lg2::error("Error opening file {PATH}", "PATH",
1027                        baseboardFruLocation);
1028             throw DBusInternalError();
1029             return false;
1030         }
1031         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1032         const char* charOffset = reinterpret_cast<const char*>(fru.data());
1033         file.write(charOffset, fru.size());
1034         return file.good();
1035     }
1036 
1037     if (hasEepromFile(bus, address))
1038     {
1039         auto path = getEepromPath(bus, address);
1040         off_t offset = 0;
1041 
1042         int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1043         if (eeprom < 0)
1044         {
1045             lg2::error("unable to open i2c device {PATH}", "PATH", path);
1046             throw DBusInternalError();
1047             return false;
1048         }
1049 
1050         std::string errorMessage = "eeprom at " + std::to_string(bus) +
1051                                    " address " + std::to_string(address);
1052         auto readFunc = [eeprom](off_t offset, size_t length, uint8_t* outbuf) {
1053             return readFromEeprom(eeprom, offset, length, outbuf);
1054         };
1055         FRUReader reader(std::move(readFunc));
1056 
1057         auto sections = findFRUHeader(reader, errorMessage, 0);
1058         if (!sections)
1059         {
1060             offset = 0;
1061         }
1062         else
1063         {
1064             offset = sections->IpmiFruOffset;
1065         }
1066 
1067         if (lseek(eeprom, offset, SEEK_SET) < 0)
1068         {
1069             lg2::error("Unable to seek to offset {OFFSET} in device: {PATH}",
1070                        "OFFSET", offset, "PATH", path);
1071             close(eeprom);
1072             throw DBusInternalError();
1073         }
1074 
1075         ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1076         if (writtenBytes < 0)
1077         {
1078             lg2::error("unable to write to i2c device {PATH}", "PATH", path);
1079             close(eeprom);
1080             throw DBusInternalError();
1081             return false;
1082         }
1083 
1084         close(eeprom);
1085         return true;
1086     }
1087 
1088     std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1089 
1090     int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1091     if (file < 0)
1092     {
1093         lg2::error("unable to open i2c device {PATH}", "PATH", i2cBus);
1094         throw DBusInternalError();
1095         return false;
1096     }
1097     if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1098     {
1099         lg2::error("unable to set device address");
1100         close(file);
1101         throw DBusInternalError();
1102         return false;
1103     }
1104 
1105     constexpr const size_t retryMax = 2;
1106     uint16_t index = 0;
1107     size_t retries = retryMax;
1108     while (index < fru.size())
1109     {
1110         if (((index != 0U) && ((index % (maxEepromPageIndex + 1)) == 0)) &&
1111             (retries == retryMax))
1112         {
1113             // The 4K EEPROM only uses the A2 and A1 device address bits
1114             // with the third bit being a memory page address bit.
1115             if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1116             {
1117                 lg2::error("unable to set device address");
1118                 close(file);
1119                 throw DBusInternalError();
1120                 return false;
1121             }
1122         }
1123 
1124         if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1125                                       fru[index]) < 0)
1126         {
1127             if ((retries--) == 0U)
1128             {
1129                 lg2::error("error writing fru: {ERR}", "ERR", strerror(errno));
1130                 close(file);
1131                 throw DBusInternalError();
1132                 return false;
1133             }
1134         }
1135         else
1136         {
1137             retries = retryMax;
1138             index++;
1139         }
1140         // most eeproms require 5-10ms between writes
1141         std::this_thread::sleep_for(std::chrono::milliseconds(10));
1142     }
1143     close(file);
1144     return true;
1145 }
1146 
rescanOneBus(BusMap & busmap,uint16_t busNum,boost::container::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)1147 void rescanOneBus(
1148     BusMap& busmap, uint16_t busNum,
1149     boost::container::flat_map<
1150         std::pair<size_t, size_t>,
1151         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1152     bool dbusCall, size_t& unknownBusObjectCount, const bool& powerIsOn,
1153     const std::set<size_t>& addressBlocklist,
1154     sdbusplus::asio::object_server& objServer)
1155 {
1156     for (auto device = foundDevices.begin(); device != foundDevices.end();)
1157     {
1158         if (device->first.first == static_cast<size_t>(busNum))
1159         {
1160             objServer.remove_interface(device->second);
1161             device = foundDevices.erase(device);
1162         }
1163         else
1164         {
1165             device++;
1166         }
1167     }
1168 
1169     fs::path busPath = fs::path("/dev/i2c-" + std::to_string(busNum));
1170     if (!fs::exists(busPath))
1171     {
1172         if (dbusCall)
1173         {
1174             lg2::error("Unable to access i2c bus {BUS}", "BUS",
1175                        static_cast<int>(busNum));
1176             throw std::invalid_argument("Invalid Bus.");
1177         }
1178         return;
1179     }
1180 
1181     std::vector<fs::path> i2cBuses;
1182     i2cBuses.emplace_back(busPath);
1183 
1184     auto scan = std::make_shared<FindDevicesWithCallback>(
1185         i2cBuses, busmap, powerIsOn, objServer, addressBlocklist,
1186         [busNum, &busmap, &dbusInterfaceMap, &unknownBusObjectCount, &powerIsOn,
1187          &objServer, &addressBlocklist]() {
1188             for (auto busIface = dbusInterfaceMap.begin();
1189                  busIface != dbusInterfaceMap.end();)
1190             {
1191                 if (busIface->first.first == static_cast<size_t>(busNum))
1192                 {
1193                     objServer.remove_interface(busIface->second);
1194                     busIface = dbusInterfaceMap.erase(busIface);
1195                 }
1196                 else
1197                 {
1198                     busIface++;
1199                 }
1200             }
1201             auto found = busmap.find(busNum);
1202             if (found == busmap.end() || found->second == nullptr)
1203             {
1204                 return;
1205             }
1206             for (auto& device : *(found->second))
1207             {
1208                 addFruObjectToDbus(device.second, dbusInterfaceMap,
1209                                    static_cast<uint32_t>(busNum), device.first,
1210                                    unknownBusObjectCount, powerIsOn,
1211                                    addressBlocklist, objServer);
1212             }
1213         });
1214     scan->run();
1215 }
1216 
rescanBusses(BusMap & busmap,boost::container::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)1217 void rescanBusses(
1218     BusMap& busmap,
1219     boost::container::flat_map<
1220         std::pair<size_t, size_t>,
1221         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1222     size_t& unknownBusObjectCount, const bool& powerIsOn,
1223     const std::set<size_t>& addressBlocklist,
1224     sdbusplus::asio::object_server& objServer)
1225 {
1226     static boost::asio::steady_timer timer(io);
1227     timer.expires_after(std::chrono::seconds(1));
1228 
1229     // setup an async wait in case we get flooded with requests
1230     timer.async_wait([&](const boost::system::error_code& ec) {
1231         if (ec == boost::asio::error::operation_aborted)
1232         {
1233             return;
1234         }
1235 
1236         if (ec)
1237         {
1238             lg2::error("Error in timer: {ERR}", "ERR", ec.message());
1239             return;
1240         }
1241 
1242         auto devDir = fs::path("/dev/");
1243         std::vector<fs::path> i2cBuses;
1244 
1245         boost::container::flat_map<size_t, fs::path> busPaths;
1246         if (!getI2cDevicePaths(devDir, busPaths))
1247         {
1248             lg2::error("unable to find i2c devices");
1249             return;
1250         }
1251 
1252         for (const auto& busPath : busPaths)
1253         {
1254             i2cBuses.emplace_back(busPath.second);
1255         }
1256 
1257         busmap.clear();
1258         for (auto& [pair, interface] : foundDevices)
1259         {
1260             objServer.remove_interface(interface);
1261         }
1262         foundDevices.clear();
1263 
1264         auto scan = std::make_shared<FindDevicesWithCallback>(
1265             i2cBuses, busmap, powerIsOn, objServer, addressBlocklist, [&]() {
1266                 for (auto& busIface : dbusInterfaceMap)
1267                 {
1268                     objServer.remove_interface(busIface.second);
1269                 }
1270 
1271                 dbusInterfaceMap.clear();
1272                 unknownBusObjectCount = 0;
1273 
1274                 // todo, get this from a more sensable place
1275                 std::vector<uint8_t> baseboardFRU;
1276                 if (readBaseboardFRU(baseboardFRU))
1277                 {
1278                     // If no device on i2c bus 0, the insertion will happen.
1279                     auto bus0 =
1280                         busmap.try_emplace(0, std::make_shared<DeviceMap>());
1281                     bus0.first->second->emplace(0, baseboardFRU);
1282                 }
1283                 for (auto& devicemap : busmap)
1284                 {
1285                     for (auto& device : *devicemap.second)
1286                     {
1287                         addFruObjectToDbus(device.second, dbusInterfaceMap,
1288                                            devicemap.first, device.first,
1289                                            unknownBusObjectCount, powerIsOn,
1290                                            addressBlocklist, objServer);
1291                     }
1292                 }
1293             });
1294         scan->run();
1295     });
1296 }
1297 
updateFruProperty(const std::string & propertyValue,uint32_t bus,uint32_t address,const std::string & propertyName,boost::container::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)1298 bool updateFruProperty(
1299     const std::string& propertyValue, uint32_t bus, uint32_t address,
1300     const std::string& propertyName,
1301     boost::container::flat_map<
1302         std::pair<size_t, size_t>,
1303         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1304     size_t& unknownBusObjectCount, const bool& powerIsOn,
1305     const std::set<size_t>& addressBlocklist,
1306     sdbusplus::asio::object_server& objServer)
1307 {
1308     lg2::debug(
1309         "updateFruProperty called: FieldName = {NAME}, FieldValue = {VALUE}",
1310         "NAME", propertyName, "VALUE", propertyValue);
1311 
1312     std::vector<uint8_t> fruData;
1313     if (!getFruData(fruData, bus, address))
1314     {
1315         lg2::error("Failure getting FRU Data from bus {BUS}, address {ADDRESS}",
1316                    "BUS", bus, "ADDRESS", address);
1317         return false;
1318     }
1319 
1320     bool success = updateAddProperty(propertyValue, propertyName, fruData);
1321     if (!success)
1322     {
1323         lg2::error(
1324             "Failed to update the property on bus {BUS}, address {ADDRESS}",
1325             "BUS", bus, "ADDRESS", address);
1326         return false;
1327     }
1328 
1329     if (!writeFRU(static_cast<uint8_t>(bus), static_cast<uint8_t>(address),
1330                   fruData))
1331     {
1332         lg2::error("Failed to write the FRU");
1333         return false;
1334     }
1335 
1336     rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1337                  addressBlocklist, objServer);
1338     return true;
1339 }
1340 
main()1341 int main()
1342 {
1343     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1344     sdbusplus::asio::object_server objServer(systemBus);
1345 
1346     static size_t unknownBusObjectCount = 0;
1347     static bool powerIsOn = false;
1348     auto devDir = fs::path("/dev/");
1349     auto matchString = std::string(R"(i2c-\d+$)");
1350     std::vector<fs::path> i2cBuses;
1351 
1352     if (!findFiles(devDir, matchString, i2cBuses))
1353     {
1354         lg2::error("unable to find i2c devices");
1355         return 1;
1356     }
1357 
1358     // check for and load blocklist with initial buses.
1359     // once busBlocklist is moved to be non global,
1360     // add it here
1361     auto addressBlocklist = loadBlocklist(blocklistPath);
1362 
1363     systemBus->request_name("xyz.openbmc_project.FruDevice");
1364 
1365     // this is a map with keys of pair(bus number, address) and values of
1366     // the object on dbus
1367     boost::container::flat_map<std::pair<size_t, size_t>,
1368                                std::shared_ptr<sdbusplus::asio::dbus_interface>>
1369         dbusInterfaceMap;
1370 
1371     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1372         objServer.add_interface("/xyz/openbmc_project/FruDevice",
1373                                 "xyz.openbmc_project.FruDeviceManager");
1374 
1375     iface->register_method("ReScan", [&]() {
1376         rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1377                      addressBlocklist, objServer);
1378     });
1379 
1380     iface->register_method("ReScanBus", [&](uint16_t bus) {
1381         rescanOneBus(busMap, bus, dbusInterfaceMap, true, unknownBusObjectCount,
1382                      powerIsOn, addressBlocklist, objServer);
1383     });
1384 
1385     iface->register_method("GetRawFru", getFRUInfo);
1386 
1387     iface->register_method(
1388         "WriteFru", [&](const uint16_t bus, const uint8_t address,
1389                         const std::vector<uint8_t>& data) {
1390             if (!writeFRU(bus, address, data))
1391             {
1392                 throw std::invalid_argument("Invalid Arguments.");
1393                 return;
1394             }
1395             // schedule rescan on success
1396             rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount,
1397                          powerIsOn, addressBlocklist, objServer);
1398         });
1399     iface->initialize();
1400 
1401     std::function<void(sdbusplus::message_t & message)> eventHandler =
1402         [&](sdbusplus::message_t& message) {
1403             std::string objectName;
1404             boost::container::flat_map<
1405                 std::string,
1406                 std::variant<std::string, bool, int64_t, uint64_t, double>>
1407                 values;
1408             message.read(objectName, values);
1409             auto findState = values.find("CurrentHostState");
1410             if (findState != values.end())
1411             {
1412                 if (std::get<std::string>(findState->second) ==
1413                     "xyz.openbmc_project.State.Host.HostState.Running")
1414                 {
1415                     powerIsOn = true;
1416                 }
1417             }
1418 
1419             if (powerIsOn)
1420             {
1421                 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount,
1422                              powerIsOn, addressBlocklist, objServer);
1423             }
1424         };
1425 
1426     sdbusplus::bus::match_t powerMatch = sdbusplus::bus::match_t(
1427         static_cast<sdbusplus::bus_t&>(*systemBus),
1428         "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
1429         "openbmc_project/state/"
1430         "host0',arg0='xyz.openbmc_project.State.Host'",
1431         eventHandler);
1432 
1433     int fd = inotify_init();
1434     inotify_add_watch(fd, i2CDevLocation, IN_CREATE | IN_MOVED_TO | IN_DELETE);
1435     std::array<char, 4096> readBuffer{};
1436     // monitor for new i2c devices
1437     boost::asio::posix::stream_descriptor dirWatch(io, fd);
1438     std::function<void(const boost::system::error_code, std::size_t)>
1439         watchI2cBusses = [&](const boost::system::error_code& ec,
1440                              std::size_t bytesTransferred) {
1441             if (ec)
1442             {
1443                 lg2::info("Callback Error {ERR}", "ERR", ec.message());
1444                 return;
1445             }
1446             size_t index = 0;
1447             while ((index + sizeof(inotify_event)) <= bytesTransferred)
1448             {
1449                 const char* p = &readBuffer[index];
1450                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1451                 const auto* iEvent = reinterpret_cast<const inotify_event*>(p);
1452                 switch (iEvent->mask)
1453                 {
1454                     case IN_CREATE:
1455                     case IN_MOVED_TO:
1456                     case IN_DELETE:
1457                     {
1458                         std::string_view name(&iEvent->name[0], iEvent->len);
1459                         if (name.starts_with("i2c"))
1460                         {
1461                             int bus = busStrToInt(name);
1462                             if (bus < 0)
1463                             {
1464                                 lg2::error("Could not parse bus {BUS}", "BUS",
1465                                            name);
1466                                 continue;
1467                             }
1468                             int rootBus = getRootBus(bus);
1469                             if (rootBus >= 0)
1470                             {
1471                                 rescanOneBus(busMap,
1472                                              static_cast<uint16_t>(rootBus),
1473                                              dbusInterfaceMap, false,
1474                                              unknownBusObjectCount, powerIsOn,
1475                                              addressBlocklist, objServer);
1476                             }
1477                             rescanOneBus(busMap, static_cast<uint16_t>(bus),
1478                                          dbusInterfaceMap, false,
1479                                          unknownBusObjectCount, powerIsOn,
1480                                          addressBlocklist, objServer);
1481                         }
1482                     }
1483                     break;
1484                     default:
1485                         break;
1486                 }
1487                 index += sizeof(inotify_event) + iEvent->len;
1488             }
1489 
1490             dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1491                                      watchI2cBusses);
1492         };
1493 
1494     dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
1495     // run the initial scan
1496     rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1497                  addressBlocklist, objServer);
1498 
1499     io.run();
1500     return 0;
1501 }
1502