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 <filesystem>
22 #include <iostream>
23 #include <sdbusplus/asio/connection.hpp>
24 #include <sdbusplus/asio/object_server.hpp>
25 #include <sdbusplus/bus/match.hpp>
26 #include <string>
27 #include <utility>
28 
29 extern "C" {
30 #include <i2c/smbus.h>
31 #include <linux/i2c-dev.h>
32 }
33 
34 constexpr const char* configType =
35     "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD";
36 
37 constexpr size_t scanRateSeconds = 5;
38 constexpr size_t maxDrives = 8; // only 1 byte alloted
39 
40 boost::asio::io_context io;
41 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
42 sdbusplus::asio::object_server objServer(conn);
43 
44 static std::string zeroPad(const uint8_t val)
45 {
46     std::ostringstream version;
47     version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val);
48     return version.str();
49 }
50 
51 struct Mux
52 {
53     Mux(size_t busIn, size_t addressIn) : bus(busIn), address(addressIn)
54     {
55     }
56     size_t bus;
57     size_t address;
58 };
59 struct Drive
60 {
61     Drive(size_t driveIndex, bool isPresent, bool isOperational, bool nvme,
62           bool rebuilding) :
63         isNvme(nvme)
64     {
65         constexpr const char* basePath =
66             "/xyz/openbmc_project/inventory/item/drive/Drive_";
67         itemIface = objServer.add_interface(
68             basePath + std::to_string(driveIndex), inventory::interface);
69         itemIface->register_property("Present", isPresent);
70         itemIface->register_property("PrettyName",
71                                      "Drive " + std::to_string(driveIndex));
72         itemIface->initialize();
73         operationalIface = objServer.add_interface(
74             itemIface->get_object_path(),
75             "xyz.openbmc_project.State.Decorator.OperationalStatus");
76         operationalIface->register_property("Functional", isOperational);
77         operationalIface->initialize();
78         rebuildingIface = objServer.add_interface(
79             itemIface->get_object_path(), "xyz.openbmc_project.State.Drive");
80         rebuildingIface->register_property("Rebuilding", rebuilding);
81         rebuildingIface->initialize();
82     }
83     ~Drive()
84     {
85         objServer.remove_interface(itemIface);
86         objServer.remove_interface(operationalIface);
87         objServer.remove_interface(rebuildingIface);
88         objServer.remove_interface(associationIface);
89     }
90 
91     void createAssociation(const std::string& path)
92     {
93         if (associationIface != nullptr)
94         {
95             return;
96         }
97         associationIface = objServer.add_interface(
98             itemIface->get_object_path(),
99             "xyz.openbmc_project.Association.Definitions");
100         std::vector<Association> associations;
101         associations.emplace_back("inventory", "drive", path);
102         associationIface->register_property("Associations", associations);
103         associationIface->initialize();
104     }
105 
106     std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
107     std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
108     std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface;
109     std::shared_ptr<sdbusplus::asio::dbus_interface> associationIface;
110     bool isNvme;
111 };
112 
113 struct Backplane
114 {
115 
116     Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn,
117               const std::string& nameIn) :
118         bus(busIn),
119         address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn),
120         timer(std::make_shared<boost::asio::steady_timer>(io)),
121         muxes(std::make_shared<std::vector<Mux>>())
122     {
123     }
124     void run()
125     {
126         file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR);
127         if (file < 0)
128         {
129             std::cerr << "unable to open bus " << bus << "\n";
130             return;
131         }
132 
133         if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
134         {
135             std::cerr << "unable to set address to " << address << "\n";
136             return;
137         }
138 
139         if (!getPresent())
140         {
141             std::cerr << "Cannot detect CPLD\n";
142             return;
143         }
144 
145         getBootVer(bootVer);
146         getFPGAVer(fpgaVer);
147         getSecurityRev(securityRev);
148         std::string dbusName = boost::replace_all_copy(name, " ", "_");
149         hsbpItemIface = objServer.add_interface(
150             "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName,
151             inventory::interface);
152         hsbpItemIface->register_property("Present", true);
153         hsbpItemIface->register_property("PrettyName", name);
154         hsbpItemIface->initialize();
155 
156         versionIface =
157             objServer.add_interface(hsbpItemIface->get_object_path(),
158                                     "xyz.openbmc_project.Software.Version");
159         versionIface->register_property("Version", zeroPad(bootVer) + "." +
160                                                        zeroPad(fpgaVer) + "." +
161                                                        zeroPad(securityRev));
162         versionIface->register_property(
163             "Purpose",
164             std::string(
165                 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP"));
166         versionIface->initialize();
167         getPresence(presence);
168         getIFDET(ifdet);
169 
170         createDrives();
171 
172         runTimer();
173     }
174 
175     void runTimer()
176     {
177         timer->expires_after(std::chrono::seconds(scanRateSeconds));
178         timer->async_wait([this](boost::system::error_code ec) {
179             if (ec == boost::asio::error::operation_aborted)
180             {
181                 // we're being destroyed
182                 return;
183             }
184             else if (ec)
185             {
186                 std::cerr << "timer error " << ec.message() << "\n";
187                 return;
188             }
189             uint8_t curPresence = 0;
190             uint8_t curIFDET = 0;
191             uint8_t curFailed = 0;
192             uint8_t curRebuild = 0;
193 
194             getPresence(curPresence);
195             getIFDET(curIFDET);
196             getFailed(curFailed);
197             getRebuild(curRebuild);
198 
199             if (curPresence != presence || curIFDET != ifdet ||
200                 curFailed != failed || curRebuild != rebuilding)
201             {
202                 presence = curPresence;
203                 ifdet = curIFDET;
204                 failed = curFailed;
205                 rebuilding = curRebuild;
206                 updateDrives();
207             }
208             runTimer();
209         });
210     }
211 
212     void createDrives()
213     {
214         uint8_t nvme = ifdet ^ presence;
215         for (size_t ii = 0; ii < maxDrives; ii++)
216         {
217             bool isNvme = nvme & (1 << ii);
218             bool isPresent = isNvme || (presence & (1 << ii));
219             bool isFailed = !isPresent || failed & (1 << ii);
220             bool isRebuilding = !isPresent && (rebuilding & (1 << ii));
221 
222             // +1 to convert from 0 based to 1 based
223             size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1;
224             drives.emplace_back(driveIndex, isPresent, !isFailed, isNvme,
225                                 isRebuilding);
226         }
227     }
228 
229     void updateDrives()
230     {
231 
232         uint8_t nvme = ifdet ^ presence;
233         for (size_t ii = 0; ii < maxDrives; ii++)
234         {
235             bool isNvme = nvme & (1 << ii);
236             bool isPresent = isNvme || (presence & (1 << ii));
237             bool isFailed = !isPresent || (failed & (1 << ii));
238             bool isRebuilding = isPresent && (rebuilding & (1 << ii));
239 
240             Drive& drive = drives[ii];
241             drive.isNvme = isNvme;
242             drive.itemIface->set_property("Present", isPresent);
243             drive.operationalIface->set_property("Functional", !isFailed);
244             drive.rebuildingIface->set_property("Rebuilding", isRebuilding);
245         }
246     }
247 
248     bool getPresent()
249     {
250         present = i2c_smbus_read_byte(file) >= 0;
251         return present;
252     }
253 
254     bool getTypeID(uint8_t& val)
255     {
256         constexpr uint8_t addr = 2;
257         int ret = i2c_smbus_read_byte_data(file, addr);
258         if (ret < 0)
259         {
260             std::cerr << "Error " << __FUNCTION__ << "\n";
261             return false;
262         }
263         val = static_cast<uint8_t>(ret);
264         return true;
265     }
266 
267     bool getBootVer(uint8_t& val)
268     {
269         constexpr uint8_t addr = 3;
270         int ret = i2c_smbus_read_byte_data(file, addr);
271         if (ret < 0)
272         {
273             std::cerr << "Error " << __FUNCTION__ << "\n";
274             return false;
275         }
276         val = static_cast<uint8_t>(ret);
277         return true;
278     }
279 
280     bool getFPGAVer(uint8_t& val)
281     {
282         constexpr uint8_t addr = 4;
283         int ret = i2c_smbus_read_byte_data(file, addr);
284         if (ret < 0)
285         {
286             std::cerr << "Error " << __FUNCTION__ << "\n";
287             return false;
288         }
289         val = static_cast<uint8_t>(ret);
290         return true;
291     }
292 
293     bool getSecurityRev(uint8_t& val)
294     {
295         constexpr uint8_t addr = 5;
296         int ret = i2c_smbus_read_byte_data(file, addr);
297         if (ret < 0)
298         {
299             std::cerr << "Error " << __FUNCTION__ << "\n";
300             return false;
301         }
302         val = static_cast<uint8_t>(ret);
303         return true;
304     }
305 
306     bool getPresence(uint8_t& val)
307     {
308         // NVMe drives do not assert PRSNTn, and as such do not get reported as
309         // PRESENT in this register
310 
311         constexpr uint8_t addr = 8;
312 
313         int ret = i2c_smbus_read_byte_data(file, addr);
314         if (ret < 0)
315         {
316             std::cerr << "Error " << __FUNCTION__ << "\n";
317             return false;
318         }
319         // presence is inverted
320         val = static_cast<uint8_t>(~ret);
321         return true;
322     }
323 
324     bool getIFDET(uint8_t& val)
325     {
326         // This register is a bitmap of parallel GPIO pins connected to the
327         // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert
328         // IFDETn low when they are inserted into the HSBP.This register, in
329         // combination with the PRESENCE register, are used by the BMC to detect
330         // the presence of NVMe drives.
331 
332         constexpr uint8_t addr = 9;
333 
334         int ret = i2c_smbus_read_byte_data(file, addr);
335         if (ret < 0)
336         {
337             std::cerr << "Error " << __FUNCTION__ << "\n";
338             return false;
339         }
340         // ifdet is inverted
341         val = static_cast<uint8_t>(~ret);
342         return true;
343     }
344 
345     bool getFailed(uint8_t& val)
346     {
347         constexpr uint8_t addr = 0xC;
348         int ret = i2c_smbus_read_byte_data(file, addr);
349         if (ret < 0)
350         {
351             std::cerr << "Error " << __FUNCTION__ << "\n";
352             return false;
353         }
354         val = static_cast<uint8_t>(ret);
355         return true;
356     }
357 
358     bool getRebuild(uint8_t& val)
359     {
360         constexpr uint8_t addr = 0xD;
361         int ret = i2c_smbus_read_byte_data(file, addr);
362         if (ret < 0)
363         {
364             std::cerr << "Error " << __FUNCTION__ << "\n";
365             return false;
366         }
367         val = static_cast<uint8_t>(ret);
368         return true;
369     }
370 
371     ~Backplane()
372     {
373         objServer.remove_interface(hsbpItemIface);
374         objServer.remove_interface(versionIface);
375         if (file >= 0)
376         {
377             close(file);
378         }
379     }
380 
381     size_t bus;
382     size_t address;
383     size_t backplaneIndex;
384     std::string name;
385     std::shared_ptr<boost::asio::steady_timer> timer;
386     bool present = false;
387     uint8_t typeId = 0;
388     uint8_t bootVer = 0;
389     uint8_t fpgaVer = 0;
390     uint8_t securityRev = 0;
391     uint8_t funSupported = 0;
392     uint8_t presence = 0;
393     uint8_t ifdet = 0;
394     uint8_t failed = 0;
395     uint8_t rebuilding = 0;
396 
397     int file = -1;
398 
399     std::string type;
400 
401     std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface;
402     std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
403 
404     std::vector<Drive> drives;
405     std::shared_ptr<std::vector<Mux>> muxes;
406 };
407 
408 std::unordered_map<std::string, Backplane> backplanes;
409 
410 void updateAssociations()
411 {
412     constexpr const char* driveType =
413         "xyz.openbmc_project.Inventory.Item.Drive";
414 
415     conn->async_method_call(
416         [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
417             if (ec)
418             {
419                 std::cerr << "Error contacting mapper " << ec.message() << "\n";
420                 return;
421             }
422             for (const auto& [path, objDict] : subtree)
423             {
424                 if (objDict.empty())
425                 {
426                     continue;
427                 }
428 
429                 const std::string& owner = objDict.begin()->first;
430                 conn->async_method_call(
431                     [path](const boost::system::error_code ec2,
432                            const boost::container::flat_map<
433                                std::string, std::variant<uint64_t>>& values) {
434                         if (ec2)
435                         {
436                             std::cerr << "Error Getting Config "
437                                       << ec2.message() << " " << __FUNCTION__
438                                       << "\n";
439                             return;
440                         }
441                         auto findBus = values.find("Bus");
442                         auto findIndex = values.find("Index");
443 
444                         if (findBus == values.end() ||
445                             findIndex == values.end())
446                         {
447                             std::cerr << "Illegal interface at " << path
448                                       << "\n";
449                             return;
450                         }
451 
452                         size_t muxBus = static_cast<size_t>(
453                             std::get<uint64_t>(findBus->second));
454                         size_t driveIndex = static_cast<size_t>(
455                             std::get<uint64_t>(findIndex->second));
456                         std::filesystem::path muxPath =
457                             "/sys/bus/i2c/devices/i2c-" +
458                             std::to_string(muxBus) + "/mux_device";
459                         if (!std::filesystem::is_symlink(muxPath))
460                         {
461                             std::cerr << path << " mux does not exist\n";
462                             return;
463                         }
464 
465                         // we should be getting something of the form 7-0052 for
466                         // bus 7 addr 52
467                         std::string fname =
468                             std::filesystem::read_symlink(muxPath).filename();
469                         auto findDash = fname.find('-');
470 
471                         if (findDash == std::string::npos ||
472                             findDash + 1 >= fname.size())
473                         {
474                             std::cerr << path << " mux path invalid\n";
475                             return;
476                         }
477 
478                         std::string busStr = fname.substr(0, findDash);
479                         std::string muxStr = fname.substr(findDash + 1);
480 
481                         size_t bus = static_cast<size_t>(std::stoi(busStr));
482                         size_t addr =
483                             static_cast<size_t>(std::stoi(muxStr, nullptr, 16));
484                         Backplane* parent = nullptr;
485                         for (auto& [name, backplane] : backplanes)
486                         {
487                             for (const Mux& mux : *(backplane.muxes))
488                             {
489                                 if (bus == mux.bus && addr == mux.address)
490                                 {
491                                     parent = &backplane;
492                                     break;
493                                 }
494                             }
495                         }
496                         if (parent == nullptr)
497                         {
498                             std::cerr << "Failed to find mux at bus " << bus
499                                       << ", addr " << addr << "\n";
500                             return;
501                         }
502                         if (parent->drives.size() <= driveIndex)
503                         {
504 
505                             std::cerr << "Illegal drive index at " << path
506                                       << " " << driveIndex << "\n";
507                             return;
508                         }
509                         Drive& drive = parent->drives[driveIndex];
510                         drive.createAssociation(path);
511                     },
512                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
513                     "xyz.openbmc_project.Inventory.Item.Drive");
514             }
515         },
516         mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
517         0, std::array<const char*, 1>{driveType});
518 }
519 
520 void populateMuxes(std::shared_ptr<std::vector<Mux>> muxes,
521                    std::string& rootPath)
522 {
523     const static std::array<const std::string, 4> muxTypes = {
524         "xyz.openbmc_project.Configuration.PCA9543Mux",
525         "xyz.openbmc_project.Configuration.PCA9544Mux",
526         "xyz.openbmc_project.Configuration.PCA9545Mux",
527         "xyz.openbmc_project.Configuration.PCA9546Mux"};
528     conn->async_method_call(
529         [muxes](const boost::system::error_code ec,
530                 const GetSubTreeType& subtree) {
531             if (ec)
532             {
533                 std::cerr << "Error contacting mapper " << ec.message() << "\n";
534                 return;
535             }
536             std::shared_ptr<std::function<void()>> callback =
537                 std::make_shared<std::function<void()>>(
538                     []() { updateAssociations(); });
539             for (const auto& [path, objDict] : subtree)
540             {
541                 if (objDict.empty() || objDict.begin()->second.empty())
542                 {
543                     continue;
544                 }
545 
546                 const std::string& owner = objDict.begin()->first;
547                 const std::vector<std::string>& interfaces =
548                     objDict.begin()->second;
549 
550                 const std::string* interface = nullptr;
551                 for (const std::string& iface : interfaces)
552                 {
553                     if (std::find(muxTypes.begin(), muxTypes.end(), iface) !=
554                         muxTypes.end())
555                     {
556                         interface = &iface;
557                         break;
558                     }
559                 }
560                 if (interface == nullptr)
561                 {
562                     std::cerr << "Cannot get mux type\n";
563                     continue;
564                 }
565 
566                 conn->async_method_call(
567                     [path, muxes, callback](
568                         const boost::system::error_code ec2,
569                         const boost::container::flat_map<
570                             std::string, std::variant<uint64_t>>& values) {
571                         if (ec2)
572                         {
573                             std::cerr << "Error Getting Config "
574                                       << ec2.message() << " " << __FUNCTION__
575                                       << "\n";
576                             return;
577                         }
578                         auto findBus = values.find("Bus");
579                         auto findAddress = values.find("Address");
580                         if (findBus == values.end() ||
581                             findAddress == values.end())
582                         {
583                             std::cerr << "Illegal configuration at " << path
584                                       << "\n";
585                             return;
586                         }
587                         size_t bus = static_cast<size_t>(
588                             std::get<uint64_t>(findBus->second));
589                         size_t address = static_cast<size_t>(
590                             std::get<uint64_t>(findAddress->second));
591                         muxes->emplace_back(bus, address);
592                         if (callback.use_count() == 1)
593                         {
594                             (*callback)();
595                         }
596                     },
597                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
598                     *interface);
599             }
600         },
601         mapper::busName, mapper::path, mapper::interface, mapper::subtree,
602         rootPath, 1, muxTypes);
603 }
604 
605 void populate()
606 {
607     conn->async_method_call(
608         [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
609             if (ec)
610             {
611                 std::cerr << "Error contacting mapper " << ec.message() << "\n";
612                 return;
613             }
614             for (const auto& [path, objDict] : subtree)
615             {
616                 if (objDict.empty())
617                 {
618                     continue;
619                 }
620 
621                 const std::string& owner = objDict.begin()->first;
622                 conn->async_method_call(
623                     [path](const boost::system::error_code ec2,
624                            const boost::container::flat_map<
625                                std::string, BasicVariantType>& resp) {
626                         if (ec2)
627                         {
628                             std::cerr << "Error Getting Config "
629                                       << ec2.message() << "\n";
630                             return;
631                         }
632                         backplanes.clear();
633                         std::optional<size_t> bus;
634                         std::optional<size_t> address;
635                         std::optional<size_t> backplaneIndex;
636                         std::optional<std::string> name;
637                         for (const auto& [key, value] : resp)
638                         {
639                             if (key == "Bus")
640                             {
641                                 bus = std::get<uint64_t>(value);
642                             }
643                             else if (key == "Address")
644                             {
645                                 address = std::get<uint64_t>(value);
646                             }
647                             else if (key == "Index")
648                             {
649                                 backplaneIndex = std::get<uint64_t>(value);
650                             }
651                             else if (key == "Name")
652                             {
653                                 name = std::get<std::string>(value);
654                             }
655                         }
656                         if (!bus || !address || !name || !backplaneIndex)
657                         {
658                             std::cerr << "Illegal configuration at " << path
659                                       << "\n";
660                             return;
661                         }
662                         std::string parentPath =
663                             std::filesystem::path(path).parent_path();
664                         const auto& [backplane, status] = backplanes.emplace(
665                             *name,
666                             Backplane(*bus, *address, *backplaneIndex, *name));
667                         backplane->second.run();
668                         populateMuxes(backplane->second.muxes, parentPath);
669                     },
670                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
671                     configType);
672             }
673         },
674         mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
675         0, std::array<const char*, 1>{configType});
676 }
677 
678 int main()
679 {
680     boost::asio::steady_timer callbackTimer(io);
681 
682     conn->request_name("xyz.openbmc_project.HsbpManager");
683 
684     sdbusplus::bus::match::match match(
685         *conn,
686         "type='signal',member='PropertiesChanged',arg0='" +
687             std::string(configType) + "'",
688         [&callbackTimer](sdbusplus::message::message&) {
689             callbackTimer.expires_after(std::chrono::seconds(2));
690             callbackTimer.async_wait([](const boost::system::error_code ec) {
691                 if (ec == boost::asio::error::operation_aborted)
692                 {
693                     // timer was restarted
694                     return;
695                 }
696                 else if (ec)
697                 {
698                     std::cerr << "Timer error" << ec.message() << "\n";
699                     return;
700                 }
701                 populate();
702             });
703         });
704 
705     sdbusplus::bus::match::match drive(
706         *conn,
707         "type='signal',member='PropertiesChanged',arg0='xyz.openbmc_project."
708         "Inventory.Item.Drive'",
709         [&callbackTimer](sdbusplus::message::message&) {
710             callbackTimer.expires_after(std::chrono::seconds(2));
711             callbackTimer.async_wait([](const boost::system::error_code ec) {
712                 if (ec == boost::asio::error::operation_aborted)
713                 {
714                     // timer was restarted
715                     return;
716                 }
717                 else if (ec)
718                 {
719                     std::cerr << "Timer error" << ec.message() << "\n";
720                     return;
721                 }
722                 populate();
723             });
724         });
725 
726     io.post([]() { populate(); });
727     io.run();
728 }
729