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/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