blob: e02b76a4290cc0e05603afcf0a37a27c2cf15d01 [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>
21#include <iostream>
22#include <sdbusplus/asio/connection.hpp>
23#include <sdbusplus/asio/object_server.hpp>
24#include <sdbusplus/bus/match.hpp>
25#include <string>
James Feist45772222019-09-27 10:38:08 -070026#include <utility>
Feist, Jamesc95cf672019-08-29 16:10:35 -070027
28extern "C" {
29#include <i2c/smbus.h>
30#include <linux/i2c-dev.h>
31}
32
33constexpr const char* configType =
34 "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD";
35
James Feist45772222019-09-27 10:38:08 -070036constexpr size_t scanRateSeconds = 5;
37constexpr size_t maxDrives = 8; // only 1 byte alloted
38
Feist, Jamesc95cf672019-08-29 16:10:35 -070039boost::asio::io_context io;
40auto conn = std::make_shared<sdbusplus::asio::connection>(io);
41sdbusplus::asio::object_server objServer(conn);
42
James Feist45772222019-09-27 10:38:08 -070043static std::string zeroPad(const uint8_t val)
44{
45 std::ostringstream version;
46 version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val);
47 return version.str();
48}
49
50struct Drive
51{
52 Drive(size_t driveIndex, bool isPresent, bool isOperational, bool nvme) :
53 isNvme(nvme)
54 {
55 constexpr const char* basePath =
56 "/xyz/openbmc_project/inventory/item/drive/Drive_";
57 itemIface = objServer.add_interface(
58 basePath + std::to_string(driveIndex), inventory::interface);
59 itemIface->register_property("Present", isPresent);
60 itemIface->register_property("PrettyName",
61 "Drive " + std::to_string(driveIndex));
62 itemIface->initialize();
63 operationalIface = objServer.add_interface(
64 basePath + std::to_string(driveIndex),
65 "xyz.openbmc_project.State.Decorator.OperationalStatus");
66 operationalIface->register_property("Functional", isOperational);
67 operationalIface->initialize();
68 }
69 ~Drive()
70 {
71 objServer.remove_interface(itemIface);
72 objServer.remove_interface(operationalIface);
73 }
74
75 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
76 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
77 bool isNvme;
78};
79
Feist, Jamesc95cf672019-08-29 16:10:35 -070080struct Backplane
81{
82
James Feist45772222019-09-27 10:38:08 -070083 Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn,
84 const std::string& nameIn) :
85 bus(busIn),
86 address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn),
87 timer(std::make_shared<boost::asio::steady_timer>(io))
Feist, Jamesc95cf672019-08-29 16:10:35 -070088 {
89 }
Feist, Jamesc95cf672019-08-29 16:10:35 -070090 void run()
91 {
92 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR);
93 if (file < 0)
94 {
95 std::cerr << "unable to open bus " << bus << "\n";
96 return;
97 }
98
99 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
100 {
101 std::cerr << "unable to set address to " << address << "\n";
102 return;
103 }
104
James Feist45772222019-09-27 10:38:08 -0700105 if (!getPresent())
106 {
107 std::cerr << "Cannot detect CPLD\n";
108 return;
109 }
110
111 getBootVer(bootVer);
112 getFPGAVer(fpgaVer);
113 getSecurityRev(securityRev);
114 std::string dbusName = boost::replace_all_copy(name, " ", "_");
Feist, Jamesc95cf672019-08-29 16:10:35 -0700115 hsbpItemIface = objServer.add_interface(
James Feist45772222019-09-27 10:38:08 -0700116 "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName,
Feist, Jamesc95cf672019-08-29 16:10:35 -0700117 inventory::interface);
James Feist45772222019-09-27 10:38:08 -0700118 hsbpItemIface->register_property("Present", true);
Feist, Jamesc95cf672019-08-29 16:10:35 -0700119 hsbpItemIface->register_property("PrettyName", name);
120 hsbpItemIface->initialize();
121
James Feist45772222019-09-27 10:38:08 -0700122 versionIface =
123 objServer.add_interface(hsbpItemIface->get_object_path(),
124 "xyz.openbmc_project.Software.Version");
125 versionIface->register_property("Version", zeroPad(bootVer) + "." +
126 zeroPad(fpgaVer) + "." +
127 zeroPad(securityRev));
128 versionIface->register_property(
129 "Purpose",
130 std::string(
131 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP"));
132 versionIface->initialize();
133 getPresence(presence);
134 getIFDET(ifdet);
135
136 createDrives();
137
138 runTimer();
139 }
140
141 void runTimer()
142 {
143 timer->expires_after(std::chrono::seconds(scanRateSeconds));
144 timer->async_wait([this](boost::system::error_code ec) {
145 if (ec == boost::asio::error::operation_aborted)
146 {
147 // we're being destroyed
148 return;
149 }
150 else if (ec)
151 {
152 std::cerr << "timer error " << ec.message() << "\n";
153 return;
154 }
155 uint8_t curPresence = 0;
156 uint8_t curIFDET = 0;
157 uint8_t curFailed = 0;
158
159 getPresence(curPresence);
160 getIFDET(curIFDET);
161 getFailed(curFailed);
162
163 if (curPresence != presence || curIFDET != ifdet ||
164 curFailed != failed)
165 {
166 presence = curPresence;
167 ifdet = curIFDET;
168 failed = curFailed;
169 updateDrives();
170 }
171 runTimer();
172 });
173 }
174
175 void createDrives()
176 {
177 uint8_t nvme = ifdet ^ presence;
178 for (size_t ii = 0; ii < maxDrives; ii++)
Feist, Jamesc95cf672019-08-29 16:10:35 -0700179 {
James Feist45772222019-09-27 10:38:08 -0700180 bool isNvme = nvme & (1 << ii);
181 bool isPresent = isNvme || (presence & (1 << ii));
182 bool isFailed = !isPresent || failed & (1 << ii);
183
184 // +1 to convert from 0 based to 1 based
185 size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1;
186 drives.emplace_back(driveIndex, isPresent, !isFailed, isNvme);
Feist, Jamesc95cf672019-08-29 16:10:35 -0700187 }
188 }
189
James Feist45772222019-09-27 10:38:08 -0700190 void updateDrives()
Feist, Jamesc95cf672019-08-29 16:10:35 -0700191 {
James Feist45772222019-09-27 10:38:08 -0700192
193 uint8_t nvme = ifdet ^ presence;
194 for (size_t ii = 0; ii < maxDrives; ii++)
Feist, Jamesc95cf672019-08-29 16:10:35 -0700195 {
James Feist45772222019-09-27 10:38:08 -0700196 bool isNvme = nvme & (1 << ii);
197 bool isPresent = isNvme || (presence & (1 << ii));
198 bool isFailed = !isPresent || failed & (1 << ii);
199
200 Drive& drive = drives[ii];
201 drive.isNvme = isNvme;
202 drive.itemIface->set_property("Present", isPresent);
203 drive.operationalIface->set_property("Functional", !isFailed);
Feist, Jamesc95cf672019-08-29 16:10:35 -0700204 }
James Feist45772222019-09-27 10:38:08 -0700205 }
206
207 bool getPresent()
208 {
209 present = i2c_smbus_read_byte(file) >= 0;
Feist, Jamesc95cf672019-08-29 16:10:35 -0700210 return present;
211 }
James Feist45772222019-09-27 10:38:08 -0700212
213 bool getTypeID(uint8_t& val)
214 {
215 constexpr uint8_t addr = 2;
216 int ret = i2c_smbus_read_byte_data(file, addr);
217 if (ret < 0)
218 {
219 std::cerr << "Error " << __FUNCTION__ << "\n";
220 return false;
221 }
222 val = static_cast<uint8_t>(ret);
223 return true;
224 }
225
226 bool getBootVer(uint8_t& val)
227 {
228 constexpr uint8_t addr = 3;
229 int ret = i2c_smbus_read_byte_data(file, addr);
230 if (ret < 0)
231 {
232 std::cerr << "Error " << __FUNCTION__ << "\n";
233 return false;
234 }
235 val = static_cast<uint8_t>(ret);
236 return true;
237 }
238
239 bool getFPGAVer(uint8_t& val)
240 {
241 constexpr uint8_t addr = 4;
242 int ret = i2c_smbus_read_byte_data(file, addr);
243 if (ret < 0)
244 {
245 std::cerr << "Error " << __FUNCTION__ << "\n";
246 return false;
247 }
248 val = static_cast<uint8_t>(ret);
249 return true;
250 }
251
252 bool getSecurityRev(uint8_t& val)
253 {
254 constexpr uint8_t addr = 5;
255 int ret = i2c_smbus_read_byte_data(file, addr);
256 if (ret < 0)
257 {
258 std::cerr << "Error " << __FUNCTION__ << "\n";
259 return false;
260 }
261 val = static_cast<uint8_t>(ret);
262 return true;
263 }
264
265 bool getPresence(uint8_t& val)
266 {
267 // NVMe drives do not assert PRSNTn, and as such do not get reported as
268 // PRESENT in this register
269
270 constexpr uint8_t addr = 8;
271
272 int ret = i2c_smbus_read_byte_data(file, addr);
273 if (ret < 0)
274 {
275 std::cerr << "Error " << __FUNCTION__ << "\n";
276 return false;
277 }
278 // presence is inverted
279 val = static_cast<uint8_t>(~ret);
280 return true;
281 }
282
283 bool getIFDET(uint8_t& val)
284 {
285 // This register is a bitmap of parallel GPIO pins connected to the
286 // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert
287 // IFDETn low when they are inserted into the HSBP.This register, in
288 // combination with the PRESENCE register, are used by the BMC to detect
289 // the presence of NVMe drives.
290
291 constexpr uint8_t addr = 9;
292
293 int ret = i2c_smbus_read_byte_data(file, addr);
294 if (ret < 0)
295 {
296 std::cerr << "Error " << __FUNCTION__ << "\n";
297 return false;
298 }
299 // ifdet is inverted
300 val = static_cast<uint8_t>(~ret);
301 return true;
302 }
303
304 bool getFailed(uint8_t& val)
305 {
306 constexpr uint8_t addr = 0xC;
307 int ret = i2c_smbus_read_byte_data(file, addr);
308 if (ret < 0)
309 {
310 std::cerr << "Error " << __FUNCTION__ << "\n";
311 return false;
312 }
313 val = static_cast<uint8_t>(ret);
314 return true;
315 }
316
317 bool getRebuild(uint8_t& val)
318 {
319 constexpr uint8_t addr = 0xD;
320 int ret = i2c_smbus_read_byte_data(file, addr);
321 if (ret < 0)
322 {
323 std::cerr << "Error " << __FUNCTION__ << "\n";
324 return false;
325 }
326 val = static_cast<uint8_t>(ret);
327 return true;
328 }
329
Feist, Jamesc95cf672019-08-29 16:10:35 -0700330 ~Backplane()
331 {
332 objServer.remove_interface(hsbpItemIface);
James Feist45772222019-09-27 10:38:08 -0700333 objServer.remove_interface(versionIface);
Feist, Jamesc95cf672019-08-29 16:10:35 -0700334 if (file >= 0)
335 {
336 close(file);
337 }
338 }
339
340 size_t bus;
341 size_t address;
James Feist45772222019-09-27 10:38:08 -0700342 size_t backplaneIndex;
Feist, Jamesc95cf672019-08-29 16:10:35 -0700343 std::string name;
James Feist45772222019-09-27 10:38:08 -0700344 std::shared_ptr<boost::asio::steady_timer> timer;
345 bool present = false;
346 uint8_t typeId = 0;
347 uint8_t bootVer = 0;
348 uint8_t fpgaVer = 0;
349 uint8_t securityRev = 0;
350 uint8_t funSupported = 0;
351 uint8_t presence = 0;
352 uint8_t ifdet = 0;
353 uint8_t failed = 0;
354
355 int file = -1;
356
Feist, Jamesc95cf672019-08-29 16:10:35 -0700357 std::string type;
358
359 std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface;
James Feist45772222019-09-27 10:38:08 -0700360 std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
361
362 std::vector<Drive> drives;
Feist, Jamesc95cf672019-08-29 16:10:35 -0700363};
364
365std::unordered_map<std::string, Backplane> backplanes;
366
367void populate()
368{
369 conn->async_method_call(
370 [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
371 if (ec)
372 {
373 std::cerr << "Error contacting mapper " << ec.message() << "\n";
374 return;
375 }
376 for (const auto& [path, objDict] : subtree)
377 {
378 if (objDict.empty())
379 {
380 continue;
381 }
382
383 const std::string& owner = objDict.begin()->first;
384 conn->async_method_call(
385 [path](const boost::system::error_code ec2,
386 const boost::container::flat_map<
387 std::string, BasicVariantType>& resp) {
388 if (ec2)
389 {
390 std::cerr << "Error Getting Config "
391 << ec2.message() << "\n";
392 return;
393 }
394 backplanes.clear();
395 std::optional<size_t> bus;
396 std::optional<size_t> address;
James Feist45772222019-09-27 10:38:08 -0700397 std::optional<size_t> backplaneIndex;
Feist, Jamesc95cf672019-08-29 16:10:35 -0700398 std::optional<std::string> name;
399 for (const auto& [key, value] : resp)
400 {
401 if (key == "Bus")
402 {
403 bus = std::get<uint64_t>(value);
404 }
405 else if (key == "Address")
406 {
407 address = std::get<uint64_t>(value);
408 }
James Feist45772222019-09-27 10:38:08 -0700409 else if (key == "Index")
410 {
411 backplaneIndex = std::get<uint64_t>(value);
412 }
Feist, Jamesc95cf672019-08-29 16:10:35 -0700413 else if (key == "Name")
414 {
415 name = std::get<std::string>(value);
416 }
417 }
James Feist45772222019-09-27 10:38:08 -0700418 if (!bus || !address || !name || !backplaneIndex)
Feist, Jamesc95cf672019-08-29 16:10:35 -0700419 {
420 std::cerr << "Illegal configuration at " << path
421 << "\n";
422 return;
423 }
424 const auto& [backplane, status] = backplanes.emplace(
James Feist45772222019-09-27 10:38:08 -0700425 *name,
426 Backplane(*bus, *address, *backplaneIndex, *name));
Feist, Jamesc95cf672019-08-29 16:10:35 -0700427 backplane->second.run();
428 },
429 owner, path, "org.freedesktop.DBus.Properties", "GetAll",
430 configType);
431 }
432 },
433 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
434 0, std::array<const char*, 1>{configType});
435}
436
437int main()
438{
439 boost::asio::steady_timer callbackTimer(io);
440
441 conn->request_name("xyz.openbmc_project.HsbpManager");
442
443 sdbusplus::bus::match::match match(
444 *conn,
445 "type='signal',member='PropertiesChanged',arg0='" +
446 std::string(configType) + "'",
447 [&callbackTimer](sdbusplus::message::message&) {
448 callbackTimer.expires_after(std::chrono::seconds(2));
449 callbackTimer.async_wait([](const boost::system::error_code ec) {
450 if (ec == boost::asio::error::operation_aborted)
451 {
452 // timer was restarted
453 return;
454 }
455 else if (ec)
456 {
457 std::cerr << "Timer error" << ec.message() << "\n";
458 return;
459 }
460 populate();
461 });
462 });
463
464 io.post([]() { populate(); });
465 io.run();
466}