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 assetInventory[key] = std::get<std::string>(value);
1703 }
1704
1705 Backplane* parent = nullptr;
1706 for (auto& [name, backplane] : backplanes)
1707 {
1708 muxIndex = 0;
1709 for (const Mux& mux : *(backplane->muxes))
1710 {
1711 if (bus == mux.bus && addr == mux.address)
1712 {
1713 parent = backplane.get();
1714 break;
1715 }
1716 muxIndex += mux.channels;
1717 }
1718 if (parent)
1719 {
1720 /* Found the backplane. No need to proceed
1721 * further */
1722 break;
1723 }
1724 }
1725
1726 // assume its a M.2 or something without a hsbp
1727 if (parent == nullptr)
1728 {
1729 std::string driveName =
1730 "Drive_" + std::to_string(getDriveCount() + 1);
1731 auto& drive = ownerlessDrives.emplace_back(
1732 driveName, true, true, true, false);
1733 drive.createAsset(assetInventory);
1734 return;
1735 }
1736
1737 driveIndex += muxIndex;
1738
1739 if (parent->drives.size() <= driveIndex)
1740 {
1741 std::cerr << __FUNCTION__
1742 << ": Illegal drive index at " << path
1743 << " " << driveIndex << "\n";
1744 drivesLoadedCallback->setError();
1745 return;
1746 }
1747 auto it = parent->drives.begin();
1748 std::advance(it, driveIndex);
1749
1750 it->createAsset(assetInventory);
1751 },
1752 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1753 "" /*all interface items*/);
1754 }
1755 },
1756 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
1757 0, std::array<const char*, 1>{nvmeIntf});
1758 }
1759
populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes,std::string & rootPath)1760 void populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes,
1761 std::string& rootPath)
1762 {
1763 const static std::array<const std::string, 4> muxTypes = {
1764 "xyz.openbmc_project.Configuration.PCA9543Mux",
1765 "xyz.openbmc_project.Configuration.PCA9544Mux",
1766 "xyz.openbmc_project.Configuration.PCA9545Mux",
1767 "xyz.openbmc_project.Configuration.PCA9546Mux"};
1768
1769 conn->async_method_call(
1770 [muxes](const boost::system::error_code ec,
1771 const GetSubTreeType& subtree) {
1772 if (ec)
1773 {
1774 std::cerr << __FUNCTION__ << ": Error contacting mapper "
1775 << ec.message() << "\n";
1776 return;
1777 }
1778 size_t index = 0; // as we use a flat map, these are sorted
1779 for (const auto& [path, objDict] : subtree)
1780 {
1781 if (objDict.empty() || objDict.begin()->second.empty())
1782 {
1783 continue;
1784 }
1785
1786 const std::string& owner = objDict.begin()->first;
1787 const std::vector<std::string>& interfaces =
1788 objDict.begin()->second;
1789
1790 const std::string* interface = nullptr;
1791 for (const std::string& iface : interfaces)
1792 {
1793 if (std::find(muxTypes.begin(), muxTypes.end(), iface) !=
1794 muxTypes.end())
1795 {
1796 interface = &iface;
1797 break;
1798 }
1799 }
1800
1801 if (interface == nullptr)
1802 {
1803 std::cerr << __FUNCTION__ << ": Cannot get mux type\n";
1804 continue;
1805 }
1806
1807 conn->async_method_call(
1808 [path, muxes, index](
1809 const boost::system::error_code ec2,
1810 const boost::container::flat_map<
1811 std::string,
1812 std::variant<uint64_t, std::vector<std::string>>>&
1813 values) {
1814 if (ec2)
1815 {
1816 std::cerr << __FUNCTION__
1817 << ": Error Getting Config "
1818 << ec2.message() << "\n";
1819 return;
1820 }
1821 auto findBus = values.find("Bus");
1822 auto findAddress = values.find("Address");
1823 auto findChannelNames = values.find("ChannelNames");
1824 if (findBus == values.end() ||
1825 findAddress == values.end())
1826 {
1827 std::cerr << __FUNCTION__
1828 << ": Illegal configuration at " << path
1829 << "\n";
1830 return;
1831 }
1832 size_t bus = static_cast<size_t>(
1833 std::get<uint64_t>(findBus->second));
1834 size_t address = static_cast<size_t>(
1835 std::get<uint64_t>(findAddress->second));
1836 std::vector<std::string> channels =
1837 std::get<std::vector<std::string>>(
1838 findChannelNames->second);
1839 muxes->emplace(bus, address, channels.size(), index);
1840 },
1841 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1842 *interface);
1843 index++;
1844 }
1845 },
1846 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
1847 rootPath, 1, muxTypes);
1848 }
1849
populateHsbpBackplanes(const std::shared_ptr<AsyncCallbackHandler> & backplanesLoadedCallback)1850 void populateHsbpBackplanes(
1851 const std::shared_ptr<AsyncCallbackHandler>& backplanesLoadedCallback)
1852 {
1853 std::cerr << __FUNCTION__ << ": Scanning Backplanes ...\n";
1854 appState = AppState::loadingBackplanes;
1855 backplanes.clear();
1856
1857 conn->async_method_call(
1858 [backplanesLoadedCallback](const boost::system::error_code ec,
1859 const GetSubTreeType& subtree) {
1860 if (ec)
1861 {
1862 std::cerr << __FUNCTION__ << ": Error contacting mapper "
1863 << ec.message() << "\n";
1864 backplanesLoadedCallback->setError();
1865 return;
1866 }
1867
1868 if (subtree.empty())
1869 {
1870 /* There wer no HSBP's detected. set teh state back to
1871 * componentsLoaded so that on backplane match event, the
1872 * process can start again */
1873 appState = AppState::componentsLoaded;
1874 std::cerr << __FUNCTION__ << ": No HSBPs Detected....\n";
1875 return;
1876 }
1877
1878 for (const auto& [path, objDict] : subtree)
1879 {
1880 if (objDict.empty())
1881 {
1882 std::cerr << __FUNCTION__
1883 << ": Subtree data "
1884 "corrupted !\n";
1885 backplanesLoadedCallback->setError();
1886 return;
1887 }
1888
1889 const std::string& owner = objDict.begin()->first;
1890 conn->async_method_call(
1891 [backplanesLoadedCallback, path,
1892 owner](const boost::system::error_code ec2,
1893 const boost::container::flat_map<
1894 std::string, BasicVariantType>& resp) {
1895 if (ec2)
1896 {
1897 std::cerr << __FUNCTION__
1898 << ": Error Getting Config "
1899 << ec2.message() << "\n";
1900 backplanesLoadedCallback->setError();
1901 return;
1902 }
1903 std::optional<size_t> bus;
1904 std::optional<size_t> address;
1905 std::optional<size_t> backplaneIndex;
1906 std::optional<std::string> name;
1907 for (const auto& [key, value] : resp)
1908 {
1909 if (key == "Bus")
1910 {
1911 bus = std::get<uint64_t>(value);
1912 }
1913 else if (key == "Address")
1914 {
1915 address = std::get<uint64_t>(value);
1916 }
1917 else if (key == "Index")
1918 {
1919 backplaneIndex = std::get<uint64_t>(value);
1920 }
1921 else if (key == "Name")
1922 {
1923 name = std::get<std::string>(value);
1924 }
1925 }
1926 if (!bus || !address || !name || !backplaneIndex)
1927 {
1928 std::cerr << __FUNCTION__
1929 << ": Illegal configuration at " << path
1930 << "\n";
1931 backplanesLoadedCallback->setError();
1932 return;
1933 }
1934 std::string parentPath =
1935 std::filesystem::path(path).parent_path();
1936 const auto& [backplane, status] = backplanes.emplace(
1937 *name, std::make_shared<Backplane>(
1938 *bus, *address, *backplaneIndex, *name));
1939 backplane->second->run(parentPath, owner);
1940 populateMuxes(backplane->second->muxes, parentPath);
1941 },
1942 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1943 hsbpCpldInft);
1944 }
1945 },
1946 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
1947 0, std::array<const char*, 1>{hsbpCpldInft});
1948 }
1949
setUpBackplanesAndDrives()1950 void setUpBackplanesAndDrives()
1951 {
1952 static bool backplanesScanInProgress = false;
1953 static bool backplanesRescanInQueue = false;
1954
1955 if (appState < AppState::componentsLoaded)
1956 {
1957 std::cerr << __FUNCTION__
1958 << ": Components are not initialized ! Cancelling scan of "
1959 "Backplanes ! \n";
1960 return;
1961 }
1962
1963 if (backplanesScanInProgress)
1964 {
1965 std::cerr << __FUNCTION__
1966 << ": Backplanes Scan is already in progress\n";
1967 if (backplanesRescanInQueue)
1968 {
1969 /* There is already a Re-Scan in queue. No need to create multiple
1970 * rescans */
1971 return;
1972 }
1973
1974 backplanesRescanInQueue = true;
1975
1976 std::cerr << __FUNCTION__ << ": Queuing the Backplane Scan \n";
1977
1978 auto backplaneScanTimer =
1979 std::make_shared<boost::asio::steady_timer>(io);
1980 backplaneScanTimer->expires_after(std::chrono::seconds(1));
1981 backplaneScanTimer->async_wait(
1982 [backplaneScanTimer](const boost::system::error_code ec) {
1983 if (ec == boost::asio::error::operation_aborted)
1984 {
1985 // Timer was Aborted
1986 return;
1987 }
1988 else if (ec)
1989 {
1990 std::cerr << "backplaneScanTimer: Timer error"
1991 << ec.message() << "\n";
1992 return;
1993 }
1994 backplanesRescanInQueue = false;
1995 setUpBackplanesAndDrives();
1996 });
1997
1998 return;
1999 }
2000
2001 backplanesScanInProgress = true;
2002
2003 /* Set Callback to be called once backplanes are populated to call
2004 * updateAssets() and checkHsbpDrivesStatus() or handle error scnenario */
2005 auto backplanesLoadedCallback = std::make_shared<AsyncCallbackHandler>(
2006 []() {
2007 /* If no HSBP's were detected, the state changes to
2008 * componentsLoaded. Proceed further only if state was
2009 * loadingBackplanes */
2010 if (appState != AppState::loadingBackplanes)
2011 {
2012 backplanesScanInProgress = false;
2013 return;
2014 }
2015
2016 /* If there is a ReScan in the Queue, dont proceed further. Load the
2017 * Backplanes again and then proceed further */
2018 if (backplanesRescanInQueue)
2019 {
2020 backplanesScanInProgress = false;
2021 return;
2022 }
2023
2024 appState = AppState::backplanesLoaded;
2025 std::cerr << __FUNCTION__ << ": Backplanes Loaded...\n";
2026
2027 checkHsbpDrivesStatus();
2028 updateAssets();
2029 backplanesScanInProgress = false;
2030 },
2031 []() {
2032 /* Loading Backplanes is an important step. If the load failed due
2033 * to an error, stop the app so that restart cant be triggerred */
2034 std::cerr << "Backplanes couldn't be loaded due to an error !...\n";
2035 appState = AppState::idle;
2036 backplanesScanInProgress = false;
2037 stopHsbpManager();
2038 });
2039
2040 populateHsbpBackplanes(backplanesLoadedCallback);
2041 }
2042
setupBackplanesAndDrivesMatch()2043 void setupBackplanesAndDrivesMatch()
2044 {
2045 static auto backplaneMatch = std::make_unique<sdbusplus::bus::match_t>(
2046 *conn,
2047 "sender='xyz.openbmc_project.EntityManager', type='signal', "
2048 "member='PropertiesChanged', "
2049 "interface='org.freedesktop.DBus.Properties', "
2050 "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" +
2051 std::string(hsbpCpldInft) + "'",
2052 [](sdbusplus::message_t& msg) {
2053 std::string intfName;
2054 boost::container::flat_map<std::string, BasicVariantType> values;
2055 msg.read(intfName, values);
2056
2057 /* This match will be triggered for each of the property being set
2058 * under the hsbpCpldInft interface. Call the loader only on one
2059 * property say "name". This will avoid multiple calls to populate
2060 * function
2061 */
2062 for (const auto& [key, value] : values)
2063 {
2064 if (key == "Name")
2065 {
2066 /* This match will be triggered when ever there is a
2067 * addition/removal of HSBP backplane. At this stage, all
2068 * the HSBP's need to be populated again and also assets
2069 * have to be re-discovered. So, setting state to
2070 * componentsLoaded and calling setUpBackplanesAndDrives()
2071 * only if configuration and components loading was
2072 * completed */
2073 if (appState < AppState::componentsLoaded)
2074 {
2075 /* Configuration is not loaded yet. Backplanes will be
2076 * loaded
2077 * once configuration and components are loaded. */
2078 std::cerr << __FUNCTION__
2079 << ": Discarding Backplane match\n";
2080 return;
2081 }
2082
2083 appState = AppState::componentsLoaded;
2084
2085 /* We will call the function after a small delay to let all
2086 * the properties to be intialized */
2087 auto backplaneTimer =
2088 std::make_shared<boost::asio::steady_timer>(io);
2089 backplaneTimer->expires_after(std::chrono::seconds(2));
2090 backplaneTimer->async_wait(
2091 [backplaneTimer](const boost::system::error_code ec) {
2092 if (ec == boost::asio::error::operation_aborted)
2093 {
2094 return;
2095 }
2096 else if (ec)
2097 {
2098 std::cerr << "backplaneTimer: Timer error"
2099 << ec.message() << "\n";
2100 return;
2101 }
2102 setUpBackplanesAndDrives();
2103 });
2104 }
2105 }
2106 });
2107
2108 static auto drivesMatch = std::make_unique<sdbusplus::bus::match_t>(
2109 *conn,
2110 "sender='xyz.openbmc_project.EntityManager', type='signal', "
2111 "member='PropertiesChanged', "
2112 "interface='org.freedesktop.DBus.Properties', arg0='" +
2113 std::string(nvmeIntf) + "'",
2114 [](sdbusplus::message_t& msg) {
2115 std::string intfName;
2116 boost::container::flat_map<std::string, BasicVariantType> values;
2117 msg.read(intfName, values);
2118
2119 /* This match will be triggered for each of the property being set
2120 * under the nvmeIntf interface. Call the loader only on one
2121 * property say "name". This will avoid multiple calls to populate
2122 * function
2123 */
2124 for (const auto& [key, value] : values)
2125 {
2126 if (key == "Name")
2127 {
2128 /* This match will be triggered when ever there is a
2129 * addition/removal of drives. At this stage only assets
2130 * have to be re-discovered. So, setting state to
2131 * backplanesLoaded and calling updateAssets() only if all
2132 * previous states are completed */
2133 if (appState < AppState::backplanesLoaded)
2134 {
2135 /* Configuration is not loaded yet. Drives will be
2136 * loaded once
2137 * configuration, components and backplanes are loaded.
2138 */
2139 std::cerr << __FUNCTION__
2140 << ": Discarding Drive match\n";
2141 return;
2142 }
2143
2144 appState = AppState::backplanesLoaded;
2145
2146 /* We will call the function after a small delay to let all
2147 * the properties to be intialized */
2148 auto driveTimer =
2149 std::make_shared<boost::asio::steady_timer>(io);
2150 driveTimer->expires_after(std::chrono::seconds(2));
2151 driveTimer->async_wait(
2152 [driveTimer](const boost::system::error_code ec) {
2153 if (ec == boost::asio::error::operation_aborted)
2154 {
2155 return;
2156 }
2157 else if (ec)
2158 {
2159 std::cerr << "driveTimer: Timer error"
2160 << ec.message() << "\n";
2161 return;
2162 }
2163 updateAssets();
2164 });
2165 }
2166 }
2167 });
2168 }
2169 /***************************** End of Section *******************************/
2170
2171 /****************************************************************************/
2172 /******************* Components related Function Definitions ****************/
2173 /****************************************************************************/
verifyComponentsLoaded()2174 bool verifyComponentsLoaded()
2175 {
2176 std::cerr << __FUNCTION__ << ": Verifying all Components...\n";
2177
2178 /* Loop through all clock buffers */
2179 for (auto& clockBuffer : clockBuffers)
2180 {
2181 if (!clockBuffer.isInitialized())
2182 {
2183 std::cerr << "Critical Error: Initializing \""
2184 << clockBuffer.getName() << "\" failed\n";
2185 return false;
2186 }
2187 }
2188
2189 /* Loop through all IO Expanders */
2190 for (auto& ioExpander : ioExpanders)
2191 {
2192 if (!ioExpander.isInitialized())
2193 {
2194 std::cerr << "Critical Error: Initializing \""
2195 << ioExpander.getName() << "\" failed\n";
2196 return false;
2197 }
2198 }
2199
2200 std::cerr << __FUNCTION__ << ": Verifying Components Complete\n";
2201
2202 return true;
2203 }
2204 /***************************** End of Section *******************************/
2205
2206 /****************************************************************************/
2207 /****************** IO expander related Function Definitions ****************/
2208 /****************************************************************************/
loadIoExpanderInfo(const std::shared_ptr<AsyncCallbackHandler> & componentsLoadedCallback)2209 void loadIoExpanderInfo(
2210 const std::shared_ptr<AsyncCallbackHandler>& componentsLoadedCallback)
2211 {
2212 appState = AppState::loadingComponents;
2213
2214 /* Clear global ioExpanders to start off */
2215 ioExpanders.clear();
2216
2217 conn->async_method_call(
2218 [componentsLoadedCallback](const boost::system::error_code ec,
2219 const GetSubTreeType& subtree) {
2220 if (ec)
2221 {
2222 std::cerr << __FUNCTION__ << ": Error contacting mapper "
2223 << ec.message() << "\n";
2224 componentsLoadedCallback->setError();
2225 return;
2226 }
2227
2228 for (auto& [path, objDict] : subtree)
2229 {
2230
2231 if (objDict.empty())
2232 {
2233 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n";
2234 componentsLoadedCallback->setError();
2235 return;
2236 }
2237
2238 /* Ideally there would be only one element in objDict as only
2239 * one service exposes it and there would be only one interface
2240 * so it is safe to directly read them without loop */
2241 const std::string& service = objDict.begin()->first;
2242 const std::string& intf = objDict.begin()->second.front();
2243
2244 conn->async_method_call(
2245 [componentsLoadedCallback](
2246 const boost::system::error_code er,
2247 const boost::container::flat_map<
2248 std::string, BasicVariantType>& resp) {
2249 if (er)
2250 {
2251 std::cerr << __FUNCTION__
2252 << ": Error Getting "
2253 "Config "
2254 << er.message() << "\n";
2255 componentsLoadedCallback->setError();
2256 return;
2257 }
2258
2259 std::optional<uint64_t> bus;
2260 std::optional<uint64_t> address;
2261 std::optional<uint64_t> confIORegAddr;
2262 std::optional<uint64_t> outCtrlBaseAddr;
2263 std::optional<uint64_t> outCtrlByteCount;
2264 std::unordered_map<std::string,
2265 std::vector<std::string>>
2266 ioMap;
2267 std::optional<std::string> name;
2268 std::optional<std::string> type;
2269
2270 /* Loop through to get all IO Expander properties */
2271 for (const auto& [key, value] : resp)
2272 {
2273 if (key == "Bus")
2274 {
2275 bus = std::get<uint64_t>(value);
2276 }
2277 else if (key == "Address")
2278 {
2279 address = std::get<uint64_t>(value);
2280 }
2281 else if (key == "ConfIORegAddr")
2282 {
2283 confIORegAddr = std::get<uint64_t>(value);
2284 }
2285 else if (key == "OutCtrlBaseAddr")
2286 {
2287 outCtrlBaseAddr = std::get<uint64_t>(value);
2288 }
2289 else if (key == "OutCtrlByteCount")
2290 {
2291 outCtrlByteCount = std::get<uint64_t>(value);
2292 }
2293 else if (key == "Name")
2294 {
2295 name = std::get<std::string>(value);
2296 }
2297 else if (key == "Type")
2298 {
2299 type = std::get<std::string>(value);
2300 }
2301 else if (key.starts_with("IO"))
2302 {
2303 std::optional<std::vector<std::string>> outList;
2304 outList = std::get<NvmeMapping>(value);
2305 if (!outList)
2306 {
2307 break;
2308 }
2309 ioMap.try_emplace(key, *outList);
2310 }
2311 }
2312
2313 /* Verify if all properties were defined */
2314 if (!bus || !address || !confIORegAddr ||
2315 !outCtrlBaseAddr || !outCtrlByteCount || !name)
2316 {
2317 std::cerr << __FUNCTION__
2318 << ": Incomplete "
2319 "Clock Buffer definition !! \n";
2320 componentsLoadedCallback->setError();
2321 return;
2322 }
2323
2324 /* Check if we were able to get byteMap correctly */
2325 if ((*outCtrlByteCount) != ioMap.size())
2326 {
2327 std::cerr << "loadIoExpanderInfo(): Incomplete "
2328 "IO Map !! \n";
2329 componentsLoadedCallback->setError();
2330 return;
2331 }
2332
2333 /* Create IO expander object and add it to global
2334 * ioExpanders vector */
2335 ioExpanders.emplace_front(
2336 *bus, *address, *confIORegAddr, *outCtrlBaseAddr,
2337 *outCtrlByteCount, ioMap, *name, *type);
2338 },
2339 service, path, "org.freedesktop.DBus.Properties", "GetAll",
2340 intf);
2341 }
2342 },
2343 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
2344 0, hsbpConfig.ioExpanderTypes);
2345 }
2346 /***************************** End of Section *******************************/
2347
2348 /****************************************************************************/
2349 /***************** Clock buffer related Function Definitions ****************/
2350 /****************************************************************************/
loadClockBufferInfo(const std::shared_ptr<AsyncCallbackHandler> & componentsLoadedCallback)2351 void loadClockBufferInfo(
2352 const std::shared_ptr<AsyncCallbackHandler>& componentsLoadedCallback)
2353 {
2354 appState = AppState::loadingComponents;
2355
2356 /* Clear global clockBuffers to start off */
2357 clockBuffers.clear();
2358
2359 conn->async_method_call(
2360 [componentsLoadedCallback](const boost::system::error_code ec,
2361 const GetSubTreeType& subtree) {
2362 if (ec)
2363 {
2364 std::cerr << __FUNCTION__ << ": Error contacting mapper "
2365 << ec.message() << "\n";
2366 componentsLoadedCallback->setError();
2367 return;
2368 }
2369
2370 for (auto& [path, objDict] : subtree)
2371 {
2372
2373 if (objDict.empty())
2374 {
2375 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n";
2376 componentsLoadedCallback->setError();
2377 return;
2378 }
2379
2380 /* Ideally there would be only one element in objDict as only
2381 * one service exposes it and there would be only one interface
2382 * so it is safe to directly read them without loop */
2383 const std::string& service = objDict.begin()->first;
2384 const std::string& intf = objDict.begin()->second.front();
2385
2386 conn->async_method_call(
2387 [componentsLoadedCallback](
2388 const boost::system::error_code er,
2389 const boost::container::flat_map<
2390 std::string, BasicVariantType>& resp) {
2391 if (er)
2392 {
2393 std::cerr << __FUNCTION__
2394 << ": Error Getting "
2395 "Config "
2396 << er.message() << "\n";
2397 componentsLoadedCallback->setError();
2398 return;
2399 }
2400
2401 std::optional<uint64_t> bus;
2402 std::optional<uint64_t> address;
2403 std::optional<std::string> mode;
2404 std::optional<uint64_t> outCtrlBaseAddr;
2405 std::optional<uint64_t> outCtrlByteCount;
2406 std::unordered_map<std::string,
2407 std::vector<std::string>>
2408 byteMap;
2409 std::optional<std::string> name;
2410 std::optional<std::string> type;
2411
2412 /* Loop through to get all Clock Buffer properties */
2413 for (const auto& [key, value] : resp)
2414 {
2415 if (key == "Bus")
2416 {
2417 bus = std::get<uint64_t>(value);
2418 }
2419 else if (key == "Address")
2420 {
2421 address = std::get<uint64_t>(value);
2422 }
2423 else if (key == "Mode")
2424 {
2425 mode = std::get<std::string>(value);
2426 }
2427 else if (key == "OutCtrlBaseAddr")
2428 {
2429 outCtrlBaseAddr = std::get<uint64_t>(value);
2430 }
2431 else if (key == "OutCtrlByteCount")
2432 {
2433 outCtrlByteCount = std::get<uint64_t>(value);
2434 }
2435 else if (key == "Name")
2436 {
2437 name = std::get<std::string>(value);
2438 }
2439 else if (key == "Type")
2440 {
2441 type = std::get<std::string>(value);
2442 }
2443 else if (key.starts_with("Byte"))
2444 {
2445 std::optional<std::vector<std::string>>
2446 byteList;
2447 byteList = std::get<NvmeMapping>(value);
2448 if (!byteList)
2449 {
2450 break;
2451 }
2452 byteMap.try_emplace(key, *byteList);
2453 }
2454 }
2455
2456 /* Verify if all properties were defined */
2457 if (!bus || !address || !mode || !outCtrlBaseAddr ||
2458 !outCtrlByteCount || !name)
2459 {
2460 std::cerr << __FUNCTION__
2461 << ": Incomplete "
2462 "Clock Buffer definition !! \n";
2463 componentsLoadedCallback->setError();
2464 return;
2465 }
2466
2467 /* Check if we were able to get byteMap correctly */
2468 if ((*outCtrlByteCount) != byteMap.size())
2469 {
2470 std::cerr << __FUNCTION__
2471 << ": Incomplete "
2472 "Byte Map !! \n";
2473 componentsLoadedCallback->setError();
2474 return;
2475 }
2476
2477 /* Create clock buffer object and add it to global
2478 * clockBuffers vector */
2479 clockBuffers.emplace_front(
2480 *bus, *address, *mode, *outCtrlBaseAddr,
2481 *outCtrlByteCount, byteMap, *name, *type);
2482 },
2483 service, path, "org.freedesktop.DBus.Properties", "GetAll",
2484 intf);
2485 }
2486 },
2487 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
2488 0, hsbpConfig.clockBufferTypes);
2489 }
2490 /***************************** End of Section *******************************/
2491
2492 /****************************************************************************/
2493 /***************** HSBP Config related Function Definitions *****************/
2494 /****************************************************************************/
loadHsbpConfig()2495 void loadHsbpConfig()
2496 {
2497 appState = AppState::loadingHsbpConfig;
2498
2499 conn->async_method_call(
2500 [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
2501 if (ec)
2502 {
2503 std::cerr << __FUNCTION__ << ": Error contacting mapper "
2504 << ec.message() << "\n";
2505 return;
2506 }
2507
2508 if (subtree.empty())
2509 {
2510 /* Entity manager is either still loading the configuration or
2511 * failed to load. In either way, return from here as the dbus
2512 * match will take care */
2513 std::cerr << __FUNCTION__ << ": No configuration detected !!\n";
2514 return;
2515 }
2516
2517 /* There should be only one HSBP Configureation exposed */
2518 if (subtree.size() != 1)
2519 {
2520 std::cerr << __FUNCTION__
2521 << ": Multiple configurations "
2522 "detected !!\n";
2523 /* Critical Error. Stop Application */
2524 stopHsbpManager();
2525 return;
2526 }
2527
2528 auto& path = subtree.begin()->first;
2529 auto& objDict = subtree.begin()->second;
2530
2531 if (objDict.empty())
2532 {
2533 /* Critical Error. Stop Application */
2534 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n";
2535 stopHsbpManager();
2536 return;
2537 }
2538
2539 const std::string& service = objDict.begin()->first;
2540
2541 conn->async_method_call(
2542 [](const boost::system::error_code er,
2543 const boost::container::flat_map<std::string,
2544 BasicVariantType>& resp) {
2545 if (er)
2546 {
2547 std::cerr << __FUNCTION__ << ": Error Getting Config "
2548 << er.message() << "\n";
2549 /* Critical Error. Stop Application */
2550 stopHsbpManager();
2551 return;
2552 }
2553
2554 std::optional<uint64_t> rootI2cBus;
2555 std::optional<std::vector<std::string>> supportedHsbps;
2556 std::optional<std::vector<std::string>> clockBufferTypes;
2557 std::optional<std::vector<std::string>> ioExpanderTypes;
2558
2559 /* Loop through to get root i2c bus and list of supported
2560 * HSBPs */
2561 for (const auto& [key, value] : resp)
2562 {
2563 if (key == "HsbpSupported")
2564 {
2565 supportedHsbps =
2566 std::get<std::vector<std::string>>(value);
2567 }
2568 else if (key == "RootI2cBus")
2569 {
2570 rootI2cBus = std::get<uint64_t>(value);
2571 }
2572 else if (key == "ClockBuffer")
2573 {
2574 clockBufferTypes =
2575 std::get<std::vector<std::string>>(value);
2576 }
2577 else if (key == "IoExpander")
2578 {
2579 ioExpanderTypes =
2580 std::get<std::vector<std::string>>(value);
2581 }
2582 }
2583
2584 /* Verify if i2c bus, supported HSBP's and clock buffers
2585 * were defined (IO Expanders are optional) */
2586 if (!rootI2cBus || !supportedHsbps || !clockBufferTypes)
2587 {
2588 std::cerr << __FUNCTION__
2589 << ": Incomplete HSBP "
2590 "configuration !! \n";
2591 /* Critical Error. Stop Application */
2592 stopHsbpManager();
2593 return;
2594 }
2595
2596 /* Clear and Load all details to global hsbp configuration
2597 * variable */
2598 hsbpConfig.clearConfig();
2599 hsbpConfig.rootBus = *rootI2cBus;
2600 hsbpConfig.supportedHsbps = std::move(*supportedHsbps);
2601
2602 for (auto& clkBuffType : *clockBufferTypes)
2603 {
2604 hsbpConfig.clockBufferTypes.emplace_back(
2605 "xyz.openbmc_project.Configuration." + clkBuffType);
2606 }
2607
2608 if (ioExpanderTypes)
2609 {
2610 for (auto& ioCntrType : *ioExpanderTypes)
2611 {
2612 hsbpConfig.ioExpanderTypes.emplace_back(
2613 "xyz.openbmc_project.Configuration." +
2614 ioCntrType);
2615 }
2616 }
2617
2618 /* Loop through to get HSBP-NVME map and Components map
2619 * details */
2620 uint8_t hsbpMapCount = 0;
2621 for (const auto& [key, value] : resp)
2622 {
2623 if (std::find(hsbpConfig.supportedHsbps.begin(),
2624 hsbpConfig.supportedHsbps.end(),
2625 key) != hsbpConfig.supportedHsbps.end())
2626 {
2627 std::optional<std::vector<std::string>> hsbpMap;
2628 hsbpMap = std::get<NvmeMapping>(value);
2629 if (!hsbpMap)
2630 {
2631 break;
2632 }
2633 hsbpConfig.hsbpNvmeMap.try_emplace(key, *hsbpMap);
2634 hsbpMapCount++;
2635 }
2636 }
2637
2638 /* Check if we were able to get all the HSBP-NVMe maps */
2639 if (hsbpConfig.supportedHsbps.size() != hsbpMapCount)
2640 {
2641 std::cerr << __FUNCTION__
2642 << ": Incomplete HSBP Map "
2643 "details !! \n";
2644 /* Critical Error. Stop Application */
2645 stopHsbpManager();
2646 return;
2647 }
2648
2649 /* HSBP configuration is loaded */
2650 appState = AppState::hsbpConfigLoaded;
2651 std::cerr << "HSBP Config loaded !\n";
2652
2653 /* Get Clock buffers and IO expander details. Create shared
2654 * object of AsyncCallbackHandler with success and error
2655 * callback */
2656 auto componentsLoadedCallback = std::make_shared<
2657 AsyncCallbackHandler>(
2658 []() {
2659 /* Verify if all components were initialized without
2660 * errors */
2661 if (!verifyComponentsLoaded())
2662 {
2663 /* The application cannot proceed further as
2664 * components initialization failed. App needs
2665 * Restart */
2666 appState = AppState::idle;
2667 std::cerr
2668 << "One or more Componenets initialization "
2669 "failed !! Restart Required !\n";
2670 stopHsbpManager();
2671 }
2672
2673 appState = AppState::componentsLoaded;
2674 setUpBackplanesAndDrives();
2675 },
2676 []() {
2677 /* The application cannot proceed further as
2678 * components load failed. App needs Restart */
2679 appState = AppState::idle;
2680 std::cerr << "Loading Componenets failed !! "
2681 "Restart Required !\n";
2682 stopHsbpManager();
2683 });
2684
2685 loadClockBufferInfo(componentsLoadedCallback);
2686
2687 if (ioExpanderTypes)
2688 {
2689 loadIoExpanderInfo(componentsLoadedCallback);
2690 }
2691 },
2692 service, path, "org.freedesktop.DBus.Properties", "GetAll",
2693 hsbpConfigIntf);
2694 },
2695 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
2696 0, std::array<const char*, 1>{hsbpConfigIntf});
2697 }
2698
setupHsbpConfigMatch()2699 void setupHsbpConfigMatch()
2700 {
2701 static auto hsbpConfigMatch = std::make_unique<sdbusplus::bus::match_t>(
2702 *conn,
2703 "sender='xyz.openbmc_project.EntityManager', type='signal', "
2704 "member='PropertiesChanged', "
2705 "interface='org.freedesktop.DBus.Properties', "
2706 "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" +
2707 std::string(hsbpConfigIntf) + "'",
2708 [](sdbusplus::message_t& msg) {
2709 std::string intfName;
2710 boost::container::flat_map<std::string, BasicVariantType> values;
2711 msg.read(intfName, values);
2712
2713 /* This match will be triggered for each of the property being set
2714 * under the hsbpConfig interface. "HsbpSupported" is one of the
2715 * important property which will enable us to read other properties.
2716 * So, when the match event occurs for "HsbpSupported" property
2717 * being set, we will call "loadHsbpConfig()" If the control has
2718 * come here, its either the first initialization or entity-manager
2719 * reload. So, we will reset the state to uninitialized
2720 */
2721 for (const auto& [key, value] : values)
2722 {
2723 if (key == "HsbpSupported")
2724 {
2725 /* Configuration change detected, change the state to stop
2726 * other processing */
2727 appState = AppState::idle;
2728
2729 /* We will call the function after a small delay to let all
2730 * the properties to be intialized */
2731 auto loadTimer =
2732 std::make_shared<boost::asio::steady_timer>(io);
2733 loadTimer->expires_after(std::chrono::seconds(1));
2734 loadTimer->async_wait(
2735 [loadTimer](const boost::system::error_code ec) {
2736 if (ec == boost::asio::error::operation_aborted)
2737 {
2738 return;
2739 }
2740 else if (ec)
2741 {
2742 std::cerr << __FUNCTION__ << ": Timer error"
2743 << ec.message() << "\n";
2744 if (hsbpConfig.supportedHsbps.empty())
2745 {
2746 /* Critical Error as none of the
2747 * configuration was loaded and timer
2748 * failed. Stop the application */
2749 stopHsbpManager();
2750 }
2751 return;
2752 }
2753 loadHsbpConfig();
2754 });
2755 }
2756 }
2757 });
2758 }
2759 /***************************** End of Section *******************************/
2760
2761 /****************************************************************************/
2762 /***************** GPIO Events related Function Definitions *****************/
2763 /****************************************************************************/
nvmeLvc3AlertHandler()2764 static void nvmeLvc3AlertHandler()
2765 {
2766 /* If the state is not backplanesLoaded, we ignore the GPIO event as we
2767 * cannot communicate to the backplanes yet */
2768 if (appState < AppState::backplanesLoaded)
2769 {
2770 std::cerr << __FUNCTION__
2771 << ": HSBP not initialized ! Dropping the interrupt ! \n";
2772 return;
2773 }
2774
2775 /* This GPIO event only indicates the addition or removal of drive to either
2776 * of CPU. The backplanes detected need to be scanned and detect which drive
2777 * has been added/removed and enable/diable clock accordingly */
2778 gpiod::line_event gpioLineEvent = nvmeLvc3AlertLine.event_read();
2779
2780 if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE)
2781 {
2782 /* Check for HSBP Drives status to determine if any new drive has been
2783 * added/removed and update clocks accordingly */
2784 checkHsbpDrivesStatus();
2785 }
2786
2787 nvmeLvc3AlertEvent.async_wait(
2788 boost::asio::posix::stream_descriptor::wait_read,
2789 [](const boost::system::error_code ec) {
2790 if (ec)
2791 {
2792 std::cerr << __FUNCTION__
2793 << ": nvmealert event error: " << ec.message()
2794 << "\n";
2795 }
2796 nvmeLvc3AlertHandler();
2797 });
2798 }
2799
hsbpRequestAlertGpioEvents(const std::string & name,const std::function<void ()> & handler,gpiod::line & gpioLine,boost::asio::posix::stream_descriptor & gpioEventDescriptor)2800 static bool hsbpRequestAlertGpioEvents(
2801 const std::string& name, const std::function<void()>& handler,
2802 gpiod::line& gpioLine,
2803 boost::asio::posix::stream_descriptor& gpioEventDescriptor)
2804 {
2805 // Find the GPIO line
2806 gpioLine = gpiod::find_line(name);
2807 if (!gpioLine)
2808 {
2809 std::cerr << __FUNCTION__ << ": Failed to find the " << name
2810 << " line\n";
2811 return false;
2812 }
2813
2814 try
2815 {
2816 gpioLine.request(
2817 {"hsbp-manager", gpiod::line_request::EVENT_BOTH_EDGES, 0});
2818 }
2819 catch (std::exception&)
2820 {
2821 std::cerr << __FUNCTION__ << ": Failed to request events for " << name
2822 << "\n";
2823 return false;
2824 }
2825
2826 int gpioLineFd = gpioLine.event_get_fd();
2827 if (gpioLineFd < 0)
2828 {
2829 std::cerr << __FUNCTION__ << ": Failed to get " << name << " fd\n";
2830 return false;
2831 }
2832
2833 gpioEventDescriptor.assign(gpioLineFd);
2834
2835 gpioEventDescriptor.async_wait(
2836 boost::asio::posix::stream_descriptor::wait_read,
2837 [&name, handler](const boost::system::error_code ec) {
2838 if (ec)
2839 {
2840 std::cerr << __FUNCTION__ << ": " << name
2841 << " fd handler error: " << ec.message() << "\n";
2842 return;
2843 }
2844 handler();
2845 });
2846 return true;
2847 }
2848 /***************************** End of Section *******************************/
2849
main()2850 int main()
2851 {
2852 std::cerr << "******* Starting hsbp-manager *******\n";
2853
2854 /* Set the Dbus name */
2855 conn->request_name(busName);
2856
2857 std::shared_ptr<sdbusplus::asio::dbus_interface> storageIface;
2858
2859 /* Add interface for storage inventory */
2860 storageIface = objServer.add_interface("/xyz/openbmc_project/inventory/item/storage/hsbp/1",
2861 "xyz.openbmc_project.Inventory.Item.Storage");
2862
2863 storageIface->initialize();
2864
2865 /* HSBP initializtion flow:
2866 * 1. Register GPIO event callback on FM_SMB_BMC_NVME_LVC3_ALERT_N line
2867 * 2. Set up Dbus match for power - determine if host is up and running
2868 * or powered off
2869 * 3. Set up Dbus match for HSBP backplanes and Drives
2870 * 4. Load HSBP config exposed by entity manager
2871 * - Also setup a match to capture HSBP configuation in case
2872 * entity-manager restarts
2873 * 5. Load Clock buffer and IO expander (and other peripherals if any
2874 * related to HSBP functionality)
2875 * - Reload the info each time HSBP configuration is changed
2876 * 6. Populate all Backpanes (HSBP's)
2877 * 7. Load all NVMe drive's and associate with HSBP Backpane
2878 */
2879
2880 /* Register GPIO Events on FM_SMB_BMC_NVME_LVC3_ALERT_N */
2881 if (!hsbpRequestAlertGpioEvents("FM_SMB_BMC_NVME_LVC3_ALERT_N",
2882 nvmeLvc3AlertHandler, nvmeLvc3AlertLine,
2883 nvmeLvc3AlertEvent))
2884 {
2885 std::cerr << __FUNCTION__
2886 << ": error: Unable to monitor events on HSBP "
2887 "Alert line\n";
2888 return -1;
2889 }
2890
2891 /* Setup Dbus-match for power */
2892 setupPowerMatch(conn);
2893
2894 /* Setup Dbus-match for HSBP backplanes and Drives */
2895 setupBackplanesAndDrivesMatch();
2896
2897 /* Setup HSBP Config match and load config
2898 * In the event of entity-manager reboot, the match will help catch new
2899 * configuration.
2900 * In the event of hsbp-manager reboot, loadHsbpConfig will get all
2901 * config details and will take care of remaining config's to be
2902 * loaded
2903 */
2904 setupHsbpConfigMatch();
2905 loadHsbpConfig();
2906
2907 io.run();
2908 std::cerr << __FUNCTION__ << ": Aborting hsbp-manager !\n";
2909 return -1;
2910 }
2911