pres: Add way to bind fan eeprom driver after plug
Some fans have EEPROMs on them that are read by other BMC code so the
contents can be added to the BMC inventory. Since fans can usually be
hotplugged, this means those EEPROMs need to be read after a fan is
plugged, and there was previously no method to trigger that.
This commit adds functionality to phosphor-fan-presence-tach to bind the
EEPROM driver to the new device after a fan with an EEPROM is plugged.
This triggers a udev event which triggers EEPROM reads if the platform
is configured to do so.
This is done with a new optional JSON section in the config.json, which
looks like:
"eeprom": {
"bus_address": "31-0050",
"driver_name": "at24",
"bind_delay_ms": 1000
}
The 'bus_address' field is the device's I2C bus and address string as it
is represented in the I2C subsystem in sysfs. The 'driver_name' field
is the name of the device driver that manages that device. The
'bind_delay_ms' field allows there to be a defined amount of time
between when the device is plugged and when the driver is bound, in case
a certain amount of time is required for the device to come online after
it receives power.
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I10d36efab954393239e6face96244ecd34035596
diff --git a/docs/presence/README.md b/docs/presence/README.md
index 279041b..82d0481 100644
--- a/docs/presence/README.md
+++ b/docs/presence/README.md
@@ -140,6 +140,7 @@
* [path](path.md)
* [methods](methods.md)
* [rpolicy](rpolicy.md)
+* [eeprom](eeprom.md) *optional*
### Comments
diff --git a/docs/presence/eeprom.md b/docs/presence/eeprom.md
new file mode 100644
index 0000000..919ec69
--- /dev/null
+++ b/docs/presence/eeprom.md
@@ -0,0 +1,65 @@
+# eeprom
+
+## Description
+Some fans have EEPROMs on them. In these cases, the `eeprom` JSON stanza can
+be used to have the application re-bind the EEPROM driver to the EEPROM
+instance after a new fan is detected. This will trigger the EEPROM to be read
+by the appropriate code if the platform is configured to do so.
+
+This is optional and only required if the above behavior is desired.
+
+## Keys
+* ["bus_address"](#bus_address)
+* ["driver_name"](#driver_name)
+* ["bind_delay_ms"](#bind_delay_ms)
+
+### "bus_address"
+The I2C bus and address string of the form BB-AAAA that is used by the I2C
+subsystem in sysfs.
+
+```
+"bus_address": "3-0050"
+```
+
+### "driver_name"
+The name of the eeprom driver in sysfs.
+
+```
+"driver_name": "at24"
+```
+
+### "bind_delay_ms"
+The number of milliseconds to wait after a fan is detected before binding the
+driver to the device in case the device takes some time to initialize after
+being plugged into power. If no delay is required, a value of zero can be
+used.
+
+```
+"bind_delay_ms": 1000
+```
+
+## Example
+<pre><code>
+[
+ {
+ "name": "fan0",
+ "path": "/system/chassis/motherboard/fan0",
+ "methods": [
+ {
+ "type": "tach",
+ "sensors": [
+ "fan0_0"
+ ]
+ }
+ ],
+ "rpolicy": {
+ "type": "anyof"
+ },
+ <b><i>"eeprom": {
+ "bus_address": "30-0050",
+ "driver_name": "at24",
+ "bind_delay_ms": 1000
+ }</i></b>
+ }
+]
+</code></pre>
diff --git a/presence/anyof.cpp b/presence/anyof.cpp
index 4dc2ec3..54a0ec0 100644
--- a/presence/anyof.cpp
+++ b/presence/anyof.cpp
@@ -34,8 +34,9 @@
static const auto powerOnDelayTime = 5s;
AnyOf::AnyOf(const Fan& fan,
- const std::vector<std::reference_wrapper<PresenceSensor>>& s) :
- RedundancyPolicy(fan),
+ const std::vector<std::reference_wrapper<PresenceSensor>>& s,
+ std::unique_ptr<EEPROMDevice> e) :
+ RedundancyPolicy(fan, std::move(e)),
state(), _powerState(getPowerStateObject()),
_powerOnDelayTimer(sdeventplus::Event::get_default(),
std::bind(&AnyOf::delayedAfterPowerOn, this)),
@@ -78,6 +79,18 @@
[](const auto& s) { return std::get<presentPos>(s); });
setPresence(fan, newState);
+ if (eepromDevice && (newState != origState))
+ {
+ if (newState)
+ {
+ eepromDevice->bind();
+ }
+ else
+ {
+ eepromDevice->unbind();
+ }
+ }
+
// At least 1 sensor said a fan was present, check if any disagree.
if (newState)
{
diff --git a/presence/anyof.hpp b/presence/anyof.hpp
index 91865b0..03fd1a4 100644
--- a/presence/anyof.hpp
+++ b/presence/anyof.hpp
@@ -41,9 +41,11 @@
*
* @param[in] fan - The fan associated with the policy.
* @param[in] s - The set of sensors associated with the policy.
+ * @param[in] e - EEPROM device instance
*/
AnyOf(const Fan& fan,
- const std::vector<std::reference_wrapper<PresenceSensor>>& s);
+ const std::vector<std::reference_wrapper<PresenceSensor>>& s,
+ std::unique_ptr<EEPROMDevice> e);
/**
* @brief stateChanged
diff --git a/presence/config_files/p10bmc/ibm,everest/config.json b/presence/config_files/p10bmc/ibm,everest/config.json
index 928eca2..3ee7500 100644
--- a/presence/config_files/p10bmc/ibm,everest/config.json
+++ b/presence/config_files/p10bmc/ibm,everest/config.json
@@ -19,6 +19,11 @@
],
"rpolicy": {
"type": "anyof"
+ },
+ "eeprom": {
+ "bus_address": "31-0050",
+ "driver_name": "at24",
+ "bind_delay_ms": 1000
}
},
{
@@ -41,6 +46,11 @@
],
"rpolicy": {
"type": "anyof"
+ },
+ "eeprom": {
+ "bus_address": "32-0050",
+ "driver_name": "at24",
+ "bind_delay_ms": 1000
}
},
{
@@ -63,6 +73,11 @@
],
"rpolicy": {
"type": "anyof"
+ },
+ "eeprom": {
+ "bus_address": "33-0050",
+ "driver_name": "at24",
+ "bind_delay_ms": 1000
}
},
{
@@ -85,6 +100,11 @@
],
"rpolicy": {
"type": "anyof"
+ },
+ "eeprom": {
+ "bus_address": "34-0050",
+ "driver_name": "at24",
+ "bind_delay_ms": 1000
}
}
]
diff --git a/presence/eeprom_device.hpp b/presence/eeprom_device.hpp
new file mode 100644
index 0000000..883cc17
--- /dev/null
+++ b/presence/eeprom_device.hpp
@@ -0,0 +1,141 @@
+#pragma once
+
+#include "sdeventplus.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <string>
+
+namespace phosphor::fan::presence
+{
+using namespace phosphor::fan::util;
+
+/**
+ * @class EEPROMDevice
+ *
+ * Provides an API to bind an EEPROM driver to a device, after waiting
+ * a configurable amount of time in case the device needs time
+ * to initialize after being plugged into a system.
+ */
+class EEPROMDevice
+{
+ public:
+ EEPROMDevice() = delete;
+ ~EEPROMDevice() = default;
+ EEPROMDevice(const EEPROMDevice&) = delete;
+ EEPROMDevice& operator=(const EEPROMDevice&) = delete;
+ EEPROMDevice(EEPROMDevice&&) = delete;
+ EEPROMDevice& operator=(EEPROMDevice&&) = delete;
+
+ /**
+ * @brief Constructor
+ * @param[in] address - The bus-address string as used by drivers
+ * in sysfs.
+ * @param[in] driver - The I2C driver name in sysfs
+ * @param[in] bindDelayInMS - The time in milliseconds to wait
+ * before actually doing the bind.
+ */
+ EEPROMDevice(const std::string& address, const std::string& driver,
+ size_t bindDelayInMS) :
+ address(address),
+ path(baseDriverPath / driver), bindDelay(bindDelayInMS),
+ timer(SDEventPlus::getEvent(),
+ std::bind(std::mem_fn(&EEPROMDevice::bindTimerExpired), this))
+ {}
+
+ /**
+ * @brief Kicks off the timer to do the actual bind
+ */
+ void bind()
+ {
+ timer.restartOnce(std::chrono::milliseconds{bindDelay});
+ }
+
+ /**
+ * @brief Stops the bind timer if running and unbinds the device
+ */
+ void unbind()
+ {
+ if (timer.isEnabled())
+ {
+ timer.setEnabled(false);
+ }
+
+ unbindDevice();
+ }
+
+ private:
+ /**
+ * @brief When the bind timer expires it will bind the device.
+ */
+ void bindTimerExpired() const
+ {
+ unbindDevice();
+
+ auto bindPath = path / "bind";
+ std::ofstream bind{bindPath};
+ if (bind.good())
+ {
+ lg2::info("Binding fan EEPROM device with address {ADDRESS}",
+ "ADDRESS", address);
+ bind << address;
+ }
+
+ if (bind.fail())
+ {
+ lg2::error("Error while binding fan EEPROM device with path {PATH}"
+ " and address {ADDR}",
+ "PATH", bindPath, "ADDR", address);
+ }
+ }
+
+ /**
+ * @brief Unbinds the device.
+ */
+ void unbindDevice() const
+ {
+ auto devicePath = path / address;
+ if (!std::filesystem::exists(devicePath))
+ {
+ return;
+ }
+
+ auto unbindPath = path / "unbind";
+ std::ofstream unbind{unbindPath};
+ if (unbind.good())
+ {
+ unbind << address;
+ }
+
+ if (unbind.fail())
+ {
+ lg2::error("Error while unbinding fan EEPROM device with path"
+ " {PATH} and address {ADDR}",
+ "PATH", unbindPath, "ADDR", address);
+ }
+ }
+
+ /** @brief The base I2C drivers directory in sysfs */
+ const std::filesystem::path baseDriverPath{"/sys/bus/i2c/drivers"};
+
+ /**
+ * @brief The address string with the i2c bus and address.
+ * Example: '32-0050'
+ */
+ const std::string address;
+
+ /** @brief The path to the driver dir, like /sys/bus/i2c/drivers/at24 */
+ const std::filesystem::path path;
+
+ /** @brief Number of milliseconds to delay to actually do the bind. */
+ const size_t bindDelay{};
+
+ /** @brief The timer to do the delay with */
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> timer;
+};
+
+} // namespace phosphor::fan::presence
diff --git a/presence/fallback.cpp b/presence/fallback.cpp
index 36824fc..a3c5e72 100644
--- a/presence/fallback.cpp
+++ b/presence/fallback.cpp
@@ -62,6 +62,18 @@
}
setPresence(fan, present);
+
+ if (eepromDevice)
+ {
+ if (present)
+ {
+ eepromDevice->bind();
+ }
+ else
+ {
+ eepromDevice->unbind();
+ }
+ }
}
void Fallback::monitor()
diff --git a/presence/fallback.hpp b/presence/fallback.hpp
index d6bad02..db661e0 100644
--- a/presence/fallback.hpp
+++ b/presence/fallback.hpp
@@ -39,10 +39,12 @@
*
* @param[in] fan - The fan associated with the policy.
* @param[in] s - The set of sensors associated with the policy.
+ * @param[in] e - EEPROM device instance
*/
Fallback(const Fan& fan,
- const std::vector<std::reference_wrapper<PresenceSensor>>& s) :
- RedundancyPolicy(fan),
+ const std::vector<std::reference_wrapper<PresenceSensor>>& s,
+ std::unique_ptr<EEPROMDevice> e) :
+ RedundancyPolicy(fan, std::move(e)),
sensors(s)
{
activeSensor = sensors.begin();
diff --git a/presence/json_parser.cpp b/presence/json_parser.cpp
index 02c9a11..dbdc54b 100644
--- a/presence/json_parser.cpp
+++ b/presence/json_parser.cpp
@@ -168,13 +168,37 @@
timeUntilError = member["fan_missing_error_time"].get<size_t>();
}
+ std::unique_ptr<EEPROMDevice> eepromDevice;
+ if (member.contains("eeprom"))
+ {
+ const auto& eeprom = member.at("eeprom");
+ if (!eeprom.contains("bus_address") ||
+ !eeprom.contains("driver_name") ||
+ !eeprom.contains("bind_delay_ms"))
+ {
+ log<level::ERR>(
+ "Missing address, driver_name, or bind_delay_ms in eeprom "
+ "section",
+ entry("FAN_NAME=%s",
+ member["name"].get<std::string>().c_str()));
+
+ throw std::runtime_error("Missing address, driver_name, or "
+ "bind_delay_ms in eeprom section");
+ }
+ eepromDevice = std::make_unique<EEPROMDevice>(
+ eeprom["bus_address"].get<std::string>(),
+ eeprom["driver_name"].get<std::string>(),
+ eeprom["bind_delay_ms"].get<size_t>());
+ }
+
auto fan =
std::make_tuple(member["name"], member["path"], timeUntilError);
// Create a fan object
fans.emplace_back(std::make_tuple(fan, std::move(sensors)));
// Add fan presence policy
- auto policy = getPolicy(member["rpolicy"], fans.back());
+ auto policy =
+ getPolicy(member["rpolicy"], fans.back(), std::move(eepromDevice));
if (policy)
{
policies.emplace_back(std::move(policy));
@@ -199,7 +223,8 @@
}
std::unique_ptr<RedundancyPolicy>
- JsonConfig::getPolicy(const json& rpolicy, const fanPolicy& fpolicy)
+ JsonConfig::getPolicy(const json& rpolicy, const fanPolicy& fpolicy,
+ std::unique_ptr<EEPROMDevice> eepromDevice)
{
if (!rpolicy.contains("type"))
{
@@ -219,7 +244,7 @@
if (func != _rpolicies.end())
{
// Call function for redundancy policy type and return the policy
- return func->second(fpolicy);
+ return func->second(fpolicy, std::move(eepromDevice));
}
else
{
@@ -326,7 +351,8 @@
namespace rpolicy
{
// Get an `Anyof` redundancy policy for the fan
-std::unique_ptr<RedundancyPolicy> getAnyof(const fanPolicy& fan)
+std::unique_ptr<RedundancyPolicy>
+ getAnyof(const fanPolicy& fan, std::unique_ptr<EEPROMDevice> eepromDevice)
{
std::vector<std::reference_wrapper<PresenceSensor>> pSensors;
for (auto& fanSensor : std::get<fanPolicySensorListPos>(fan))
@@ -334,11 +360,14 @@
pSensors.emplace_back(*fanSensor);
}
- return std::make_unique<AnyOf>(std::get<fanPolicyFanPos>(fan), pSensors);
+ return std::make_unique<AnyOf>(std::get<fanPolicyFanPos>(fan), pSensors,
+ std::move(eepromDevice));
}
// Get a `Fallback` redundancy policy for the fan
-std::unique_ptr<RedundancyPolicy> getFallback(const fanPolicy& fan)
+std::unique_ptr<RedundancyPolicy>
+ getFallback(const fanPolicy& fan,
+ std::unique_ptr<EEPROMDevice> eepromDevice)
{
std::vector<std::reference_wrapper<PresenceSensor>> pSensors;
for (auto& fanSensor : std::get<fanPolicySensorListPos>(fan))
@@ -347,7 +376,8 @@
pSensors.emplace_back(*fanSensor);
}
- return std::make_unique<Fallback>(std::get<fanPolicyFanPos>(fan), pSensors);
+ return std::make_unique<Fallback>(std::get<fanPolicyFanPos>(fan), pSensors,
+ std::move(eepromDevice));
}
} // namespace rpolicy
diff --git a/presence/json_parser.hpp b/presence/json_parser.hpp
index 7dbe115..9fe5645 100644
--- a/presence/json_parser.hpp
+++ b/presence/json_parser.hpp
@@ -37,8 +37,8 @@
using methodHandler =
std::function<std::unique_ptr<PresenceSensor>(size_t, const json&)>;
// Presence redundancy policy handler function
-using rpolicyHandler =
- std::function<std::unique_ptr<RedundancyPolicy>(const fanPolicy&)>;
+using rpolicyHandler = std::function<std::unique_ptr<RedundancyPolicy>(
+ const fanPolicy&, std::unique_ptr<EEPROMDevice>)>;
class JsonConfig
{
@@ -123,11 +123,13 @@
*
* @param[in] rpolicy - policy type to construct
* @param[in] fpolicy - fan policy object
+ * @param[in] eepromDevice - EEPROM device object
*
* @return - The constructed redundancy policy type for the fan
*/
- std::unique_ptr<RedundancyPolicy> getPolicy(const json& rpolicy,
- const fanPolicy& fpolicy);
+ std::unique_ptr<RedundancyPolicy>
+ getPolicy(const json& rpolicy, const fanPolicy& fpolicy,
+ std::unique_ptr<EEPROMDevice> eepromDevice);
};
/**
@@ -167,20 +169,25 @@
* sensors for a fan
*
* @param[in] fan - fan policy object with the presence sensors for the fan
+ * @param[in] eepromDevice - EEPROM device object
*
* @return - An `Anyof` redundancy policy
*/
-std::unique_ptr<RedundancyPolicy> getAnyof(const fanPolicy& fan);
+std::unique_ptr<RedundancyPolicy>
+ getAnyof(const fanPolicy& fan, std::unique_ptr<EEPROMDevice> eepromDevice);
/**
* @brief Create a `Fallback` redundancy policy on the created presence
* sensors for a fan
*
* @param[in] fan - fan policy object with the presence sensors for the fan
+ * @param[in] eepromDevice - EEPROM device object
*
* @return - A `Fallback` redundancy policy
*/
-std::unique_ptr<RedundancyPolicy> getFallback(const fanPolicy& fan);
+std::unique_ptr<RedundancyPolicy>
+ getFallback(const fanPolicy& fan,
+ std::unique_ptr<EEPROMDevice> eepromDevice);
} // namespace rpolicy
diff --git a/presence/rpolicy.hpp b/presence/rpolicy.hpp
index d596dfa..e190029 100644
--- a/presence/rpolicy.hpp
+++ b/presence/rpolicy.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "eeprom_device.hpp"
#include "fan.hpp"
namespace phosphor
@@ -35,8 +36,12 @@
* @brief Construct a new Redundancy Policy.
*
* @param[in] fan - The fan associated with this policy.
+ * @param[in] eeprom - EEPROM device instance
*/
- explicit RedundancyPolicy(const Fan& f) : fan(f)
+ explicit RedundancyPolicy(const Fan& f,
+ std::unique_ptr<EEPROMDevice> eeprom) :
+ fan(f),
+ eepromDevice(std::move(eeprom))
{}
/**
@@ -62,6 +67,12 @@
protected:
/** @brief Fan name and inventory path. */
const Fan& fan;
+
+ /**
+ * @brief Handles binding the EEPROM driver on fan plug
+ * if configured to do so.
+ */
+ std::unique_ptr<EEPROMDevice> eepromDevice;
};
/**