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