blob: 87b7d40bca462dc5f4a874dc55e6417bec0fbd95 [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
Patrick Venturea49dc332019-10-26 08:32:02 -070017#include "Utils.hpp"
18
James Feist3b860982018-10-02 14:34:07 -070019#include <errno.h>
James Feist3cb5fec2018-01-23 14:41:51 -080020#include <fcntl.h>
James Feist3b860982018-10-02 14:34:07 -070021#include <sys/inotify.h>
22#include <sys/ioctl.h>
23
James Feist3b860982018-10-02 14:34:07 -070024#include <boost/algorithm/string/predicate.hpp>
25#include <boost/container/flat_map.hpp>
26#include <chrono>
27#include <ctime>
Patrick Venturee3754002019-08-06 09:39:12 -070028#include <filesystem>
James Feist3cb5fec2018-01-23 14:41:51 -080029#include <fstream>
30#include <future>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070031#include <iomanip>
James Feist3cb5fec2018-01-23 14:41:51 -080032#include <iostream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070033#include <nlohmann/json.hpp>
James Feist3f8a2782018-02-12 09:24:42 -080034#include <regex>
James Feist3b860982018-10-02 14:34:07 -070035#include <sdbusplus/asio/connection.hpp>
36#include <sdbusplus/asio/object_server.hpp>
Patrick Venturec50e1ff2019-08-06 10:22:28 -070037#include <set>
38#include <sstream>
Patrick Venture11f1ff42019-08-01 10:42:12 -070039#include <string>
James Feist3b860982018-10-02 14:34:07 -070040#include <thread>
James Feista465ccc2019-02-08 12:51:01 -080041#include <variant>
James Feist3b860982018-10-02 14:34:07 -070042
43extern "C" {
44#include <i2c/smbus.h>
45#include <linux/i2c-dev.h>
46}
James Feist3cb5fec2018-01-23 14:41:51 -080047
Ed Tanous072e25d2018-12-16 21:45:20 -080048namespace fs = std::filesystem;
James Feist3cb5fec2018-01-23 14:41:51 -080049static constexpr bool DEBUG = false;
50static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feistb49ffc32018-05-02 11:10:43 -070051constexpr size_t MAX_FRU_SIZE = 512;
52constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
James Feist26c27ad2018-07-25 15:09:40 -070053constexpr size_t busTimeoutSeconds = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080054
Patrick Venture11f1ff42019-08-01 10:42:12 -070055constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
56
James Feista465ccc2019-02-08 12:51:01 -080057const static constexpr char* BASEBOARD_FRU_LOCATION =
James Feist3cb5fec2018-01-23 14:41:51 -080058 "/etc/fru/baseboard.fru.bin";
59
James Feista465ccc2019-02-08 12:51:01 -080060const static constexpr char* I2C_DEV_LOCATION = "/dev";
James Feist4131aea2018-03-09 09:47:30 -080061
James Feista465ccc2019-02-08 12:51:01 -080062static constexpr std::array<const char*, 5> FRU_AREAS = {
James Feist3cb5fec2018-01-23 14:41:51 -080063 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -070064const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
James Feist3cb5fec2018-01-23 14:41:51 -080065using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
66using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
67
James Feist444830e2019-04-05 08:38:16 -070068static std::set<size_t> busBlacklist;
James Feist6ebf9de2018-05-15 15:01:17 -070069struct FindDevicesWithCallback;
70
Nikhil Potaded8884f12019-03-27 13:27:13 -070071static BusMap busMap;
72
James Feist8a983922019-10-24 17:11:56 -070073static boost::container::flat_map<
74 std::pair<size_t, size_t>, std::shared_ptr<sdbusplus::asio::dbus_interface>>
75 foundDevices;
76
James Feist5cb06082019-10-24 17:11:13 -070077boost::asio::io_service io;
78auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
79auto objServer = sdbusplus::asio::object_server(systemBus);
80
Patrick Venturec50e1ff2019-08-06 10:22:28 -070081// Given a bus/address, produce the path in sysfs for an eeprom.
82static std::string getEepromPath(size_t bus, size_t address)
83{
84 std::stringstream output;
85 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
86 << std::setfill('0') << std::setw(4) << std::hex << address
87 << "/eeprom";
88 return output.str();
89}
90
91static bool hasEepromFile(size_t bus, size_t address)
92{
93 auto path = getEepromPath(bus, address);
94 try
95 {
96 return fs::exists(path);
97 }
98 catch (...)
99 {
100 return false;
101 }
102}
103
104static ssize_t readFromEeprom(int fd, uint16_t offset, uint8_t len,
105 uint8_t* buf)
106{
107 auto result = lseek(fd, offset, SEEK_SET);
108 if (result < 0)
109 {
110 std::cerr << "failed to seek\n";
111 return -1;
112 }
113
114 return read(fd, buf, len);
115}
116
James Feistc95cb142018-02-26 10:41:42 -0800117static bool isMuxBus(size_t bus)
118{
Ed Tanous072e25d2018-12-16 21:45:20 -0800119 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -0800120 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
121}
122
James Feist8a983922019-10-24 17:11:56 -0700123static void makeProbeInterface(size_t bus, size_t address)
124{
125 if (isMuxBus(bus))
126 {
127 return; // the mux buses are random, no need to publish
128 }
129 auto [it, success] = foundDevices.emplace(
130 std::make_pair(bus, address),
131 objServer.add_interface(
132 "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" +
133 std::to_string(address),
134 "xyz.openbmc_project.Inventory.Item.I2CDevice"));
135 if (!success)
136 {
137 return; // already added
138 }
139 it->second->register_property("Bus", bus);
140 it->second->register_property("Address", address);
141 it->second->initialize();
142}
143
Vijay Khemka2d681f62018-11-06 15:51:00 -0800144static int isDevice16Bit(int file)
145{
146 /* Get first byte */
147 int byte1 = i2c_smbus_read_byte_data(file, 0);
148 if (byte1 < 0)
149 {
150 return byte1;
151 }
152 /* Read 7 more bytes, it will read same first byte in case of
153 * 8 bit but it will read next byte in case of 16 bit
154 */
155 for (int i = 0; i < 7; i++)
156 {
157 int byte2 = i2c_smbus_read_byte_data(file, 0);
158 if (byte2 < 0)
159 {
160 return byte2;
161 }
162 if (byte2 != byte1)
163 {
164 return 1;
165 }
166 }
167 return 0;
168}
169
Patrick Venture4f47fe62019-08-08 16:30:38 -0700170static int readBlockData(int flag, int file, uint16_t offset, uint8_t len,
171 uint8_t* buf)
Vijay Khemka2d681f62018-11-06 15:51:00 -0800172{
Patrick Venture4f47fe62019-08-08 16:30:38 -0700173 uint8_t lowAddr = static_cast<uint8_t>(offset);
174 uint8_t highAddr = static_cast<uint8_t>(offset >> 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800175
176 if (flag == 0)
177 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700178 return i2c_smbus_read_i2c_block_data(file, lowAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800179 }
180
181 /* This is for 16 bit addressing EEPROM device. First an offset
182 * needs to be written before read data from a offset
183 */
Patrick Venture4f47fe62019-08-08 16:30:38 -0700184 int ret = i2c_smbus_write_byte_data(file, 0, lowAddr);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800185 if (ret < 0)
186 {
187 return ret;
188 }
189
Patrick Venture4f47fe62019-08-08 16:30:38 -0700190 return i2c_smbus_read_i2c_block_data(file, highAddr, len, buf);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800191}
192
James Feist24bae7a2019-04-03 09:50:56 -0700193bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
194{
195 // ipmi spec format version number is currently at 1, verify it
196 if (blockData[0] != 0x1)
197 {
198 return false;
199 }
200
201 // verify pad is set to 0
202 if (blockData[6] != 0x0)
203 {
204 return false;
205 }
206
207 // verify offsets are 0, or don't point to another offset
208 std::set<uint8_t> foundOffsets;
209 for (int ii = 1; ii < 6; ii++)
210 {
211 if (blockData[ii] == 0)
212 {
213 continue;
214 }
James Feist0eb40352019-04-09 14:44:04 -0700215 auto inserted = foundOffsets.insert(blockData[ii]);
216 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700217 {
218 return false;
219 }
220 }
221
222 // validate checksum
223 size_t sum = 0;
224 for (int jj = 0; jj < 7; jj++)
225 {
226 sum += blockData[jj];
227 }
228 sum = (256 - sum) & 0xFF;
229
230 if (sum != blockData[7])
231 {
232 return false;
233 }
234 return true;
235}
236
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700237// TODO: This code is very similar to the non-eeprom version and can be merged
238// with some tweaks.
239static std::vector<char> processEeprom(int bus, int address)
240{
241 std::vector<char> device;
Patrick Venture4f47fe62019-08-08 16:30:38 -0700242 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700243
244 auto path = getEepromPath(bus, address);
245
246 int file = open(path.c_str(), O_RDONLY);
247 if (file < 0)
248 {
249 std::cerr << "Unable to open eeprom file: " << path << "\n";
250 return device;
251 }
252
Patrick Venture4f47fe62019-08-08 16:30:38 -0700253 ssize_t readBytes = readFromEeprom(file, 0, 0x8, blockData.data());
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700254 if (readBytes < 0)
255 {
256 std::cerr << "failed to read eeprom at " << bus << " address "
257 << address << "\n";
258 close(file);
259 return device;
260 }
261
Patrick Venture4f47fe62019-08-08 16:30:38 -0700262 if (!validateHeader(blockData))
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700263 {
264 if (DEBUG)
265 {
266 std::cerr << "Illegal header at bus " << bus << " address "
267 << address << "\n";
268 }
269
270 close(file);
271 return device;
272 }
273
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700274 // Copy the IPMI Fru Header
Patrick Venture4f47fe62019-08-08 16:30:38 -0700275 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700276
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700277 int fruLength = 0;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700278 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
279 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700280 // TODO: offset can be 255, device is holding "chars" that's not good.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700281 int areaOffset = device[jj];
282 if (areaOffset == 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700283 {
284 continue;
285 }
286
Patrick Venture4f47fe62019-08-08 16:30:38 -0700287 areaOffset *= 8;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700288
Patrick Venture4f47fe62019-08-08 16:30:38 -0700289 if (readFromEeprom(file, static_cast<uint16_t>(areaOffset), 0x2,
290 blockData.data()) < 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700291 {
292 std::cerr << "failed to read bus " << bus << " address " << address
293 << "\n";
294 device.clear();
295 close(file);
296 return device;
297 }
298
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700299 // Ignore data type.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700300 int length = blockData[1] * 8;
301 areaOffset += length;
302 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700303 }
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700304
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700305 // You already copied these first 8 bytes (the ipmi fru header size)
306 fruLength -= 8;
307
308 int readOffset = 8;
309
310 while (fruLength > 0)
311 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700312 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700313
314 if (readFromEeprom(file, static_cast<uint16_t>(readOffset),
Patrick Venture4f47fe62019-08-08 16:30:38 -0700315 static_cast<uint8_t>(requestLength),
316 blockData.data()) < 0)
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700317 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700318 std::cerr << "failed to read bus " << bus << " address " << address
319 << "\n";
320 device.clear();
321 close(file);
322 return device;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700323 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700324
Patrick Venture4f47fe62019-08-08 16:30:38 -0700325 device.insert(device.end(), blockData.begin(),
326 blockData.begin() + requestLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700327
Patrick Venture4f47fe62019-08-08 16:30:38 -0700328 readOffset += requestLength;
329 fruLength -= requestLength;
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700330 }
331
332 close(file);
333 return device;
334}
335
336std::set<int> findI2CEeproms(int i2cBus, std::shared_ptr<DeviceMap> devices)
337{
338 std::set<int> foundList;
339
340 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
341
342 // For each file listed under the i2c device
343 // NOTE: This should be faster than just checking for each possible address
344 // path.
345 for (const auto& p : fs::directory_iterator(path))
346 {
347 const std::string node = p.path().string();
348 std::smatch m;
349 bool found =
350 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
351
352 if (!found)
353 {
354 continue;
355 }
356 if (m.size() != 2)
357 {
358 std::cerr << "regex didn't capture\n";
359 continue;
360 }
361
362 std::ssub_match subMatch = m[1];
363 std::string addressString = subMatch.str();
364
365 std::size_t ignored;
366 const int hexBase = 16;
367 int address = std::stoi(addressString, &ignored, hexBase);
368
369 const std::string eeprom = node + "/eeprom";
370
371 try
372 {
373 if (!fs::exists(eeprom))
374 {
375 continue;
376 }
377 }
378 catch (...)
379 {
380 continue;
381 }
382
383 // There is an eeprom file at this address, it may have invalid
384 // contents, but we found it.
385 foundList.insert(address);
386
387 std::vector<char> device = processEeprom(i2cBus, address);
388 if (!device.empty())
389 {
390 devices->emplace(address, device);
391 }
392 }
393
394 return foundList;
395}
396
Patrick Venture4f47fe62019-08-08 16:30:38 -0700397int getBusFrus(int file, int first, int last, int bus,
398 std::shared_ptr<DeviceMap> devices)
James Feist3cb5fec2018-01-23 14:41:51 -0800399{
James Feist3cb5fec2018-01-23 14:41:51 -0800400
James Feist26c27ad2018-07-25 15:09:40 -0700401 std::future<int> future = std::async(std::launch::async, [&]() {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700402 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
Vijay Khemka2d681f62018-11-06 15:51:00 -0800403
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700404 // NOTE: When reading the devices raw on the bus, it can interfere with
405 // the driver's ability to operate, therefore read eeproms first before
406 // scanning for devices without drivers. Several experiments were run
407 // and it was determined that if there were any devices on the bus
408 // before the eeprom was hit and read, the eeprom driver wouldn't open
409 // while the bus device was open. An experiment was not performed to see
410 // if this issue was resolved if the i2c bus device was closed, but
411 // hexdumps of the eeprom later were successful.
412
413 // Scan for i2c eeproms loaded on this bus.
414 std::set<int> skipList = findI2CEeproms(bus, devices);
415
James Feist26c27ad2018-07-25 15:09:40 -0700416 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800417 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -0700418 if (skipList.find(ii) != skipList.end())
419 {
420 continue;
421 }
James Feist3cb5fec2018-01-23 14:41:51 -0800422
James Feist26c27ad2018-07-25 15:09:40 -0700423 // Set slave address
424 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800425 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700426 std::cerr << "device at bus " << bus << " register " << ii
Patrick Venture5d8b61d2019-08-06 12:36:10 -0700427 << " busy\n";
James Feist26c27ad2018-07-25 15:09:40 -0700428 continue;
429 }
430 // probe
431 else if (i2c_smbus_read_byte(file) < 0)
432 {
433 continue;
434 }
James Feist3cb5fec2018-01-23 14:41:51 -0800435
James Feist26c27ad2018-07-25 15:09:40 -0700436 if (DEBUG)
437 {
Patrick Venture98e0cf32019-08-02 11:11:03 -0700438 std::cout << "something at bus " << bus << " addr " << ii
James Feist26c27ad2018-07-25 15:09:40 -0700439 << "\n";
440 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800441
James Feist8a983922019-10-24 17:11:56 -0700442 makeProbeInterface(bus, ii);
443
Vijay Khemka2d681f62018-11-06 15:51:00 -0800444 /* Check for Device type if it is 8 bit or 16 bit */
445 int flag = isDevice16Bit(file);
446 if (flag < 0)
447 {
448 std::cerr << "failed to read bus " << bus << " address " << ii
449 << "\n";
450 continue;
451 }
452
Patrick Venture4f47fe62019-08-08 16:30:38 -0700453 if (readBlockData(flag, file, 0x0, 0x8, blockData.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700454 {
455 std::cerr << "failed to read bus " << bus << " address " << ii
456 << "\n";
457 continue;
458 }
James Feist26c27ad2018-07-25 15:09:40 -0700459
460 // check the header checksum
Patrick Venture4f47fe62019-08-08 16:30:38 -0700461 if (!validateHeader(blockData))
James Feist26c27ad2018-07-25 15:09:40 -0700462 {
Patrick Venture786f1792019-08-05 16:33:44 -0700463 if (DEBUG)
James Feist26c27ad2018-07-25 15:09:40 -0700464 {
Patrick Venture786f1792019-08-05 16:33:44 -0700465 std::cerr << "Illegal header at bus " << bus << " address "
466 << ii << "\n";
467 }
468 continue;
469 }
470
471 std::vector<char> device;
Patrick Venture4f47fe62019-08-08 16:30:38 -0700472 device.insert(device.end(), blockData.begin(),
473 blockData.begin() + 8);
Patrick Venture786f1792019-08-05 16:33:44 -0700474
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700475 int fruLength = 0;
Patrick Venture786f1792019-08-05 16:33:44 -0700476 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
477 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700478 // TODO: offset can be 255, device is holding "chars" that's not
479 // good.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700480 int areaOffset = device[jj];
481 if (areaOffset == 0)
Patrick Venture786f1792019-08-05 16:33:44 -0700482 {
Patrick Venture64fd7e22019-08-05 16:38:20 -0700483 continue;
484 }
485
Patrick Venture4f47fe62019-08-08 16:30:38 -0700486 areaOffset *= 8;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700487
Patrick Venture4f47fe62019-08-08 16:30:38 -0700488 if (readBlockData(flag, file, static_cast<uint16_t>(areaOffset),
489 0x2, blockData.data()) < 0)
Patrick Venture64fd7e22019-08-05 16:38:20 -0700490 {
491 std::cerr << "failed to read bus " << bus << " address "
492 << ii << "\n";
493 return -1;
494 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700495
496 // Ignore data type.
Patrick Venture4f47fe62019-08-08 16:30:38 -0700497 int length = blockData[1] * 8;
498 areaOffset += length;
499 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700500 }
Patrick Venture64fd7e22019-08-05 16:38:20 -0700501
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700502 // You already copied these first 8 bytes (the ipmi fru header size)
503 fruLength -= 8;
504
505 int readOffset = 8;
506
507 while (fruLength > 0)
508 {
Patrick Venture4f47fe62019-08-08 16:30:38 -0700509 int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700510
Patrick Venture4f47fe62019-08-08 16:30:38 -0700511 if (readBlockData(flag, file, static_cast<uint16_t>(readOffset),
512 static_cast<uint8_t>(requestLength),
513 blockData.data()) < 0)
Patrick Venture64fd7e22019-08-05 16:38:20 -0700514 {
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700515 std::cerr << "failed to read bus " << bus << " address "
516 << ii << "\n";
517 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800518 }
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700519
Patrick Venture4f47fe62019-08-08 16:30:38 -0700520 device.insert(device.end(), blockData.begin(),
521 blockData.begin() + requestLength);
Patrick Venture4cc29fc2019-08-08 13:36:18 -0700522
Patrick Venture4f47fe62019-08-08 16:30:38 -0700523 readOffset += requestLength;
524 fruLength -= requestLength;
James Feist3cb5fec2018-01-23 14:41:51 -0800525 }
Patrick Venture786f1792019-08-05 16:33:44 -0700526 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800527 }
James Feist26c27ad2018-07-25 15:09:40 -0700528 return 1;
529 });
530 std::future_status status =
531 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
532 if (status == std::future_status::timeout)
533 {
534 std::cerr << "Error reading bus " << bus << "\n";
James Feist444830e2019-04-05 08:38:16 -0700535 busBlacklist.insert(bus);
536 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700537 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800538 }
539
James Feist444830e2019-04-05 08:38:16 -0700540 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700541 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800542}
543
Patrick Venture11f1ff42019-08-01 10:42:12 -0700544void loadBlacklist(const char* path)
545{
546 std::ifstream blacklistStream(path);
547 if (!blacklistStream.good())
548 {
549 // File is optional.
550 std::cerr << "Cannot open blacklist file.\n\n";
551 return;
552 }
553
554 nlohmann::json data =
555 nlohmann::json::parse(blacklistStream, nullptr, false);
556 if (data.is_discarded())
557 {
558 std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
559 "exiting\n";
560 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700561 }
562
563 // It's expected to have at least one field, "buses" that is an array of the
564 // buses by integer. Allow for future options to exclude further aspects,
565 // such as specific addresses or ranges.
566 if (data.type() != nlohmann::json::value_t::object)
567 {
568 std::cerr << "Illegal blacklist, expected to read dictionary\n";
569 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700570 }
571
572 // If buses field is missing, that's fine.
573 if (data.count("buses") == 1)
574 {
575 // Parse the buses array after a little validation.
576 auto buses = data.at("buses");
577 if (buses.type() != nlohmann::json::value_t::array)
578 {
579 // Buses field present but invalid, therefore this is an error.
580 std::cerr << "Invalid contents for blacklist buses field\n";
581 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700582 }
583
584 // Catch exception here for type mis-match.
585 try
586 {
587 for (const auto& bus : buses)
588 {
589 busBlacklist.insert(bus.get<size_t>());
590 }
591 }
592 catch (const nlohmann::detail::type_error& e)
593 {
594 // Type mis-match is a critical error.
595 std::cerr << "Invalid bus type: " << e.what() << "\n";
596 std::exit(EXIT_FAILURE);
Patrick Venture11f1ff42019-08-01 10:42:12 -0700597 }
598 }
599
600 return;
601}
602
James Feista465ccc2019-02-08 12:51:01 -0800603static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700604 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800605{
James Feista465ccc2019-02-08 12:51:01 -0800606 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800607 {
608 auto busnum = i2cBus.string();
609 auto lastDash = busnum.rfind(std::string("-"));
610 // delete everything before dash inclusive
611 if (lastDash != std::string::npos)
612 {
613 busnum.erase(0, lastDash + 1);
614 }
615 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700616 if (busBlacklist.find(bus) != busBlacklist.end())
617 {
618 continue; // skip previously failed busses
619 }
James Feist3cb5fec2018-01-23 14:41:51 -0800620
621 auto file = open(i2cBus.c_str(), O_RDWR);
622 if (file < 0)
623 {
624 std::cerr << "unable to open i2c device " << i2cBus.string()
625 << "\n";
626 continue;
627 }
628 unsigned long funcs = 0;
629
630 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
631 {
632 std::cerr
Patrick Venture98e0cf32019-08-02 11:11:03 -0700633 << "Error: Could not get the adapter functionality matrix bus "
James Feist3cb5fec2018-01-23 14:41:51 -0800634 << bus << "\n";
635 continue;
636 }
637 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
638 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
639 {
640 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
641 << bus << "\n";
642 continue;
643 }
James Feist98132792019-07-09 13:29:09 -0700644 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800645 device = std::make_shared<DeviceMap>();
646
Nikhil Potaded8884f12019-03-27 13:27:13 -0700647 // i2cdetect by default uses the range 0x03 to 0x77, as
648 // this is what we have tested with, use this range. Could be
649 // changed in future.
650 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800651 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700652 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800653 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700654
655 // fd is closed in this function in case the bus locks up
Patrick Venture4f47fe62019-08-08 16:30:38 -0700656 getBusFrus(file, 0x03, 0x77, bus, device);
Nikhil Potaded8884f12019-03-27 13:27:13 -0700657
658 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800659 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700660 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800661 }
James Feist3cb5fec2018-01-23 14:41:51 -0800662 }
James Feist3cb5fec2018-01-23 14:41:51 -0800663}
664
James Feist6ebf9de2018-05-15 15:01:17 -0700665// this class allows an async response after all i2c devices are discovered
666struct FindDevicesWithCallback
667 : std::enable_shared_from_this<FindDevicesWithCallback>
668{
James Feista465ccc2019-02-08 12:51:01 -0800669 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist5cb06082019-10-24 17:11:13 -0700670 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800671 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700672 _i2cBuses(i2cBuses),
James Feist5cb06082019-10-24 17:11:13 -0700673 _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700674 {
675 }
676 ~FindDevicesWithCallback()
677 {
678 _callback();
679 }
680 void run()
681 {
James Feist98132792019-07-09 13:29:09 -0700682 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700683 }
684
James Feista465ccc2019-02-08 12:51:01 -0800685 const std::vector<fs::path>& _i2cBuses;
James Feista465ccc2019-02-08 12:51:01 -0800686 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700687 std::function<void(void)> _callback;
688};
689
James Feist3cb5fec2018-01-23 14:41:51 -0800690static const std::tm intelEpoch(void)
691{
James Feist98132792019-07-09 13:29:09 -0700692 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800693 val.tm_year = 1996 - 1900;
694 return val;
695}
696
James Feista465ccc2019-02-08 12:51:01 -0800697bool formatFru(const std::vector<char>& fruBytes,
698 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800699{
James Feista465ccc2019-02-08 12:51:01 -0800700 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800701 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800702
James Feista465ccc2019-02-08 12:51:01 -0800703 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800704 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
705 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800706
James Feista465ccc2019-02-08 12:51:01 -0800707 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800708 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
709 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
710 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800711
James Feistd068e932018-09-20 10:53:07 -0700712 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800713 {
714 return false;
715 }
716 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700717 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800718 std::to_string(static_cast<int>(*fruAreaOffsetField));
719
James Feista465ccc2019-02-08 12:51:01 -0800720 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800721
James Feist0eb40352019-04-09 14:44:04 -0700722 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800723 {
724 fruAreaOffsetField++;
725 if (fruAreaOffsetField >= fruBytes.end())
726 {
727 return false;
728 }
729 size_t offset = (*fruAreaOffsetField) * 8;
730
731 if (offset > 1)
732 {
733 // +2 to skip format and length
734 std::vector<char>::const_iterator fruBytesIter =
735 fruBytes.begin() + offset + 2;
736
737 if (fruBytesIter >= fruBytes.end())
738 {
739 return false;
740 }
741
742 if (area == "CHASSIS")
743 {
744 result["CHASSIS_TYPE"] =
745 std::to_string(static_cast<int>(*fruBytesIter));
746 fruBytesIter += 1;
747 fieldData = &CHASSIS_FRU_AREAS;
748 }
749 else if (area == "BOARD")
750 {
751 result["BOARD_LANGUAGE_CODE"] =
752 std::to_string(static_cast<int>(*fruBytesIter));
753 fruBytesIter += 1;
754 if (fruBytesIter >= fruBytes.end())
755 {
756 return false;
757 }
758
759 unsigned int minutes = *fruBytesIter |
760 *(fruBytesIter + 1) << 8 |
761 *(fruBytesIter + 2) << 16;
762 std::tm fruTime = intelEpoch();
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700763 std::time_t timeValue = std::mktime(&fruTime);
James Feist3cb5fec2018-01-23 14:41:51 -0800764 timeValue += minutes * 60;
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700765 fruTime = *std::gmtime(&timeValue);
James Feist3cb5fec2018-01-23 14:41:51 -0800766
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700767 // Tue Nov 20 23:08:00 2018
768 char timeString[32] = {0};
769 auto bytes = std::strftime(timeString, sizeof(timeString),
Patrick Venturefff050a2019-08-13 11:44:30 -0700770 "%Y-%m-%d - %H:%M:%S", &fruTime);
Patrick Venturee0e6f5f2019-08-12 19:00:11 -0700771 if (bytes == 0)
772 {
773 std::cerr << "invalid time string encountered\n";
774 return false;
775 }
776
777 result["BOARD_MANUFACTURE_DATE"] = std::string(timeString);
James Feist3cb5fec2018-01-23 14:41:51 -0800778 fruBytesIter += 3;
779 fieldData = &BOARD_FRU_AREAS;
780 }
781 else if (area == "PRODUCT")
782 {
783 result["PRODUCT_LANGUAGE_CODE"] =
784 std::to_string(static_cast<int>(*fruBytesIter));
785 fruBytesIter += 1;
786 fieldData = &PRODUCT_FRU_AREAS;
787 }
788 else
789 {
790 continue;
791 }
James Feista465ccc2019-02-08 12:51:01 -0800792 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800793 {
794 if (fruBytesIter >= fruBytes.end())
795 {
796 return false;
797 }
798
Vijay Khemka5d5de442018-11-07 10:51:25 -0800799 /* Checking for last byte C1 to indicate that no more
800 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700801 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800802 {
803 break;
804 }
805
Ed Tanous2147e672019-02-27 13:59:56 -0800806 size_t length = *fruBytesIter & 0x3f;
807 fruBytesIter += 1;
808
James Feist3cb5fec2018-01-23 14:41:51 -0800809 if (fruBytesIter >= fruBytes.end())
810 {
811 return false;
812 }
Ed Tanous2147e672019-02-27 13:59:56 -0800813 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800814
Ed Tanous2147e672019-02-27 13:59:56 -0800815 // Strip non null characters from the end
816 value.erase(std::find_if(value.rbegin(), value.rend(),
817 [](char ch) { return ch != 0; })
818 .base(),
819 value.end());
820
James Feist0eb40352019-04-09 14:44:04 -0700821 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800822
James Feist3cb5fec2018-01-23 14:41:51 -0800823 fruBytesIter += length;
824 if (fruBytesIter >= fruBytes.end())
825 {
826 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800827 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800828 {
829 std::cerr << c;
830 }
831 std::cerr << "\n";
832 if (DEBUG)
833 {
James Feista465ccc2019-02-08 12:51:01 -0800834 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800835 {
836 std::cerr << keyPair.first << " : "
837 << keyPair.second << "\n";
838 }
839 }
840 return false;
841 }
842 }
843 }
844 }
845
846 return true;
847}
848
Nikhil Potaded8884f12019-03-27 13:27:13 -0700849std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
850{
851 auto deviceMap = busMap.find(bus);
852 if (deviceMap == busMap.end())
853 {
854 throw std::invalid_argument("Invalid Bus.");
855 }
856 auto device = deviceMap->second->find(address);
857 if (device == deviceMap->second->end())
858 {
859 throw std::invalid_argument("Invalid Address.");
860 }
861 std::vector<uint8_t>& ret =
862 reinterpret_cast<std::vector<uint8_t>&>(device->second);
863
864 return ret;
865}
866
James Feist3cb5fec2018-01-23 14:41:51 -0800867void AddFruObjectToDbus(
James Feist5cb06082019-10-24 17:11:13 -0700868 std::vector<char>& device,
James Feista465ccc2019-02-08 12:51:01 -0800869 boost::container::flat_map<
870 std::pair<size_t, size_t>,
871 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700872 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800873{
874 boost::container::flat_map<std::string, std::string> formattedFru;
875 if (!formatFru(device, formattedFru))
876 {
Patrick Ventureb755c832019-08-07 11:09:14 -0700877 std::cerr << "failed to format fru for device at bus " << bus
878 << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800879 return;
880 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700881
James Feist3cb5fec2018-01-23 14:41:51 -0800882 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
883 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700884 // Not found under Board section or an empty string.
885 if (productNameFind == formattedFru.end() ||
886 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800887 {
888 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
889 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700890 // Found under Product section and not an empty string.
891 if (productNameFind != formattedFru.end() &&
892 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800893 {
894 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800895 std::regex illegalObject("[^A-Za-z0-9_]");
896 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800897 }
898 else
899 {
900 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
901 UNKNOWN_BUS_OBJECT_COUNT++;
902 }
903
James Feist918e18c2018-02-13 15:51:07 -0800904 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800905 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700906 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800907 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700908 int highest = -1;
909 bool found = false;
910
James Feista465ccc2019-02-08 12:51:01 -0800911 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800912 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700913 std::string path = busIface.second->get_object_path();
914 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
James Feist918e18c2018-02-13 15:51:07 -0800915 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700916 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700917 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
918 static_cast<uint8_t>(busIface.first.second)) ==
919 getFruInfo(static_cast<uint8_t>(bus),
920 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700921 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700922 // This device is already added to the lower numbered bus,
923 // do not replicate it.
924 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700925 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700926
927 // Check if the match named has extra information.
928 found = true;
929 std::smatch base_match;
930
931 bool match = std::regex_match(
932 path, base_match, std::regex(productName + "_(\\d+)$"));
933 if (match)
James Feist79e9c0b2018-03-15 15:21:17 -0700934 {
Patrick Venture015fb0a2019-08-16 09:33:31 -0700935 if (base_match.size() == 2)
936 {
937 std::ssub_match base_sub_match = base_match[1];
938 std::string base = base_sub_match.str();
939
940 int value = std::stoi(base);
941 highest = (value > highest) ? value : highest;
942 }
James Feist79e9c0b2018-03-15 15:21:17 -0700943 }
James Feist918e18c2018-02-13 15:51:07 -0800944 }
Patrick Venture015fb0a2019-08-16 09:33:31 -0700945 } // end searching objects
946
947 if (found)
948 {
949 // We found something with the same name. If highest was still -1,
950 // it means this new entry will be _0.
951 productName += "_";
952 productName += std::to_string(++highest);
James Feist918e18c2018-02-13 15:51:07 -0800953 }
954 }
James Feist3cb5fec2018-01-23 14:41:51 -0800955
James Feist9eb0b582018-04-27 12:15:46 -0700956 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
957 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
958 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
959
James Feista465ccc2019-02-08 12:51:01 -0800960 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800961 {
James Feist9eb0b582018-04-27 12:15:46 -0700962
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700963 std::regex_replace(property.second.begin(), property.second.begin(),
964 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700965 if (property.second.empty())
966 {
967 continue;
968 }
969 std::string key =
970 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
971 if (!iface->register_property(key, property.second + '\0'))
972 {
973 std::cerr << "illegal key: " << key << "\n";
974 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700975 if (DEBUG)
976 {
977 std::cout << property.first << ": " << property.second << "\n";
978 }
James Feist3cb5fec2018-01-23 14:41:51 -0800979 }
James Feist2a9d6db2018-04-27 15:48:28 -0700980
981 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700982 iface->register_property("BUS", bus);
983 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700984
James Feist9eb0b582018-04-27 12:15:46 -0700985 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800986}
987
James Feista465ccc2019-02-08 12:51:01 -0800988static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800989{
990 // try to read baseboard fru from file
991 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
992 if (baseboardFruFile.good())
993 {
994 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -0700995 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -0800996 baseboardFru.resize(fileSize);
997 baseboardFruFile.seekg(0, std::ios_base::beg);
998 baseboardFruFile.read(baseboardFru.data(), fileSize);
999 }
1000 else
1001 {
1002 return false;
1003 }
1004 return true;
1005}
1006
James Feista465ccc2019-02-08 12:51:01 -08001007bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -07001008{
1009 boost::container::flat_map<std::string, std::string> tmp;
1010 if (fru.size() > MAX_FRU_SIZE)
1011 {
1012 std::cerr << "Invalid fru.size() during writeFru\n";
1013 return false;
1014 }
1015 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -08001016 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -07001017 {
1018 std::cerr << "Invalid fru format during writeFru\n";
1019 return false;
1020 }
1021 // baseboard fru
1022 if (bus == 0 && address == 0)
1023 {
1024 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
1025 if (!file.good())
1026 {
1027 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
1028 << "\n";
James Feistddb78302018-09-06 11:45:42 -07001029 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001030 return false;
1031 }
James Feista465ccc2019-02-08 12:51:01 -08001032 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -07001033 return file.good();
1034 }
1035 else
1036 {
Patrick Venturec50e1ff2019-08-06 10:22:28 -07001037 if (hasEepromFile(bus, address))
1038 {
1039 auto path = getEepromPath(bus, address);
1040 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
1041 if (eeprom < 0)
1042 {
1043 std::cerr << "unable to open i2c device " << path << "\n";
1044 throw DBusInternalError();
1045 return false;
1046 }
1047
1048 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
1049 if (writtenBytes < 0)
1050 {
1051 std::cerr << "unable to write to i2c device " << path << "\n";
1052 close(eeprom);
1053 throw DBusInternalError();
1054 return false;
1055 }
1056
1057 close(eeprom);
1058 return true;
1059 }
1060
James Feistb49ffc32018-05-02 11:10:43 -07001061 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
1062
1063 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
1064 if (file < 0)
1065 {
1066 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -07001067 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001068 return false;
1069 }
1070 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
1071 {
1072 std::cerr << "unable to set device address\n";
1073 close(file);
James Feistddb78302018-09-06 11:45:42 -07001074 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001075 return false;
1076 }
1077
1078 constexpr const size_t RETRY_MAX = 2;
1079 uint16_t index = 0;
1080 size_t retries = RETRY_MAX;
1081 while (index < fru.size())
1082 {
1083 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
1084 (retries == RETRY_MAX))
1085 {
1086 // The 4K EEPROM only uses the A2 and A1 device address bits
1087 // with the third bit being a memory page address bit.
1088 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
1089 {
1090 std::cerr << "unable to set device address\n";
1091 close(file);
James Feistddb78302018-09-06 11:45:42 -07001092 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001093 return false;
1094 }
1095 }
1096
James Feist98132792019-07-09 13:29:09 -07001097 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
1098 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -07001099 {
1100 if (!retries--)
1101 {
1102 std::cerr << "error writing fru: " << strerror(errno)
1103 << "\n";
1104 close(file);
James Feistddb78302018-09-06 11:45:42 -07001105 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -07001106 return false;
1107 }
1108 }
1109 else
1110 {
1111 retries = RETRY_MAX;
1112 index++;
1113 }
1114 // most eeproms require 5-10ms between writes
1115 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1116 }
1117 close(file);
1118 return true;
1119 }
1120}
1121
James Feist9eb0b582018-04-27 12:15:46 -07001122void rescanBusses(
James Feist5cb06082019-10-24 17:11:13 -07001123 BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -08001124 boost::container::flat_map<
1125 std::pair<size_t, size_t>,
James Feist5cb06082019-10-24 17:11:13 -07001126 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -08001127{
James Feist6ebf9de2018-05-15 15:01:17 -07001128 static boost::asio::deadline_timer timer(io);
1129 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -08001130
Gunnar Mills6f0ae942018-08-31 12:38:03 -05001131 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -07001132 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -08001133 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -08001134 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -08001135
Nikhil Potaded8884f12019-03-27 13:27:13 -07001136 boost::container::flat_map<size_t, fs::path> busPaths;
1137 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -08001138 {
James Feist4131aea2018-03-09 09:47:30 -08001139 std::cerr << "unable to find i2c devices\n";
1140 return;
James Feist918e18c2018-02-13 15:51:07 -08001141 }
Nikhil Potaded8884f12019-03-27 13:27:13 -07001142
1143 for (auto busPath : busPaths)
1144 {
1145 i2cBuses.emplace_back(busPath.second);
1146 }
James Feist4131aea2018-03-09 09:47:30 -08001147
James Feist98132792019-07-09 13:29:09 -07001148 busmap.clear();
James Feist8a983922019-10-24 17:11:56 -07001149 for (auto& [pair, interface] : foundDevices)
1150 {
1151 objServer.remove_interface(interface);
1152 }
1153 foundDevices.clear();
1154
James Feist5cb06082019-10-24 17:11:13 -07001155 auto scan =
1156 std::make_shared<FindDevicesWithCallback>(i2cBuses, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -08001157 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -07001158 {
1159 objServer.remove_interface(busIface.second);
1160 }
James Feist4131aea2018-03-09 09:47:30 -08001161
James Feist6ebf9de2018-05-15 15:01:17 -07001162 dbusInterfaceMap.clear();
1163 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -08001164
James Feist6ebf9de2018-05-15 15:01:17 -07001165 // todo, get this from a more sensable place
1166 std::vector<char> baseboardFru;
1167 if (readBaseboardFru(baseboardFru))
1168 {
1169 boost::container::flat_map<int, std::vector<char>>
1170 baseboardDev;
1171 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -07001172 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -07001173 }
James Feist98132792019-07-09 13:29:09 -07001174 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -07001175 {
James Feista465ccc2019-02-08 12:51:01 -08001176 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -07001177 {
James Feist5cb06082019-10-24 17:11:13 -07001178 AddFruObjectToDbus(device.second, dbusInterfaceMap,
1179 devicemap.first, device.first);
James Feist6ebf9de2018-05-15 15:01:17 -07001180 }
1181 }
1182 });
1183 scan->run();
1184 });
James Feist918e18c2018-02-13 15:51:07 -08001185}
1186
James Feist98132792019-07-09 13:29:09 -07001187int main()
James Feist3cb5fec2018-01-23 14:41:51 -08001188{
1189 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -08001190 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -08001191 std::vector<fs::path> i2cBuses;
1192
James Feista3c180a2018-08-09 16:06:04 -07001193 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -08001194 {
1195 std::cerr << "unable to find i2c devices\n";
1196 return 1;
1197 }
James Feist3cb5fec2018-01-23 14:41:51 -08001198
Patrick Venture11f1ff42019-08-01 10:42:12 -07001199 // check for and load blacklist with initial buses.
1200 loadBlacklist(blacklistPath);
1201
Vijay Khemka065f6d92018-12-18 10:37:47 -08001202 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -08001203
James Feist6ebf9de2018-05-15 15:01:17 -07001204 // this is a map with keys of pair(bus number, address) and values of
1205 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -08001206 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -07001207 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1208 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -08001209
James Feist9eb0b582018-04-27 12:15:46 -07001210 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1211 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1212 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -08001213
James Feist5cb06082019-10-24 17:11:13 -07001214 iface->register_method("ReScan",
1215 [&]() { rescanBusses(busMap, dbusInterfaceMap); });
James Feist2a9d6db2018-04-27 15:48:28 -07001216
Nikhil Potaded8884f12019-03-27 13:27:13 -07001217 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -07001218
1219 iface->register_method("WriteFru", [&](const uint8_t bus,
1220 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -08001221 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -07001222 if (!writeFru(bus, address, data))
1223 {
James Feistddb78302018-09-06 11:45:42 -07001224 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -07001225 return;
1226 }
1227 // schedule rescan on success
James Feist5cb06082019-10-24 17:11:13 -07001228 rescanBusses(busMap, dbusInterfaceMap);
James Feistb49ffc32018-05-02 11:10:43 -07001229 });
James Feist9eb0b582018-04-27 12:15:46 -07001230 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -08001231
James Feist9eb0b582018-04-27 12:15:46 -07001232 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -08001233 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -08001234 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -07001235 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -08001236 std::string,
1237 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -07001238 values;
1239 message.read(objectName, values);
James Feist0eeb79c2019-10-09 13:35:29 -07001240 auto findState = values.find("CurrentHostState");
1241 bool on = false;
1242 if (findState != values.end())
James Feist918e18c2018-02-13 15:51:07 -08001243 {
James Feist0eeb79c2019-10-09 13:35:29 -07001244 on = boost::ends_with(std::get<std::string>(findState->second),
1245 "Running");
1246 }
James Feist6ebf9de2018-05-15 15:01:17 -07001247
James Feist0eeb79c2019-10-09 13:35:29 -07001248 if (on)
1249 {
James Feist5cb06082019-10-24 17:11:13 -07001250 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001251 }
James Feist918e18c2018-02-13 15:51:07 -08001252 };
James Feist9eb0b582018-04-27 12:15:46 -07001253
1254 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -08001255 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -07001256 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
James Feist0eeb79c2019-10-09 13:35:29 -07001257 "openbmc_project/state/"
1258 "host0',arg0='xyz.openbmc_project.State.Host'",
James Feist9eb0b582018-04-27 12:15:46 -07001259 eventHandler);
1260
James Feist4131aea2018-03-09 09:47:30 -08001261 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -07001262 inotify_add_watch(fd, I2C_DEV_LOCATION,
1263 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -08001264 std::array<char, 4096> readBuffer;
1265 std::string pendingBuffer;
1266 // monitor for new i2c devices
1267 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1268 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -08001269 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -08001270 std::size_t bytes_transferred) {
1271 if (ec)
1272 {
1273 std::cout << "Callback Error " << ec << "\n";
1274 return;
1275 }
1276 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
1277 bool devChange = false;
1278 while (pendingBuffer.size() > sizeof(inotify_event))
1279 {
James Feista465ccc2019-02-08 12:51:01 -08001280 const inotify_event* iEvent =
1281 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -08001282 pendingBuffer.data());
1283 switch (iEvent->mask)
1284 {
James Feist9eb0b582018-04-27 12:15:46 -07001285 case IN_CREATE:
1286 case IN_MOVED_TO:
1287 case IN_DELETE:
1288 if (boost::starts_with(std::string(iEvent->name),
1289 "i2c"))
1290 {
1291 devChange = true;
1292 }
James Feist4131aea2018-03-09 09:47:30 -08001293 }
1294
1295 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
1296 }
James Feist6ebf9de2018-05-15 15:01:17 -07001297 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -08001298 {
James Feist5cb06082019-10-24 17:11:13 -07001299 rescanBusses(busMap, dbusInterfaceMap);
James Feist4131aea2018-03-09 09:47:30 -08001300 }
James Feist6ebf9de2018-05-15 15:01:17 -07001301
James Feist4131aea2018-03-09 09:47:30 -08001302 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1303 watchI2cBusses);
1304 };
1305
1306 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -05001307 // run the initial scan
James Feist5cb06082019-10-24 17:11:13 -07001308 rescanBusses(busMap, dbusInterfaceMap);
James Feist918e18c2018-02-13 15:51:07 -08001309
James Feist3cb5fec2018-01-23 14:41:51 -08001310 io.run();
1311 return 0;
1312}