blob: 83b60e2f48732186a056757384863fd1d25e89dd [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -08001/*
2// Copyright (c) 2018 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#include <boost/container/flat_map.hpp>
19#include <ctime>
20#include <dbus/connection.hpp>
21#include <dbus/endpoint.hpp>
22#include <dbus/message.hpp>
23#include <dbus/properties.hpp>
24#include <fcntl.h>
25#include <fstream>
26#include <future>
James Feistb5320a72018-01-24 12:28:12 -080027#include <linux/i2c-dev-user.h>
James Feist3cb5fec2018-01-23 14:41:51 -080028#include <iostream>
29#include <sys/ioctl.h>
James Feist3f8a2782018-02-12 09:24:42 -080030#include <regex>
James Feist3cb5fec2018-01-23 14:41:51 -080031
32namespace fs = std::experimental::filesystem;
33static constexpr bool DEBUG = false;
James Feist918e18c2018-02-13 15:51:07 -080034static constexpr size_t SLEEP_SECONDS_AFTER_PGOOD = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080035static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
36
37const static constexpr char *BASEBOARD_FRU_LOCATION =
38 "/etc/fru/baseboard.fru.bin";
39
40static constexpr std::array<const char *, 5> FRU_AREAS = {
41 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
James Feist918e18c2018-02-13 15:51:07 -080042const static constexpr char *POWER_OBJECT_NAME = "/org/openbmc/control/power0";
James Feist3cb5fec2018-01-23 14:41:51 -080043using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
44using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
45
46int get_bus_frus(int file, int first, int last, int bus,
47 std::shared_ptr<DeviceMap> devices)
48{
49 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data;
50
51 for (int ii = first; ii <= last; ii++)
52 {
53 // Set slave address
54 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
55 {
56 std::cerr << "device at bus " << bus << "register " << ii
57 << "busy\n";
58 continue;
59 }
60 // probe
61 else if (i2c_smbus_read_byte(file) < 0)
62 {
63 continue;
64 }
65
66 if (DEBUG)
67 {
68 std::cout << "something at bus " << bus << "addr " << ii << "\n";
69 }
70 if (i2c_smbus_read_i2c_block_data(file, 0x0, 0x8, block_data.data()) <
71 0)
72 {
73 std::cerr << "failed to read bus " << bus << " address " << ii
74 << "\n";
75 continue;
76 }
77 size_t sum = 0;
78 for (int jj = 0; jj < 7; jj++)
79 {
80 sum += block_data[jj];
81 }
82 sum = (256 - sum) & 0xFF;
83
84 // check the header checksum
85 if (sum == block_data[7])
86 {
87 std::vector<char> device;
88 device.insert(device.end(), block_data.begin(),
89 block_data.begin() + 8);
90
91 for (int jj = 1; jj <= FRU_AREAS.size(); jj++)
92 {
93 auto area_offset = device[jj];
94 if (area_offset != 0)
95 {
96 area_offset *= 8;
97 if (i2c_smbus_read_i2c_block_data(file, area_offset, 0x8,
98 block_data.data()) < 0)
99 {
100 std::cerr << "failed to read bus " << bus << " address "
101 << ii << "\n";
102 return -1;
103 }
104 int length = block_data[1] * 8;
105 device.insert(device.end(), block_data.begin(),
106 block_data.begin() + 8);
107 length -= 8;
108 area_offset += 8;
109
110 while (length > 0)
111 {
112 auto to_get = std::min(0x20, length);
113 if (i2c_smbus_read_i2c_block_data(
114 file, area_offset, to_get, block_data.data()) <
115 0)
116 {
117 std::cerr << "failed to read bus " << bus
118 << " address " << ii << "\n";
119 return -1;
120 }
121 device.insert(device.end(), block_data.begin(),
122 block_data.begin() + to_get);
123 area_offset += to_get;
124 length -= to_get;
125 }
126 }
127 }
128 (*devices).emplace(ii, device);
129 }
130 }
131
132 return 0;
133}
134
135static BusMap FindI2CDevices(const std::vector<fs::path> &i2cBuses)
136{
James Feist918e18c2018-02-13 15:51:07 -0800137 std::vector<std::future<void>> futures;
James Feist3cb5fec2018-01-23 14:41:51 -0800138 BusMap busMap;
139 for (auto &i2cBus : i2cBuses)
140 {
141 auto busnum = i2cBus.string();
142 auto lastDash = busnum.rfind(std::string("-"));
143 // delete everything before dash inclusive
144 if (lastDash != std::string::npos)
145 {
146 busnum.erase(0, lastDash + 1);
147 }
148 auto bus = std::stoi(busnum);
149
150 auto file = open(i2cBus.c_str(), O_RDWR);
151 if (file < 0)
152 {
153 std::cerr << "unable to open i2c device " << i2cBus.string()
154 << "\n";
155 continue;
156 }
157 unsigned long funcs = 0;
158
159 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
160 {
161 std::cerr
162 << "Error: Could not get the adapter functionality matrix bus"
163 << bus << "\n";
164 continue;
165 }
166 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
167 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
168 {
169 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
170 << bus << "\n";
171 continue;
172 }
173 auto &device = busMap[bus];
174 device = std::make_shared<DeviceMap>();
175
176 // todo: call with boost asio?
177 futures.emplace_back(
178 std::async(std::launch::async, [file, device, bus] {
179 // i2cdetect by default uses the range 0x03 to 0x77, as this is
180 // what we
181 // have tested with, use this range. Could be changed in
182 // future.
183 get_bus_frus(file, 0x03, 0x77, bus, device);
184 close(file);
185 }));
186 }
187 for (auto &fut : futures)
188 {
189 fut.get(); // wait for all scans
190 }
191 return busMap;
192}
193
194static const std::tm intelEpoch(void)
195{
196 std::tm val = {0};
197 val.tm_year = 1996 - 1900;
198 return val;
199}
200
201bool formatFru(const std::vector<char> &fruBytes,
202 boost::container::flat_map<std::string, std::string> &result)
203{
204 static const std::vector<const char *> CHASSIS_FRU_AREAS = {
205 "PART_NUMBER", "SERIAL_NUMBER", "CHASSIS_INFO_AM1", "CHASSIS_INFO_AM2"};
206
207 static const std::vector<const char *> BOARD_FRU_AREAS = {
208 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
209 "VERSION_ID"};
210
211 static const std::vector<const char *> PRODUCT_FRU_AREAS = {
212 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
213 "PRODUCT_VERSION", "PRODUCT_SERIAL_NUMBER", "ASSET_TAG"};
214
215 size_t sum = 0;
216
217 if (fruBytes.size() < 8)
218 {
219 return false;
220 }
221 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
222 result["Common Format Version"] =
223 std::to_string(static_cast<int>(*fruAreaOffsetField));
224
225 const std::vector<const char *> *fieldData;
226
227 for (auto &area : FRU_AREAS)
228 {
229 fruAreaOffsetField++;
230 if (fruAreaOffsetField >= fruBytes.end())
231 {
232 return false;
233 }
234 size_t offset = (*fruAreaOffsetField) * 8;
235
236 if (offset > 1)
237 {
238 // +2 to skip format and length
239 std::vector<char>::const_iterator fruBytesIter =
240 fruBytes.begin() + offset + 2;
241
242 if (fruBytesIter >= fruBytes.end())
243 {
244 return false;
245 }
246
247 if (area == "CHASSIS")
248 {
249 result["CHASSIS_TYPE"] =
250 std::to_string(static_cast<int>(*fruBytesIter));
251 fruBytesIter += 1;
252 fieldData = &CHASSIS_FRU_AREAS;
253 }
254 else if (area == "BOARD")
255 {
256 result["BOARD_LANGUAGE_CODE"] =
257 std::to_string(static_cast<int>(*fruBytesIter));
258 fruBytesIter += 1;
259 if (fruBytesIter >= fruBytes.end())
260 {
261 return false;
262 }
263
264 unsigned int minutes = *fruBytesIter |
265 *(fruBytesIter + 1) << 8 |
266 *(fruBytesIter + 2) << 16;
267 std::tm fruTime = intelEpoch();
268 time_t timeValue = mktime(&fruTime);
269 timeValue += minutes * 60;
270 fruTime = *gmtime(&timeValue);
271
272 result["BOARD_MANUFACTURE_DATE"] = asctime(&fruTime);
273 result["BOARD_MANUFACTURE_DATE"]
274 .pop_back(); // remove trailing null
275 fruBytesIter += 3;
276 fieldData = &BOARD_FRU_AREAS;
277 }
278 else if (area == "PRODUCT")
279 {
280 result["PRODUCT_LANGUAGE_CODE"] =
281 std::to_string(static_cast<int>(*fruBytesIter));
282 fruBytesIter += 1;
283 fieldData = &PRODUCT_FRU_AREAS;
284 }
285 else
286 {
287 continue;
288 }
289 for (auto &field : *fieldData)
290 {
291 if (fruBytesIter >= fruBytes.end())
292 {
293 return false;
294 }
295
296 size_t length = *fruBytesIter & 0x3f;
297 fruBytesIter += 1;
298
299 if (fruBytesIter >= fruBytes.end())
300 {
301 return false;
302 }
303
304 result[std::string(area) + "_" + field] =
305 std::string(fruBytesIter, fruBytesIter + length);
306 fruBytesIter += length;
307 if (fruBytesIter >= fruBytes.end())
308 {
309 std::cerr << "Warning Fru Length Mismatch:\n ";
310 for (auto &c : fruBytes)
311 {
312 std::cerr << c;
313 }
314 std::cerr << "\n";
315 if (DEBUG)
316 {
317 for (auto &keyPair : result)
318 {
319 std::cerr << keyPair.first << " : "
320 << keyPair.second << "\n";
321 }
322 }
323 return false;
324 }
325 }
326 }
327 }
328
329 return true;
330}
331
James Feist918e18c2018-02-13 15:51:07 -0800332static bool isMuxBus(size_t bus)
333{
334 return std::experimental::filesystem::exists(
335 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device");
336}
337
James Feist3cb5fec2018-01-23 14:41:51 -0800338void AddFruObjectToDbus(
339 std::shared_ptr<dbus::connection> dbusConn, std::vector<char> &device,
340 dbus::DbusObjectServer &objServer,
341 boost::container::flat_map<std::pair<size_t, size_t>,
342 std::shared_ptr<dbus::DbusObject>>
343 &dbusObjectMap,
344 int bus, size_t address)
345{
346 boost::container::flat_map<std::string, std::string> formattedFru;
347 if (!formatFru(device, formattedFru))
348 {
349 std::cerr << "failed to format fru for device at bus " << std::hex
350 << bus << "address " << address << "\n";
351 return;
352 }
353 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
354 std::string productName;
355 if (productNameFind == formattedFru.end())
356 {
357 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
358 }
359 if (productNameFind != formattedFru.end())
360 {
361 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800362 std::regex illegalObject("[^A-Za-z0-9_]");
363 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800364 }
365 else
366 {
367 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
368 UNKNOWN_BUS_OBJECT_COUNT++;
369 }
370
James Feist918e18c2018-02-13 15:51:07 -0800371 productName = "/xyz/openbmc_project/FruDevice/" + productName;
372
373 // avoid duplicates by checking to see if on a mux
374 if (bus > 0 && isMuxBus(bus))
375 {
376 for (auto const &busObj : dbusObjectMap)
377 {
378 if ((address == busObj.first.second) &&
379 (busObj.second->object_name == productName))
380 {
381 continue;
382 }
383 }
384 }
385 auto object = objServer.add_object(productName);
James Feist3cb5fec2018-01-23 14:41:51 -0800386 dbusObjectMap[std::pair<size_t, size_t>(bus, address)] = object;
387
388 auto iface = std::make_shared<dbus::DbusInterface>(
389 "xyz.openbmc_project.FruDevice", dbusConn);
390 object->register_interface(iface);
391 for (auto &property : formattedFru)
392 {
393 iface->set_property(property.first, property.second);
394 }
395 // baseboard can set this to -1 to not set a bus / address
396 if (bus > 0)
397 {
398 std::stringstream data;
399 data << "0x" << std::hex << bus;
400 iface->set_property("BUS", data.str());
401 data.str("");
402 data << "0x" << std::hex << address;
403 iface->set_property("ADDRESS", data.str());
404 }
405}
406
407static bool readBaseboardFru(std::vector<char> &baseboardFru)
408{
409 // try to read baseboard fru from file
410 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
411 if (baseboardFruFile.good())
412 {
413 baseboardFruFile.seekg(0, std::ios_base::end);
414 std::streampos fileSize = baseboardFruFile.tellg();
415 baseboardFru.resize(fileSize);
416 baseboardFruFile.seekg(0, std::ios_base::beg);
417 baseboardFruFile.read(baseboardFru.data(), fileSize);
418 }
419 else
420 {
421 return false;
422 }
423 return true;
424}
425
James Feist918e18c2018-02-13 15:51:07 -0800426void rescanBusses(boost::container::flat_map<std::pair<size_t, size_t>,
427 std::shared_ptr<dbus::DbusObject>>
428 &dbusObjectMap,
429 std::shared_ptr<dbus::connection> systemBus,
430 dbus::DbusObjectServer &objServer)
431{
432 auto devDir = fs::path("/dev/");
433 auto matchString = std::string("i2c*");
434 std::vector<fs::path> i2cBuses;
435
436 if (!find_files(devDir, matchString, i2cBuses, 0))
437 {
438 std::cerr << "unable to find i2c devices\n";
439 return;
440 }
441 BusMap busMap = FindI2CDevices(i2cBuses);
442
443 for (auto &busObj : dbusObjectMap)
444 {
445 objServer.remove_object(busObj.second);
446 }
447
448 dbusObjectMap.clear();
449 UNKNOWN_BUS_OBJECT_COUNT = 0;
450
451 for (auto &devicemap : busMap)
452 {
453 for (auto &device : *devicemap.second)
454 {
455 AddFruObjectToDbus(systemBus, device.second, objServer,
456 dbusObjectMap, devicemap.first, device.first);
457 }
458 }
459 // todo, get this from a more sensable place
460 std::vector<char> baseboardFru;
461 if (readBaseboardFru(baseboardFru))
462 {
463 AddFruObjectToDbus(systemBus, baseboardFru, objServer, dbusObjectMap,
464 -1, -1);
465 }
466}
467
James Feist3cb5fec2018-01-23 14:41:51 -0800468int main(int argc, char **argv)
469{
470 auto devDir = fs::path("/dev/");
471 auto matchString = std::string("i2c*");
472 std::vector<fs::path> i2cBuses;
473
474 if (!find_files(devDir, matchString, i2cBuses, 0))
475 {
476 std::cerr << "unable to find i2c devices\n";
477 return 1;
478 }
479 BusMap busMap = FindI2CDevices(i2cBuses);
480
481 boost::asio::io_service io;
482 auto systemBus = std::make_shared<dbus::connection>(io, dbus::bus::system);
483 dbus::DbusObjectServer objServer(systemBus);
484 systemBus->request_name("com.intel.FruDevice");
485
486 // this is a map with keys of pair(bus number, adddress) and values of the
487 // object on dbus
488 boost::container::flat_map<std::pair<size_t, size_t>,
489 std::shared_ptr<dbus::DbusObject>>
490 dbusObjectMap;
491
James Feist918e18c2018-02-13 15:51:07 -0800492 rescanBusses(dbusObjectMap, systemBus, objServer);
James Feist3cb5fec2018-01-23 14:41:51 -0800493
494 auto object = std::make_shared<dbus::DbusObject>(
495 systemBus, "/xyz/openbmc_project/FruDevice");
496 objServer.register_object(object);
497 auto iface = std::make_shared<dbus::DbusInterface>(
498 "xyz.openbmc_project.FruDeviceManager", systemBus);
499 object->register_interface(iface);
500
James Feist918e18c2018-02-13 15:51:07 -0800501 std::atomic_bool threadRunning(false);
502 std::future<void> future;
503
James Feist3cb5fec2018-01-23 14:41:51 -0800504 iface->register_method("ReScan", [&]() {
James Feist918e18c2018-02-13 15:51:07 -0800505 if (!threadRunning)
James Feist3cb5fec2018-01-23 14:41:51 -0800506 {
James Feist918e18c2018-02-13 15:51:07 -0800507 threadRunning = true;
508 future = std::async(std::launch::async, [&] {
509 rescanBusses(dbusObjectMap, systemBus, objServer);
510 threadRunning = false;
511 });
James Feist3cb5fec2018-01-23 14:41:51 -0800512 }
James Feist3cb5fec2018-01-23 14:41:51 -0800513 return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
514 // of return
515 });
516
James Feist918e18c2018-02-13 15:51:07 -0800517 dbus::match powerChange(systemBus,
518 "type='signal',path_namespace='" +
519 std::string(POWER_OBJECT_NAME) + "'");
520
521 dbus::filter filter(systemBus, [](dbus::message &m) {
522 auto member = m.get_member();
523 return member == "PropertiesChanged";
524 });
525 std::function<void(boost::system::error_code, dbus::message)> eventHandler =
526 [&](boost::system::error_code ec, dbus::message s) {
527 boost::container::flat_map<std::string, dbus::dbus_variant> values;
528 std::string objectName;
529 s.unpack(objectName, values);
530 auto findPgood = values.find("pgood");
531 if (findPgood != values.end())
532 {
533 if (!threadRunning)
534 {
535 threadRunning = true;
536 future = std::async(std::launch::async, [&] {
537 std::this_thread::sleep_for(
538 std::chrono::seconds(SLEEP_SECONDS_AFTER_PGOOD));
539 rescanBusses(dbusObjectMap, systemBus, objServer);
540 threadRunning = false;
541 });
542 }
543 }
544 filter.async_dispatch(eventHandler);
545
546 };
547 filter.async_dispatch(eventHandler);
548
James Feist3cb5fec2018-01-23 14:41:51 -0800549 io.run();
550 return 0;
551}