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