Move components from proving-ground
Move all needed components from proving ground to this
repo. Some clean up was done in json files to fix probes
as well as some slight modification to readme.
Change-Id: I05b7f6459704640c4850420a4573d157500d0aff
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
new file mode 100644
index 0000000..950644f
--- /dev/null
+++ b/src/EntityManager.cpp
@@ -0,0 +1,634 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include <Utils.hpp>
+#include <dbus/properties.hpp>
+#include <nlohmann/json.hpp>
+#include <fstream>
+#include <regex>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/variant/apply_visitor.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <VariantVisitors.hpp>
+
+constexpr const char *OUTPUT_DIR = "/var/configuration/";
+constexpr const char *CONFIGURATION_DIR = "/usr/share/configurations";
+constexpr const char *TEMPLATE_CHAR = "$";
+constexpr const size_t MAX_MAPPER_DEPTH = 99;
+
+namespace fs = std::experimental::filesystem;
+struct cmp_str
+{
+ bool operator()(const char *a, const char *b) const
+ {
+ return std::strcmp(a, b) < 0;
+ }
+};
+
+// underscore T for collison with dbus c api
+enum class probe_type_codes
+{
+ FALSE_T,
+ TRUE_T,
+ AND,
+ OR,
+ FOUND
+};
+const static boost::container::flat_map<const char *, probe_type_codes, cmp_str>
+ PROBE_TYPES{{{"FALSE", probe_type_codes::FALSE_T},
+ {"TRUE", probe_type_codes::TRUE_T},
+ {"AND", probe_type_codes::AND},
+ {"OR", probe_type_codes::OR},
+ {"FOUND", probe_type_codes::FOUND}}};
+
+using GetSubTreeType = std::vector<
+ std::pair<std::string,
+ std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+
+using ManagedObjectType = boost::container::flat_map<
+ dbus::object_path,
+ boost::container::flat_map<
+ std::string,
+ boost::container::flat_map<std::string, dbus::dbus_variant>>>;
+
+boost::container::flat_map<
+ std::string,
+ std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>>
+ DBUS_PROBE_OBJECTS;
+std::vector<std::string> PASSED_PROBES;
+
+// todo: pass this through nicer
+std::shared_ptr<dbus::connection> SYSTEM_BUS;
+
+// calls the mapper to find all exposed objects of an interface type
+// and creates a vector<flat_map> that contains all the key value pairs
+// getManagedObjects
+bool findDbusObjects(
+ std::shared_ptr<dbus::connection> connection,
+ std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
+ &interfaceDevices,
+ std::string interface)
+{
+ // find all connections in the mapper that expose a specific type
+ static const dbus::endpoint mapper("xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper",
+ "GetSubTree");
+ dbus::message getMap = dbus::message::new_call(mapper);
+ std::vector<std::string> objects = {interface};
+ if (!getMap.pack("", MAX_MAPPER_DEPTH, objects))
+ {
+ std::cerr << "Pack Failed GetSensorSubtree\n";
+ return false;
+ }
+ dbus::message getMapResp = connection->send(getMap);
+ GetSubTreeType interfaceSubtree;
+ if (!getMapResp.unpack(interfaceSubtree))
+ {
+ std::cerr << "Error communicating to mapper\n";
+ return false;
+ }
+ boost::container::flat_set<std::string> connections;
+ for (auto &object : interfaceSubtree)
+ {
+ for (auto &connPair : object.second)
+ {
+ connections.insert(connPair.first);
+ }
+ }
+ // iterate through the connections, adding creating individual device
+ // dictionaries
+ for (auto &conn : connections)
+ {
+ auto managedObj =
+ dbus::endpoint(conn, "/", "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+ dbus::message getManagedObj = dbus::message::new_call(managedObj);
+ dbus::message getManagedObjResp = connection->send(getManagedObj);
+ ManagedObjectType managedInterface;
+ if (!getManagedObjResp.unpack(managedInterface))
+ {
+ std::cerr << "error getting managed object for device " << conn
+ << "\n";
+ continue;
+ }
+ for (auto &interfaceManagedObj : managedInterface)
+ {
+ auto ifaceObjFind = interfaceManagedObj.second.find(interface);
+ if (ifaceObjFind != interfaceManagedObj.second.end())
+ {
+ interfaceDevices.emplace_back(ifaceObjFind->second);
+ }
+ }
+ }
+ return true;
+}
+
+// probes interface dictionary for a key with a value that matches a regex
+bool probeDbus(
+ const std::string &interface,
+ const std::map<std::string, nlohmann::json> &matches,
+ std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
+ &devices,
+ bool &foundProbe)
+{
+ auto &dbusObject = DBUS_PROBE_OBJECTS[interface];
+ if (dbusObject.empty())
+ {
+ if (!findDbusObjects(SYSTEM_BUS, dbusObject, interface))
+ {
+ std::cerr << "Found no dbus objects with interface "
+ << interface << "\n";
+ foundProbe = false;
+ return false;
+ }
+ }
+ foundProbe = true;
+
+ bool foundMatch = false;
+ for (auto &device : dbusObject)
+ {
+ bool deviceMatches = true;
+ for (auto &match : matches)
+ {
+ auto deviceValue = device.find(match.first);
+ if (deviceValue != device.end())
+ {
+ switch (match.second.type())
+ {
+ case nlohmann::json::value_t::string:
+ {
+ std::regex search(match.second.get<std::string>());
+ std::smatch match;
+
+ // convert value to string respresentation
+ std::string probeValue = boost::apply_visitor(
+ [](const auto &x) {
+ return boost::lexical_cast<std::string>(x);
+ },
+ deviceValue->second);
+ if (!std::regex_search(probeValue, match, search))
+ {
+ deviceMatches = false;
+ break;
+ }
+ break;
+ }
+ case nlohmann::json::value_t::boolean:
+ case nlohmann::json::value_t::number_unsigned:
+ {
+ unsigned int probeValue = boost::apply_visitor(
+ VariantToUnsignedIntVisitor(), deviceValue->second);
+
+ if (probeValue != match.second.get<unsigned int>())
+ {
+ deviceMatches = false;
+ }
+ break;
+ }
+ case nlohmann::json::value_t::number_integer:
+ {
+ int probeValue = boost::apply_visitor(VariantToIntVisitor(),
+ deviceValue->second);
+
+ if (probeValue != match.second.get<int>())
+ {
+ deviceMatches = false;
+ }
+ break;
+ }
+ case nlohmann::json::value_t::number_float:
+ {
+ float probeValue = boost::apply_visitor(
+ VariantToFloatVisitor(), deviceValue->second);
+
+ if (probeValue != match.second.get<float>())
+ {
+ deviceMatches = false;
+ }
+ break;
+ }
+ }
+ }
+ else
+ {
+ deviceMatches = false;
+ break;
+ }
+ }
+ if (deviceMatches)
+ {
+ devices.emplace_back(
+ boost::container::flat_map<std::string, dbus::dbus_variant>(
+ device));
+ foundMatch = true;
+ deviceMatches = false; // for next iteration
+ }
+ }
+ return foundMatch;
+}
+
+// default probe entry point, iterates a list looking for specific types to
+// call specific probe functions
+bool probe(
+ const std::vector<std::string> probeCommand,
+ std::vector<boost::container::flat_map<std::string, dbus::dbus_variant>>
+ &foundDevs)
+{
+ const static std::regex command(R"(\((.*)\))");
+ std::smatch match;
+ bool ret = false;
+ bool cur = true;
+ probe_type_codes lastCommand = probe_type_codes::FALSE_T;
+
+ for (auto &probe : probeCommand)
+ {
+ bool foundProbe = false;
+ boost::container::flat_map<const char *, probe_type_codes,
+ cmp_str>::const_iterator probeType;
+
+ for (probeType = PROBE_TYPES.begin(); probeType != PROBE_TYPES.end();
+ probeType++)
+ {
+ if (probe.find(probeType->first) != std::string::npos)
+ {
+ foundProbe = true;
+ break;
+ }
+ }
+ if (foundProbe)
+ {
+ switch (probeType->second)
+ {
+ case probe_type_codes::FALSE_T:
+ {
+ return false; // todo, actually evaluate?
+ break;
+ }
+ case probe_type_codes::TRUE_T:
+ {
+ return true; // todo, actually evaluate?
+ break;
+ }
+ /*case probe_type_codes::AND:
+ break;
+ case probe_type_codes::OR:
+ break;
+ // these are no-ops until the last command switch
+ */
+ case probe_type_codes::FOUND:
+ {
+ if (!std::regex_search(probe, match, command))
+ {
+ std::cerr << "found probe sytax error " << probe << "\n";
+ return false;
+ }
+ std::string commandStr = *(match.begin() + 1);
+ commandStr = boost::replace_all_copy(commandStr, "'", "");
+ cur = (std::find(PASSED_PROBES.begin(), PASSED_PROBES.end(),
+ commandStr) != PASSED_PROBES.end());
+ break;
+ }
+ }
+ }
+ // look on dbus for object
+ else
+ {
+ if (!std::regex_search(probe, match, command))
+ {
+ std::cerr << "dbus probe sytax error " << probe << "\n";
+ return false;
+ }
+ std::string commandStr = *(match.begin() + 1);
+ // convert single ticks and single slashes into legal json
+ commandStr = boost::replace_all_copy(commandStr, "'", R"(")");
+ commandStr = boost::replace_all_copy(commandStr, R"(\)", R"(\\)");
+ auto json = nlohmann::json::parse(commandStr, nullptr, false);
+ if (json.is_discarded())
+ {
+ std::cerr << "dbus command sytax error " << commandStr << "\n";
+ return false;
+ }
+ // we can match any (string, variant) property. (string, string)
+ // does a regex
+ std::map<std::string, nlohmann::json> dbusProbeMap =
+ json.get<std::map<std::string, nlohmann::json>>();
+ auto findStart = probe.find("(");
+ if (findStart == std::string::npos)
+ {
+ return false;
+ }
+ std::string probeInterface = probe.substr(0, findStart);
+ cur =
+ probeDbus(probeInterface, dbusProbeMap, foundDevs, foundProbe);
+ }
+
+ // some functions like AND and OR only take affect after the
+ // fact
+ switch (lastCommand)
+ {
+ case probe_type_codes::AND:
+ ret = cur && ret;
+ break;
+ case probe_type_codes::OR:
+ ret = cur || ret;
+ break;
+ default:
+ ret = cur;
+ break;
+ }
+ lastCommand = probeType != PROBE_TYPES.end()
+ ? probeType->second
+ : probe_type_codes::FALSE_T;
+
+ if (!foundProbe)
+ {
+ std::cerr << "Illegal probe type " << probe << "\n";
+ return false;
+ }
+ }
+
+ // probe passed, but empty device
+ // todo: should this be done in main?
+ if (ret && foundDevs.size() == 0)
+ {
+ foundDevs.emplace_back(
+ boost::container::flat_map<std::string, dbus::dbus_variant>());
+ }
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ // find configuration files
+ std::vector<fs::path> jsonPaths;
+ if (!find_files(fs::path(CONFIGURATION_DIR), R"(.*\.json)", jsonPaths, 0))
+ {
+ std::cerr << "Unable to find any configuration files in "
+ << CONFIGURATION_DIR << "\n";
+ return 1;
+ }
+ // setup connection to dbus
+ boost::asio::io_service io;
+ SYSTEM_BUS = std::make_shared<dbus::connection>(io, dbus::bus::system);
+ dbus::DbusObjectServer objServer(SYSTEM_BUS);
+ SYSTEM_BUS->request_name("xyz.openbmc_project.EntityManager");
+
+ std::vector<nlohmann::json> configurations;
+ for (auto &jsonPath : jsonPaths)
+ {
+ std::ifstream jsonStream(jsonPath.c_str());
+ if (!jsonStream.good())
+ {
+ std::cerr << "unable to open " << jsonPath.string() << "\n";
+ continue;
+ }
+ auto data = nlohmann::json::parse(jsonStream, nullptr, false);
+ if (data.is_discarded())
+ {
+ std::cerr << "syntax error in " << jsonPath.string() << "\n";
+ continue;
+ }
+ if (data.type() == nlohmann::json::value_t::array)
+ {
+ for (auto &d : data)
+ {
+ configurations.emplace_back(d);
+ }
+ }
+ else
+ {
+ configurations.emplace_back(data);
+ }
+ }
+
+ // keep looping as long as at least 1 new probe passed, removing
+ // configurations from the memory store once the probe passes
+ bool probePassed = true;
+ nlohmann::json systemConfiguration;
+ while (probePassed)
+ {
+ probePassed = false;
+ for (auto it = configurations.begin(); it != configurations.end();)
+ {
+ bool eraseConfig = false;
+ auto findProbe = (*it).find("probe");
+ auto findName = (*it).find("name");
+
+ // check for poorly formatted fields
+ if (findProbe == (*it).end())
+ {
+ std::cerr << "configuration file missing probe:\n " << *it
+ << "\n";
+ eraseConfig = true;
+ }
+ if (findName == (*it).end())
+ {
+ std::cerr << "configuration file missing name:\n " << *it
+ << "\n";
+ eraseConfig = true;
+ }
+
+ nlohmann::json probeCommand;
+ if ((*findProbe).type() != nlohmann::json::value_t::array)
+ {
+ probeCommand = nlohmann::json::array();
+ probeCommand.push_back(*findProbe);
+ }
+ else
+ {
+ probeCommand = *findProbe;
+ }
+ std::vector<
+ boost::container::flat_map<std::string, dbus::dbus_variant>>
+ foundDevices;
+ if (probe(probeCommand, foundDevices))
+ {
+ eraseConfig = true;
+ probePassed = true;
+ PASSED_PROBES.push_back(*findName);
+
+ size_t foundDeviceIdx = 0;
+
+ for (auto &foundDevice : foundDevices)
+ {
+ auto findExpose = (*it).find("exposes");
+ if (findExpose == (*it).end())
+ {
+ std::cerr
+ << "Warning, configuration file missing exposes"
+ << *it << "\n";
+ continue;
+ }
+ for (auto &expose : *findExpose)
+ {
+ for (auto keyPair = expose.begin();
+ keyPair != expose.end(); keyPair++)
+ {
+ // fill in template characters with devices
+ // found
+ if (keyPair.value().type() ==
+ nlohmann::json::value_t::string)
+ {
+ std::string value = keyPair.value();
+ if (value.find(TEMPLATE_CHAR) !=
+ std::string::npos)
+ {
+ std::string templateValue = value;
+
+ templateValue.erase(
+ 0, 1); // remove template character
+
+ // special case index
+ if ("index" == templateValue)
+ {
+ keyPair.value() = foundDeviceIdx;
+ }
+ else
+ {
+ std::string subsitute;
+ for (auto &foundDevicePair :
+ foundDevice)
+ {
+ if (boost::iequals(
+ foundDevicePair.first,
+ templateValue))
+ {
+ // convert value to string
+ // respresentation
+ subsitute =
+ boost::apply_visitor(
+ [](const auto &x) {
+ return boost::
+ lexical_cast<
+ std::
+ string>(
+ x);
+ },
+ foundDevicePair.second);
+ break;
+ }
+ }
+ if (!subsitute.size())
+ {
+ std::cerr << "could not find "
+ << templateValue
+ << " in device "
+ << expose["name"] << "\n";
+ }
+ else
+ {
+ keyPair.value() = subsitute;
+ }
+ }
+ }
+
+ // special case bind
+ if (boost::starts_with(keyPair.key(), "bind_"))
+ {
+ bool foundBind = false;
+ std::string bind = keyPair.key().substr(
+ sizeof("bind_") - 1);
+ for (auto &configuration :
+ systemConfiguration)
+ {
+ auto &configList =
+ configuration["exposes"];
+ for (auto exposedObjectIt =
+ configList.begin();
+ exposedObjectIt !=
+ configList.end();)
+ {
+ std::string foundObjectName =
+ (*exposedObjectIt)["name"];
+ if (boost::iequals(foundObjectName,
+ value))
+ {
+ (*exposedObjectIt)["status"] =
+ "okay"; // todo: is this the
+ // right spot?
+ expose[bind] =
+ (*exposedObjectIt);
+ foundBind = true;
+ exposedObjectIt =
+ configList.erase(
+ exposedObjectIt);
+ break;
+ }
+ else
+ {
+ exposedObjectIt++;
+ }
+ }
+ if (foundBind)
+ {
+ break;
+ }
+ }
+ if (!foundBind)
+ {
+ std::cerr << "configuration file "
+ "dependency error, "
+ "could not find bind "
+ << value << "\n";
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ systemConfiguration.push_back(*it);
+ foundDeviceIdx++;
+ }
+ }
+
+ if (eraseConfig)
+ {
+ it = configurations.erase(it);
+ }
+ else
+ {
+ it++;
+ }
+ }
+ }
+ // below here is temporary, to be added to dbus
+ std::ofstream output(std::string(OUTPUT_DIR) + "system.json");
+ output << systemConfiguration.dump(4);
+ output.close();
+
+ auto flat = nlohmann::json::array();
+ for (auto &pair : nlohmann::json::iterator_wrapper(systemConfiguration))
+ {
+ auto value = pair.value();
+ auto exposes = value.find("exposes");
+ if (exposes != value.end())
+ {
+ for (auto &item : *exposes)
+ {
+ flat.push_back(item);
+ }
+ }
+ }
+ output = std::ofstream(std::string(OUTPUT_DIR) + "flattened.json");
+ output << flat.dump(4);
+ output.close();
+
+ return 0;
+}
\ No newline at end of file
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
new file mode 100644
index 0000000..b431f81
--- /dev/null
+++ b/src/FruDevice.cpp
@@ -0,0 +1,476 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include <Utils.hpp>
+#include <boost/container/flat_map.hpp>
+#include <ctime>
+#include <dbus/connection.hpp>
+#include <dbus/endpoint.hpp>
+#include <dbus/message.hpp>
+#include <dbus/properties.hpp>
+#include <fcntl.h>
+#include <fstream>
+#include <future>
+#include <i2c-dev-user.h>
+#include <iostream>
+#include <sys/ioctl.h>
+
+namespace fs = std::experimental::filesystem;
+static constexpr bool DEBUG = false;
+static size_t UNKNOWN_BUS_OBJECT_COUNT = 0;
+
+const static constexpr char *BASEBOARD_FRU_LOCATION =
+ "/etc/fru/baseboard.fru.bin";
+
+static constexpr std::array<const char *, 5> FRU_AREAS = {
+ "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
+
+using DeviceMap = boost::container::flat_map<int, std::vector<char>>;
+using BusMap = boost::container::flat_map<int, std::shared_ptr<DeviceMap>>;
+
+int get_bus_frus(int file, int first, int last, int bus,
+ std::shared_ptr<DeviceMap> devices)
+{
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> block_data;
+
+ for (int ii = first; ii <= last; ii++)
+ {
+ // Set slave address
+ if (ioctl(file, I2C_SLAVE_FORCE, ii) < 0)
+ {
+ std::cerr << "device at bus " << bus << "register " << ii
+ << "busy\n";
+ continue;
+ }
+ // probe
+ else if (i2c_smbus_read_byte(file) < 0)
+ {
+ continue;
+ }
+
+ if (DEBUG)
+ {
+ std::cout << "something at bus " << bus << "addr " << ii << "\n";
+ }
+ if (i2c_smbus_read_i2c_block_data(file, 0x0, 0x8, block_data.data()) <
+ 0)
+ {
+ std::cerr << "failed to read bus " << bus << " address " << ii
+ << "\n";
+ continue;
+ }
+ size_t sum = 0;
+ for (int jj = 0; jj < 7; jj++)
+ {
+ sum += block_data[jj];
+ }
+ sum = (256 - sum) & 0xFF;
+
+ // check the header checksum
+ if (sum == block_data[7])
+ {
+ std::vector<char> device;
+ device.insert(device.end(), block_data.begin(),
+ block_data.begin() + 8);
+
+ for (int jj = 1; jj <= FRU_AREAS.size(); jj++)
+ {
+ auto area_offset = device[jj];
+ if (area_offset != 0)
+ {
+ area_offset *= 8;
+ if (i2c_smbus_read_i2c_block_data(file, area_offset, 0x8,
+ block_data.data()) < 0)
+ {
+ std::cerr << "failed to read bus " << bus << " address "
+ << ii << "\n";
+ return -1;
+ }
+ int length = block_data[1] * 8;
+ device.insert(device.end(), block_data.begin(),
+ block_data.begin() + 8);
+ length -= 8;
+ area_offset += 8;
+
+ while (length > 0)
+ {
+ auto to_get = std::min(0x20, length);
+ if (i2c_smbus_read_i2c_block_data(
+ file, area_offset, to_get, block_data.data()) <
+ 0)
+ {
+ std::cerr << "failed to read bus " << bus
+ << " address " << ii << "\n";
+ return -1;
+ }
+ device.insert(device.end(), block_data.begin(),
+ block_data.begin() + to_get);
+ area_offset += to_get;
+ length -= to_get;
+ }
+ }
+ }
+ (*devices).emplace(ii, device);
+ }
+ }
+
+ return 0;
+}
+
+static BusMap FindI2CDevices(const std::vector<fs::path> &i2cBuses)
+{
+ static std::vector<std::future<void>> futures;
+ BusMap busMap;
+ for (auto &i2cBus : i2cBuses)
+ {
+ auto busnum = i2cBus.string();
+ auto lastDash = busnum.rfind(std::string("-"));
+ // delete everything before dash inclusive
+ if (lastDash != std::string::npos)
+ {
+ busnum.erase(0, lastDash + 1);
+ }
+ auto bus = std::stoi(busnum);
+
+ auto file = open(i2cBus.c_str(), O_RDWR);
+ if (file < 0)
+ {
+ std::cerr << "unable to open i2c device " << i2cBus.string()
+ << "\n";
+ continue;
+ }
+ unsigned long funcs = 0;
+
+ if (ioctl(file, I2C_FUNCS, &funcs) < 0)
+ {
+ std::cerr
+ << "Error: Could not get the adapter functionality matrix bus"
+ << bus << "\n";
+ continue;
+ }
+ if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
+ !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+ {
+ std::cerr << "Error: Can't use SMBus Receive Byte command bus "
+ << bus << "\n";
+ continue;
+ }
+ auto &device = busMap[bus];
+ device = std::make_shared<DeviceMap>();
+
+ // todo: call with boost asio?
+ futures.emplace_back(
+ std::async(std::launch::async, [file, device, bus] {
+ // i2cdetect by default uses the range 0x03 to 0x77, as this is
+ // what we
+ // have tested with, use this range. Could be changed in
+ // future.
+ get_bus_frus(file, 0x03, 0x77, bus, device);
+ close(file);
+ }));
+ }
+ for (auto &fut : futures)
+ {
+ fut.get(); // wait for all scans
+ }
+ return busMap;
+}
+
+static const std::tm intelEpoch(void)
+{
+ std::tm val = {0};
+ val.tm_year = 1996 - 1900;
+ return val;
+}
+
+bool formatFru(const std::vector<char> &fruBytes,
+ boost::container::flat_map<std::string, std::string> &result)
+{
+ static const std::vector<const char *> CHASSIS_FRU_AREAS = {
+ "PART_NUMBER", "SERIAL_NUMBER", "CHASSIS_INFO_AM1", "CHASSIS_INFO_AM2"};
+
+ static const std::vector<const char *> BOARD_FRU_AREAS = {
+ "MANUFACTURER", "PRODUCT_NAME", "SERIAL_NUMBER", "PART_NUMBER",
+ "VERSION_ID"};
+
+ static const std::vector<const char *> PRODUCT_FRU_AREAS = {
+ "MANUFACTURER", "PRODUCT_NAME", "PART_NUMBER",
+ "PRODUCT_VERSION", "PRODUCT_SERIAL_NUMBER", "ASSET_TAG"};
+
+ size_t sum = 0;
+
+ if (fruBytes.size() < 8)
+ {
+ return false;
+ }
+ std::vector<char>::const_iterator fruAreaOffsetField = fruBytes.begin();
+ result["Common Format Version"] =
+ std::to_string(static_cast<int>(*fruAreaOffsetField));
+
+ const std::vector<const char *> *fieldData;
+
+ for (auto &area : FRU_AREAS)
+ {
+ fruAreaOffsetField++;
+ if (fruAreaOffsetField >= fruBytes.end())
+ {
+ return false;
+ }
+ size_t offset = (*fruAreaOffsetField) * 8;
+
+ if (offset > 1)
+ {
+ // +2 to skip format and length
+ std::vector<char>::const_iterator fruBytesIter =
+ fruBytes.begin() + offset + 2;
+
+ if (fruBytesIter >= fruBytes.end())
+ {
+ return false;
+ }
+
+ if (area == "CHASSIS")
+ {
+ result["CHASSIS_TYPE"] =
+ std::to_string(static_cast<int>(*fruBytesIter));
+ fruBytesIter += 1;
+ fieldData = &CHASSIS_FRU_AREAS;
+ }
+ else if (area == "BOARD")
+ {
+ result["BOARD_LANGUAGE_CODE"] =
+ std::to_string(static_cast<int>(*fruBytesIter));
+ fruBytesIter += 1;
+ if (fruBytesIter >= fruBytes.end())
+ {
+ return false;
+ }
+
+ unsigned int minutes = *fruBytesIter |
+ *(fruBytesIter + 1) << 8 |
+ *(fruBytesIter + 2) << 16;
+ std::tm fruTime = intelEpoch();
+ time_t timeValue = mktime(&fruTime);
+ timeValue += minutes * 60;
+ fruTime = *gmtime(&timeValue);
+
+ result["BOARD_MANUFACTURE_DATE"] = asctime(&fruTime);
+ result["BOARD_MANUFACTURE_DATE"]
+ .pop_back(); // remove trailing null
+ fruBytesIter += 3;
+ fieldData = &BOARD_FRU_AREAS;
+ }
+ else if (area == "PRODUCT")
+ {
+ result["PRODUCT_LANGUAGE_CODE"] =
+ std::to_string(static_cast<int>(*fruBytesIter));
+ fruBytesIter += 1;
+ fieldData = &PRODUCT_FRU_AREAS;
+ }
+ else
+ {
+ continue;
+ }
+ for (auto &field : *fieldData)
+ {
+ if (fruBytesIter >= fruBytes.end())
+ {
+ return false;
+ }
+
+ size_t length = *fruBytesIter & 0x3f;
+ fruBytesIter += 1;
+
+ if (fruBytesIter >= fruBytes.end())
+ {
+ return false;
+ }
+
+ result[std::string(area) + "_" + field] =
+ std::string(fruBytesIter, fruBytesIter + length);
+ fruBytesIter += length;
+ if (fruBytesIter >= fruBytes.end())
+ {
+ std::cerr << "Warning Fru Length Mismatch:\n ";
+ for (auto &c : fruBytes)
+ {
+ std::cerr << c;
+ }
+ std::cerr << "\n";
+ if (DEBUG)
+ {
+ for (auto &keyPair : result)
+ {
+ std::cerr << keyPair.first << " : "
+ << keyPair.second << "\n";
+ }
+ }
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+void AddFruObjectToDbus(
+ std::shared_ptr<dbus::connection> dbusConn, std::vector<char> &device,
+ dbus::DbusObjectServer &objServer,
+ boost::container::flat_map<std::pair<size_t, size_t>,
+ std::shared_ptr<dbus::DbusObject>>
+ &dbusObjectMap,
+ int bus, size_t address)
+{
+ boost::container::flat_map<std::string, std::string> formattedFru;
+ if (!formatFru(device, formattedFru))
+ {
+ std::cerr << "failed to format fru for device at bus " << std::hex
+ << bus << "address " << address << "\n";
+ return;
+ }
+ auto productNameFind = formattedFru.find("BOARD_PRODUCT_NAME");
+ std::string productName;
+ if (productNameFind == formattedFru.end())
+ {
+ productNameFind = formattedFru.find("PRODUCT_PRODUCT_NAME");
+ }
+ if (productNameFind != formattedFru.end())
+ {
+ productName = productNameFind->second;
+ }
+ else
+ {
+ productName = "UNKNOWN" + std::to_string(UNKNOWN_BUS_OBJECT_COUNT);
+ UNKNOWN_BUS_OBJECT_COUNT++;
+ }
+
+ auto object =
+ objServer.add_object("/xyz/openbmc_project/FruDevice/" + productName);
+ dbusObjectMap[std::pair<size_t, size_t>(bus, address)] = object;
+
+ auto iface = std::make_shared<dbus::DbusInterface>(
+ "xyz.openbmc_project.FruDevice", dbusConn);
+ object->register_interface(iface);
+ for (auto &property : formattedFru)
+ {
+ iface->set_property(property.first, property.second);
+ }
+ // baseboard can set this to -1 to not set a bus / address
+ if (bus > 0)
+ {
+ std::stringstream data;
+ data << "0x" << std::hex << bus;
+ iface->set_property("BUS", data.str());
+ data.str("");
+ data << "0x" << std::hex << address;
+ iface->set_property("ADDRESS", data.str());
+ }
+}
+
+static bool readBaseboardFru(std::vector<char> &baseboardFru)
+{
+ // try to read baseboard fru from file
+ std::ifstream baseboardFruFile(BASEBOARD_FRU_LOCATION, std::ios::binary);
+ if (baseboardFruFile.good())
+ {
+ baseboardFruFile.seekg(0, std::ios_base::end);
+ std::streampos fileSize = baseboardFruFile.tellg();
+ baseboardFru.resize(fileSize);
+ baseboardFruFile.seekg(0, std::ios_base::beg);
+ baseboardFruFile.read(baseboardFru.data(), fileSize);
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ auto devDir = fs::path("/dev/");
+ auto matchString = std::string("i2c*");
+ std::vector<fs::path> i2cBuses;
+
+ if (!find_files(devDir, matchString, i2cBuses, 0))
+ {
+ std::cerr << "unable to find i2c devices\n";
+ return 1;
+ }
+ BusMap busMap = FindI2CDevices(i2cBuses);
+
+ boost::asio::io_service io;
+ auto systemBus = std::make_shared<dbus::connection>(io, dbus::bus::system);
+ dbus::DbusObjectServer objServer(systemBus);
+ systemBus->request_name("com.intel.FruDevice");
+
+ // this is a map with keys of pair(bus number, adddress) and values of the
+ // object on dbus
+ boost::container::flat_map<std::pair<size_t, size_t>,
+ std::shared_ptr<dbus::DbusObject>>
+ dbusObjectMap;
+
+ for (auto &devicemap : busMap)
+ {
+ for (auto &device : *devicemap.second)
+ {
+ AddFruObjectToDbus(systemBus, device.second, objServer,
+ dbusObjectMap, devicemap.first, device.first);
+ }
+ }
+
+ std::vector<char> baseboardFru;
+ if (readBaseboardFru(baseboardFru))
+ {
+ AddFruObjectToDbus(systemBus, baseboardFru, objServer, dbusObjectMap,
+ -1, -1);
+ }
+
+ auto object = std::make_shared<dbus::DbusObject>(
+ systemBus, "/xyz/openbmc_project/FruDevice");
+ objServer.register_object(object);
+ auto iface = std::make_shared<dbus::DbusInterface>(
+ "xyz.openbmc_project.FruDeviceManager", systemBus);
+ object->register_interface(iface);
+
+ iface->register_method("ReScan", [&]() {
+ busMap = FindI2CDevices(i2cBuses);
+
+ for (auto &busObj : dbusObjectMap)
+ {
+ objServer.remove_object(busObj.second);
+ }
+ dbusObjectMap.clear();
+ UNKNOWN_BUS_OBJECT_COUNT = 0;
+
+ for (auto &devicemap : busMap)
+ {
+ for (auto &device : *devicemap.second)
+ {
+ AddFruObjectToDbus(systemBus, device.second, objServer,
+ dbusObjectMap, devicemap.first,
+ device.first);
+ }
+ }
+
+ return std::tuple<>(); // this is a bug in boost-dbus, needs some sort
+ // of return
+ });
+
+ io.run();
+ return 0;
+}
diff --git a/src/Utils.cpp b/src/Utils.cpp
new file mode 100644
index 0000000..e63861e
--- /dev/null
+++ b/src/Utils.cpp
@@ -0,0 +1,49 @@
+/*
+// Copyright (c) 2017 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include <Utils.hpp>
+#include <experimental/filesystem>
+#include <fstream>
+#include <regex>
+
+namespace fs = std::experimental::filesystem;
+
+bool find_files(const fs::path &dir_path, const std::string &match_string,
+ std::vector<fs::path> &found_paths, unsigned int symlink_depth)
+{
+ if (!fs::exists(dir_path))
+ return false;
+
+ fs::directory_iterator end_itr;
+ std::regex search(match_string);
+ std::smatch match;
+ for (auto &p : fs::recursive_directory_iterator(dir_path))
+ {
+ std::string path = p.path().string();
+ if (!is_directory(p))
+ {
+ if (std::regex_search(path, match, search))
+ found_paths.emplace_back(p.path());
+ }
+ // since we're using a recursve iterator, these should only be symlink
+ // dirs
+ else if (symlink_depth)
+ {
+ find_files(p.path(), match_string, found_paths, symlink_depth - 1);
+ }
+ }
+ return true;
+}
\ No newline at end of file