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