blob: ca0e5416c753ee417fcc3e697e28ce8f608b6099 [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
Ed Tanous072e25d2018-12-16 21:45:20 -080040namespace fs = std::filesystem;
James Feist3cb5fec2018-01-23 14:41:51 -080041static 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{
Ed Tanous072e25d2018-12-16 21:45:20 -080062 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -080063 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
64}
65
Vijay Khemka2d681f62018-11-06 15:51:00 -080066static int isDevice16Bit(int file)
67{
68 /* Get first byte */
69 int byte1 = i2c_smbus_read_byte_data(file, 0);
70 if (byte1 < 0)
71 {
72 return byte1;
73 }
74 /* Read 7 more bytes, it will read same first byte in case of
75 * 8 bit but it will read next byte in case of 16 bit
76 */
77 for (int i = 0; i < 7; i++)
78 {
79 int byte2 = i2c_smbus_read_byte_data(file, 0);
80 if (byte2 < 0)
81 {
82 return byte2;
83 }
84 if (byte2 != byte1)
85 {
86 return 1;
87 }
88 }
89 return 0;
90}
91
92static int read_block_data(int flag, int file, uint16_t offset, uint8_t len,
93 uint8_t *buf)
94{
95 uint8_t low_addr = offset & 0xFF;
96 uint8_t high_addr = (offset >> 8) & 0xFF;
97
98 if (flag == 0)
99 {
100 return i2c_smbus_read_i2c_block_data(file, low_addr, len, buf);
101 }
102
103 /* This is for 16 bit addressing EEPROM device. First an offset
104 * needs to be written before read data from a offset
105 */
106 int ret = i2c_smbus_write_byte_data(file, 0, low_addr);
107 if (ret < 0)
108 {
109 return ret;
110 }
111
112 return i2c_smbus_read_i2c_block_data(file, high_addr, len, buf);
113}
114
James Feist3cb5fec2018-01-23 14:41:51 -0800115int get_bus_frus(int file, int first, int last, int bus,
116 std::shared_ptr<DeviceMap> devices)
117{
James Feist3cb5fec2018-01-23 14:41:51 -0800118
James Feist26c27ad2018-07-25 15:09:40 -0700119 std::future<int> future = std::async(std::launch::async, [&]() {
120 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data;
Vijay Khemka2d681f62018-11-06 15:51:00 -0800121
James Feist26c27ad2018-07-25 15:09:40 -0700122 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800123 {
James Feist3cb5fec2018-01-23 14:41:51 -0800124
James Feist26c27ad2018-07-25 15:09:40 -0700125 // Set slave address
126 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800127 {
James Feist26c27ad2018-07-25 15:09:40 -0700128 std::cerr << "device at bus " << bus << "register " << ii
129 << "busy\n";
130 continue;
131 }
132 // probe
133 else if (i2c_smbus_read_byte(file) < 0)
134 {
135 continue;
136 }
James Feist3cb5fec2018-01-23 14:41:51 -0800137
James Feist26c27ad2018-07-25 15:09:40 -0700138 if (DEBUG)
139 {
140 std::cout << "something at bus " << bus << "addr " << ii
141 << "\n";
142 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800143
144 /* Check for Device type if it is 8 bit or 16 bit */
145 int flag = isDevice16Bit(file);
146 if (flag < 0)
147 {
148 std::cerr << "failed to read bus " << bus << " address " << ii
149 << "\n";
150 continue;
151 }
152
153 if (read_block_data(flag, file, 0x0, 0x8, block_data.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700154 {
155 std::cerr << "failed to read bus " << bus << " address " << ii
156 << "\n";
157 continue;
158 }
159 size_t sum = 0;
160 for (int jj = 0; jj < 7; jj++)
161 {
162 sum += block_data[jj];
163 }
164 sum = (256 - sum) & 0xFF;
165
166 // check the header checksum
167 if (sum == block_data[7])
168 {
169 std::vector<char> device;
170 device.insert(device.end(), block_data.begin(),
171 block_data.begin() + 8);
172
173 for (int jj = 1; jj <= FRU_AREAS.size(); jj++)
174 {
175 auto area_offset = device[jj];
176 if (area_offset != 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800177 {
James Feist26c27ad2018-07-25 15:09:40 -0700178 area_offset *= 8;
Vijay Khemka2d681f62018-11-06 15:51:00 -0800179 if (read_block_data(flag, file, area_offset, 0x8,
180 block_data.data()) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800181 {
182 std::cerr << "failed to read bus " << bus
183 << " address " << ii << "\n";
184 return -1;
185 }
James Feist26c27ad2018-07-25 15:09:40 -0700186 int length = block_data[1] * 8;
James Feist3cb5fec2018-01-23 14:41:51 -0800187 device.insert(device.end(), block_data.begin(),
James Feist26c27ad2018-07-25 15:09:40 -0700188 block_data.begin() + 8);
189 length -= 8;
190 area_offset += 8;
191
192 while (length > 0)
193 {
194 auto to_get = std::min(0x20, length);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800195 if (read_block_data(flag, file, area_offset, to_get,
196 block_data.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700197 {
198 std::cerr << "failed to read bus " << bus
199 << " address " << ii << "\n";
200 return -1;
201 }
202 device.insert(device.end(), block_data.begin(),
203 block_data.begin() + to_get);
204 area_offset += to_get;
205 length -= to_get;
206 }
James Feist3cb5fec2018-01-23 14:41:51 -0800207 }
208 }
James Feist26c27ad2018-07-25 15:09:40 -0700209 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800210 }
James Feist3cb5fec2018-01-23 14:41:51 -0800211 }
James Feist26c27ad2018-07-25 15:09:40 -0700212 return 1;
213 });
214 std::future_status status =
215 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
216 if (status == std::future_status::timeout)
217 {
218 std::cerr << "Error reading bus " << bus << "\n";
219 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800220 }
221
James Feist26c27ad2018-07-25 15:09:40 -0700222 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800223}
224
James Feist6ebf9de2018-05-15 15:01:17 -0700225static void FindI2CDevices(const std::vector<fs::path> &i2cBuses,
226 std::shared_ptr<FindDevicesWithCallback> context,
227 boost::asio::io_service &io, BusMap &busMap)
James Feist3cb5fec2018-01-23 14:41:51 -0800228{
James Feist3cb5fec2018-01-23 14:41:51 -0800229 for (auto &i2cBus : i2cBuses)
230 {
231 auto busnum = i2cBus.string();
232 auto lastDash = busnum.rfind(std::string("-"));
233 // delete everything before dash inclusive
234 if (lastDash != std::string::npos)
235 {
236 busnum.erase(0, lastDash + 1);
237 }
238 auto bus = std::stoi(busnum);
239
240 auto file = open(i2cBus.c_str(), O_RDWR);
241 if (file < 0)
242 {
243 std::cerr << "unable to open i2c device " << i2cBus.string()
244 << "\n";
245 continue;
246 }
247 unsigned long funcs = 0;
248
249 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
250 {
251 std::cerr
252 << "Error: Could not get the adapter functionality matrix bus"
253 << bus << "\n";
254 continue;
255 }
256 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
257 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
258 {
259 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
260 << bus << "\n";
261 continue;
262 }
263 auto &device = busMap[bus];
264 device = std::make_shared<DeviceMap>();
265
James Feistc95cb142018-02-26 10:41:42 -0800266 // don't scan muxed buses async as don't want to confuse the mux
267 if (isMuxBus(bus))
268 {
269 get_bus_frus(file, 0x03, 0x77, bus, device);
270 close(file);
271 }
272 else
273 {
James Feist6ebf9de2018-05-15 15:01:17 -0700274 io.post([file, device, bus, context] {
275 // i2cdetect by default uses the range 0x03 to 0x77, as
276 // this is
277 // what we
278 // have tested with, use this range. Could be changed in
279 // future.
280 get_bus_frus(file, 0x03, 0x77, bus, device);
281 close(file);
282 });
James Feistc95cb142018-02-26 10:41:42 -0800283 }
James Feist3cb5fec2018-01-23 14:41:51 -0800284 }
James Feist3cb5fec2018-01-23 14:41:51 -0800285}
286
James Feist6ebf9de2018-05-15 15:01:17 -0700287// this class allows an async response after all i2c devices are discovered
288struct FindDevicesWithCallback
289 : std::enable_shared_from_this<FindDevicesWithCallback>
290{
291 FindDevicesWithCallback(const std::vector<fs::path> &i2cBuses,
292 boost::asio::io_service &io, BusMap &busMap,
293 std::function<void(void)> &&callback) :
294 _i2cBuses(i2cBuses),
295 _io(io), _busMap(busMap), _callback(std::move(callback))
296 {
297 }
298 ~FindDevicesWithCallback()
299 {
300 _callback();
301 }
302 void run()
303 {
304 FindI2CDevices(_i2cBuses, shared_from_this(), _io, _busMap);
305 }
306
307 const std::vector<fs::path> &_i2cBuses;
308 boost::asio::io_service &_io;
309 BusMap &_busMap;
310 std::function<void(void)> _callback;
311};
312
James Feist3cb5fec2018-01-23 14:41:51 -0800313static const std::tm intelEpoch(void)
314{
315 std::tm val = {0};
316 val.tm_year = 1996 - 1900;
317 return val;
318}
319
320bool formatFru(const std::vector<char> &fruBytes,
321 boost::container::flat_map<std::string, std::string> &result)
322{
323 static const std::vector<const char *> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800324 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800325
326 static const std::vector<const char *> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800327 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
328 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800329
330 static const std::vector<const char *> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800331 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
332 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
333 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800334
335 size_t sum = 0;
336
James Feistd068e932018-09-20 10:53:07 -0700337 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800338 {
339 return false;
340 }
341 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700342 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800343 std::to_string(static_cast<int>(*fruAreaOffsetField));
344
345 const std::vector<const char *> *fieldData;
346
347 for (auto &area : FRU_AREAS)
348 {
349 fruAreaOffsetField++;
350 if (fruAreaOffsetField >= fruBytes.end())
351 {
352 return false;
353 }
354 size_t offset = (*fruAreaOffsetField) * 8;
355
356 if (offset > 1)
357 {
358 // +2 to skip format and length
359 std::vector<char>::const_iterator fruBytesIter =
360 fruBytes.begin() + offset + 2;
361
362 if (fruBytesIter >= fruBytes.end())
363 {
364 return false;
365 }
366
367 if (area == "CHASSIS")
368 {
369 result["CHASSIS_TYPE"] =
370 std::to_string(static_cast<int>(*fruBytesIter));
371 fruBytesIter += 1;
372 fieldData = &CHASSIS_FRU_AREAS;
373 }
374 else if (area == "BOARD")
375 {
376 result["BOARD_LANGUAGE_CODE"] =
377 std::to_string(static_cast<int>(*fruBytesIter));
378 fruBytesIter += 1;
379 if (fruBytesIter >= fruBytes.end())
380 {
381 return false;
382 }
383
384 unsigned int minutes = *fruBytesIter |
385 *(fruBytesIter + 1) << 8 |
386 *(fruBytesIter + 2) << 16;
387 std::tm fruTime = intelEpoch();
388 time_t timeValue = mktime(&fruTime);
389 timeValue += minutes * 60;
390 fruTime = *gmtime(&timeValue);
391
392 result["BOARD_MANUFACTURE_DATE"] = asctime(&fruTime);
393 result["BOARD_MANUFACTURE_DATE"]
394 .pop_back(); // remove trailing null
395 fruBytesIter += 3;
396 fieldData = &BOARD_FRU_AREAS;
397 }
398 else if (area == "PRODUCT")
399 {
400 result["PRODUCT_LANGUAGE_CODE"] =
401 std::to_string(static_cast<int>(*fruBytesIter));
402 fruBytesIter += 1;
403 fieldData = &PRODUCT_FRU_AREAS;
404 }
405 else
406 {
407 continue;
408 }
409 for (auto &field : *fieldData)
410 {
411 if (fruBytesIter >= fruBytes.end())
412 {
413 return false;
414 }
415
416 size_t length = *fruBytesIter & 0x3f;
417 fruBytesIter += 1;
418
Vijay Khemka5d5de442018-11-07 10:51:25 -0800419 /* Checking for length if field present */
420 if (length == 0)
421 {
422 result[std::string(area) + "_" + field] =
423 std::string("Null");
424 continue;
425 }
426
427 /* Checking for last byte C1 to indicate that no more
428 * field to be read */
429 if (length == 1)
430 {
431 break;
432 }
433
James Feist3cb5fec2018-01-23 14:41:51 -0800434 if (fruBytesIter >= fruBytes.end())
435 {
436 return false;
437 }
438
439 result[std::string(area) + "_" + field] =
440 std::string(fruBytesIter, fruBytesIter + length);
441 fruBytesIter += length;
442 if (fruBytesIter >= fruBytes.end())
443 {
444 std::cerr << "Warning Fru Length Mismatch:\n ";
445 for (auto &c : fruBytes)
446 {
447 std::cerr << c;
448 }
449 std::cerr << "\n";
450 if (DEBUG)
451 {
452 for (auto &keyPair : result)
453 {
454 std::cerr << keyPair.first << " : "
455 << keyPair.second << "\n";
456 }
457 }
458 return false;
459 }
460 }
461 }
462 }
463
464 return true;
465}
466
467void AddFruObjectToDbus(
James Feist9eb0b582018-04-27 12:15:46 -0700468 std::shared_ptr<sdbusplus::asio::connection> dbusConn,
469 std::vector<char> &device, sdbusplus::asio::object_server &objServer,
James Feist3cb5fec2018-01-23 14:41:51 -0800470 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -0700471 std::shared_ptr<sdbusplus::asio::dbus_interface>>
472 &dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700473 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800474{
475 boost::container::flat_map<std::string, std::string> formattedFru;
476 if (!formatFru(device, formattedFru))
477 {
478 std::cerr << "failed to format fru for device at bus " << std::hex
479 << bus << "address " << address << "\n";
480 return;
481 }
482 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
483 std::string productName;
484 if (productNameFind == formattedFru.end())
485 {
486 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
487 }
488 if (productNameFind != formattedFru.end())
489 {
490 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800491 std::regex illegalObject("[^A-Za-z0-9_]");
492 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800493 }
494 else
495 {
496 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
497 UNKNOWN_BUS_OBJECT_COUNT++;
498 }
499
James Feist918e18c2018-02-13 15:51:07 -0800500 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800501 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700502 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800503 {
James Feist79e9c0b2018-03-15 15:21:17 -0700504 size_t index = 0;
James Feist9eb0b582018-04-27 12:15:46 -0700505 for (auto const &busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800506 {
James Feist9eb0b582018-04-27 12:15:46 -0700507 if ((busIface.second->get_object_path() == productName))
James Feist918e18c2018-02-13 15:51:07 -0800508 {
James Feist9eb0b582018-04-27 12:15:46 -0700509 if (isMuxBus(bus) && address == busIface.first.second)
James Feist79e9c0b2018-03-15 15:21:17 -0700510 {
511 continue;
512 }
513 // add underscore _index for the same object path on dbus
514 std::string strIndex = std::to_string(index);
515 if (index > 0)
516 {
517 productName.substr(0, productName.size() - strIndex.size());
518 }
519 else
520 {
521 productName += "_";
522 }
523 productName += std::to_string(index++);
James Feist918e18c2018-02-13 15:51:07 -0800524 }
525 }
526 }
James Feist3cb5fec2018-01-23 14:41:51 -0800527
James Feist9eb0b582018-04-27 12:15:46 -0700528 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
529 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
530 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
531
James Feist3cb5fec2018-01-23 14:41:51 -0800532 for (auto &property : formattedFru)
533 {
James Feist9eb0b582018-04-27 12:15:46 -0700534
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700535 std::regex_replace(property.second.begin(), property.second.begin(),
536 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700537 if (property.second.empty())
538 {
539 continue;
540 }
541 std::string key =
542 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
543 if (!iface->register_property(key, property.second + '\0'))
544 {
545 std::cerr << "illegal key: " << key << "\n";
546 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700547 if (DEBUG)
548 {
549 std::cout << property.first << ": " << property.second << "\n";
550 }
James Feist3cb5fec2018-01-23 14:41:51 -0800551 }
James Feist2a9d6db2018-04-27 15:48:28 -0700552
553 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700554 iface->register_property("BUS", bus);
555 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700556
James Feist9eb0b582018-04-27 12:15:46 -0700557 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800558}
559
560static bool readBaseboardFru(std::vector<char> &baseboardFru)
561{
562 // try to read baseboard fru from file
563 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
564 if (baseboardFruFile.good())
565 {
566 baseboardFruFile.seekg(0, std::ios_base::end);
567 std::streampos fileSize = baseboardFruFile.tellg();
568 baseboardFru.resize(fileSize);
569 baseboardFruFile.seekg(0, std::ios_base::beg);
570 baseboardFruFile.read(baseboardFru.data(), fileSize);
571 }
572 else
573 {
574 return false;
575 }
576 return true;
577}
578
James Feistb49ffc32018-05-02 11:10:43 -0700579bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t> &fru)
580{
581 boost::container::flat_map<std::string, std::string> tmp;
582 if (fru.size() > MAX_FRU_SIZE)
583 {
584 std::cerr << "Invalid fru.size() during writeFru\n";
585 return false;
586 }
587 // verify legal fru by running it through fru parsing logic
588 if (!formatFru(reinterpret_cast<const std::vector<char> &>(fru), tmp))
589 {
590 std::cerr << "Invalid fru format during writeFru\n";
591 return false;
592 }
593 // baseboard fru
594 if (bus == 0 && address == 0)
595 {
596 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
597 if (!file.good())
598 {
599 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
600 << "\n";
James Feistddb78302018-09-06 11:45:42 -0700601 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700602 return false;
603 }
604 file.write(reinterpret_cast<const char *>(fru.data()), fru.size());
605 return file.good();
606 }
607 else
608 {
609 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
610
611 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
612 if (file < 0)
613 {
614 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -0700615 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700616 return false;
617 }
618 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
619 {
620 std::cerr << "unable to set device address\n";
621 close(file);
James Feistddb78302018-09-06 11:45:42 -0700622 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700623 return false;
624 }
625
626 constexpr const size_t RETRY_MAX = 2;
627 uint16_t index = 0;
628 size_t retries = RETRY_MAX;
629 while (index < fru.size())
630 {
631 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
632 (retries == RETRY_MAX))
633 {
634 // The 4K EEPROM only uses the A2 and A1 device address bits
635 // with the third bit being a memory page address bit.
636 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
637 {
638 std::cerr << "unable to set device address\n";
639 close(file);
James Feistddb78302018-09-06 11:45:42 -0700640 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700641 return false;
642 }
643 }
644
645 if (i2c_smbus_write_byte_data(file, index & 0xFF, fru[index]) < 0)
646 {
647 if (!retries--)
648 {
649 std::cerr << "error writing fru: " << strerror(errno)
650 << "\n";
651 close(file);
James Feistddb78302018-09-06 11:45:42 -0700652 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700653 return false;
654 }
655 }
656 else
657 {
658 retries = RETRY_MAX;
659 index++;
660 }
661 // most eeproms require 5-10ms between writes
662 std::this_thread::sleep_for(std::chrono::milliseconds(10));
663 }
664 close(file);
665 return true;
666 }
667}
668
James Feist9eb0b582018-04-27 12:15:46 -0700669void rescanBusses(
James Feist6ebf9de2018-05-15 15:01:17 -0700670 boost::asio::io_service &io, BusMap &busMap,
James Feist9eb0b582018-04-27 12:15:46 -0700671 boost::container::flat_map<std::pair<size_t, size_t>,
672 std::shared_ptr<sdbusplus::asio::dbus_interface>>
673 &dbusInterfaceMap,
James Feist6ebf9de2018-05-15 15:01:17 -0700674 std::shared_ptr<sdbusplus::asio::connection> &systemBus,
675 sdbusplus::asio::object_server &objServer)
James Feist918e18c2018-02-13 15:51:07 -0800676{
James Feist6ebf9de2018-05-15 15:01:17 -0700677 static boost::asio::deadline_timer timer(io);
678 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -0800679
Gunnar Mills6f0ae942018-08-31 12:38:03 -0500680 // setup an async wait in case we get flooded with requests
James Feist6ebf9de2018-05-15 15:01:17 -0700681 timer.async_wait([&](const boost::system::error_code &ec) {
James Feist4131aea2018-03-09 09:47:30 -0800682 auto devDir = fs::path("/dev/");
683 auto matchString = std::string("i2c*");
684 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -0800685
James Feista3c180a2018-08-09 16:06:04 -0700686 if (!findFiles(devDir, matchString, i2cBuses))
James Feist918e18c2018-02-13 15:51:07 -0800687 {
James Feist4131aea2018-03-09 09:47:30 -0800688 std::cerr << "unable to find i2c devices\n";
689 return;
James Feist918e18c2018-02-13 15:51:07 -0800690 }
James Feist4131aea2018-03-09 09:47:30 -0800691 // scanning muxes in reverse order seems to have adverse effects
692 // sorting this list seems to be a fix for that
693 std::sort(i2cBuses.begin(), i2cBuses.end());
James Feist4131aea2018-03-09 09:47:30 -0800694
James Feist6ebf9de2018-05-15 15:01:17 -0700695 busMap.clear();
696 auto scan = std::make_shared<FindDevicesWithCallback>(
697 i2cBuses, io, busMap, [&]() {
698 for (auto &busIface : dbusInterfaceMap)
699 {
700 objServer.remove_interface(busIface.second);
701 }
James Feist4131aea2018-03-09 09:47:30 -0800702
James Feist6ebf9de2018-05-15 15:01:17 -0700703 dbusInterfaceMap.clear();
704 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -0800705
James Feist6ebf9de2018-05-15 15:01:17 -0700706 // todo, get this from a more sensable place
707 std::vector<char> baseboardFru;
708 if (readBaseboardFru(baseboardFru))
709 {
710 boost::container::flat_map<int, std::vector<char>>
711 baseboardDev;
712 baseboardDev.emplace(0, baseboardFru);
713 busMap[0] = std::make_shared<DeviceMap>(baseboardDev);
714 }
715 for (auto &devicemap : busMap)
716 {
717 for (auto &device : *devicemap.second)
718 {
719 AddFruObjectToDbus(systemBus, device.second, objServer,
720 dbusInterfaceMap, devicemap.first,
721 device.first);
722 }
723 }
724 });
725 scan->run();
726 });
James Feist918e18c2018-02-13 15:51:07 -0800727}
728
James Feist3cb5fec2018-01-23 14:41:51 -0800729int main(int argc, char **argv)
730{
731 auto devDir = fs::path("/dev/");
732 auto matchString = std::string("i2c*");
733 std::vector<fs::path> i2cBuses;
734
James Feista3c180a2018-08-09 16:06:04 -0700735 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -0800736 {
737 std::cerr << "unable to find i2c devices\n";
738 return 1;
739 }
James Feist3cb5fec2018-01-23 14:41:51 -0800740
741 boost::asio::io_service io;
James Feist9eb0b582018-04-27 12:15:46 -0700742 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
743 auto objServer = sdbusplus::asio::object_server(systemBus);
Vijay Khemka065f6d92018-12-18 10:37:47 -0800744 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -0800745
James Feist6ebf9de2018-05-15 15:01:17 -0700746 // this is a map with keys of pair(bus number, address) and values of
747 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -0800748 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -0700749 std::shared_ptr<sdbusplus::asio::dbus_interface>>
750 dbusInterfaceMap;
James Feist2a9d6db2018-04-27 15:48:28 -0700751 BusMap busmap;
James Feist3cb5fec2018-01-23 14:41:51 -0800752
James Feist9eb0b582018-04-27 12:15:46 -0700753 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
754 objServer.add_interface("/xyz/openbmc_project/FruDevice",
755 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -0800756
757 iface->register_method("ReScan", [&]() {
James Feist6ebf9de2018-05-15 15:01:17 -0700758 rescanBusses(io, busmap, dbusInterfaceMap, systemBus, objServer);
James Feist3cb5fec2018-01-23 14:41:51 -0800759 });
James Feist2a9d6db2018-04-27 15:48:28 -0700760
761 iface->register_method(
762 "GetRawFru", [&](const uint8_t &bus, const uint8_t &address) {
763 auto deviceMap = busmap.find(bus);
764 if (deviceMap == busmap.end())
765 {
James Feistddb78302018-09-06 11:45:42 -0700766 throw std::invalid_argument("Invalid Bus.");
James Feist2a9d6db2018-04-27 15:48:28 -0700767 }
768 auto device = deviceMap->second->find(address);
769 if (device == deviceMap->second->end())
770 {
James Feistddb78302018-09-06 11:45:42 -0700771 throw std::invalid_argument("Invalid Address.");
James Feist2a9d6db2018-04-27 15:48:28 -0700772 }
773 std::vector<uint8_t> &ret =
774 reinterpret_cast<std::vector<uint8_t> &>(device->second);
775 return ret;
776 });
James Feistb49ffc32018-05-02 11:10:43 -0700777
778 iface->register_method("WriteFru", [&](const uint8_t bus,
779 const uint8_t address,
780 const std::vector<uint8_t> &data) {
781 if (!writeFru(bus, address, data))
782 {
James Feistddb78302018-09-06 11:45:42 -0700783 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -0700784 return;
785 }
786 // schedule rescan on success
787 rescanBusses(io, busmap, dbusInterfaceMap, systemBus, objServer);
James Feistb49ffc32018-05-02 11:10:43 -0700788 });
James Feist9eb0b582018-04-27 12:15:46 -0700789 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800790
James Feist9eb0b582018-04-27 12:15:46 -0700791 std::function<void(sdbusplus::message::message & message)> eventHandler =
792 [&](sdbusplus::message::message &message) {
James Feist918e18c2018-02-13 15:51:07 -0800793 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -0700794 boost::container::flat_map<
795 std::string, sdbusplus::message::variant<
796 std::string, bool, int64_t, uint64_t, double>>
797 values;
798 message.read(objectName, values);
James Feist918e18c2018-02-13 15:51:07 -0800799 auto findPgood = values.find("pgood");
800 if (findPgood != values.end())
801 {
James Feist6ebf9de2018-05-15 15:01:17 -0700802
803 rescanBusses(io, busmap, dbusInterfaceMap, systemBus,
804 objServer);
James Feist918e18c2018-02-13 15:51:07 -0800805 }
James Feist918e18c2018-02-13 15:51:07 -0800806 };
James Feist9eb0b582018-04-27 12:15:46 -0700807
808 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
809 static_cast<sdbusplus::bus::bus &>(*systemBus),
810 "type='signal',interface='org.freedesktop.DBus.Properties',path_"
Kuiying Wang1dc43102018-05-09 15:09:29 +0800811 "namespace='/xyz/openbmc_project/Chassis/Control/"
812 "power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
James Feist9eb0b582018-04-27 12:15:46 -0700813 eventHandler);
814
James Feist4131aea2018-03-09 09:47:30 -0800815 int fd = inotify_init();
816 int wd = inotify_add_watch(fd, I2C_DEV_LOCATION,
817 IN_CREATE | IN_MOVED_TO | IN_DELETE);
818 std::array<char, 4096> readBuffer;
819 std::string pendingBuffer;
820 // monitor for new i2c devices
821 boost::asio::posix::stream_descriptor dirWatch(io, fd);
822 std::function<void(const boost::system::error_code, std::size_t)>
823 watchI2cBusses = [&](const boost::system::error_code &ec,
824 std::size_t bytes_transferred) {
825 if (ec)
826 {
827 std::cout << "Callback Error " << ec << "\n";
828 return;
829 }
830 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
831 bool devChange = false;
832 while (pendingBuffer.size() > sizeof(inotify_event))
833 {
834 const inotify_event *iEvent =
835 reinterpret_cast<const inotify_event *>(
836 pendingBuffer.data());
837 switch (iEvent->mask)
838 {
James Feist9eb0b582018-04-27 12:15:46 -0700839 case IN_CREATE:
840 case IN_MOVED_TO:
841 case IN_DELETE:
842 if (boost::starts_with(std::string(iEvent->name),
843 "i2c"))
844 {
845 devChange = true;
846 }
James Feist4131aea2018-03-09 09:47:30 -0800847 }
848
849 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
850 }
James Feist6ebf9de2018-05-15 15:01:17 -0700851 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -0800852 {
James Feist6ebf9de2018-05-15 15:01:17 -0700853 rescanBusses(io, busmap, dbusInterfaceMap, systemBus,
854 objServer);
James Feist4131aea2018-03-09 09:47:30 -0800855 }
James Feist6ebf9de2018-05-15 15:01:17 -0700856
James Feist4131aea2018-03-09 09:47:30 -0800857 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
858 watchI2cBusses);
859 };
860
861 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -0500862 // run the initial scan
James Feist6ebf9de2018-05-15 15:01:17 -0700863 rescanBusses(io, busmap, dbusInterfaceMap, systemBus, objServer);
James Feist918e18c2018-02-13 15:51:07 -0800864
James Feist3cb5fec2018-01-23 14:41:51 -0800865 io.run();
866 return 0;
867}