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