blob: 68622d89ce8d7d431a96017357c6afcf1ab1afd7 [file] [log] [blame]
John Chunge2fae4b2024-11-13 18:10:31 -06001#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
17namespace serialbridge
18{
19
20/**
21 * @brief Table of special characters
22 */
23static 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 */
34uint8_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 */
48uint8_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 */
65int 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 */
124uint8_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 */
151int 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 uint8_t netFn = 0xff;
167 uint8_t lun = 0xff;
168 uint8_t cmd = 0xff;
169 uint8_t cc = 0xff;
170 std::vector<uint8_t> data;
171
Vernon Maueryec9e8bb2025-05-24 15:11:33 -0700172 m.read(netFn, lun, cmd, cc, data);
John Chunge2fae4b2024-11-13 18:10:31 -0600173
174 uint8_t netFnLun = (netFn << netFnShift) | (lun & lunMask);
175 uint8_t seqLun = (seq << netFnShift) | (lun & lunMask);
176
177 std::vector<uint8_t> connectionHeader = {rqAddr, netFnLun};
178 std::vector<uint8_t> messageHeader = {rsAddr, seqLun, cmd, cc};
179
180 // Reserve the buffer size to avoid relloc and copy
181 responseBuffer.clear();
182 responseBuffer.reserve(
183 sizeof(struct IpmiSerialHeader) + 2 * data.size() +
184 4); // 4 for bmStart & bmStop & 2 checksums
185
186 // bmStart
187 responseBuffer.push_back(bmStart);
188
189 // Assemble connection header and checksum
190 checksum = processEscapedCharacter(responseBuffer, connectionHeader);
John Chung88466692025-06-25 11:27:18 -0500191 checksum = static_cast<uint8_t>(~checksum + 1); // checksum1
192 processEscapedCharacter(responseBuffer, std::vector<uint8_t>{checksum});
John Chunge2fae4b2024-11-13 18:10:31 -0600193
194 // Assemble response message and checksum
195 checksum = processEscapedCharacter(responseBuffer, messageHeader);
196 checksum +=
197 processEscapedCharacter(responseBuffer, std::vector<uint8_t>(data));
John Chung88466692025-06-25 11:27:18 -0500198 checksum = static_cast<uint8_t>(~checksum + 1); // checksum2
199 processEscapedCharacter(responseBuffer, std::vector<uint8_t>{checksum});
John Chunge2fae4b2024-11-13 18:10:31 -0600200
201 // bmStop
202 responseBuffer.push_back(bmStop);
203
204 out = std::span<uint8_t>(responseBuffer.begin(), responseBuffer.end());
205
206 if (verbose)
207 {
208 lg2::info(
209 "Write serial request message with len={LEN}, netfn={NETFN}, "
210 "lun={LUN}, cmd={CMD}, seq={SEQ}",
211 "LEN", responseBuffer.size(), "NETFN", netFn, "LUN", lun, "CMD",
212 cmd, "SEQ", seq);
213
214 std::string msgData = "Tx: ";
215 for (auto c : responseBuffer)
216 {
217 msgData += std::format("{:#x} ", c);
218 }
219 lg2::info(msgData.c_str());
220 }
221
222 stdplus::fd::writeExact(uart, out);
223 }
224 catch (const std::exception& e)
225 {
226 lg2::error("IPMI Response failure: {MSG}", "MSG", e);
227
228 return -1;
229 }
230
231 return out.size();
232}
233
234/**
235 * @brief Read function
236 */
237void SerialChannel::read(stdplus::Fd& uart, sdbusplus::bus_t& bus,
238 sdbusplus::slot_t& outstanding)
239{
240 std::array<uint8_t, ipmiSerialMaxBufferSize> buffer;
241 auto ipmiSerialPacket = stdplus::fd::read(uart, buffer);
242
243 if (ipmiSerialPacket.empty())
244 {
245 return;
246 }
247
248 if (outstanding)
249 {
250 lg2::error("Canceling outstanding request \n");
251 outstanding = sdbusplus::slot_t(nullptr);
252 }
253
254 // process ipmi serial packet
255 if (!consumeIpmiSerialPacket(ipmiSerialPacket, requestBuffer))
256 {
257 lg2::info("Wait for more data ... \n");
258 return;
259 }
260
261 // validate ipmi serial packet length
262 if (requestBuffer.size() <
263 (sizeof(struct IpmiSerialHeader) + ipmiSerialChecksumSize))
264 {
265 lg2::error("Invalid request length, ignoring \n");
266 requestBuffer.clear();
267 return;
268 }
269
270 // validate checksum1
271 if (calculateChecksum(std::span<uint8_t>(requestBuffer.begin(),
272 ipmiSerialConnectionHeaderLength)))
273 {
274 lg2::error("Invalid request checksum 1 \n");
275 requestBuffer.clear();
276 return;
277 }
278
279 // validate checksum2
280 if (calculateChecksum(std::span<uint8_t>(
281 &requestBuffer[ipmiSerialConnectionHeaderLength],
282 requestBuffer.size() - ipmiSerialConnectionHeaderLength)))
283 {
284 lg2::error("Invalid request checksum 2 \n");
285 requestBuffer.clear();
286 return;
287 }
288
289 auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
290 "/xyz/openbmc_project/Ipmi",
291 "xyz.openbmc_project.Ipmi.Server", "execute");
292
293 std::map<std::string, std::variant<int>> options;
294 struct IpmiSerialHeader* header =
295 reinterpret_cast<struct IpmiSerialHeader*>(requestBuffer.data());
296
297 uint8_t rsAddr = header->rsAddr;
298 uint8_t netFn = header->rsNetFnLUN >> netFnShift;
299 uint8_t lun = header->rsNetFnLUN & lunMask;
300 uint8_t rqAddr = header->rqAddr;
301 uint8_t seq = header->rqSeqLUN >> netFnShift;
302 uint8_t cmd = header->cmd;
303
304 std::span reqSpan{requestBuffer.begin(),
305 requestBuffer.end() -
306 ipmiSerialChecksumSize}; // remove checksum 2
307 m.append(netFn, lun, cmd, reqSpan.subspan(sizeof(IpmiSerialHeader)),
308 options);
309
310 if (verbose)
311 {
312 lg2::info("Read serial request message with len={LEN}, netFn={NETFN}, "
313 "lun={LUN}, cmd={CMD}, seq={SEQ}",
314 "LEN", requestBuffer.size(), "NETFN", netFn, "LUN", lun,
315 "CMD", cmd, "SEQ", seq);
316
317 std::string msgData = "Rx: ";
318 for (auto c : requestBuffer)
319 {
320 msgData += std::format("{:#x} ", c);
321 }
322 lg2::info(msgData.c_str());
323 }
324
325 outstanding = m.call_async(stdplus::exception::ignore(
326 [&outstanding, this, &uart, _rsAddr{rsAddr}, _rqAddr{rqAddr},
327 _seq{seq}](sdbusplus::message_t&& m) {
328 outstanding = sdbusplus::slot_t(nullptr);
329
330 if (write(uart, _rsAddr, _rqAddr, _seq, std::move(m)) < 0)
331 {
332 lg2::error(
333 "Occur an error while attempting to send the response.");
334 }
335 }));
336
337 requestBuffer.clear();
338
339 return;
340}
341
342} // namespace serialbridge