blob: a7fe02b0839e7b1e487df751cf47664104bc163c [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>
Patrick Venturee3754002019-08-06 09:39:12 -070027#include <filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080028#include <fstream>
29#include <future>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070030#include <iomanip>
James Feist3cb5fec2018-01-23 14:41:51 -080031#include <iostream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070032#include <nlohmann/json.hpp>
James Feist3f8a2782018-02-12 09:24:42 -080033#include <regex>
James Feist3b860982018-10-02 14:34:07 -070034#include <sdbusplus/asio/connection.hpp>
35#include <sdbusplus/asio/object_server.hpp>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070036#include <set>
37#include <sstream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070038#include <string>
James Feist3b860982018-10-02 14:34:07 -070039#include <thread>
James Feista465ccc2019-02-08 12:51:01 -080040#include <variant>
James Feist3b860982018-10-02 14:34:07 -070041
42extern "C" {
43#include <i2c/smbus.h>
44#include <linux/i2c-dev.h>
45}
James Feist3cb5fec2018-01-23 14:41:51 -080046
Ed Tanous072e25d2018-12-16 21:45:20 -080047namespace fs = std::filesystem;
James Feist3cb5fec2018-01-23 14:41:51 -080048static constexpr bool DEBUG = false;
49static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feistb49ffc32018-05-02 11:10:43 -070050constexpr size_t MAX_FRU_SIZE = 512;
51constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
James Feist26c27ad2018-07-25 15:09:40 -070052constexpr size_t busTimeoutSeconds = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080053
Patrick Venture11f1ff42019-08-01 10:42:12 -070054constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
55
James Feista465ccc2019-02-08 12:51:01 -080056const static constexpr char* BASEBOARD_FRU_LOCATION =
James Feist3cb5fec2018-01-23 14:41:51 -080057 "/etc/fru/baseboard.fru.bin";
58
James Feista465ccc2019-02-08 12:51:01 -080059const static constexpr char* I2C_DEV_LOCATION = "/dev";
James Feist4131aea2018-03-09 09:47:30 -080060
James Feista465ccc2019-02-08 12:51:01 -080061static constexpr std::array<const char*, 5> FRU_AREAS = {
James Feist3cb5fec2018-01-23 14:41:51 -080062 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -070063const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
James Feist3cb5fec2018-01-23 14:41:51 -080064using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
65using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
66
James Feist444830e2019-04-05 08:38:16 -070067static std::set<size_t> busBlacklist;
James Feist6ebf9de2018-05-15 15:01:17 -070068struct FindDevicesWithCallback;
69
Nikhil Potaded8884f12019-03-27 13:27:13 -070070static BusMap busMap;
71
Patrick Venturec50e1ff2019-08-06 10:22:28 -070072// Given a bus/address, produce the path in sysfs for an eeprom.
73static std::string getEepromPath(size_t bus, size_t address)
74{
75 std::stringstream output;
76 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
77 << std::setfill('0') << std::setw(4) << std::hex << address
78 << "/eeprom";
79 return output.str();
80}
81
82static bool hasEepromFile(size_t bus, size_t address)
83{
84 auto path = getEepromPath(bus, address);
85 try
86 {
87 return fs::exists(path);
88 }
89 catch (...)
90 {
91 return false;
92 }
93}
94
95static ssize_t readFromEeprom(int fd, uint16_t offset, uint8_t len,
96 uint8_t* buf)
97{
98 auto result = lseek(fd, offset, SEEK_SET);
99 if (result < 0)
100 {
101 std::cerr << "failed to seek\n";
102 return -1;
103 }
104
105 return read(fd, buf, len);
106}
107
James Feistc95cb142018-02-26 10:41:42 -0800108static bool isMuxBus(size_t bus)
109{
Ed Tanous072e25d2018-12-16 21:45:20 -0800110 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -0800111 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
112}
113
Vijay Khemka2d681f62018-11-06 15:51:00 -0800114static int isDevice16Bit(int file)
115{
116 /* Get first byte */
117 int byte1 = i2c_smbus_read_byte_data(file, 0);
118 if (byte1 < 0)
119 {
120 return byte1;
121 }
122 /* Read 7 more bytes, it will read same first byte in case of
123 * 8 bit but it will read next byte in case of 16 bit
124 */
125 for (int i = 0; i < 7; i++)
126 {
127 int byte2 = i2c_smbus_read_byte_data(file, 0);
128 if (byte2 < 0)
129 {
130 return byte2;
131 }
132 if (byte2 != byte1)
133 {
134 return 1;
135 }
136 }
137 return 0;
138}
139
140static int read_block_data(int flag, int file, uint16_t offset, uint8_t len,
James Feista465ccc2019-02-08 12:51:01 -0800141 uint8_t* buf)
Vijay Khemka2d681f62018-11-06 15:51:00 -0800142{
James Feist98132792019-07-09 13:29:09 -0700143 uint8_t low_addr = static_cast<uint8_t>(offset);
144 uint8_t high_addr = static_cast<uint8_t>(offset >> 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800145
146 if (flag == 0)
147 {
148 return i2c_smbus_read_i2c_block_data(file, low_addr, len, buf);
149 }
150
151 /* This is for 16 bit addressing EEPROM device. First an offset
152 * needs to be written before read data from a offset
153 */
154 int ret = i2c_smbus_write_byte_data(file, 0, low_addr);
155 if (ret < 0)
156 {
157 return ret;
158 }
159
160 return i2c_smbus_read_i2c_block_data(file, high_addr, len, buf);
161}
162
James Feist24bae7a2019-04-03 09:50:56 -0700163bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
164{
165 // ipmi spec format version number is currently at 1, verify it
166 if (blockData[0] != 0x1)
167 {
168 return false;
169 }
170
171 // verify pad is set to 0
172 if (blockData[6] != 0x0)
173 {
174 return false;
175 }
176
177 // verify offsets are 0, or don't point to another offset
178 std::set<uint8_t> foundOffsets;
179 for (int ii = 1; ii < 6; ii++)
180 {
181 if (blockData[ii] == 0)
182 {
183 continue;
184 }
James Feist0eb40352019-04-09 14:44:04 -0700185 auto inserted = foundOffsets.insert(blockData[ii]);
186 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700187 {
188 return false;
189 }
190 }
191
192 // validate checksum
193 size_t sum = 0;
194 for (int jj = 0; jj < 7; jj++)
195 {
196 sum += blockData[jj];
197 }
198 sum = (256 - sum) & 0xFF;
199
200 if (sum != blockData[7])
201 {
202 return false;
203 }
204 return true;
205}
206
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700207// TODO: This code is very similar to the non-eeprom version and can be merged
208// with some tweaks.
209static std::vector<char> processEeprom(int bus, int address)
210{
211 std::vector<char> device;
212 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data;
213
214 auto path = getEepromPath(bus, address);
215
216 int file = open(path.c_str(), O_RDONLY);
217 if (file < 0)
218 {
219 std::cerr << "Unable to open eeprom file: " << path << "\n";
220 return device;
221 }
222
223 ssize_t readBytes = readFromEeprom(file, 0, 0x8, block_data.data());
224 if (readBytes < 0)
225 {
226 std::cerr << "failed to read eeprom at " << bus << " address "
227 << address << "\n";
228 close(file);
229 return device;
230 }
231
232 if (!validateHeader(block_data))
233 {
234 if (DEBUG)
235 {
236 std::cerr << "Illegal header at bus " << bus << " address "
237 << address << "\n";
238 }
239
240 close(file);
241 return device;
242 }
243
244 device.insert(device.end(), block_data.begin(), block_data.begin() + 8);
245
246 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
247 {
248 auto area_offset = device[jj];
249 if (area_offset == 0)
250 {
251 continue;
252 }
253
254 area_offset = static_cast<char>(area_offset * 8);
255 if (readFromEeprom(file, area_offset, 0x8, block_data.data()) < 0)
256 {
257 std::cerr << "failed to read bus " << bus << " address " << address
258 << "\n";
259 device.clear();
260 close(file);
261 return device;
262 }
263
264 int length = block_data[1] * 8;
265 device.insert(device.end(), block_data.begin(), block_data.begin() + 8);
266 length -= 8;
267 area_offset = static_cast<char>(area_offset + 8);
268
269 while (length > 0)
270 {
271 auto to_get = std::min(0x20, length);
272
273 if (readFromEeprom(file, area_offset, static_cast<uint8_t>(to_get),
274 block_data.data()) < 0)
275 {
276 std::cerr << "failed to read bus " << bus << " address "
277 << address << "\n";
278 device.clear();
279 close(file);
280 return device;
281 }
282
283 device.insert(device.end(), block_data.begin(),
284 block_data.begin() + to_get);
285 area_offset = static_cast<char>(area_offset + to_get);
286 length -= to_get;
287 }
288 }
289
290 close(file);
291 return device;
292}
293
294std::set<int> findI2CEeproms(int i2cBus, std::shared_ptr<DeviceMap> devices)
295{
296 std::set<int> foundList;
297
298 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
299
300 // For each file listed under the i2c device
301 // NOTE: This should be faster than just checking for each possible address
302 // path.
303 for (const auto& p : fs::directory_iterator(path))
304 {
305 const std::string node = p.path().string();
306 std::smatch m;
307 bool found =
308 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
309
310 if (!found)
311 {
312 continue;
313 }
314 if (m.size() != 2)
315 {
316 std::cerr << "regex didn't capture\n";
317 continue;
318 }
319
320 std::ssub_match subMatch = m[1];
321 std::string addressString = subMatch.str();
322
323 std::size_t ignored;
324 const int hexBase = 16;
325 int address = std::stoi(addressString, &ignored, hexBase);
326
327 const std::string eeprom = node + "/eeprom";
328
329 try
330 {
331 if (!fs::exists(eeprom))
332 {
333 continue;
334 }
335 }
336 catch (...)
337 {
338 continue;
339 }
340
341 // There is an eeprom file at this address, it may have invalid
342 // contents, but we found it.
343 foundList.insert(address);
344
345 std::vector<char> device = processEeprom(i2cBus, address);
346 if (!device.empty())
347 {
348 devices->emplace(address, device);
349 }
350 }
351
352 return foundList;
353}
354
James Feist3cb5fec2018-01-23 14:41:51 -0800355int get_bus_frus(int file, int first, int last, int bus,
356 std::shared_ptr<DeviceMap> devices)
357{
James Feist3cb5fec2018-01-23 14:41:51 -0800358
James Feist26c27ad2018-07-25 15:09:40 -0700359 std::future<int> future = std::async(std::launch::async, [&]() {
360 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data;
Vijay Khemka2d681f62018-11-06 15:51:00 -0800361
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700362 // NOTE: When reading the devices raw on the bus, it can interfere with
363 // the driver's ability to operate, therefore read eeproms first before
364 // scanning for devices without drivers. Several experiments were run
365 // and it was determined that if there were any devices on the bus
366 // before the eeprom was hit and read, the eeprom driver wouldn't open
367 // while the bus device was open. An experiment was not performed to see
368 // if this issue was resolved if the i2c bus device was closed, but
369 // hexdumps of the eeprom later were successful.
370
371 // Scan for i2c eeproms loaded on this bus.
372 std::set<int> skipList = findI2CEeproms(bus, devices);
373
James Feist26c27ad2018-07-25 15:09:40 -0700374 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800375 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700376 if (skipList.find(ii) != skipList.end())
377 {
378 continue;
379 }
James Feist3cb5fec2018-01-23 14:41:51 -0800380
James Feist26c27ad2018-07-25 15:09:40 -0700381 // Set slave address
382 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800383 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700384 std::cerr << "device at bus " << bus << " register " << ii
Patrick Venture5d8b61d2019-08-06 12:36:10 -0700385 << " busy\n";
James Feist26c27ad2018-07-25 15:09:40 -0700386 continue;
387 }
388 // probe
389 else if (i2c_smbus_read_byte(file) < 0)
390 {
391 continue;
392 }
James Feist3cb5fec2018-01-23 14:41:51 -0800393
James Feist26c27ad2018-07-25 15:09:40 -0700394 if (DEBUG)
395 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700396 std::cout << "something at bus " << bus << " addr " << ii
James Feist26c27ad2018-07-25 15:09:40 -0700397 << "\n";
398 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800399
400 /* Check for Device type if it is 8 bit or 16 bit */
401 int flag = isDevice16Bit(file);
402 if (flag < 0)
403 {
404 std::cerr << "failed to read bus " << bus << " address " << ii
405 << "\n";
406 continue;
407 }
408
409 if (read_block_data(flag, file, 0x0, 0x8, block_data.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700410 {
411 std::cerr << "failed to read bus " << bus << " address " << ii
412 << "\n";
413 continue;
414 }
James Feist26c27ad2018-07-25 15:09:40 -0700415
416 // check the header checksum
Patrick Venture786f1792019-08-05 16:33:44 -0700417 if (!validateHeader(block_data))
James Feist26c27ad2018-07-25 15:09:40 -0700418 {
Patrick Venture786f1792019-08-05 16:33:44 -0700419 if (DEBUG)
James Feist26c27ad2018-07-25 15:09:40 -0700420 {
Patrick Venture786f1792019-08-05 16:33:44 -0700421 std::cerr << "Illegal header at bus " << bus << " address "
422 << ii << "\n";
423 }
424 continue;
425 }
426
427 std::vector<char> device;
428 device.insert(device.end(), block_data.begin(),
429 block_data.begin() + 8);
430
431 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
432 {
433 auto area_offset = device[jj];
Patrick Venture64fd7e22019-08-05 16:38:20 -0700434 if (area_offset == 0)
Patrick Venture786f1792019-08-05 16:33:44 -0700435 {
Patrick Venture64fd7e22019-08-05 16:38:20 -0700436 continue;
437 }
438
439 area_offset = static_cast<char>(area_offset * 8);
440 if (read_block_data(flag, file, area_offset, 0x8,
441 block_data.data()) < 0)
442 {
443 std::cerr << "failed to read bus " << bus << " address "
444 << ii << "\n";
445 return -1;
446 }
447 int length = block_data[1] * 8;
448 device.insert(device.end(), block_data.begin(),
449 block_data.begin() + 8);
450 length -= 8;
451 area_offset = static_cast<char>(area_offset + 8);
452
453 while (length > 0)
454 {
455 auto to_get = std::min(0x20, length);
456
457 if (read_block_data(flag, file, area_offset,
458 static_cast<uint8_t>(to_get),
Patrick Venture786f1792019-08-05 16:33:44 -0700459 block_data.data()) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800460 {
Patrick Venture786f1792019-08-05 16:33:44 -0700461 std::cerr << "failed to read bus " << bus << " address "
462 << ii << "\n";
463 return -1;
464 }
Patrick Venture786f1792019-08-05 16:33:44 -0700465 device.insert(device.end(), block_data.begin(),
Patrick Venture64fd7e22019-08-05 16:38:20 -0700466 block_data.begin() + to_get);
467 area_offset = static_cast<char>(area_offset + to_get);
468 length -= to_get;
James Feist3cb5fec2018-01-23 14:41:51 -0800469 }
470 }
Patrick Venture786f1792019-08-05 16:33:44 -0700471 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800472 }
James Feist26c27ad2018-07-25 15:09:40 -0700473 return 1;
474 });
475 std::future_status status =
476 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
477 if (status == std::future_status::timeout)
478 {
479 std::cerr << "Error reading bus " << bus << "\n";
James Feist444830e2019-04-05 08:38:16 -0700480 busBlacklist.insert(bus);
481 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700482 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800483 }
484
James Feist444830e2019-04-05 08:38:16 -0700485 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700486 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800487}
488
Patrick Venture11f1ff42019-08-01 10:42:12 -0700489void loadBlacklist(const char* path)
490{
491 std::ifstream blacklistStream(path);
492 if (!blacklistStream.good())
493 {
494 // File is optional.
495 std::cerr << "Cannot open blacklist file.\n\n";
496 return;
497 }
498
499 nlohmann::json data =
500 nlohmann::json::parse(blacklistStream, nullptr, false);
501 if (data.is_discarded())
502 {
503 std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
504 "exiting\n";
505 std::exit(EXIT_FAILURE);
506 return;
507 }
508
509 // It's expected to have at least one field, "buses" that is an array of the
510 // buses by integer. Allow for future options to exclude further aspects,
511 // such as specific addresses or ranges.
512 if (data.type() != nlohmann::json::value_t::object)
513 {
514 std::cerr << "Illegal blacklist, expected to read dictionary\n";
515 std::exit(EXIT_FAILURE);
516 return;
517 }
518
519 // If buses field is missing, that's fine.
520 if (data.count("buses") == 1)
521 {
522 // Parse the buses array after a little validation.
523 auto buses = data.at("buses");
524 if (buses.type() != nlohmann::json::value_t::array)
525 {
526 // Buses field present but invalid, therefore this is an error.
527 std::cerr << "Invalid contents for blacklist buses field\n";
528 std::exit(EXIT_FAILURE);
529 return;
530 }
531
532 // Catch exception here for type mis-match.
533 try
534 {
535 for (const auto& bus : buses)
536 {
537 busBlacklist.insert(bus.get<size_t>());
538 }
539 }
540 catch (const nlohmann::detail::type_error& e)
541 {
542 // Type mis-match is a critical error.
543 std::cerr << "Invalid bus type: " << e.what() << "\n";
544 std::exit(EXIT_FAILURE);
545 return;
546 }
547 }
548
549 return;
550}
551
James Feista465ccc2019-02-08 12:51:01 -0800552static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700553 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800554{
James Feista465ccc2019-02-08 12:51:01 -0800555 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800556 {
557 auto busnum = i2cBus.string();
558 auto lastDash = busnum.rfind(std::string("-"));
559 // delete everything before dash inclusive
560 if (lastDash != std::string::npos)
561 {
562 busnum.erase(0, lastDash + 1);
563 }
564 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700565 if (busBlacklist.find(bus) != busBlacklist.end())
566 {
567 continue; // skip previously failed busses
568 }
James Feist3cb5fec2018-01-23 14:41:51 -0800569
570 auto file = open(i2cBus.c_str(), O_RDWR);
571 if (file < 0)
572 {
573 std::cerr << "unable to open i2c device " << i2cBus.string()
574 << "\n";
575 continue;
576 }
577 unsigned long funcs = 0;
578
579 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
580 {
581 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700582 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800583 << bus << "\n";
584 continue;
585 }
586 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
587 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
588 {
589 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
590 << bus << "\n";
591 continue;
592 }
James Feist98132792019-07-09 13:29:09 -0700593 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800594 device = std::make_shared<DeviceMap>();
595
Nikhil Potaded8884f12019-03-27 13:27:13 -0700596 // i2cdetect by default uses the range 0x03 to 0x77, as
597 // this is what we have tested with, use this range. Could be
598 // changed in future.
599 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800600 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700601 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800602 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700603
604 // fd is closed in this function in case the bus locks up
605 get_bus_frus(file, 0x03, 0x77, bus, device);
606
607 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800608 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700609 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800610 }
James Feist3cb5fec2018-01-23 14:41:51 -0800611 }
James Feist3cb5fec2018-01-23 14:41:51 -0800612}
613
James Feist6ebf9de2018-05-15 15:01:17 -0700614// this class allows an async response after all i2c devices are discovered
615struct FindDevicesWithCallback
616 : std::enable_shared_from_this<FindDevicesWithCallback>
617{
James Feista465ccc2019-02-08 12:51:01 -0800618 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700619 boost::asio::io_service& io, BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800620 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700621 _i2cBuses(i2cBuses),
James Feist98132792019-07-09 13:29:09 -0700622 _io(io), _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700623 {
624 }
625 ~FindDevicesWithCallback()
626 {
627 _callback();
628 }
629 void run()
630 {
James Feist98132792019-07-09 13:29:09 -0700631 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700632 }
633
James Feista465ccc2019-02-08 12:51:01 -0800634 const std::vector<fs::path>& _i2cBuses;
635 boost::asio::io_service& _io;
636 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700637 std::function<void(void)> _callback;
638};
639
James Feist3cb5fec2018-01-23 14:41:51 -0800640static const std::tm intelEpoch(void)
641{
James Feist98132792019-07-09 13:29:09 -0700642 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800643 val.tm_year = 1996 - 1900;
644 return val;
645}
646
James Feista465ccc2019-02-08 12:51:01 -0800647bool formatFru(const std::vector<char>& fruBytes,
648 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800649{
James Feista465ccc2019-02-08 12:51:01 -0800650 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800651 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800652
James Feista465ccc2019-02-08 12:51:01 -0800653 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800654 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
655 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800656
James Feista465ccc2019-02-08 12:51:01 -0800657 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800658 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
659 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
660 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800661
James Feistd068e932018-09-20 10:53:07 -0700662 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800663 {
664 return false;
665 }
666 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700667 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800668 std::to_string(static_cast<int>(*fruAreaOffsetField));
669
James Feista465ccc2019-02-08 12:51:01 -0800670 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800671
James Feist0eb40352019-04-09 14:44:04 -0700672 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800673 {
674 fruAreaOffsetField++;
675 if (fruAreaOffsetField >= fruBytes.end())
676 {
677 return false;
678 }
679 size_t offset = (*fruAreaOffsetField) * 8;
680
681 if (offset > 1)
682 {
683 // +2 to skip format and length
684 std::vector<char>::const_iterator fruBytesIter =
685 fruBytes.begin() + offset + 2;
686
687 if (fruBytesIter >= fruBytes.end())
688 {
689 return false;
690 }
691
692 if (area == "CHASSIS")
693 {
694 result["CHASSIS_TYPE"] =
695 std::to_string(static_cast<int>(*fruBytesIter));
696 fruBytesIter += 1;
697 fieldData = &CHASSIS_FRU_AREAS;
698 }
699 else if (area == "BOARD")
700 {
701 result["BOARD_LANGUAGE_CODE"] =
702 std::to_string(static_cast<int>(*fruBytesIter));
703 fruBytesIter += 1;
704 if (fruBytesIter >= fruBytes.end())
705 {
706 return false;
707 }
708
709 unsigned int minutes = *fruBytesIter |
710 *(fruBytesIter + 1) << 8 |
711 *(fruBytesIter + 2) << 16;
712 std::tm fruTime = intelEpoch();
713 time_t timeValue = mktime(&fruTime);
714 timeValue += minutes * 60;
715 fruTime = *gmtime(&timeValue);
716
717 result["BOARD_MANUFACTURE_DATE"] = asctime(&fruTime);
718 result["BOARD_MANUFACTURE_DATE"]
719 .pop_back(); // remove trailing null
720 fruBytesIter += 3;
721 fieldData = &BOARD_FRU_AREAS;
722 }
723 else if (area == "PRODUCT")
724 {
725 result["PRODUCT_LANGUAGE_CODE"] =
726 std::to_string(static_cast<int>(*fruBytesIter));
727 fruBytesIter += 1;
728 fieldData = &PRODUCT_FRU_AREAS;
729 }
730 else
731 {
732 continue;
733 }
James Feista465ccc2019-02-08 12:51:01 -0800734 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800735 {
736 if (fruBytesIter >= fruBytes.end())
737 {
738 return false;
739 }
740
Vijay Khemka5d5de442018-11-07 10:51:25 -0800741 /* Checking for last byte C1 to indicate that no more
742 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700743 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800744 {
745 break;
746 }
747
Ed Tanous2147e672019-02-27 13:59:56 -0800748 size_t length = *fruBytesIter & 0x3f;
749 fruBytesIter += 1;
750
James Feist3cb5fec2018-01-23 14:41:51 -0800751 if (fruBytesIter >= fruBytes.end())
752 {
753 return false;
754 }
Ed Tanous2147e672019-02-27 13:59:56 -0800755 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800756
Ed Tanous2147e672019-02-27 13:59:56 -0800757 // Strip non null characters from the end
758 value.erase(std::find_if(value.rbegin(), value.rend(),
759 [](char ch) { return ch != 0; })
760 .base(),
761 value.end());
762
James Feist0eb40352019-04-09 14:44:04 -0700763 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800764
James Feist3cb5fec2018-01-23 14:41:51 -0800765 fruBytesIter += length;
766 if (fruBytesIter >= fruBytes.end())
767 {
768 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800769 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800770 {
771 std::cerr << c;
772 }
773 std::cerr << "\n";
774 if (DEBUG)
775 {
James Feista465ccc2019-02-08 12:51:01 -0800776 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800777 {
778 std::cerr << keyPair.first << " : "
779 << keyPair.second << "\n";
780 }
781 }
782 return false;
783 }
784 }
785 }
786 }
787
788 return true;
789}
790
Nikhil Potaded8884f12019-03-27 13:27:13 -0700791std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
792{
793 auto deviceMap = busMap.find(bus);
794 if (deviceMap == busMap.end())
795 {
796 throw std::invalid_argument("Invalid Bus.");
797 }
798 auto device = deviceMap->second->find(address);
799 if (device == deviceMap->second->end())
800 {
801 throw std::invalid_argument("Invalid Address.");
802 }
803 std::vector<uint8_t>& ret =
804 reinterpret_cast<std::vector<uint8_t>&>(device->second);
805
806 return ret;
807}
808
James Feist3cb5fec2018-01-23 14:41:51 -0800809void AddFruObjectToDbus(
James Feista465ccc2019-02-08 12:51:01 -0800810 std::vector<char>& device, sdbusplus::asio::object_server& objServer,
811 boost::container::flat_map<
812 std::pair<size_t, size_t>,
813 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700814 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800815{
816 boost::container::flat_map<std::string, std::string> formattedFru;
817 if (!formatFru(device, formattedFru))
818 {
819 std::cerr << "failed to format fru for device at bus " << std::hex
Nikhil Potaded8884f12019-03-27 13:27:13 -0700820 << bus << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800821 return;
822 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700823
James Feist3cb5fec2018-01-23 14:41:51 -0800824 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
825 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700826 // Not found under Board section or an empty string.
827 if (productNameFind == formattedFru.end() ||
828 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800829 {
830 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
831 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700832 // Found under Product section and not an empty string.
833 if (productNameFind != formattedFru.end() &&
834 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800835 {
836 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800837 std::regex illegalObject("[^A-Za-z0-9_]");
838 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800839 }
840 else
841 {
842 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
843 UNKNOWN_BUS_OBJECT_COUNT++;
844 }
845
James Feist918e18c2018-02-13 15:51:07 -0800846 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800847 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700848 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800849 {
James Feist79e9c0b2018-03-15 15:21:17 -0700850 size_t index = 0;
James Feista465ccc2019-02-08 12:51:01 -0800851 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800852 {
James Feist9eb0b582018-04-27 12:15:46 -0700853 if ((busIface.second->get_object_path() == productName))
James Feist918e18c2018-02-13 15:51:07 -0800854 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700855 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700856 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
857 static_cast<uint8_t>(busIface.first.second)) ==
858 getFruInfo(static_cast<uint8_t>(bus),
859 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700860 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700861 // This device is already added to the lower numbered bus,
862 // do not replicate it.
863 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700864 }
865 // add underscore _index for the same object path on dbus
866 std::string strIndex = std::to_string(index);
867 if (index > 0)
868 {
869 productName.substr(0, productName.size() - strIndex.size());
870 }
871 else
872 {
873 productName += "_";
874 }
875 productName += std::to_string(index++);
James Feist918e18c2018-02-13 15:51:07 -0800876 }
877 }
878 }
James Feist3cb5fec2018-01-23 14:41:51 -0800879
James Feist9eb0b582018-04-27 12:15:46 -0700880 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
881 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
882 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
883
James Feista465ccc2019-02-08 12:51:01 -0800884 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800885 {
James Feist9eb0b582018-04-27 12:15:46 -0700886
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700887 std::regex_replace(property.second.begin(), property.second.begin(),
888 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700889 if (property.second.empty())
890 {
891 continue;
892 }
893 std::string key =
894 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
895 if (!iface->register_property(key, property.second + '\0'))
896 {
897 std::cerr << "illegal key: " << key << "\n";
898 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700899 if (DEBUG)
900 {
901 std::cout << property.first << ": " << property.second << "\n";
902 }
James Feist3cb5fec2018-01-23 14:41:51 -0800903 }
James Feist2a9d6db2018-04-27 15:48:28 -0700904
905 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700906 iface->register_property("BUS", bus);
907 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700908
James Feist9eb0b582018-04-27 12:15:46 -0700909 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800910}
911
James Feista465ccc2019-02-08 12:51:01 -0800912static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800913{
914 // try to read baseboard fru from file
915 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
916 if (baseboardFruFile.good())
917 {
918 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -0700919 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -0800920 baseboardFru.resize(fileSize);
921 baseboardFruFile.seekg(0, std::ios_base::beg);
922 baseboardFruFile.read(baseboardFru.data(), fileSize);
923 }
924 else
925 {
926 return false;
927 }
928 return true;
929}
930
James Feista465ccc2019-02-08 12:51:01 -0800931bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -0700932{
933 boost::container::flat_map<std::string, std::string> tmp;
934 if (fru.size() > MAX_FRU_SIZE)
935 {
936 std::cerr << "Invalid fru.size() during writeFru\n";
937 return false;
938 }
939 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -0800940 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -0700941 {
942 std::cerr << "Invalid fru format during writeFru\n";
943 return false;
944 }
945 // baseboard fru
946 if (bus == 0 && address == 0)
947 {
948 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
949 if (!file.good())
950 {
951 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
952 << "\n";
James Feistddb78302018-09-06 11:45:42 -0700953 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700954 return false;
955 }
James Feista465ccc2019-02-08 12:51:01 -0800956 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -0700957 return file.good();
958 }
959 else
960 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700961 if (hasEepromFile(bus, address))
962 {
963 auto path = getEepromPath(bus, address);
964 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
965 if (eeprom < 0)
966 {
967 std::cerr << "unable to open i2c device " << path << "\n";
968 throw DBusInternalError();
969 return false;
970 }
971
972 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
973 if (writtenBytes < 0)
974 {
975 std::cerr << "unable to write to i2c device " << path << "\n";
976 close(eeprom);
977 throw DBusInternalError();
978 return false;
979 }
980
981 close(eeprom);
982 return true;
983 }
984
James Feistb49ffc32018-05-02 11:10:43 -0700985 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
986
987 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
988 if (file < 0)
989 {
990 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -0700991 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700992 return false;
993 }
994 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
995 {
996 std::cerr << "unable to set device address\n";
997 close(file);
James Feistddb78302018-09-06 11:45:42 -0700998 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700999 return false;
1000 }
1001
1002 constexpr const size_t RETRY_MAX = 2;
1003 uint16_t index = 0;
1004 size_t retries = RETRY_MAX;
1005 while (index < fru.size())
1006 {
1007 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1008 (retries == RETRY_MAX))
1009 {
1010 // The 4K EEPROM only uses the A2 and A1 device address bits
1011 // with the third bit being a memory page address bit.
1012 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1013 {
1014 std::cerr << "unable to set device address\n";
1015 close(file);
James Feistddb78302018-09-06 11:45:42 -07001016 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001017 return false;
1018 }
1019 }
1020
James Feist98132792019-07-09 13:29:09 -07001021 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1022 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001023 {
1024 if (!retries--)
1025 {
1026 std::cerr << "error writing fru: " << strerror(errno)
1027 << "\n";
1028 close(file);
James Feistddb78302018-09-06 11:45:42 -07001029 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001030 return false;
1031 }
1032 }
1033 else
1034 {
1035 retries = RETRY_MAX;
1036 index++;
1037 }
1038 // most eeproms require 5-10ms between writes
1039 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1040 }
1041 close(file);
1042 return true;
1043 }
1044}
1045
James Feist9eb0b582018-04-27 12:15:46 -07001046void rescanBusses(
James Feist98132792019-07-09 13:29:09 -07001047 boost::asio::io_service& io, BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001048 boost::container::flat_map<
1049 std::pair<size_t, size_t>,
1050 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feista465ccc2019-02-08 12:51:01 -08001051 sdbusplus::asio::object_server& objServer)
James Feist918e18c2018-02-13 15:51:07 -08001052{
James Feist6ebf9de2018-05-15 15:01:17 -07001053 static boost::asio::deadline_timer timer(io);
1054 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001055
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001056 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001057 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001058 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001059 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001060
Nikhil Potaded8884f12019-03-27 13:27:13 -07001061 boost::container::flat_map<size_t, fs::path> busPaths;
1062 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001063 {
James Feist4131aea2018-03-09 09:47:30 -08001064 std::cerr << "unable to find i2c devices\n";
1065 return;
James Feist918e18c2018-02-13 15:51:07 -08001066 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001067
1068 for (auto busPath : busPaths)
1069 {
1070 i2cBuses.emplace_back(busPath.second);
1071 }
James Feist4131aea2018-03-09 09:47:30 -08001072
James Feist98132792019-07-09 13:29:09 -07001073 busmap.clear();
James Feist6ebf9de2018-05-15 15:01:17 -07001074 auto scan = std::make_shared<FindDevicesWithCallback>(
James Feist98132792019-07-09 13:29:09 -07001075 i2cBuses, io, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001076 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001077 {
1078 objServer.remove_interface(busIface.second);
1079 }
James Feist4131aea2018-03-09 09:47:30 -08001080
James Feist6ebf9de2018-05-15 15:01:17 -07001081 dbusInterfaceMap.clear();
1082 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001083
James Feist6ebf9de2018-05-15 15:01:17 -07001084 // todo, get this from a more sensable place
1085 std::vector<char> baseboardFru;
1086 if (readBaseboardFru(baseboardFru))
1087 {
1088 boost::container::flat_map<int, std::vector<char>>
1089 baseboardDev;
1090 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -07001091 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001092 }
James Feist98132792019-07-09 13:29:09 -07001093 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001094 {
James Feista465ccc2019-02-08 12:51:01 -08001095 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001096 {
James Feist98132792019-07-09 13:29:09 -07001097 AddFruObjectToDbus(device.second, objServer,
James Feist6ebf9de2018-05-15 15:01:17 -07001098 dbusInterfaceMap, devicemap.first,
1099 device.first);
1100 }
1101 }
1102 });
1103 scan->run();
1104 });
James Feist918e18c2018-02-13 15:51:07 -08001105}
1106
James Feist98132792019-07-09 13:29:09 -07001107int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001108{
1109 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001110 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001111 std::vector<fs::path> i2cBuses;
1112
James Feista3c180a2018-08-09 16:06:04 -07001113 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001114 {
1115 std::cerr << "unable to find i2c devices\n";
1116 return 1;
1117 }
James Feist3cb5fec2018-01-23 14:41:51 -08001118
Patrick Venture11f1ff42019-08-01 10:42:12 -07001119 // check for and load blacklist with initial buses.
1120 loadBlacklist(blacklistPath);
1121
James Feist3cb5fec2018-01-23 14:41:51 -08001122 boost::asio::io_service io;
James Feist9eb0b582018-04-27 12:15:46 -07001123 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1124 auto objServer = sdbusplus::asio::object_server(systemBus);
Vijay Khemka065f6d92018-12-18 10:37:47 -08001125 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001126
James Feist6ebf9de2018-05-15 15:01:17 -07001127 // this is a map with keys of pair(bus number, address) and values of
1128 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001129 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001130 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1131 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001132
James Feist9eb0b582018-04-27 12:15:46 -07001133 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1134 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1135 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001136
1137 iface->register_method("ReScan", [&]() {
James Feist98132792019-07-09 13:29:09 -07001138 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist3cb5fec2018-01-23 14:41:51 -08001139 });
James Feist2a9d6db2018-04-27 15:48:28 -07001140
Nikhil Potaded8884f12019-03-27 13:27:13 -07001141 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001142
1143 iface->register_method("WriteFru", [&](const uint8_t bus,
1144 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001145 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -07001146 if (!writeFru(bus, address, data))
1147 {
James Feistddb78302018-09-06 11:45:42 -07001148 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001149 return;
1150 }
1151 // schedule rescan on success
James Feist98132792019-07-09 13:29:09 -07001152 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feistb49ffc32018-05-02 11:10:43 -07001153 });
James Feist9eb0b582018-04-27 12:15:46 -07001154 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001155
James Feist9eb0b582018-04-27 12:15:46 -07001156 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08001157 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08001158 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07001159 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08001160 std::string,
1161 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07001162 values;
1163 message.read(objectName, values);
James Feist918e18c2018-02-13 15:51:07 -08001164 auto findPgood = values.find("pgood");
1165 if (findPgood != values.end())
1166 {
James Feist6ebf9de2018-05-15 15:01:17 -07001167
James Feist98132792019-07-09 13:29:09 -07001168 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist918e18c2018-02-13 15:51:07 -08001169 }
James Feist918e18c2018-02-13 15:51:07 -08001170 };
James Feist9eb0b582018-04-27 12:15:46 -07001171
1172 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08001173 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07001174 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
1175 "openbmc_project/Chassis/Control/"
1176 "Power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
James Feist9eb0b582018-04-27 12:15:46 -07001177 eventHandler);
1178
James Feist4131aea2018-03-09 09:47:30 -08001179 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07001180 inotify_add_watch(fd, I2C_DEV_LOCATION,
1181 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08001182 std::array<char, 4096> readBuffer;
1183 std::string pendingBuffer;
1184 // monitor for new i2c devices
1185 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1186 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08001187 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08001188 std::size_t bytes_transferred) {
1189 if (ec)
1190 {
1191 std::cout << "Callback Error " << ec << "\n";
1192 return;
1193 }
1194 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
1195 bool devChange = false;
1196 while (pendingBuffer.size() > sizeof(inotify_event))
1197 {
James Feista465ccc2019-02-08 12:51:01 -08001198 const inotify_event* iEvent =
1199 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08001200 pendingBuffer.data());
1201 switch (iEvent->mask)
1202 {
James Feist9eb0b582018-04-27 12:15:46 -07001203 case IN_CREATE:
1204 case IN_MOVED_TO:
1205 case IN_DELETE:
1206 if (boost::starts_with(std::string(iEvent->name),
1207 "i2c"))
1208 {
1209 devChange = true;
1210 }
James Feist4131aea2018-03-09 09:47:30 -08001211 }
1212
1213 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
1214 }
James Feist6ebf9de2018-05-15 15:01:17 -07001215 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -08001216 {
James Feist98132792019-07-09 13:29:09 -07001217 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist4131aea2018-03-09 09:47:30 -08001218 }
James Feist6ebf9de2018-05-15 15:01:17 -07001219
James Feist4131aea2018-03-09 09:47:30 -08001220 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1221 watchI2cBusses);
1222 };
1223
1224 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05001225 // run the initial scan
James Feist98132792019-07-09 13:29:09 -07001226 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist918e18c2018-02-13 15:51:07 -08001227
James Feist3cb5fec2018-01-23 14:41:51 -08001228 io.run();
1229 return 0;
1230}