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