xref: /openbmc/phosphor-host-ipmid/transport/serialbridge/serialcmd.cpp (revision 89f8280033a218c27cfa7ac933ef840ec1ea0b90)
1 #include "serialcmd.hpp"
2 
3 #include <fmt/format.h>
4 
5 #include <phosphor-logging/lg2.hpp>
6 #include <sdbusplus/bus.hpp>
7 #include <sdbusplus/exception.hpp>
8 #include <sdbusplus/message.hpp>
9 #include <sdbusplus/slot.hpp>
10 #include <stdplus/exception.hpp>
11 #include <stdplus/fd/ops.hpp>
12 
13 #include <numeric>
14 #include <ranges>
15 #include <unordered_map>
16 
17 namespace serialbridge
18 {
19 
20 /**
21  * @brief Table of special characters
22  */
23 static const std::unordered_map<uint8_t, uint8_t> characters = {
24     {bmStart, 0xB0},     /* start */
25     {bmStop, 0xB5},      /* stop */
26     {bmHandshake, 0xB6}, /* packet handshake */
27     {bmEscape, 0xBA},    /* data escape */
28     {0x1B, 0x3B}         /* escape */
29 };
30 
31 /**
32  * @brief Calculate IPMI checksum
33  */
calculateChecksum(std::span<uint8_t> data)34 uint8_t SerialChannel::calculateChecksum(std::span<uint8_t> data)
35 {
36     uint8_t checksum;
37 
38     checksum = std::accumulate(data.begin(), data.end(), 0);
39     checksum = (~checksum) + 1;
40 
41     // return checksum
42     return checksum;
43 }
44 
45 /**
46  * @brief Return unescaped character for the given one
47  */
getUnescapedCharacter(uint8_t c)48 uint8_t SerialChannel::getUnescapedCharacter(uint8_t c)
49 {
50     auto search =
51         std::find_if(characters.begin(), characters.end(),
52                      [c](const auto& map_set) { return map_set.second == c; });
53 
54     if (search == characters.end())
55     {
56         return c;
57     }
58 
59     return search->first;
60 }
61 
62 /**
63  * @brief Process IPMI Serial Request State Machine
64  */
consumeIpmiSerialPacket(std::span<uint8_t> & escapedDataBytes,std::vector<uint8_t> & unescapedDataBytes)65 int SerialChannel::consumeIpmiSerialPacket(
66     std::span<uint8_t>& escapedDataBytes,
67     std::vector<uint8_t>& unescapedDataBytes)
68 {
69     unescapedDataBytes.reserve(escapedDataBytes.size());
70 
71     for (auto c : escapedDataBytes)
72     {
73         if (c == bmStart) // START
74         {
75             msgState = MsgState::msgInProgress;
76         }
77         else if (msgState == MsgState::msgIdle)
78         {
79             continue;
80         }
81         else if (msgState == MsgState::msgInEscape)
82         {
83             uint8_t unescapedCharacter;
84             unescapedCharacter = getUnescapedCharacter(c);
85 
86             if (unescapedCharacter == c)
87             {
88                 // error, then reset
89                 msgState = MsgState::msgIdle;
90                 unescapedDataBytes.clear();
91                 continue;
92             }
93 
94             unescapedDataBytes.push_back(unescapedCharacter);
95             msgState = MsgState::msgInProgress;
96         }
97         else if (c == bmEscape)
98         {
99             msgState = MsgState::msgInEscape;
100             continue;
101         }
102         else if (c == bmStop) // STOP
103         {
104             msgState = MsgState::msgIdle;
105             return true;
106         }
107         else if (c == bmHandshake) // Handshake
108         {
109             unescapedDataBytes.clear();
110             continue;
111         }
112         else if (msgState == MsgState::msgInProgress)
113         {
114             unescapedDataBytes.push_back(c);
115         }
116     }
117 
118     return false;
119 }
120 
121 /**
122  * @brief Encapsluate response to avoid escape character
123  */
processEscapedCharacter(std::vector<uint8_t> & buffer,const std::vector<uint8_t> & data)124 uint8_t SerialChannel::processEscapedCharacter(std::vector<uint8_t>& buffer,
125                                                const std::vector<uint8_t>& data)
126 {
127     uint8_t checksum = 0;
128 
129     std::ranges::for_each(data.begin(), data.end(),
130                           [&buffer, &checksum](const auto& c) {
131                               auto search = characters.find(c);
132                               if (search != characters.end())
133                               {
134                                   buffer.push_back(bmEscape);
135                                   buffer.push_back(search->second);
136                               }
137                               else
138                               {
139                                   buffer.push_back(c);
140                               }
141 
142                               checksum += c;
143                           });
144 
145     return checksum;
146 }
147 
148 /**
149  * @brief Write function
150  */
write(stdplus::Fd & uart,uint8_t rsAddr,uint8_t rqAddr,uint8_t seq,sdbusplus::message_t && m)151 int SerialChannel::write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr,
152                          uint8_t seq, sdbusplus::message_t&& m)
153 {
154     std::span<uint8_t> out;
155     uint8_t checksum;
156 
157     try
158     {
159         if (m.is_method_error())
160         {
161             // Extra copy to workaround lack of `const sd_bus_error` constructor
162             auto error = *m.get_error();
163             throw sdbusplus::exception::SdBusError(&error, "ipmid response");
164         }
165 
166         std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
167             ret;
168         m.read(ret);
169 
170         const auto& [netFn, lun, cmd, cc, data] = ret;
171 
172         uint8_t netFnLun = (netFn << netFnShift) | (lun & lunMask);
173         uint8_t seqLun = (seq << netFnShift) | (lun & lunMask);
174 
175         std::vector<uint8_t> connectionHeader = {rqAddr, netFnLun};
176         std::vector<uint8_t> messageHeader = {rsAddr, seqLun, cmd, cc};
177 
178         // Reserve the buffer size to avoid relloc and copy
179         responseBuffer.clear();
180         responseBuffer.reserve(
181             sizeof(struct IpmiSerialHeader) + 2 * data.size() +
182             4); // 4 for bmStart & bmStop & 2 checksums
183 
184         // bmStart
185         responseBuffer.push_back(bmStart);
186 
187         // Assemble connection header and checksum
188         checksum = processEscapedCharacter(responseBuffer, connectionHeader);
189         checksum = static_cast<uint8_t>(~checksum + 1); // checksum1
190         processEscapedCharacter(responseBuffer, std::vector<uint8_t>{checksum});
191 
192         // Assemble response message and checksum
193         checksum = processEscapedCharacter(responseBuffer, messageHeader);
194         checksum +=
195             processEscapedCharacter(responseBuffer, std::vector<uint8_t>(data));
196         checksum = static_cast<uint8_t>(~checksum + 1); // checksum2
197         processEscapedCharacter(responseBuffer, std::vector<uint8_t>{checksum});
198 
199         // bmStop
200         responseBuffer.push_back(bmStop);
201 
202         out = std::span<uint8_t>(responseBuffer.begin(), responseBuffer.end());
203 
204         if (verbose)
205         {
206             lg2::info(
207                 "Write serial request message with len={LEN}, netfn={NETFN}, "
208                 "lun={LUN}, cmd={CMD}, seq={SEQ}",
209                 "LEN", responseBuffer.size(), "NETFN", netFn, "LUN", lun, "CMD",
210                 cmd, "SEQ", seq);
211 
212             std::string msgData = "Tx: ";
213             for (auto c : responseBuffer)
214             {
215                 msgData += std::format("{:#x} ", c);
216             }
217             lg2::info(msgData.c_str());
218         }
219 
220         stdplus::fd::writeExact(uart, out);
221     }
222     catch (const std::exception& e)
223     {
224         lg2::error("IPMI Response failure: {MSG}", "MSG", e);
225 
226         return -1;
227     }
228 
229     return out.size();
230 }
231 
232 /**
233  * @brief Read function
234  */
read(stdplus::Fd & uart,sdbusplus::bus_t & bus,sdbusplus::slot_t & outstanding)235 void SerialChannel::read(stdplus::Fd& uart, sdbusplus::bus_t& bus,
236                          sdbusplus::slot_t& outstanding)
237 {
238     std::array<uint8_t, ipmiSerialMaxBufferSize> buffer;
239     auto ipmiSerialPacket = stdplus::fd::read(uart, buffer);
240 
241     if (ipmiSerialPacket.empty())
242     {
243         return;
244     }
245 
246     if (outstanding)
247     {
248         lg2::error("Canceling outstanding request \n");
249         outstanding = sdbusplus::slot_t(nullptr);
250     }
251 
252     // process ipmi serial packet
253     if (!consumeIpmiSerialPacket(ipmiSerialPacket, requestBuffer))
254     {
255         lg2::info("Wait for more data ... \n");
256         return;
257     }
258 
259     // validate ipmi serial packet length
260     if (requestBuffer.size() <
261         (sizeof(struct IpmiSerialHeader) + ipmiSerialChecksumSize))
262     {
263         lg2::error("Invalid request length, ignoring \n");
264         requestBuffer.clear();
265         return;
266     }
267 
268     // validate checksum1
269     if (calculateChecksum(std::span<uint8_t>(requestBuffer.begin(),
270                                              ipmiSerialConnectionHeaderLength)))
271     {
272         lg2::error("Invalid request checksum 1 \n");
273         requestBuffer.clear();
274         return;
275     }
276 
277     // validate checksum2
278     if (calculateChecksum(std::span<uint8_t>(
279             &requestBuffer[ipmiSerialConnectionHeaderLength],
280             requestBuffer.size() - ipmiSerialConnectionHeaderLength)))
281     {
282         lg2::error("Invalid request checksum 2 \n");
283         requestBuffer.clear();
284         return;
285     }
286 
287     auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
288                                  "/xyz/openbmc_project/Ipmi",
289                                  "xyz.openbmc_project.Ipmi.Server", "execute");
290 
291     std::map<std::string, std::variant<int>> options;
292     struct IpmiSerialHeader* header =
293         reinterpret_cast<struct IpmiSerialHeader*>(requestBuffer.data());
294 
295     uint8_t rsAddr = header->rsAddr;
296     uint8_t netFn = header->rsNetFnLUN >> netFnShift;
297     uint8_t lun = header->rsNetFnLUN & lunMask;
298     uint8_t rqAddr = header->rqAddr;
299     uint8_t seq = header->rqSeqLUN >> netFnShift;
300     uint8_t cmd = header->cmd;
301 
302     std::span reqSpan{requestBuffer.begin(),
303                       requestBuffer.end() -
304                           ipmiSerialChecksumSize}; // remove checksum 2
305     m.append(netFn, lun, cmd, reqSpan.subspan(sizeof(IpmiSerialHeader)),
306              options);
307 
308     if (verbose)
309     {
310         lg2::info("Read serial request message with len={LEN}, netFn={NETFN}, "
311                   "lun={LUN}, cmd={CMD}, seq={SEQ}",
312                   "LEN", requestBuffer.size(), "NETFN", netFn, "LUN", lun,
313                   "CMD", cmd, "SEQ", seq);
314 
315         std::string msgData = "Rx: ";
316         for (auto c : requestBuffer)
317         {
318             msgData += std::format("{:#x} ", c);
319         }
320         lg2::info(msgData.c_str());
321     }
322 
323     outstanding = m.call_async(stdplus::exception::ignore(
324         [&outstanding, this, &uart, _rsAddr{rsAddr}, _rqAddr{rqAddr},
325          _seq{seq}](sdbusplus::message_t&& m) {
326             outstanding = sdbusplus::slot_t(nullptr);
327 
328             if (write(uart, _rsAddr, _rqAddr, _seq, std::move(m)) < 0)
329             {
330                 lg2::error(
331                     "Occur an error while attempting to send the response.");
332             }
333         }));
334 
335     requestBuffer.clear();
336 
337     return;
338 }
339 
340 } // namespace serialbridge
341