1 #pragma once
2 
3 #include "sdeventplus.hpp"
4 
5 #include <phosphor-logging/lg2.hpp>
6 #include <sdeventplus/clock.hpp>
7 #include <sdeventplus/utility/timer.hpp>
8 
9 #include <filesystem>
10 #include <fstream>
11 #include <string>
12 
13 namespace phosphor::fan::presence
14 {
15 using namespace phosphor::fan::util;
16 
17 /**
18  * @class EEPROMDevice
19  *
20  * Provides an API to bind an EEPROM driver to a device, after waiting
21  * a configurable amount of time in case the device needs time
22  * to initialize after being plugged into a system.
23  */
24 class EEPROMDevice
25 {
26   public:
27     EEPROMDevice() = delete;
28     ~EEPROMDevice() = default;
29     EEPROMDevice(const EEPROMDevice&) = delete;
30     EEPROMDevice& operator=(const EEPROMDevice&) = delete;
31     EEPROMDevice(EEPROMDevice&&) = delete;
32     EEPROMDevice& operator=(EEPROMDevice&&) = delete;
33 
34     /**
35      * @brief Constructor
36      * @param[in] address - The bus-address string as used by drivers
37      *                      in sysfs.
38      * @param[in] driver - The I2C driver name in sysfs
39      * @param[in] bindDelayInMS - The time in milliseconds to wait
40      *            before actually doing the bind.
41      */
42     EEPROMDevice(const std::string& address, const std::string& driver,
43                  size_t bindDelayInMS) :
44         address(address),
45         path(baseDriverPath / driver), bindDelay(bindDelayInMS),
46         timer(SDEventPlus::getEvent(),
47               std::bind(std::mem_fn(&EEPROMDevice::bindTimerExpired), this))
48     {}
49 
50     /**
51      * @brief Kicks off the timer to do the actual bind
52      */
53     void bind()
54     {
55         timer.restartOnce(std::chrono::milliseconds{bindDelay});
56     }
57 
58     /**
59      * @brief Stops the bind timer if running and unbinds the device
60      */
61     void unbind()
62     {
63         if (timer.isEnabled())
64         {
65             timer.setEnabled(false);
66         }
67 
68         unbindDevice();
69     }
70 
71   private:
72     /**
73      * @brief When the bind timer expires it will bind the device.
74      */
75     void bindTimerExpired() const
76     {
77         unbindDevice();
78 
79         auto bindPath = path / "bind";
80         std::ofstream bind{bindPath};
81         if (bind.good())
82         {
83             lg2::info("Binding fan EEPROM device with address {ADDRESS}",
84                       "ADDRESS", address);
85             bind << address;
86         }
87 
88         if (bind.fail())
89         {
90             lg2::error("Error while binding fan EEPROM device with path {PATH}"
91                        " and address {ADDR}",
92                        "PATH", bindPath, "ADDR", address);
93         }
94     }
95 
96     /**
97      * @brief Unbinds the device.
98      */
99     void unbindDevice() const
100     {
101         auto devicePath = path / address;
102         if (!std::filesystem::exists(devicePath))
103         {
104             return;
105         }
106 
107         auto unbindPath = path / "unbind";
108         std::ofstream unbind{unbindPath};
109         if (unbind.good())
110         {
111             unbind << address;
112         }
113 
114         if (unbind.fail())
115         {
116             lg2::error("Error while unbinding fan EEPROM device with path"
117                        " {PATH} and address {ADDR}",
118                        "PATH", unbindPath, "ADDR", address);
119         }
120     }
121 
122     /** @brief The base I2C drivers directory in sysfs */
123     const std::filesystem::path baseDriverPath{"/sys/bus/i2c/drivers"};
124 
125     /**
126      * @brief The address string with the i2c bus and address.
127      * Example: '32-0050'
128      */
129     const std::string address;
130 
131     /** @brief The path to the driver dir, like /sys/bus/i2c/drivers/at24 */
132     const std::filesystem::path path;
133 
134     /** @brief Number of milliseconds to delay to actually do the bind. */
135     const size_t bindDelay{};
136 
137     /** @brief The timer to do the delay with */
138     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> timer;
139 };
140 
141 } // namespace phosphor::fan::presence
142