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 <algorithm>
20 #include <bitset>
21 #include <boost/algorithm/string/replace.hpp>
22 #include <boost/asio/posix/stream_descriptor.hpp>
23 #include <boost/asio/steady_timer.hpp>
24 #include <boost/container/flat_set.hpp>
25 #include <filesystem>
26 #include <forward_list>
27 #include <fstream>
28 #include <gpiod.hpp>
29 #include <iostream>
30 #include <list>
31 #include <sdbusplus/asio/connection.hpp>
32 #include <sdbusplus/asio/object_server.hpp>
33 #include <sdbusplus/bus/match.hpp>
34 #include <string>
35 #include <utility>
36
37 extern "C" {
38 #include <i2c/smbus.h>
39 #include <linux/i2c-dev.h>
40 }
41
42 /****************************************************************************/
43 /******************** Global Constants/Type Declarations ********************/
44 /****************************************************************************/
45 constexpr const char* hsbpCpldInft =
46 "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD";
47 constexpr const char* hsbpConfigIntf =
48 "xyz.openbmc_project.Configuration.HSBPConfiguration";
49 constexpr const char* nvmeIntf = "xyz.openbmc_project.Inventory.Item.NVMe";
50 constexpr const char* busName = "xyz.openbmc_project.HsbpManager";
51
52 constexpr size_t scanRateSeconds = 5;
53 constexpr size_t maxDrives = 8; // only 1 byte alloted
54
55 using NvmeMapping = std::vector<std::string>;
56 /***************************** End of Section *******************************/
57
58 /****************************************************************************/
59 /**************************** Enums Definitions *****************************/
60 /****************************************************************************/
61 enum class AppState : uint8_t
62 {
63 idle,
64 loadingHsbpConfig,
65 hsbpConfigLoaded,
66 loadingComponents,
67 componentsLoaded,
68 loadingBackplanes,
69 backplanesLoaded,
70 loadingDrives,
71 drivesLoaded
72 };
73
74 enum class BlinkPattern : uint8_t
75 {
76 off = 0x0,
77 error = 0x2,
78 terminate = 0x3
79 };
80 /***************************** End of Section *******************************/
81
82 /****************************************************************************/
83 /************ HSBP Configuration related struct/class Definitions ***********/
84 /****************************************************************************/
85 struct HsbpConfig
86 {
87 size_t rootBus;
88 std::vector<std::string> supportedHsbps;
89 std::unordered_map<std::string, NvmeMapping> hsbpNvmeMap;
90 std::vector<std::string> clockBufferTypes;
91 std::vector<std::string> ioExpanderTypes;
92
clearConfigHsbpConfig93 void clearConfig()
94 {
95 rootBus = -1;
96 supportedHsbps.clear();
97 hsbpNvmeMap.clear();
98 clockBufferTypes.clear();
99 ioExpanderTypes.clear();
100 }
101 };
102
103 class ClockBuffer
104 {
105 size_t bus;
106 size_t address;
107 std::string modeOfOperation;
108 size_t outCtrlBaseAddr;
109 size_t outCtrlByteCount;
110 std::unordered_map<std::string, std::vector<std::string>> byteMap;
111 std::string name;
112 std::string type;
113
114 int file = -1;
115 bool initialized = false;
116
initialize()117 void initialize()
118 {
119 /* Execute below operation only when mode of operation is SMBus. By
120 * default the clock buffer is configured to follow OE pin output, so we
121 * need to set the output value to 0 to disable the clock outputs. If
122 * mode of operation is IO, then the IO value will determine the
123 * disable/enable of clock output */
124 if (modeOfOperation == "SMBus")
125 {
126 if (file < 0)
127 {
128 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(),
129 O_RDWR | O_CLOEXEC);
130 if (file < 0)
131 {
132 std::cerr << "ClockBuffer : \"" << name
133 << "\" - Unable to open bus : " << bus << "\n";
134 return;
135 }
136 }
137
138 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
139 {
140 std::cerr << "ClockBuffer : \"" << name
141 << "\" - Unable to set address to " << address
142 << "\n";
143 return;
144 }
145
146 for (uint8_t i = 0; i < outCtrlByteCount; i++)
147 {
148 std::string byteName = "Byte" + std::to_string(i);
149
150 auto byte = byteMap.find(byteName);
151 if (byte == byteMap.end())
152 {
153 std::cerr << "ClockBuffer : \"" << name
154 << "\" - Byte map error ! Unable to find "
155 << byteName << "\n";
156 return;
157 }
158
159 /* Get current value of output control register */
160 int read = i2c_smbus_read_byte_data(
161 file, static_cast<uint8_t>(outCtrlBaseAddr + i));
162 if (read < 0)
163 {
164 std::cerr << "ClockBuffer : \"" << name
165 << "\" - Error: Unable to read data from clock "
166 "buffer register\n";
167 return;
168 }
169
170 std::bitset<8> currByte(read);
171 bool writeRequired = false;
172
173 /* Set zero only at bit position that we have a NVMe drive (i.e.
174 * ignore where byteMap is "-"). We do not want to touch other
175 * bits */
176 for (uint8_t bit = 0; bit < 8; bit++)
177 {
178 if (byte->second.at(bit) != "-")
179 {
180 writeRequired = true;
181 currByte.reset(bit);
182 }
183 }
184
185 if (writeRequired)
186 {
187 int ret = i2c_smbus_write_byte_data(
188 file, static_cast<uint8_t>(outCtrlBaseAddr + i),
189 static_cast<uint8_t>(currByte.to_ulong()));
190
191 if (ret < 0)
192 {
193 std::cerr
194 << "ClockBuffer : \"" << name
195 << "\" - Error: Unable to write data to clock "
196 "buffer register\n";
197 return;
198 }
199 }
200 }
201 }
202 initialized = true;
203 std::cerr << "ClockBuffer : \"" << name << "\" initialized\n";
204 }
205
206 public:
ClockBuffer(size_t busIn,size_t addressIn,std::string & modeOfOperationIn,size_t outCtrlBaseAddrIn,size_t outCtrlByteCountIn,std::unordered_map<std::string,std::vector<std::string>> & byteMapIn,std::string & nameIn,std::string & typeIn)207 ClockBuffer(
208 size_t busIn, size_t addressIn, std::string& modeOfOperationIn,
209 size_t outCtrlBaseAddrIn, size_t outCtrlByteCountIn,
210 std::unordered_map<std::string, std::vector<std::string>>& byteMapIn,
211 std::string& nameIn, std::string& typeIn) :
212 bus(busIn),
213 address(addressIn), modeOfOperation(std::move(modeOfOperationIn)),
214 outCtrlBaseAddr(outCtrlBaseAddrIn),
215 outCtrlByteCount(outCtrlByteCountIn), byteMap(std::move(byteMapIn)),
216 name(std::move(nameIn)), type(std::move(typeIn))
217 {
218 initialize();
219 }
220
isInitialized()221 bool isInitialized()
222 {
223 if (!initialized)
224 {
225 /* There was an issue with the initialization of this component. Try
226 * to invoke initialization again */
227 initialize();
228 }
229 return initialized;
230 }
231
getName()232 std::string getName()
233 {
234 return name;
235 }
236
enableDisableClock(std::forward_list<std::string> & nvmeDrivesInserted,std::forward_list<std::string> & nvmeDrivesRemoved)237 bool enableDisableClock(std::forward_list<std::string>& nvmeDrivesInserted,
238 std::forward_list<std::string>& nvmeDrivesRemoved)
239 {
240 if (modeOfOperation != "SMBus")
241 {
242 /* The clock is enabled using IO expander. No action needed from
243 * here */
244 return true;
245 }
246
247 if (nvmeDrivesInserted.empty() && nvmeDrivesRemoved.empty())
248 {
249 /* There are no drives to update */
250 return true;
251 }
252
253 for (uint8_t i = 0; i < outCtrlByteCount; i++)
254 {
255 std::string byteName = "Byte" + std::to_string(i);
256
257 auto byte = byteMap.find(byteName);
258 if (byte == byteMap.end())
259 {
260 std::cerr << "ClockBuffer : \"" << name
261 << "\" - Byte map error ! Unable to find " << byteName
262 << "\n";
263 return false;
264 }
265
266 /* Get current value of output control register */
267 int read = i2c_smbus_read_byte_data(
268 file, static_cast<uint8_t>(outCtrlBaseAddr + i));
269 if (read < 0)
270 {
271 std::cerr << "ClockBuffer : \"" << name
272 << "\" - Error: Unable to read data from clock "
273 "buffer register\n";
274 return false;
275 }
276
277 std::bitset<8> currByte(read);
278 bool writeRequired = false;
279
280 /* Set the bit if the NVMe drive is found in nvmeDrivesInserted, and
281 * reset the bit if found in nvmeDrivesRemoved */
282 for (uint8_t bit = 0; bit < 8; bit++)
283 {
284 /* The remove function returns number of elements removed from
285 * list indicating the presence of the drive and also removing
286 * it form the list */
287 if (nvmeDrivesInserted.remove(byte->second.at(bit)))
288 {
289 writeRequired = true;
290 currByte.set(bit);
291 continue;
292 }
293
294 if (nvmeDrivesRemoved.remove(byte->second.at(bit)))
295 {
296 writeRequired = true;
297 currByte.reset(bit);
298 }
299 }
300
301 if (!writeRequired)
302 {
303 /* No Write is required as there are no changes */
304 continue;
305 }
306
307 int ret = i2c_smbus_write_byte_data(
308 file, static_cast<uint8_t>(outCtrlBaseAddr + i),
309 static_cast<uint8_t>(currByte.to_ulong()));
310 if (ret < 0)
311 {
312 std::cerr << "ClockBuffer : \"" << name
313 << "\" - Error: Unable to write data to clock "
314 "buffer register\n";
315 return false;
316 }
317 }
318
319 return true;
320 }
321
~ClockBuffer()322 ~ClockBuffer()
323 {
324 if (file >= 0)
325 {
326 close(file);
327 }
328 }
329 };
330
331 class IoExpander
332 {
333 size_t bus;
334 size_t address;
335 size_t confIORegAddr;
336 size_t outCtrlBaseAddr;
337 size_t outCtrlByteCount;
338 std::unordered_map<std::string, std::vector<std::string>> ioMap;
339 std::string name;
340 std::string type;
341
342 int file = -1;
343 bool initialized = false;
344
initialize()345 void initialize()
346 {
347 /* Initialize the IO expander Control register to configure the IO ports
348 * as outputs and set the output to low by default */
349 if (file < 0)
350 {
351 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(),
352 O_RDWR | O_CLOEXEC);
353 if (file < 0)
354 {
355 std::cerr << "IoExpander : " << name
356 << " - Unable to open bus : " << bus << "\n";
357 return;
358 }
359 }
360
361 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
362 {
363 std::cerr << "IoExpander : \"" << name
364 << "\" - Unable to set address to " << address << "\n";
365 return;
366 }
367
368 for (uint8_t i = 0; i < outCtrlByteCount; i++)
369 {
370 std::string ioName = "IO" + std::to_string(i);
371
372 auto io = ioMap.find(ioName);
373 if (io == ioMap.end())
374 {
375 std::cerr << "IoExpander : \"" << name
376 << "\" - IO map error ! Unable to find " << ioName
377 << "\n";
378 return;
379 }
380
381 /* Get current value of IO configuration register */
382 int read1 = i2c_smbus_read_byte_data(
383 file, static_cast<uint8_t>(confIORegAddr + i));
384 if (read1 < 0)
385 {
386 std::cerr << "IoExpander : \"" << name
387 << "\" - Error: Unable to read data from io expander "
388 "IO control register\n";
389 return;
390 }
391
392 /* Get current value of IO Ouput register */
393 int read2 = i2c_smbus_read_byte_data(
394 file, static_cast<uint8_t>(confIORegAddr + i));
395 if (read2 < 0)
396 {
397 std::cerr << "IoExpander : \"" << name
398 << "\" - Error: Unable to read data from io expander "
399 "IO output register\n";
400 return;
401 }
402
403 bool writeRequired = false;
404 std::bitset<8> currCtrlVal(read1);
405 std::bitset<8> currOutVal(read2);
406
407 /* Set zero only at bit position that we have a NVMe drive (i.e.
408 * ignore where ioMap is "-"). We do not want to touch other
409 * bits */
410 for (uint8_t bit = 0; bit < 8; bit++)
411 {
412 if (io->second.at(bit) != "-")
413 {
414 writeRequired = true;
415 currCtrlVal.reset(bit);
416 currOutVal.reset(bit);
417 }
418 }
419
420 if (writeRequired)
421 {
422 int ret1 = i2c_smbus_write_byte_data(
423 file, static_cast<uint8_t>(confIORegAddr + i),
424 static_cast<uint8_t>(currCtrlVal.to_ulong()));
425 if (ret1 < 0)
426 {
427 std::cerr
428 << "IoExpander : \"" << name
429 << "\" - Error: Unable to write data to IO expander "
430 "IO control register\n";
431 return;
432 }
433
434 int ret2 = i2c_smbus_write_byte_data(
435 file, static_cast<uint8_t>(outCtrlBaseAddr + i),
436 static_cast<uint8_t>(currOutVal.to_ulong()));
437 if (ret2 < 0)
438 {
439 std::cerr
440 << "IoExpander : \"" << name
441 << "\" - Error: Unable to write data to IO expander "
442 "IO output register\n";
443 return;
444 }
445 }
446 }
447 initialized = true;
448 std::cerr << "IoExpander : \"" << name << "\" initialized\n";
449 }
450
451 public:
IoExpander(size_t busIn,size_t addressIn,size_t confIORegAddrIn,size_t outCtrlBaseAddrIn,size_t outCtrlByteCountIn,std::unordered_map<std::string,std::vector<std::string>> & ioMapIn,std::string & nameIn,std::string & typeIn)452 IoExpander(
453 size_t busIn, size_t addressIn, size_t confIORegAddrIn,
454 size_t outCtrlBaseAddrIn, size_t outCtrlByteCountIn,
455 std::unordered_map<std::string, std::vector<std::string>>& ioMapIn,
456 std::string& nameIn, std::string& typeIn) :
457 bus(busIn),
458 address(addressIn), confIORegAddr(confIORegAddrIn),
459 outCtrlBaseAddr(outCtrlBaseAddrIn),
460 outCtrlByteCount(outCtrlByteCountIn), ioMap(std::move(ioMapIn)),
461 name(std::move(nameIn)), type(std::move(typeIn))
462 {
463 initialize();
464 }
465
isInitialized()466 bool isInitialized()
467 {
468 if (!initialized)
469 {
470 /* There was an issue with the initialization of this component. Try
471 * to invoke initialization again */
472 initialize();
473 }
474 return initialized;
475 }
476
getName()477 std::string getName()
478 {
479 return name;
480 }
481
enableDisableOuput(std::forward_list<std::string> & nvmeDrivesInserted,std::forward_list<std::string> & nvmeDrivesRemoved)482 bool enableDisableOuput(std::forward_list<std::string>& nvmeDrivesInserted,
483 std::forward_list<std::string>& nvmeDrivesRemoved)
484 {
485 if (nvmeDrivesInserted.empty() && nvmeDrivesRemoved.empty())
486 {
487 /* There are no drives to update */
488 return true;
489 }
490
491 for (uint8_t i = 0; i < outCtrlByteCount; i++)
492 {
493 std::string ioName = "IO" + std::to_string(i);
494
495 auto io = ioMap.find(ioName);
496 if (io == ioMap.end())
497 {
498 std::cerr << "IoExpander : \"" << name
499 << "\" - IO map error ! Unable to find " << ioName
500 << "\n";
501 return false;
502 }
503
504 /* Get current value of IO output register */
505 int read = i2c_smbus_read_byte_data(
506 file, static_cast<uint8_t>(outCtrlBaseAddr + i));
507 if (read < 0)
508 {
509 std::cerr << "IoExpander : \"" << name
510 << "\" - Error: Unable to read data from io expander "
511 "register\n";
512 return false;
513 }
514
515 std::bitset<8> currVal(read);
516 bool writeRequired = false;
517
518 /* Set the bit if the NVMe drive is found in nvmeDrivesInserted, and
519 * reset the bit if found in nvmeDrivesRemoved */
520 for (uint8_t bit = 0; bit < 8; bit++)
521 {
522 /* The remove function returns number of elements removed from
523 * list indicating the presence of the drive and also removing
524 * it form the list */
525 if (nvmeDrivesInserted.remove(io->second.at(bit)))
526 {
527 writeRequired = true;
528 currVal.set(bit);
529 continue;
530 }
531
532 if (nvmeDrivesRemoved.remove(io->second.at(bit)))
533 {
534 writeRequired = true;
535 currVal.reset(bit);
536 }
537 }
538
539 if (!writeRequired)
540 {
541 /* No Write is required as there are no changes */
542 continue;
543 }
544
545 int ret = i2c_smbus_write_byte_data(
546 file, static_cast<uint8_t>(outCtrlBaseAddr + i),
547 static_cast<uint8_t>(currVal.to_ulong()));
548 if (ret < 0)
549 {
550 std::cerr << "IoExpander : \"" << name
551 << "\" - Error: Unable to write data to IO expander "
552 "register\n";
553 return false;
554 }
555 }
556
557 return true;
558 }
559
~IoExpander()560 ~IoExpander()
561 {
562 if (file >= 0)
563 {
564 close(file);
565 }
566 }
567 };
568 /***************************** End of Section *******************************/
569
570 /****************************************************************************/
571 /*********************** Global Variables Declarations **********************/
572 /****************************************************************************/
573 /* State os Application */
574 static AppState appState = AppState::idle;
575
576 /* Configuration and Components */
577 static HsbpConfig hsbpConfig;
578 std::forward_list<ClockBuffer> clockBuffers;
579 std::forward_list<IoExpander> ioExpanders;
580
581 /* Boost IO context and Dbus variables */
582 boost::asio::io_context io;
583 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
584 sdbusplus::asio::object_server objServer(conn);
585
586 /* GPIO Lines and GPIO Event Descriptors */
587 static gpiod::line nvmeLvc3AlertLine;
588 static boost::asio::posix::stream_descriptor nvmeLvc3AlertEvent(io);
589 /***************************** End of Section *******************************/
590
591 /****************************************************************************/
592 /********** HSBP Backplane related struct and Global definitions ************/
593 /****************************************************************************/
594 struct Mux
595 {
MuxMux596 Mux(size_t busIn, size_t addressIn, size_t channelsIn, size_t indexIn) :
597 bus(busIn), address(addressIn), channels(channelsIn), index(indexIn)
598 {
599 }
600 size_t bus;
601 size_t address;
602 size_t channels;
603 size_t index;
604
605 // to sort in the flat set
operator <Mux606 bool operator<(const Mux& rhs) const
607 {
608 return index < rhs.index;
609 }
610 };
611
612 struct Led : std::enable_shared_from_this<Led>
613 {
614 // led pattern addresses start at 0x10
LedLed615 Led(const std::string& path, size_t index, int fd) :
616 address(static_cast<uint8_t>(index + 0x10)), file(fd),
617 ledInterface(objServer.add_interface(path, ledGroup::interface))
618 {
619 if (index >= maxDrives)
620 {
621 throw std::runtime_error("Invalid drive index");
622 }
623
624 if (!set(BlinkPattern::off))
625 {
626 std::cerr << "Cannot initialize LED " << path << "\n";
627 }
628 }
629
630 // this has to be called outside the constructor for shared_from_this to
631 // work
createInterfaceLed632 void createInterface(void)
633 {
634 ledInterface->register_property(
635 ledGroup::asserted, false,
636 [weakRef{weak_from_this()}](const bool req, bool& val) {
637 auto self = weakRef.lock();
638 if (!self)
639 {
640 return 0;
641 }
642 if (req == val)
643 {
644 return 1;
645 }
646
647 if (!isPowerOn())
648 {
649 std::cerr << "Can't change blink state when power is off\n";
650 throw std::runtime_error(
651 "Can't change blink state when power is off");
652 }
653 BlinkPattern pattern =
654 req ? BlinkPattern::error : BlinkPattern::terminate;
655 if (!self->set(pattern))
656 {
657 std::cerr << "Can't change blink pattern\n";
658 throw std::runtime_error("Cannot set blink pattern");
659 }
660 val = req;
661 return 1;
662 });
663 ledInterface->initialize();
664 }
665
~LedLed666 virtual ~Led()
667 {
668 objServer.remove_interface(ledInterface);
669 }
670
setLed671 bool set(BlinkPattern pattern)
672 {
673 int ret = i2c_smbus_write_byte_data(file, address,
674 static_cast<uint8_t>(pattern));
675 return ret >= 0;
676 }
677
678 uint8_t address;
679 int file;
680 std::shared_ptr<sdbusplus::asio::dbus_interface> ledInterface;
681 };
682
683 struct Drive
684 {
DriveDrive685 Drive(std::string driveName, bool present, bool isOperational, bool nvme,
686 bool rebuilding) :
687 isNvme(nvme),
688 isPresent(present), name(driveName)
689 {
690 constexpr const char* basePath =
691 "/xyz/openbmc_project/inventory/item/drive/";
692 itemIface =
693 objServer.add_interface(basePath + driveName, inventory::interface);
694 itemIface->register_property("Present", isPresent);
695 itemIface->register_property("PrettyName", driveName);
696 itemIface->initialize();
697 operationalIface = objServer.add_interface(
698 itemIface->get_object_path(),
699 "xyz.openbmc_project.State.Decorator.OperationalStatus");
700
701 operationalIface->register_property(
702 "Functional", isOperational,
703 [this](const bool req, bool& property) {
704 if (!isPresent)
705 {
706 return 0;
707 }
708 if (property == req)
709 {
710 return 1;
711 }
712 property = req;
713 if (req)
714 {
715 clearFailed();
716 return 1;
717 }
718 markFailed();
719 return 1;
720 });
721
722 operationalIface->initialize();
723 rebuildingIface = objServer.add_interface(
724 itemIface->get_object_path(), "xyz.openbmc_project.State.Drive");
725 rebuildingIface->register_property("Rebuilding", rebuilding);
726 rebuildingIface->initialize();
727 driveIface =
728 objServer.add_interface(itemIface->get_object_path(),
729 "xyz.openbmc_project.Inventory.Item.Drive");
730 driveIface->initialize();
731 associations = objServer.add_interface(itemIface->get_object_path(),
732 association::interface);
733 associations->register_property("Associations",
734 std::vector<Association>{});
735 associations->initialize();
736
737 if (isPresent && (!isOperational || rebuilding))
738 {
739 markFailed();
740 }
741 }
~DriveDrive742 virtual ~Drive()
743 {
744 objServer.remove_interface(itemIface);
745 objServer.remove_interface(operationalIface);
746 objServer.remove_interface(rebuildingIface);
747 objServer.remove_interface(assetIface);
748 objServer.remove_interface(driveIface);
749 objServer.remove_interface(associations);
750 }
751
removeAssetDrive752 void removeAsset()
753 {
754 objServer.remove_interface(assetIface);
755 assetIface = nullptr;
756 }
757
createAssetDrive758 void createAsset(
759 const boost::container::flat_map<std::string, std::string>& data)
760 {
761 if (assetIface != nullptr)
762 {
763 return;
764 }
765 assetIface = objServer.add_interface(
766 itemIface->get_object_path(),
767 "xyz.openbmc_project.Inventory.Decorator.Asset");
768 for (const auto& [key, value] : data)
769 {
770 assetIface->register_property(key, value);
771 if (key == "SerialNumber")
772 {
773 serialNumber = value;
774 serialNumberInitialized = true;
775 }
776 }
777 assetIface->initialize();
778 }
779
markFailedDrive780 void markFailed(void)
781 {
782 // todo: maybe look this up via mapper
783 constexpr const char* globalInventoryPath =
784 "/xyz/openbmc_project/CallbackManager";
785
786 if (!isPresent)
787 {
788 return;
789 }
790
791 operationalIface->set_property("Functional", false);
792 std::vector<Association> warning = {
793 {"", "warning", globalInventoryPath}};
794 associations->set_property("Associations", warning);
795 logDriveError("Drive " + name);
796 }
797
clearFailedDrive798 void clearFailed(void)
799 {
800 operationalIface->set_property("Functional", true);
801 associations->set_property("Associations", std::vector<Association>{});
802 }
803
setPresentDrive804 void setPresent(bool set)
805 {
806 // nvme drives get detected by their fru
807 if (set == isPresent)
808 {
809 return;
810 }
811 itemIface->set_property("Present", set);
812 isPresent = set;
813 }
814
logPresentDrive815 void logPresent()
816 {
817 if (isNvme && !serialNumberInitialized)
818 {
819 // wait until NVMe asset is updated to include the serial number
820 // from the NVMe drive
821 return;
822 }
823
824 if (!isPresent && loggedPresent)
825 {
826 loggedPresent = false;
827 logDeviceRemoved("Drive", name, serialNumber);
828 serialNumber = "N/A";
829 serialNumberInitialized = false;
830 removeAsset();
831 }
832 else if (isPresent && !loggedPresent)
833 {
834 loggedPresent = true;
835 logDeviceAdded("Drive", name, serialNumber);
836 }
837 }
838
839 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
840 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
841 std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface;
842 std::shared_ptr<sdbusplus::asio::dbus_interface> assetIface;
843 std::shared_ptr<sdbusplus::asio::dbus_interface> driveIface;
844 std::shared_ptr<sdbusplus::asio::dbus_interface> associations;
845
846 bool isNvme;
847 bool isPresent;
848 std::string name;
849 std::string serialNumber = "N/A";
850 bool serialNumberInitialized = false;
851 bool loggedPresent = false;
852 };
853
854 struct Backplane : std::enable_shared_from_this<Backplane>
855 {
856
BackplaneBackplane857 Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn,
858 const std::string& nameIn) :
859 bus(busIn),
860 address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn),
861 timer(boost::asio::steady_timer(io)),
862 muxes(std::make_shared<boost::container::flat_set<Mux>>())
863 {
864 }
populateAssetBackplane865 void populateAsset(const std::string& rootPath, const std::string& busname)
866 {
867 conn->async_method_call(
868 [assetIface{assetInterface}](
869 const boost::system::error_code ec,
870 const boost::container::flat_map<
871 std::string, std::variant<std::string>>& values) mutable {
872 if (ec)
873 {
874 std::cerr
875 << "Error getting asset tag from HSBP configuration\n";
876
877 return;
878 }
879 for (const auto& [key, value] : values)
880 {
881 const std::string* ptr = std::get_if<std::string>(&value);
882 if (ptr == nullptr)
883 {
884 std::cerr << key << " Invalid type!\n";
885 continue;
886 }
887 assetIface->register_property(key, *ptr);
888 }
889 assetIface->initialize();
890 },
891 busname, rootPath, "org.freedesktop.DBus.Properties", "GetAll",
892 assetTag);
893 }
894
zeroPadBackplane895 static std::string zeroPad(const uint8_t val)
896 {
897 std::ostringstream version;
898 version << std::setw(2) << std::setfill('0')
899 << static_cast<size_t>(val);
900 return version.str();
901 }
902
runBackplane903 void run(const std::string& rootPath, const std::string& busname)
904 {
905 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(),
906 O_RDWR | O_CLOEXEC);
907 if (file < 0)
908 {
909 std::cerr << "unable to open bus " << bus << "\n";
910 return;
911 }
912
913 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
914 {
915 std::cerr << "unable to set address to " << address << "\n";
916 return;
917 }
918
919 if (!getPresent())
920 {
921 std::cerr << "Cannot detect CPLD\n";
922 return;
923 }
924
925 getBootVer(bootVer);
926 getFPGAVer(fpgaVer);
927 getSecurityRev(securityRev);
928 std::string dbusName = boost::replace_all_copy(name, " ", "_");
929 hsbpItemIface = objServer.add_interface(
930 "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName,
931 inventory::interface);
932 hsbpItemIface->register_property("Present", true);
933 hsbpItemIface->register_property("PrettyName", name);
934 hsbpItemIface->initialize();
935
936 storageInterface = objServer.add_interface(
937 hsbpItemIface->get_object_path(),
938 "xyz.openbmc_project.Inventory.Item.StorageController");
939 storageInterface->initialize();
940
941 assetInterface =
942 objServer.add_interface(hsbpItemIface->get_object_path(), assetTag);
943
944 versionIface =
945 objServer.add_interface("/xyz/openbmc_project/software/" + dbusName,
946 "xyz.openbmc_project.Software.Version");
947 versionIface->register_property("Version", zeroPad(bootVer) + "." +
948 zeroPad(fpgaVer) + "." +
949 zeroPad(securityRev));
950 versionIface->register_property(
951 "Purpose",
952 std::string(
953 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP"));
954 versionIface->initialize();
955
956 activationIface =
957 objServer.add_interface("/xyz/openbmc_project/software/" + dbusName,
958 "xyz.openbmc_project.Software.Activation");
959 activationIface->register_property(
960 "Activation",
961 std::string(
962 "xyz.openbmc_project.Software.Activation.Activations.Active"));
963 activationIface->register_property(
964 "RequestedActivation",
965 std::string("xyz.openbmc_project.Software.Activation."
966 "RequestedActivations.None"));
967 activationIface->initialize();
968
969 getPresence(presence);
970 getIFDET(ifdet);
971
972 populateAsset(rootPath, busname);
973
974 createDrives();
975
976 runTimer();
977 }
978
runTimerBackplane979 void runTimer()
980 {
981 timer.expires_after(std::chrono::seconds(scanRateSeconds));
982 timer.async_wait([weak{std::weak_ptr<Backplane>(shared_from_this())}](
983 boost::system::error_code ec) {
984 auto self = weak.lock();
985 if (!self)
986 {
987 return;
988 }
989 if (ec == boost::asio::error::operation_aborted)
990 {
991 // we're being destroyed
992 return;
993 }
994 else if (ec)
995 {
996 std::cerr << "timer error " << ec.message() << "\n";
997 return;
998 }
999
1000 if (!isPowerOn())
1001 {
1002 // can't access hsbp when power is off
1003 self->runTimer();
1004 return;
1005 }
1006
1007 self->getPresence(self->presence);
1008 self->getIFDET(self->ifdet);
1009 self->getFailed(self->failed);
1010 self->getRebuild(self->rebuilding);
1011
1012 self->updateDrives();
1013 self->runTimer();
1014 });
1015 }
1016
createDrivesBackplane1017 void createDrives()
1018 {
1019 for (size_t ii = 0; ii < maxDrives; ii++)
1020 {
1021 uint8_t driveSlot = (1 << ii);
1022 bool isNvme = ((ifdet & driveSlot) && !(presence & driveSlot));
1023 bool isPresent = isNvme || (presence & driveSlot);
1024 bool isFailed = !isPresent || failed & driveSlot;
1025 bool isRebuilding = !isPresent && (rebuilding & driveSlot);
1026
1027 // +1 to convert from 0 based to 1 based
1028 std::string driveName = boost::replace_all_copy(name, " ", "_") +
1029 "_Drive_" + std::to_string(ii + 1);
1030 Drive& drive = drives.emplace_back(driveName, isPresent, !isFailed,
1031 isNvme, isRebuilding);
1032 std::shared_ptr<Led> led = leds.emplace_back(std::make_shared<Led>(
1033 drive.itemIface->get_object_path(), ii, file));
1034 led->createInterface();
1035 }
1036 }
1037
updateDrivesBackplane1038 void updateDrives()
1039 {
1040 size_t ii = 0;
1041
1042 for (auto it = drives.begin(); it != drives.end(); it++, ii++)
1043 {
1044 uint8_t driveSlot = (1 << ii);
1045 bool isNvme = ((ifdet & driveSlot) && !(presence & driveSlot));
1046 bool isPresent = isNvme || (presence & driveSlot);
1047 bool isFailed = !isPresent || (failed & driveSlot);
1048 bool isRebuilding = isPresent && (rebuilding & driveSlot);
1049
1050 it->isNvme = isNvme;
1051 it->setPresent(isPresent);
1052 it->logPresent();
1053
1054 it->rebuildingIface->set_property("Rebuilding", isRebuilding);
1055 if (isFailed || isRebuilding)
1056 {
1057 it->markFailed();
1058 }
1059 else
1060 {
1061 it->clearFailed();
1062 }
1063 }
1064 }
1065
getPresentBackplane1066 bool getPresent()
1067 {
1068 present = i2c_smbus_read_byte(file) >= 0;
1069 return present;
1070 }
1071
getTypeIDBackplane1072 bool getTypeID(uint8_t& val)
1073 {
1074 constexpr uint8_t addr = 2;
1075 int ret = i2c_smbus_read_byte_data(file, addr);
1076 if (ret < 0)
1077 {
1078 std::cerr << "Error " << __FUNCTION__ << "\n";
1079 return false;
1080 }
1081 val = static_cast<uint8_t>(ret);
1082 return true;
1083 }
1084
getBootVerBackplane1085 bool getBootVer(uint8_t& val)
1086 {
1087 constexpr uint8_t addr = 3;
1088 int ret = i2c_smbus_read_byte_data(file, addr);
1089 if (ret < 0)
1090 {
1091 std::cerr << "Error " << __FUNCTION__ << "\n";
1092 return false;
1093 }
1094 val = static_cast<uint8_t>(ret);
1095 return true;
1096 }
1097
getFPGAVerBackplane1098 bool getFPGAVer(uint8_t& val)
1099 {
1100 constexpr uint8_t addr = 4;
1101 int ret = i2c_smbus_read_byte_data(file, addr);
1102 if (ret < 0)
1103 {
1104 std::cerr << "Error " << __FUNCTION__ << "\n";
1105 return false;
1106 }
1107 val = static_cast<uint8_t>(ret);
1108 return true;
1109 }
1110
getSecurityRevBackplane1111 bool getSecurityRev(uint8_t& val)
1112 {
1113 constexpr uint8_t addr = 5;
1114 int ret = i2c_smbus_read_byte_data(file, addr);
1115 if (ret < 0)
1116 {
1117 std::cerr << "Error " << __FUNCTION__ << "\n";
1118 return false;
1119 }
1120 val = static_cast<uint8_t>(ret);
1121 return true;
1122 }
1123
getPresenceBackplane1124 bool getPresence(uint8_t& val)
1125 {
1126 // NVMe drives do not assert PRSNTn, and as such do not get reported as
1127 // PRESENT in this register
1128
1129 constexpr uint8_t addr = 8;
1130
1131 int ret = i2c_smbus_read_byte_data(file, addr);
1132 if (ret < 0)
1133 {
1134 std::cerr << "Error " << __FUNCTION__ << "\n";
1135 return false;
1136 }
1137 // presence is inverted
1138 val = static_cast<uint8_t>(~ret);
1139 return true;
1140 }
1141
getIFDETBackplane1142 bool getIFDET(uint8_t& val)
1143 {
1144 // This register is a bitmap of parallel GPIO pins connected to the
1145 // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert
1146 // IFDETn low when they are inserted into the HSBP.This register, in
1147 // combination with the PRESENCE register, are used by the BMC to detect
1148 // the presence of NVMe drives.
1149
1150 constexpr uint8_t addr = 9;
1151
1152 int ret = i2c_smbus_read_byte_data(file, addr);
1153 if (ret < 0)
1154 {
1155 std::cerr << "Error " << __FUNCTION__ << "\n";
1156 return false;
1157 }
1158 // ifdet is inverted
1159 val = static_cast<uint8_t>(~ret);
1160 return true;
1161 }
1162
getFailedBackplane1163 bool getFailed(uint8_t& val)
1164 {
1165 constexpr uint8_t addr = 0xC;
1166 int ret = i2c_smbus_read_byte_data(file, addr);
1167 if (ret < 0)
1168 {
1169 std::cerr << "Error " << __FUNCTION__ << "\n";
1170 return false;
1171 }
1172 val = static_cast<uint8_t>(ret);
1173 return true;
1174 }
1175
getRebuildBackplane1176 bool getRebuild(uint8_t& val)
1177 {
1178 constexpr uint8_t addr = 0xD;
1179 int ret = i2c_smbus_read_byte_data(file, addr);
1180 if (ret < 0)
1181 {
1182 std::cerr << "Error " << __FUNCTION__ << " " << strerror(ret)
1183 << "\n";
1184 return false;
1185 }
1186 val = static_cast<uint8_t>(ret);
1187 return true;
1188 }
1189
getInsertedAndRemovedNvmeDrivesBackplane1190 bool getInsertedAndRemovedNvmeDrives(
1191 std::forward_list<std::string>& nvmeDrivesInserted,
1192 std::forward_list<std::string>& nvmeDrivesRemoved)
1193 {
1194 /* Get the current drives status */
1195 std::bitset<8> currDriveStatus;
1196 uint8_t nPresence;
1197 uint8_t nIfdet;
1198
1199 if (!getPresence(nPresence) || !getIFDET(nIfdet))
1200 {
1201 /* Error getting value. Return */
1202 std::cerr << "Backplane " << name
1203 << " failed to get drive status\n";
1204 return false;
1205 }
1206
1207 std::string dbusHsbpName = boost::replace_all_copy(name, " ", "_");
1208 auto nvmeMap = hsbpConfig.hsbpNvmeMap.find(dbusHsbpName);
1209 if (nvmeMap == hsbpConfig.hsbpNvmeMap.end())
1210 {
1211 std::cerr << "Couldn't get the NVMe Map for the backplane : "
1212 << name << "\n";
1213 return false;
1214 }
1215
1216 /* NVMe drives do not assert PRSNTn, and as such do not get reported in
1217 * "presence" register, but assert ifdet low. This implies for a NVMe
1218 * drive to be present, corresponding precense bit has to be 0 and idfet
1219 * has to be 1 (as the values of these regosters are negated: check
1220 * getPresence() and getIfdet() functions) */
1221 for (uint8_t bit = 0; bit < 8; bit++)
1222 {
1223 if ((nPresence & (1U << bit)) == 0)
1224 {
1225 if (nIfdet & (1U << bit))
1226 {
1227 currDriveStatus.set(bit);
1228 }
1229 }
1230 }
1231
1232 /* Determine Inserted and Removed Drives
1233 * Prev Bit | Curr Bit | Status
1234 * 0 | 0 | No Change
1235 * 0 | 1 | Inserted
1236 * 1 | 0 | Removed
1237 * 1 | 1 | No Change
1238 */
1239 for (uint8_t index = 0; index < 8; index++)
1240 {
1241 /* Inserted */
1242 if (!prevDriveStatus.test(index) && currDriveStatus.test(index))
1243 {
1244 nvmeDrivesInserted.emplace_front(nvmeMap->second.at(index));
1245 std::cerr << name << " : " << nvmeDrivesInserted.front()
1246 << " Inserted !\n";
1247 }
1248
1249 /* Removed */
1250 else if (prevDriveStatus.test(index) &&
1251 !currDriveStatus.test(index))
1252 {
1253 nvmeDrivesRemoved.emplace_front(nvmeMap->second.at(index));
1254 std::cerr << name << " : " << nvmeDrivesRemoved.front()
1255 << " Removed !\n";
1256 }
1257 }
1258
1259 prevDriveStatus = currDriveStatus;
1260 return true;
1261 }
1262
~BackplaneBackplane1263 virtual ~Backplane()
1264 {
1265 timer.cancel();
1266 objServer.remove_interface(hsbpItemIface);
1267 objServer.remove_interface(versionIface);
1268 objServer.remove_interface(storageInterface);
1269 objServer.remove_interface(assetInterface);
1270 objServer.remove_interface(activationIface);
1271 if (file >= 0)
1272 {
1273 close(file);
1274 }
1275 }
1276
1277 size_t bus;
1278 size_t address;
1279 size_t backplaneIndex;
1280 std::string name;
1281 boost::asio::steady_timer timer;
1282 bool present = false;
1283 uint8_t typeId = 0;
1284 uint8_t bootVer = 0;
1285 uint8_t fpgaVer = 0;
1286 uint8_t securityRev = 0;
1287 uint8_t funSupported = 0;
1288 uint8_t presence = 0;
1289 uint8_t ifdet = 0;
1290 uint8_t failed = 0;
1291 uint8_t rebuilding = 0;
1292 std::bitset<8> prevDriveStatus;
1293
1294 int file = -1;
1295
1296 std::string type;
1297
1298 std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface;
1299 std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
1300 std::shared_ptr<sdbusplus::asio::dbus_interface> storageInterface;
1301 std::shared_ptr<sdbusplus::asio::dbus_interface> assetInterface;
1302 std::shared_ptr<sdbusplus::asio::dbus_interface> activationIface;
1303 std::list<Drive> drives;
1304 std::vector<std::shared_ptr<Led>> leds;
1305 std::shared_ptr<boost::container::flat_set<Mux>> muxes;
1306 };
1307
1308 /* Global HSBP backplanes and NVMe drives collection */
1309 std::unordered_map<std::string, std::shared_ptr<Backplane>> backplanes;
1310 std::list<Drive> ownerlessDrives; // drives without a backplane
1311 /***************************** End of Section *******************************/
1312
1313 /****************************************************************************/
1314 /***************** Miscellaneous Class/Function Definitions *****************/
1315 /****************************************************************************/
1316 /* The purpose of this class is to sync the code flow. Often times there could
1317 * be multiple dbus calls which are async, and upon completely finishing all
1318 * Dbus calls, we need to call next function, or handle the error.
1319 * When an object of this class goes out of scope, the respective handlers
1320 * will be called */
1321 class AsyncCallbackHandler
1322 {
1323 bool errorOccurred = false;
1324 std::function<void()> onSuccess = nullptr;
1325 std::function<void()> onError = nullptr;
1326
1327 public:
AsyncCallbackHandler(std::function<void ()> onSuccessIn,std::function<void ()> onErrorIn)1328 explicit AsyncCallbackHandler(std::function<void()> onSuccessIn,
1329 std::function<void()> onErrorIn) :
1330 onSuccess(std::move(onSuccessIn)),
1331 onError(std::move(onErrorIn))
1332 {
1333 }
1334
setError()1335 void setError()
1336 {
1337 errorOccurred = true;
1338 }
1339
~AsyncCallbackHandler()1340 ~AsyncCallbackHandler()
1341 {
1342 /* If error occurred flag was set, execute the error handler */
1343 if (errorOccurred)
1344 {
1345 /* Check if Error Handler is defined */
1346 if (onError)
1347 {
1348 onError();
1349 }
1350
1351 return;
1352 }
1353
1354 /* If Success Handler is present, execute Success Handler */
1355 if (onSuccess)
1356 {
1357 onSuccess();
1358 }
1359 }
1360 };
1361
stopHsbpManager()1362 void stopHsbpManager()
1363 {
1364 std::cerr << __FUNCTION__ << ": Stopping hsbp-manager\n";
1365 appState = AppState::idle;
1366 hsbpConfig.clearConfig();
1367 clockBuffers.clear();
1368 ioExpanders.clear();
1369 backplanes.clear();
1370
1371 io.stop();
1372 }
1373 /***************************** End of Section *******************************/
1374
1375 /****************************************************************************/
1376 /********* HSBP clock enable/disable related Function Definitions ***********/
1377 /****************************************************************************/
updateHsbpClocks(std::forward_list<std::string> & nvmeDrivesInserted,std::forward_list<std::string> & nvmeDrivesRemoved)1378 void updateHsbpClocks(std::forward_list<std::string>& nvmeDrivesInserted,
1379 std::forward_list<std::string>& nvmeDrivesRemoved)
1380 {
1381 if (appState < AppState::backplanesLoaded)
1382 {
1383 std::cerr << "HSBP not initialized ! Cancelling Clock Update ! \n";
1384 return;
1385 }
1386
1387 std::cerr << "Updating HSBP drive clocks ...\n";
1388
1389 /* Loop through all clock buffers and try to update the clocks (this will be
1390 * done if the mode of operation of the clock buffer is SMBus) */
1391 for (auto& clockBuffer : clockBuffers)
1392 {
1393 if (!clockBuffer.enableDisableClock(nvmeDrivesInserted,
1394 nvmeDrivesRemoved))
1395 {
1396 std::cerr << "Error Occurred while setting the clock in \""
1397 << clockBuffer.getName() << "\"\n";
1398 }
1399 }
1400
1401 /* If there are drives yet to be updated, check all the IO Expanders in case
1402 * they are mapped to the drives and enable the respective IO */
1403 if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty())
1404 {
1405 for (auto& ioExpander : ioExpanders)
1406 {
1407 if (!ioExpander.enableDisableOuput(nvmeDrivesInserted,
1408 nvmeDrivesRemoved))
1409 {
1410 std::cerr << "Error Occurred while setting the IO in \""
1411 << ioExpander.getName() << "\"\n";
1412 }
1413 }
1414 }
1415
1416 /* If there are drives still left, then one or more drives clock
1417 * enable/diable failed. There is a possibility of improper mapping or
1418 * current communication with the device failed */
1419 if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty())
1420 {
1421 std::cerr << "Critical Error !!!\nMapping issue detected !\n";
1422
1423 if (!nvmeDrivesInserted.empty())
1424 {
1425 std::cerr << "The clock enable failed for : ";
1426 for (auto& nvme1 : nvmeDrivesInserted)
1427 {
1428 std::cerr << nvme1 << ", ";
1429 }
1430 std::cerr << "\n";
1431 }
1432
1433 if (!nvmeDrivesRemoved.empty())
1434 {
1435 std::cerr << "The clock disable failed for : ";
1436 for (auto& nvme1 : nvmeDrivesRemoved)
1437 {
1438 std::cerr << nvme1 << ", ";
1439 }
1440 std::cerr << "\n";
1441 }
1442 }
1443 }
1444
scanHsbpDrives(bool & hsbpDriveScanInProgress)1445 void scanHsbpDrives(bool& hsbpDriveScanInProgress)
1446 {
1447 std::cerr << __FUNCTION__ << ": Scanning HSBP drives status ...\n";
1448
1449 /* List variables to store the drives Inserted/Removed */
1450 std::forward_list<std::string> nvmeDrivesInserted;
1451 std::forward_list<std::string> nvmeDrivesRemoved;
1452
1453 /* Loop through each backplane present and get the list of inserted/removed
1454 * drives */
1455 for (auto& [name, backplane] : backplanes)
1456 {
1457 backplane->getInsertedAndRemovedNvmeDrives(nvmeDrivesInserted,
1458 nvmeDrivesRemoved);
1459 }
1460
1461 if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty())
1462 {
1463 updateHsbpClocks(nvmeDrivesInserted, nvmeDrivesRemoved);
1464 }
1465
1466 std::cerr << __FUNCTION__ << ": Scanning HSBP drives Completed\n";
1467
1468 hsbpDriveScanInProgress = false;
1469 }
1470
checkHsbpDrivesStatus()1471 void checkHsbpDrivesStatus()
1472 {
1473 static bool hsbpDriveScanInProgress = false;
1474 static bool hsbpDriveRescanInQueue = false;
1475
1476 if (appState < AppState::backplanesLoaded)
1477 {
1478 std::cerr << __FUNCTION__
1479 << ": HSBP not initialized ! Cancelling scan of HSBP drives "
1480 "status ! \n";
1481 return;
1482 }
1483
1484 if (hsbpDriveScanInProgress)
1485 {
1486 /* Scan and Clock Update already in progress. Try again after sometime.
1487 * This event can occur due to the GPIO interrupt */
1488 std::cerr << __FUNCTION__
1489 << ": HSBP Drives Scan is already in progress\n";
1490 if (hsbpDriveRescanInQueue)
1491 {
1492 /* There is already a Re-Scan in queue. No need to create multiple
1493 * rescans */
1494 return;
1495 }
1496
1497 hsbpDriveRescanInQueue = true;
1498
1499 std::cerr << __FUNCTION__ << ": Queuing the Scan \n";
1500
1501 auto driveScanTimer = std::make_shared<boost::asio::steady_timer>(io);
1502 driveScanTimer->expires_after(std::chrono::seconds(1));
1503 driveScanTimer->async_wait(
1504 [driveScanTimer](const boost::system::error_code ec) {
1505 if (ec == boost::asio::error::operation_aborted)
1506 {
1507 // Timer was Aborted
1508 return;
1509 }
1510 else if (ec)
1511 {
1512 std::cerr << "driveScanTimer: Timer error" << ec.message()
1513 << "\n";
1514 return;
1515 }
1516 hsbpDriveRescanInQueue = false;
1517 checkHsbpDrivesStatus();
1518 });
1519
1520 return;
1521 }
1522
1523 hsbpDriveScanInProgress = true;
1524
1525 /* Post the scan to IO queue and return from here. This enables capturing
1526 * next GPIO event if any */
1527 boost::asio::post(io, []() { scanHsbpDrives(hsbpDriveScanInProgress); });
1528 }
1529 /***************************** End of Section *******************************/
1530
1531 /****************************************************************************/
1532 /********** Backplanes and NVMe drives related Function Definitions *********/
1533 /****************************************************************************/
getDriveCount()1534 static size_t getDriveCount()
1535 {
1536 size_t count = 0;
1537 for (const auto& [key, backplane] : backplanes)
1538 {
1539 count += backplane->drives.size();
1540 }
1541 return count + ownerlessDrives.size();
1542 }
1543
updateAssets()1544 void updateAssets()
1545 {
1546 appState = AppState::loadingDrives;
1547
1548 /* Setup a callback to be called once the assets are populated completely or
1549 * fallback to error handler */
1550 auto drivesLoadedCallback = std::make_shared<AsyncCallbackHandler>(
1551 []() {
1552 appState = AppState::drivesLoaded;
1553 std::cerr << "Drives Updated !\n";
1554 },
1555 []() {
1556 // TODO: Handle this error if needed
1557 appState = AppState::backplanesLoaded;
1558 std::cerr << "An error occured ! Drives load failed \n";
1559 });
1560
1561 conn->async_method_call(
1562 [drivesLoadedCallback](const boost::system::error_code ec,
1563 const GetSubTreeType& subtree) {
1564 if (ec)
1565 {
1566 std::cerr << __FUNCTION__ << ": Error contacting mapper "
1567 << ec.message() << "\n";
1568 drivesLoadedCallback->setError();
1569 return;
1570 }
1571
1572 // drives may get an owner during this, or we might disover more
1573 // drives
1574 ownerlessDrives.clear();
1575 for (const auto& [path, objDict] : subtree)
1576 {
1577 if (objDict.empty())
1578 {
1579 continue;
1580 }
1581
1582 const std::string& owner = objDict.begin()->first;
1583 // we export this interface too
1584 if (owner == busName)
1585 {
1586 continue;
1587 }
1588 if (std::find(objDict.begin()->second.begin(),
1589 objDict.begin()->second.end(),
1590 assetTag) == objDict.begin()->second.end())
1591 {
1592 // no asset tag to associate to
1593 continue;
1594 }
1595
1596 conn->async_method_call(
1597 [path, drivesLoadedCallback](
1598 const boost::system::error_code ec2,
1599 const boost::container::flat_map<
1600 std::string, std::variant<uint64_t, std::string>>&
1601 values) {
1602 if (ec2)
1603 {
1604 std::cerr << __FUNCTION__
1605 << ": Error Getting Config "
1606 << ec2.message() << " "
1607 << "\n";
1608 drivesLoadedCallback->setError();
1609 return;
1610 }
1611 auto findBus = values.find("Bus");
1612
1613 if (findBus == values.end())
1614 {
1615 std::cerr << __FUNCTION__
1616 << ": Illegal interface at " << path
1617 << "\n";
1618 drivesLoadedCallback->setError();
1619 return;
1620 }
1621
1622 // find the mux bus and addr
1623 size_t muxBus = static_cast<size_t>(
1624 std::get<uint64_t>(findBus->second));
1625 std::filesystem::path muxPath =
1626 "/sys/bus/i2c/devices/i2c-" +
1627 std::to_string(muxBus) + "/mux_device";
1628 if (!std::filesystem::is_symlink(muxPath))
1629 {
1630 std::cerr << path << " mux does not exist\n";
1631 drivesLoadedCallback->setError();
1632 return;
1633 }
1634
1635 // we should be getting something of the form 7-0052
1636 // for bus 7 addr 52
1637 std::string fname =
1638 std::filesystem::read_symlink(muxPath).filename();
1639 auto findDash = fname.find('-');
1640
1641 if (findDash == std::string::npos ||
1642 findDash + 1 >= fname.size())
1643 {
1644 std::cerr << path << " mux path invalid\n";
1645 drivesLoadedCallback->setError();
1646 return;
1647 }
1648
1649 std::string busStr = fname.substr(0, findDash);
1650 std::string muxStr = fname.substr(findDash + 1);
1651
1652 size_t bus = static_cast<size_t>(std::stoi(busStr));
1653 size_t addr =
1654 static_cast<size_t>(std::stoi(muxStr, nullptr, 16));
1655 size_t muxIndex = 0;
1656
1657 // find the channel of the mux the drive is on
1658 std::ifstream nameFile("/sys/bus/i2c/devices/i2c-" +
1659 std::to_string(muxBus) +
1660 "/name");
1661 if (!nameFile)
1662 {
1663 std::cerr << __FUNCTION__
1664 << ": Unable to open name file of bus "
1665 << muxBus << "\n";
1666 drivesLoadedCallback->setError();
1667 return;
1668 }
1669
1670 std::string nameStr;
1671 std::getline(nameFile, nameStr);
1672
1673 // file is of the form "i2c-4-mux (chan_id 1)", get chan
1674 // assume single digit chan
1675 const std::string prefix = "chan_id ";
1676 size_t findId = nameStr.find(prefix);
1677 if (findId == std::string::npos ||
1678 findId + 1 >= nameStr.size())
1679 {
1680 std::cerr << __FUNCTION__
1681 << ": Illegal name file on bus " << muxBus
1682 << "\n";
1683 }
1684
1685 std::string indexStr =
1686 nameStr.substr(findId + prefix.size(), 1);
1687
1688 size_t driveIndex = std::stoi(indexStr);
1689
1690 boost::container::flat_map<std::string, std::string>
1691 assetInventory;
1692 const std::array<const char*, 4> assetKeys = {
1693 "PartNumber", "SerialNumber", "Manufacturer",
1694 "Model"};
1695 for (const auto& [key, value] : values)
1696 {
1697 if (std::find(assetKeys.begin(), assetKeys.end(),
1698 key) == assetKeys.end())
1699 {
1700 continue;
1701 }
1702 if (std::holds_alternative<std::string>(value))
1703 {
1704 assetInventory[key] =
1705 std::get<std::string>(value);
1706 }
1707 }
1708
1709 Backplane* parent = nullptr;
1710 for (auto& [name, backplane] : backplanes)
1711 {
1712 muxIndex = 0;
1713 for (const Mux& mux : *(backplane->muxes))
1714 {
1715 if (bus == mux.bus && addr == mux.address)
1716 {
1717 parent = backplane.get();
1718 break;
1719 }
1720 muxIndex += mux.channels;
1721 }
1722 if (parent)
1723 {
1724 /* Found the backplane. No need to proceed
1725 * further */
1726 break;
1727 }
1728 }
1729
1730 // assume its a M.2 or something without a hsbp
1731 if (parent == nullptr)
1732 {
1733 std::string driveName =
1734 "Drive_" + std::to_string(getDriveCount() + 1);
1735 auto& drive = ownerlessDrives.emplace_back(
1736 driveName, true, true, true, false);
1737 drive.createAsset(assetInventory);
1738 return;
1739 }
1740
1741 driveIndex += muxIndex;
1742
1743 if (parent->drives.size() <= driveIndex)
1744 {
1745 std::cerr << __FUNCTION__
1746 << ": Illegal drive index at " << path
1747 << " " << driveIndex << "\n";
1748 drivesLoadedCallback->setError();
1749 return;
1750 }
1751 auto it = parent->drives.begin();
1752 std::advance(it, driveIndex);
1753
1754 it->createAsset(assetInventory);
1755 },
1756 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1757 "" /*all interface items*/);
1758 }
1759 },
1760 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
1761 0, std::array<const char*, 1>{nvmeIntf});
1762 }
1763
populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes,std::string & rootPath)1764 void populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes,
1765 std::string& rootPath)
1766 {
1767 const static std::array<const std::string, 4> muxTypes = {
1768 "xyz.openbmc_project.Configuration.PCA9543Mux",
1769 "xyz.openbmc_project.Configuration.PCA9544Mux",
1770 "xyz.openbmc_project.Configuration.PCA9545Mux",
1771 "xyz.openbmc_project.Configuration.PCA9546Mux"};
1772
1773 conn->async_method_call(
1774 [muxes](const boost::system::error_code ec,
1775 const GetSubTreeType& subtree) {
1776 if (ec)
1777 {
1778 std::cerr << __FUNCTION__ << ": Error contacting mapper "
1779 << ec.message() << "\n";
1780 return;
1781 }
1782 size_t index = 0; // as we use a flat map, these are sorted
1783 for (const auto& [path, objDict] : subtree)
1784 {
1785 if (objDict.empty() || objDict.begin()->second.empty())
1786 {
1787 continue;
1788 }
1789
1790 const std::string& owner = objDict.begin()->first;
1791 const std::vector<std::string>& interfaces =
1792 objDict.begin()->second;
1793
1794 const std::string* interface = nullptr;
1795 for (const std::string& iface : interfaces)
1796 {
1797 if (std::find(muxTypes.begin(), muxTypes.end(), iface) !=
1798 muxTypes.end())
1799 {
1800 interface = &iface;
1801 break;
1802 }
1803 }
1804
1805 if (interface == nullptr)
1806 {
1807 std::cerr << __FUNCTION__ << ": Cannot get mux type\n";
1808 continue;
1809 }
1810
1811 conn->async_method_call(
1812 [path, muxes, index](
1813 const boost::system::error_code ec2,
1814 const boost::container::flat_map<
1815 std::string,
1816 std::variant<uint64_t, std::vector<std::string>>>&
1817 values) {
1818 if (ec2)
1819 {
1820 std::cerr << __FUNCTION__
1821 << ": Error Getting Config "
1822 << ec2.message() << "\n";
1823 return;
1824 }
1825 auto findBus = values.find("Bus");
1826 auto findAddress = values.find("Address");
1827 auto findChannelNames = values.find("ChannelNames");
1828 if (findBus == values.end() ||
1829 findAddress == values.end())
1830 {
1831 std::cerr << __FUNCTION__
1832 << ": Illegal configuration at " << path
1833 << "\n";
1834 return;
1835 }
1836 size_t bus = static_cast<size_t>(
1837 std::get<uint64_t>(findBus->second));
1838 size_t address = static_cast<size_t>(
1839 std::get<uint64_t>(findAddress->second));
1840 std::vector<std::string> channels =
1841 std::get<std::vector<std::string>>(
1842 findChannelNames->second);
1843 muxes->emplace(bus, address, channels.size(), index);
1844 },
1845 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1846 *interface);
1847 index++;
1848 }
1849 },
1850 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
1851 rootPath, 1, muxTypes);
1852 }
1853
populateHsbpBackplanes(const std::shared_ptr<AsyncCallbackHandler> & backplanesLoadedCallback)1854 void populateHsbpBackplanes(
1855 const std::shared_ptr<AsyncCallbackHandler>& backplanesLoadedCallback)
1856 {
1857 std::cerr << __FUNCTION__ << ": Scanning Backplanes ...\n";
1858 appState = AppState::loadingBackplanes;
1859 backplanes.clear();
1860
1861 conn->async_method_call(
1862 [backplanesLoadedCallback](const boost::system::error_code ec,
1863 const GetSubTreeType& subtree) {
1864 if (ec)
1865 {
1866 std::cerr << __FUNCTION__ << ": Error contacting mapper "
1867 << ec.message() << "\n";
1868 backplanesLoadedCallback->setError();
1869 return;
1870 }
1871
1872 if (subtree.empty())
1873 {
1874 /* There wer no HSBP's detected. set teh state back to
1875 * componentsLoaded so that on backplane match event, the
1876 * process can start again */
1877 appState = AppState::componentsLoaded;
1878 std::cerr << __FUNCTION__ << ": No HSBPs Detected....\n";
1879 return;
1880 }
1881
1882 for (const auto& [path, objDict] : subtree)
1883 {
1884 if (objDict.empty())
1885 {
1886 std::cerr << __FUNCTION__
1887 << ": Subtree data "
1888 "corrupted !\n";
1889 backplanesLoadedCallback->setError();
1890 return;
1891 }
1892
1893 const std::string& owner = objDict.begin()->first;
1894 conn->async_method_call(
1895 [backplanesLoadedCallback, path,
1896 owner](const boost::system::error_code ec2,
1897 const boost::container::flat_map<
1898 std::string, BasicVariantType>& resp) {
1899 if (ec2)
1900 {
1901 std::cerr << __FUNCTION__
1902 << ": Error Getting Config "
1903 << ec2.message() << "\n";
1904 backplanesLoadedCallback->setError();
1905 return;
1906 }
1907 std::optional<size_t> bus;
1908 std::optional<size_t> address;
1909 std::optional<size_t> backplaneIndex;
1910 std::optional<std::string> name;
1911 for (const auto& [key, value] : resp)
1912 {
1913 if (key == "Bus")
1914 {
1915 bus = std::get<uint64_t>(value);
1916 }
1917 else if (key == "Address")
1918 {
1919 address = std::get<uint64_t>(value);
1920 }
1921 else if (key == "Index")
1922 {
1923 backplaneIndex = std::get<uint64_t>(value);
1924 }
1925 else if (key == "Name")
1926 {
1927 name = std::get<std::string>(value);
1928 }
1929 }
1930 if (!bus || !address || !name || !backplaneIndex)
1931 {
1932 std::cerr << __FUNCTION__
1933 << ": Illegal configuration at " << path
1934 << "\n";
1935 backplanesLoadedCallback->setError();
1936 return;
1937 }
1938 std::string parentPath =
1939 std::filesystem::path(path).parent_path();
1940 const auto& [backplane, status] = backplanes.emplace(
1941 *name, std::make_shared<Backplane>(
1942 *bus, *address, *backplaneIndex, *name));
1943 backplane->second->run(parentPath, owner);
1944 populateMuxes(backplane->second->muxes, parentPath);
1945 },
1946 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1947 hsbpCpldInft);
1948 }
1949 },
1950 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
1951 0, std::array<const char*, 1>{hsbpCpldInft});
1952 }
1953
setUpBackplanesAndDrives()1954 void setUpBackplanesAndDrives()
1955 {
1956 static bool backplanesScanInProgress = false;
1957 static bool backplanesRescanInQueue = false;
1958
1959 if (appState < AppState::componentsLoaded)
1960 {
1961 std::cerr << __FUNCTION__
1962 << ": Components are not initialized ! Cancelling scan of "
1963 "Backplanes ! \n";
1964 return;
1965 }
1966
1967 if (backplanesScanInProgress)
1968 {
1969 std::cerr << __FUNCTION__
1970 << ": Backplanes Scan is already in progress\n";
1971 if (backplanesRescanInQueue)
1972 {
1973 /* There is already a Re-Scan in queue. No need to create multiple
1974 * rescans */
1975 return;
1976 }
1977
1978 backplanesRescanInQueue = true;
1979
1980 std::cerr << __FUNCTION__ << ": Queuing the Backplane Scan \n";
1981
1982 auto backplaneScanTimer =
1983 std::make_shared<boost::asio::steady_timer>(io);
1984 backplaneScanTimer->expires_after(std::chrono::seconds(1));
1985 backplaneScanTimer->async_wait(
1986 [backplaneScanTimer](const boost::system::error_code ec) {
1987 if (ec == boost::asio::error::operation_aborted)
1988 {
1989 // Timer was Aborted
1990 return;
1991 }
1992 else if (ec)
1993 {
1994 std::cerr << "backplaneScanTimer: Timer error"
1995 << ec.message() << "\n";
1996 return;
1997 }
1998 backplanesRescanInQueue = false;
1999 setUpBackplanesAndDrives();
2000 });
2001
2002 return;
2003 }
2004
2005 backplanesScanInProgress = true;
2006
2007 /* Set Callback to be called once backplanes are populated to call
2008 * updateAssets() and checkHsbpDrivesStatus() or handle error scnenario */
2009 auto backplanesLoadedCallback = std::make_shared<AsyncCallbackHandler>(
2010 []() {
2011 /* If no HSBP's were detected, the state changes to
2012 * componentsLoaded. Proceed further only if state was
2013 * loadingBackplanes */
2014 if (appState != AppState::loadingBackplanes)
2015 {
2016 backplanesScanInProgress = false;
2017 return;
2018 }
2019
2020 /* If there is a ReScan in the Queue, dont proceed further. Load the
2021 * Backplanes again and then proceed further */
2022 if (backplanesRescanInQueue)
2023 {
2024 backplanesScanInProgress = false;
2025 return;
2026 }
2027
2028 appState = AppState::backplanesLoaded;
2029 std::cerr << __FUNCTION__ << ": Backplanes Loaded...\n";
2030
2031 checkHsbpDrivesStatus();
2032 updateAssets();
2033 backplanesScanInProgress = false;
2034 },
2035 []() {
2036 /* Loading Backplanes is an important step. If the load failed due
2037 * to an error, stop the app so that restart cant be triggerred */
2038 std::cerr << "Backplanes couldn't be loaded due to an error !...\n";
2039 appState = AppState::idle;
2040 backplanesScanInProgress = false;
2041 stopHsbpManager();
2042 });
2043
2044 populateHsbpBackplanes(backplanesLoadedCallback);
2045 }
2046
setupBackplanesAndDrivesMatch()2047 void setupBackplanesAndDrivesMatch()
2048 {
2049 static auto backplaneMatch = std::make_unique<sdbusplus::bus::match_t>(
2050 *conn,
2051 "sender='xyz.openbmc_project.EntityManager', type='signal', "
2052 "member='PropertiesChanged', "
2053 "interface='org.freedesktop.DBus.Properties', "
2054 "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" +
2055 std::string(hsbpCpldInft) + "'",
2056 [](sdbusplus::message_t& msg) {
2057 std::string intfName;
2058 boost::container::flat_map<std::string, BasicVariantType> values;
2059 msg.read(intfName, values);
2060
2061 /* This match will be triggered for each of the property being set
2062 * under the hsbpCpldInft interface. Call the loader only on one
2063 * property say "name". This will avoid multiple calls to populate
2064 * function
2065 */
2066 for (const auto& [key, value] : values)
2067 {
2068 if (key == "Name")
2069 {
2070 /* This match will be triggered when ever there is a
2071 * addition/removal of HSBP backplane. At this stage, all
2072 * the HSBP's need to be populated again and also assets
2073 * have to be re-discovered. So, setting state to
2074 * componentsLoaded and calling setUpBackplanesAndDrives()
2075 * only if configuration and components loading was
2076 * completed */
2077 if (appState < AppState::componentsLoaded)
2078 {
2079 /* Configuration is not loaded yet. Backplanes will be
2080 * loaded
2081 * once configuration and components are loaded. */
2082 std::cerr << __FUNCTION__
2083 << ": Discarding Backplane match\n";
2084 return;
2085 }
2086
2087 appState = AppState::componentsLoaded;
2088
2089 /* We will call the function after a small delay to let all
2090 * the properties to be intialized */
2091 auto backplaneTimer =
2092 std::make_shared<boost::asio::steady_timer>(io);
2093 backplaneTimer->expires_after(std::chrono::seconds(2));
2094 backplaneTimer->async_wait(
2095 [backplaneTimer](const boost::system::error_code ec) {
2096 if (ec == boost::asio::error::operation_aborted)
2097 {
2098 return;
2099 }
2100 else if (ec)
2101 {
2102 std::cerr << "backplaneTimer: Timer error"
2103 << ec.message() << "\n";
2104 return;
2105 }
2106 setUpBackplanesAndDrives();
2107 });
2108 }
2109 }
2110 });
2111
2112 static auto drivesMatch = std::make_unique<sdbusplus::bus::match_t>(
2113 *conn,
2114 "sender='xyz.openbmc_project.EntityManager', type='signal', "
2115 "member='PropertiesChanged', "
2116 "interface='org.freedesktop.DBus.Properties', arg0='" +
2117 std::string(nvmeIntf) + "'",
2118 [](sdbusplus::message_t& msg) {
2119 std::string intfName;
2120 boost::container::flat_map<std::string, BasicVariantType> values;
2121 msg.read(intfName, values);
2122
2123 /* This match will be triggered for each of the property being set
2124 * under the nvmeIntf interface. Call the loader only on one
2125 * property say "name". This will avoid multiple calls to populate
2126 * function
2127 */
2128 for (const auto& [key, value] : values)
2129 {
2130 if (key == "Name")
2131 {
2132 /* This match will be triggered when ever there is a
2133 * addition/removal of drives. At this stage only assets
2134 * have to be re-discovered. So, setting state to
2135 * backplanesLoaded and calling updateAssets() only if all
2136 * previous states are completed */
2137 if (appState < AppState::backplanesLoaded)
2138 {
2139 /* Configuration is not loaded yet. Drives will be
2140 * loaded once
2141 * configuration, components and backplanes are loaded.
2142 */
2143 std::cerr << __FUNCTION__
2144 << ": Discarding Drive match\n";
2145 return;
2146 }
2147
2148 appState = AppState::backplanesLoaded;
2149
2150 /* We will call the function after a small delay to let all
2151 * the properties to be intialized */
2152 auto driveTimer =
2153 std::make_shared<boost::asio::steady_timer>(io);
2154 driveTimer->expires_after(std::chrono::seconds(2));
2155 driveTimer->async_wait(
2156 [driveTimer](const boost::system::error_code ec) {
2157 if (ec == boost::asio::error::operation_aborted)
2158 {
2159 return;
2160 }
2161 else if (ec)
2162 {
2163 std::cerr << "driveTimer: Timer error"
2164 << ec.message() << "\n";
2165 return;
2166 }
2167 updateAssets();
2168 });
2169 }
2170 }
2171 });
2172 }
2173 /***************************** End of Section *******************************/
2174
2175 /****************************************************************************/
2176 /******************* Components related Function Definitions ****************/
2177 /****************************************************************************/
verifyComponentsLoaded()2178 bool verifyComponentsLoaded()
2179 {
2180 std::cerr << __FUNCTION__ << ": Verifying all Components...\n";
2181
2182 /* Loop through all clock buffers */
2183 for (auto& clockBuffer : clockBuffers)
2184 {
2185 if (!clockBuffer.isInitialized())
2186 {
2187 std::cerr << "Critical Error: Initializing \""
2188 << clockBuffer.getName() << "\" failed\n";
2189 return false;
2190 }
2191 }
2192
2193 /* Loop through all IO Expanders */
2194 for (auto& ioExpander : ioExpanders)
2195 {
2196 if (!ioExpander.isInitialized())
2197 {
2198 std::cerr << "Critical Error: Initializing \""
2199 << ioExpander.getName() << "\" failed\n";
2200 return false;
2201 }
2202 }
2203
2204 std::cerr << __FUNCTION__ << ": Verifying Components Complete\n";
2205
2206 return true;
2207 }
2208 /***************************** End of Section *******************************/
2209
2210 /****************************************************************************/
2211 /****************** IO expander related Function Definitions ****************/
2212 /****************************************************************************/
loadIoExpanderInfo(const std::shared_ptr<AsyncCallbackHandler> & componentsLoadedCallback)2213 void loadIoExpanderInfo(
2214 const std::shared_ptr<AsyncCallbackHandler>& componentsLoadedCallback)
2215 {
2216 appState = AppState::loadingComponents;
2217
2218 /* Clear global ioExpanders to start off */
2219 ioExpanders.clear();
2220
2221 conn->async_method_call(
2222 [componentsLoadedCallback](const boost::system::error_code ec,
2223 const GetSubTreeType& subtree) {
2224 if (ec)
2225 {
2226 std::cerr << __FUNCTION__ << ": Error contacting mapper "
2227 << ec.message() << "\n";
2228 componentsLoadedCallback->setError();
2229 return;
2230 }
2231
2232 for (auto& [path, objDict] : subtree)
2233 {
2234
2235 if (objDict.empty())
2236 {
2237 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n";
2238 componentsLoadedCallback->setError();
2239 return;
2240 }
2241
2242 /* Ideally there would be only one element in objDict as only
2243 * one service exposes it and there would be only one interface
2244 * so it is safe to directly read them without loop */
2245 const std::string& service = objDict.begin()->first;
2246 const std::string& intf = objDict.begin()->second.front();
2247
2248 conn->async_method_call(
2249 [componentsLoadedCallback](
2250 const boost::system::error_code er,
2251 const boost::container::flat_map<
2252 std::string, BasicVariantType>& resp) {
2253 if (er)
2254 {
2255 std::cerr << __FUNCTION__
2256 << ": Error Getting "
2257 "Config "
2258 << er.message() << "\n";
2259 componentsLoadedCallback->setError();
2260 return;
2261 }
2262
2263 std::optional<uint64_t> bus;
2264 std::optional<uint64_t> address;
2265 std::optional<uint64_t> confIORegAddr;
2266 std::optional<uint64_t> outCtrlBaseAddr;
2267 std::optional<uint64_t> outCtrlByteCount;
2268 std::unordered_map<std::string,
2269 std::vector<std::string>>
2270 ioMap;
2271 std::optional<std::string> name;
2272 std::optional<std::string> type;
2273
2274 /* Loop through to get all IO Expander properties */
2275 for (const auto& [key, value] : resp)
2276 {
2277 if (key == "Bus")
2278 {
2279 bus = std::get<uint64_t>(value);
2280 }
2281 else if (key == "Address")
2282 {
2283 address = std::get<uint64_t>(value);
2284 }
2285 else if (key == "ConfIORegAddr")
2286 {
2287 confIORegAddr = std::get<uint64_t>(value);
2288 }
2289 else if (key == "OutCtrlBaseAddr")
2290 {
2291 outCtrlBaseAddr = std::get<uint64_t>(value);
2292 }
2293 else if (key == "OutCtrlByteCount")
2294 {
2295 outCtrlByteCount = std::get<uint64_t>(value);
2296 }
2297 else if (key == "Name")
2298 {
2299 name = std::get<std::string>(value);
2300 }
2301 else if (key == "Type")
2302 {
2303 type = std::get<std::string>(value);
2304 }
2305 else if (key.starts_with("IO"))
2306 {
2307 std::optional<std::vector<std::string>> outList;
2308 outList = std::get<NvmeMapping>(value);
2309 if (!outList)
2310 {
2311 break;
2312 }
2313 ioMap.try_emplace(key, *outList);
2314 }
2315 }
2316
2317 /* Verify if all properties were defined */
2318 if (!bus || !address || !confIORegAddr ||
2319 !outCtrlBaseAddr || !outCtrlByteCount || !name)
2320 {
2321 std::cerr << __FUNCTION__
2322 << ": Incomplete "
2323 "Clock Buffer definition !! \n";
2324 componentsLoadedCallback->setError();
2325 return;
2326 }
2327
2328 /* Check if we were able to get byteMap correctly */
2329 if ((*outCtrlByteCount) != ioMap.size())
2330 {
2331 std::cerr << "loadIoExpanderInfo(): Incomplete "
2332 "IO Map !! \n";
2333 componentsLoadedCallback->setError();
2334 return;
2335 }
2336
2337 /* Create IO expander object and add it to global
2338 * ioExpanders vector */
2339 ioExpanders.emplace_front(
2340 *bus, *address, *confIORegAddr, *outCtrlBaseAddr,
2341 *outCtrlByteCount, ioMap, *name, *type);
2342 },
2343 service, path, "org.freedesktop.DBus.Properties", "GetAll",
2344 intf);
2345 }
2346 },
2347 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
2348 0, hsbpConfig.ioExpanderTypes);
2349 }
2350 /***************************** End of Section *******************************/
2351
2352 /****************************************************************************/
2353 /***************** Clock buffer related Function Definitions ****************/
2354 /****************************************************************************/
loadClockBufferInfo(const std::shared_ptr<AsyncCallbackHandler> & componentsLoadedCallback)2355 void loadClockBufferInfo(
2356 const std::shared_ptr<AsyncCallbackHandler>& componentsLoadedCallback)
2357 {
2358 appState = AppState::loadingComponents;
2359
2360 /* Clear global clockBuffers to start off */
2361 clockBuffers.clear();
2362
2363 conn->async_method_call(
2364 [componentsLoadedCallback](const boost::system::error_code ec,
2365 const GetSubTreeType& subtree) {
2366 if (ec)
2367 {
2368 std::cerr << __FUNCTION__ << ": Error contacting mapper "
2369 << ec.message() << "\n";
2370 componentsLoadedCallback->setError();
2371 return;
2372 }
2373
2374 for (auto& [path, objDict] : subtree)
2375 {
2376
2377 if (objDict.empty())
2378 {
2379 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n";
2380 componentsLoadedCallback->setError();
2381 return;
2382 }
2383
2384 /* Ideally there would be only one element in objDict as only
2385 * one service exposes it and there would be only one interface
2386 * so it is safe to directly read them without loop */
2387 const std::string& service = objDict.begin()->first;
2388 const std::string& intf = objDict.begin()->second.front();
2389
2390 conn->async_method_call(
2391 [componentsLoadedCallback](
2392 const boost::system::error_code er,
2393 const boost::container::flat_map<
2394 std::string, BasicVariantType>& resp) {
2395 if (er)
2396 {
2397 std::cerr << __FUNCTION__
2398 << ": Error Getting "
2399 "Config "
2400 << er.message() << "\n";
2401 componentsLoadedCallback->setError();
2402 return;
2403 }
2404
2405 std::optional<uint64_t> bus;
2406 std::optional<uint64_t> address;
2407 std::optional<std::string> mode;
2408 std::optional<uint64_t> outCtrlBaseAddr;
2409 std::optional<uint64_t> outCtrlByteCount;
2410 std::unordered_map<std::string,
2411 std::vector<std::string>>
2412 byteMap;
2413 std::optional<std::string> name;
2414 std::optional<std::string> type;
2415
2416 /* Loop through to get all Clock Buffer properties */
2417 for (const auto& [key, value] : resp)
2418 {
2419 if (key == "Bus")
2420 {
2421 bus = std::get<uint64_t>(value);
2422 }
2423 else if (key == "Address")
2424 {
2425 address = std::get<uint64_t>(value);
2426 }
2427 else if (key == "Mode")
2428 {
2429 mode = std::get<std::string>(value);
2430 }
2431 else if (key == "OutCtrlBaseAddr")
2432 {
2433 outCtrlBaseAddr = std::get<uint64_t>(value);
2434 }
2435 else if (key == "OutCtrlByteCount")
2436 {
2437 outCtrlByteCount = std::get<uint64_t>(value);
2438 }
2439 else if (key == "Name")
2440 {
2441 name = std::get<std::string>(value);
2442 }
2443 else if (key == "Type")
2444 {
2445 type = std::get<std::string>(value);
2446 }
2447 else if (key.starts_with("Byte"))
2448 {
2449 std::optional<std::vector<std::string>>
2450 byteList;
2451 byteList = std::get<NvmeMapping>(value);
2452 if (!byteList)
2453 {
2454 break;
2455 }
2456 byteMap.try_emplace(key, *byteList);
2457 }
2458 }
2459
2460 /* Verify if all properties were defined */
2461 if (!bus || !address || !mode || !outCtrlBaseAddr ||
2462 !outCtrlByteCount || !name)
2463 {
2464 std::cerr << __FUNCTION__
2465 << ": Incomplete "
2466 "Clock Buffer definition !! \n";
2467 componentsLoadedCallback->setError();
2468 return;
2469 }
2470
2471 /* Check if we were able to get byteMap correctly */
2472 if ((*outCtrlByteCount) != byteMap.size())
2473 {
2474 std::cerr << __FUNCTION__
2475 << ": Incomplete "
2476 "Byte Map !! \n";
2477 componentsLoadedCallback->setError();
2478 return;
2479 }
2480
2481 /* Create clock buffer object and add it to global
2482 * clockBuffers vector */
2483 clockBuffers.emplace_front(
2484 *bus, *address, *mode, *outCtrlBaseAddr,
2485 *outCtrlByteCount, byteMap, *name, *type);
2486 },
2487 service, path, "org.freedesktop.DBus.Properties", "GetAll",
2488 intf);
2489 }
2490 },
2491 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
2492 0, hsbpConfig.clockBufferTypes);
2493 }
2494 /***************************** End of Section *******************************/
2495
2496 /****************************************************************************/
2497 /***************** HSBP Config related Function Definitions *****************/
2498 /****************************************************************************/
loadHsbpConfig()2499 void loadHsbpConfig()
2500 {
2501 appState = AppState::loadingHsbpConfig;
2502
2503 conn->async_method_call(
2504 [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
2505 if (ec)
2506 {
2507 std::cerr << __FUNCTION__ << ": Error contacting mapper "
2508 << ec.message() << "\n";
2509 return;
2510 }
2511
2512 if (subtree.empty())
2513 {
2514 /* Entity manager is either still loading the configuration or
2515 * failed to load. In either way, return from here as the dbus
2516 * match will take care */
2517 std::cerr << __FUNCTION__ << ": No configuration detected !!\n";
2518 return;
2519 }
2520
2521 /* There should be only one HSBP Configureation exposed */
2522 if (subtree.size() != 1)
2523 {
2524 std::cerr << __FUNCTION__
2525 << ": Multiple configurations "
2526 "detected !!\n";
2527 /* Critical Error. Stop Application */
2528 stopHsbpManager();
2529 return;
2530 }
2531
2532 auto& path = subtree.begin()->first;
2533 auto& objDict = subtree.begin()->second;
2534
2535 if (objDict.empty())
2536 {
2537 /* Critical Error. Stop Application */
2538 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n";
2539 stopHsbpManager();
2540 return;
2541 }
2542
2543 const std::string& service = objDict.begin()->first;
2544
2545 conn->async_method_call(
2546 [](const boost::system::error_code er,
2547 const boost::container::flat_map<std::string,
2548 BasicVariantType>& resp) {
2549 if (er)
2550 {
2551 std::cerr << __FUNCTION__ << ": Error Getting Config "
2552 << er.message() << "\n";
2553 /* Critical Error. Stop Application */
2554 stopHsbpManager();
2555 return;
2556 }
2557
2558 std::optional<uint64_t> rootI2cBus;
2559 std::optional<std::vector<std::string>> supportedHsbps;
2560 std::optional<std::vector<std::string>> clockBufferTypes;
2561 std::optional<std::vector<std::string>> ioExpanderTypes;
2562
2563 /* Loop through to get root i2c bus and list of supported
2564 * HSBPs */
2565 for (const auto& [key, value] : resp)
2566 {
2567 if (key == "HsbpSupported")
2568 {
2569 supportedHsbps =
2570 std::get<std::vector<std::string>>(value);
2571 }
2572 else if (key == "RootI2cBus")
2573 {
2574 rootI2cBus = std::get<uint64_t>(value);
2575 }
2576 else if (key == "ClockBuffer")
2577 {
2578 clockBufferTypes =
2579 std::get<std::vector<std::string>>(value);
2580 }
2581 else if (key == "IoExpander")
2582 {
2583 ioExpanderTypes =
2584 std::get<std::vector<std::string>>(value);
2585 }
2586 }
2587
2588 /* Verify if i2c bus, supported HSBP's and clock buffers
2589 * were defined (IO Expanders are optional) */
2590 if (!rootI2cBus || !supportedHsbps || !clockBufferTypes)
2591 {
2592 std::cerr << __FUNCTION__
2593 << ": Incomplete HSBP "
2594 "configuration !! \n";
2595 /* Critical Error. Stop Application */
2596 stopHsbpManager();
2597 return;
2598 }
2599
2600 /* Clear and Load all details to global hsbp configuration
2601 * variable */
2602 hsbpConfig.clearConfig();
2603 hsbpConfig.rootBus = *rootI2cBus;
2604 hsbpConfig.supportedHsbps = std::move(*supportedHsbps);
2605
2606 for (auto& clkBuffType : *clockBufferTypes)
2607 {
2608 hsbpConfig.clockBufferTypes.emplace_back(
2609 "xyz.openbmc_project.Configuration." + clkBuffType);
2610 }
2611
2612 if (ioExpanderTypes)
2613 {
2614 for (auto& ioCntrType : *ioExpanderTypes)
2615 {
2616 hsbpConfig.ioExpanderTypes.emplace_back(
2617 "xyz.openbmc_project.Configuration." +
2618 ioCntrType);
2619 }
2620 }
2621
2622 /* Loop through to get HSBP-NVME map and Components map
2623 * details */
2624 uint8_t hsbpMapCount = 0;
2625 for (const auto& [key, value] : resp)
2626 {
2627 if (std::find(hsbpConfig.supportedHsbps.begin(),
2628 hsbpConfig.supportedHsbps.end(),
2629 key) != hsbpConfig.supportedHsbps.end())
2630 {
2631 std::optional<std::vector<std::string>> hsbpMap;
2632 hsbpMap = std::get<NvmeMapping>(value);
2633 if (!hsbpMap)
2634 {
2635 break;
2636 }
2637 hsbpConfig.hsbpNvmeMap.try_emplace(key, *hsbpMap);
2638 hsbpMapCount++;
2639 }
2640 }
2641
2642 /* Check if we were able to get all the HSBP-NVMe maps */
2643 if (hsbpConfig.supportedHsbps.size() != hsbpMapCount)
2644 {
2645 std::cerr << __FUNCTION__
2646 << ": Incomplete HSBP Map "
2647 "details !! \n";
2648 /* Critical Error. Stop Application */
2649 stopHsbpManager();
2650 return;
2651 }
2652
2653 /* HSBP configuration is loaded */
2654 appState = AppState::hsbpConfigLoaded;
2655 std::cerr << "HSBP Config loaded !\n";
2656
2657 /* Get Clock buffers and IO expander details. Create shared
2658 * object of AsyncCallbackHandler with success and error
2659 * callback */
2660 auto componentsLoadedCallback = std::make_shared<
2661 AsyncCallbackHandler>(
2662 []() {
2663 /* Verify if all components were initialized without
2664 * errors */
2665 if (!verifyComponentsLoaded())
2666 {
2667 /* The application cannot proceed further as
2668 * components initialization failed. App needs
2669 * Restart */
2670 appState = AppState::idle;
2671 std::cerr
2672 << "One or more Componenets initialization "
2673 "failed !! Restart Required !\n";
2674 stopHsbpManager();
2675 }
2676
2677 appState = AppState::componentsLoaded;
2678 setUpBackplanesAndDrives();
2679 },
2680 []() {
2681 /* The application cannot proceed further as
2682 * components load failed. App needs Restart */
2683 appState = AppState::idle;
2684 std::cerr << "Loading Componenets failed !! "
2685 "Restart Required !\n";
2686 stopHsbpManager();
2687 });
2688
2689 loadClockBufferInfo(componentsLoadedCallback);
2690
2691 if (ioExpanderTypes)
2692 {
2693 loadIoExpanderInfo(componentsLoadedCallback);
2694 }
2695 },
2696 service, path, "org.freedesktop.DBus.Properties", "GetAll",
2697 hsbpConfigIntf);
2698 },
2699 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
2700 0, std::array<const char*, 1>{hsbpConfigIntf});
2701 }
2702
setupHsbpConfigMatch()2703 void setupHsbpConfigMatch()
2704 {
2705 static auto hsbpConfigMatch = std::make_unique<sdbusplus::bus::match_t>(
2706 *conn,
2707 "sender='xyz.openbmc_project.EntityManager', type='signal', "
2708 "member='PropertiesChanged', "
2709 "interface='org.freedesktop.DBus.Properties', "
2710 "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" +
2711 std::string(hsbpConfigIntf) + "'",
2712 [](sdbusplus::message_t& msg) {
2713 std::string intfName;
2714 boost::container::flat_map<std::string, BasicVariantType> values;
2715 msg.read(intfName, values);
2716
2717 /* This match will be triggered for each of the property being set
2718 * under the hsbpConfig interface. "HsbpSupported" is one of the
2719 * important property which will enable us to read other properties.
2720 * So, when the match event occurs for "HsbpSupported" property
2721 * being set, we will call "loadHsbpConfig()" If the control has
2722 * come here, its either the first initialization or entity-manager
2723 * reload. So, we will reset the state to uninitialized
2724 */
2725 for (const auto& [key, value] : values)
2726 {
2727 if (key == "HsbpSupported")
2728 {
2729 /* Configuration change detected, change the state to stop
2730 * other processing */
2731 appState = AppState::idle;
2732
2733 /* We will call the function after a small delay to let all
2734 * the properties to be intialized */
2735 auto loadTimer =
2736 std::make_shared<boost::asio::steady_timer>(io);
2737 loadTimer->expires_after(std::chrono::seconds(1));
2738 loadTimer->async_wait(
2739 [loadTimer](const boost::system::error_code ec) {
2740 if (ec == boost::asio::error::operation_aborted)
2741 {
2742 return;
2743 }
2744 else if (ec)
2745 {
2746 std::cerr << __FUNCTION__ << ": Timer error"
2747 << ec.message() << "\n";
2748 if (hsbpConfig.supportedHsbps.empty())
2749 {
2750 /* Critical Error as none of the
2751 * configuration was loaded and timer
2752 * failed. Stop the application */
2753 stopHsbpManager();
2754 }
2755 return;
2756 }
2757 loadHsbpConfig();
2758 });
2759 }
2760 }
2761 });
2762 }
2763 /***************************** End of Section *******************************/
2764
2765 /****************************************************************************/
2766 /***************** GPIO Events related Function Definitions *****************/
2767 /****************************************************************************/
nvmeLvc3AlertHandler()2768 static void nvmeLvc3AlertHandler()
2769 {
2770 /* If the state is not backplanesLoaded, we ignore the GPIO event as we
2771 * cannot communicate to the backplanes yet */
2772 if (appState < AppState::backplanesLoaded)
2773 {
2774 std::cerr << __FUNCTION__
2775 << ": HSBP not initialized ! Dropping the interrupt ! \n";
2776 return;
2777 }
2778
2779 /* This GPIO event only indicates the addition or removal of drive to either
2780 * of CPU. The backplanes detected need to be scanned and detect which drive
2781 * has been added/removed and enable/diable clock accordingly */
2782 gpiod::line_event gpioLineEvent = nvmeLvc3AlertLine.event_read();
2783
2784 if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE)
2785 {
2786 /* Check for HSBP Drives status to determine if any new drive has been
2787 * added/removed and update clocks accordingly */
2788 checkHsbpDrivesStatus();
2789 }
2790
2791 nvmeLvc3AlertEvent.async_wait(
2792 boost::asio::posix::stream_descriptor::wait_read,
2793 [](const boost::system::error_code ec) {
2794 if (ec)
2795 {
2796 std::cerr << __FUNCTION__
2797 << ": nvmealert event error: " << ec.message()
2798 << "\n";
2799 }
2800 nvmeLvc3AlertHandler();
2801 });
2802 }
2803
hsbpRequestAlertGpioEvents(const std::string & name,const std::function<void ()> & handler,gpiod::line & gpioLine,boost::asio::posix::stream_descriptor & gpioEventDescriptor)2804 static bool hsbpRequestAlertGpioEvents(
2805 const std::string& name, const std::function<void()>& handler,
2806 gpiod::line& gpioLine,
2807 boost::asio::posix::stream_descriptor& gpioEventDescriptor)
2808 {
2809 // Find the GPIO line
2810 gpioLine = gpiod::find_line(name);
2811 if (!gpioLine)
2812 {
2813 std::cerr << __FUNCTION__ << ": Failed to find the " << name
2814 << " line\n";
2815 return false;
2816 }
2817
2818 try
2819 {
2820 gpioLine.request(
2821 {"hsbp-manager", gpiod::line_request::EVENT_BOTH_EDGES, 0});
2822 }
2823 catch (std::exception&)
2824 {
2825 std::cerr << __FUNCTION__ << ": Failed to request events for " << name
2826 << "\n";
2827 return false;
2828 }
2829
2830 int gpioLineFd = gpioLine.event_get_fd();
2831 if (gpioLineFd < 0)
2832 {
2833 std::cerr << __FUNCTION__ << ": Failed to get " << name << " fd\n";
2834 return false;
2835 }
2836
2837 gpioEventDescriptor.assign(gpioLineFd);
2838
2839 gpioEventDescriptor.async_wait(
2840 boost::asio::posix::stream_descriptor::wait_read,
2841 [&name, handler](const boost::system::error_code ec) {
2842 if (ec)
2843 {
2844 std::cerr << __FUNCTION__ << ": " << name
2845 << " fd handler error: " << ec.message() << "\n";
2846 return;
2847 }
2848 handler();
2849 });
2850 return true;
2851 }
2852 /***************************** End of Section *******************************/
2853
main()2854 int main()
2855 {
2856 std::cerr << "******* Starting hsbp-manager *******\n";
2857
2858 /* Set the Dbus name */
2859 conn->request_name(busName);
2860
2861 std::shared_ptr<sdbusplus::asio::dbus_interface> storageIface;
2862
2863 /* Add interface for storage inventory */
2864 storageIface = objServer.add_interface(
2865 "/xyz/openbmc_project/inventory/item/storage/hsbp/1",
2866 "xyz.openbmc_project.Inventory.Item.Storage");
2867
2868 storageIface->initialize();
2869
2870 /* HSBP initializtion flow:
2871 * 1. Register GPIO event callback on FM_SMB_BMC_NVME_LVC3_ALERT_N line
2872 * 2. Set up Dbus match for power - determine if host is up and running
2873 * or powered off
2874 * 3. Set up Dbus match for HSBP backplanes and Drives
2875 * 4. Load HSBP config exposed by entity manager
2876 * - Also setup a match to capture HSBP configuation in case
2877 * entity-manager restarts
2878 * 5. Load Clock buffer and IO expander (and other peripherals if any
2879 * related to HSBP functionality)
2880 * - Reload the info each time HSBP configuration is changed
2881 * 6. Populate all Backpanes (HSBP's)
2882 * 7. Load all NVMe drive's and associate with HSBP Backpane
2883 */
2884
2885 /* Register GPIO Events on FM_SMB_BMC_NVME_LVC3_ALERT_N */
2886 if (!hsbpRequestAlertGpioEvents("FM_SMB_BMC_NVME_LVC3_ALERT_N",
2887 nvmeLvc3AlertHandler, nvmeLvc3AlertLine,
2888 nvmeLvc3AlertEvent))
2889 {
2890 std::cerr << __FUNCTION__
2891 << ": error: Unable to monitor events on HSBP "
2892 "Alert line\n";
2893 return -1;
2894 }
2895
2896 /* Setup Dbus-match for power */
2897 setupPowerMatch(conn);
2898
2899 /* Setup Dbus-match for HSBP backplanes and Drives */
2900 setupBackplanesAndDrivesMatch();
2901
2902 /* Setup HSBP Config match and load config
2903 * In the event of entity-manager reboot, the match will help catch new
2904 * configuration.
2905 * In the event of hsbp-manager reboot, loadHsbpConfig will get all
2906 * config details and will take care of remaining config's to be
2907 * loaded
2908 */
2909 setupHsbpConfigMatch();
2910 loadHsbpConfig();
2911
2912 io.run();
2913 std::cerr << __FUNCTION__ << ": Aborting hsbp-manager !\n";
2914 return -1;
2915 }
2916