1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include <host-ipmid/ipmid-api.h>
18 
19 #include <array>
20 #include <iostream>
21 #include <oemcommands.hpp>
22 #include <phosphor-ipmi-host/utils.hpp>
23 #include <phosphor-logging/log.hpp>
24 #include <sdbusplus/bus.hpp>
25 #include <sstream>
26 #include <string>
27 #include <vector>
28 #include <xyz/openbmc_project/Common/error.hpp>
29 
30 namespace ipmi
31 {
32 static void register_netfn_firmware_functions() __attribute__((constructor));
33 sdbusplus::bus::bus _dbus(ipmid_get_sd_bus_connection()); // from ipmid-api.h
34 static constexpr size_t maxFruStringLength = 0x3F;
35 
36 // return code: 0 successful
37 int8_t getChassisSerialNumber(sdbusplus::bus::bus& bus, std::string& serial)
38 {
39     std::string objpath = "/xyz/openbmc_project/FruDevice";
40     std::string intf = "xyz.openbmc_project.FruDeviceManager";
41     std::string service = getService(bus, intf, objpath);
42     ObjectValueTree valueTree = getManagedObjects(bus, service, "/");
43     if (valueTree.empty())
44     {
45         phosphor::logging::log<phosphor::logging::level::ERR>(
46             "No object implements interface",
47             phosphor::logging::entry("INTF=%s", intf.c_str()));
48         return -1;
49     }
50 
51     for (auto& item : valueTree)
52     {
53         auto interface = item.second.find("xyz.openbmc_project.FruDevice");
54         if (interface == item.second.end())
55         {
56             continue;
57         }
58 
59         auto property = interface->second.find("CHASSIS_SERIAL_NUMBER");
60         if (property == interface->second.end())
61         {
62             continue;
63         }
64 
65         try
66         {
67             Value variant = property->second;
68             std::string& result = variant.get<std::string>();
69             if (result.size() > maxFruStringLength)
70             {
71                 phosphor::logging::log<phosphor::logging::level::ERR>(
72                     "FRU serial number exceed maximum length");
73 
74                 return -1;
75             }
76             else
77             {
78                 serial = result;
79             }
80             return 0;
81         }
82         catch (mapbox::util::bad_variant_access& e)
83         {
84             std::cerr << e.what() << std::endl;
85             return -1;
86         }
87     }
88     return -1;
89 }
90 ipmi_ret_t ipmiOEMWildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
91                            ipmi_request_t request, ipmi_response_t response,
92                            ipmi_data_len_t data_len, ipmi_context_t context)
93 {
94     phosphor::logging::log<phosphor::logging::level::INFO>(
95         "Handling OEM WILDCARD", phosphor::logging::entry("NETFN=%x", netfn),
96         phosphor::logging::entry("CMD=%x", cmd));
97 
98     // Status code.
99     ipmi_ret_t rc = IPMI_CC_INVALID;
100     *data_len = 0;
101     return rc;
102 }
103 
104 // Returns the Chassis Identifier (serial #)
105 ipmi_ret_t ipmiOEMGetChassisIdentifier(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
106                                        ipmi_request_t request,
107                                        ipmi_response_t response,
108                                        ipmi_data_len_t data_len,
109                                        ipmi_context_t context)
110 {
111     std::string serial;
112     if (*data_len != 0) // invalid request if there are extra parameters
113     {
114         return IPMI_CC_REQ_DATA_LEN_INVALID;
115     }
116     if (getChassisSerialNumber(_dbus, serial) == 0)
117     {
118         *data_len = serial.size(); // length will never exceed response length
119                                    // as it is checked in getChassisSerialNumber
120         char* resp = static_cast<char*>(response);
121         serial.copy(resp, *data_len);
122         return IPMI_CC_OK;
123     }
124     else
125     {
126         *data_len = 0;
127         return IPMI_CC_RESPONSE_ERROR;
128     }
129 }
130 
131 ipmi_ret_t ipmiOEMSetSystemGUID(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
132                                 ipmi_request_t request,
133                                 ipmi_response_t response,
134                                 ipmi_data_len_t data_len,
135                                 ipmi_context_t context)
136 {
137     static constexpr size_t safeBufferLength = 50;
138     char buf[safeBufferLength] = {0};
139     GUIDData* Data = reinterpret_cast<GUIDData*>(request);
140 
141     if (*data_len != sizeof(GUIDData)) // 16bytes
142     {
143         *data_len = 0;
144         return IPMI_CC_REQ_DATA_LEN_INVALID;
145     }
146 
147     *data_len = 0;
148 
149     snprintf(
150         buf, safeBufferLength,
151         "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
152         Data->timeLow4, Data->timeLow3, Data->timeLow2, Data->timeLow1,
153         Data->timeMid2, Data->timeMid1, Data->timeHigh2, Data->timeHigh1,
154         Data->clock2, Data->clock1, Data->node6, Data->node5, Data->node4,
155         Data->node3, Data->node2, Data->node1);
156     // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
157     std::string guid = buf;
158     phosphor::logging::log<phosphor::logging::level::INFO>(
159         "Set System GUID", phosphor::logging::entry("GUID=%s", guid.c_str()));
160 
161     std::string objpath = "/xyz/openbmc_project/control/host0/systemGUID";
162     std::string intf = "xyz.openbmc_project.Common.UUID";
163     std::string service = getService(_dbus, intf, objpath);
164     setDbusProperty(_dbus, service, objpath, intf, "UUID", guid);
165     return IPMI_CC_OK;
166 }
167 
168 ipmi_ret_t ipmiOEMSetBIOSID(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
169                             ipmi_request_t request, ipmi_response_t response,
170                             ipmi_data_len_t dataLen, ipmi_context_t context)
171 {
172     DeviceInfo* data = reinterpret_cast<DeviceInfo*>(request);
173 
174     if ((*dataLen < 2) || (*dataLen != (1 + data->biosIdLength)))
175     {
176         phosphor::logging::log<phosphor::logging::level::ERR>(
177             "Invalid parameter", phosphor::logging::entry("LEN=%d", *dataLen),
178             phosphor::logging::entry("BIOSIDLEN=%d", data->biosIdLength));
179 
180         *dataLen = 0;
181         return IPMI_CC_REQ_DATA_LEN_INVALID;
182     }
183     std::string idString((char*)data->biosId, data->biosIdLength);
184 
185     std::string service = getService(_dbus, biosIntf, biosObjPath);
186     setDbusProperty(_dbus, service, biosObjPath, biosIntf, biosProp, idString);
187     uint8_t* bytesWritten = static_cast<uint8_t*>(response);
188     *bytesWritten =
189         data->biosIdLength; // how many bytes are written into storage
190     *dataLen = 1;
191     return IPMI_CC_OK;
192 }
193 
194 ipmi_ret_t ipmiOEMGetDeviceInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
195                                 ipmi_request_t request,
196                                 ipmi_response_t response,
197                                 ipmi_data_len_t dataLen, ipmi_context_t context)
198 {
199     GetOemDeviceInfoReq* req = reinterpret_cast<GetOemDeviceInfoReq*>(request);
200     GetOemDeviceInfoRes* res = reinterpret_cast<GetOemDeviceInfoRes*>(response);
201 
202     if (*dataLen == 0)
203     {
204         phosphor::logging::log<phosphor::logging::level::ERR>(
205             "Paramter length should be at least one byte");
206         return IPMI_CC_REQ_DATA_LEN_INVALID;
207     }
208 
209     size_t reqDataLen = *dataLen;
210     *dataLen = 0;
211     if (req->entityType > OEMDevEntityType::sdrVer)
212     {
213         phosphor::logging::log<phosphor::logging::level::ERR>(
214             "Out of range",
215             phosphor::logging::entry("TYPE=%x", req->entityType));
216 
217         return IPMI_CC_INVALID_FIELD_REQUEST;
218     }
219 
220     // handle OEM command items
221     switch (req->entityType)
222     {
223         case OEMDevEntityType::biosId:
224         {
225             if (sizeof(GetOemDeviceInfoReq) != reqDataLen)
226             {
227                 phosphor::logging::log<phosphor::logging::level::ERR>(
228                     "Data length does not match request",
229                     phosphor::logging::entry("REQLEN=%d", reqDataLen),
230                     phosphor::logging::entry("LEN=%d",
231                                              sizeof(GetOemDeviceInfoReq)));
232                 return IPMI_CC_REQ_DATA_LEN_INVALID;
233             }
234 
235             std::string service = getService(_dbus, biosIntf, biosObjPath);
236             try
237             {
238                 Value variant = getDbusProperty(_dbus, service, biosObjPath,
239                                                 biosIntf, biosProp);
240                 std::string& idString = variant.get<std::string>();
241                 if (req->offset >= idString.size())
242                 {
243                     phosphor::logging::log<phosphor::logging::level::ERR>(
244                         "offset exceed range",
245                         phosphor::logging::entry("OFFSET=%d", req->offset),
246                         phosphor::logging::entry("IDLEN=%d", idString.size()));
247                     return IPMI_CC_PARM_OUT_OF_RANGE;
248                 }
249                 else
250                 {
251                     size_t length = 0;
252                     if (req->countToRead > (idString.size() - req->offset))
253                     {
254                         length = idString.size() - req->offset;
255                     }
256                     else
257                     {
258                         length = req->countToRead;
259                     }
260                     std::copy(idString.begin() + req->offset, idString.end(),
261                               res->data);
262                     res->resDatalen = length;
263                     *dataLen = res->resDatalen + 1;
264                 }
265             }
266             catch (mapbox::util::bad_variant_access& e)
267             {
268                 std::cerr << e.what() << std::endl;
269                 return IPMI_CC_UNSPECIFIED_ERROR;
270             }
271         }
272         break;
273 
274         case OEMDevEntityType::devVer:
275         case OEMDevEntityType::sdrVer:
276             // TODO:
277             return IPMI_CC_ILLEGAL_COMMAND;
278         default:
279             return IPMI_CC_INVALID_FIELD_REQUEST;
280     }
281     return IPMI_CC_OK;
282 }
283 
284 ipmi_ret_t ipmiOEMGetAICFRU(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
285                             ipmi_request_t request, ipmi_response_t response,
286                             ipmi_data_len_t dataLen, ipmi_context_t context)
287 {
288     if (*dataLen != 0)
289     {
290         phosphor::logging::log<phosphor::logging::level::ERR>(
291             "This command should not have any paramter");
292         return IPMI_CC_REQ_DATA_LEN_INVALID;
293     }
294 
295     *dataLen = 1;
296     uint8_t* res = reinterpret_cast<uint8_t*>(response);
297     // temporary fix. We don't support AIC FRU now. Just tell BIOS that no
298     // AIC is available so that BIOS will not timeout repeatly which leads to
299     // slow booting.
300     *res = 0; // Byte1=Count of SlotPosition/FruID records.
301     return IPMI_CC_OK;
302 }
303 
304 void ipmi_register(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_context_t context,
305                    ipmid_callback_t handler, ipmi_cmd_privilege_t priv)
306 {
307     std::ostringstream os;
308     os << "Registering NetFn:[0x" << std::hex << std::uppercase << netfn
309        << "], Cmd:[0x" << cmd << "]\n";
310     phosphor::logging::log<phosphor::logging::level::INFO>(os.str().c_str());
311     ipmi_register_callback(netfn, cmd, context, handler, priv);
312 }
313 
314 static void register_netfn_firmware_functions(void)
315 {
316     phosphor::logging::log<phosphor::logging::level::INFO>(
317         "Registering OEM commands");
318     ipmi_register(netfunIntcOEMGeneral, IPMI_CMD_WILDCARD, NULL,
319                   ipmiOEMWildcard,
320                   PRIVILEGE_USER); // wildcard default handler
321     ipmi_register(netfunIntcOEMGeneral, cmdGetChassisIdentifier, NULL,
322                   ipmiOEMGetChassisIdentifier,
323                   PRIVILEGE_USER); // get chassis identifier
324     ipmi_register(netfunIntcOEMGeneral, cmdSetSystemGUID, NULL,
325                   ipmiOEMSetSystemGUID,
326                   PRIVILEGE_ADMIN); // set system guid
327     ipmi_register(netfunIntcOEMGeneral, cmdSetBIOSID, NULL, ipmiOEMSetBIOSID,
328                   PRIVILEGE_ADMIN);
329     ipmi_register(netfunIntcOEMGeneral, cmdGetOEMDeviceInfo, NULL,
330                   ipmiOEMGetDeviceInfo, PRIVILEGE_USER);
331     ipmi_register(netfunIntcOEMGeneral, cmdGetAICSlotFRUIDRecords, NULL,
332                   ipmiOEMGetAICFRU, PRIVILEGE_USER);
333     return;
334 }
335 
336 } // namespace ipmi
337