xref: /openbmc/fb-ipmi-oem/src/selcommands.cpp (revision c1921c637bb124538638bb99510771da3e497df8)
1 /*
2  * Copyright (c)  2018 Intel Corporation.
3  * Copyright (c)  2018-present Facebook.
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 <ipmid/api.hpp>
19 
20 #include <boost/algorithm/string/join.hpp>
21 #include <nlohmann/json.hpp>
22 #include <iostream>
23 #include <sstream>
24 #include <fstream>
25 #include <phosphor-logging/log.hpp>
26 #include <sdbusplus/message/types.hpp>
27 #include <sdbusplus/timer.hpp>
28 #include <storagecommands.hpp>
29 
30 //----------------------------------------------------------------------
31 // Platform specific functions for storing app data
32 //----------------------------------------------------------------------
33 
34 static void toHexStr(std::vector<uint8_t> &bytes, std::string &hexStr)
35 {
36     std::stringstream stream;
37     stream << std::hex << std::uppercase << std::setfill('0');
38     for (const uint8_t byte : bytes)
39     {
40         stream << std::setw(2) << static_cast<int>(byte);
41     }
42     hexStr = stream.str();
43 }
44 
45 static int fromHexStr(const std::string hexStr, std::vector<uint8_t> &data)
46 {
47     for (unsigned int i = 0; i < hexStr.size(); i += 2)
48     {
49         try
50         {
51             data.push_back(static_cast<uint8_t>(
52                 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
53         }
54         catch (std::invalid_argument &e)
55         {
56             phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
57             return -1;
58         }
59         catch (std::out_of_range &e)
60         {
61             phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
62             return -1;
63         }
64     }
65     return 0;
66 }
67 
68 namespace fb_oem::ipmi::sel
69 {
70 
71 class SELData
72 {
73   private:
74     nlohmann::json selDataObj;
75 
76     void flush()
77     {
78         std::ofstream file(SEL_JSON_DATA_FILE);
79         file << selDataObj;
80         file.close();
81     }
82 
83     void init()
84     {
85         selDataObj[KEY_SEL_VER] = 0x51;
86         selDataObj[KEY_SEL_COUNT] = 0;
87         selDataObj[KEY_ADD_TIME] = 0xFFFFFFFF;
88         selDataObj[KEY_ERASE_TIME] = 0xFFFFFFFF;
89         selDataObj[KEY_OPER_SUPP] = 0x02;
90         /* Spec indicates that more than 64kB is free */
91         selDataObj[KEY_FREE_SPACE] = 0xFFFF;
92     }
93 
94   public:
95     SELData()
96     {
97         /* Get App data stored in json file */
98         std::ifstream file(SEL_JSON_DATA_FILE);
99         if (file)
100         {
101             file >> selDataObj;
102             file.close();
103         }
104 
105         /* Initialize SelData object if no entries. */
106         if (selDataObj.find(KEY_SEL_COUNT) == selDataObj.end())
107         {
108             init();
109         }
110     }
111 
112     int clear()
113     {
114         /* Clear the complete Sel Json object */
115         selDataObj.clear();
116         /* Reinitialize it with basic data */
117         init();
118         /* Save the erase time */
119         struct timespec selTime = {};
120         if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
121         {
122             return -1;
123         }
124         selDataObj[KEY_ERASE_TIME] = selTime.tv_sec;
125         flush();
126         return 0;
127     }
128 
129     uint32_t getCount()
130     {
131         return selDataObj[KEY_SEL_COUNT];
132     }
133 
134     void getInfo(GetSELInfoData &info)
135     {
136         info.selVersion = selDataObj[KEY_SEL_VER];
137         info.entries = selDataObj[KEY_SEL_COUNT];
138         info.freeSpace = selDataObj[KEY_FREE_SPACE];
139         info.addTimeStamp = selDataObj[KEY_ADD_TIME];
140         info.eraseTimeStamp = selDataObj[KEY_ERASE_TIME];
141         info.operationSupport = selDataObj[KEY_OPER_SUPP];
142     }
143 
144     int getEntry(uint32_t index, std::string &rawStr)
145     {
146         std::stringstream ss;
147         ss << std::hex;
148         ss << std::setw(2) << std::setfill('0') << index;
149 
150         /* Check or the requested SEL Entry, if record is available */
151         if (selDataObj.find(ss.str()) == selDataObj.end())
152         {
153             return -1;
154         }
155 
156         rawStr = selDataObj[ss.str()][KEY_SEL_ENTRY_RAW];
157         return 0;
158     }
159 
160     int addEntry(std::string keyStr)
161     {
162         struct timespec selTime = {};
163 
164         if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
165         {
166             return -1;
167         }
168 
169         selDataObj[KEY_ADD_TIME] = selTime.tv_sec;
170 
171         int selCount = selDataObj[KEY_SEL_COUNT];
172         selDataObj[KEY_SEL_COUNT] = ++selCount;
173 
174         std::stringstream ss;
175         ss << std::hex;
176         ss << std::setw(2) << std::setfill('0') << selCount;
177 
178         selDataObj[ss.str()][KEY_SEL_ENTRY_RAW] = keyStr;
179         flush();
180         return selCount;
181     }
182 };
183 
184 } // namespace fb_oem::ipmi::sel
185 
186 namespace ipmi
187 {
188 
189 namespace storage
190 {
191 
192 static void registerSELFunctions() __attribute__((constructor));
193 static fb_oem::ipmi::sel::SELData selObj __attribute__((init_priority(101)));
194 
195 ipmi::RspType<uint8_t,  // SEL version
196               uint16_t, // SEL entry count
197               uint16_t, // free space
198               uint32_t, // last add timestamp
199               uint32_t, // last erase timestamp
200               uint8_t>  // operation support
201     ipmiStorageGetSELInfo()
202 {
203 
204     fb_oem::ipmi::sel::GetSELInfoData info;
205 
206     selObj.getInfo(info);
207     return ipmi::responseSuccess(info.selVersion, info.entries, info.freeSpace,
208                                  info.addTimeStamp, info.eraseTimeStamp,
209                                  info.operationSupport);
210 }
211 
212 ipmi::RspType<uint16_t, std::vector<uint8_t>>
213     ipmiStorageGetSELEntry(std::vector<uint8_t> data)
214 {
215 
216     if (data.size() != sizeof(fb_oem::ipmi::sel::GetSELEntryRequest))
217     {
218         return ipmi::responseReqDataLenInvalid();
219     }
220 
221     fb_oem::ipmi::sel::GetSELEntryRequest *reqData =
222         reinterpret_cast<fb_oem::ipmi::sel::GetSELEntryRequest *>(&data[0]);
223 
224     if (reqData->reservID != 0)
225     {
226         if (!checkSELReservation(reqData->reservID))
227         {
228             return ipmi::responseInvalidReservationId();
229         }
230     }
231 
232     uint16_t selCnt = selObj.getCount();
233     if (selCnt == 0)
234     {
235         return ipmi::responseSensorInvalid();
236     }
237 
238     /* If it is asked for first entry */
239     if (reqData->recordID == fb_oem::ipmi::sel::firstEntry)
240     {
241         /* First Entry (0x0000) as per Spec */
242         reqData->recordID = 1;
243     }
244     else if (reqData->recordID == fb_oem::ipmi::sel::lastEntry)
245     {
246         /* Last entry (0xFFFF) as per Spec */
247         reqData->recordID = selCnt;
248     }
249 
250     std::string ipmiRaw;
251 
252     if (selObj.getEntry(reqData->recordID, ipmiRaw) < 0)
253     {
254         return ipmi::responseSensorInvalid();
255     }
256 
257     std::vector<uint8_t> recDataBytes;
258     if (fromHexStr(ipmiRaw, recDataBytes) < 0)
259     {
260         return ipmi::responseUnspecifiedError();
261     }
262 
263     /* Identify the next SEL record ID. If recordID is same as
264      * total SeL count then next id should be last entry else
265      * it should be incremented by 1 to current RecordID
266      */
267     uint16_t nextRecord;
268     if (reqData->recordID == selCnt)
269     {
270         nextRecord = fb_oem::ipmi::sel::lastEntry;
271     }
272     else
273     {
274         nextRecord = reqData->recordID + 1;
275     }
276 
277     if (reqData->readLen == fb_oem::ipmi::sel::entireRecord)
278     {
279         return ipmi::responseSuccess(nextRecord, recDataBytes);
280     }
281     else
282     {
283         if (reqData->offset >= fb_oem::ipmi::sel::selRecordSize ||
284             reqData->readLen > fb_oem::ipmi::sel::selRecordSize)
285         {
286             return ipmi::responseUnspecifiedError();
287         }
288         std::vector<uint8_t> recPartData;
289 
290         auto diff = fb_oem::ipmi::sel::selRecordSize - reqData->offset;
291         auto readLength = std::min(diff, static_cast<int>(reqData->readLen));
292 
293         for (int i = 0; i < readLength; i++)
294         {
295             recPartData.push_back(recDataBytes[i + reqData->offset]);
296         }
297         return ipmi::responseSuccess(nextRecord, recPartData);
298     }
299 }
300 
301 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(std::vector<uint8_t> data)
302 {
303     /* Per the IPMI spec, need to cancel any reservation when a
304      * SEL entry is added
305      */
306     cancelSELReservation();
307 
308     if (data.size() != fb_oem::ipmi::sel::selRecordSize)
309     {
310         return ipmi::responseReqDataLenInvalid();
311     }
312 
313     std::string ipmiRaw, logErr;
314     toHexStr(data, ipmiRaw);
315 
316     /* Log the Raw SEL message to the journal */
317     std::string journalMsg = "SEL Entry Added: " + ipmiRaw;
318     phosphor::logging::log<phosphor::logging::level::INFO>(journalMsg.c_str());
319 
320     int responseID = selObj.addEntry(ipmiRaw.c_str());
321     if (responseID < 0)
322     {
323         return ipmi::responseUnspecifiedError();
324     }
325     return ipmi::responseSuccess((uint16_t)responseID);
326 }
327 
328 ipmi::RspType<uint8_t> ipmiStorageClearSEL(uint16_t reservationID,
329                                            const std::array<uint8_t, 3> &clr,
330                                            uint8_t eraseOperation)
331 {
332     if (!checkSELReservation(reservationID))
333     {
334         return ipmi::responseInvalidReservationId();
335     }
336 
337     static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
338     if (clr != clrExpected)
339     {
340         return ipmi::responseInvalidFieldRequest();
341     }
342 
343     /* If there is no sel then return erase complete */
344     if (selObj.getCount() == 0)
345     {
346         return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
347     }
348 
349     /* Erasure status cannot be fetched, so always return erasure
350      * status as `erase completed`.
351      */
352     if (eraseOperation == fb_oem::ipmi::sel::getEraseStatus)
353     {
354         return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
355     }
356 
357     /* Check that initiate erase is correct */
358     if (eraseOperation != fb_oem::ipmi::sel::initiateErase)
359     {
360         return ipmi::responseInvalidFieldRequest();
361     }
362 
363     /* Per the IPMI spec, need to cancel any reservation when the
364      * SEL is cleared
365      */
366     cancelSELReservation();
367 
368     /* Clear the complete Sel Json object */
369     if (selObj.clear() < 0)
370     {
371         return ipmi::responseUnspecifiedError();
372     }
373 
374     return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
375 }
376 
377 ipmi::RspType<uint32_t> ipmiStorageGetSELTime()
378 {
379     struct timespec selTime = {};
380 
381     if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
382     {
383         return ipmi::responseUnspecifiedError();
384     }
385 
386     return ipmi::responseSuccess(selTime.tv_sec);
387 }
388 
389 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime)
390 {
391     // Set SEL Time is not supported
392     return ipmi::responseInvalidCommand();
393 }
394 
395 ipmi::RspType<uint16_t> ipmiStorageGetSELTimeUtcOffset()
396 {
397     /* TODO: For now, the SEL time stamp is based on UTC time,
398      * so return 0x0000 as offset. Might need to change once
399      * supporting zones in SEL time stamps
400      */
401 
402     uint16_t utcOffset = 0x0000;
403     return ipmi::responseSuccess(utcOffset);
404 }
405 
406 void registerSELFunctions()
407 {
408     // <Get SEL Info>
409     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
410                           ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
411                           ipmiStorageGetSELInfo);
412 
413     // <Get SEL Entry>
414     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
415                           ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
416                           ipmiStorageGetSELEntry);
417 
418     // <Add SEL Entry>
419     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
420                           ipmi::storage::cmdAddSelEntry,
421                           ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
422 
423     // <Clear SEL>
424     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
425                           ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
426                           ipmiStorageClearSEL);
427 
428     // <Get SEL Time>
429     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
430                           ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
431                           ipmiStorageGetSELTime);
432 
433     // <Set SEL Time>
434     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
435                           ipmi::storage::cmdSetSelTime,
436                           ipmi::Privilege::Operator, ipmiStorageSetSELTime);
437 
438     // <Get SEL Time UTC Offset>
439     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
440                           ipmi::storage::cmdGetSelTimeUtcOffset,
441                           ipmi::Privilege::User,
442                           ipmiStorageGetSELTimeUtcOffset);
443 
444     return;
445 }
446 
447 } // namespace storage
448 } // namespace ipmi
449