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