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