1 /*
2 // Copyright (c) 2019 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "utils.hpp"
18 
19 #include <boost/algorithm/string/replace.hpp>
20 #include <boost/asio/steady_timer.hpp>
21 #include <filesystem>
22 #include <iostream>
23 #include <sdbusplus/asio/connection.hpp>
24 #include <sdbusplus/asio/object_server.hpp>
25 #include <sdbusplus/bus/match.hpp>
26 #include <string>
27 #include <utility>
28 
29 extern "C" {
30 #include <i2c/smbus.h>
31 #include <linux/i2c-dev.h>
32 }
33 
34 constexpr const char* configType =
35     "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD";
36 constexpr const char* busName = "xyz.openbmc_project.HsbpManager";
37 
38 constexpr size_t scanRateSeconds = 5;
39 constexpr size_t maxDrives = 8; // only 1 byte alloted
40 
41 boost::asio::io_context io;
42 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
43 sdbusplus::asio::object_server objServer(conn);
44 
45 static std::string zeroPad(const uint8_t val)
46 {
47     std::ostringstream version;
48     version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val);
49     return version.str();
50 }
51 
52 struct Mux
53 {
54     Mux(size_t busIn, size_t addressIn) : bus(busIn), address(addressIn)
55     {
56     }
57     size_t bus;
58     size_t address;
59 };
60 
61 enum class BlinkPattern : uint8_t
62 {
63     off = 0x0,
64     error = 0x2,
65     terminate = 0x3
66 };
67 
68 struct Led : std::enable_shared_from_this<Led>
69 {
70     // led pattern addresses start at 0x10
71     Led(const std::string& path, size_t index, int fd) :
72         address(static_cast<uint8_t>(index + 0x10)), file(fd),
73         ledInterface(objServer.add_interface(path, ledGroup::interface))
74     {
75         if (index >= maxDrives)
76         {
77             throw std::runtime_error("Invalid drive index");
78         }
79 
80         if (!set(BlinkPattern::off))
81         {
82             std::cerr << "Cannot initialize LED " << path << "\n";
83         }
84     }
85 
86     // this has to be called outside the constructor for shared_from_this to
87     // work
88     void createInterface(void)
89     {
90         std::shared_ptr<Led> self = shared_from_this();
91 
92         ledInterface->register_property(
93             ledGroup::asserted, false, [self](const bool req, bool& val) {
94                 if (req == val)
95                 {
96                     return 1;
97                 }
98                 BlinkPattern pattern =
99                     req ? BlinkPattern::error : BlinkPattern::terminate;
100                 if (!self->set(pattern))
101                 {
102                     throw std::runtime_error("Cannot set blink pattern");
103                 }
104                 val = req;
105                 return 1;
106             });
107         ledInterface->initialize();
108     }
109 
110     virtual ~Led()
111     {
112         objServer.remove_interface(ledInterface);
113     }
114 
115     bool set(BlinkPattern pattern)
116     {
117         int ret = i2c_smbus_write_byte_data(file, address,
118                                             static_cast<uint8_t>(pattern));
119         return ret >= 0;
120     }
121 
122     uint8_t address;
123     int file;
124     std::shared_ptr<sdbusplus::asio::dbus_interface> ledInterface;
125 };
126 
127 struct Drive
128 {
129     Drive(size_t driveIndex, bool isPresent, bool isOperational, bool nvme,
130           bool rebuilding) :
131         isNvme(nvme)
132     {
133         constexpr const char* basePath =
134             "/xyz/openbmc_project/inventory/item/drive/Drive_";
135         itemIface = objServer.add_interface(
136             basePath + std::to_string(driveIndex), inventory::interface);
137         itemIface->register_property("Present", isPresent);
138         itemIface->register_property("PrettyName",
139                                      "Drive " + std::to_string(driveIndex));
140         itemIface->initialize();
141         operationalIface = objServer.add_interface(
142             itemIface->get_object_path(),
143             "xyz.openbmc_project.State.Decorator.OperationalStatus");
144         operationalIface->register_property("Functional", isOperational);
145         operationalIface->initialize();
146         rebuildingIface = objServer.add_interface(
147             itemIface->get_object_path(), "xyz.openbmc_project.State.Drive");
148         rebuildingIface->register_property("Rebuilding", rebuilding);
149         rebuildingIface->initialize();
150     }
151     virtual ~Drive()
152     {
153         objServer.remove_interface(itemIface);
154         objServer.remove_interface(operationalIface);
155         objServer.remove_interface(rebuildingIface);
156         objServer.remove_interface(associationIface);
157         objServer.remove_interface(driveIface);
158     }
159 
160     void createAssociation(const std::string& path)
161     {
162         if (associationIface != nullptr)
163         {
164             return;
165         }
166         associationIface = objServer.add_interface(
167             itemIface->get_object_path(),
168             "xyz.openbmc_project.Association.Definitions");
169         std::vector<Association> associations;
170         associations.emplace_back("inventory", "drive", path);
171         associationIface->register_property("Associations", associations);
172         associationIface->initialize();
173     }
174 
175     void removeDriveIface()
176     {
177         objServer.remove_interface(driveIface);
178     }
179 
180     void createDriveIface()
181     {
182         if (associationIface != nullptr || driveIface != nullptr)
183         {
184             // this shouldn't be used if we found another provider of the drive
185             // interface, or if we already created one
186             return;
187         }
188         driveIface =
189             objServer.add_interface(itemIface->get_object_path(),
190                                     "xyz.openbmc_project.Inventory.Item.Drive");
191         driveIface->initialize();
192         createAssociation(itemIface->get_object_path());
193     }
194 
195     std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
196     std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
197     std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface;
198     std::shared_ptr<sdbusplus::asio::dbus_interface> associationIface;
199 
200     // if something else doesn't expose a driveInterface for this, we need to
201     // export it ourselves
202     std::shared_ptr<sdbusplus::asio::dbus_interface> driveIface;
203 
204     bool isNvme;
205 };
206 
207 struct Backplane
208 {
209 
210     Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn,
211               const std::string& nameIn) :
212         bus(busIn),
213         address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn),
214         timer(std::make_shared<boost::asio::steady_timer>(io)),
215         muxes(std::make_shared<std::vector<Mux>>())
216     {
217     }
218     void run()
219     {
220         file = open(("/dev/i2c-" + std::to_string(bus)).c_str(),
221                     O_RDWR | O_CLOEXEC);
222         if (file < 0)
223         {
224             std::cerr << "unable to open bus " << bus << "\n";
225             return;
226         }
227 
228         if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
229         {
230             std::cerr << "unable to set address to " << address << "\n";
231             return;
232         }
233 
234         if (!getPresent())
235         {
236             std::cerr << "Cannot detect CPLD\n";
237             return;
238         }
239 
240         getBootVer(bootVer);
241         getFPGAVer(fpgaVer);
242         getSecurityRev(securityRev);
243         std::string dbusName = boost::replace_all_copy(name, " ", "_");
244         hsbpItemIface = objServer.add_interface(
245             "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName,
246             inventory::interface);
247         hsbpItemIface->register_property("Present", true);
248         hsbpItemIface->register_property("PrettyName", name);
249         hsbpItemIface->initialize();
250 
251         versionIface =
252             objServer.add_interface(hsbpItemIface->get_object_path(),
253                                     "xyz.openbmc_project.Software.Version");
254         versionIface->register_property("Version", zeroPad(bootVer) + "." +
255                                                        zeroPad(fpgaVer) + "." +
256                                                        zeroPad(securityRev));
257         versionIface->register_property(
258             "Purpose",
259             std::string(
260                 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP"));
261         versionIface->initialize();
262         getPresence(presence);
263         getIFDET(ifdet);
264 
265         createDrives();
266 
267         runTimer();
268     }
269 
270     void runTimer()
271     {
272         timer->expires_after(std::chrono::seconds(scanRateSeconds));
273         timer->async_wait([this](boost::system::error_code ec) {
274             if (ec == boost::asio::error::operation_aborted)
275             {
276                 // we're being destroyed
277                 return;
278             }
279             else if (ec)
280             {
281                 std::cerr << "timer error " << ec.message() << "\n";
282                 return;
283             }
284             uint8_t curPresence = 0;
285             uint8_t curIFDET = 0;
286             uint8_t curFailed = 0;
287             uint8_t curRebuild = 0;
288 
289             getPresence(curPresence);
290             getIFDET(curIFDET);
291             getFailed(curFailed);
292             getRebuild(curRebuild);
293 
294             if (curPresence != presence || curIFDET != ifdet ||
295                 curFailed != failed || curRebuild != rebuilding)
296             {
297                 presence = curPresence;
298                 ifdet = curIFDET;
299                 failed = curFailed;
300                 rebuilding = curRebuild;
301                 updateDrives();
302             }
303             runTimer();
304         });
305     }
306 
307     void createDrives()
308     {
309         uint8_t nvme = ifdet ^ presence;
310         for (size_t ii = 0; ii < maxDrives; ii++)
311         {
312             bool isNvme = nvme & (1 << ii);
313             bool isPresent = isNvme || (presence & (1 << ii));
314             bool isFailed = !isPresent || failed & (1 << ii);
315             bool isRebuilding = !isPresent && (rebuilding & (1 << ii));
316 
317             // +1 to convert from 0 based to 1 based
318             size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1;
319             Drive& drive = drives.emplace_back(driveIndex, isPresent, !isFailed,
320                                                isNvme, isRebuilding);
321             std::shared_ptr<Led> led = leds.emplace_back(std::make_shared<Led>(
322                 drive.itemIface->get_object_path(), ii, file));
323             led->createInterface();
324         }
325     }
326 
327     void updateDrives()
328     {
329 
330         uint8_t nvme = ifdet ^ presence;
331         for (size_t ii = 0; ii < maxDrives; ii++)
332         {
333             bool isNvme = nvme & (1 << ii);
334             bool isPresent = isNvme || (presence & (1 << ii));
335             bool isFailed = !isPresent || (failed & (1 << ii));
336             bool isRebuilding = isPresent && (rebuilding & (1 << ii));
337 
338             Drive& drive = drives[ii];
339             drive.isNvme = isNvme;
340             drive.itemIface->set_property("Present", isPresent);
341             drive.operationalIface->set_property("Functional", !isFailed);
342             drive.rebuildingIface->set_property("Rebuilding", isRebuilding);
343         }
344     }
345 
346     bool getPresent()
347     {
348         present = i2c_smbus_read_byte(file) >= 0;
349         return present;
350     }
351 
352     bool getTypeID(uint8_t& val)
353     {
354         constexpr uint8_t addr = 2;
355         int ret = i2c_smbus_read_byte_data(file, addr);
356         if (ret < 0)
357         {
358             std::cerr << "Error " << __FUNCTION__ << "\n";
359             return false;
360         }
361         val = static_cast<uint8_t>(ret);
362         return true;
363     }
364 
365     bool getBootVer(uint8_t& val)
366     {
367         constexpr uint8_t addr = 3;
368         int ret = i2c_smbus_read_byte_data(file, addr);
369         if (ret < 0)
370         {
371             std::cerr << "Error " << __FUNCTION__ << "\n";
372             return false;
373         }
374         val = static_cast<uint8_t>(ret);
375         return true;
376     }
377 
378     bool getFPGAVer(uint8_t& val)
379     {
380         constexpr uint8_t addr = 4;
381         int ret = i2c_smbus_read_byte_data(file, addr);
382         if (ret < 0)
383         {
384             std::cerr << "Error " << __FUNCTION__ << "\n";
385             return false;
386         }
387         val = static_cast<uint8_t>(ret);
388         return true;
389     }
390 
391     bool getSecurityRev(uint8_t& val)
392     {
393         constexpr uint8_t addr = 5;
394         int ret = i2c_smbus_read_byte_data(file, addr);
395         if (ret < 0)
396         {
397             std::cerr << "Error " << __FUNCTION__ << "\n";
398             return false;
399         }
400         val = static_cast<uint8_t>(ret);
401         return true;
402     }
403 
404     bool getPresence(uint8_t& val)
405     {
406         // NVMe drives do not assert PRSNTn, and as such do not get reported as
407         // PRESENT in this register
408 
409         constexpr uint8_t addr = 8;
410 
411         int ret = i2c_smbus_read_byte_data(file, addr);
412         if (ret < 0)
413         {
414             std::cerr << "Error " << __FUNCTION__ << "\n";
415             return false;
416         }
417         // presence is inverted
418         val = static_cast<uint8_t>(~ret);
419         return true;
420     }
421 
422     bool getIFDET(uint8_t& val)
423     {
424         // This register is a bitmap of parallel GPIO pins connected to the
425         // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert
426         // IFDETn low when they are inserted into the HSBP.This register, in
427         // combination with the PRESENCE register, are used by the BMC to detect
428         // the presence of NVMe drives.
429 
430         constexpr uint8_t addr = 9;
431 
432         int ret = i2c_smbus_read_byte_data(file, addr);
433         if (ret < 0)
434         {
435             std::cerr << "Error " << __FUNCTION__ << "\n";
436             return false;
437         }
438         // ifdet is inverted
439         val = static_cast<uint8_t>(~ret);
440         return true;
441     }
442 
443     bool getFailed(uint8_t& val)
444     {
445         constexpr uint8_t addr = 0xC;
446         int ret = i2c_smbus_read_byte_data(file, addr);
447         if (ret < 0)
448         {
449             std::cerr << "Error " << __FUNCTION__ << "\n";
450             return false;
451         }
452         val = static_cast<uint8_t>(ret);
453         return true;
454     }
455 
456     bool getRebuild(uint8_t& val)
457     {
458         constexpr uint8_t addr = 0xD;
459         int ret = i2c_smbus_read_byte_data(file, addr);
460         if (ret < 0)
461         {
462             std::cerr << "Error " << __FUNCTION__ << "\n";
463             return false;
464         }
465         val = static_cast<uint8_t>(ret);
466         return true;
467     }
468 
469     virtual ~Backplane()
470     {
471         objServer.remove_interface(hsbpItemIface);
472         objServer.remove_interface(versionIface);
473         if (file >= 0)
474         {
475             close(file);
476         }
477     }
478 
479     size_t bus;
480     size_t address;
481     size_t backplaneIndex;
482     std::string name;
483     std::shared_ptr<boost::asio::steady_timer> timer;
484     bool present = false;
485     uint8_t typeId = 0;
486     uint8_t bootVer = 0;
487     uint8_t fpgaVer = 0;
488     uint8_t securityRev = 0;
489     uint8_t funSupported = 0;
490     uint8_t presence = 0;
491     uint8_t ifdet = 0;
492     uint8_t failed = 0;
493     uint8_t rebuilding = 0;
494 
495     int file = -1;
496 
497     std::string type;
498 
499     std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface;
500     std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
501 
502     std::vector<Drive> drives;
503     std::vector<std::shared_ptr<Led>> leds;
504     std::shared_ptr<std::vector<Mux>> muxes;
505 };
506 
507 std::unordered_map<std::string, Backplane> backplanes;
508 
509 void createDriveInterfaces(size_t referenceCount)
510 {
511     if (referenceCount != 1)
512     {
513         return;
514     }
515     for (auto& [name, backplane] : backplanes)
516     {
517         for (Drive& drive : backplane.drives)
518         {
519             drive.createDriveIface();
520         }
521     }
522 }
523 
524 void updateAssociations()
525 {
526     constexpr const char* driveType =
527         "xyz.openbmc_project.Inventory.Item.Drive";
528 
529     conn->async_method_call(
530         [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
531             if (ec)
532             {
533                 std::cerr << "Error contacting mapper " << ec.message() << "\n";
534                 return;
535             }
536             for (const auto& [path, objDict] : subtree)
537             {
538                 if (objDict.empty())
539                 {
540                     continue;
541                 }
542 
543                 const std::string& owner = objDict.begin()->first;
544                 // we export this interface too
545                 if (owner == busName)
546                 {
547                     continue;
548                 }
549                 std::shared_ptr<bool> referenceCount = std::make_shared<bool>();
550                 conn->async_method_call(
551                     [path, referenceCount](
552                         const boost::system::error_code ec2,
553                         const boost::container::flat_map<
554                             std::string, std::variant<uint64_t>>& values) {
555                         if (ec2)
556                         {
557                             std::cerr << "Error Getting Config "
558                                       << ec2.message() << " " << __FUNCTION__
559                                       << "\n";
560                             createDriveInterfaces(referenceCount.use_count());
561                             return;
562                         }
563                         auto findBus = values.find("Bus");
564                         auto findIndex = values.find("Index");
565 
566                         if (findBus == values.end() ||
567                             findIndex == values.end())
568                         {
569                             std::cerr << "Illegal interface at " << path
570                                       << "\n";
571                             createDriveInterfaces(referenceCount.use_count());
572                             return;
573                         }
574 
575                         size_t muxBus = static_cast<size_t>(
576                             std::get<uint64_t>(findBus->second));
577                         size_t driveIndex = static_cast<size_t>(
578                             std::get<uint64_t>(findIndex->second));
579                         std::filesystem::path muxPath =
580                             "/sys/bus/i2c/devices/i2c-" +
581                             std::to_string(muxBus) + "/mux_device";
582                         if (!std::filesystem::is_symlink(muxPath))
583                         {
584                             std::cerr << path << " mux does not exist\n";
585                             createDriveInterfaces(referenceCount.use_count());
586                             return;
587                         }
588 
589                         // we should be getting something of the form 7-0052 for
590                         // bus 7 addr 52
591                         std::string fname =
592                             std::filesystem::read_symlink(muxPath).filename();
593                         auto findDash = fname.find('-');
594 
595                         if (findDash == std::string::npos ||
596                             findDash + 1 >= fname.size())
597                         {
598                             std::cerr << path << " mux path invalid\n";
599                             createDriveInterfaces(referenceCount.use_count());
600                             return;
601                         }
602 
603                         std::string busStr = fname.substr(0, findDash);
604                         std::string muxStr = fname.substr(findDash + 1);
605 
606                         size_t bus = static_cast<size_t>(std::stoi(busStr));
607                         size_t addr =
608                             static_cast<size_t>(std::stoi(muxStr, nullptr, 16));
609                         Backplane* parent = nullptr;
610                         for (auto& [name, backplane] : backplanes)
611                         {
612                             for (const Mux& mux : *(backplane.muxes))
613                             {
614                                 if (bus == mux.bus && addr == mux.address)
615                                 {
616                                     parent = &backplane;
617                                     break;
618                                 }
619                             }
620                         }
621                         if (parent == nullptr)
622                         {
623                             std::cerr << "Failed to find mux at bus " << bus
624                                       << ", addr " << addr << "\n";
625                             createDriveInterfaces(referenceCount.use_count());
626                             return;
627                         }
628                         if (parent->drives.size() <= driveIndex)
629                         {
630 
631                             std::cerr << "Illegal drive index at " << path
632                                       << " " << driveIndex << "\n";
633                             createDriveInterfaces(referenceCount.use_count());
634                             return;
635                         }
636                         Drive& drive = parent->drives[driveIndex];
637                         drive.removeDriveIface();
638                         drive.createAssociation(path);
639                         createDriveInterfaces(referenceCount.use_count());
640                     },
641                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
642                     "xyz.openbmc_project.Inventory.Item.Drive");
643             }
644         },
645         mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
646         0, std::array<const char*, 1>{driveType});
647 }
648 
649 void populateMuxes(std::shared_ptr<std::vector<Mux>> muxes,
650                    std::string& rootPath)
651 {
652     const static std::array<const std::string, 4> muxTypes = {
653         "xyz.openbmc_project.Configuration.PCA9543Mux",
654         "xyz.openbmc_project.Configuration.PCA9544Mux",
655         "xyz.openbmc_project.Configuration.PCA9545Mux",
656         "xyz.openbmc_project.Configuration.PCA9546Mux"};
657     conn->async_method_call(
658         [muxes](const boost::system::error_code ec,
659                 const GetSubTreeType& subtree) {
660             if (ec)
661             {
662                 std::cerr << "Error contacting mapper " << ec.message() << "\n";
663                 return;
664             }
665             std::shared_ptr<std::function<void()>> callback =
666                 std::make_shared<std::function<void()>>(
667                     []() { updateAssociations(); });
668             for (const auto& [path, objDict] : subtree)
669             {
670                 if (objDict.empty() || objDict.begin()->second.empty())
671                 {
672                     continue;
673                 }
674 
675                 const std::string& owner = objDict.begin()->first;
676                 const std::vector<std::string>& interfaces =
677                     objDict.begin()->second;
678 
679                 const std::string* interface = nullptr;
680                 for (const std::string& iface : interfaces)
681                 {
682                     if (std::find(muxTypes.begin(), muxTypes.end(), iface) !=
683                         muxTypes.end())
684                     {
685                         interface = &iface;
686                         break;
687                     }
688                 }
689                 if (interface == nullptr)
690                 {
691                     std::cerr << "Cannot get mux type\n";
692                     continue;
693                 }
694 
695                 conn->async_method_call(
696                     [path, muxes, callback](
697                         const boost::system::error_code ec2,
698                         const boost::container::flat_map<
699                             std::string, std::variant<uint64_t>>& values) {
700                         if (ec2)
701                         {
702                             std::cerr << "Error Getting Config "
703                                       << ec2.message() << " " << __FUNCTION__
704                                       << "\n";
705                             return;
706                         }
707                         auto findBus = values.find("Bus");
708                         auto findAddress = values.find("Address");
709                         if (findBus == values.end() ||
710                             findAddress == values.end())
711                         {
712                             std::cerr << "Illegal configuration at " << path
713                                       << "\n";
714                             return;
715                         }
716                         size_t bus = static_cast<size_t>(
717                             std::get<uint64_t>(findBus->second));
718                         size_t address = static_cast<size_t>(
719                             std::get<uint64_t>(findAddress->second));
720                         muxes->emplace_back(bus, address);
721                         if (callback.use_count() == 1)
722                         {
723                             (*callback)();
724                         }
725                     },
726                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
727                     *interface);
728             }
729         },
730         mapper::busName, mapper::path, mapper::interface, mapper::subtree,
731         rootPath, 1, muxTypes);
732 }
733 
734 void populate()
735 {
736     conn->async_method_call(
737         [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
738             if (ec)
739             {
740                 std::cerr << "Error contacting mapper " << ec.message() << "\n";
741                 return;
742             }
743             for (const auto& [path, objDict] : subtree)
744             {
745                 if (objDict.empty())
746                 {
747                     continue;
748                 }
749 
750                 const std::string& owner = objDict.begin()->first;
751                 conn->async_method_call(
752                     [path](const boost::system::error_code ec2,
753                            const boost::container::flat_map<
754                                std::string, BasicVariantType>& resp) {
755                         if (ec2)
756                         {
757                             std::cerr << "Error Getting Config "
758                                       << ec2.message() << "\n";
759                             return;
760                         }
761                         backplanes.clear();
762                         std::optional<size_t> bus;
763                         std::optional<size_t> address;
764                         std::optional<size_t> backplaneIndex;
765                         std::optional<std::string> name;
766                         for (const auto& [key, value] : resp)
767                         {
768                             if (key == "Bus")
769                             {
770                                 bus = std::get<uint64_t>(value);
771                             }
772                             else if (key == "Address")
773                             {
774                                 address = std::get<uint64_t>(value);
775                             }
776                             else if (key == "Index")
777                             {
778                                 backplaneIndex = std::get<uint64_t>(value);
779                             }
780                             else if (key == "Name")
781                             {
782                                 name = std::get<std::string>(value);
783                             }
784                         }
785                         if (!bus || !address || !name || !backplaneIndex)
786                         {
787                             std::cerr << "Illegal configuration at " << path
788                                       << "\n";
789                             return;
790                         }
791                         std::string parentPath =
792                             std::filesystem::path(path).parent_path();
793                         const auto& [backplane, status] = backplanes.emplace(
794                             *name,
795                             Backplane(*bus, *address, *backplaneIndex, *name));
796                         backplane->second.run();
797                         populateMuxes(backplane->second.muxes, parentPath);
798                     },
799                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
800                     configType);
801             }
802         },
803         mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
804         0, std::array<const char*, 1>{configType});
805 }
806 
807 int main()
808 {
809     boost::asio::steady_timer callbackTimer(io);
810 
811     conn->request_name(busName);
812 
813     sdbusplus::bus::match::match match(
814         *conn,
815         "type='signal',member='PropertiesChanged',arg0='" +
816             std::string(configType) + "'",
817         [&callbackTimer](sdbusplus::message::message&) {
818             callbackTimer.expires_after(std::chrono::seconds(2));
819             callbackTimer.async_wait([](const boost::system::error_code ec) {
820                 if (ec == boost::asio::error::operation_aborted)
821                 {
822                     // timer was restarted
823                     return;
824                 }
825                 else if (ec)
826                 {
827                     std::cerr << "Timer error" << ec.message() << "\n";
828                     return;
829                 }
830                 populate();
831             });
832         });
833 
834     sdbusplus::bus::match::match drive(
835         *conn,
836         "type='signal',member='PropertiesChanged',arg0='xyz.openbmc_project."
837         "Inventory.Item.Drive'",
838         [&callbackTimer](sdbusplus::message::message& message) {
839             callbackTimer.expires_after(std::chrono::seconds(2));
840             if (message.get_sender() == conn->get_unique_name())
841             {
842                 return;
843             }
844             callbackTimer.async_wait([](const boost::system::error_code ec) {
845                 if (ec == boost::asio::error::operation_aborted)
846                 {
847                     // timer was restarted
848                     return;
849                 }
850                 else if (ec)
851                 {
852                     std::cerr << "Timer error" << ec.message() << "\n";
853                     return;
854                 }
855                 populate();
856             });
857         });
858 
859     io.post([]() { populate(); });
860     io.run();
861 }
862