xref: /openbmc/fb-ipmi-oem/src/selcommands.cpp (revision 11b9c3b1bd22c6719868d3b5589640ab188a24f8)
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 void registerSELFunctions()
329 {
330     // <Get SEL Info>
331     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
332                           ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
333                           ipmiStorageGetSELInfo);
334 
335     // <Get SEL Entry>
336     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
337                           ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
338                           ipmiStorageGetSELEntry);
339 
340     // <Add SEL Entry>
341     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
342                           ipmi::storage::cmdAddSelEntry,
343                           ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
344 
345     return;
346 }
347 
348 } // namespace storage
349 } // namespace ipmi
350