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