xref: /openbmc/dbus-sensors/src/nvidia-gpu/NvidiaGpuMctpVdm.cpp (revision 68a8e2dd92a09d7f1735cce0cbd4aa722816e8ae)
1 /*
2  * SPDX-FileCopyrightText: Copyright OpenBMC Authors
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 
6 #include "NvidiaGpuMctpVdm.hpp"
7 
8 #include "OcpMctpVdm.hpp"
9 
10 #include <endian.h>
11 
12 #include <cerrno>
13 #include <cstddef>
14 #include <cstdint>
15 #include <cstring>
16 #include <span>
17 #include <vector>
18 
19 namespace gpu
20 {
21 // These functions encode/decode data communicated over the network
22 // The use of reinterpret_cast enables direct memory access to raw byte buffers
23 // without doing unnecessary data copying
24 // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
packHeader(const ocp::accelerator_management::BindingPciVidInfo & hdr,ocp::accelerator_management::BindingPciVid & msg)25 int packHeader(const ocp::accelerator_management::BindingPciVidInfo& hdr,
26                ocp::accelerator_management::BindingPciVid& msg)
27 {
28     return ocp::accelerator_management::packHeader(nvidiaPciVendorId, hdr, msg);
29 }
30 
encodeQueryDeviceIdentificationRequest(uint8_t instanceId,const std::span<uint8_t> buf)31 int encodeQueryDeviceIdentificationRequest(uint8_t instanceId,
32                                            const std::span<uint8_t> buf)
33 {
34     if (buf.size() < sizeof(QueryDeviceIdentificationRequest))
35     {
36         return EINVAL;
37     }
38 
39     auto* msg = reinterpret_cast<QueryDeviceIdentificationRequest*>(buf.data());
40 
41     ocp::accelerator_management::BindingPciVidInfo header{};
42 
43     header.ocp_accelerator_management_msg_type =
44         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
45     header.instance_id = instanceId &
46                          ocp::accelerator_management::instanceIdBitMask;
47     header.msg_type =
48         static_cast<uint8_t>(MessageType::DEVICE_CAPABILITY_DISCOVERY);
49 
50     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
51 
52     if (rc != 0)
53     {
54         return rc;
55     }
56 
57     msg->hdr.command = static_cast<uint8_t>(
58         DeviceCapabilityDiscoveryCommands::QUERY_DEVICE_IDENTIFICATION);
59     msg->hdr.data_size = 0;
60 
61     return 0;
62 }
63 
decodeQueryDeviceIdentificationResponse(const std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,uint8_t & deviceIdentification,uint8_t & deviceInstance)64 int decodeQueryDeviceIdentificationResponse(
65     const std::span<const uint8_t> buf,
66     ocp::accelerator_management::CompletionCode& cc, uint16_t& reasonCode,
67     uint8_t& deviceIdentification, uint8_t& deviceInstance)
68 {
69     auto rc =
70         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
71 
72     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
73     {
74         return rc;
75     }
76 
77     if (buf.size() < sizeof(QueryDeviceIdentificationResponse))
78     {
79         return EINVAL;
80     }
81 
82     const auto* response =
83         reinterpret_cast<const QueryDeviceIdentificationResponse*>(buf.data());
84 
85     deviceIdentification = response->device_identification;
86     deviceInstance = response->instance_id;
87 
88     return 0;
89 }
90 
encodeGetTemperatureReadingRequest(uint8_t instanceId,uint8_t sensorId,std::span<uint8_t> buf)91 int encodeGetTemperatureReadingRequest(uint8_t instanceId, uint8_t sensorId,
92                                        std::span<uint8_t> buf)
93 {
94     if (buf.size() < sizeof(GetTemperatureReadingRequest))
95     {
96         return EINVAL;
97     }
98 
99     auto* msg = reinterpret_cast<GetTemperatureReadingRequest*>(buf.data());
100 
101     ocp::accelerator_management::BindingPciVidInfo header{};
102     header.ocp_accelerator_management_msg_type =
103         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
104     header.instance_id = instanceId &
105                          ocp::accelerator_management::instanceIdBitMask;
106     header.msg_type = static_cast<uint8_t>(MessageType::PLATFORM_ENVIRONMENTAL);
107 
108     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
109 
110     if (rc != 0)
111     {
112         return rc;
113     }
114 
115     msg->hdr.command = static_cast<uint8_t>(
116         PlatformEnvironmentalCommands::GET_TEMPERATURE_READING);
117     msg->hdr.data_size = sizeof(sensorId);
118     msg->sensor_id = sensorId;
119 
120     return 0;
121 }
122 
decodeGetTemperatureReadingResponse(const std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,double & temperatureReading)123 int decodeGetTemperatureReadingResponse(
124     const std::span<const uint8_t> buf,
125     ocp::accelerator_management::CompletionCode& cc, uint16_t& reasonCode,
126     double& temperatureReading)
127 {
128     auto rc =
129         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
130 
131     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
132     {
133         return rc;
134     }
135 
136     if (buf.size() < sizeof(GetTemperatureReadingResponse))
137     {
138         return EINVAL;
139     }
140 
141     const auto* response =
142         reinterpret_cast<const GetTemperatureReadingResponse*>(buf.data());
143 
144     uint16_t dataSize = le16toh(response->hdr.data_size);
145 
146     if (dataSize != sizeof(int32_t))
147     {
148         return EINVAL;
149     }
150 
151     int32_t reading = le32toh(response->reading);
152     temperatureReading = reading / static_cast<double>(1 << 8);
153 
154     return 0;
155 }
156 
encodeReadThermalParametersRequest(uint8_t instanceId,uint8_t sensorId,std::span<uint8_t> buf)157 int encodeReadThermalParametersRequest(uint8_t instanceId, uint8_t sensorId,
158                                        std::span<uint8_t> buf)
159 {
160     if (buf.size() < sizeof(ReadThermalParametersRequest))
161     {
162         return EINVAL;
163     }
164 
165     auto* msg = reinterpret_cast<ReadThermalParametersRequest*>(buf.data());
166 
167     ocp::accelerator_management::BindingPciVidInfo header{};
168     header.ocp_accelerator_management_msg_type =
169         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
170     header.instance_id = instanceId &
171                          ocp::accelerator_management::instanceIdBitMask;
172     header.msg_type = static_cast<uint8_t>(MessageType::PLATFORM_ENVIRONMENTAL);
173 
174     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
175 
176     if (rc != 0)
177     {
178         return rc;
179     }
180 
181     msg->hdr.command = static_cast<uint8_t>(
182         PlatformEnvironmentalCommands::READ_THERMAL_PARAMETERS);
183     msg->hdr.data_size = sizeof(sensorId);
184     msg->sensor_id = sensorId;
185 
186     return 0;
187 }
188 
decodeReadThermalParametersResponse(std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,int32_t & threshold)189 int decodeReadThermalParametersResponse(
190     std::span<const uint8_t> buf,
191     ocp::accelerator_management::CompletionCode& cc, uint16_t& reasonCode,
192     int32_t& threshold)
193 {
194     auto rc =
195         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
196 
197     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
198     {
199         return rc;
200     }
201 
202     if (buf.size() < sizeof(ReadThermalParametersResponse))
203     {
204         return EINVAL;
205     }
206 
207     const auto* response =
208         reinterpret_cast<const ReadThermalParametersResponse*>(buf.data());
209 
210     uint16_t dataSize = le16toh(response->hdr.data_size);
211 
212     if (dataSize != sizeof(int32_t))
213     {
214         return EINVAL;
215     }
216 
217     threshold = le32toh(response->threshold);
218 
219     return 0;
220 }
221 
encodeGetPowerDrawRequest(PlatformEnvironmentalCommands commandCode,uint8_t instanceId,uint8_t sensorId,uint8_t averagingInterval,std::span<uint8_t> buf)222 int encodeGetPowerDrawRequest(PlatformEnvironmentalCommands commandCode,
223                               uint8_t instanceId, uint8_t sensorId,
224                               uint8_t averagingInterval, std::span<uint8_t> buf)
225 {
226     if (buf.size() < sizeof(GetPowerDrawRequest))
227     {
228         return EINVAL;
229     }
230 
231     auto* msg = reinterpret_cast<GetPowerDrawRequest*>(buf.data());
232 
233     ocp::accelerator_management::BindingPciVidInfo header{};
234     header.ocp_accelerator_management_msg_type =
235         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
236     header.instance_id = instanceId &
237                          ocp::accelerator_management::instanceIdBitMask;
238     header.msg_type = static_cast<uint8_t>(MessageType::PLATFORM_ENVIRONMENTAL);
239 
240     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
241 
242     if (rc != 0)
243     {
244         return rc;
245     }
246 
247     msg->hdr.command = static_cast<uint8_t>(commandCode);
248     msg->hdr.data_size = sizeof(sensorId) + sizeof(averagingInterval);
249     msg->sensorId = sensorId;
250     msg->averagingInterval = averagingInterval;
251 
252     return 0;
253 }
254 
decodeGetPowerDrawResponse(std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,uint32_t & power)255 int decodeGetPowerDrawResponse(std::span<const uint8_t> buf,
256                                ocp::accelerator_management::CompletionCode& cc,
257                                uint16_t& reasonCode, uint32_t& power)
258 {
259     auto rc =
260         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
261 
262     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
263     {
264         return rc;
265     }
266 
267     if (buf.size() < sizeof(GetPowerDrawResponse))
268     {
269         return EINVAL;
270     }
271 
272     const auto* response =
273         reinterpret_cast<const GetPowerDrawResponse*>(buf.data());
274 
275     const uint16_t dataSize = le16toh(response->hdr.data_size);
276 
277     if (dataSize != sizeof(uint32_t))
278     {
279         return EINVAL;
280     }
281 
282     power = le32toh(response->power);
283 
284     return 0;
285 }
286 
encodeGetCurrentEnergyCounterRequest(uint8_t instanceId,uint8_t sensorId,std::span<uint8_t> buf)287 int encodeGetCurrentEnergyCounterRequest(uint8_t instanceId, uint8_t sensorId,
288                                          std::span<uint8_t> buf)
289 {
290     if (buf.size() < sizeof(GetTemperatureReadingRequest))
291     {
292         return EINVAL;
293     }
294 
295     auto* msg = reinterpret_cast<GetCurrentEnergyCounterRequest*>(buf.data());
296 
297     ocp::accelerator_management::BindingPciVidInfo header{};
298     header.ocp_accelerator_management_msg_type =
299         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
300     header.instance_id = instanceId &
301                          ocp::accelerator_management::instanceIdBitMask;
302     header.msg_type = static_cast<uint8_t>(MessageType::PLATFORM_ENVIRONMENTAL);
303 
304     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
305 
306     if (rc != 0)
307     {
308         return rc;
309     }
310 
311     msg->hdr.command = static_cast<uint8_t>(
312         PlatformEnvironmentalCommands::GET_CURRENT_ENERGY_COUNTER);
313     msg->hdr.data_size = sizeof(sensorId);
314     msg->sensor_id = sensorId;
315 
316     return 0;
317 }
318 
decodeGetCurrentEnergyCounterResponse(std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,uint64_t & energy)319 int decodeGetCurrentEnergyCounterResponse(
320     std::span<const uint8_t> buf,
321     ocp::accelerator_management::CompletionCode& cc, uint16_t& reasonCode,
322     uint64_t& energy)
323 {
324     auto rc =
325         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
326 
327     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
328     {
329         return rc;
330     }
331 
332     if (buf.size() < sizeof(GetPowerDrawResponse))
333     {
334         return EINVAL;
335     }
336 
337     const auto* response =
338         reinterpret_cast<const GetCurrentEnergyCounterResponse*>(buf.data());
339 
340     const uint16_t dataSize = le16toh(response->hdr.data_size);
341 
342     if (dataSize != sizeof(uint64_t))
343     {
344         return EINVAL;
345     }
346 
347     energy = le32toh(response->energy);
348 
349     return 0;
350 }
351 
encodeGetVoltageRequest(uint8_t instanceId,uint8_t sensorId,std::span<uint8_t> buf)352 int encodeGetVoltageRequest(uint8_t instanceId, uint8_t sensorId,
353                             std::span<uint8_t> buf)
354 {
355     if (buf.size() < sizeof(GetVoltageRequest))
356     {
357         return EINVAL;
358     }
359 
360     auto* msg = reinterpret_cast<GetVoltageRequest*>(buf.data());
361 
362     ocp::accelerator_management::BindingPciVidInfo header{};
363     header.ocp_accelerator_management_msg_type =
364         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
365     header.instance_id = instanceId &
366                          ocp::accelerator_management::instanceIdBitMask;
367     header.msg_type = static_cast<uint8_t>(MessageType::PLATFORM_ENVIRONMENTAL);
368 
369     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
370 
371     if (rc != 0)
372     {
373         return rc;
374     }
375 
376     msg->hdr.command =
377         static_cast<uint8_t>(PlatformEnvironmentalCommands::GET_VOLTAGE);
378     msg->hdr.data_size = sizeof(sensorId);
379     msg->sensor_id = sensorId;
380 
381     return 0;
382 }
383 
decodeGetVoltageResponse(std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,uint32_t & voltage)384 int decodeGetVoltageResponse(std::span<const uint8_t> buf,
385                              ocp::accelerator_management::CompletionCode& cc,
386                              uint16_t& reasonCode, uint32_t& voltage)
387 {
388     auto rc =
389         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
390 
391     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
392     {
393         return rc;
394     }
395 
396     if (buf.size() < sizeof(GetVoltageResponse))
397     {
398         return EINVAL;
399     }
400 
401     const auto* response =
402         reinterpret_cast<const GetVoltageResponse*>(buf.data());
403 
404     const uint16_t dataSize = le16toh(response->hdr.data_size);
405 
406     if (dataSize != sizeof(uint32_t))
407     {
408         return EINVAL;
409     }
410 
411     voltage = le32toh(response->voltage);
412 
413     return 0;
414 }
415 
encodeGetInventoryInformationRequest(uint8_t instanceId,uint8_t propertyId,std::span<uint8_t> buf)416 int encodeGetInventoryInformationRequest(uint8_t instanceId, uint8_t propertyId,
417                                          std::span<uint8_t> buf)
418 {
419     if (buf.size() < sizeof(GetInventoryInformationRequest))
420     {
421         return EINVAL;
422     }
423 
424     auto* msg = reinterpret_cast<GetInventoryInformationRequest*>(buf.data());
425 
426     ocp::accelerator_management::BindingPciVidInfo header{};
427     header.ocp_accelerator_management_msg_type =
428         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
429     header.instance_id = instanceId &
430                          ocp::accelerator_management::instanceIdBitMask;
431     header.msg_type = static_cast<uint8_t>(MessageType::PLATFORM_ENVIRONMENTAL);
432 
433     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
434 
435     if (rc != 0)
436     {
437         return rc;
438     }
439 
440     msg->hdr.command = static_cast<uint8_t>(
441         PlatformEnvironmentalCommands::GET_INVENTORY_INFORMATION);
442     msg->hdr.data_size = sizeof(propertyId);
443     msg->property_id = propertyId;
444 
445     return 0;
446 }
447 
decodeGetInventoryInformationResponse(std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,InventoryPropertyId propertyId,InventoryValue & value)448 int decodeGetInventoryInformationResponse(
449     std::span<const uint8_t> buf,
450     ocp::accelerator_management::CompletionCode& cc, uint16_t& reasonCode,
451     InventoryPropertyId propertyId, InventoryValue& value)
452 {
453     auto rc =
454         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
455     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
456     {
457         return rc;
458     }
459     // Expect at least one byte of inventory response data after common response
460     if (buf.size() < (sizeof(ocp::accelerator_management::CommonResponse) + 1))
461     {
462         return EINVAL;
463     }
464 
465     const auto* response =
466         reinterpret_cast<const GetInventoryInformationResponse*>(buf.data());
467     uint16_t dataSize = le16toh(response->hdr.data_size);
468 
469     if (dataSize == 0 || dataSize > maxInventoryDataSize)
470     {
471         return EINVAL;
472     }
473 
474     const uint8_t* dataPtr = response->data.data();
475 
476     switch (propertyId)
477     {
478         case InventoryPropertyId::BOARD_PART_NUMBER:
479         case InventoryPropertyId::SERIAL_NUMBER:
480         case InventoryPropertyId::MARKETING_NAME:
481         case InventoryPropertyId::DEVICE_PART_NUMBER:
482             value =
483                 std::string(reinterpret_cast<const char*>(dataPtr), dataSize);
484             break;
485         case InventoryPropertyId::DEVICE_GUID:
486             value = std::vector<uint8_t>(dataPtr, dataPtr + dataSize);
487             break;
488         default:
489             return EINVAL;
490     }
491     return 0;
492 }
493 
encodeQueryScalarGroupTelemetryV2Request(uint8_t instanceId,PciePortType portType,uint8_t upstreamPortNumber,uint8_t portNumber,uint8_t groupId,std::span<uint8_t> buf)494 int encodeQueryScalarGroupTelemetryV2Request(
495     uint8_t instanceId, PciePortType portType, uint8_t upstreamPortNumber,
496     uint8_t portNumber, uint8_t groupId, std::span<uint8_t> buf)
497 {
498     if (buf.size() < sizeof(QueryScalarGroupTelemetryV2Request))
499     {
500         return EINVAL;
501     }
502 
503     auto* msg =
504         reinterpret_cast<QueryScalarGroupTelemetryV2Request*>(buf.data());
505 
506     ocp::accelerator_management::BindingPciVidInfo header{};
507     header.ocp_accelerator_management_msg_type =
508         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
509     header.instance_id = instanceId &
510                          ocp::accelerator_management::instanceIdBitMask;
511     header.msg_type = static_cast<uint8_t>(MessageType::PCIE_LINK);
512 
513     auto rc = packHeader(header, msg->hdr.msgHdr.hdr);
514 
515     if (rc != 0)
516     {
517         return rc;
518     }
519 
520     msg->hdr.command =
521         static_cast<uint8_t>(PcieLinkCommands::QueryScalarGroupTelemetryV2);
522     msg->hdr.data_size = 3;
523     msg->upstreamPortNumber =
524         (static_cast<uint8_t>(portType) << 7) | (upstreamPortNumber & 0x7F);
525     msg->portNumber = portNumber;
526     msg->groupId = groupId;
527 
528     return 0;
529 }
530 
decodeQueryScalarGroupTelemetryV2Response(std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,size_t & numTelemetryValues,std::vector<uint32_t> & telemetryValues)531 int decodeQueryScalarGroupTelemetryV2Response(
532     std::span<const uint8_t> buf,
533     ocp::accelerator_management::CompletionCode& cc, uint16_t& reasonCode,
534     size_t& numTelemetryValues, std::vector<uint32_t>& telemetryValues)
535 {
536     auto rc =
537         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
538 
539     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
540     {
541         return rc;
542     }
543 
544     if (buf.size() < sizeof(ocp::accelerator_management::CommonResponse))
545     {
546         return EINVAL;
547     }
548 
549     const auto* response =
550         reinterpret_cast<const ocp::accelerator_management::CommonResponse*>(
551             buf.data());
552 
553     const uint16_t dataSize = le16toh(response->data_size);
554 
555     if (buf.size() <
556         dataSize + sizeof(ocp::accelerator_management::CommonResponse))
557     {
558         return EINVAL;
559     }
560 
561     numTelemetryValues = dataSize / sizeof(uint32_t);
562 
563     if (telemetryValues.size() < numTelemetryValues)
564     {
565         telemetryValues.resize(numTelemetryValues);
566     }
567 
568     const auto* telemetryDataPtr =
569         buf.data() + sizeof(ocp::accelerator_management::CommonResponse);
570 
571     for (size_t i = 0; i < numTelemetryValues; i++)
572     {
573         std::memcpy(&telemetryValues[i],
574                     telemetryDataPtr + i * sizeof(uint32_t), sizeof(uint32_t));
575 
576         telemetryValues[i] = le32toh(telemetryValues[i]);
577     }
578 
579     return 0;
580 }
581 
encodeListPciePortsRequest(uint8_t instanceId,std::span<uint8_t> buf)582 int encodeListPciePortsRequest(uint8_t instanceId, std::span<uint8_t> buf)
583 {
584     if (buf.size() < sizeof(ocp::accelerator_management::CommonRequest))
585     {
586         return EINVAL;
587     }
588 
589     auto* msg = reinterpret_cast<ocp::accelerator_management::CommonRequest*>(
590         buf.data());
591 
592     ocp::accelerator_management::BindingPciVidInfo header{};
593     header.ocp_accelerator_management_msg_type =
594         static_cast<uint8_t>(ocp::accelerator_management::MessageType::REQUEST);
595     header.instance_id = instanceId &
596                          ocp::accelerator_management::instanceIdBitMask;
597     header.msg_type = static_cast<uint8_t>(MessageType::PCIE_LINK);
598 
599     auto rc = packHeader(header, msg->msgHdr.hdr);
600 
601     if (rc != 0)
602     {
603         return rc;
604     }
605 
606     msg->command = static_cast<uint8_t>(PcieLinkCommands::ListPCIePorts);
607     msg->data_size = 0;
608 
609     return 0;
610 }
611 
decodeListPciePortsResponse(std::span<const uint8_t> buf,ocp::accelerator_management::CompletionCode & cc,uint16_t & reasonCode,uint16_t & numUpstreamPorts,std::vector<uint8_t> & numDownstreamPorts)612 int decodeListPciePortsResponse(
613     std::span<const uint8_t> buf,
614     ocp::accelerator_management::CompletionCode& cc, uint16_t& reasonCode,
615     uint16_t& numUpstreamPorts, std::vector<uint8_t>& numDownstreamPorts)
616 {
617     auto rc =
618         ocp::accelerator_management::decodeReasonCodeAndCC(buf, cc, reasonCode);
619 
620     if (rc != 0 || cc != ocp::accelerator_management::CompletionCode::SUCCESS)
621     {
622         return rc;
623     }
624 
625     if (buf.size() < sizeof(ListPCIePortsResponse))
626     {
627         return EINVAL;
628     }
629 
630     const auto* response =
631         reinterpret_cast<const ListPCIePortsResponse*>(buf.data());
632 
633     const uint16_t dataSize = le16toh(response->hdr.data_size);
634 
635     if (dataSize < sizeof(uint16_t))
636     {
637         return EINVAL;
638     }
639 
640     uint16_t upstreamPorts = le16toh(response->numUpstreamPorts);
641 
642     numUpstreamPorts = 0;
643     numDownstreamPorts.clear();
644     numDownstreamPorts.reserve(upstreamPorts);
645 
646     size_t offset = sizeof(ListPCIePortsResponse);
647 
648     for (size_t i = 0; i < upstreamPorts; i++)
649     {
650         if (offset + sizeof(ListPCIePortsDownstreamPortsData) > buf.size())
651         {
652             return EINVAL;
653         }
654 
655         const auto* downstreamPortData =
656             reinterpret_cast<const ListPCIePortsDownstreamPortsData*>(
657                 buf.data() + offset);
658 
659         // Count only external upstream ports
660         if (downstreamPortData->isInternal == 0)
661         {
662             ++numUpstreamPorts;
663             numDownstreamPorts.push_back(downstreamPortData->count);
664         }
665 
666         offset += sizeof(ListPCIePortsDownstreamPortsData);
667     }
668 
669     return 0;
670 }
671 // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
672 } // namespace gpu
673