blob: 0f1350c924aa89a72cb28abbb31be9bb14336948 [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -08001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
James Feist3b860982018-10-02 14:34:07 -070017#include <errno.h>
James Feist3cb5fec2018-01-23 14:41:51 -080018#include <fcntl.h>
James Feist3b860982018-10-02 14:34:07 -070019#include <sys/inotify.h>
20#include <sys/ioctl.h>
21
22#include <Utils.hpp>
23#include <boost/algorithm/string/predicate.hpp>
24#include <boost/container/flat_map.hpp>
25#include <chrono>
26#include <ctime>
James Feist3cb5fec2018-01-23 14:41:51 -080027#include <fstream>
28#include <future>
James Feist3cb5fec2018-01-23 14:41:51 -080029#include <iostream>
James Feist3f8a2782018-02-12 09:24:42 -080030#include <regex>
James Feist3b860982018-10-02 14:34:07 -070031#include <sdbusplus/asio/connection.hpp>
32#include <sdbusplus/asio/object_server.hpp>
33#include <thread>
James Feista465ccc2019-02-08 12:51:01 -080034#include <variant>
James Feist3b860982018-10-02 14:34:07 -070035
36extern "C" {
37#include <i2c/smbus.h>
38#include <linux/i2c-dev.h>
39}
James Feist3cb5fec2018-01-23 14:41:51 -080040
Ed Tanous072e25d2018-12-16 21:45:20 -080041namespace fs = std::filesystem;
James Feist3cb5fec2018-01-23 14:41:51 -080042static constexpr bool DEBUG = false;
43static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feistb49ffc32018-05-02 11:10:43 -070044constexpr size_t MAX_FRU_SIZE = 512;
45constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
James Feist26c27ad2018-07-25 15:09:40 -070046constexpr size_t busTimeoutSeconds = 5;
James Feist3cb5fec2018-01-23 14:41:51 -080047
James Feista465ccc2019-02-08 12:51:01 -080048const static constexpr char* BASEBOARD_FRU_LOCATION =
James Feist3cb5fec2018-01-23 14:41:51 -080049 "/etc/fru/baseboard.fru.bin";
50
James Feista465ccc2019-02-08 12:51:01 -080051const static constexpr char* I2C_DEV_LOCATION = "/dev";
James Feist4131aea2018-03-09 09:47:30 -080052
James Feista465ccc2019-02-08 12:51:01 -080053static constexpr std::array<const char*, 5> FRU_AREAS = {
James Feist3cb5fec2018-01-23 14:41:51 -080054 "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -070055const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
James Feist3cb5fec2018-01-23 14:41:51 -080056using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
57using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
58
James Feist444830e2019-04-05 08:38:16 -070059static std::set<size_t> busBlacklist;
James Feist6ebf9de2018-05-15 15:01:17 -070060struct FindDevicesWithCallback;
61
Nikhil Potaded8884f12019-03-27 13:27:13 -070062static BusMap busMap;
63
James Feistc95cb142018-02-26 10:41:42 -080064static bool isMuxBus(size_t bus)
65{
Ed Tanous072e25d2018-12-16 21:45:20 -080066 return is_symlink(std::filesystem::path(
James Feistc95cb142018-02-26 10:41:42 -080067 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
68}
69
Vijay Khemka2d681f62018-11-06 15:51:00 -080070static int isDevice16Bit(int file)
71{
72 /* Get first byte */
73 int byte1 = i2c_smbus_read_byte_data(file, 0);
74 if (byte1 < 0)
75 {
76 return byte1;
77 }
78 /* Read 7 more bytes, it will read same first byte in case of
79 * 8 bit but it will read next byte in case of 16 bit
80 */
81 for (int i = 0; i < 7; i++)
82 {
83 int byte2 = i2c_smbus_read_byte_data(file, 0);
84 if (byte2 < 0)
85 {
86 return byte2;
87 }
88 if (byte2 != byte1)
89 {
90 return 1;
91 }
92 }
93 return 0;
94}
95
96static int read_block_data(int flag, int file, uint16_t offset, uint8_t len,
James Feista465ccc2019-02-08 12:51:01 -080097 uint8_t* buf)
Vijay Khemka2d681f62018-11-06 15:51:00 -080098{
James Feist98132792019-07-09 13:29:09 -070099 uint8_t low_addr = static_cast<uint8_t>(offset);
100 uint8_t high_addr = static_cast<uint8_t>(offset >> 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800101
102 if (flag == 0)
103 {
104 return i2c_smbus_read_i2c_block_data(file, low_addr, len, buf);
105 }
106
107 /* This is for 16 bit addressing EEPROM device. First an offset
108 * needs to be written before read data from a offset
109 */
110 int ret = i2c_smbus_write_byte_data(file, 0, low_addr);
111 if (ret < 0)
112 {
113 return ret;
114 }
115
116 return i2c_smbus_read_i2c_block_data(file, high_addr, len, buf);
117}
118
James Feist24bae7a2019-04-03 09:50:56 -0700119bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
120{
121 // ipmi spec format version number is currently at 1, verify it
122 if (blockData[0] != 0x1)
123 {
124 return false;
125 }
126
127 // verify pad is set to 0
128 if (blockData[6] != 0x0)
129 {
130 return false;
131 }
132
133 // verify offsets are 0, or don't point to another offset
134 std::set<uint8_t> foundOffsets;
135 for (int ii = 1; ii < 6; ii++)
136 {
137 if (blockData[ii] == 0)
138 {
139 continue;
140 }
James Feist0eb40352019-04-09 14:44:04 -0700141 auto inserted = foundOffsets.insert(blockData[ii]);
142 if (!inserted.second)
James Feist24bae7a2019-04-03 09:50:56 -0700143 {
144 return false;
145 }
146 }
147
148 // validate checksum
149 size_t sum = 0;
150 for (int jj = 0; jj < 7; jj++)
151 {
152 sum += blockData[jj];
153 }
154 sum = (256 - sum) & 0xFF;
155
156 if (sum != blockData[7])
157 {
158 return false;
159 }
160 return true;
161}
162
James Feist3cb5fec2018-01-23 14:41:51 -0800163int get_bus_frus(int file, int first, int last, int bus,
164 std::shared_ptr<DeviceMap> devices)
165{
James Feist3cb5fec2018-01-23 14:41:51 -0800166
James Feist26c27ad2018-07-25 15:09:40 -0700167 std::future<int> future = std::async(std::launch::async, [&]() {
168 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data;
Vijay Khemka2d681f62018-11-06 15:51:00 -0800169
James Feist26c27ad2018-07-25 15:09:40 -0700170 for (int ii = first; ii <= last; ii++)
James Feist3cb5fec2018-01-23 14:41:51 -0800171 {
James Feist3cb5fec2018-01-23 14:41:51 -0800172
James Feist26c27ad2018-07-25 15:09:40 -0700173 // Set slave address
174 if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800175 {
James Feist26c27ad2018-07-25 15:09:40 -0700176 std::cerr << "device at bus " << bus << "register " << ii
177 << "busy\n";
178 continue;
179 }
180 // probe
181 else if (i2c_smbus_read_byte(file) < 0)
182 {
183 continue;
184 }
James Feist3cb5fec2018-01-23 14:41:51 -0800185
James Feist26c27ad2018-07-25 15:09:40 -0700186 if (DEBUG)
187 {
188 std::cout << "something at bus " << bus << "addr " << ii
189 << "\n";
190 }
Vijay Khemka2d681f62018-11-06 15:51:00 -0800191
192 /* Check for Device type if it is 8 bit or 16 bit */
193 int flag = isDevice16Bit(file);
194 if (flag < 0)
195 {
196 std::cerr << "failed to read bus " << bus << " address " << ii
197 << "\n";
198 continue;
199 }
200
201 if (read_block_data(flag, file, 0x0, 0x8, block_data.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700202 {
203 std::cerr << "failed to read bus " << bus << " address " << ii
204 << "\n";
205 continue;
206 }
James Feist26c27ad2018-07-25 15:09:40 -0700207
208 // check the header checksum
James Feist24bae7a2019-04-03 09:50:56 -0700209 if (validateHeader(block_data))
James Feist26c27ad2018-07-25 15:09:40 -0700210 {
211 std::vector<char> device;
212 device.insert(device.end(), block_data.begin(),
213 block_data.begin() + 8);
214
James Feist0eb40352019-04-09 14:44:04 -0700215 for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
James Feist26c27ad2018-07-25 15:09:40 -0700216 {
217 auto area_offset = device[jj];
218 if (area_offset != 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800219 {
James Feist98132792019-07-09 13:29:09 -0700220 area_offset = static_cast<char>(area_offset * 8);
Vijay Khemka2d681f62018-11-06 15:51:00 -0800221 if (read_block_data(flag, file, area_offset, 0x8,
222 block_data.data()) < 0)
James Feist3cb5fec2018-01-23 14:41:51 -0800223 {
224 std::cerr << "failed to read bus " << bus
225 << " address " << ii << "\n";
226 return -1;
227 }
James Feist26c27ad2018-07-25 15:09:40 -0700228 int length = block_data[1] * 8;
James Feist3cb5fec2018-01-23 14:41:51 -0800229 device.insert(device.end(), block_data.begin(),
James Feist26c27ad2018-07-25 15:09:40 -0700230 block_data.begin() + 8);
231 length -= 8;
James Feist98132792019-07-09 13:29:09 -0700232 area_offset = static_cast<char>(area_offset + 8);
James Feist26c27ad2018-07-25 15:09:40 -0700233
234 while (length > 0)
235 {
236 auto to_get = std::min(0x20, length);
James Feist98132792019-07-09 13:29:09 -0700237
238 if (read_block_data(flag, file, area_offset,
239 static_cast<uint8_t>(to_get),
Vijay Khemka2d681f62018-11-06 15:51:00 -0800240 block_data.data()) < 0)
James Feist26c27ad2018-07-25 15:09:40 -0700241 {
242 std::cerr << "failed to read bus " << bus
243 << " address " << ii << "\n";
244 return -1;
245 }
246 device.insert(device.end(), block_data.begin(),
247 block_data.begin() + to_get);
James Feist98132792019-07-09 13:29:09 -0700248 area_offset =
249 static_cast<char>(area_offset + to_get);
James Feist26c27ad2018-07-25 15:09:40 -0700250 length -= to_get;
251 }
James Feist3cb5fec2018-01-23 14:41:51 -0800252 }
253 }
James Feist26c27ad2018-07-25 15:09:40 -0700254 devices->emplace(ii, device);
James Feist3cb5fec2018-01-23 14:41:51 -0800255 }
James Feist24bae7a2019-04-03 09:50:56 -0700256 else if (DEBUG)
James Feist9945ddf2019-03-07 13:57:36 -0800257 {
James Feist24bae7a2019-04-03 09:50:56 -0700258 std::cerr << "Illegal header at bus " << bus << " address "
259 << ii << "\n";
James Feist9945ddf2019-03-07 13:57:36 -0800260 }
James Feist3cb5fec2018-01-23 14:41:51 -0800261 }
James Feist26c27ad2018-07-25 15:09:40 -0700262 return 1;
263 });
264 std::future_status status =
265 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
266 if (status == std::future_status::timeout)
267 {
268 std::cerr << "Error reading bus " << bus << "\n";
James Feist444830e2019-04-05 08:38:16 -0700269 busBlacklist.insert(bus);
270 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700271 return -1;
James Feist3cb5fec2018-01-23 14:41:51 -0800272 }
273
James Feist444830e2019-04-05 08:38:16 -0700274 close(file);
James Feist26c27ad2018-07-25 15:09:40 -0700275 return future.get();
James Feist3cb5fec2018-01-23 14:41:51 -0800276}
277
James Feista465ccc2019-02-08 12:51:01 -0800278static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700279 BusMap& busmap)
James Feist3cb5fec2018-01-23 14:41:51 -0800280{
James Feista465ccc2019-02-08 12:51:01 -0800281 for (auto& i2cBus : i2cBuses)
James Feist3cb5fec2018-01-23 14:41:51 -0800282 {
283 auto busnum = i2cBus.string();
284 auto lastDash = busnum.rfind(std::string("-"));
285 // delete everything before dash inclusive
286 if (lastDash != std::string::npos)
287 {
288 busnum.erase(0, lastDash + 1);
289 }
290 auto bus = std::stoi(busnum);
James Feist444830e2019-04-05 08:38:16 -0700291 if (busBlacklist.find(bus) != busBlacklist.end())
292 {
293 continue; // skip previously failed busses
294 }
James Feist3cb5fec2018-01-23 14:41:51 -0800295
296 auto file = open(i2cBus.c_str(), O_RDWR);
297 if (file < 0)
298 {
299 std::cerr << "unable to open i2c device " << i2cBus.string()
300 << "\n";
301 continue;
302 }
303 unsigned long funcs = 0;
304
305 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
306 {
307 std::cerr
308 << "Error: Could not get the adapter functionality matrix bus"
309 << bus << "\n";
310 continue;
311 }
312 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
313 !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
314 {
315 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
316 << bus << "\n";
317 continue;
318 }
James Feist98132792019-07-09 13:29:09 -0700319 auto& device = busmap[bus];
James Feist3cb5fec2018-01-23 14:41:51 -0800320 device = std::make_shared<DeviceMap>();
321
Nikhil Potaded8884f12019-03-27 13:27:13 -0700322 // i2cdetect by default uses the range 0x03 to 0x77, as
323 // this is what we have tested with, use this range. Could be
324 // changed in future.
325 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800326 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700327 std::cerr << "Scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800328 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700329
330 // fd is closed in this function in case the bus locks up
331 get_bus_frus(file, 0x03, 0x77, bus, device);
332
333 if (DEBUG)
James Feistc95cb142018-02-26 10:41:42 -0800334 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700335 std::cerr << "Done scanning bus " << bus << "\n";
James Feistc95cb142018-02-26 10:41:42 -0800336 }
James Feist3cb5fec2018-01-23 14:41:51 -0800337 }
James Feist3cb5fec2018-01-23 14:41:51 -0800338}
339
James Feist6ebf9de2018-05-15 15:01:17 -0700340// this class allows an async response after all i2c devices are discovered
341struct FindDevicesWithCallback
342 : std::enable_shared_from_this<FindDevicesWithCallback>
343{
James Feista465ccc2019-02-08 12:51:01 -0800344 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
James Feist98132792019-07-09 13:29:09 -0700345 boost::asio::io_service& io, BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800346 std::function<void(void)>&& callback) :
James Feist6ebf9de2018-05-15 15:01:17 -0700347 _i2cBuses(i2cBuses),
James Feist98132792019-07-09 13:29:09 -0700348 _io(io), _busMap(busmap), _callback(std::move(callback))
James Feist6ebf9de2018-05-15 15:01:17 -0700349 {
350 }
351 ~FindDevicesWithCallback()
352 {
353 _callback();
354 }
355 void run()
356 {
James Feist98132792019-07-09 13:29:09 -0700357 FindI2CDevices(_i2cBuses, _busMap);
James Feist6ebf9de2018-05-15 15:01:17 -0700358 }
359
James Feista465ccc2019-02-08 12:51:01 -0800360 const std::vector<fs::path>& _i2cBuses;
361 boost::asio::io_service& _io;
362 BusMap& _busMap;
James Feist6ebf9de2018-05-15 15:01:17 -0700363 std::function<void(void)> _callback;
364};
365
James Feist3cb5fec2018-01-23 14:41:51 -0800366static const std::tm intelEpoch(void)
367{
James Feist98132792019-07-09 13:29:09 -0700368 std::tm val = {};
James Feist3cb5fec2018-01-23 14:41:51 -0800369 val.tm_year = 1996 - 1900;
370 return val;
371}
372
James Feista465ccc2019-02-08 12:51:01 -0800373bool formatFru(const std::vector<char>& fruBytes,
374 boost::container::flat_map<std::string, std::string>& result)
James Feist3cb5fec2018-01-23 14:41:51 -0800375{
James Feista465ccc2019-02-08 12:51:01 -0800376 static const std::vector<const char*> CHASSIS_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800377 "PART_NUMBER", "SERIAL_NUMBER", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800378
James Feista465ccc2019-02-08 12:51:01 -0800379 static const std::vector<const char*> BOARD_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800380 "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
381 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800382
James Feista465ccc2019-02-08 12:51:01 -0800383 static const std::vector<const char*> PRODUCT_FRU_AREAS = {
Vijay Khemka5d5de442018-11-07 10:51:25 -0800384 "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
385 "VERSION", "SERIAL_NUMBER", "ASSET_TAG",
386 "FRU_VERSION_ID", "INFO_AM1", "INFO_AM2"};
James Feist3cb5fec2018-01-23 14:41:51 -0800387
James Feistd068e932018-09-20 10:53:07 -0700388 if (fruBytes.size() <= 8)
James Feist3cb5fec2018-01-23 14:41:51 -0800389 {
390 return false;
391 }
392 std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
James Feist9eb0b582018-04-27 12:15:46 -0700393 result["Common_Format_Version"] =
James Feist3cb5fec2018-01-23 14:41:51 -0800394 std::to_string(static_cast<int>(*fruAreaOffsetField));
395
James Feista465ccc2019-02-08 12:51:01 -0800396 const std::vector<const char*>* fieldData;
James Feist3cb5fec2018-01-23 14:41:51 -0800397
James Feist0eb40352019-04-09 14:44:04 -0700398 for (const std::string& area : FRU_AREAS)
James Feist3cb5fec2018-01-23 14:41:51 -0800399 {
400 fruAreaOffsetField++;
401 if (fruAreaOffsetField >= fruBytes.end())
402 {
403 return false;
404 }
405 size_t offset = (*fruAreaOffsetField) * 8;
406
407 if (offset > 1)
408 {
409 // +2 to skip format and length
410 std::vector<char>::const_iterator fruBytesIter =
411 fruBytes.begin() + offset + 2;
412
413 if (fruBytesIter >= fruBytes.end())
414 {
415 return false;
416 }
417
418 if (area == "CHASSIS")
419 {
420 result["CHASSIS_TYPE"] =
421 std::to_string(static_cast<int>(*fruBytesIter));
422 fruBytesIter += 1;
423 fieldData = &CHASSIS_FRU_AREAS;
424 }
425 else if (area == "BOARD")
426 {
427 result["BOARD_LANGUAGE_CODE"] =
428 std::to_string(static_cast<int>(*fruBytesIter));
429 fruBytesIter += 1;
430 if (fruBytesIter >= fruBytes.end())
431 {
432 return false;
433 }
434
435 unsigned int minutes = *fruBytesIter |
436 *(fruBytesIter + 1) << 8 |
437 *(fruBytesIter + 2) << 16;
438 std::tm fruTime = intelEpoch();
439 time_t timeValue = mktime(&fruTime);
440 timeValue += minutes * 60;
441 fruTime = *gmtime(&timeValue);
442
443 result["BOARD_MANUFACTURE_DATE"] = asctime(&fruTime);
444 result["BOARD_MANUFACTURE_DATE"]
445 .pop_back(); // remove trailing null
446 fruBytesIter += 3;
447 fieldData = &BOARD_FRU_AREAS;
448 }
449 else if (area == "PRODUCT")
450 {
451 result["PRODUCT_LANGUAGE_CODE"] =
452 std::to_string(static_cast<int>(*fruBytesIter));
453 fruBytesIter += 1;
454 fieldData = &PRODUCT_FRU_AREAS;
455 }
456 else
457 {
458 continue;
459 }
James Feista465ccc2019-02-08 12:51:01 -0800460 for (auto& field : *fieldData)
James Feist3cb5fec2018-01-23 14:41:51 -0800461 {
462 if (fruBytesIter >= fruBytes.end())
463 {
464 return false;
465 }
466
Vijay Khemka5d5de442018-11-07 10:51:25 -0800467 /* Checking for last byte C1 to indicate that no more
468 * field to be read */
James Feist98132792019-07-09 13:29:09 -0700469 if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
Vijay Khemka5d5de442018-11-07 10:51:25 -0800470 {
471 break;
472 }
473
Ed Tanous2147e672019-02-27 13:59:56 -0800474 size_t length = *fruBytesIter & 0x3f;
475 fruBytesIter += 1;
476
James Feist3cb5fec2018-01-23 14:41:51 -0800477 if (fruBytesIter >= fruBytes.end())
478 {
479 return false;
480 }
Ed Tanous2147e672019-02-27 13:59:56 -0800481 std::string value(fruBytesIter, fruBytesIter + length);
James Feist3cb5fec2018-01-23 14:41:51 -0800482
Ed Tanous2147e672019-02-27 13:59:56 -0800483 // Strip non null characters from the end
484 value.erase(std::find_if(value.rbegin(), value.rend(),
485 [](char ch) { return ch != 0; })
486 .base(),
487 value.end());
488
James Feist0eb40352019-04-09 14:44:04 -0700489 result[area + "_" + field] = std::move(value);
Ed Tanous2147e672019-02-27 13:59:56 -0800490
James Feist3cb5fec2018-01-23 14:41:51 -0800491 fruBytesIter += length;
492 if (fruBytesIter >= fruBytes.end())
493 {
494 std::cerr << "Warning Fru Length Mismatch:\n ";
James Feista465ccc2019-02-08 12:51:01 -0800495 for (auto& c : fruBytes)
James Feist3cb5fec2018-01-23 14:41:51 -0800496 {
497 std::cerr << c;
498 }
499 std::cerr << "\n";
500 if (DEBUG)
501 {
James Feista465ccc2019-02-08 12:51:01 -0800502 for (auto& keyPair : result)
James Feist3cb5fec2018-01-23 14:41:51 -0800503 {
504 std::cerr << keyPair.first << " : "
505 << keyPair.second << "\n";
506 }
507 }
508 return false;
509 }
510 }
511 }
512 }
513
514 return true;
515}
516
Nikhil Potaded8884f12019-03-27 13:27:13 -0700517std::vector<uint8_t>& getFruInfo(const uint8_t& bus, const uint8_t& address)
518{
519 auto deviceMap = busMap.find(bus);
520 if (deviceMap == busMap.end())
521 {
522 throw std::invalid_argument("Invalid Bus.");
523 }
524 auto device = deviceMap->second->find(address);
525 if (device == deviceMap->second->end())
526 {
527 throw std::invalid_argument("Invalid Address.");
528 }
529 std::vector<uint8_t>& ret =
530 reinterpret_cast<std::vector<uint8_t>&>(device->second);
531
532 return ret;
533}
534
James Feist3cb5fec2018-01-23 14:41:51 -0800535void AddFruObjectToDbus(
James Feista465ccc2019-02-08 12:51:01 -0800536 std::vector<char>& device, sdbusplus::asio::object_server& objServer,
537 boost::container::flat_map<
538 std::pair<size_t, size_t>,
539 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feist13b86d62018-05-29 11:24:35 -0700540 uint32_t bus, uint32_t address)
James Feist3cb5fec2018-01-23 14:41:51 -0800541{
542 boost::container::flat_map<std::string, std::string> formattedFru;
543 if (!formatFru(device, formattedFru))
544 {
545 std::cerr << "failed to format fru for device at bus " << std::hex
Nikhil Potaded8884f12019-03-27 13:27:13 -0700546 << bus << " address " << address << "\n";
James Feist3cb5fec2018-01-23 14:41:51 -0800547 return;
548 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700549
James Feist3cb5fec2018-01-23 14:41:51 -0800550 auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
551 std::string productName;
Patrick Venture96cdaef2019-07-30 13:30:52 -0700552 // Not found under Board section or an empty string.
553 if (productNameFind == formattedFru.end() ||
554 productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800555 {
556 productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
557 }
Patrick Venture96cdaef2019-07-30 13:30:52 -0700558 // Found under Product section and not an empty string.
559 if (productNameFind != formattedFru.end() &&
560 !productNameFind->second.empty())
James Feist3cb5fec2018-01-23 14:41:51 -0800561 {
562 productName = productNameFind->second;
James Feist3f8a2782018-02-12 09:24:42 -0800563 std::regex illegalObject("[^A-Za-z0-9_]");
564 productName = std::regex_replace(productName, illegalObject, "_");
James Feist3cb5fec2018-01-23 14:41:51 -0800565 }
566 else
567 {
568 productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
569 UNKNOWN_BUS_OBJECT_COUNT++;
570 }
571
James Feist918e18c2018-02-13 15:51:07 -0800572 productName = "/xyz/openbmc_project/FruDevice/" + productName;
James Feist918e18c2018-02-13 15:51:07 -0800573 // avoid duplicates by checking to see if on a mux
James Feist79e9c0b2018-03-15 15:21:17 -0700574 if (bus > 0)
James Feist918e18c2018-02-13 15:51:07 -0800575 {
James Feist79e9c0b2018-03-15 15:21:17 -0700576 size_t index = 0;
James Feista465ccc2019-02-08 12:51:01 -0800577 for (auto const& busIface : dbusInterfaceMap)
James Feist918e18c2018-02-13 15:51:07 -0800578 {
James Feist9eb0b582018-04-27 12:15:46 -0700579 if ((busIface.second->get_object_path() == productName))
James Feist918e18c2018-02-13 15:51:07 -0800580 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700581 if (isMuxBus(bus) && address == busIface.first.second &&
James Feist98132792019-07-09 13:29:09 -0700582 (getFruInfo(static_cast<uint8_t>(busIface.first.first),
583 static_cast<uint8_t>(busIface.first.second)) ==
584 getFruInfo(static_cast<uint8_t>(bus),
585 static_cast<uint8_t>(address))))
James Feist79e9c0b2018-03-15 15:21:17 -0700586 {
Nikhil Potaded8884f12019-03-27 13:27:13 -0700587 // This device is already added to the lower numbered bus,
588 // do not replicate it.
589 return;
James Feist79e9c0b2018-03-15 15:21:17 -0700590 }
591 // add underscore _index for the same object path on dbus
592 std::string strIndex = std::to_string(index);
593 if (index > 0)
594 {
595 productName.substr(0, productName.size() - strIndex.size());
596 }
597 else
598 {
599 productName += "_";
600 }
601 productName += std::to_string(index++);
James Feist918e18c2018-02-13 15:51:07 -0800602 }
603 }
604 }
James Feist3cb5fec2018-01-23 14:41:51 -0800605
James Feist9eb0b582018-04-27 12:15:46 -0700606 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
607 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
608 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
609
James Feista465ccc2019-02-08 12:51:01 -0800610 for (auto& property : formattedFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800611 {
James Feist9eb0b582018-04-27 12:15:46 -0700612
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700613 std::regex_replace(property.second.begin(), property.second.begin(),
614 property.second.end(), NON_ASCII_REGEX, "_");
James Feist9eb0b582018-04-27 12:15:46 -0700615 if (property.second.empty())
616 {
617 continue;
618 }
619 std::string key =
620 std::regex_replace(property.first, NON_ASCII_REGEX, "_");
621 if (!iface->register_property(key, property.second + '\0'))
622 {
623 std::cerr << "illegal key: " << key << "\n";
624 }
Jae Hyun Yoo3936e7a2018-03-23 17:26:16 -0700625 if (DEBUG)
626 {
627 std::cout << property.first << ": " << property.second << "\n";
628 }
James Feist3cb5fec2018-01-23 14:41:51 -0800629 }
James Feist2a9d6db2018-04-27 15:48:28 -0700630
631 // baseboard will be 0, 0
James Feist13b86d62018-05-29 11:24:35 -0700632 iface->register_property("BUS", bus);
633 iface->register_property("ADDRESS", address);
James Feist2a9d6db2018-04-27 15:48:28 -0700634
James Feist9eb0b582018-04-27 12:15:46 -0700635 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800636}
637
James Feista465ccc2019-02-08 12:51:01 -0800638static bool readBaseboardFru(std::vector<char>& baseboardFru)
James Feist3cb5fec2018-01-23 14:41:51 -0800639{
640 // try to read baseboard fru from file
641 std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
642 if (baseboardFruFile.good())
643 {
644 baseboardFruFile.seekg(0, std::ios_base::end);
James Feist98132792019-07-09 13:29:09 -0700645 size_t fileSize = static_cast<size_t>(baseboardFruFile.tellg());
James Feist3cb5fec2018-01-23 14:41:51 -0800646 baseboardFru.resize(fileSize);
647 baseboardFruFile.seekg(0, std::ios_base::beg);
648 baseboardFruFile.read(baseboardFru.data(), fileSize);
649 }
650 else
651 {
652 return false;
653 }
654 return true;
655}
656
James Feista465ccc2019-02-08 12:51:01 -0800657bool writeFru(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
James Feistb49ffc32018-05-02 11:10:43 -0700658{
659 boost::container::flat_map<std::string, std::string> tmp;
660 if (fru.size() > MAX_FRU_SIZE)
661 {
662 std::cerr << "Invalid fru.size() during writeFru\n";
663 return false;
664 }
665 // verify legal fru by running it through fru parsing logic
James Feista465ccc2019-02-08 12:51:01 -0800666 if (!formatFru(reinterpret_cast<const std::vector<char>&>(fru), tmp))
James Feistb49ffc32018-05-02 11:10:43 -0700667 {
668 std::cerr << "Invalid fru format during writeFru\n";
669 return false;
670 }
671 // baseboard fru
672 if (bus == 0 && address == 0)
673 {
674 std::ofstream file(BASEBOARD_FRU_LOCATION, std::ios_base::binary);
675 if (!file.good())
676 {
677 std::cerr << "Error opening file " << BASEBOARD_FRU_LOCATION
678 << "\n";
James Feistddb78302018-09-06 11:45:42 -0700679 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700680 return false;
681 }
James Feista465ccc2019-02-08 12:51:01 -0800682 file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
James Feistb49ffc32018-05-02 11:10:43 -0700683 return file.good();
684 }
685 else
686 {
687 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
688
689 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
690 if (file < 0)
691 {
692 std::cerr << "unable to open i2c device " << i2cBus << "\n";
James Feistddb78302018-09-06 11:45:42 -0700693 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700694 return false;
695 }
696 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
697 {
698 std::cerr << "unable to set device address\n";
699 close(file);
James Feistddb78302018-09-06 11:45:42 -0700700 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700701 return false;
702 }
703
704 constexpr const size_t RETRY_MAX = 2;
705 uint16_t index = 0;
706 size_t retries = RETRY_MAX;
707 while (index < fru.size())
708 {
709 if ((index && ((index % (MAX_EEPROM_PAGE_INDEX + 1)) == 0)) &&
710 (retries == RETRY_MAX))
711 {
712 // The 4K EEPROM only uses the A2 and A1 device address bits
713 // with the third bit being a memory page address bit.
714 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
715 {
716 std::cerr << "unable to set device address\n";
717 close(file);
James Feistddb78302018-09-06 11:45:42 -0700718 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700719 return false;
720 }
721 }
722
James Feist98132792019-07-09 13:29:09 -0700723 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
724 fru[index]) < 0)
James Feistb49ffc32018-05-02 11:10:43 -0700725 {
726 if (!retries--)
727 {
728 std::cerr << "error writing fru: " << strerror(errno)
729 << "\n";
730 close(file);
James Feistddb78302018-09-06 11:45:42 -0700731 throw DBusInternalError();
James Feistb49ffc32018-05-02 11:10:43 -0700732 return false;
733 }
734 }
735 else
736 {
737 retries = RETRY_MAX;
738 index++;
739 }
740 // most eeproms require 5-10ms between writes
741 std::this_thread::sleep_for(std::chrono::milliseconds(10));
742 }
743 close(file);
744 return true;
745 }
746}
747
James Feist9eb0b582018-04-27 12:15:46 -0700748void rescanBusses(
James Feist98132792019-07-09 13:29:09 -0700749 boost::asio::io_service& io, BusMap& busmap,
James Feista465ccc2019-02-08 12:51:01 -0800750 boost::container::flat_map<
751 std::pair<size_t, size_t>,
752 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
James Feista465ccc2019-02-08 12:51:01 -0800753 sdbusplus::asio::object_server& objServer)
James Feist918e18c2018-02-13 15:51:07 -0800754{
James Feist6ebf9de2018-05-15 15:01:17 -0700755 static boost::asio::deadline_timer timer(io);
756 timer.expires_from_now(boost::posix_time::seconds(1));
James Feist918e18c2018-02-13 15:51:07 -0800757
Gunnar Mills6f0ae942018-08-31 12:38:03 -0500758 // setup an async wait in case we get flooded with requests
James Feist98132792019-07-09 13:29:09 -0700759 timer.async_wait([&](const boost::system::error_code&) {
James Feist4131aea2018-03-09 09:47:30 -0800760 auto devDir = fs::path("/dev/");
James Feist4131aea2018-03-09 09:47:30 -0800761 std::vector<fs::path> i2cBuses;
James Feist918e18c2018-02-13 15:51:07 -0800762
Nikhil Potaded8884f12019-03-27 13:27:13 -0700763 boost::container::flat_map<size_t, fs::path> busPaths;
764 if (!getI2cDevicePaths(devDir, busPaths))
James Feist918e18c2018-02-13 15:51:07 -0800765 {
James Feist4131aea2018-03-09 09:47:30 -0800766 std::cerr << "unable to find i2c devices\n";
767 return;
James Feist918e18c2018-02-13 15:51:07 -0800768 }
Nikhil Potaded8884f12019-03-27 13:27:13 -0700769
770 for (auto busPath : busPaths)
771 {
772 i2cBuses.emplace_back(busPath.second);
773 }
James Feist4131aea2018-03-09 09:47:30 -0800774
James Feist98132792019-07-09 13:29:09 -0700775 busmap.clear();
James Feist6ebf9de2018-05-15 15:01:17 -0700776 auto scan = std::make_shared<FindDevicesWithCallback>(
James Feist98132792019-07-09 13:29:09 -0700777 i2cBuses, io, busmap, [&]() {
James Feista465ccc2019-02-08 12:51:01 -0800778 for (auto& busIface : dbusInterfaceMap)
James Feist6ebf9de2018-05-15 15:01:17 -0700779 {
780 objServer.remove_interface(busIface.second);
781 }
James Feist4131aea2018-03-09 09:47:30 -0800782
James Feist6ebf9de2018-05-15 15:01:17 -0700783 dbusInterfaceMap.clear();
784 UNKNOWN_BUS_OBJECT_COUNT = 0;
James Feist4131aea2018-03-09 09:47:30 -0800785
James Feist6ebf9de2018-05-15 15:01:17 -0700786 // todo, get this from a more sensable place
787 std::vector<char> baseboardFru;
788 if (readBaseboardFru(baseboardFru))
789 {
790 boost::container::flat_map<int, std::vector<char>>
791 baseboardDev;
792 baseboardDev.emplace(0, baseboardFru);
James Feist98132792019-07-09 13:29:09 -0700793 busmap[0] = std::make_shared<DeviceMap>(baseboardDev);
James Feist6ebf9de2018-05-15 15:01:17 -0700794 }
James Feist98132792019-07-09 13:29:09 -0700795 for (auto& devicemap : busmap)
James Feist6ebf9de2018-05-15 15:01:17 -0700796 {
James Feista465ccc2019-02-08 12:51:01 -0800797 for (auto& device : *devicemap.second)
James Feist6ebf9de2018-05-15 15:01:17 -0700798 {
James Feist98132792019-07-09 13:29:09 -0700799 AddFruObjectToDbus(device.second, objServer,
James Feist6ebf9de2018-05-15 15:01:17 -0700800 dbusInterfaceMap, devicemap.first,
801 device.first);
802 }
803 }
804 });
805 scan->run();
806 });
James Feist918e18c2018-02-13 15:51:07 -0800807}
808
James Feist98132792019-07-09 13:29:09 -0700809int main()
James Feist3cb5fec2018-01-23 14:41:51 -0800810{
811 auto devDir = fs::path("/dev/");
James Feistc9dff1b2019-02-13 13:33:13 -0800812 auto matchString = std::string(R"(i2c-\d+$)");
James Feist3cb5fec2018-01-23 14:41:51 -0800813 std::vector<fs::path> i2cBuses;
814
James Feista3c180a2018-08-09 16:06:04 -0700815 if (!findFiles(devDir, matchString, i2cBuses))
James Feist3cb5fec2018-01-23 14:41:51 -0800816 {
817 std::cerr << "unable to find i2c devices\n";
818 return 1;
819 }
James Feist3cb5fec2018-01-23 14:41:51 -0800820
821 boost::asio::io_service io;
James Feist9eb0b582018-04-27 12:15:46 -0700822 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
823 auto objServer = sdbusplus::asio::object_server(systemBus);
Vijay Khemka065f6d92018-12-18 10:37:47 -0800824 systemBus->request_name("xyz.openbmc_project.FruDevice");
James Feist3cb5fec2018-01-23 14:41:51 -0800825
James Feist6ebf9de2018-05-15 15:01:17 -0700826 // this is a map with keys of pair(bus number, address) and values of
827 // the object on dbus
James Feist3cb5fec2018-01-23 14:41:51 -0800828 boost::container::flat_map<std::pair<size_t, size_t>,
James Feist9eb0b582018-04-27 12:15:46 -0700829 std::shared_ptr<sdbusplus::asio::dbus_interface>>
830 dbusInterfaceMap;
James Feist3cb5fec2018-01-23 14:41:51 -0800831
James Feist9eb0b582018-04-27 12:15:46 -0700832 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
833 objServer.add_interface("/xyz/openbmc_project/FruDevice",
834 "xyz.openbmc_project.FruDeviceManager");
James Feist3cb5fec2018-01-23 14:41:51 -0800835
836 iface->register_method("ReScan", [&]() {
James Feist98132792019-07-09 13:29:09 -0700837 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist3cb5fec2018-01-23 14:41:51 -0800838 });
James Feist2a9d6db2018-04-27 15:48:28 -0700839
Nikhil Potaded8884f12019-03-27 13:27:13 -0700840 iface->register_method("GetRawFru", getFruInfo);
James Feistb49ffc32018-05-02 11:10:43 -0700841
842 iface->register_method("WriteFru", [&](const uint8_t bus,
843 const uint8_t address,
James Feista465ccc2019-02-08 12:51:01 -0800844 const std::vector<uint8_t>& data) {
James Feistb49ffc32018-05-02 11:10:43 -0700845 if (!writeFru(bus, address, data))
846 {
James Feistddb78302018-09-06 11:45:42 -0700847 throw std::invalid_argument("Invalid Arguments.");
James Feistb49ffc32018-05-02 11:10:43 -0700848 return;
849 }
850 // schedule rescan on success
James Feist98132792019-07-09 13:29:09 -0700851 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feistb49ffc32018-05-02 11:10:43 -0700852 });
James Feist9eb0b582018-04-27 12:15:46 -0700853 iface->initialize();
James Feist3cb5fec2018-01-23 14:41:51 -0800854
James Feist9eb0b582018-04-27 12:15:46 -0700855 std::function<void(sdbusplus::message::message & message)> eventHandler =
James Feista465ccc2019-02-08 12:51:01 -0800856 [&](sdbusplus::message::message& message) {
James Feist918e18c2018-02-13 15:51:07 -0800857 std::string objectName;
James Feist9eb0b582018-04-27 12:15:46 -0700858 boost::container::flat_map<
James Feista465ccc2019-02-08 12:51:01 -0800859 std::string,
860 std::variant<std::string, bool, int64_t, uint64_t, double>>
James Feist9eb0b582018-04-27 12:15:46 -0700861 values;
862 message.read(objectName, values);
James Feist918e18c2018-02-13 15:51:07 -0800863 auto findPgood = values.find("pgood");
864 if (findPgood != values.end())
865 {
James Feist6ebf9de2018-05-15 15:01:17 -0700866
James Feist98132792019-07-09 13:29:09 -0700867 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist918e18c2018-02-13 15:51:07 -0800868 }
James Feist918e18c2018-02-13 15:51:07 -0800869 };
James Feist9eb0b582018-04-27 12:15:46 -0700870
871 sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
James Feista465ccc2019-02-08 12:51:01 -0800872 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist7bcd3f22019-03-18 16:04:04 -0700873 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
874 "openbmc_project/Chassis/Control/"
875 "Power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
James Feist9eb0b582018-04-27 12:15:46 -0700876 eventHandler);
877
James Feist4131aea2018-03-09 09:47:30 -0800878 int fd = inotify_init();
James Feist0eb40352019-04-09 14:44:04 -0700879 inotify_add_watch(fd, I2C_DEV_LOCATION,
880 IN_CREATE | IN_MOVED_TO | IN_DELETE);
James Feist4131aea2018-03-09 09:47:30 -0800881 std::array<char, 4096> readBuffer;
882 std::string pendingBuffer;
883 // monitor for new i2c devices
884 boost::asio::posix::stream_descriptor dirWatch(io, fd);
885 std::function<void(const boost::system::error_code, std::size_t)>
James Feista465ccc2019-02-08 12:51:01 -0800886 watchI2cBusses = [&](const boost::system::error_code& ec,
James Feist4131aea2018-03-09 09:47:30 -0800887 std::size_t bytes_transferred) {
888 if (ec)
889 {
890 std::cout << "Callback Error " << ec << "\n";
891 return;
892 }
893 pendingBuffer += std::string(readBuffer.data(), bytes_transferred);
894 bool devChange = false;
895 while (pendingBuffer.size() > sizeof(inotify_event))
896 {
James Feista465ccc2019-02-08 12:51:01 -0800897 const inotify_event* iEvent =
898 reinterpret_cast<const inotify_event*>(
James Feist4131aea2018-03-09 09:47:30 -0800899 pendingBuffer.data());
900 switch (iEvent->mask)
901 {
James Feist9eb0b582018-04-27 12:15:46 -0700902 case IN_CREATE:
903 case IN_MOVED_TO:
904 case IN_DELETE:
905 if (boost::starts_with(std::string(iEvent->name),
906 "i2c"))
907 {
908 devChange = true;
909 }
James Feist4131aea2018-03-09 09:47:30 -0800910 }
911
912 pendingBuffer.erase(0, sizeof(inotify_event) + iEvent->len);
913 }
James Feist6ebf9de2018-05-15 15:01:17 -0700914 if (devChange)
James Feist4131aea2018-03-09 09:47:30 -0800915 {
James Feist98132792019-07-09 13:29:09 -0700916 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist4131aea2018-03-09 09:47:30 -0800917 }
James Feist6ebf9de2018-05-15 15:01:17 -0700918
James Feist4131aea2018-03-09 09:47:30 -0800919 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
920 watchI2cBusses);
921 };
922
923 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
Gunnar Millsb3e42fe2018-06-13 15:48:27 -0500924 // run the initial scan
James Feist98132792019-07-09 13:29:09 -0700925 rescanBusses(io, busMap, dbusInterfaceMap, objServer);
James Feist918e18c2018-02-13 15:51:07 -0800926
James Feist3cb5fec2018-01-23 14:41:51 -0800927 io.run();
928 return 0;
929}