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