1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "google_accel_oob.hpp"
16 
17 #include "commands.hpp"
18 
19 #include <cstdint>
20 #include <cstring>
21 #include <sdbusplus/bus.hpp>
22 #include <span>
23 #include <string>
24 #include <vector>
25 
26 namespace google
27 {
28 namespace ipmi
29 {
30 
31 #ifndef MAX_IPMI_BUFFER
32 #define MAX_IPMI_BUFFER 64
33 #endif
34 
35 // token + address(8) + num_bytes + data(8) + len + NULL
36 constexpr size_t MAX_NAME_SIZE = MAX_IPMI_BUFFER - 1 - 8 - 1 - 8 - 1 - 1;
37 
38 Resp accelOobDeviceCount(std::span<const uint8_t> data,
39                          HandlerInterface* handler)
40 {
41     struct Request
42     {
43     } __attribute__((packed));
44 
45     struct Reply
46     {
47         uint32_t count;
48     } __attribute__((packed));
49 
50     if (data.size_bytes() < sizeof(Request))
51     {
52         std::fprintf(stderr, "AccelOob DeviceCount command too small: %zu\n",
53                      data.size_bytes());
54         return ::ipmi::responseReqDataLenInvalid();
55     }
56 
57     if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
58     {
59         std::fprintf(stderr,
60                      "AccelOob DeviceCount command too large for reply buffer: "
61                      "command=%zuB, payload=%zuB, max=%dB\n",
62                      data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
63         return ::ipmi::responseReqDataLenExceeded();
64     }
65 
66     uint32_t count = handler->accelOobDeviceCount();
67 
68     std::vector<uint8_t> replyBuf(sizeof(Reply));
69     auto* reply = reinterpret_cast<Reply*>(replyBuf.data());
70     reply->count = count;
71 
72     return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobDeviceCount,
73                                    replyBuf);
74 }
75 
76 Resp accelOobDeviceName(std::span<const uint8_t> data,
77                         HandlerInterface* handler)
78 {
79     struct Request
80     {
81         uint32_t index;
82     } __attribute__((packed));
83 
84     struct Reply
85     {
86         uint8_t nameLength;
87         char name[MAX_NAME_SIZE];
88     } __attribute__((packed));
89 
90     if (data.size_bytes() < sizeof(Request))
91     {
92         std::fprintf(stderr, "AccelOob DeviceName command too small: %zu\n",
93                      data.size_bytes());
94         return ::ipmi::responseReqDataLenInvalid();
95     }
96 
97     if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
98     {
99         std::fprintf(stderr,
100                      "AccelOob DeviceName command too large for reply buffer: "
101                      "command=%zuB, payload=%zuB, max=%dB\n",
102                      data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
103         return ::ipmi::responseReqDataLenExceeded();
104     }
105 
106     auto* req = reinterpret_cast<const Request*>(data.data());
107     std::string name = handler->accelOobDeviceName(req->index);
108 
109     if (name.size() > MAX_NAME_SIZE)
110     {
111         std::fprintf(stderr,
112                      "AccelOob: name was too long. "
113                      "'%s' len must be <= %zu\n",
114                      name.c_str(), MAX_NAME_SIZE);
115         return ::ipmi::responseReqDataTruncated();
116     }
117 
118     std::vector<uint8_t> replyBuf(data.size_bytes() + sizeof(Reply));
119     std::copy(data.begin(), data.end(), replyBuf.data());
120     auto* reply = reinterpret_cast<Reply*>(replyBuf.data() + data.size_bytes());
121     reply->nameLength = name.length();
122     memcpy(reply->name, name.c_str(), reply->nameLength + 1);
123 
124     return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobDeviceName,
125                                    replyBuf);
126 }
127 
128 namespace
129 {
130 
131 struct NameHeader
132 {
133     uint8_t nameLength;
134     char name[MAX_NAME_SIZE];
135 } __attribute__((packed));
136 
137 // Reads the variable-length name from reqBuf and outputs the name and a pointer
138 // to the payload (next byte after name).
139 //
140 // Returns: =0: success.
141 //          >0: if dataLen is too small, returns the minimum buffers size.
142 //
143 // Params:
144 //    [in]  reqBuf      - the request buffer
145 //    [in]  dataLen     - the length of reqBuf, in bytes
146 //    [in]  payloadSize - the size of the expected payload
147 //    [out] name        - the name string
148 //    [out] payload     - pointer into reqBuf just after name
149 size_t ReadNameHeader(const uint8_t* reqBuf, size_t dataLen, size_t payloadSize,
150                       std::string* name, const uint8_t** payload)
151 {
152     constexpr size_t kNameHeaderSize = sizeof(NameHeader) - MAX_NAME_SIZE;
153 
154     auto* req_header = reinterpret_cast<const NameHeader*>(reqBuf);
155 
156     size_t minDataLen = kNameHeaderSize + payloadSize + req_header->nameLength;
157     if (dataLen < minDataLen)
158     {
159         return minDataLen;
160     }
161 
162     if (name)
163     {
164         *name = std::string(req_header->name, req_header->nameLength);
165     }
166     if (payload)
167     {
168         *payload = reqBuf + kNameHeaderSize + req_header->nameLength;
169     }
170     return 0;
171 }
172 
173 } // namespace
174 
175 Resp accelOobRead(std::span<const uint8_t> data, HandlerInterface* handler)
176 {
177     struct Request
178     {
179         // Variable length header, handled by ReadNameHeader
180         // uint8_t  nameLength;  // <= MAX_NAME_SIZE
181         // char     name[nameLength];
182 
183         // Additional arguments
184         uint8_t token;
185         uint64_t address;
186         uint8_t num_bytes;
187     } __attribute__((packed));
188 
189     struct Reply
190     {
191         uint64_t data;
192     } __attribute__((packed));
193 
194     std::fprintf(stderr,
195                  "AccelOob Read command sizes: "
196                  "command=%zuB, payload=%zuB, max=%dB\n",
197                  data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
198 
199     std::string name;
200     const uint8_t* payload;
201 
202     size_t min_size = ReadNameHeader(data.data(), data.size_bytes(),
203                                      sizeof(Request), &name, &payload);
204     if (min_size != 0)
205     {
206         std::fprintf(stderr, "AccelOob Read command too small: %zuB < %zuB\n",
207                      data.size_bytes(), min_size);
208         return ::ipmi::responseReqDataLenInvalid();
209     }
210 
211     if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
212     {
213         std::fprintf(stderr,
214                      "AccelOob Read command too large for reply buffer: "
215                      "command=%zuB, payload=%zuB, max=%dB\n",
216                      data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
217         return ::ipmi::responseReqDataLenExceeded();
218     }
219 
220     auto req = reinterpret_cast<const Request*>(payload);
221     uint64_t r = handler->accelOobRead(name, req->address, req->num_bytes);
222 
223     std::vector<uint8_t> replyBuf(data.size_bytes() + sizeof(Reply));
224     std::copy(data.begin(), data.end(), replyBuf.data());
225     auto* reply = reinterpret_cast<Reply*>(replyBuf.data() + data.size_bytes());
226     reply->data = r;
227 
228     return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobRead, replyBuf);
229 }
230 
231 Resp accelOobWrite(std::span<const uint8_t> data, HandlerInterface* handler)
232 {
233     struct Request
234     {
235         // Variable length header, handled by ReadNameHeader
236         // uint8_t  nameLength;  // <= MAX_NAME_SIZE
237         // char     name[nameLength];
238 
239         // Additional arguments
240         uint8_t token;
241         uint64_t address;
242         uint8_t num_bytes;
243         uint64_t data;
244     } __attribute__((packed));
245 
246     struct Reply
247     {
248         // Empty
249     } __attribute__((packed));
250 
251     std::string name{};
252     const uint8_t* payload;
253 
254     size_t min_size = ReadNameHeader(data.data(), data.size_bytes(),
255                                      sizeof(Request), &name, &payload);
256     if (min_size != 0)
257     {
258         std::fprintf(stderr, "AccelOob Write command too small: %zuB < %zuB\n",
259                      data.size_bytes(), min_size);
260         return ::ipmi::responseReqDataLenInvalid();
261     }
262 
263     if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
264     {
265         std::fprintf(stderr,
266                      "AccelOob Write command too large for reply buffer: "
267                      "command=%zuB, payload=%zuB, max=%dB\n",
268                      data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
269         return ::ipmi::responseReqDataLenExceeded();
270     }
271 
272     auto req = reinterpret_cast<const Request*>(payload);
273     handler->accelOobWrite(name, req->address, req->num_bytes, req->data);
274 
275     std::vector<uint8_t> replyBuf(data.size_bytes() + sizeof(Reply));
276     std::copy(data.begin(), data.end(), replyBuf.data());
277 
278     return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobWrite, replyBuf);
279 }
280 
281 } // namespace ipmi
282 } // namespace google
283