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