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