1 #include "gpio_presence.hpp"
2 
3 #include "xyz/openbmc_project/Common/error.hpp"
4 
5 #include <fcntl.h>
6 #include <libevdev/libevdev.h>
7 
8 #include <phosphor-logging/elog-errors.hpp>
9 #include <phosphor-logging/elog.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 
12 #include <fstream>
13 
14 namespace phosphor
15 {
16 namespace gpio
17 {
18 namespace presence
19 {
20 
21 using namespace phosphor::logging;
22 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
23 
24 constexpr auto INVENTORY_PATH = "/xyz/openbmc_project/inventory";
25 constexpr auto INVENTORY_INTF = "xyz.openbmc_project.Inventory.Manager";
26 
27 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
28 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
29 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
30 
31 std::string getService(const std::string& path, const std::string& interface,
32                        sdbusplus::bus_t& bus)
33 {
34     auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
35                                           MAPPER_INTERFACE, "GetObject");
36 
37     mapperCall.append(path);
38     mapperCall.append(std::vector<std::string>({interface}));
39 
40     std::map<std::string, std::vector<std::string>> mapperResponse;
41     try
42     {
43         auto mapperResponseMsg = bus.call(mapperCall);
44         mapperResponseMsg.read(mapperResponse);
45     }
46     catch (const sdbusplus::exception_t& e)
47     {
48         lg2::error(
49             "Error in mapper call to get service name, path: {PATH}, interface: {INTERFACE}, error: {ERROR}",
50             "PATH", path, "INTERFACE", interface, "ERROR", e);
51         elog<InternalFailure>();
52     }
53 
54     return mapperResponse.begin()->first;
55 }
56 
57 void Presence::determinePresence()
58 {
59     auto present = false;
60     auto value = static_cast<int>(0);
61     auto fetch_rc =
62         libevdev_fetch_event_value(devicePtr.get(), EV_KEY, key, &value);
63     if (0 == fetch_rc)
64     {
65         lg2::error("Device does not support event type, key: {KEYCODE}",
66                    "KEYCODE", key);
67         elog<InternalFailure>();
68         return;
69     }
70     if (value > 0)
71     {
72         present = true;
73     }
74 
75     updateInventory(present);
76 }
77 
78 // Callback handler when there is an activity on the FD
79 int Presence::processEvents(sd_event_source*, int, uint32_t, void* userData)
80 {
81     auto presence = static_cast<Presence*>(userData);
82 
83     presence->analyzeEvent();
84     return 0;
85 }
86 
87 // Analyzes the GPIO event
88 void Presence::analyzeEvent()
89 {
90     // Data returned
91     struct input_event ev
92     {};
93     int rc = 0;
94 
95     // While testing, observed that not having a loop here was leading
96     // into events being missed.
97     while (rc >= 0)
98     {
99         // Wait until no more events are available on the device.
100         rc = libevdev_next_event(devicePtr.get(), LIBEVDEV_READ_FLAG_NORMAL,
101                                  &ev);
102         if (rc < 0)
103         {
104             // There was an error waiting for events, mostly that there are no
105             // events to be read.. So continue waiting...
106             return;
107         }
108 
109         if (rc == LIBEVDEV_READ_STATUS_SUCCESS)
110         {
111             if (ev.type == EV_SYN && ev.code == SYN_REPORT)
112             {
113                 continue;
114             }
115             else if (ev.code == key)
116             {
117                 auto present = false;
118                 if (ev.value > 0)
119                 {
120                     present = true;
121                     std::this_thread::sleep_for(
122                         std::chrono::milliseconds(delay));
123                     bindOrUnbindDrivers(present);
124                     updateInventory(present);
125                 }
126                 else
127                 {
128                     updateInventory(present);
129                     bindOrUnbindDrivers(present);
130                 }
131             }
132         }
133     }
134 
135     return;
136 }
137 
138 Presence::ObjectMap Presence::getObjectMap(bool present)
139 {
140     ObjectMap invObj;
141     InterfaceMap invIntf;
142     PropertyMap invProp;
143 
144     invProp.emplace("Present", present);
145     invProp.emplace("PrettyName", name);
146     invIntf.emplace("xyz.openbmc_project.Inventory.Item", std::move(invProp));
147     // Add any extra interfaces we want to associate with the inventory item
148     for (auto& iface : ifaces)
149     {
150         invIntf.emplace(iface, PropertyMap());
151     }
152     invObj.emplace(std::move(inventory), std::move(invIntf));
153 
154     return invObj;
155 }
156 
157 void Presence::updateInventory(bool present)
158 {
159     ObjectMap invObj = getObjectMap(present);
160 
161     lg2::info(
162         "Updating inventory present property value to {PRESENT}, path: {PATH}",
163         "PRESENT", present, "PATH", inventory);
164 
165     auto invService = getService(INVENTORY_PATH, INVENTORY_INTF, bus);
166 
167     // Update inventory
168     auto invMsg = bus.new_method_call(invService.c_str(), INVENTORY_PATH,
169                                       INVENTORY_INTF, "Notify");
170     invMsg.append(std::move(invObj));
171     try
172     {
173         auto invMgrResponseMsg = bus.call(invMsg);
174     }
175     catch (const sdbusplus::exception_t& e)
176     {
177         lg2::error(
178             "Error in inventory manager call to update inventory: {ERROR}",
179             "ERROR", e);
180         elog<InternalFailure>();
181     }
182 }
183 
184 void Presence::bindOrUnbindDrivers(bool present)
185 {
186     auto action = (present) ? "bind" : "unbind";
187 
188     for (auto& driver : drivers)
189     {
190         auto path = std::get<pathField>(driver) / action;
191         auto device = std::get<deviceField>(driver);
192 
193         if (present)
194         {
195             lg2::info("Binding a {DEVICE} driver: {PATH}", "DEVICE", device,
196                       "PATH", path);
197         }
198         else
199         {
200             lg2::info("Unbinding a {DEVICE} driver: {PATH}", "DEVICE", device,
201                       "PATH", path);
202         }
203 
204         std::ofstream file;
205 
206         file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
207                         std::ofstream::eofbit);
208 
209         try
210         {
211             file.open(path);
212             file << device;
213             file.close();
214         }
215         catch (const std::exception& e)
216         {
217             lg2::error(
218                 "Failed binding or unbinding a {DEVICE} after a card was removed or added, path: {PATH}, error: {ERROR}",
219                 "DEVICE", device, "PATH", path, "ERROR", e);
220         }
221     }
222 }
223 
224 } // namespace presence
225 } // namespace gpio
226 } // namespace phosphor
227