Add Intel-specific IPMI sensor commands
Also includes SDR storage commands that are required to support
the 'ipmitool sensor list' command.
Change-Id: Id1830097d93882114085fce723f0b92367b2db48
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
diff --git a/src/storagecommands.cpp b/src/storagecommands.cpp
new file mode 100644
index 0000000..e71d47f
--- /dev/null
+++ b/src/storagecommands.cpp
@@ -0,0 +1,315 @@
+/*
+// Copyright (c) 2017 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 <host-ipmid/ipmid-api.h>
+
+#include <boost/container/flat_map.hpp>
+#include <commandutils.hpp>
+#include <iostream>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+#include <storagecommands.hpp>
+
+namespace ipmi
+{
+
+namespace storage
+{
+
+constexpr static const size_t maxFruSdrNameSize = 16;
+using ManagedObjectType = boost::container::flat_map<
+ sdbusplus::message::object_path,
+ boost::container::flat_map<
+ std::string, boost::container::flat_map<std::string, DbusVariant>>>;
+using ManagedEntry = std::pair<
+ sdbusplus::message::object_path,
+ boost::container::flat_map<
+ std::string, boost::container::flat_map<std::string, DbusVariant>>>;
+
+constexpr static const char* fruDeviceServiceName = "com.intel.FruDevice";
+
+static std::vector<uint8_t> fruCache;
+static uint8_t cacheBus = 0xFF;
+static uint8_t cacheAddr = 0XFF;
+
+std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
+
+// we unfortunately have to build a map of hashes in case there is a
+// collision to verify our dev-id
+boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
+
+static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
+
+bool writeFru()
+{
+ sdbusplus::message::message writeFru = dbus.new_method_call(
+ fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+ "xyz.openbmc_project.FruDeviceManager", "WriteFru");
+ writeFru.append(cacheBus, cacheAddr, fruCache);
+ try
+ {
+ sdbusplus::message::message writeFruResp = dbus.call(writeFru);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ // todo: log sel?
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "error writing fru");
+ return false;
+ }
+ return true;
+}
+
+ipmi_ret_t replaceCacheFru(uint8_t devId)
+{
+ static uint8_t lastDevId = 0xFF;
+
+ bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
+ if (lastDevId == devId && timerRunning)
+ {
+ return IPMI_CC_OK; // cache already up to date
+ }
+ // if timer is running, stop it and writeFru manually
+ else if (timerRunning)
+ {
+ cacheTimer->stop();
+ writeFru();
+ }
+
+ sdbusplus::message::message getObjects = dbus.new_method_call(
+ fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+ ManagedObjectType frus;
+ try
+ {
+ sdbusplus::message::message resp = dbus.call(getObjects);
+ resp.read(frus);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "replaceCacheFru: error getting managed objects");
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+
+ deviceHashes.clear();
+
+ // hash the object paths to create unique device id's. increment on
+ // collision
+ std::hash<std::string> hasher;
+ for (const auto& fru : frus)
+ {
+ auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
+ if (fruIface == fru.second.end())
+ {
+ continue;
+ }
+
+ auto busFind = fruIface->second.find("BUS");
+ auto addrFind = fruIface->second.find("ADDRESS");
+ if (busFind == fruIface->second.end() ||
+ addrFind == fruIface->second.end())
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "fru device missing Bus or Address",
+ phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
+ continue;
+ }
+
+ uint8_t fruBus =
+ sdbusplus::message::variant_ns::get<uint32_t>(busFind->second);
+ uint8_t fruAddr =
+ sdbusplus::message::variant_ns::get<uint32_t>(addrFind->second);
+
+ uint8_t fruHash = 0;
+ if (fruBus != 0 || fruAddr != 0)
+ {
+ fruHash = hasher(fru.first.str);
+ // can't be 0xFF based on spec, and 0 is reserved for baseboard
+ if (fruHash == 0 || fruHash == 0xFF)
+ {
+ fruHash = 1;
+ }
+ }
+ std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
+
+ bool emplacePassed = false;
+ while (!emplacePassed)
+ {
+ auto resp = deviceHashes.emplace(fruHash, newDev);
+ emplacePassed = resp.second;
+ if (!emplacePassed)
+ {
+ fruHash++;
+ // can't be 0xFF based on spec, and 0 is reserved for
+ // baseboard
+ if (fruHash == 0XFF)
+ {
+ fruHash = 0x1;
+ }
+ }
+ }
+ }
+ auto deviceFind = deviceHashes.find(devId);
+ if (deviceFind == deviceHashes.end())
+ {
+ return IPMI_CC_SENSOR_INVALID;
+ }
+
+ fruCache.clear();
+ sdbusplus::message::message getRawFru = dbus.new_method_call(
+ fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+ "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
+ cacheBus = deviceFind->second.first;
+ cacheAddr = deviceFind->second.second;
+ getRawFru.append(cacheBus, cacheAddr);
+ try
+ {
+ sdbusplus::message::message getRawResp = dbus.call(getRawFru);
+ getRawResp.read(fruCache);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ lastDevId = 0xFF;
+ cacheBus = 0xFF;
+ cacheAddr = 0xFF;
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+
+ lastDevId = devId;
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t getFruSdrCount(size_t& count)
+{
+ ipmi_ret_t ret = replaceCacheFru(0);
+ if (ret != IPMI_CC_OK)
+ {
+ return ret;
+ }
+ count = deviceHashes.size();
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
+{
+ ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
+ if (ret != IPMI_CC_OK)
+ {
+ return ret;
+ }
+ if (deviceHashes.size() < index)
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+ auto device = deviceHashes.begin() + index;
+ uint8_t& bus = device->second.first;
+ uint8_t& address = device->second.second;
+
+ ManagedObjectType frus;
+
+ sdbusplus::message::message getObjects = dbus.new_method_call(
+ fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+ try
+ {
+ sdbusplus::message::message resp = dbus.call(getObjects);
+ resp.read(frus);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+ boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
+ auto fru =
+ std::find_if(frus.begin(), frus.end(),
+ [bus, address, &fruData](ManagedEntry& entry) {
+ auto findFruDevice =
+ entry.second.find("xyz.openbmc_project.FruDevice");
+ if (findFruDevice == entry.second.end())
+ {
+ return false;
+ }
+ fruData = &(findFruDevice->second);
+ auto findBus = findFruDevice->second.find("BUS");
+ auto findAddress =
+ findFruDevice->second.find("ADDRESS");
+ if (findBus == findFruDevice->second.end() ||
+ findAddress == findFruDevice->second.end())
+ {
+ return false;
+ }
+ if (sdbusplus::message::variant_ns::get<uint32_t>(
+ findBus->second) != bus)
+ {
+ return false;
+ }
+ if (sdbusplus::message::variant_ns::get<uint32_t>(
+ findAddress->second) != address)
+ {
+ return false;
+ }
+ return true;
+ });
+ if (fru == frus.end())
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+ std::string name;
+ auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
+ auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
+ if (findProductName != fruData->end())
+ {
+ name = sdbusplus::message::variant_ns::get<std::string>(
+ findProductName->second);
+ }
+ else if (findBoardName != fruData->end())
+ {
+ name = sdbusplus::message::variant_ns::get<std::string>(
+ findBoardName->second);
+ }
+ else
+ {
+ name = "UNKNOWN";
+ }
+ if (name.size() > maxFruSdrNameSize)
+ {
+ name = name.substr(0, maxFruSdrNameSize);
+ }
+ size_t sizeDiff = maxFruSdrNameSize - name.size();
+
+ resp.header.record_id_lsb = 0x0; // calling code is to implement these
+ resp.header.record_id_msb = 0x0;
+ resp.header.sdr_version = ipmiSdrVersion;
+ resp.header.record_type = 0x11; // FRU Device Locator
+ resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
+ resp.key.deviceAddress = 0x20;
+ resp.key.fruID = device->first;
+ resp.key.accessLun = 0x80; // logical / physical fru device
+ resp.key.channelNumber = 0x0;
+ resp.body.reserved = 0x0;
+ resp.body.deviceType = 0x10;
+ resp.body.entityID = 0x0;
+ resp.body.entityInstance = 0x1;
+ resp.body.oem = 0x0;
+ resp.body.deviceIDLen = name.size();
+ name.copy(resp.body.deviceID, name.size());
+
+ return IPMI_CC_OK;
+}
+} // namespace storage
+} // namespace ipmi
\ No newline at end of file