1 /**
2  * Copyright © 2019 Facebook
3  * Copyright © 2023 9elements GmbH
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #include "gpio_presence.hpp"
19 
20 #include "xyz/openbmc_project/Common/error.hpp"
21 
22 #include <phosphor-logging/elog-errors.hpp>
23 #include <phosphor-logging/elog.hpp>
24 #include <phosphor-logging/lg2.hpp>
25 #include <sdbusplus/bus.hpp>
26 
27 namespace phosphor
28 {
29 namespace gpio
30 {
31 
32 using namespace phosphor::logging;
33 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
34 
35 constexpr auto INVENTORY_PATH = "/xyz/openbmc_project/inventory";
36 constexpr auto INVENTORY_INTF = "xyz.openbmc_project.Inventory.Manager";
37 
38 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
39 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
40 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
41 
getService(const std::string & path,const std::string & interface,sdbusplus::bus_t & bus)42 std::string getService(const std::string& path, const std::string& interface,
43                        sdbusplus::bus_t& bus)
44 {
45     auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
46                                           MAPPER_INTERFACE, "GetObject");
47 
48     mapperCall.append(path);
49     mapperCall.append(std::vector<std::string>({interface}));
50 
51     std::map<std::string, std::vector<std::string>> mapperResponse;
52     try
53     {
54         auto mapperResponseMsg = bus.call(mapperCall);
55         mapperResponseMsg.read(mapperResponse);
56     }
57     catch (const sdbusplus::exception_t& e)
58     {
59         lg2::error(
60             "Error in mapper call to get service name, path: {PATH}, interface: {INTERFACE}, error: {ERROR}",
61             "PATH", path, "INTERFACE", interface, "ERROR", e);
62         elog<InternalFailure>();
63     }
64 
65     return mapperResponse.begin()->first;
66 }
67 
getObjectMap(bool present)68 GpioPresence::ObjectMap GpioPresence::getObjectMap(bool present)
69 {
70     ObjectMap invObj;
71     InterfaceMap invIntf;
72     PropertyMap invProp;
73 
74     invProp.emplace("Present", present);
75     invProp.emplace("PrettyName", name);
76     invIntf.emplace("xyz.openbmc_project.Inventory.Item", std::move(invProp));
77     // Add any extra interfaces we want to associate with the inventory item
78     for (auto& iface : interfaces)
79     {
80         invIntf.emplace(iface, PropertyMap());
81     }
82     invObj.emplace(std::move(inventory), std::move(invIntf));
83 
84     return invObj;
85 }
86 
updateInventory(bool present)87 void GpioPresence::updateInventory(bool present)
88 {
89     ObjectMap invObj = getObjectMap(present);
90 
91     lg2::info(
92         "Updating inventory present property value to {PRESENT}, path: {PATH}",
93         "PRESENT", present, "PATH", inventory);
94 
95     auto bus = sdbusplus::bus::new_default();
96     auto invService = getService(INVENTORY_PATH, INVENTORY_INTF, bus);
97 
98     // Update inventory
99     auto invMsg = bus.new_method_call(invService.c_str(), INVENTORY_PATH,
100                                       INVENTORY_INTF, "Notify");
101     invMsg.append(std::move(invObj));
102     try
103     {
104         auto invMgrResponseMsg = bus.call(invMsg);
105     }
106     catch (const sdbusplus::exception_t& e)
107     {
108         lg2::error(
109             "Error in inventory manager call to update inventory: {ERROR}",
110             "ERROR", e);
111         elog<InternalFailure>();
112     }
113 }
114 
scheduleEventHandler()115 void GpioPresence::scheduleEventHandler()
116 {
117     std::string gpio = std::string(gpioLineMsg);
118 
119     gpioEventDescriptor.async_wait(
120         boost::asio::posix::stream_descriptor::wait_read,
121         [this, gpio](const boost::system::error_code& ec) {
122         if (ec == boost::asio::error::operation_aborted)
123         {
124             // we were cancelled
125             return;
126         }
127         if (ec)
128         {
129             lg2::error("{GPIO} event handler error: {ERROR}", "GPIO", gpio,
130                        "ERROR", ec.message());
131             return;
132         }
133         gpioEventHandler();
134     });
135 }
136 
cancelEventHandler()137 void GpioPresence::cancelEventHandler()
138 {
139     gpioEventDescriptor.cancel();
140 }
141 
gpioEventHandler()142 void GpioPresence::gpioEventHandler()
143 {
144     gpiod_line_event gpioLineEvent;
145 
146     if (gpiod_line_event_read_fd(gpioEventDescriptor.native_handle(),
147                                  &gpioLineEvent) < 0)
148     {
149         lg2::error("Failed to read {GPIO} from fd", "GPIO", gpioLineMsg);
150         return;
151     }
152 
153     if (gpioLineEvent.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
154     {
155         lg2::info("{GPIO} Asserted", "GPIO", gpioLineMsg);
156     }
157     else
158     {
159         lg2::info("{GPIO} Deasserted", "GPIO", gpioLineMsg);
160     }
161     updateInventory(gpioLineEvent.event_type == GPIOD_LINE_EVENT_RISING_EDGE);
162 
163     /* Schedule a wait event */
164     scheduleEventHandler();
165 }
166 
requestGPIOEvents()167 int GpioPresence::requestGPIOEvents()
168 {
169     std::string flags;
170 
171     /* Request an event to monitor for respected gpio line */
172     if (gpiod_line_request(gpioLine, &gpioConfig, 0) < 0)
173     {
174         lg2::error("Failed to request {GPIO}: {ERRNO}", "GPIO", gpioLineMsg,
175                    "ERRNO", errno);
176         return -1;
177     }
178 
179     int gpioLineFd = gpiod_line_event_get_fd(gpioLine);
180     if (gpioLineFd < 0)
181     {
182         lg2::error("Failed to get fd for {GPIO}", "GPIO", gpioLineMsg);
183         return -1;
184     }
185 
186     if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE)
187     {
188         flags += " Bias DISABLE";
189     }
190     else if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
191     {
192         flags += " Bias PULL_UP";
193     }
194     else if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
195     {
196         flags += " Bias PULL_DOWN";
197     }
198 
199     if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
200     {
201         flags += " ActiveLow";
202     }
203 
204     if (!flags.empty())
205     {
206         flags = "[" + flags + "]";
207     }
208 
209     lg2::info("{GPIO} {FLAGS} monitoring started", "GPIO", gpioLineMsg, "FLAGS",
210               flags);
211 
212     /* Assign line fd to descriptor for monitoring */
213     gpioEventDescriptor.assign(gpioLineFd);
214 
215     updateInventory(gpiod_line_get_value(gpioLine));
216 
217     /* Schedule a wait event */
218     scheduleEventHandler();
219 
220     return 0;
221 }
222 } // namespace gpio
223 } // namespace phosphor
224