1 /*
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 /// \file fru_device.cpp
17
18 #include "fru_utils.hpp"
19 #include "utils.hpp"
20
21 #include <fcntl.h>
22 #include <sys/inotify.h>
23 #include <sys/ioctl.h>
24
25 #include <boost/algorithm/string/predicate.hpp>
26 #include <boost/asio/io_context.hpp>
27 #include <boost/asio/steady_timer.hpp>
28 #include <boost/container/flat_map.hpp>
29 #include <nlohmann/json.hpp>
30 #include <sdbusplus/asio/connection.hpp>
31 #include <sdbusplus/asio/object_server.hpp>
32
33 #include <array>
34 #include <cerrno>
35 #include <charconv>
36 #include <chrono>
37 #include <ctime>
38 #include <filesystem>
39 #include <fstream>
40 #include <functional>
41 #include <future>
42 #include <iomanip>
43 #include <iostream>
44 #include <limits>
45 #include <map>
46 #include <optional>
47 #include <regex>
48 #include <set>
49 #include <sstream>
50 #include <string>
51 #include <thread>
52 #include <utility>
53 #include <variant>
54 #include <vector>
55
56 extern "C"
57 {
58 #include <i2c/smbus.h>
59 #include <linux/i2c-dev.h>
60 }
61
62 namespace fs = std::filesystem;
63 static constexpr bool debug = false;
64 constexpr size_t maxFruSize = 512;
65 constexpr size_t maxEepromPageIndex = 255;
66 constexpr size_t busTimeoutSeconds = 10;
67
68 constexpr const char* blocklistPath = PACKAGE_DIR "blacklist.json";
69
70 const static constexpr char* baseboardFruLocation =
71 "/etc/fru/baseboard.fru.bin";
72
73 const static constexpr char* i2CDevLocation = "/dev";
74
75 // TODO Refactor these to not be globals
76 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
77 static boost::container::flat_map<size_t, std::optional<std::set<size_t>>>
78 busBlocklist;
79 struct FindDevicesWithCallback;
80
81 static boost::container::flat_map<
82 std::pair<size_t, size_t>, std::shared_ptr<sdbusplus::asio::dbus_interface>>
83 foundDevices;
84
85 static boost::container::flat_map<size_t, std::set<size_t>> failedAddresses;
86 static boost::container::flat_map<size_t, std::set<size_t>> fruAddresses;
87
88 boost::asio::io_context io;
89 // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
90
91 bool updateFRUProperty(
92 const std::string& updatePropertyReq, uint32_t bus, uint32_t address,
93 const std::string& propertyName,
94 boost::container::flat_map<
95 std::pair<size_t, size_t>,
96 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
97 size_t& unknownBusObjectCount, const bool& powerIsOn,
98 sdbusplus::asio::object_server& objServer,
99 std::shared_ptr<sdbusplus::asio::connection>& systemBus);
100
101 // Given a bus/address, produce the path in sysfs for an eeprom.
getEepromPath(size_t bus,size_t address)102 static std::string getEepromPath(size_t bus, size_t address)
103 {
104 std::stringstream output;
105 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
106 << std::setfill('0') << std::setw(4) << std::hex << address
107 << "/eeprom";
108 return output.str();
109 }
110
hasEepromFile(size_t bus,size_t address)111 static bool hasEepromFile(size_t bus, size_t address)
112 {
113 auto path = getEepromPath(bus, address);
114 try
115 {
116 return fs::exists(path);
117 }
118 catch (...)
119 {
120 return false;
121 }
122 }
123
readFromEeprom(int fd,off_t offset,size_t len,uint8_t * buf)124 static int64_t readFromEeprom(int fd, off_t offset, size_t len, uint8_t* buf)
125 {
126 auto result = lseek(fd, offset, SEEK_SET);
127 if (result < 0)
128 {
129 std::cerr << "failed to seek\n";
130 return -1;
131 }
132
133 return read(fd, buf, len);
134 }
135
busStrToInt(const std::string_view busName)136 static int busStrToInt(const std::string_view busName)
137 {
138 auto findBus = busName.rfind('-');
139 if (findBus == std::string::npos)
140 {
141 return -1;
142 }
143 std::string_view num = busName.substr(findBus + 1);
144 int val = 0;
145 std::from_chars(num.data(), num.data() + num.size(), val);
146 return val;
147 }
148
getRootBus(size_t bus)149 static int getRootBus(size_t bus)
150 {
151 auto ec = std::error_code();
152 auto path = std::filesystem::read_symlink(
153 std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
154 std::to_string(bus) + "/mux_device"),
155 ec);
156 if (ec)
157 {
158 return -1;
159 }
160
161 std::string filename = path.filename();
162 auto findBus = filename.find('-');
163 if (findBus == std::string::npos)
164 {
165 return -1;
166 }
167 return std::stoi(filename.substr(0, findBus));
168 }
169
isMuxBus(size_t bus)170 static bool isMuxBus(size_t bus)
171 {
172 auto ec = std::error_code();
173 auto isSymlink =
174 is_symlink(std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
175 std::to_string(bus) + "/mux_device"),
176 ec);
177 return (!ec && isSymlink);
178 }
179
makeProbeInterface(size_t bus,size_t address,sdbusplus::asio::object_server & objServer)180 static void makeProbeInterface(size_t bus, size_t address,
181 sdbusplus::asio::object_server& objServer)
182 {
183 if (isMuxBus(bus))
184 {
185 return; // the mux buses are random, no need to publish
186 }
187 auto [it, success] = foundDevices.emplace(
188 std::make_pair(bus, address),
189 objServer.add_interface(
190 "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" +
191 std::to_string(address),
192 "xyz.openbmc_project.Inventory.Item.I2CDevice"));
193 if (!success)
194 {
195 return; // already added
196 }
197 it->second->register_property("Bus", bus);
198 it->second->register_property("Address", address);
199 it->second->initialize();
200 }
201
isDevice16Bit(int file)202 static std::optional<bool> isDevice16Bit(int file)
203 {
204 // Set the higher data word address bits to 0. It's safe on 8-bit addressing
205 // EEPROMs because it doesn't write any actual data.
206 int ret = i2c_smbus_write_byte(file, 0);
207 if (ret < 0)
208 {
209 return std::nullopt;
210 }
211
212 /* Get first byte */
213 int byte1 = i2c_smbus_read_byte_data(file, 0);
214 if (byte1 < 0)
215 {
216 return std::nullopt;
217 }
218 /* Read 7 more bytes, it will read same first byte in case of
219 * 8 bit but it will read next byte in case of 16 bit
220 */
221 for (int i = 0; i < 7; i++)
222 {
223 int byte2 = i2c_smbus_read_byte_data(file, 0);
224 if (byte2 < 0)
225 {
226 return std::nullopt;
227 }
228 if (byte2 != byte1)
229 {
230 return true;
231 }
232 }
233 return false;
234 }
235
236 // Issue an I2C transaction to first write to_target_buf_len bytes,then read
237 // from_target_buf_len bytes.
i2cSmbusWriteThenRead(int file,uint16_t address,uint8_t * toTargetBuf,uint8_t toTargetBufLen,uint8_t * fromTargetBuf,uint8_t fromTargetBufLen)238 static int i2cSmbusWriteThenRead(int file, uint16_t address,
239 uint8_t* toTargetBuf, uint8_t toTargetBufLen,
240 uint8_t* fromTargetBuf,
241 uint8_t fromTargetBufLen)
242 {
243 if (toTargetBuf == nullptr || toTargetBufLen == 0 ||
244 fromTargetBuf == nullptr || fromTargetBufLen == 0)
245 {
246 return -1;
247 }
248
249 constexpr size_t smbusWriteThenReadMsgCount = 2;
250 std::array<struct i2c_msg, smbusWriteThenReadMsgCount> msgs{};
251 struct i2c_rdwr_ioctl_data rdwr
252 {};
253
254 msgs[0].addr = address;
255 msgs[0].flags = 0;
256 msgs[0].len = toTargetBufLen;
257 msgs[0].buf = toTargetBuf;
258 msgs[1].addr = address;
259 msgs[1].flags = I2C_M_RD;
260 msgs[1].len = fromTargetBufLen;
261 msgs[1].buf = fromTargetBuf;
262
263 rdwr.msgs = msgs.data();
264 rdwr.nmsgs = msgs.size();
265
266 int ret = ioctl(file, I2C_RDWR, &rdwr);
267
268 return (ret == static_cast<int>(msgs.size())) ? msgs[1].len : -1;
269 }
270
readData(bool is16bit,bool isBytewise,int file,uint16_t address,off_t offset,size_t len,uint8_t * buf)271 static int64_t readData(bool is16bit, bool isBytewise, int file,
272 uint16_t address, off_t offset, size_t len,
273 uint8_t* buf)
274 {
275 if (!is16bit)
276 {
277 if (!isBytewise)
278 {
279 return i2c_smbus_read_i2c_block_data(
280 file, static_cast<uint8_t>(offset), len, buf);
281 }
282
283 std::span<uint8_t> bufspan{buf, len};
284 for (size_t i = 0; i < len; i++)
285 {
286 int byte = i2c_smbus_read_byte_data(
287 file, static_cast<uint8_t>(offset + i));
288 if (byte < 0)
289 {
290 return static_cast<int64_t>(byte);
291 }
292 bufspan[i] = static_cast<uint8_t>(byte);
293 }
294 return static_cast<int64_t>(len);
295 }
296
297 offset = htobe16(offset);
298 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
299 uint8_t* u8Offset = reinterpret_cast<uint8_t*>(&offset);
300 return i2cSmbusWriteThenRead(file, address, u8Offset, 2, buf, len);
301 }
302
303 // TODO: This code is very similar to the non-eeprom version and can be merged
304 // with some tweaks.
processEeprom(int bus,int address)305 static std::vector<uint8_t> processEeprom(int bus, int address)
306 {
307 auto path = getEepromPath(bus, address);
308
309 int file = open(path.c_str(), O_RDONLY);
310 if (file < 0)
311 {
312 std::cerr << "Unable to open eeprom file: " << path << "\n";
313 return {};
314 }
315
316 std::string errorMessage = "eeprom at " + std::to_string(bus) +
317 " address " + std::to_string(address);
318 auto readFunc = [file](off_t offset, size_t length, uint8_t* outbuf) {
319 return readFromEeprom(file, offset, length, outbuf);
320 };
321 FRUReader reader(std::move(readFunc));
322 std::pair<std::vector<uint8_t>, bool> pair = readFRUContents(reader,
323 errorMessage);
324
325 close(file);
326 return pair.first;
327 }
328
findI2CEeproms(int i2cBus,const std::shared_ptr<DeviceMap> & devices)329 std::set<size_t> findI2CEeproms(int i2cBus,
330 const std::shared_ptr<DeviceMap>& devices)
331 {
332 std::set<size_t> foundList;
333
334 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
335
336 // For each file listed under the i2c device
337 // NOTE: This should be faster than just checking for each possible address
338 // path.
339 auto ec = std::error_code();
340 for (const auto& p : fs::directory_iterator(path, ec))
341 {
342 if (ec)
343 {
344 std::cerr << "directory_iterator err " << ec.message() << "\n";
345 break;
346 }
347 const std::string node = p.path().string();
348 std::smatch m;
349 bool found = std::regex_match(node, m,
350 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 std::string_view addressStringView(addressString);
365
366 size_t address = 0;
367 std::from_chars(addressStringView.begin(), addressStringView.end(),
368 address, 16);
369
370 const std::string eeprom = node + "/eeprom";
371
372 try
373 {
374 if (!fs::exists(eeprom))
375 {
376 continue;
377 }
378 }
379 catch (...)
380 {
381 continue;
382 }
383
384 // There is an eeprom file at this address, it may have invalid
385 // contents, but we found it.
386 foundList.insert(address);
387
388 std::vector<uint8_t> device = processEeprom(i2cBus, address);
389 if (!device.empty())
390 {
391 devices->emplace(address, device);
392 }
393 }
394
395 return foundList;
396 }
397
getBusFRUs(int file,int first,int last,int bus,std::shared_ptr<DeviceMap> devices,const bool & powerIsOn,sdbusplus::asio::object_server & objServer)398 int getBusFRUs(int file, int first, int last, int bus,
399 std::shared_ptr<DeviceMap> devices, const bool& powerIsOn,
400 sdbusplus::asio::object_server& objServer)
401 {
402 std::future<int> future = std::async(std::launch::async, [&]() {
403 // NOTE: When reading the devices raw on the bus, it can interfere with
404 // the driver's ability to operate, therefore read eeproms first before
405 // scanning for devices without drivers. Several experiments were run
406 // and it was determined that if there were any devices on the bus
407 // before the eeprom was hit and read, the eeprom driver wouldn't open
408 // while the bus device was open. An experiment was not performed to see
409 // if this issue was resolved if the i2c bus device was closed, but
410 // hexdumps of the eeprom later were successful.
411
412 // Scan for i2c eeproms loaded on this bus.
413 std::set<size_t> skipList = findI2CEeproms(bus, devices);
414 std::set<size_t>& failedItems = failedAddresses[bus];
415 std::set<size_t>& foundItems = fruAddresses[bus];
416 foundItems.clear();
417
418 auto busFind = busBlocklist.find(bus);
419 if (busFind != busBlocklist.end())
420 {
421 if (busFind->second != std::nullopt)
422 {
423 for (const auto& address : *(busFind->second))
424 {
425 skipList.insert(address);
426 }
427 }
428 }
429
430 std::set<size_t>* rootFailures = nullptr;
431 int rootBus = getRootBus(bus);
432
433 if (rootBus >= 0)
434 {
435 auto rootBusFind = busBlocklist.find(rootBus);
436 if (rootBusFind != busBlocklist.end())
437 {
438 if (rootBusFind->second != std::nullopt)
439 {
440 for (const auto& rootAddress : *(rootBusFind->second))
441 {
442 skipList.insert(rootAddress);
443 }
444 }
445 }
446 rootFailures = &(failedAddresses[rootBus]);
447 foundItems = fruAddresses[rootBus];
448 }
449
450 constexpr int startSkipTargetAddr = 0;
451 constexpr int endSkipTargetAddr = 12;
452
453 for (int ii = first; ii <= last; ii++)
454 {
455 if (foundItems.find(ii) != foundItems.end())
456 {
457 continue;
458 }
459 if (skipList.find(ii) != skipList.end())
460 {
461 continue;
462 }
463 // skipping since no device is present in this range
464 if (ii >= startSkipTargetAddr && ii <= endSkipTargetAddr)
465 {
466 continue;
467 }
468 // Set target address
469 if (ioctl(file, I2C_SLAVE, ii) < 0)
470 {
471 std::cerr << "device at bus " << bus << " address " << ii
472 << " busy\n";
473 continue;
474 }
475 // probe
476 if (i2c_smbus_read_byte(file) < 0)
477 {
478 continue;
479 }
480
481 if (debug)
482 {
483 std::cout << "something at bus " << bus << " addr " << ii
484 << "\n";
485 }
486
487 makeProbeInterface(bus, ii, objServer);
488
489 if (failedItems.find(ii) != failedItems.end())
490 {
491 // if we failed to read it once, unlikely we can read it later
492 continue;
493 }
494
495 if (rootFailures != nullptr)
496 {
497 if (rootFailures->find(ii) != rootFailures->end())
498 {
499 continue;
500 }
501 }
502
503 /* Check for Device type if it is 8 bit or 16 bit */
504 std::optional<bool> is16Bit = isDevice16Bit(file);
505 if (!is16Bit.has_value())
506 {
507 std::cerr << "failed to read bus " << bus << " address " << ii
508 << "\n";
509 if (powerIsOn)
510 {
511 failedItems.insert(ii);
512 }
513 continue;
514 }
515 bool is16BitBool{*is16Bit};
516
517 auto readFunc = [is16BitBool, file, ii](off_t offset, size_t length,
518 uint8_t* outbuf) {
519 return readData(is16BitBool, false, file, ii, offset, length,
520 outbuf);
521 };
522 FRUReader reader(std::move(readFunc));
523 std::string errorMessage = "bus " + std::to_string(bus) +
524 " address " + std::to_string(ii);
525 std::pair<std::vector<uint8_t>, bool> pair =
526 readFRUContents(reader, errorMessage);
527 const bool foundHeader = pair.second;
528
529 if (!foundHeader && !is16BitBool)
530 {
531 // certain FRU eeproms require bytewise reading.
532 // otherwise garbage is read. e.g. SuperMicro PWS 920P-SQ
533
534 auto readFunc = [is16BitBool, file, ii](off_t offset,
535 size_t length,
536 uint8_t* outbuf) {
537 return readData(is16BitBool, true, file, ii, offset, length,
538 outbuf);
539 };
540 FRUReader readerBytewise(std::move(readFunc));
541 pair = readFRUContents(readerBytewise, errorMessage);
542 }
543
544 if (pair.first.empty())
545 {
546 continue;
547 }
548
549 devices->emplace(ii, pair.first);
550 fruAddresses[bus].insert(ii);
551 }
552 return 1;
553 });
554 std::future_status status =
555 future.wait_for(std::chrono::seconds(busTimeoutSeconds));
556 if (status == std::future_status::timeout)
557 {
558 std::cerr << "Error reading bus " << bus << "\n";
559 if (powerIsOn)
560 {
561 busBlocklist[bus] = std::nullopt;
562 }
563 close(file);
564 return -1;
565 }
566
567 close(file);
568 return future.get();
569 }
570
loadBlocklist(const char * path)571 void loadBlocklist(const char* path)
572 {
573 std::ifstream blocklistStream(path);
574 if (!blocklistStream.good())
575 {
576 // File is optional.
577 std::cerr << "Cannot open blocklist file.\n\n";
578 return;
579 }
580
581 nlohmann::json data = nlohmann::json::parse(blocklistStream, nullptr,
582 false);
583 if (data.is_discarded())
584 {
585 std::cerr << "Illegal blocklist file detected, cannot validate JSON, "
586 "exiting\n";
587 std::exit(EXIT_FAILURE);
588 }
589
590 // It's expected to have at least one field, "buses" that is an array of the
591 // buses by integer. Allow for future options to exclude further aspects,
592 // such as specific addresses or ranges.
593 if (data.type() != nlohmann::json::value_t::object)
594 {
595 std::cerr << "Illegal blocklist, expected to read dictionary\n";
596 std::exit(EXIT_FAILURE);
597 }
598
599 // If buses field is missing, that's fine.
600 if (data.count("buses") == 1)
601 {
602 // Parse the buses array after a little validation.
603 auto buses = data.at("buses");
604 if (buses.type() != nlohmann::json::value_t::array)
605 {
606 // Buses field present but invalid, therefore this is an error.
607 std::cerr << "Invalid contents for blocklist buses field\n";
608 std::exit(EXIT_FAILURE);
609 }
610
611 // Catch exception here for type mis-match.
612 try
613 {
614 for (const auto& busIterator : buses)
615 {
616 // If bus and addresses field are missing, that's fine.
617 if (busIterator.contains("bus") &&
618 busIterator.contains("addresses"))
619 {
620 auto busData = busIterator.at("bus");
621 auto bus = busData.get<size_t>();
622
623 auto addressData = busIterator.at("addresses");
624 auto addresses =
625 addressData.get<std::set<std::string_view>>();
626
627 auto& block = busBlocklist[bus].emplace();
628 for (const auto& address : addresses)
629 {
630 size_t addressInt = 0;
631 std::from_chars(address.begin() + 2, address.end(),
632 addressInt, 16);
633 block.insert(addressInt);
634 }
635 }
636 else
637 {
638 busBlocklist[busIterator.get<size_t>()] = std::nullopt;
639 }
640 }
641 }
642 catch (const nlohmann::detail::type_error& e)
643 {
644 // Type mis-match is a critical error.
645 std::cerr << "Invalid bus type: " << e.what() << "\n";
646 std::exit(EXIT_FAILURE);
647 }
648 }
649 }
650
findI2CDevices(const std::vector<fs::path> & i2cBuses,BusMap & busmap,const bool & powerIsOn,sdbusplus::asio::object_server & objServer)651 static void findI2CDevices(const std::vector<fs::path>& i2cBuses,
652 BusMap& busmap, const bool& powerIsOn,
653 sdbusplus::asio::object_server& objServer)
654 {
655 for (const auto& i2cBus : i2cBuses)
656 {
657 int bus = busStrToInt(i2cBus.string());
658
659 if (bus < 0)
660 {
661 std::cerr << "Cannot translate " << i2cBus << " to int\n";
662 continue;
663 }
664 auto busFind = busBlocklist.find(bus);
665 if (busFind != busBlocklist.end())
666 {
667 if (busFind->second == std::nullopt)
668 {
669 continue; // Skip blocked busses.
670 }
671 }
672 int rootBus = getRootBus(bus);
673 auto rootBusFind = busBlocklist.find(rootBus);
674 if (rootBusFind != busBlocklist.end())
675 {
676 if (rootBusFind->second == std::nullopt)
677 {
678 continue;
679 }
680 }
681
682 auto file = open(i2cBus.c_str(), O_RDWR);
683 if (file < 0)
684 {
685 std::cerr << "unable to open i2c device " << i2cBus.string()
686 << "\n";
687 continue;
688 }
689 unsigned long funcs = 0;
690
691 if (ioctl(file, I2C_FUNCS, &funcs) < 0)
692 {
693 std::cerr
694 << "Error: Could not get the adapter functionality matrix bus "
695 << bus << "\n";
696 close(file);
697 continue;
698 }
699 if (((funcs & I2C_FUNC_SMBUS_READ_BYTE) == 0U) ||
700 ((I2C_FUNC_SMBUS_READ_I2C_BLOCK) == 0))
701 {
702 std::cerr << "Error: Can't use SMBus Receive Byte command bus "
703 << bus << "\n";
704 continue;
705 }
706 auto& device = busmap[bus];
707 device = std::make_shared<DeviceMap>();
708
709 // i2cdetect by default uses the range 0x03 to 0x77, as
710 // this is what we have tested with, use this range. Could be
711 // changed in future.
712 if (debug)
713 {
714 std::cerr << "Scanning bus " << bus << "\n";
715 }
716
717 // fd is closed in this function in case the bus locks up
718 getBusFRUs(file, 0x03, 0x77, bus, device, powerIsOn, objServer);
719
720 if (debug)
721 {
722 std::cerr << "Done scanning bus " << bus << "\n";
723 }
724 }
725 }
726
727 // this class allows an async response after all i2c devices are discovered
728 struct FindDevicesWithCallback :
729 std::enable_shared_from_this<FindDevicesWithCallback>
730 {
FindDevicesWithCallbackFindDevicesWithCallback731 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
732 BusMap& busmap, const bool& powerIsOn,
733 sdbusplus::asio::object_server& objServer,
734 std::function<void()>&& callback) :
735 _i2cBuses(i2cBuses),
736 _busMap(busmap), _powerIsOn(powerIsOn), _objServer(objServer),
737 _callback(std::move(callback))
738 {}
~FindDevicesWithCallbackFindDevicesWithCallback739 ~FindDevicesWithCallback()
740 {
741 _callback();
742 }
runFindDevicesWithCallback743 void run()
744 {
745 findI2CDevices(_i2cBuses, _busMap, _powerIsOn, _objServer);
746 }
747
748 const std::vector<fs::path>& _i2cBuses;
749 BusMap& _busMap;
750 const bool& _powerIsOn;
751 sdbusplus::asio::object_server& _objServer;
752 std::function<void()> _callback;
753 };
754
addFruObjectToDbus(std::vector<uint8_t> & device,boost::container::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,uint32_t bus,uint32_t address,size_t & unknownBusObjectCount,const bool & powerIsOn,sdbusplus::asio::object_server & objServer,std::shared_ptr<sdbusplus::asio::connection> & systemBus)755 void addFruObjectToDbus(
756 std::vector<uint8_t>& device,
757 boost::container::flat_map<
758 std::pair<size_t, size_t>,
759 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
760 uint32_t bus, uint32_t address, size_t& unknownBusObjectCount,
761 const bool& powerIsOn, sdbusplus::asio::object_server& objServer,
762 std::shared_ptr<sdbusplus::asio::connection>& systemBus)
763 {
764 boost::container::flat_map<std::string, std::string> formattedFRU;
765
766 std::optional<std::string> optionalProductName = getProductName(
767 device, formattedFRU, bus, address, unknownBusObjectCount);
768 if (!optionalProductName)
769 {
770 std::cerr << "getProductName failed. product name is empty.\n";
771 return;
772 }
773
774 std::string productName = "/xyz/openbmc_project/FruDevice/" +
775 optionalProductName.value();
776
777 std::optional<int> index = findIndexForFRU(dbusInterfaceMap, productName);
778 if (index.has_value())
779 {
780 productName += "_";
781 productName += std::to_string(++(*index));
782 }
783
784 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
785 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
786 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
787
788 for (auto& property : formattedFRU)
789 {
790 std::regex_replace(property.second.begin(), property.second.begin(),
791 property.second.end(), nonAsciiRegex, "_");
792 if (property.second.empty() && property.first != "PRODUCT_ASSET_TAG")
793 {
794 continue;
795 }
796 std::string key = std::regex_replace(property.first, nonAsciiRegex,
797 "_");
798
799 if (property.first == "PRODUCT_ASSET_TAG")
800 {
801 std::string propertyName = property.first;
802 iface->register_property(
803 key, property.second + '\0',
804 [bus, address, propertyName, &dbusInterfaceMap,
805 &unknownBusObjectCount, &powerIsOn, &objServer,
806 &systemBus](const std::string& req, std::string& resp) {
807 if (strcmp(req.c_str(), resp.c_str()) != 0)
808 {
809 // call the method which will update
810 if (updateFRUProperty(req, bus, address, propertyName,
811 dbusInterfaceMap,
812 unknownBusObjectCount, powerIsOn,
813 objServer, systemBus))
814 {
815 resp = req;
816 }
817 else
818 {
819 throw std::invalid_argument(
820 "FRU property update failed.");
821 }
822 }
823 return 1;
824 });
825 }
826 else if (!iface->register_property(key, property.second + '\0'))
827 {
828 std::cerr << "illegal key: " << key << "\n";
829 }
830 if (debug)
831 {
832 std::cout << property.first << ": " << property.second << "\n";
833 }
834 }
835
836 // baseboard will be 0, 0
837 iface->register_property("BUS", bus);
838 iface->register_property("ADDRESS", address);
839
840 iface->initialize();
841 }
842
readBaseboardFRU(std::vector<uint8_t> & baseboardFRU)843 static bool readBaseboardFRU(std::vector<uint8_t>& baseboardFRU)
844 {
845 // try to read baseboard fru from file
846 std::ifstream baseboardFRUFile(baseboardFruLocation, std::ios::binary);
847 if (baseboardFRUFile.good())
848 {
849 baseboardFRUFile.seekg(0, std::ios_base::end);
850 size_t fileSize = static_cast<size_t>(baseboardFRUFile.tellg());
851 baseboardFRU.resize(fileSize);
852 baseboardFRUFile.seekg(0, std::ios_base::beg);
853 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
854 char* charOffset = reinterpret_cast<char*>(baseboardFRU.data());
855 baseboardFRUFile.read(charOffset, fileSize);
856 }
857 else
858 {
859 return false;
860 }
861 return true;
862 }
863
writeFRU(uint8_t bus,uint8_t address,const std::vector<uint8_t> & fru)864 bool writeFRU(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
865 {
866 boost::container::flat_map<std::string, std::string> tmp;
867 if (fru.size() > maxFruSize)
868 {
869 std::cerr << "Invalid fru.size() during writeFRU\n";
870 return false;
871 }
872 // verify legal fru by running it through fru parsing logic
873 if (formatIPMIFRU(fru, tmp) != resCodes::resOK)
874 {
875 std::cerr << "Invalid fru format during writeFRU\n";
876 return false;
877 }
878 // baseboard fru
879 if (bus == 0 && address == 0)
880 {
881 std::ofstream file(baseboardFruLocation, std::ios_base::binary);
882 if (!file.good())
883 {
884 std::cerr << "Error opening file " << baseboardFruLocation << "\n";
885 throw DBusInternalError();
886 return false;
887 }
888 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
889 const char* charOffset = reinterpret_cast<const char*>(fru.data());
890 file.write(charOffset, fru.size());
891 return file.good();
892 }
893
894 if (hasEepromFile(bus, address))
895 {
896 auto path = getEepromPath(bus, address);
897 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
898 if (eeprom < 0)
899 {
900 std::cerr << "unable to open i2c device " << path << "\n";
901 throw DBusInternalError();
902 return false;
903 }
904
905 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
906 if (writtenBytes < 0)
907 {
908 std::cerr << "unable to write to i2c device " << path << "\n";
909 close(eeprom);
910 throw DBusInternalError();
911 return false;
912 }
913
914 close(eeprom);
915 return true;
916 }
917
918 std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
919
920 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
921 if (file < 0)
922 {
923 std::cerr << "unable to open i2c device " << i2cBus << "\n";
924 throw DBusInternalError();
925 return false;
926 }
927 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
928 {
929 std::cerr << "unable to set device address\n";
930 close(file);
931 throw DBusInternalError();
932 return false;
933 }
934
935 constexpr const size_t retryMax = 2;
936 uint16_t index = 0;
937 size_t retries = retryMax;
938 while (index < fru.size())
939 {
940 if (((index != 0U) && ((index % (maxEepromPageIndex + 1)) == 0)) &&
941 (retries == retryMax))
942 {
943 // The 4K EEPROM only uses the A2 and A1 device address bits
944 // with the third bit being a memory page address bit.
945 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
946 {
947 std::cerr << "unable to set device address\n";
948 close(file);
949 throw DBusInternalError();
950 return false;
951 }
952 }
953
954 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
955 fru[index]) < 0)
956 {
957 if ((retries--) == 0U)
958 {
959 std::cerr << "error writing fru: " << strerror(errno) << "\n";
960 close(file);
961 throw DBusInternalError();
962 return false;
963 }
964 }
965 else
966 {
967 retries = retryMax;
968 index++;
969 }
970 // most eeproms require 5-10ms between writes
971 std::this_thread::sleep_for(std::chrono::milliseconds(10));
972 }
973 close(file);
974 return true;
975 }
976
rescanOneBus(BusMap & busmap,uint16_t busNum,boost::container::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,bool dbusCall,size_t & unknownBusObjectCount,const bool & powerIsOn,sdbusplus::asio::object_server & objServer,std::shared_ptr<sdbusplus::asio::connection> & systemBus)977 void rescanOneBus(
978 BusMap& busmap, uint16_t busNum,
979 boost::container::flat_map<
980 std::pair<size_t, size_t>,
981 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
982 bool dbusCall, size_t& unknownBusObjectCount, const bool& powerIsOn,
983 sdbusplus::asio::object_server& objServer,
984 std::shared_ptr<sdbusplus::asio::connection>& systemBus)
985 {
986 for (auto device = foundDevices.begin(); device != foundDevices.end();)
987 {
988 if (device->first.first == static_cast<size_t>(busNum))
989 {
990 objServer.remove_interface(device->second);
991 device = foundDevices.erase(device);
992 }
993 else
994 {
995 device++;
996 }
997 }
998
999 fs::path busPath = fs::path("/dev/i2c-" + std::to_string(busNum));
1000 if (!fs::exists(busPath))
1001 {
1002 if (dbusCall)
1003 {
1004 std::cerr << "Unable to access i2c bus " << static_cast<int>(busNum)
1005 << "\n";
1006 throw std::invalid_argument("Invalid Bus.");
1007 }
1008 return;
1009 }
1010
1011 std::vector<fs::path> i2cBuses;
1012 i2cBuses.emplace_back(busPath);
1013
1014 auto scan = std::make_shared<FindDevicesWithCallback>(
1015 i2cBuses, busmap, powerIsOn, objServer,
1016 [busNum, &busmap, &dbusInterfaceMap, &unknownBusObjectCount, &powerIsOn,
1017 &objServer, &systemBus]() {
1018 for (auto busIface = dbusInterfaceMap.begin();
1019 busIface != dbusInterfaceMap.end();)
1020 {
1021 if (busIface->first.first == static_cast<size_t>(busNum))
1022 {
1023 objServer.remove_interface(busIface->second);
1024 busIface = dbusInterfaceMap.erase(busIface);
1025 }
1026 else
1027 {
1028 busIface++;
1029 }
1030 }
1031 auto found = busmap.find(busNum);
1032 if (found == busmap.end() || found->second == nullptr)
1033 {
1034 return;
1035 }
1036 for (auto& device : *(found->second))
1037 {
1038 addFruObjectToDbus(device.second, dbusInterfaceMap,
1039 static_cast<uint32_t>(busNum), device.first,
1040 unknownBusObjectCount, powerIsOn, objServer,
1041 systemBus);
1042 }
1043 });
1044 scan->run();
1045 }
1046
rescanBusses(BusMap & busmap,boost::container::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,size_t & unknownBusObjectCount,const bool & powerIsOn,sdbusplus::asio::object_server & objServer,std::shared_ptr<sdbusplus::asio::connection> & systemBus)1047 void rescanBusses(
1048 BusMap& busmap,
1049 boost::container::flat_map<
1050 std::pair<size_t, size_t>,
1051 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1052 size_t& unknownBusObjectCount, const bool& powerIsOn,
1053 sdbusplus::asio::object_server& objServer,
1054 std::shared_ptr<sdbusplus::asio::connection>& systemBus)
1055 {
1056 static boost::asio::steady_timer timer(io);
1057 timer.expires_from_now(std::chrono::seconds(1));
1058
1059 // setup an async wait in case we get flooded with requests
1060 timer.async_wait([&](const boost::system::error_code& ec) {
1061 if (ec == boost::asio::error::operation_aborted)
1062 {
1063 return;
1064 }
1065
1066 if (ec)
1067 {
1068 std::cerr << "Error in timer: " << ec.message() << "\n";
1069 return;
1070 }
1071
1072 auto devDir = fs::path("/dev/");
1073 std::vector<fs::path> i2cBuses;
1074
1075 boost::container::flat_map<size_t, fs::path> busPaths;
1076 if (!getI2cDevicePaths(devDir, busPaths))
1077 {
1078 std::cerr << "unable to find i2c devices\n";
1079 return;
1080 }
1081
1082 for (const auto& busPath : busPaths)
1083 {
1084 i2cBuses.emplace_back(busPath.second);
1085 }
1086
1087 busmap.clear();
1088 for (auto& [pair, interface] : foundDevices)
1089 {
1090 objServer.remove_interface(interface);
1091 }
1092 foundDevices.clear();
1093
1094 auto scan = std::make_shared<FindDevicesWithCallback>(
1095 i2cBuses, busmap, powerIsOn, objServer, [&]() {
1096 for (auto& busIface : dbusInterfaceMap)
1097 {
1098 objServer.remove_interface(busIface.second);
1099 }
1100
1101 dbusInterfaceMap.clear();
1102 unknownBusObjectCount = 0;
1103
1104 // todo, get this from a more sensable place
1105 std::vector<uint8_t> baseboardFRU;
1106 if (readBaseboardFRU(baseboardFRU))
1107 {
1108 // If no device on i2c bus 0, the insertion will happen.
1109 auto bus0 = busmap.try_emplace(0,
1110 std::make_shared<DeviceMap>());
1111 bus0.first->second->emplace(0, baseboardFRU);
1112 }
1113 for (auto& devicemap : busmap)
1114 {
1115 for (auto& device : *devicemap.second)
1116 {
1117 addFruObjectToDbus(device.second, dbusInterfaceMap,
1118 devicemap.first, device.first,
1119 unknownBusObjectCount, powerIsOn,
1120 objServer, systemBus);
1121 }
1122 }
1123 });
1124 scan->run();
1125 });
1126 }
1127
1128 // Details with example of Asset Tag Update
1129 // To find location of Product Info Area asset tag as per FRU specification
1130 // 1. Find product Info area starting offset (*8 - as header will be in
1131 // multiple of 8 bytes).
1132 // 2. Skip 3 bytes of product info area (like format version, area length,
1133 // and language code).
1134 // 3. Traverse manufacturer name, product name, product version, & product
1135 // serial number, by reading type/length code to reach the Asset Tag.
1136 // 4. Update the Asset Tag, reposition the product Info area in multiple of
1137 // 8 bytes. Update the Product area length and checksum.
1138
updateFRUProperty(const std::string & updatePropertyReq,uint32_t bus,uint32_t address,const std::string & propertyName,boost::container::flat_map<std::pair<size_t,size_t>,std::shared_ptr<sdbusplus::asio::dbus_interface>> & dbusInterfaceMap,size_t & unknownBusObjectCount,const bool & powerIsOn,sdbusplus::asio::object_server & objServer,std::shared_ptr<sdbusplus::asio::connection> & systemBus)1139 bool updateFRUProperty(
1140 const std::string& updatePropertyReq, uint32_t bus, uint32_t address,
1141 const std::string& propertyName,
1142 boost::container::flat_map<
1143 std::pair<size_t, size_t>,
1144 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1145 size_t& unknownBusObjectCount, const bool& powerIsOn,
1146 sdbusplus::asio::object_server& objServer,
1147 std::shared_ptr<sdbusplus::asio::connection>& systemBus)
1148 {
1149 size_t updatePropertyReqLen = updatePropertyReq.length();
1150 if (updatePropertyReqLen == 1 || updatePropertyReqLen > 63)
1151 {
1152 std::cerr
1153 << "FRU field data cannot be of 1 char or more than 63 chars. "
1154 "Invalid Length "
1155 << updatePropertyReqLen << "\n";
1156 return false;
1157 }
1158
1159 std::vector<uint8_t> fruData;
1160
1161 if (!getFruData(fruData, bus, address))
1162 {
1163 std::cerr << "Failure getting FRU Data \n";
1164 return false;
1165 }
1166
1167 struct FruArea fruAreaParams
1168 {};
1169
1170 if (!findFruAreaLocationAndField(fruData, propertyName, fruAreaParams))
1171 {
1172 std::cerr << "findFruAreaLocationAndField failed \n";
1173 return false;
1174 }
1175
1176 std::vector<uint8_t> restFRUAreaFieldsData;
1177 if (!copyRestFRUArea(fruData, propertyName, fruAreaParams,
1178 restFRUAreaFieldsData))
1179 {
1180 std::cerr << "copyRestFRUArea failed \n";
1181 return false;
1182 }
1183
1184 // Push post update fru areas if any
1185 unsigned int nextFRUAreaLoc = 0;
1186 for (fruAreas nextFRUArea = fruAreas::fruAreaInternal;
1187 nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea)
1188 {
1189 unsigned int fruAreaLoc =
1190 fruData[getHeaderAreaFieldOffset(nextFRUArea)] * fruBlockSize;
1191 if ((fruAreaLoc > fruAreaParams.restFieldsEnd) &&
1192 ((nextFRUAreaLoc == 0) || (fruAreaLoc < nextFRUAreaLoc)))
1193 {
1194 nextFRUAreaLoc = fruAreaLoc;
1195 }
1196 }
1197 std::vector<uint8_t> restFRUAreasData;
1198 if (nextFRUAreaLoc != 0U)
1199 {
1200 std::copy_n(fruData.begin() + nextFRUAreaLoc,
1201 fruData.size() - nextFRUAreaLoc,
1202 std::back_inserter(restFRUAreasData));
1203 }
1204
1205 // check FRU area size
1206 size_t fruAreaDataSize =
1207 ((fruAreaParams.updateFieldLoc - fruAreaParams.start + 1) +
1208 restFRUAreaFieldsData.size());
1209 size_t fruAreaAvailableSize = fruAreaParams.size - fruAreaDataSize;
1210 if ((updatePropertyReqLen + 1) > fruAreaAvailableSize)
1211 {
1212 #ifdef ENABLE_FRU_AREA_RESIZE
1213 size_t newFRUAreaSize = fruAreaDataSize + updatePropertyReqLen + 1;
1214 // round size to 8-byte blocks
1215 newFRUAreaSize = ((newFRUAreaSize - 1) / fruBlockSize + 1) *
1216 fruBlockSize;
1217 size_t newFRUDataSize = fruData.size() + newFRUAreaSize -
1218 fruAreaParams.size;
1219 fruData.resize(newFRUDataSize);
1220 fruAreaParams.size = newFRUAreaSize;
1221 fruAreaParams.end = fruAreaParams.start + fruAreaParams.size;
1222 #else
1223 std::cerr << "FRU field length: " << updatePropertyReqLen + 1
1224 << " should not be greater than available FRU area size: "
1225 << fruAreaAvailableSize << "\n";
1226 return false;
1227 #endif // ENABLE_FRU_AREA_RESIZE
1228 }
1229
1230 // write new requested property field length and data
1231 constexpr uint8_t newTypeLenMask = 0xC0;
1232 fruData[fruAreaParams.updateFieldLoc] =
1233 static_cast<uint8_t>(updatePropertyReqLen | newTypeLenMask);
1234 fruAreaParams.updateFieldLoc++;
1235 std::copy(updatePropertyReq.begin(), updatePropertyReq.end(),
1236 fruData.begin() + fruAreaParams.updateFieldLoc);
1237
1238 // Copy remaining data to main fru area - post updated fru field vector
1239 fruAreaParams.restFieldsLoc = fruAreaParams.updateFieldLoc +
1240 updatePropertyReqLen;
1241 size_t fruAreaDataEnd = fruAreaParams.restFieldsLoc +
1242 restFRUAreaFieldsData.size();
1243
1244 std::copy(restFRUAreaFieldsData.begin(), restFRUAreaFieldsData.end(),
1245 fruData.begin() + fruAreaParams.restFieldsLoc);
1246
1247 // Update final fru with new fru area length and checksum
1248 unsigned int nextFRUAreaNewLoc = updateFRUAreaLenAndChecksum(
1249 fruData, fruAreaParams.start, fruAreaDataEnd, fruAreaParams.end);
1250
1251 #ifdef ENABLE_FRU_AREA_RESIZE
1252 ++nextFRUAreaNewLoc;
1253 ssize_t nextFRUAreaOffsetDiff = (nextFRUAreaNewLoc - nextFRUAreaLoc) /
1254 fruBlockSize;
1255 // Append rest FRU Areas if size changed and there were other sections after
1256 // updated one
1257 if (nextFRUAreaOffsetDiff && nextFRUAreaLoc)
1258 {
1259 std::copy(restFRUAreasData.begin(), restFRUAreasData.end(),
1260 fruData.begin() + nextFRUAreaNewLoc);
1261 // Update Common Header
1262 for (fruAreas nextFRUArea = fruAreas::fruAreaInternal;
1263 nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea)
1264 {
1265 unsigned int fruAreaOffsetField =
1266 getHeaderAreaFieldOffset(nextFRUArea);
1267 size_t curFRUAreaOffset = fruData[fruAreaOffsetField];
1268 if (curFRUAreaOffset > fruAreaParams.end)
1269 {
1270 fruData[fruAreaOffsetField] = static_cast<int8_t>(
1271 curFRUAreaOffset + nextFRUAreaOffsetDiff);
1272 }
1273 }
1274 // Calculate new checksum
1275 std::vector<uint8_t> headerFRUData;
1276 std::copy_n(fruData.begin(), 7, std::back_inserter(headerFRUData));
1277 size_t checksumVal = calculateChecksum(headerFRUData);
1278 fruData[7] = static_cast<uint8_t>(checksumVal);
1279 // fill zeros if FRU Area size decreased
1280 if (nextFRUAreaOffsetDiff < 0)
1281 {
1282 std::fill(fruData.begin() + nextFRUAreaNewLoc +
1283 restFRUAreasData.size(),
1284 fruData.end(), 0);
1285 }
1286 }
1287 #else
1288 // this is to avoid "unused variable" warning
1289 (void)nextFRUAreaNewLoc;
1290 #endif // ENABLE_FRU_AREA_RESIZE
1291 if (fruData.empty())
1292 {
1293 return false;
1294 }
1295
1296 if (!writeFRU(static_cast<uint8_t>(bus), static_cast<uint8_t>(address),
1297 fruData))
1298 {
1299 return false;
1300 }
1301
1302 // Rescan the bus so that GetRawFru dbus-call fetches updated values
1303 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1304 objServer, systemBus);
1305 return true;
1306 }
1307
main()1308 int main()
1309 {
1310 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1311 sdbusplus::asio::object_server objServer(systemBus);
1312
1313 static size_t unknownBusObjectCount = 0;
1314 static bool powerIsOn = false;
1315 auto devDir = fs::path("/dev/");
1316 auto matchString = std::string(R"(i2c-\d+$)");
1317 std::vector<fs::path> i2cBuses;
1318
1319 if (!findFiles(devDir, matchString, i2cBuses))
1320 {
1321 std::cerr << "unable to find i2c devices\n";
1322 return 1;
1323 }
1324
1325 // check for and load blocklist with initial buses.
1326 loadBlocklist(blocklistPath);
1327
1328 systemBus->request_name("xyz.openbmc_project.FruDevice");
1329
1330 // this is a map with keys of pair(bus number, address) and values of
1331 // the object on dbus
1332 boost::container::flat_map<std::pair<size_t, size_t>,
1333 std::shared_ptr<sdbusplus::asio::dbus_interface>>
1334 dbusInterfaceMap;
1335
1336 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1337 objServer.add_interface("/xyz/openbmc_project/FruDevice",
1338 "xyz.openbmc_project.FruDeviceManager");
1339
1340 iface->register_method("ReScan", [&]() {
1341 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1342 objServer, systemBus);
1343 });
1344
1345 iface->register_method("ReScanBus", [&](uint16_t bus) {
1346 rescanOneBus(busMap, bus, dbusInterfaceMap, true, unknownBusObjectCount,
1347 powerIsOn, objServer, systemBus);
1348 });
1349
1350 iface->register_method("GetRawFru", getFRUInfo);
1351
1352 iface->register_method("WriteFru",
1353 [&](const uint16_t bus, const uint8_t address,
1354 const std::vector<uint8_t>& data) {
1355 if (!writeFRU(bus, address, data))
1356 {
1357 throw std::invalid_argument("Invalid Arguments.");
1358 return;
1359 }
1360 // schedule rescan on success
1361 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1362 objServer, systemBus);
1363 });
1364 iface->initialize();
1365
1366 std::function<void(sdbusplus::message_t & message)> eventHandler =
1367 [&](sdbusplus::message_t& message) {
1368 std::string objectName;
1369 boost::container::flat_map<
1370 std::string,
1371 std::variant<std::string, bool, int64_t, uint64_t, double>>
1372 values;
1373 message.read(objectName, values);
1374 auto findState = values.find("CurrentHostState");
1375 if (findState != values.end())
1376 {
1377 if (std::get<std::string>(findState->second) ==
1378 "xyz.openbmc_project.State.Host.HostState.Running")
1379 {
1380 powerIsOn = true;
1381 }
1382 }
1383
1384 if (powerIsOn)
1385 {
1386 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount,
1387 powerIsOn, objServer, systemBus);
1388 }
1389 };
1390
1391 sdbusplus::bus::match_t powerMatch = sdbusplus::bus::match_t(
1392 static_cast<sdbusplus::bus_t&>(*systemBus),
1393 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
1394 "openbmc_project/state/"
1395 "host0',arg0='xyz.openbmc_project.State.Host'",
1396 eventHandler);
1397
1398 int fd = inotify_init();
1399 inotify_add_watch(fd, i2CDevLocation, IN_CREATE | IN_MOVED_TO | IN_DELETE);
1400 std::array<char, 4096> readBuffer{};
1401 // monitor for new i2c devices
1402 boost::asio::posix::stream_descriptor dirWatch(io, fd);
1403 std::function<void(const boost::system::error_code, std::size_t)>
1404 watchI2cBusses = [&](const boost::system::error_code& ec,
1405 std::size_t bytesTransferred) {
1406 if (ec)
1407 {
1408 std::cout << "Callback Error " << ec << "\n";
1409 return;
1410 }
1411 size_t index = 0;
1412 while ((index + sizeof(inotify_event)) <= bytesTransferred)
1413 {
1414 const char* p = &readBuffer[index];
1415 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
1416 const auto* iEvent = reinterpret_cast<const inotify_event*>(p);
1417 switch (iEvent->mask)
1418 {
1419 case IN_CREATE:
1420 case IN_MOVED_TO:
1421 case IN_DELETE:
1422 {
1423 std::string_view name(&iEvent->name[0], iEvent->len);
1424 if (boost::starts_with(name, "i2c"))
1425 {
1426 int bus = busStrToInt(name);
1427 if (bus < 0)
1428 {
1429 std::cerr << "Could not parse bus " << name << "\n";
1430 continue;
1431 }
1432 int rootBus = getRootBus(bus);
1433 if (rootBus >= 0)
1434 {
1435 rescanOneBus(busMap, static_cast<uint16_t>(rootBus),
1436 dbusInterfaceMap, false,
1437 unknownBusObjectCount, powerIsOn,
1438 objServer, systemBus);
1439 }
1440 rescanOneBus(busMap, static_cast<uint16_t>(bus),
1441 dbusInterfaceMap, false,
1442 unknownBusObjectCount, powerIsOn,
1443 objServer, systemBus);
1444 }
1445 }
1446 break;
1447 default:
1448 break;
1449 }
1450 index += sizeof(inotify_event) + iEvent->len;
1451 }
1452
1453 dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1454 watchI2cBusses);
1455 };
1456
1457 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
1458 // run the initial scan
1459 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1460 objServer, systemBus);
1461
1462 io.run();
1463 return 0;
1464 }
1465