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