blob: 03cfed6d55d2cb3c9ef84c5129ab9cd33859e0b4 [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
James Feist3b860982018-10-02 14:34:07 -070017#include <errno.h>
James Feist3cb5fec2018-01-23 14:41:51 -080018#include <fcntl.h>
James Feist3b860982018-10-02 14:34:07 -070019#include <sys/inotify.h>
20#include <sys/ioctl.h>
21
22#include <Utils.hpp>
23#include <boost/algorithm/string/predicate.hpp>
24#include <boost/container/flat_map.hpp>
25#include <chrono>
26#include <ctime>
James Feist3cb5fec2018-01-23 14:41:51 -080027#include <fstream>
28#include <future>
James Feist3cb5fec2018-01-23 14:41:51 -080029#include <iostream>
James Feist3f8a2782018-02-12 09:24:42 -080030#include <regex>
James Feist3b860982018-10-02 14:34:07 -070031#include <sdbusplus/asio/connection.hpp>
32#include <sdbusplus/asio/object_server.hpp>
33#include <thread>
34
35extern "C" {
36#include <i2c/smbus.h>
37#include <linux/i2c-dev.h>
38}
James Feist3cb5fec2018-01-23 14:41:51 -080039
40namespace fs = std::experimental::filesystem;
41static constexpr bool DEBUG = false;
42static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feistb49ffc32018-05-02 11:10:43 -070043constexpr size_t MAX_FRU_SIZE = 512;
44constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
James Feist26c27ad2018-07-25 15:09:40 -070045constexpr size_t busTimeoutSeconds = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080046
47const static constexpr char *BASEBOARD_FRU_LOCATION =
48 "/etc/fru/baseboard.fru.bin";
49
James Feist4131aea2018-03-09 09:47:30 -080050const static constexpr char *I2C_DEV_LOCATION = "/dev";
51
James Feist3cb5fec2018-01-23 14:41:51 -080052static constexpr std::array<const char *, 5> FRU_AREAS = {
53 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -070054const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
James Feist3cb5fec2018-01-23 14:41:51 -080055using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
56using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
57
James Feist6ebf9de2018-05-15 15:01:17 -070058struct FindDevicesWithCallback;
59
James Feistc95cb142018-02-26 10:41:42 -080060static bool isMuxBus(size_t bus)
61{
62 return is_symlink(std::experimental::filesystem::path(
63 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
64}
65
James Feist3cb5fec2018-01-23 14:41:51 -080066int get_bus_frus(int file, int first, int last, int bus,
67 std::shared_ptr<DeviceMap> devices)
68{
James Feist3cb5fec2018-01-23 14:41:51 -080069
James Feist26c27ad2018-07-25 15:09:40 -070070 std::future<int> future = std::async(std::launch::async, [&]() {
71 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data;
72 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -080073 {
James Feist3cb5fec2018-01-23 14:41:51 -080074
James Feist26c27ad2018-07-25 15:09:40 -070075 // Set slave address
76 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -080077 {
James Feist26c27ad2018-07-25 15:09:40 -070078 std::cerr << "device at bus " << bus << "register " << ii
79 << "busy\n";
80 continue;
81 }
82 // probe
83 else if (i2c_smbus_read_byte(file) < 0)
84 {
85 continue;
86 }
James Feist3cb5fec2018-01-23 14:41:51 -080087
James Feist26c27ad2018-07-25 15:09:40 -070088 if (DEBUG)
89 {
90 std::cout << "something at bus " << bus << "addr " << ii
91 << "\n";
92 }
93 if (i2c_smbus_read_i2c_block_data(file, 0x0, 0x8,
94 block_data.data()) < 0)
95 {
96 std::cerr << "failed to read bus " << bus << " address " << ii
97 << "\n";
98 continue;
99 }
100 size_t sum = 0;
101 for (int jj = 0; jj < 7; jj++)
102 {
103 sum += block_data[jj];
104 }
105 sum = (256 - sum) & 0xFF;
106
107 // check the header checksum
108 if (sum == block_data[7])
109 {
110 std::vector<char> device;
111 device.insert(device.end(), block_data.begin(),
112 block_data.begin() + 8);
113
114 for (int jj = 1; jj <= FRU_AREAS.size(); jj++)
115 {
116 auto area_offset = device[jj];
117 if (area_offset != 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800118 {
James Feist26c27ad2018-07-25 15:09:40 -0700119 area_offset *= 8;
James Feist3cb5fec2018-01-23 14:41:51 -0800120 if (i2c_smbus_read_i2c_block_data(
James Feist26c27ad2018-07-25 15:09:40 -0700121 file, area_offset, 0x8, block_data.data()) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800122 {
123 std::cerr << "failed to read bus " << bus
124 << " address " << ii << "\n";
125 return -1;
126 }
James Feist26c27ad2018-07-25 15:09:40 -0700127 int length = block_data[1] * 8;
James Feist3cb5fec2018-01-23 14:41:51 -0800128 device.insert(device.end(), block_data.begin(),
James Feist26c27ad2018-07-25 15:09:40 -0700129 block_data.begin() + 8);
130 length -= 8;
131 area_offset += 8;
132
133 while (length > 0)
134 {
135 auto to_get = std::min(0x20, length);
136 if (i2c_smbus_read_i2c_block_data(
137 file, area_offset, to_get,
138 block_data.data()) < 0)
139 {
140 std::cerr << "failed to read bus " << bus
141 << " address " << ii << "\n";
142 return -1;
143 }
144 device.insert(device.end(), block_data.begin(),
145 block_data.begin() + to_get);
146 area_offset += to_get;
147 length -= to_get;
148 }
James Feist3cb5fec2018-01-23 14:41:51 -0800149 }
150 }
James Feist26c27ad2018-07-25 15:09:40 -0700151 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800152 }
James Feist3cb5fec2018-01-23 14:41:51 -0800153 }
James Feist26c27ad2018-07-25 15:09:40 -0700154 return 1;
155 });
156 std::future_status status =
157 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
158 if (status == std::future_status::timeout)
159 {
160 std::cerr << "Error reading bus " << bus << "\n";
161 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800162 }
163
James Feist26c27ad2018-07-25 15:09:40 -0700164 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800165}
166
James Feist6ebf9de2018-05-15 15:01:17 -0700167static void FindI2CDevices(const std::vector<fs::path> &i2cBuses,
168 std::shared_ptr<FindDevicesWithCallback> context,
169 boost::asio::io_service &io, BusMap &busMap)
James Feist3cb5fec2018-01-23 14:41:51 -0800170{
James Feist3cb5fec2018-01-23 14:41:51 -0800171 for (auto &i2cBus : i2cBuses)
172 {
173 auto busnum = i2cBus.string();
174 auto lastDash = busnum.rfind(std::string("-"));
175 // delete everything before dash inclusive
176 if (lastDash != std::string::npos)
177 {
178 busnum.erase(0, lastDash + 1);
179 }
180 auto bus = std::stoi(busnum);
181
182 auto file = open(i2cBus.c_str(), O_RDWR);
183 if (file < 0)
184 {
185 std::cerr << "unable to open i2c device " << i2cBus.string()
186 << "\n";
187 continue;
188 }
189 unsigned long funcs = 0;
190
191 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
192 {
193 std::cerr
194 << "Error: Could not get the adapter functionality matrix bus"
195 << bus << "\n";
196 continue;
197 }
198 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
199 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
200 {
201 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
202 << bus << "\n";
203 continue;
204 }
205 auto &device = busMap[bus];
206 device = std::make_shared<DeviceMap>();
207
James Feistc95cb142018-02-26 10:41:42 -0800208 // don't scan muxed buses async as don't want to confuse the mux
209 if (isMuxBus(bus))
210 {
211 get_bus_frus(file, 0x03, 0x77, bus, device);
212 close(file);
213 }
214 else
215 {
James Feist6ebf9de2018-05-15 15:01:17 -0700216 io.post([file, device, bus, context] {
217 // i2cdetect by default uses the range 0x03 to 0x77, as
218 // this is
219 // what we
220 // have tested with, use this range. Could be changed in
221 // future.
222 get_bus_frus(file, 0x03, 0x77, bus, device);
223 close(file);
224 });
James Feistc95cb142018-02-26 10:41:42 -0800225 }
James Feist3cb5fec2018-01-23 14:41:51 -0800226 }
James Feist3cb5fec2018-01-23 14:41:51 -0800227}
228
James Feist6ebf9de2018-05-15 15:01:17 -0700229// this class allows an async response after all i2c devices are discovered
230struct FindDevicesWithCallback
231 : std::enable_shared_from_this<FindDevicesWithCallback>
232{
233 FindDevicesWithCallback(const std::vector<fs::path> &i2cBuses,
234 boost::asio::io_service &io, BusMap &busMap,
235 std::function<void(void)> &&callback) :
236 _i2cBuses(i2cBuses),
237 _io(io), _busMap(busMap), _callback(std::move(callback))
238 {
239 }
240 ~FindDevicesWithCallback()
241 {
242 _callback();
243 }
244 void run()
245 {
246 FindI2CDevices(_i2cBuses, shared_from_this(), _io, _busMap);
247 }
248
249 const std::vector<fs::path> &_i2cBuses;
250 boost::asio::io_service &_io;
251 BusMap &_busMap;
252 std::function<void(void)> _callback;
253};
254
James Feist3cb5fec2018-01-23 14:41:51 -0800255static const std::tm intelEpoch(void)
256{
257 std::tm val = {0};
258 val.tm_year = 1996 - 1900;
259 return val;
260}
261
262bool formatFru(const std::vector<char> &fruBytes,
263 boost::container::flat_map<std::string, std::string> &result)
264{
265 static const std::vector<const char *> CHASSIS_FRU_AREAS = {
266 "PART_NUMBER", "SERIAL_NUMBER", "CHASSIS_INFO_AM1", "CHASSIS_INFO_AM2"};
267
268 static const std::vector<const char *> BOARD_FRU_AREAS = {
269 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
270 "VERSION_ID"};
271
272 static const std::vector<const char *> PRODUCT_FRU_AREAS = {
273 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
274 "PRODUCT_VERSION", "PRODUCT_SERIAL_NUMBER", "ASSET_TAG"};
275
276 size_t sum = 0;
277
James Feistd068e932018-09-20 10:53:07 -0700278 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800279 {
280 return false;
281 }
282 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700283 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800284 std::to_string(static_cast<int>(*fruAreaOffsetField));
285
286 const std::vector<const char *> *fieldData;
287
288 for (auto &area : FRU_AREAS)
289 {
290 fruAreaOffsetField++;
291 if (fruAreaOffsetField >= fruBytes.end())
292 {
293 return false;
294 }
295 size_t offset = (*fruAreaOffsetField) * 8;
296
297 if (offset > 1)
298 {
299 // +2 to skip format and length
300 std::vector<char>::const_iterator fruBytesIter =
301 fruBytes.begin() + offset + 2;
302
303 if (fruBytesIter >= fruBytes.end())
304 {
305 return false;
306 }
307
308 if (area == "CHASSIS")
309 {
310 result["CHASSIS_TYPE"] =
311 std::to_string(static_cast<int>(*fruBytesIter));
312 fruBytesIter += 1;
313 fieldData = &CHASSIS_FRU_AREAS;
314 }
315 else if (area == "BOARD")
316 {
317 result["BOARD_LANGUAGE_CODE"] =
318 std::to_string(static_cast<int>(*fruBytesIter));
319 fruBytesIter += 1;
320 if (fruBytesIter >= fruBytes.end())
321 {
322 return false;
323 }
324
325 unsigned int minutes = *fruBytesIter |
326 *(fruBytesIter + 1) << 8 |
327 *(fruBytesIter + 2) << 16;
328 std::tm fruTime = intelEpoch();
329 time_t timeValue = mktime(&fruTime);
330 timeValue += minutes * 60;
331 fruTime = *gmtime(&timeValue);
332
333 result["BOARD_MANUFACTURE_DATE"] = asctime(&fruTime);
334 result["BOARD_MANUFACTURE_DATE"]
335 .pop_back(); // remove trailing null
336 fruBytesIter += 3;
337 fieldData = &BOARD_FRU_AREAS;
338 }
339 else if (area == "PRODUCT")
340 {
341 result["PRODUCT_LANGUAGE_CODE"] =
342 std::to_string(static_cast<int>(*fruBytesIter));
343 fruBytesIter += 1;
344 fieldData = &PRODUCT_FRU_AREAS;
345 }
346 else
347 {
348 continue;
349 }
350 for (auto &field : *fieldData)
351 {
352 if (fruBytesIter >= fruBytes.end())
353 {
354 return false;
355 }
356
357 size_t length = *fruBytesIter & 0x3f;
358 fruBytesIter += 1;
359
360 if (fruBytesIter >= fruBytes.end())
361 {
362 return false;
363 }
364
365 result[std::string(area) + "_" + field] =
366 std::string(fruBytesIter, fruBytesIter + length);
367 fruBytesIter += length;
368 if (fruBytesIter >= fruBytes.end())
369 {
370 std::cerr << "Warning Fru Length Mismatch:\n ";
371 for (auto &c : fruBytes)
372 {
373 std::cerr << c;
374 }
375 std::cerr << "\n";
376 if (DEBUG)
377 {
378 for (auto &keyPair : result)
379 {
380 std::cerr << keyPair.first << " : "
381 << keyPair.second << "\n";
382 }
383 }
384 return false;
385 }
386 }
387 }
388 }
389
390 return true;
391}
392
393void AddFruObjectToDbus(
James Feist9eb0b582018-04-27 12:15:46 -0700394 std::shared_ptr<sdbusplus::asio::connection> dbusConn,
395 std::vector<char> &device, sdbusplus::asio::object_server &objServer,
James Feist3cb5fec2018-01-23 14:41:51 -0800396 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -0700397 std::shared_ptr<sdbusplus::asio::dbus_interface>>
398 &dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700399 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800400{
401 boost::container::flat_map<std::string, std::string> formattedFru;
402 if (!formatFru(device, formattedFru))
403 {
404 std::cerr << "failed to format fru for device at bus " << std::hex
405 << bus << "address " << address << "\n";
406 return;
407 }
408 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
409 std::string productName;
410 if (productNameFind == formattedFru.end())
411 {
412 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
413 }
414 if (productNameFind != formattedFru.end())
415 {
416 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800417 std::regex illegalObject("[^A-Za-z0-9_]");
418 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800419 }
420 else
421 {
422 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
423 UNKNOWN_BUS_OBJECT_COUNT++;
424 }
425
James Feist918e18c2018-02-13 15:51:07 -0800426 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800427 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700428 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800429 {
James Feist79e9c0b2018-03-15 15:21:17 -0700430 size_t index = 0;
James Feist9eb0b582018-04-27 12:15:46 -0700431 for (auto const &busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800432 {
James Feist9eb0b582018-04-27 12:15:46 -0700433 if ((busIface.second->get_object_path() == productName))
James Feist918e18c2018-02-13 15:51:07 -0800434 {
James Feist9eb0b582018-04-27 12:15:46 -0700435 if (isMuxBus(bus) && address == busIface.first.second)
James Feist79e9c0b2018-03-15 15:21:17 -0700436 {
437 continue;
438 }
439 // add underscore _index for the same object path on dbus
440 std::string strIndex = std::to_string(index);
441 if (index > 0)
442 {
443 productName.substr(0, productName.size() - strIndex.size());
444 }
445 else
446 {
447 productName += "_";
448 }
449 productName += std::to_string(index++);
James Feist918e18c2018-02-13 15:51:07 -0800450 }
451 }
452 }
James Feist3cb5fec2018-01-23 14:41:51 -0800453
James Feist9eb0b582018-04-27 12:15:46 -0700454 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
455 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
456 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
457
James Feist3cb5fec2018-01-23 14:41:51 -0800458 for (auto &property : formattedFru)
459 {
James Feist9eb0b582018-04-27 12:15:46 -0700460
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700461 std::regex_replace(property.second.begin(), property.second.begin(),
462 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700463 if (property.second.empty())
464 {
465 continue;
466 }
467 std::string key =
468 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
469 if (!iface->register_property(key, property.second + '\0'))
470 {
471 std::cerr << "illegal key: " << key << "\n";
472 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700473 if (DEBUG)
474 {
475 std::cout << property.first << ": " << property.second << "\n";
476 }
James Feist3cb5fec2018-01-23 14:41:51 -0800477 }
James Feist2a9d6db2018-04-27 15:48:28 -0700478
479 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700480 iface->register_property("BUS", bus);
481 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700482
James Feist9eb0b582018-04-27 12:15:46 -0700483 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800484}
485
486static bool readBaseboardFru(std::vector<char> &baseboardFru)
487{
488 // try to read baseboard fru from file
489 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
490 if (baseboardFruFile.good())
491 {
492 baseboardFruFile.seekg(0, std::ios_base::end);
493 std::streampos fileSize = baseboardFruFile.tellg();
494 baseboardFru.resize(fileSize);
495 baseboardFruFile.seekg(0, std::ios_base::beg);
496 baseboardFruFile.read(baseboardFru.data(), fileSize);
497 }
498 else
499 {
500 return false;
501 }
502 return true;
503}
504
James Feistb49ffc32018-05-02 11:10:43 -0700505bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t> &fru)
506{
507 boost::container::flat_map<std::string, std::string> tmp;
508 if (fru.size() > MAX_FRU_SIZE)
509 {
510 std::cerr << "Invalid fru.size() during writeFru\n";
511 return false;
512 }
513 // verify legal fru by running it through fru parsing logic
514 if (!formatFru(reinterpret_cast<const std::vector<char> &>(fru), tmp))
515 {
516 std::cerr << "Invalid fru format during writeFru\n";
517 return false;
518 }
519 // baseboard fru
520 if (bus == 0 && address == 0)
521 {
522 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
523 if (!file.good())
524 {
525 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
526 << "\n";
James Feistddb78302018-09-06 11:45:42 -0700527 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700528 return false;
529 }
530 file.write(reinterpret_cast<const char *>(fru.data()), fru.size());
531 return file.good();
532 }
533 else
534 {
535 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
536
537 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
538 if (file < 0)
539 {
540 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -0700541 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700542 return false;
543 }
544 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
545 {
546 std::cerr << "unable to set device address\n";
547 close(file);
James Feistddb78302018-09-06 11:45:42 -0700548 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700549 return false;
550 }
551
552 constexpr const size_t RETRY_MAX = 2;
553 uint16_t index = 0;
554 size_t retries = RETRY_MAX;
555 while (index < fru.size())
556 {
557 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
558 (retries == RETRY_MAX))
559 {
560 // The 4K EEPROM only uses the A2 and A1 device address bits
561 // with the third bit being a memory page address bit.
562 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
563 {
564 std::cerr << "unable to set device address\n";
565 close(file);
James Feistddb78302018-09-06 11:45:42 -0700566 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700567 return false;
568 }
569 }
570
571 if (i2c_smbus_write_byte_data(file, index & 0xFF, fru[index]) < 0)
572 {
573 if (!retries--)
574 {
575 std::cerr << "error writing fru: " << strerror(errno)
576 << "\n";
577 close(file);
James Feistddb78302018-09-06 11:45:42 -0700578 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700579 return false;
580 }
581 }
582 else
583 {
584 retries = RETRY_MAX;
585 index++;
586 }
587 // most eeproms require 5-10ms between writes
588 std::this_thread::sleep_for(std::chrono::milliseconds(10));
589 }
590 close(file);
591 return true;
592 }
593}
594
James Feist9eb0b582018-04-27 12:15:46 -0700595void rescanBusses(
James Feist6ebf9de2018-05-15 15:01:17 -0700596 boost::asio::io_service &io, BusMap &busMap,
James Feist9eb0b582018-04-27 12:15:46 -0700597 boost::container::flat_map<std::pair<size_t, size_t>,
598 std::shared_ptr<sdbusplus::asio::dbus_interface>>
599 &dbusInterfaceMap,
James Feist6ebf9de2018-05-15 15:01:17 -0700600 std::shared_ptr<sdbusplus::asio::connection> &systemBus,
601 sdbusplus::asio::object_server &objServer)
James Feist918e18c2018-02-13 15:51:07 -0800602{
James Feist6ebf9de2018-05-15 15:01:17 -0700603 static boost::asio::deadline_timer timer(io);
604 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -0800605
Gunnar Mills6f0ae942018-08-31 12:38:03 -0500606 // setup an async wait in case we get flooded with requests
James Feist6ebf9de2018-05-15 15:01:17 -0700607 timer.async_wait([&](const boost::system::error_code &ec) {
James Feist4131aea2018-03-09 09:47:30 -0800608 auto devDir = fs::path("/dev/");
609 auto matchString = std::string("i2c*");
610 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -0800611
James Feista3c180a2018-08-09 16:06:04 -0700612 if (!findFiles(devDir, matchString, i2cBuses))
James Feist918e18c2018-02-13 15:51:07 -0800613 {
James Feist4131aea2018-03-09 09:47:30 -0800614 std::cerr << "unable to find i2c devices\n";
615 return;
James Feist918e18c2018-02-13 15:51:07 -0800616 }
James Feist4131aea2018-03-09 09:47:30 -0800617 // scanning muxes in reverse order seems to have adverse effects
618 // sorting this list seems to be a fix for that
619 std::sort(i2cBuses.begin(), i2cBuses.end());
James Feist4131aea2018-03-09 09:47:30 -0800620
James Feist6ebf9de2018-05-15 15:01:17 -0700621 busMap.clear();
622 auto scan = std::make_shared<FindDevicesWithCallback>(
623 i2cBuses, io, busMap, [&]() {
624 for (auto &busIface : dbusInterfaceMap)
625 {
626 objServer.remove_interface(busIface.second);
627 }
James Feist4131aea2018-03-09 09:47:30 -0800628
James Feist6ebf9de2018-05-15 15:01:17 -0700629 dbusInterfaceMap.clear();
630 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -0800631
James Feist6ebf9de2018-05-15 15:01:17 -0700632 // todo, get this from a more sensable place
633 std::vector<char> baseboardFru;
634 if (readBaseboardFru(baseboardFru))
635 {
636 boost::container::flat_map<int, std::vector<char>>
637 baseboardDev;
638 baseboardDev.emplace(0, baseboardFru);
639 busMap[0] = std::make_shared<DeviceMap>(baseboardDev);
640 }
641 for (auto &devicemap : busMap)
642 {
643 for (auto &device : *devicemap.second)
644 {
645 AddFruObjectToDbus(systemBus, device.second, objServer,
646 dbusInterfaceMap, devicemap.first,
647 device.first);
648 }
649 }
650 });
651 scan->run();
652 });
James Feist918e18c2018-02-13 15:51:07 -0800653}
654
James Feist3cb5fec2018-01-23 14:41:51 -0800655int main(int argc, char **argv)
656{
657 auto devDir = fs::path("/dev/");
658 auto matchString = std::string("i2c*");
659 std::vector<fs::path> i2cBuses;
660
James Feista3c180a2018-08-09 16:06:04 -0700661 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -0800662 {
663 std::cerr << "unable to find i2c devices\n";
664 return 1;
665 }
James Feist3cb5fec2018-01-23 14:41:51 -0800666
667 boost::asio::io_service io;
James Feist9eb0b582018-04-27 12:15:46 -0700668 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
669 auto objServer = sdbusplus::asio::object_server(systemBus);
James Feist3cb5fec2018-01-23 14:41:51 -0800670 systemBus->request_name("com.intel.FruDevice");
671
James Feist6ebf9de2018-05-15 15:01:17 -0700672 // this is a map with keys of pair(bus number, address) and values of
673 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -0800674 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -0700675 std::shared_ptr<sdbusplus::asio::dbus_interface>>
676 dbusInterfaceMap;
James Feist2a9d6db2018-04-27 15:48:28 -0700677 BusMap busmap;
James Feist3cb5fec2018-01-23 14:41:51 -0800678
James Feist9eb0b582018-04-27 12:15:46 -0700679 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
680 objServer.add_interface("/xyz/openbmc_project/FruDevice",
681 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -0800682
683 iface->register_method("ReScan", [&]() {
James Feist6ebf9de2018-05-15 15:01:17 -0700684 rescanBusses(io, busmap, dbusInterfaceMap, systemBus, objServer);
James Feist3cb5fec2018-01-23 14:41:51 -0800685 });
James Feist2a9d6db2018-04-27 15:48:28 -0700686
687 iface->register_method(
688 "GetRawFru", [&](const uint8_t &bus, const uint8_t &address) {
689 auto deviceMap = busmap.find(bus);
690 if (deviceMap == busmap.end())
691 {
James Feistddb78302018-09-06 11:45:42 -0700692 throw std::invalid_argument("Invalid Bus.");
James Feist2a9d6db2018-04-27 15:48:28 -0700693 }
694 auto device = deviceMap->second->find(address);
695 if (device == deviceMap->second->end())
696 {
James Feistddb78302018-09-06 11:45:42 -0700697 throw std::invalid_argument("Invalid Address.");
James Feist2a9d6db2018-04-27 15:48:28 -0700698 }
699 std::vector<uint8_t> &ret =
700 reinterpret_cast<std::vector<uint8_t> &>(device->second);
701 return ret;
702 });
James Feistb49ffc32018-05-02 11:10:43 -0700703
704 iface->register_method("WriteFru", [&](const uint8_t bus,
705 const uint8_t address,
706 const std::vector<uint8_t> &data) {
707 if (!writeFru(bus, address, data))
708 {
James Feistddb78302018-09-06 11:45:42 -0700709 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -0700710 return;
711 }
712 // schedule rescan on success
713 rescanBusses(io, busmap, dbusInterfaceMap, systemBus, objServer);
James Feistb49ffc32018-05-02 11:10:43 -0700714 });
James Feist9eb0b582018-04-27 12:15:46 -0700715 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800716
James Feist9eb0b582018-04-27 12:15:46 -0700717 std::function<void(sdbusplus::message::message & message)> eventHandler =
718 [&](sdbusplus::message::message &message) {
James Feist918e18c2018-02-13 15:51:07 -0800719 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -0700720 boost::container::flat_map<
721 std::string, sdbusplus::message::variant<
722 std::string, bool, int64_t, uint64_t, double>>
723 values;
724 message.read(objectName, values);
James Feist918e18c2018-02-13 15:51:07 -0800725 auto findPgood = values.find("pgood");
726 if (findPgood != values.end())
727 {
James Feist6ebf9de2018-05-15 15:01:17 -0700728
729 rescanBusses(io, busmap, dbusInterfaceMap, systemBus,
730 objServer);
James Feist918e18c2018-02-13 15:51:07 -0800731 }
James Feist918e18c2018-02-13 15:51:07 -0800732 };
James Feist9eb0b582018-04-27 12:15:46 -0700733
734 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
735 static_cast<sdbusplus::bus::bus &>(*systemBus),
736 "type='signal',interface='org.freedesktop.DBus.Properties',path_"
Kuiying Wang1dc43102018-05-09 15:09:29 +0800737 "namespace='/xyz/openbmc_project/Chassis/Control/"
738 "power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
James Feist9eb0b582018-04-27 12:15:46 -0700739 eventHandler);
740
James Feist4131aea2018-03-09 09:47:30 -0800741 int fd = inotify_init();
742 int wd = inotify_add_watch(fd, I2C_DEV_LOCATION,
743 IN_CREATE | IN_MOVED_TO | IN_DELETE);
744 std::array<char, 4096> readBuffer;
745 std::string pendingBuffer;
746 // monitor for new i2c devices
747 boost::asio::posix::stream_descriptor dirWatch(io, fd);
748 std::function<void(const boost::system::error_code, std::size_t)>
749 watchI2cBusses = [&](const boost::system::error_code &ec,
750 std::size_t bytes_transferred) {
751 if (ec)
752 {
753 std::cout << "Callback Error " << ec << "\n";
754 return;
755 }
756 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
757 bool devChange = false;
758 while (pendingBuffer.size() > sizeof(inotify_event))
759 {
760 const inotify_event *iEvent =
761 reinterpret_cast<const inotify_event *>(
762 pendingBuffer.data());
763 switch (iEvent->mask)
764 {
James Feist9eb0b582018-04-27 12:15:46 -0700765 case IN_CREATE:
766 case IN_MOVED_TO:
767 case IN_DELETE:
768 if (boost::starts_with(std::string(iEvent->name),
769 "i2c"))
770 {
771 devChange = true;
772 }
James Feist4131aea2018-03-09 09:47:30 -0800773 }
774
775 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
776 }
James Feist6ebf9de2018-05-15 15:01:17 -0700777 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -0800778 {
James Feist6ebf9de2018-05-15 15:01:17 -0700779 rescanBusses(io, busmap, dbusInterfaceMap, systemBus,
780 objServer);
James Feist4131aea2018-03-09 09:47:30 -0800781 }
James Feist6ebf9de2018-05-15 15:01:17 -0700782
James Feist4131aea2018-03-09 09:47:30 -0800783 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
784 watchI2cBusses);
785 };
786
787 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -0500788 // run the initial scan
James Feist6ebf9de2018-05-15 15:01:17 -0700789 rescanBusses(io, busmap, dbusInterfaceMap, systemBus, objServer);
James Feist918e18c2018-02-13 15:51:07 -0800790
James Feist3cb5fec2018-01-23 14:41:51 -0800791 io.run();
792 return 0;
793}