1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright (C) 2018 IBM Corp.
3 
4 #include "config.h"
5 
6 #include "hiomap.hpp"
7 
8 #include <endian.h>
9 #include <host-ipmid/ipmid-api.h>
10 #include <string.h>
11 #include <systemd/sd-bus.h>
12 
13 #include <fstream>
14 #include <functional>
15 #include <host-ipmid/ipmid-host-cmd-utils.hpp>
16 #include <host-ipmid/ipmid-host-cmd.hpp>
17 #include <iostream>
18 #include <phosphor-logging/log.hpp>
19 #include <sdbusplus/bus.hpp>
20 #include <sdbusplus/bus/match.hpp>
21 #include <sdbusplus/exception.hpp>
22 
23 using namespace sdbusplus;
24 using namespace phosphor::host::command;
25 
26 static void register_openpower_hiomap_commands() __attribute__((constructor));
27 
28 namespace openpower
29 {
30 namespace flash
31 {
32 constexpr auto BMC_EVENT_DAEMON_READY = 1 << 7;
33 constexpr auto BMC_EVENT_FLASH_CTRL_LOST = 1 << 6;
34 constexpr auto BMC_EVENT_WINDOW_RESET = 1 << 1;
35 constexpr auto BMC_EVENT_PROTOCOL_RESET = 1 << 0;
36 
37 constexpr auto IPMI_CMD_HIOMAP_EVENT = 0x0f;
38 
39 constexpr auto HIOMAPD_SERVICE = "xyz.openbmc_project.Hiomapd";
40 constexpr auto HIOMAPD_OBJECT = "/xyz/openbmc_project/Hiomapd";
41 constexpr auto HIOMAPD_IFACE = "xyz.openbmc_project.Hiomapd.Protocol";
42 constexpr auto HIOMAPD_IFACE_V2 = "xyz.openbmc_project.Hiomapd.Protocol.V2";
43 
44 constexpr auto DBUS_IFACE_PROPERTIES = "org.freedesktop.DBus.Properties";
45 
46 struct hiomap
47 {
48     bus::bus *bus;
49 
50     /* Signals */
51     bus::match::match *properties;
52     bus::match::match *window_reset;
53     bus::match::match *bmc_reboot;
54 
55     /* Protocol state */
56     std::map<std::string, int> event_lookup;
57     uint8_t bmc_events;
58 };
59 
60 /* TODO: Replace get/put with packed structs and direct assignment */
61 template <typename T> static inline T get(void *buf)
62 {
63     T t;
64     memcpy(&t, buf, sizeof(t));
65     return t;
66 }
67 
68 template <typename T> static inline void put(void *buf, T &&t)
69 {
70     memcpy(buf, &t, sizeof(t));
71 }
72 
73 typedef ipmi_ret_t (*hiomap_command)(ipmi_request_t req, ipmi_response_t resp,
74                                      ipmi_data_len_t data_len,
75                                      ipmi_context_t context);
76 
77 struct errno_cc_entry
78 {
79     int err;
80     int cc;
81 };
82 
83 static const errno_cc_entry errno_cc_map[] = {
84     {0, IPMI_CC_OK},
85     {EBUSY, IPMI_CC_BUSY},
86     {ENOTSUP, IPMI_CC_INVALID},
87     {ETIMEDOUT, 0xc3}, /* FIXME: Replace when defined in ipmid-api.h */
88     {ENOSPC, 0xc4},    /* FIXME: Replace when defined in ipmid-api.h */
89     {EINVAL, IPMI_CC_PARM_OUT_OF_RANGE},
90     {ENODEV, IPMI_CC_SENSOR_INVALID},
91     {EPERM, IPMI_CC_INSUFFICIENT_PRIVILEGE},
92     {EACCES, IPMI_CC_INSUFFICIENT_PRIVILEGE},
93     {-1, IPMI_CC_UNSPECIFIED_ERROR},
94 };
95 
96 static int hiomap_xlate_errno(int err)
97 {
98     const errno_cc_entry *entry = &errno_cc_map[0];
99 
100     while (!(entry->err == err || entry->err == -1))
101     {
102         entry++;
103     }
104 
105     return entry->cc;
106 }
107 
108 static void ipmi_hiomap_event_response(IpmiCmdData cmd, bool status)
109 {
110     using namespace phosphor::logging;
111 
112     if (!status)
113     {
114         log<level::ERR>("Failed to deliver host command",
115                         entry("SEL_COMMAND=%x:%x", cmd.first, cmd.second));
116     }
117 }
118 
119 static int hiomap_handle_property_update(struct hiomap *ctx,
120                                          sdbusplus::message::message &msg)
121 {
122     std::map<std::string, sdbusplus::message::variant<bool>> msgData;
123 
124     std::string iface;
125     msg.read(iface, msgData);
126 
127     for (auto const &x : msgData)
128     {
129         if (!ctx->event_lookup.count(x.first))
130         {
131             /* Unsupported event? */
132             continue;
133         }
134 
135         uint8_t mask = ctx->event_lookup[x.first];
136         auto value = sdbusplus::message::variant_ns::get<bool>(x.second);
137 
138         if (value)
139         {
140             ctx->bmc_events |= mask;
141         }
142         else
143         {
144             ctx->bmc_events &= ~mask;
145         }
146     }
147 
148     auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events);
149 
150     ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response));
151 
152     return 0;
153 }
154 
155 static bus::match::match hiomap_match_properties(struct hiomap *ctx)
156 {
157     auto properties =
158         bus::match::rules::propertiesChanged(HIOMAPD_OBJECT, HIOMAPD_IFACE_V2);
159 
160     bus::match::match match(
161         *ctx->bus, properties,
162         std::bind(hiomap_handle_property_update, ctx, std::placeholders::_1));
163 
164     return match;
165 }
166 
167 static int hiomap_handle_signal_v2(struct hiomap *ctx, const char *name)
168 {
169     ctx->bmc_events |= ctx->event_lookup[name];
170 
171     auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events);
172 
173     ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response));
174 
175     return 0;
176 }
177 
178 static bus::match::match hiomap_match_signal_v2(struct hiomap *ctx,
179                                                 const char *name)
180 {
181     using namespace bus::match;
182 
183     auto signals = rules::type::signal() + rules::path(HIOMAPD_OBJECT) +
184                    rules::interface(HIOMAPD_IFACE_V2) + rules::member(name);
185 
186     bus::match::match match(*ctx->bus, signals,
187                             std::bind(hiomap_handle_signal_v2, ctx, name));
188 
189     return match;
190 }
191 
192 static ipmi_ret_t hiomap_reset(ipmi_request_t request, ipmi_response_t response,
193                                ipmi_data_len_t data_len, ipmi_context_t context)
194 {
195     struct hiomap *ctx = static_cast<struct hiomap *>(context);
196 
197     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
198                                        HIOMAPD_IFACE, "Reset");
199     try
200     {
201         ctx->bus->call(m);
202 
203         *data_len = 0;
204     }
205     catch (const exception::SdBusError &e)
206     {
207         return hiomap_xlate_errno(e.get_errno());
208     }
209 
210     return IPMI_CC_OK;
211 }
212 
213 static ipmi_ret_t hiomap_get_info(ipmi_request_t request,
214                                   ipmi_response_t response,
215                                   ipmi_data_len_t data_len,
216                                   ipmi_context_t context)
217 {
218     struct hiomap *ctx = static_cast<struct hiomap *>(context);
219 
220     if (*data_len < 1)
221     {
222         return IPMI_CC_REQ_DATA_LEN_INVALID;
223     }
224 
225     uint8_t *reqdata = (uint8_t *)request;
226     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
227                                        HIOMAPD_IFACE, "GetInfo");
228     m.append(reqdata[0]);
229 
230     try
231     {
232         auto reply = ctx->bus->call(m);
233 
234         uint8_t version;
235         uint8_t blockSizeShift;
236         uint16_t timeout;
237         reply.read(version, blockSizeShift, timeout);
238 
239         uint8_t *respdata = (uint8_t *)response;
240 
241         /* FIXME: Assumes v2! */
242         put(&respdata[0], version);
243         put(&respdata[1], blockSizeShift);
244         put(&respdata[2], htole16(timeout));
245 
246         *data_len = 4;
247     }
248     catch (const exception::SdBusError &e)
249     {
250         return hiomap_xlate_errno(e.get_errno());
251     }
252 
253     return IPMI_CC_OK;
254 }
255 
256 static ipmi_ret_t hiomap_get_flash_info(ipmi_request_t request,
257                                         ipmi_response_t response,
258                                         ipmi_data_len_t data_len,
259                                         ipmi_context_t context)
260 {
261     struct hiomap *ctx = static_cast<struct hiomap *>(context);
262 
263     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
264                                        HIOMAPD_IFACE_V2, "GetFlashInfo");
265     try
266     {
267         auto reply = ctx->bus->call(m);
268 
269         uint16_t flashSize, eraseSize;
270         reply.read(flashSize, eraseSize);
271 
272         uint8_t *respdata = (uint8_t *)response;
273         put(&respdata[0], htole16(flashSize));
274         put(&respdata[2], htole16(eraseSize));
275 
276         *data_len = 4;
277     }
278     catch (const exception::SdBusError &e)
279     {
280         return hiomap_xlate_errno(e.get_errno());
281     }
282 
283     return IPMI_CC_OK;
284 }
285 
286 static ipmi_ret_t hiomap_create_window(struct hiomap *ctx, bool ro,
287                                        ipmi_request_t request,
288                                        ipmi_response_t response,
289                                        ipmi_data_len_t data_len)
290 {
291     if (*data_len < 4)
292     {
293         return IPMI_CC_REQ_DATA_LEN_INVALID;
294     }
295 
296     uint8_t *reqdata = (uint8_t *)request;
297     auto windowType = ro ? "CreateReadWindow" : "CreateWriteWindow";
298 
299     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
300                                        HIOMAPD_IFACE_V2, windowType);
301     m.append(le16toh(get<uint16_t>(&reqdata[0])));
302     m.append(le16toh(get<uint16_t>(&reqdata[2])));
303 
304     try
305     {
306         auto reply = ctx->bus->call(m);
307 
308         uint16_t lpcAddress, size, offset;
309         reply.read(lpcAddress, size, offset);
310 
311         uint8_t *respdata = (uint8_t *)response;
312 
313         /* FIXME: Assumes v2! */
314         put(&respdata[0], htole16(lpcAddress));
315         put(&respdata[2], htole16(size));
316         put(&respdata[4], htole16(offset));
317 
318         *data_len = 6;
319     }
320     catch (const exception::SdBusError &e)
321     {
322         return hiomap_xlate_errno(e.get_errno());
323     }
324 
325     return IPMI_CC_OK;
326 }
327 
328 static ipmi_ret_t hiomap_create_read_window(ipmi_request_t request,
329                                             ipmi_response_t response,
330                                             ipmi_data_len_t data_len,
331                                             ipmi_context_t context)
332 {
333     struct hiomap *ctx = static_cast<struct hiomap *>(context);
334 
335     return hiomap_create_window(ctx, true, request, response, data_len);
336 }
337 
338 static ipmi_ret_t hiomap_create_write_window(ipmi_request_t request,
339                                              ipmi_response_t response,
340                                              ipmi_data_len_t data_len,
341                                              ipmi_context_t context)
342 {
343     struct hiomap *ctx = static_cast<struct hiomap *>(context);
344 
345     return hiomap_create_window(ctx, false, request, response, data_len);
346 }
347 
348 static ipmi_ret_t hiomap_close_window(ipmi_request_t request,
349                                       ipmi_response_t response,
350                                       ipmi_data_len_t data_len,
351                                       ipmi_context_t context)
352 {
353     struct hiomap *ctx = static_cast<struct hiomap *>(context);
354 
355     if (*data_len < 1)
356     {
357         return IPMI_CC_REQ_DATA_LEN_INVALID;
358     }
359 
360     uint8_t *reqdata = (uint8_t *)request;
361     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
362                                        HIOMAPD_IFACE_V2, "CloseWindow");
363     m.append(reqdata[0]);
364 
365     try
366     {
367         auto reply = ctx->bus->call(m);
368 
369         *data_len = 0;
370     }
371     catch (const exception::SdBusError &e)
372     {
373         return hiomap_xlate_errno(e.get_errno());
374     }
375 
376     return IPMI_CC_OK;
377 }
378 
379 static const hiomap_command hiomap_commands[] = {
380     [0] = NULL, /* 0 is an invalid command ID */
381     [1] = hiomap_reset,
382     [2] = hiomap_get_info,
383     [3] = hiomap_get_flash_info,
384     [4] = hiomap_create_read_window,
385     [5] = hiomap_close_window,
386     [6] = hiomap_create_write_window,
387 };
388 
389 /* FIXME: Define this in the "right" place, wherever that is */
390 /* FIXME: Double evaluation */
391 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
392 
393 static ipmi_ret_t hiomap_dispatch(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
394                                   ipmi_request_t request,
395                                   ipmi_response_t response,
396                                   ipmi_data_len_t data_len,
397                                   ipmi_context_t context)
398 {
399     struct hiomap *ctx = static_cast<struct hiomap *>(context);
400 
401     if (*data_len < 2)
402     {
403         *data_len = 0;
404         return IPMI_CC_REQ_DATA_LEN_INVALID;
405     }
406 
407     uint8_t *ipmi_req = (uint8_t *)request;
408     uint8_t *ipmi_resp = (uint8_t *)response;
409     uint8_t hiomap_cmd = ipmi_req[0];
410 
411     if (hiomap_cmd == 0 || hiomap_cmd > ARRAY_SIZE(hiomap_commands) - 1)
412     {
413         *data_len = 0;
414         return IPMI_CC_PARM_OUT_OF_RANGE;
415     }
416     uint8_t *flash_req = ipmi_req + 2;
417     size_t flash_len = *data_len - 2;
418     uint8_t *flash_resp = ipmi_resp + 2;
419 
420     ipmi_ret_t cc =
421         hiomap_commands[hiomap_cmd](flash_req, flash_resp, &flash_len, context);
422     if (cc != IPMI_CC_OK)
423     {
424         *data_len = 0;
425         return cc;
426     }
427 
428     /* Populate the response command and sequence */
429     ipmi_resp[0] = hiomap_cmd;
430     ipmi_resp[1] = ipmi_req[1];
431 
432     *data_len = flash_len + 2;
433 
434     return cc;
435 }
436 } // namespace flash
437 } // namespace openpower
438 
439 static void register_openpower_hiomap_commands()
440 {
441     using namespace openpower::flash;
442 
443     /* FIXME: Clean this up? Can we unregister? */
444     struct hiomap *ctx = new hiomap();
445 
446     /* Initialise mapping from signal and property names to status bit */
447     ctx->event_lookup["DaemonReady"] = BMC_EVENT_DAEMON_READY;
448     ctx->event_lookup["FlashControlLost"] = BMC_EVENT_FLASH_CTRL_LOST;
449     ctx->event_lookup["WindowReset"] = BMC_EVENT_WINDOW_RESET;
450     ctx->event_lookup["ProtocolReset"] = BMC_EVENT_PROTOCOL_RESET;
451 
452     ctx->bus = new bus::bus(ipmid_get_sd_bus_connection());
453 
454     /* Initialise signal handling */
455 
456     /*
457      * Can't use temporaries here because that causes SEGFAULTs due to slot
458      * destruction (!?), so enjoy the weird wrapping.
459      */
460     ctx->properties =
461         new bus::match::match(std::move(hiomap_match_properties(ctx)));
462     ctx->bmc_reboot = new bus::match::match(
463         std::move(hiomap_match_signal_v2(ctx, "ProtocolReset")));
464     ctx->window_reset = new bus::match::match(
465         std::move(hiomap_match_signal_v2(ctx, "WindowReset")));
466 
467     ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_HIOMAP, ctx,
468                            openpower::flash::hiomap_dispatch, SYSTEM_INTERFACE);
469 }
470