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