blob: e2ff85701dde43e39ba9326e30fbcd568fbb1350 [file] [log] [blame]
Feist, Jamesc95cf672019-08-29 16:10:35 -07001/*
2// Copyright (c) 2019 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
17#include "utils.hpp"
18
19#include <boost/algorithm/string/replace.hpp>
20#include <boost/asio/steady_timer.hpp>
James Feist8675a912019-10-16 14:36:58 -070021#include <boost/container/flat_set.hpp>
James Feist0b236ab2019-10-02 09:09:16 -070022#include <filesystem>
James Feist8675a912019-10-16 14:36:58 -070023#include <fstream>
Feist, Jamesc95cf672019-08-29 16:10:35 -070024#include <iostream>
25#include <sdbusplus/asio/connection.hpp>
26#include <sdbusplus/asio/object_server.hpp>
27#include <sdbusplus/bus/match.hpp>
28#include <string>
James Feist45772222019-09-27 10:38:08 -070029#include <utility>
Feist, Jamesc95cf672019-08-29 16:10:35 -070030
31extern "C" {
32#include <i2c/smbus.h>
33#include <linux/i2c-dev.h>
34}
35
36constexpr const char* configType =
37 "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD";
James Feistdb2e0e72019-10-07 16:34:06 -070038constexpr const char* busName = "xyz.openbmc_project.HsbpManager";
Feist, Jamesc95cf672019-08-29 16:10:35 -070039
James Feist45772222019-09-27 10:38:08 -070040constexpr size_t scanRateSeconds = 5;
41constexpr size_t maxDrives = 8; // only 1 byte alloted
42
Feist, Jamesc95cf672019-08-29 16:10:35 -070043boost::asio::io_context io;
44auto conn = std::make_shared<sdbusplus::asio::connection>(io);
45sdbusplus::asio::object_server objServer(conn);
46
James Feist45772222019-09-27 10:38:08 -070047static std::string zeroPad(const uint8_t val)
48{
49 std::ostringstream version;
50 version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val);
51 return version.str();
52}
53
James Feist0b236ab2019-10-02 09:09:16 -070054struct Mux
55{
James Feist8675a912019-10-16 14:36:58 -070056 Mux(size_t busIn, size_t addressIn, size_t channelsIn, size_t indexIn) :
57 bus(busIn), address(addressIn), channels(channelsIn), index(indexIn)
James Feist0b236ab2019-10-02 09:09:16 -070058 {
59 }
60 size_t bus;
61 size_t address;
James Feist8675a912019-10-16 14:36:58 -070062 size_t channels;
63 size_t index;
64
65 // to sort in the flat set
66 bool operator<(const Mux& rhs) const
67 {
68 return index < rhs.index;
69 }
James Feist0b236ab2019-10-02 09:09:16 -070070};
James Feist09dd2312019-10-09 09:29:03 -070071
72enum class BlinkPattern : uint8_t
73{
74 off = 0x0,
75 error = 0x2,
76 terminate = 0x3
77};
78
James Feistda0c35f2020-02-03 10:16:13 -080079static void rescanFruDeviceBus(size_t bus)
80{
81 conn->async_method_call(
82 [bus](const boost::system::error_code ec) {
83 if (ec)
84 {
85 std::cerr << "Error trigger rescan at bus " << bus << "\n";
86 }
87 },
88 "xyz.openbmc_project.FruDevice", "/xyz/openbmc_project/FruDevice",
89 "xyz.openbmc_project.FruDeviceManager", "ReScanBus",
90 static_cast<uint8_t>(bus));
91}
92
James Feist09dd2312019-10-09 09:29:03 -070093struct Led : std::enable_shared_from_this<Led>
94{
95 // led pattern addresses start at 0x10
96 Led(const std::string& path, size_t index, int fd) :
97 address(static_cast<uint8_t>(index + 0x10)), file(fd),
98 ledInterface(objServer.add_interface(path, ledGroup::interface))
99 {
100 if (index >= maxDrives)
101 {
102 throw std::runtime_error("Invalid drive index");
103 }
104
105 if (!set(BlinkPattern::off))
106 {
107 std::cerr << "Cannot initialize LED " << path << "\n";
108 }
109 }
110
111 // this has to be called outside the constructor for shared_from_this to
112 // work
113 void createInterface(void)
114 {
115 std::shared_ptr<Led> self = shared_from_this();
116
117 ledInterface->register_property(
118 ledGroup::asserted, false, [self](const bool req, bool& val) {
119 if (req == val)
120 {
121 return 1;
122 }
James Feist9f6565d2019-10-09 13:15:13 -0700123
124 if (!isPowerOn())
125 {
126 std::cerr << "Can't change blink state when power is off\n";
127 throw std::runtime_error(
128 "Can't change blink state when power is off");
129 }
James Feist09dd2312019-10-09 09:29:03 -0700130 BlinkPattern pattern =
131 req ? BlinkPattern::error : BlinkPattern::terminate;
132 if (!self->set(pattern))
133 {
James Feist9f6565d2019-10-09 13:15:13 -0700134 std::cerr << "Can't change blink pattern\n";
James Feist09dd2312019-10-09 09:29:03 -0700135 throw std::runtime_error("Cannot set blink pattern");
136 }
137 val = req;
138 return 1;
139 });
140 ledInterface->initialize();
141 }
142
143 virtual ~Led()
144 {
145 objServer.remove_interface(ledInterface);
146 }
147
148 bool set(BlinkPattern pattern)
149 {
150 int ret = i2c_smbus_write_byte_data(file, address,
151 static_cast<uint8_t>(pattern));
152 return ret >= 0;
153 }
154
155 uint8_t address;
156 int file;
157 std::shared_ptr<sdbusplus::asio::dbus_interface> ledInterface;
158};
159
James Feist45772222019-09-27 10:38:08 -0700160struct Drive
161{
James Feist42b49c12019-10-29 15:18:43 -0700162 Drive(size_t driveIndex, bool present, bool isOperational, bool nvme,
James Feist244f3232019-09-27 15:15:14 -0700163 bool rebuilding) :
James Feist42b49c12019-10-29 15:18:43 -0700164 isNvme(nvme),
James Feiste8818522019-11-04 13:36:10 -0800165 isPresent(present), index(driveIndex)
James Feist45772222019-09-27 10:38:08 -0700166 {
167 constexpr const char* basePath =
168 "/xyz/openbmc_project/inventory/item/drive/Drive_";
169 itemIface = objServer.add_interface(
170 basePath + std::to_string(driveIndex), inventory::interface);
171 itemIface->register_property("Present", isPresent);
James Feiste8818522019-11-04 13:36:10 -0800172 if (isPresent && !isNvme)
173 {
174 // nvme drives get detected by their fru
175 logDeviceAdded("Drive", std::to_string(index), "N/A");
176 }
James Feist45772222019-09-27 10:38:08 -0700177 itemIface->register_property("PrettyName",
178 "Drive " + std::to_string(driveIndex));
179 itemIface->initialize();
180 operationalIface = objServer.add_interface(
James Feist244f3232019-09-27 15:15:14 -0700181 itemIface->get_object_path(),
James Feist45772222019-09-27 10:38:08 -0700182 "xyz.openbmc_project.State.Decorator.OperationalStatus");
James Feist42b49c12019-10-29 15:18:43 -0700183
184 operationalIface->register_property(
185 "Functional", isOperational,
186 [this](const bool req, bool& property) {
187 if (!isPresent)
188 {
189 return 0;
190 }
191 if (property == req)
192 {
193 return 1;
194 }
195 property = req;
196 if (req)
197 {
198 clearFailed();
199 return 1;
200 }
201 markFailed();
202 return 1;
203 });
204
James Feist45772222019-09-27 10:38:08 -0700205 operationalIface->initialize();
James Feist244f3232019-09-27 15:15:14 -0700206 rebuildingIface = objServer.add_interface(
207 itemIface->get_object_path(), "xyz.openbmc_project.State.Drive");
208 rebuildingIface->register_property("Rebuilding", rebuilding);
209 rebuildingIface->initialize();
James Feist8675a912019-10-16 14:36:58 -0700210 driveIface =
211 objServer.add_interface(itemIface->get_object_path(),
212 "xyz.openbmc_project.Inventory.Item.Drive");
213 driveIface->initialize();
James Feist42b49c12019-10-29 15:18:43 -0700214 associations = objServer.add_interface(itemIface->get_object_path(),
215 association::interface);
216 associations->register_property("Associations",
217 std::vector<Association>{});
218 associations->initialize();
219
220 if (isPresent && (!isOperational || rebuilding))
221 {
222 markFailed();
223 }
James Feist45772222019-09-27 10:38:08 -0700224 }
James Feist09dd2312019-10-09 09:29:03 -0700225 virtual ~Drive()
James Feist45772222019-09-27 10:38:08 -0700226 {
227 objServer.remove_interface(itemIface);
228 objServer.remove_interface(operationalIface);
James Feist244f3232019-09-27 15:15:14 -0700229 objServer.remove_interface(rebuildingIface);
James Feist8675a912019-10-16 14:36:58 -0700230 objServer.remove_interface(assetIface);
James Feistdb2e0e72019-10-07 16:34:06 -0700231 objServer.remove_interface(driveIface);
James Feist42b49c12019-10-29 15:18:43 -0700232 objServer.remove_interface(associations);
James Feist0b236ab2019-10-02 09:09:16 -0700233 }
234
James Feist8675a912019-10-16 14:36:58 -0700235 void createAsset(
236 const boost::container::flat_map<std::string, std::string>& data)
James Feist0b236ab2019-10-02 09:09:16 -0700237 {
James Feist8675a912019-10-16 14:36:58 -0700238 if (assetIface != nullptr)
James Feist0b236ab2019-10-02 09:09:16 -0700239 {
240 return;
241 }
James Feist8675a912019-10-16 14:36:58 -0700242 assetIface = objServer.add_interface(
James Feist0b236ab2019-10-02 09:09:16 -0700243 itemIface->get_object_path(),
James Feist8675a912019-10-16 14:36:58 -0700244 "xyz.openbmc_project.Inventory.Decorator.Asset");
245 for (const auto& [key, value] : data)
James Feistdb2e0e72019-10-07 16:34:06 -0700246 {
James Feist8675a912019-10-16 14:36:58 -0700247 assetIface->register_property(key, value);
James Feistdb2e0e72019-10-07 16:34:06 -0700248 }
James Feist8675a912019-10-16 14:36:58 -0700249 assetIface->initialize();
James Feistdb2e0e72019-10-07 16:34:06 -0700250 }
251
James Feist42b49c12019-10-29 15:18:43 -0700252 void markFailed(void)
253 {
254 // todo: maybe look this up via mapper
255 constexpr const char* globalInventoryPath =
256 "/xyz/openbmc_project/CallbackManager";
257
258 if (!isPresent)
259 {
260 return;
261 }
262
263 operationalIface->set_property("Functional", false);
264 std::vector<Association> warning = {
265 {"", "warning", globalInventoryPath}};
266 associations->set_property("Associations", warning);
James Feiste8818522019-11-04 13:36:10 -0800267 logDriveError("Drive " + std::to_string(index));
James Feist42b49c12019-10-29 15:18:43 -0700268 }
269
270 void clearFailed(void)
271 {
272 operationalIface->set_property("Functional", true);
273 associations->set_property("Associations", std::vector<Association>{});
274 }
275
James Feistda0c35f2020-02-03 10:16:13 -0800276 void setPresent(bool set, bool nvme)
James Feiste8818522019-11-04 13:36:10 -0800277 {
278 // nvme drives get detected by their fru
James Feistda0c35f2020-02-03 10:16:13 -0800279 if (nvme || set == isPresent)
James Feiste8818522019-11-04 13:36:10 -0800280 {
281 return;
282 }
283 itemIface->set_property("Present", set);
284 isPresent = set;
285 if (isPresent)
286 {
287 logDeviceAdded("Drive", std::to_string(index), "N/A");
288 }
289 else
290 {
291 logDeviceRemoved("Drive", std::to_string(index), "N/A");
292 }
293 }
294
James Feist45772222019-09-27 10:38:08 -0700295 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
296 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
James Feist244f3232019-09-27 15:15:14 -0700297 std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface;
James Feist8675a912019-10-16 14:36:58 -0700298 std::shared_ptr<sdbusplus::asio::dbus_interface> assetIface;
James Feistdb2e0e72019-10-07 16:34:06 -0700299 std::shared_ptr<sdbusplus::asio::dbus_interface> driveIface;
James Feist42b49c12019-10-29 15:18:43 -0700300 std::shared_ptr<sdbusplus::asio::dbus_interface> associations;
James Feistdb2e0e72019-10-07 16:34:06 -0700301
James Feist45772222019-09-27 10:38:08 -0700302 bool isNvme;
James Feist42b49c12019-10-29 15:18:43 -0700303 bool isPresent;
James Feiste8818522019-11-04 13:36:10 -0800304 size_t index;
James Feist45772222019-09-27 10:38:08 -0700305};
306
Feist, Jamesc95cf672019-08-29 16:10:35 -0700307struct Backplane
308{
309
James Feist45772222019-09-27 10:38:08 -0700310 Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn,
311 const std::string& nameIn) :
312 bus(busIn),
313 address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn),
James Feist0b236ab2019-10-02 09:09:16 -0700314 timer(std::make_shared<boost::asio::steady_timer>(io)),
James Feist8675a912019-10-16 14:36:58 -0700315 muxes(std::make_shared<boost::container::flat_set<Mux>>())
Feist, Jamesc95cf672019-08-29 16:10:35 -0700316 {
317 }
James Feistd0d36f12019-11-21 10:19:44 -0800318 void populateAsset(const std::string& rootPath, const std::string& busname)
319 {
320 conn->async_method_call(
321 [assetIface{assetInterface}, hsbpIface{hsbpItemIface}](
322 const boost::system::error_code ec,
323 const boost::container::flat_map<
324 std::string, std::variant<std::string>>& values) mutable {
325 if (ec)
326 {
327 std::cerr
328 << "Error getting asset tag from HSBP configuration\n";
329
330 return;
331 }
332 assetIface = objServer.add_interface(
333 hsbpIface->get_object_path(), assetTag);
334 for (const auto& [key, value] : values)
335 {
336 const std::string* ptr = std::get_if<std::string>(&value);
337 if (ptr == nullptr)
338 {
339 std::cerr << key << " Invalid type!\n";
340 continue;
341 }
342 assetIface->register_property(key, *ptr);
343 }
344 assetIface->initialize();
345 },
346 busname, rootPath, "org.freedesktop.DBus.Properties", "GetAll",
347 assetTag);
348 }
349
350 void run(const std::string& rootPath, const std::string& busname)
Feist, Jamesc95cf672019-08-29 16:10:35 -0700351 {
James Feist09dd2312019-10-09 09:29:03 -0700352 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(),
353 O_RDWR | O_CLOEXEC);
Feist, Jamesc95cf672019-08-29 16:10:35 -0700354 if (file < 0)
355 {
356 std::cerr << "unable to open bus " << bus << "\n";
357 return;
358 }
359
360 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
361 {
362 std::cerr << "unable to set address to " << address << "\n";
363 return;
364 }
365
James Feist45772222019-09-27 10:38:08 -0700366 if (!getPresent())
367 {
368 std::cerr << "Cannot detect CPLD\n";
369 return;
370 }
371
372 getBootVer(bootVer);
373 getFPGAVer(fpgaVer);
374 getSecurityRev(securityRev);
375 std::string dbusName = boost::replace_all_copy(name, " ", "_");
Feist, Jamesc95cf672019-08-29 16:10:35 -0700376 hsbpItemIface = objServer.add_interface(
James Feist45772222019-09-27 10:38:08 -0700377 "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName,
Feist, Jamesc95cf672019-08-29 16:10:35 -0700378 inventory::interface);
James Feist45772222019-09-27 10:38:08 -0700379 hsbpItemIface->register_property("Present", true);
Feist, Jamesc95cf672019-08-29 16:10:35 -0700380 hsbpItemIface->register_property("PrettyName", name);
381 hsbpItemIface->initialize();
382
James Feistd0d36f12019-11-21 10:19:44 -0800383 storageInterface = objServer.add_interface(
384 hsbpItemIface->get_object_path(),
385 "xyz.openbmc_project.Inventory.Item.StorageController");
386 storageInterface->initialize();
387
James Feist45772222019-09-27 10:38:08 -0700388 versionIface =
James Feiste6db7832020-01-06 14:20:09 -0800389 objServer.add_interface("/xyz/openbmc_project/software/" + dbusName,
James Feist45772222019-09-27 10:38:08 -0700390 "xyz.openbmc_project.Software.Version");
391 versionIface->register_property("Version", zeroPad(bootVer) + "." +
392 zeroPad(fpgaVer) + "." +
393 zeroPad(securityRev));
394 versionIface->register_property(
395 "Purpose",
396 std::string(
397 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP"));
398 versionIface->initialize();
jayaprakash Mutyala05f8d572020-01-31 15:56:52 +0000399
400 auto activationIface =
401 objServer.add_interface("/xyz/openbmc_project/software/" + dbusName,
402 "xyz.openbmc_project.Software.Activation");
403
404 activationIface->register_property(
405 "Activation",
406 std::string(
407 "xyz.openbmc_project.Software.Activation.Activations.Active"));
408 activationIface->register_property(
409 "RequestedActivation",
410 std::string("xyz.openbmc_project.Software.Activation."
411 "RequestedActivations.None"));
412
413 activationIface->initialize();
414
James Feist45772222019-09-27 10:38:08 -0700415 getPresence(presence);
416 getIFDET(ifdet);
417
James Feistd0d36f12019-11-21 10:19:44 -0800418 populateAsset(rootPath, busname);
419
James Feist45772222019-09-27 10:38:08 -0700420 createDrives();
421
422 runTimer();
423 }
424
425 void runTimer()
426 {
427 timer->expires_after(std::chrono::seconds(scanRateSeconds));
428 timer->async_wait([this](boost::system::error_code ec) {
429 if (ec == boost::asio::error::operation_aborted)
430 {
431 // we're being destroyed
432 return;
433 }
434 else if (ec)
435 {
436 std::cerr << "timer error " << ec.message() << "\n";
437 return;
438 }
James Feist9f6565d2019-10-09 13:15:13 -0700439
440 if (!isPowerOn())
441 {
442 // can't access hsbp when power is off
443 runTimer();
444 return;
445 }
James Feist45772222019-09-27 10:38:08 -0700446 uint8_t curPresence = 0;
447 uint8_t curIFDET = 0;
448 uint8_t curFailed = 0;
James Feist244f3232019-09-27 15:15:14 -0700449 uint8_t curRebuild = 0;
James Feist45772222019-09-27 10:38:08 -0700450
451 getPresence(curPresence);
452 getIFDET(curIFDET);
453 getFailed(curFailed);
James Feist244f3232019-09-27 15:15:14 -0700454 getRebuild(curRebuild);
James Feist45772222019-09-27 10:38:08 -0700455
456 if (curPresence != presence || curIFDET != ifdet ||
James Feist244f3232019-09-27 15:15:14 -0700457 curFailed != failed || curRebuild != rebuilding)
James Feist45772222019-09-27 10:38:08 -0700458 {
459 presence = curPresence;
460 ifdet = curIFDET;
461 failed = curFailed;
James Feist244f3232019-09-27 15:15:14 -0700462 rebuilding = curRebuild;
James Feist45772222019-09-27 10:38:08 -0700463 updateDrives();
464 }
465 runTimer();
466 });
467 }
468
469 void createDrives()
470 {
471 uint8_t nvme = ifdet ^ presence;
472 for (size_t ii = 0; ii < maxDrives; ii++)
Feist, Jamesc95cf672019-08-29 16:10:35 -0700473 {
James Feist45772222019-09-27 10:38:08 -0700474 bool isNvme = nvme & (1 << ii);
475 bool isPresent = isNvme || (presence & (1 << ii));
476 bool isFailed = !isPresent || failed & (1 << ii);
James Feist244f3232019-09-27 15:15:14 -0700477 bool isRebuilding = !isPresent && (rebuilding & (1 << ii));
James Feist45772222019-09-27 10:38:08 -0700478
479 // +1 to convert from 0 based to 1 based
480 size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1;
James Feist09dd2312019-10-09 09:29:03 -0700481 Drive& drive = drives.emplace_back(driveIndex, isPresent, !isFailed,
482 isNvme, isRebuilding);
483 std::shared_ptr<Led> led = leds.emplace_back(std::make_shared<Led>(
484 drive.itemIface->get_object_path(), ii, file));
485 led->createInterface();
Feist, Jamesc95cf672019-08-29 16:10:35 -0700486 }
487 }
488
James Feist45772222019-09-27 10:38:08 -0700489 void updateDrives()
Feist, Jamesc95cf672019-08-29 16:10:35 -0700490 {
James Feist45772222019-09-27 10:38:08 -0700491
492 uint8_t nvme = ifdet ^ presence;
James Feist42b49c12019-10-29 15:18:43 -0700493 size_t ii = 0;
494
495 for (auto it = drives.begin(); it != drives.end(); it++, ii++)
Feist, Jamesc95cf672019-08-29 16:10:35 -0700496 {
James Feist45772222019-09-27 10:38:08 -0700497 bool isNvme = nvme & (1 << ii);
James Feistda0c35f2020-02-03 10:16:13 -0800498 bool wasNvme = it->isNvme || isNvme;
James Feist45772222019-09-27 10:38:08 -0700499 bool isPresent = isNvme || (presence & (1 << ii));
James Feist244f3232019-09-27 15:15:14 -0700500 bool isFailed = !isPresent || (failed & (1 << ii));
501 bool isRebuilding = isPresent && (rebuilding & (1 << ii));
James Feist45772222019-09-27 10:38:08 -0700502
James Feistda0c35f2020-02-03 10:16:13 -0800503 if (isNvme != it->isNvme)
504 {
505 rescanFruDeviceBus(bus);
506 }
507
James Feist42b49c12019-10-29 15:18:43 -0700508 it->isNvme = isNvme;
James Feistda0c35f2020-02-03 10:16:13 -0800509
510 // if it was nvme, treat it as nvme for presence
511 it->setPresent(isPresent, wasNvme);
512
James Feist42b49c12019-10-29 15:18:43 -0700513 it->rebuildingIface->set_property("Rebuilding", isRebuilding);
514 if (isFailed || isRebuilding)
515 {
516 it->markFailed();
517 }
518 else
519 {
520 it->clearFailed();
521 }
Feist, Jamesc95cf672019-08-29 16:10:35 -0700522 }
James Feist45772222019-09-27 10:38:08 -0700523 }
524
525 bool getPresent()
526 {
527 present = i2c_smbus_read_byte(file) >= 0;
Feist, Jamesc95cf672019-08-29 16:10:35 -0700528 return present;
529 }
James Feist45772222019-09-27 10:38:08 -0700530
531 bool getTypeID(uint8_t& val)
532 {
533 constexpr uint8_t addr = 2;
534 int ret = i2c_smbus_read_byte_data(file, addr);
535 if (ret < 0)
536 {
537 std::cerr << "Error " << __FUNCTION__ << "\n";
538 return false;
539 }
540 val = static_cast<uint8_t>(ret);
541 return true;
542 }
543
544 bool getBootVer(uint8_t& val)
545 {
546 constexpr uint8_t addr = 3;
547 int ret = i2c_smbus_read_byte_data(file, addr);
548 if (ret < 0)
549 {
550 std::cerr << "Error " << __FUNCTION__ << "\n";
551 return false;
552 }
553 val = static_cast<uint8_t>(ret);
554 return true;
555 }
556
557 bool getFPGAVer(uint8_t& val)
558 {
559 constexpr uint8_t addr = 4;
560 int ret = i2c_smbus_read_byte_data(file, addr);
561 if (ret < 0)
562 {
563 std::cerr << "Error " << __FUNCTION__ << "\n";
564 return false;
565 }
566 val = static_cast<uint8_t>(ret);
567 return true;
568 }
569
570 bool getSecurityRev(uint8_t& val)
571 {
572 constexpr uint8_t addr = 5;
573 int ret = i2c_smbus_read_byte_data(file, addr);
574 if (ret < 0)
575 {
576 std::cerr << "Error " << __FUNCTION__ << "\n";
577 return false;
578 }
579 val = static_cast<uint8_t>(ret);
580 return true;
581 }
582
583 bool getPresence(uint8_t& val)
584 {
585 // NVMe drives do not assert PRSNTn, and as such do not get reported as
586 // PRESENT in this register
587
588 constexpr uint8_t addr = 8;
589
590 int ret = i2c_smbus_read_byte_data(file, addr);
591 if (ret < 0)
592 {
593 std::cerr << "Error " << __FUNCTION__ << "\n";
594 return false;
595 }
596 // presence is inverted
597 val = static_cast<uint8_t>(~ret);
598 return true;
599 }
600
601 bool getIFDET(uint8_t& val)
602 {
603 // This register is a bitmap of parallel GPIO pins connected to the
604 // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert
605 // IFDETn low when they are inserted into the HSBP.This register, in
606 // combination with the PRESENCE register, are used by the BMC to detect
607 // the presence of NVMe drives.
608
609 constexpr uint8_t addr = 9;
610
611 int ret = i2c_smbus_read_byte_data(file, addr);
612 if (ret < 0)
613 {
614 std::cerr << "Error " << __FUNCTION__ << "\n";
615 return false;
616 }
617 // ifdet is inverted
618 val = static_cast<uint8_t>(~ret);
619 return true;
620 }
621
622 bool getFailed(uint8_t& val)
623 {
624 constexpr uint8_t addr = 0xC;
625 int ret = i2c_smbus_read_byte_data(file, addr);
626 if (ret < 0)
627 {
628 std::cerr << "Error " << __FUNCTION__ << "\n";
629 return false;
630 }
631 val = static_cast<uint8_t>(ret);
632 return true;
633 }
634
635 bool getRebuild(uint8_t& val)
636 {
637 constexpr uint8_t addr = 0xD;
638 int ret = i2c_smbus_read_byte_data(file, addr);
639 if (ret < 0)
640 {
641 std::cerr << "Error " << __FUNCTION__ << "\n";
642 return false;
643 }
644 val = static_cast<uint8_t>(ret);
645 return true;
646 }
647
James Feist09dd2312019-10-09 09:29:03 -0700648 virtual ~Backplane()
Feist, Jamesc95cf672019-08-29 16:10:35 -0700649 {
650 objServer.remove_interface(hsbpItemIface);
James Feist45772222019-09-27 10:38:08 -0700651 objServer.remove_interface(versionIface);
Feist, Jamesc95cf672019-08-29 16:10:35 -0700652 if (file >= 0)
653 {
654 close(file);
655 }
656 }
657
658 size_t bus;
659 size_t address;
James Feist45772222019-09-27 10:38:08 -0700660 size_t backplaneIndex;
Feist, Jamesc95cf672019-08-29 16:10:35 -0700661 std::string name;
James Feist45772222019-09-27 10:38:08 -0700662 std::shared_ptr<boost::asio::steady_timer> timer;
663 bool present = false;
664 uint8_t typeId = 0;
665 uint8_t bootVer = 0;
666 uint8_t fpgaVer = 0;
667 uint8_t securityRev = 0;
668 uint8_t funSupported = 0;
669 uint8_t presence = 0;
670 uint8_t ifdet = 0;
671 uint8_t failed = 0;
James Feist244f3232019-09-27 15:15:14 -0700672 uint8_t rebuilding = 0;
James Feist45772222019-09-27 10:38:08 -0700673
674 int file = -1;
675
Feist, Jamesc95cf672019-08-29 16:10:35 -0700676 std::string type;
677
678 std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface;
James Feist45772222019-09-27 10:38:08 -0700679 std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
James Feistd0d36f12019-11-21 10:19:44 -0800680 std::shared_ptr<sdbusplus::asio::dbus_interface> storageInterface;
681 std::shared_ptr<sdbusplus::asio::dbus_interface> assetInterface;
James Feist45772222019-09-27 10:38:08 -0700682
James Feist42b49c12019-10-29 15:18:43 -0700683 std::list<Drive> drives;
James Feist09dd2312019-10-09 09:29:03 -0700684 std::vector<std::shared_ptr<Led>> leds;
James Feist8675a912019-10-16 14:36:58 -0700685 std::shared_ptr<boost::container::flat_set<Mux>> muxes;
Feist, Jamesc95cf672019-08-29 16:10:35 -0700686};
687
688std::unordered_map<std::string, Backplane> backplanes;
James Feist42b49c12019-10-29 15:18:43 -0700689std::list<Drive> ownerlessDrives; // drives without a backplane
Feist, Jamesc95cf672019-08-29 16:10:35 -0700690
James Feist8675a912019-10-16 14:36:58 -0700691static size_t getDriveCount()
James Feistdb2e0e72019-10-07 16:34:06 -0700692{
James Feist8675a912019-10-16 14:36:58 -0700693 size_t count = 0;
694 for (const auto& [key, backplane] : backplanes)
James Feistdb2e0e72019-10-07 16:34:06 -0700695 {
James Feist8675a912019-10-16 14:36:58 -0700696 count += backplane.drives.size();
James Feistdb2e0e72019-10-07 16:34:06 -0700697 }
James Feist8675a912019-10-16 14:36:58 -0700698 return count + ownerlessDrives.size();
James Feistdb2e0e72019-10-07 16:34:06 -0700699}
700
James Feist8675a912019-10-16 14:36:58 -0700701void updateAssets()
James Feist0b236ab2019-10-02 09:09:16 -0700702{
James Feist8675a912019-10-16 14:36:58 -0700703 static constexpr const char* nvmeType =
704 "xyz.openbmc_project.Inventory.Item.NVMe";
James Feist0b236ab2019-10-02 09:09:16 -0700705
706 conn->async_method_call(
707 [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
708 if (ec)
709 {
710 std::cerr << "Error contacting mapper " << ec.message() << "\n";
711 return;
712 }
James Feist8675a912019-10-16 14:36:58 -0700713
714 // drives may get an owner during this, or we might disover more
715 // drives
716 ownerlessDrives.clear();
James Feist0b236ab2019-10-02 09:09:16 -0700717 for (const auto& [path, objDict] : subtree)
718 {
719 if (objDict.empty())
720 {
721 continue;
722 }
723
724 const std::string& owner = objDict.begin()->first;
James Feistdb2e0e72019-10-07 16:34:06 -0700725 // we export this interface too
726 if (owner == busName)
727 {
728 continue;
729 }
James Feist8675a912019-10-16 14:36:58 -0700730 if (std::find(objDict.begin()->second.begin(),
731 objDict.begin()->second.end(),
732 assetTag) == objDict.begin()->second.end())
733 {
734 // no asset tag to associate to
735 continue;
736 }
737
James Feist0b236ab2019-10-02 09:09:16 -0700738 conn->async_method_call(
James Feist8675a912019-10-16 14:36:58 -0700739 [path](const boost::system::error_code ec2,
740 const boost::container::flat_map<
741 std::string,
742 std::variant<uint64_t, std::string>>& values) {
James Feist0b236ab2019-10-02 09:09:16 -0700743 if (ec2)
744 {
745 std::cerr << "Error Getting Config "
746 << ec2.message() << " " << __FUNCTION__
747 << "\n";
748 return;
749 }
750 auto findBus = values.find("Bus");
James Feist0b236ab2019-10-02 09:09:16 -0700751
James Feist8675a912019-10-16 14:36:58 -0700752 if (findBus == values.end())
James Feist0b236ab2019-10-02 09:09:16 -0700753 {
754 std::cerr << "Illegal interface at " << path
755 << "\n";
756 return;
757 }
758
James Feist8675a912019-10-16 14:36:58 -0700759 // find the mux bus and addr
James Feist0b236ab2019-10-02 09:09:16 -0700760 size_t muxBus = static_cast<size_t>(
761 std::get<uint64_t>(findBus->second));
James Feist0b236ab2019-10-02 09:09:16 -0700762 std::filesystem::path muxPath =
763 "/sys/bus/i2c/devices/i2c-" +
764 std::to_string(muxBus) + "/mux_device";
765 if (!std::filesystem::is_symlink(muxPath))
766 {
767 std::cerr << path << " mux does not exist\n";
768 return;
769 }
770
James Feist8675a912019-10-16 14:36:58 -0700771 // we should be getting something of the form 7-0052
772 // for bus 7 addr 52
James Feist0b236ab2019-10-02 09:09:16 -0700773 std::string fname =
774 std::filesystem::read_symlink(muxPath).filename();
775 auto findDash = fname.find('-');
776
777 if (findDash == std::string::npos ||
778 findDash + 1 >= fname.size())
779 {
780 std::cerr << path << " mux path invalid\n";
781 return;
782 }
783
784 std::string busStr = fname.substr(0, findDash);
785 std::string muxStr = fname.substr(findDash + 1);
786
787 size_t bus = static_cast<size_t>(std::stoi(busStr));
788 size_t addr =
789 static_cast<size_t>(std::stoi(muxStr, nullptr, 16));
James Feist8675a912019-10-16 14:36:58 -0700790 size_t muxIndex = 0;
791
792 // find the channel of the mux the drive is on
793 std::ifstream nameFile("/sys/bus/i2c/devices/i2c-" +
794 std::to_string(muxBus) +
795 "/name");
796 if (!nameFile)
797 {
798 std::cerr << "Unable to open name file of bus "
799 << muxBus << "\n";
800 return;
801 }
802
803 std::string nameStr;
804 std::getline(nameFile, nameStr);
805
806 // file is of the form "i2c-4-mux (chan_id 1)", get chan
807 // assume single digit chan
808 const std::string prefix = "chan_id ";
809 size_t findId = nameStr.find(prefix);
810 if (findId == std::string::npos ||
811 findId + 1 >= nameStr.size())
812 {
813 std::cerr << "Illegal name file on bus " << muxBus
814 << "\n";
815 }
816
817 std::string indexStr =
818 nameStr.substr(findId + prefix.size(), 1);
819
820 size_t driveIndex = std::stoi(indexStr);
821
James Feist0b236ab2019-10-02 09:09:16 -0700822 Backplane* parent = nullptr;
823 for (auto& [name, backplane] : backplanes)
824 {
James Feist8675a912019-10-16 14:36:58 -0700825 muxIndex = 0;
James Feist0b236ab2019-10-02 09:09:16 -0700826 for (const Mux& mux : *(backplane.muxes))
827 {
828 if (bus == mux.bus && addr == mux.address)
829 {
830 parent = &backplane;
831 break;
832 }
James Feist8675a912019-10-16 14:36:58 -0700833 muxIndex += mux.channels;
James Feist0b236ab2019-10-02 09:09:16 -0700834 }
835 }
James Feist8675a912019-10-16 14:36:58 -0700836 boost::container::flat_map<std::string, std::string>
837 assetInventory;
838 const std::array<const char*, 4> assetKeys = {
839 "PartNumber", "SerialNumber", "Manufacturer",
840 "Model"};
841 for (const auto& [key, value] : values)
842 {
843 if (std::find(assetKeys.begin(), assetKeys.end(),
844 key) == assetKeys.end())
845 {
846 continue;
847 }
848 assetInventory[key] = std::get<std::string>(value);
849 }
850
851 // assume its a M.2 or something without a hsbp
James Feist0b236ab2019-10-02 09:09:16 -0700852 if (parent == nullptr)
853 {
James Feist8675a912019-10-16 14:36:58 -0700854 auto& drive = ownerlessDrives.emplace_back(
855 getDriveCount() + 1, true, true, true, false);
856 drive.createAsset(assetInventory);
James Feist0b236ab2019-10-02 09:09:16 -0700857 return;
858 }
James Feist8675a912019-10-16 14:36:58 -0700859
860 driveIndex += muxIndex;
861
James Feist0b236ab2019-10-02 09:09:16 -0700862 if (parent->drives.size() <= driveIndex)
863 {
James Feist0b236ab2019-10-02 09:09:16 -0700864 std::cerr << "Illegal drive index at " << path
865 << " " << driveIndex << "\n";
866 return;
867 }
James Feist42b49c12019-10-29 15:18:43 -0700868 auto it = parent->drives.begin();
869 std::advance(it, driveIndex);
James Feist8675a912019-10-16 14:36:58 -0700870
James Feist42b49c12019-10-29 15:18:43 -0700871 it->createAsset(assetInventory);
James Feist0b236ab2019-10-02 09:09:16 -0700872 },
873 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
James Feist8675a912019-10-16 14:36:58 -0700874 "" /*all interface items*/);
James Feist0b236ab2019-10-02 09:09:16 -0700875 }
876 },
877 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
James Feist8675a912019-10-16 14:36:58 -0700878 0, std::array<const char*, 1>{nvmeType});
James Feist0b236ab2019-10-02 09:09:16 -0700879}
880
James Feist8675a912019-10-16 14:36:58 -0700881void populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes,
James Feist0b236ab2019-10-02 09:09:16 -0700882 std::string& rootPath)
883{
884 const static std::array<const std::string, 4> muxTypes = {
885 "xyz.openbmc_project.Configuration.PCA9543Mux",
886 "xyz.openbmc_project.Configuration.PCA9544Mux",
887 "xyz.openbmc_project.Configuration.PCA9545Mux",
888 "xyz.openbmc_project.Configuration.PCA9546Mux"};
889 conn->async_method_call(
890 [muxes](const boost::system::error_code ec,
891 const GetSubTreeType& subtree) {
892 if (ec)
893 {
894 std::cerr << "Error contacting mapper " << ec.message() << "\n";
895 return;
896 }
897 std::shared_ptr<std::function<void()>> callback =
898 std::make_shared<std::function<void()>>(
James Feist8675a912019-10-16 14:36:58 -0700899 []() { updateAssets(); });
900 size_t index = 0; // as we use a flat map, these are sorted
James Feist0b236ab2019-10-02 09:09:16 -0700901 for (const auto& [path, objDict] : subtree)
902 {
903 if (objDict.empty() || objDict.begin()->second.empty())
904 {
905 continue;
906 }
907
908 const std::string& owner = objDict.begin()->first;
909 const std::vector<std::string>& interfaces =
910 objDict.begin()->second;
911
912 const std::string* interface = nullptr;
913 for (const std::string& iface : interfaces)
914 {
915 if (std::find(muxTypes.begin(), muxTypes.end(), iface) !=
916 muxTypes.end())
917 {
918 interface = &iface;
919 break;
920 }
921 }
922 if (interface == nullptr)
923 {
924 std::cerr << "Cannot get mux type\n";
925 continue;
926 }
927
928 conn->async_method_call(
James Feist8675a912019-10-16 14:36:58 -0700929 [path, muxes, callback, index](
James Feist0b236ab2019-10-02 09:09:16 -0700930 const boost::system::error_code ec2,
931 const boost::container::flat_map<
James Feist8675a912019-10-16 14:36:58 -0700932 std::string,
933 std::variant<uint64_t, std::vector<std::string>>>&
934 values) {
James Feist0b236ab2019-10-02 09:09:16 -0700935 if (ec2)
936 {
937 std::cerr << "Error Getting Config "
938 << ec2.message() << " " << __FUNCTION__
939 << "\n";
940 return;
941 }
942 auto findBus = values.find("Bus");
943 auto findAddress = values.find("Address");
James Feist8675a912019-10-16 14:36:58 -0700944 auto findChannelNames = values.find("ChannelNames");
James Feist0b236ab2019-10-02 09:09:16 -0700945 if (findBus == values.end() ||
946 findAddress == values.end())
947 {
948 std::cerr << "Illegal configuration at " << path
949 << "\n";
950 return;
951 }
952 size_t bus = static_cast<size_t>(
953 std::get<uint64_t>(findBus->second));
954 size_t address = static_cast<size_t>(
955 std::get<uint64_t>(findAddress->second));
James Feist8675a912019-10-16 14:36:58 -0700956 std::vector<std::string> channels =
957 std::get<std::vector<std::string>>(
958 findChannelNames->second);
959 muxes->emplace(bus, address, channels.size(), index);
James Feist0b236ab2019-10-02 09:09:16 -0700960 if (callback.use_count() == 1)
961 {
962 (*callback)();
963 }
964 },
965 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
966 *interface);
James Feist8675a912019-10-16 14:36:58 -0700967 index++;
James Feist0b236ab2019-10-02 09:09:16 -0700968 }
969 },
970 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
971 rootPath, 1, muxTypes);
972}
973
Feist, Jamesc95cf672019-08-29 16:10:35 -0700974void populate()
975{
976 conn->async_method_call(
977 [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
978 if (ec)
979 {
980 std::cerr << "Error contacting mapper " << ec.message() << "\n";
981 return;
982 }
983 for (const auto& [path, objDict] : subtree)
984 {
985 if (objDict.empty())
986 {
987 continue;
988 }
989
990 const std::string& owner = objDict.begin()->first;
991 conn->async_method_call(
James Feistd0d36f12019-11-21 10:19:44 -0800992 [path, owner](const boost::system::error_code ec2,
993 const boost::container::flat_map<
994 std::string, BasicVariantType>& resp) {
Feist, Jamesc95cf672019-08-29 16:10:35 -0700995 if (ec2)
996 {
997 std::cerr << "Error Getting Config "
998 << ec2.message() << "\n";
999 return;
1000 }
1001 backplanes.clear();
1002 std::optional<size_t> bus;
1003 std::optional<size_t> address;
James Feist45772222019-09-27 10:38:08 -07001004 std::optional<size_t> backplaneIndex;
Feist, Jamesc95cf672019-08-29 16:10:35 -07001005 std::optional<std::string> name;
1006 for (const auto& [key, value] : resp)
1007 {
1008 if (key == "Bus")
1009 {
1010 bus = std::get<uint64_t>(value);
1011 }
1012 else if (key == "Address")
1013 {
1014 address = std::get<uint64_t>(value);
1015 }
James Feist45772222019-09-27 10:38:08 -07001016 else if (key == "Index")
1017 {
1018 backplaneIndex = std::get<uint64_t>(value);
1019 }
Feist, Jamesc95cf672019-08-29 16:10:35 -07001020 else if (key == "Name")
1021 {
1022 name = std::get<std::string>(value);
1023 }
1024 }
James Feist45772222019-09-27 10:38:08 -07001025 if (!bus || !address || !name || !backplaneIndex)
Feist, Jamesc95cf672019-08-29 16:10:35 -07001026 {
1027 std::cerr << "Illegal configuration at " << path
1028 << "\n";
1029 return;
1030 }
James Feist0b236ab2019-10-02 09:09:16 -07001031 std::string parentPath =
1032 std::filesystem::path(path).parent_path();
Feist, Jamesc95cf672019-08-29 16:10:35 -07001033 const auto& [backplane, status] = backplanes.emplace(
James Feist45772222019-09-27 10:38:08 -07001034 *name,
1035 Backplane(*bus, *address, *backplaneIndex, *name));
James Feistd0d36f12019-11-21 10:19:44 -08001036 backplane->second.run(parentPath, owner);
James Feist0b236ab2019-10-02 09:09:16 -07001037 populateMuxes(backplane->second.muxes, parentPath);
Feist, Jamesc95cf672019-08-29 16:10:35 -07001038 },
1039 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
1040 configType);
1041 }
1042 },
1043 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
1044 0, std::array<const char*, 1>{configType});
1045}
1046
1047int main()
1048{
1049 boost::asio::steady_timer callbackTimer(io);
1050
James Feistdb2e0e72019-10-07 16:34:06 -07001051 conn->request_name(busName);
Feist, Jamesc95cf672019-08-29 16:10:35 -07001052
1053 sdbusplus::bus::match::match match(
1054 *conn,
1055 "type='signal',member='PropertiesChanged',arg0='" +
1056 std::string(configType) + "'",
1057 [&callbackTimer](sdbusplus::message::message&) {
1058 callbackTimer.expires_after(std::chrono::seconds(2));
1059 callbackTimer.async_wait([](const boost::system::error_code ec) {
1060 if (ec == boost::asio::error::operation_aborted)
1061 {
1062 // timer was restarted
1063 return;
1064 }
1065 else if (ec)
1066 {
1067 std::cerr << "Timer error" << ec.message() << "\n";
1068 return;
1069 }
1070 populate();
1071 });
1072 });
1073
James Feist0b236ab2019-10-02 09:09:16 -07001074 sdbusplus::bus::match::match drive(
1075 *conn,
1076 "type='signal',member='PropertiesChanged',arg0='xyz.openbmc_project."
James Feist8675a912019-10-16 14:36:58 -07001077 "Inventory.Item.NVMe'",
James Feistdb2e0e72019-10-07 16:34:06 -07001078 [&callbackTimer](sdbusplus::message::message& message) {
James Feist0b236ab2019-10-02 09:09:16 -07001079 callbackTimer.expires_after(std::chrono::seconds(2));
James Feistdb2e0e72019-10-07 16:34:06 -07001080 if (message.get_sender() == conn->get_unique_name())
1081 {
1082 return;
1083 }
James Feist0b236ab2019-10-02 09:09:16 -07001084 callbackTimer.async_wait([](const boost::system::error_code ec) {
1085 if (ec == boost::asio::error::operation_aborted)
1086 {
1087 // timer was restarted
1088 return;
1089 }
1090 else if (ec)
1091 {
1092 std::cerr << "Timer error" << ec.message() << "\n";
1093 return;
1094 }
1095 populate();
1096 });
1097 });
1098
James Feist42b49c12019-10-29 15:18:43 -07001099 auto iface =
1100 objServer.add_interface("/xyz/openbmc_project/inventory/item/storage",
1101 "xyz.openbmc_project.inventory.item.storage");
1102
Feist, Jamesc95cf672019-08-29 16:10:35 -07001103 io.post([]() { populate(); });
James Feist9f6565d2019-10-09 13:15:13 -07001104 setupPowerMatch(conn);
Feist, Jamesc95cf672019-08-29 16:10:35 -07001105 io.run();
1106}