Add IPMI interface for CustomAccel service
Change-Id: I28a8976e382b457233ac521e9ab71f75abe029d1
Signed-off-by: Steve Foreman <foremans@google.com>
diff --git a/.gitignore b/.gitignore
index 327ea97..fe2e2c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
/build*/
/subprojects/*/
+/.cache/clangd/index/
+/.vscode/
diff --git a/README.md b/README.md
index eb79fd5..70fa020 100644
--- a/README.md
+++ b/README.md
@@ -235,3 +235,168 @@
|Byte(s) |Value |Data
|--------|-------|----
|0x00|0x0A|Subcommand
+
+### AccelOobDeviceCount - SubCommand 0x0B
+
+Query the number of available devices from the google-accel-oob service.
+
+If not enough data is proveded, `IPMI_CC_REQ_DATA_LEN_INVALID` is returned.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0B|Subcommand
+
+Response
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0B|Subcommand
+|0x01..0x04| |Number of devices available
+
+### AccelOobDeviceName - SubCommand 0x0C
+
+Query the name of a single device from the google-accel-oob service.
+
+This name is used as the identifier for the AccelOobRead and AccelOobWrite
+commands.
+
+Index values start at zero and go up to (but don't include) the device count.
+
+The name of the device is exactly as it appears in DBus, except for the common
+"/com/google/customAccel/" prefix. This prefix is removed to reduce the size of
+the IPMI packet.
+
+DBus requires all element names to be non-empty strings of ASCII characters
+"[A-Z][a-z][0-9]_", seperated by ASCII '/'. Therefore, all device names will be
+valid ASCII strings (1 byte/character).
+
+For convenience, the name string is followed by a single 0x00 (NULL terminator)
+byte which is not included as part of the length.
+
+The length field (byte 5) is the number of bytes in the name string (not
+including the trailing NULL terminator byte).
+
+The maximum length for any name is 43 bytes (not including the trailing NULL).
+
+If a name is longer than 43 bytes, `IPMI_CC_REQ_DATA_TRUNCATED` is returned.
+These names will not be usable in the rest of the API. Changing the name
+requires code changes to the `managed_acceld` service binary.
+
+If not enough data is proveded, `IPMI_CC_REQ_DATA_LEN_INVALID` is returned.
+
+If a name does not begin with the expected "/com/google/customAccel/" prefix,
+`IPMI_CC_INVALID` is returned. This indicates a change in the DBus API for the
+google-accel-oob service that requires a matching code change in the handler.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0C|Subcommand
+|0x05| |Length of the name
+|0x06..n| |Name of the device
+
+Response
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0C|Subcommand
+|0x01..0x04| |Index of the device
+|0x05| |Length of the name
+|0x06..n| |Name of the device
+
+### AccelOobRead - SubCommand 0x0D
+
+Read a PCIe CSR from a device.
+
+Length is the length of the name, in bytes.
+
+The device name gets prepended with "/com/google/customAccel/" and sent to DBus.
+This string must **NOT** have a trailing NULL terminator.
+
+The token is an arbitrary byte that gets echoed back in the reply; it is not
+interpreted by the service at all. This is used to disambiguate identical
+requests so clients can check for lost transactions.
+
+Address is the 64b PCIe address to read from.
+
+Number of bytes is the size of the read, in bytes (max 8). The value is subject
+to hardware limitations (both PCIe and ASIC), so it will generally be 1, 2, 4,
+or 8.
+
+The register data is always returned in 8 bytes (uint64) in little Endian order.
+If fewer than than 8 bytes are read, the MSBs are padded with 0s.
+
+On success, the response ends with the data read as a single uint64.
+
+If the number of bytes requested would not fit in a single IPMI payload,
+`IPMI_CC_REQUESTED_TOO_MANY_BYTES` is returned.
+
+If not enough data is proveded, `IPMI_CC_REQ_DATA_LEN_INVALID` is returned.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0D|Subcommand
+|0x01| |Number of bytes in the device name
+|0x02..n| |Name of the device (from `AccelOobDeviceName`)
+|n+1| |Token
+|n+2..n+10| |Address
+|n+11| |Number of bytes
+
+Response
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0D|Subcommand
+|0x01| |Number of bytes in the device name
+|0x02..n| |Name of the device (no trailing NULL)
+|n+1| |Token
+|n+2..n+10| |Address
+|n+11| |Number of bytes
+|n+12..n+20| |Data
+
+### AccelOobWrite - SubCommand 0x0E
+
+Write a PCIe CSR from a device.
+
+All parameters are identical to AccelOobRead (above). The only difference is
+the register data becomes an input parameter (in the Request) instead of an
+output value (in the Response).
+
+As with read, the register data must be 8 bytes (uint64) in little Endian order.
+If fewer than 8 bytes will be written, only the LSBs will be read and the the
+MSBs will be ignored.
+
+All fields returned in the Response are simply a copy of the Request.
+
+On success, `IPMI_CC_OK` is returned.
+
+If not enough data is proveded, `IPMI_CC_REQ_DATA_LEN_INVALID` is returned.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0D|Subcommand
+|0x01| |Number of bytes in the device name
+|0x02..n| |Name of the device (from `AccelOobDeviceName`)
+|n+1| |Token
+|n+2..n+10| |Address
+|n+11| |Number of bytes
+|n+12..n+20| |Data
+
+Response
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0D|Subcommand
+|0x01| |Number of bytes in the device name
+|0x02..n| |Name of the device (no trailing NULL)
+|n+1| |Token
+|n+2..n+10| |Address
+|n+11| |Number of bytes
+|n+12..n+20| |Data
diff --git a/commands.hpp b/commands.hpp
index 87333c5..8471185 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -43,6 +43,14 @@
SysGetFlashSize = 9,
// The Sys Host Power Off with disabled fallback watchdog
SysHostPowerOff = 10,
+ // Google CustomAccel service - get the number of devices available
+ SysAccelOobDeviceCount = 11,
+ // Google CustomAccel service - get the name of a single device
+ SysAccelOobDeviceName = 12,
+ // Google CustomAccel service - read from a device
+ SysAccelOobRead = 13,
+ // Google CustomAccel service - write to a device
+ SysAccelOobWrite = 14,
};
} // namespace ipmi
diff --git a/google_accel_oob.cpp b/google_accel_oob.cpp
new file mode 100644
index 0000000..e0fa615
--- /dev/null
+++ b/google_accel_oob.cpp
@@ -0,0 +1,282 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "google_accel_oob.hpp"
+
+#include "commands.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <sdbusplus/bus.hpp>
+#include <span>
+#include <string>
+#include <vector>
+
+namespace google
+{
+namespace ipmi
+{
+
+#ifndef MAX_IPMI_BUFFER
+#define MAX_IPMI_BUFFER 64
+#endif
+
+// token + address(8) + num_bytes + data(8) + len + NULL
+constexpr size_t MAX_NAME_SIZE = MAX_IPMI_BUFFER - 1 - 8 - 1 - 8 - 1 - 1;
+
+Resp accelOobDeviceCount(std::span<const uint8_t> data,
+ HandlerInterface* handler)
+{
+ struct Request
+ {
+ } __attribute__((packed));
+
+ struct Reply
+ {
+ uint32_t count;
+ } __attribute__((packed));
+
+ if (data.size_bytes() < sizeof(Request))
+ {
+ std::fprintf(stderr, "AccelOob DeviceCount command too small: %zu\n",
+ data.size_bytes());
+ return ::ipmi::responseReqDataLenInvalid();
+ }
+
+ if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
+ {
+ std::fprintf(stderr,
+ "AccelOob DeviceCount command too large for reply buffer: "
+ "command=%zuB, payload=%zuB, max=%dB\n",
+ data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
+ return ::ipmi::responseReqDataLenExceeded();
+ }
+
+ uint32_t count = handler->accelOobDeviceCount();
+
+ std::vector<uint8_t> replyBuf(sizeof(Reply));
+ auto* reply = reinterpret_cast<Reply*>(replyBuf.data());
+ reply->count = count;
+
+ return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobDeviceCount,
+ replyBuf);
+}
+
+Resp accelOobDeviceName(std::span<const uint8_t> data,
+ HandlerInterface* handler)
+{
+ struct Request
+ {
+ uint32_t index;
+ } __attribute__((packed));
+
+ struct Reply
+ {
+ uint8_t nameLength;
+ char name[MAX_NAME_SIZE];
+ } __attribute__((packed));
+
+ if (data.size_bytes() < sizeof(Request))
+ {
+ std::fprintf(stderr, "AccelOob DeviceName command too small: %zu\n",
+ data.size_bytes());
+ return ::ipmi::responseReqDataLenInvalid();
+ }
+
+ if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
+ {
+ std::fprintf(stderr,
+ "AccelOob DeviceName command too large for reply buffer: "
+ "command=%zuB, payload=%zuB, max=%dB\n",
+ data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
+ return ::ipmi::responseReqDataLenExceeded();
+ }
+
+ auto* req = reinterpret_cast<const Request*>(data.data());
+ std::string name = handler->accelOobDeviceName(req->index);
+
+ if (name.size() > MAX_NAME_SIZE)
+ {
+ std::fprintf(stderr,
+ "AccelOob: name was too long. "
+ "'%s' len must be <= %zu\n",
+ name.c_str(), MAX_NAME_SIZE);
+ return ::ipmi::responseReqDataTruncated();
+ }
+
+ std::vector<uint8_t> replyBuf(data.size_bytes() + sizeof(Reply));
+ std::copy(data.begin(), data.end(), replyBuf.data());
+ auto* reply = reinterpret_cast<Reply*>(replyBuf.data() + data.size_bytes());
+ reply->nameLength = name.length();
+ memcpy(reply->name, name.c_str(), reply->nameLength + 1);
+
+ return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobDeviceName,
+ replyBuf);
+}
+
+namespace
+{
+
+struct NameHeader
+{
+ uint8_t nameLength;
+ char name[MAX_NAME_SIZE];
+} __attribute__((packed));
+
+// Reads the variable-length name from reqBuf and outputs the name and a pointer
+// to the payload (next byte after name).
+//
+// Returns: =0: success.
+// >0: if dataLen is too small, returns the minimum buffers size.
+//
+// Params:
+// [in] reqBuf - the request buffer
+// [in] dataLen - the length of reqBuf, in bytes
+// [in] payloadSize - the size of the expected payload
+// [out] name - the name string
+// [out] payload - pointer into reqBuf just after name
+size_t ReadNameHeader(const uint8_t* reqBuf, size_t dataLen, size_t payloadSize,
+ std::string* name, const uint8_t** payload)
+{
+ constexpr size_t kNameHeaderSize = sizeof(NameHeader) - MAX_NAME_SIZE;
+
+ auto* req_header = reinterpret_cast<const NameHeader*>(reqBuf);
+
+ size_t minDataLen = kNameHeaderSize + payloadSize + req_header->nameLength;
+ if (dataLen < minDataLen)
+ {
+ return minDataLen;
+ }
+
+ if (name)
+ {
+ *name = std::string(req_header->name, req_header->nameLength);
+ }
+ if (payload)
+ {
+ *payload = reqBuf + kNameHeaderSize + req_header->nameLength;
+ }
+ return 0;
+}
+
+} // namespace
+
+Resp accelOobRead(std::span<const uint8_t> data, HandlerInterface* handler)
+{
+ struct Request
+ {
+ // Variable length header, handled by ReadNameHeader
+ // uint8_t nameLength; // <= MAX_NAME_SIZE
+ // char name[nameLength];
+
+ // Additional arguments
+ uint8_t token;
+ uint64_t address;
+ uint8_t num_bytes;
+ } __attribute__((packed));
+
+ struct Reply
+ {
+ uint64_t data;
+ } __attribute__((packed));
+
+ std::fprintf(stderr,
+ "AccelOob Read command sizes: "
+ "command=%zuB, payload=%zuB, max=%dB\n",
+ data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
+
+ std::string name;
+ const uint8_t* payload;
+
+ size_t min_size = ReadNameHeader(data.data(), data.size_bytes(),
+ sizeof(Request), &name, &payload);
+ if (min_size != 0)
+ {
+ std::fprintf(stderr, "AccelOob Read command too small: %zuB < %zuB\n",
+ data.size_bytes(), min_size);
+ return ::ipmi::responseReqDataLenInvalid();
+ }
+
+ if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
+ {
+ std::fprintf(stderr,
+ "AccelOob Read command too large for reply buffer: "
+ "command=%zuB, payload=%zuB, max=%dB\n",
+ data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
+ return ::ipmi::responseReqDataLenExceeded();
+ }
+
+ auto req = reinterpret_cast<const Request*>(payload);
+ uint64_t r = handler->accelOobRead(name, req->address, req->num_bytes);
+
+ std::vector<uint8_t> replyBuf(data.size_bytes() + sizeof(Reply));
+ std::copy(data.begin(), data.end(), replyBuf.data());
+ auto* reply = reinterpret_cast<Reply*>(replyBuf.data() + data.size_bytes());
+ reply->data = r;
+
+ return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobRead, replyBuf);
+}
+
+Resp accelOobWrite(std::span<const uint8_t> data, HandlerInterface* handler)
+{
+ struct Request
+ {
+ // Variable length header, handled by ReadNameHeader
+ // uint8_t nameLength; // <= MAX_NAME_SIZE
+ // char name[nameLength];
+
+ // Additional arguments
+ uint8_t token;
+ uint64_t address;
+ uint8_t num_bytes;
+ uint64_t data;
+ } __attribute__((packed));
+
+ struct Reply
+ {
+ // Empty
+ } __attribute__((packed));
+
+ std::string name{};
+ const uint8_t* payload;
+
+ size_t min_size = ReadNameHeader(data.data(), data.size_bytes(),
+ sizeof(Request), &name, &payload);
+ if (min_size != 0)
+ {
+ std::fprintf(stderr, "AccelOob Write command too small: %zuB < %zuB\n",
+ data.size_bytes(), min_size);
+ return ::ipmi::responseReqDataLenInvalid();
+ }
+
+ if (data.size_bytes() + sizeof(Reply) > MAX_IPMI_BUFFER)
+ {
+ std::fprintf(stderr,
+ "AccelOob Write command too large for reply buffer: "
+ "command=%zuB, payload=%zuB, max=%dB\n",
+ data.size_bytes(), sizeof(Reply), MAX_IPMI_BUFFER);
+ return ::ipmi::responseReqDataLenExceeded();
+ }
+
+ auto req = reinterpret_cast<const Request*>(payload);
+ handler->accelOobWrite(name, req->address, req->num_bytes, req->data);
+
+ std::vector<uint8_t> replyBuf(data.size_bytes() + sizeof(Reply));
+ std::copy(data.begin(), data.end(), replyBuf.data());
+
+ return ::ipmi::responseSuccess(SysOEMCommands::SysAccelOobWrite, replyBuf);
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/google_accel_oob.hpp b/google_accel_oob.hpp
new file mode 100644
index 0000000..2624f2b
--- /dev/null
+++ b/google_accel_oob.hpp
@@ -0,0 +1,40 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "handler.hpp"
+
+#include <ipmid/api.h>
+
+#include <span>
+
+namespace google
+{
+namespace ipmi
+{
+
+// Handle the Accel OOB device count command
+Resp accelOobDeviceCount(std::span<const uint8_t> data,
+ HandlerInterface* handler);
+
+Resp accelOobDeviceName(std::span<const uint8_t> data,
+ HandlerInterface* handler);
+
+Resp accelOobRead(std::span<const uint8_t> data, HandlerInterface* handler);
+
+Resp accelOobWrite(std::span<const uint8_t> data, HandlerInterface* handler);
+
+} // namespace ipmi
+} // namespace google
diff --git a/handler.cpp b/handler.cpp
index c716af6..8ec7863 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
#include <string>
#include <string_view>
#include <tuple>
+#include <variant>
#include <xyz/openbmc_project/Common/error.hpp>
#ifndef NCSI_IF_NAME
@@ -392,5 +393,211 @@
return _pcie_i2c_map[entry];
}
+namespace
+{
+
+static constexpr std::string_view ACCEL_OOB_ROOT = "/com/google/customAccel/";
+static constexpr char ACCEL_OOB_SERVICE[] = "com.google.custom_accel";
+static constexpr char ACCEL_OOB_INTERFACE[] = "com.google.custom_accel.BAR";
+
+// C type for "a{oa{sa{sv}}}" from DBus.ObjectManager::GetManagedObjects()
+using AnyType = std::variant<std::string, uint8_t, uint32_t, uint64_t>;
+using AnyTypeList = std::vector<std::pair<std::string, AnyType>>;
+using NamedArrayOfAnyTypeLists =
+ std::vector<std::pair<std::string, AnyTypeList>>;
+using ArrayOfObjectPathsAndTieredAnyTypeLists = std::vector<
+ std::pair<sdbusplus::message::object_path, NamedArrayOfAnyTypeLists>>;
+
+} // namespace
+
+sdbusplus::bus::bus Handler::accelOobGetDbus() const
+{
+ return sdbusplus::bus::new_default();
+}
+
+uint32_t Handler::accelOobDeviceCount() const
+{
+ ArrayOfObjectPathsAndTieredAnyTypeLists data;
+
+ try
+ {
+ auto bus = accelOobGetDbus();
+ auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/",
+ "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+ bus.call(method).read(data);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>(
+ "Failed to call GetManagedObjects on com.google.custom_accel",
+ entry("WHAT=%s", ex.what()));
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+
+ return data.size();
+}
+
+std::string Handler::accelOobDeviceName(size_t index) const
+{
+ ArrayOfObjectPathsAndTieredAnyTypeLists data;
+
+ try
+ {
+ auto bus = accelOobGetDbus();
+ auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/",
+ "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+ bus.call(method).read(data);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>(
+ "Failed to call GetManagedObjects on com.google.custom_accel",
+ entry("WHAT=%s", ex.what()));
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+
+ if (index >= data.size())
+ {
+ log<level::WARNING>(
+ "Requested index is larger than the number of entries.",
+ entry("INDEX=%zu", index), entry("NUM_NAMES=%zu", data.size()));
+ throw IpmiException(IPMI_CC_PARM_OUT_OF_RANGE);
+ }
+
+ std::string_view name(data[index].first.str);
+ if (!name.starts_with(ACCEL_OOB_ROOT))
+ {
+ throw IpmiException(IPMI_CC_INVALID);
+ }
+ name.remove_prefix(ACCEL_OOB_ROOT.length());
+ return std::string(name);
+}
+
+uint64_t Handler::accelOobRead(std::string_view name, uint64_t address,
+ uint8_t num_bytes) const
+{
+ static constexpr char ACCEL_OOB_METHOD[] = "Read";
+
+ std::string object_name(ACCEL_OOB_ROOT);
+ object_name.append(name);
+
+ auto bus = accelOobGetDbus();
+ auto method = bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(),
+ ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD);
+ method.append(address, static_cast<uint64_t>(num_bytes));
+
+ std::vector<uint8_t> bytes;
+
+ try
+ {
+ bus.call(method).read(bytes);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>("Failed to call Read on com.google.custom_accel",
+ entry("WHAT=%s", ex.what()),
+ entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE),
+ entry("DBUS_OBJECT=%s", object_name.c_str()),
+ entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE),
+ entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD),
+ entry("DBUS_ARG_ADDRESS=%016llx", address),
+ entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes));
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+
+ if (bytes.size() < num_bytes)
+ {
+ log<level::ERR>(
+ "Call to Read on com.google.custom_accel didn't return the expected"
+ " number of bytes.",
+ entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE),
+ entry("DBUS_OBJECT=%s", object_name.c_str()),
+ entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE),
+ entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD),
+ entry("DBUS_ARG_ADDRESS=%016llx", address),
+ entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes),
+ entry("DBUS_RETURN_SIZE=%zu", bytes.size()));
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+
+ if (bytes.size() > sizeof(uint64_t))
+ {
+ log<level::ERR>(
+ "Call to Read on com.google.custom_accel returned more than 8B.",
+ entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE),
+ entry("DBUS_OBJECT=%s", object_name.c_str()),
+ entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE),
+ entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD),
+ entry("DBUS_ARG_ADDRESS=%016llx", address),
+ entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes),
+ entry("DBUS_RETURN_SIZE=%zu", bytes.size()));
+ throw IpmiException(IPMI_CC_REQ_DATA_TRUNCATED);
+ }
+
+ uint64_t data = 0;
+ for (size_t i = 0; i < num_bytes; ++i)
+ {
+ data = (data << 8) | bytes[i];
+ }
+
+ return data;
+}
+
+void Handler::accelOobWrite(std::string_view name, uint64_t address,
+ uint8_t num_bytes, uint64_t data) const
+{
+ static constexpr std::string_view ACCEL_OOB_METHOD = "Write";
+
+ std::string object_name(ACCEL_OOB_ROOT);
+ object_name.append(name);
+
+ if (num_bytes > sizeof(data))
+ {
+ log<level::ERR>(
+ "Call to Write on com.google.custom_accel requested more than 8B.",
+ entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE),
+ entry("DBUS_OBJECT=%s", object_name.c_str()),
+ entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE),
+ entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()),
+ entry("DBUS_ARG_ADDRESS=%016llx", address),
+ entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes),
+ entry("DBUS_ARG_DATA=%016llx", data));
+ throw IpmiException(IPMI_CC_PARM_OUT_OF_RANGE);
+ }
+
+ std::vector<uint8_t> bytes;
+ bytes.reserve(num_bytes);
+ for (size_t i = 0; i < num_bytes; ++i)
+ {
+ bytes.emplace_back(data & 0xff);
+ data >>= 8;
+ }
+
+ try
+ {
+ auto bus = accelOobGetDbus();
+ auto method =
+ bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(),
+ ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD.data());
+ method.append(address, bytes);
+ bus.call_noreply(method);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>("Failed to call Write on com.google.custom_accel",
+ entry("WHAT=%s", ex.what()),
+ entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE),
+ entry("DBUS_OBJECT=%s", object_name.c_str()),
+ entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE),
+ entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()),
+ entry("DBUS_ARG_ADDRESS=%016llx", address),
+ entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes),
+ entry("DBUS_ARG_DATA=%016llx", data));
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+}
+
} // namespace ipmi
} // namespace google
diff --git a/handler.hpp b/handler.hpp
index 26950e9..8c36c52 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
#include <map>
#include <span>
#include <string>
+#include <string_view>
#include <tuple>
#include <vector>
@@ -136,6 +137,56 @@
* @throw IpmiException on failure.
*/
virtual void hostPowerOffDelay(std::uint32_t delay) const = 0;
+
+ /**
+ * Return the number of devices from the CustomAccel service.
+ *
+ * @return the number of devices.
+ * @throw IpmiException on failure.
+ */
+ virtual uint32_t accelOobDeviceCount() const = 0;
+
+ /**
+ * Return the name of a single device from the CustomAccel service.
+ *
+ * Valid indexes start at 0 and go up to (but don't include) the number of
+ * devices. The number of devices can be queried with accelOobDeviceCount.
+ *
+ * @param[in] index - the index of the device, starting at 0.
+ * @return the name of the device.
+ * @throw IpmiException on failure.
+ */
+ virtual std::string accelOobDeviceName(size_t index) const = 0;
+
+ /**
+ * Read from a single CustomAccel service device.
+ *
+ * Valid device names can be queried with accelOobDeviceName.
+ * If num_bytes < 8, all unused MSBs are padded with 0s.
+ *
+ * @param[in] name - the name of the device (from DeviceName).
+ * @param[in] address - the address to read from.
+ * @param[in] num_bytes - the size of the read, in bytes.
+ * @return the data read, with 0s padding any unused MSBs.
+ * @throw IpmiException on failure.
+ */
+ virtual uint64_t accelOobRead(std::string_view name, uint64_t address,
+ uint8_t num_bytes) const = 0;
+
+ /**
+ * Write to a single CustomAccel service device.
+ *
+ * Valid device names can be queried with accelOobDeviceName.
+ * If num_bytes < 8, all unused MSBs are ignored.
+ *
+ * @param[in] name - the name of the device (from DeviceName).
+ * @param[in] address - the address to read from.
+ * @param[in] num_bytes - the size of the read, in bytes.
+ * @param[in] data - the data to write.
+ * @throw IpmiException on failure.
+ */
+ virtual void accelOobWrite(std::string_view name, uint64_t address,
+ uint8_t num_bytes, uint64_t data) const = 0;
};
} // namespace ipmi
diff --git a/handler_impl.hpp b/handler_impl.hpp
index 88a901b..f46e79c 100644
--- a/handler_impl.hpp
+++ b/handler_impl.hpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,7 +19,9 @@
#include <cstdint>
#include <map>
#include <nlohmann/json.hpp>
+#include <sdbusplus/bus.hpp>
#include <string>
+#include <string_view>
#include <tuple>
#include <vector>
@@ -53,6 +55,17 @@
std::tuple<std::uint32_t, std::string>
getI2cEntry(unsigned int entry) const override;
+ uint32_t accelOobDeviceCount() const override;
+ std::string accelOobDeviceName(size_t index) const override;
+ uint64_t accelOobRead(std::string_view name, uint64_t address,
+ uint8_t num_bytes) const override;
+ void accelOobWrite(std::string_view name, uint64_t address,
+ uint8_t num_bytes, uint64_t data) const override;
+
+ protected:
+ // Exposed for dependency injection
+ virtual sdbusplus::bus::bus accelOobGetDbus() const;
+
private:
std::string _configFile;
diff --git a/ipmi.cpp b/ipmi.cpp
index 1f760f4..715264b 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
#include "entity_name.hpp"
#include "eth.hpp"
#include "flash_size.hpp"
+#include "google_accel_oob.hpp"
#include "handler.hpp"
#include "host_power_off.hpp"
#include "machine_name.hpp"
@@ -67,6 +68,14 @@
return getFlashSize(data, handler);
case SysHostPowerOff:
return hostPowerOff(data, handler);
+ case SysAccelOobDeviceCount:
+ return accelOobDeviceCount(data, handler);
+ case SysAccelOobDeviceName:
+ return accelOobDeviceName(data, handler);
+ case SysAccelOobRead:
+ return accelOobRead(data, handler);
+ case SysAccelOobWrite:
+ return accelOobWrite(data, handler);
default:
std::fprintf(stderr, "Invalid subcommand: 0x%x\n", cmd);
return ::ipmi::responseInvalidCommand();
diff --git a/meson.build b/meson.build
index d6f5c96..ee34798 100644
--- a/meson.build
+++ b/meson.build
@@ -40,6 +40,7 @@
'ipmi.cpp',
'machine_name.cpp',
'pcie_i2c.cpp',
+ 'google_accel_oob.cpp',
'psu.cpp',
'util.cpp',
implicit_include_directories: false,
diff --git a/test/google_accel_oob_unittest.cpp b/test/google_accel_oob_unittest.cpp
new file mode 100644
index 0000000..163b602
--- /dev/null
+++ b/test/google_accel_oob_unittest.cpp
@@ -0,0 +1,239 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "commands.hpp"
+#include "errors.hpp"
+#include "google_accel_oob.hpp"
+#include "handler_mock.hpp"
+
+#include <ipmid/api.h>
+
+#include <gtest/gtest.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+using ::testing::Return;
+
+TEST(GoogleAccelOobTest, DeviceCount_Success)
+{
+ ::testing::StrictMock<HandlerMock> h;
+
+ uint8_t reqBuf[1]; // Could be 0, but zero-length arrays are an extension
+
+ struct Reply
+ {
+ uint32_t count;
+ } __attribute__((packed));
+
+ constexpr uint32_t kTestDeviceCount = 2;
+
+ EXPECT_CALL(h, accelOobDeviceCount()).WillOnce(Return(kTestDeviceCount));
+
+ Resp r = accelOobDeviceCount(reqBuf, &h);
+
+ const auto response = std::get<0>(r);
+ EXPECT_EQ(response, IPMI_CC_OK);
+
+ const auto payload = std::get<1>(r);
+ ASSERT_EQ(payload.has_value(), true);
+ const auto payload_tuple = payload.value();
+ const auto reply_cmd = std::get<0>(payload_tuple);
+ EXPECT_EQ(reply_cmd, SysAccelOobDeviceCount);
+ const auto reply_buff = std::get<1>(payload_tuple);
+ ASSERT_EQ(reply_buff.size(), sizeof(Reply));
+
+ auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+ EXPECT_EQ(reply->count, kTestDeviceCount);
+}
+
+TEST(GoogleAccelOobTest, DeviceName_Success)
+{
+ ::testing::StrictMock<HandlerMock> h;
+
+ struct Request
+ {
+ uint32_t index;
+ } __attribute__((packed));
+
+ struct Reply
+ {
+ uint32_t index;
+ uint8_t length;
+ char name[1];
+ } __attribute__((packed));
+
+ constexpr uint32_t kTestDeviceIndex = 0;
+ const std::string kTestDeviceName("testDeviceName");
+
+ EXPECT_CALL(h, accelOobDeviceName(kTestDeviceIndex))
+ .WillOnce(Return(kTestDeviceName));
+
+ Request reqBuf{kTestDeviceIndex};
+ Resp r = accelOobDeviceName(
+ std::span(reinterpret_cast<const uint8_t*>(&reqBuf), sizeof(Request)),
+ &h);
+
+ const auto response = std::get<0>(r);
+ EXPECT_EQ(response, IPMI_CC_OK);
+
+ const auto payload = std::get<1>(r);
+ ASSERT_EQ(payload.has_value(), true);
+ const auto payload_tuple = payload.value();
+ const auto reply_cmd = std::get<0>(payload_tuple);
+ EXPECT_EQ(reply_cmd, SysAccelOobDeviceName);
+ const auto reply_buff = std::get<1>(payload_tuple);
+ ASSERT_GE(reply_buff.size(), sizeof(Reply));
+
+ auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+ EXPECT_EQ(reply->index, kTestDeviceIndex);
+ EXPECT_EQ(reply->length, kTestDeviceName.length());
+ EXPECT_STREQ(reply->name, kTestDeviceName.c_str());
+}
+
+TEST(GoogleAccelOobTest, Read_Success)
+{
+ ::testing::StrictMock<HandlerMock> h;
+
+ constexpr char kTestDeviceName[] = "testDeviceName";
+ constexpr uint8_t kTestDeviceNameLength =
+ (sizeof(kTestDeviceName) / sizeof(*kTestDeviceName)) - 1;
+ constexpr uint8_t kTestToken = 0xAB;
+ constexpr uint64_t kTestAddress = 0;
+ constexpr uint8_t kTestReadSize = 8;
+ constexpr uint64_t kTestData = 0x12345678;
+
+ struct Request
+ {
+ uint8_t nameLength;
+ char name[kTestDeviceNameLength];
+ uint8_t token;
+ uint64_t address;
+ uint8_t num_bytes;
+ } __attribute__((packed));
+
+ struct Reply
+ {
+ uint8_t nameLength;
+ char name[kTestDeviceNameLength];
+ uint8_t token;
+ uint64_t address;
+ uint8_t num_bytes;
+ uint64_t data;
+ } __attribute__((packed));
+
+ const std::string_view kTestDeviceNameStr(kTestDeviceName,
+ kTestDeviceNameLength);
+ EXPECT_CALL(h,
+ accelOobRead(kTestDeviceNameStr, kTestAddress, kTestReadSize))
+ .WillOnce(Return(kTestData));
+
+ Request reqBuf{kTestDeviceNameLength, "", kTestToken, kTestAddress,
+ kTestReadSize};
+ memcpy(reqBuf.name, kTestDeviceName, kTestDeviceNameLength);
+ Resp r = accelOobRead(
+ std::span(reinterpret_cast<const uint8_t*>(&reqBuf), sizeof(Request)),
+ &h);
+
+ const auto response = std::get<0>(r);
+ EXPECT_EQ(response, IPMI_CC_OK);
+
+ const auto payload = std::get<1>(r);
+ ASSERT_EQ(payload.has_value(), true);
+ const auto payload_tuple = payload.value();
+ const auto reply_cmd = std::get<0>(payload_tuple);
+ EXPECT_EQ(reply_cmd, SysAccelOobRead);
+ const auto reply_buff = std::get<1>(payload_tuple);
+ ASSERT_GE(reply_buff.size(), sizeof(Reply));
+
+ auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+ EXPECT_EQ(reply->nameLength, kTestDeviceNameLength);
+ EXPECT_EQ(std::string_view(reply->name, reply->nameLength),
+ kTestDeviceNameStr);
+ EXPECT_EQ(reply->token, kTestToken);
+ EXPECT_EQ(reply->address, kTestAddress);
+ EXPECT_EQ(reply->num_bytes, kTestReadSize);
+ EXPECT_EQ(reply->data, kTestData);
+}
+
+TEST(GoogleAccelOobTest, Write_Success)
+{
+ ::testing::StrictMock<HandlerMock> h;
+
+ constexpr char kTestDeviceName[] = "testDeviceName";
+ constexpr uint8_t kTestDeviceNameLength =
+ (sizeof(kTestDeviceName) / sizeof(*kTestDeviceName)) - 1;
+ constexpr uint8_t kTestToken = 0xAB;
+ constexpr uint64_t kTestAddress = 0;
+ constexpr uint8_t kTestWriteSize = 8;
+ constexpr uint64_t kTestData = 0x12345678;
+
+ struct Request
+ {
+ uint8_t nameLength;
+ char name[kTestDeviceNameLength];
+ uint8_t token;
+ uint64_t address;
+ uint8_t num_bytes;
+ uint64_t data;
+ } __attribute__((packed));
+
+ struct Reply
+ {
+ uint8_t nameLength;
+ char name[kTestDeviceNameLength];
+ uint8_t token;
+ uint64_t address;
+ uint8_t num_bytes;
+ uint64_t data;
+ } __attribute__((packed));
+
+ const std::string_view kTestDeviceNameStr(kTestDeviceName,
+ kTestDeviceNameLength);
+ EXPECT_CALL(h, accelOobWrite(kTestDeviceNameStr, kTestAddress,
+ kTestWriteSize, kTestData))
+ .WillOnce(Return());
+
+ Request reqBuf{kTestDeviceNameLength, "", kTestToken, kTestAddress,
+ kTestWriteSize, kTestData};
+ memcpy(reqBuf.name, kTestDeviceName, kTestDeviceNameLength);
+ Resp r = accelOobWrite(
+ std::span(reinterpret_cast<const uint8_t*>(&reqBuf), sizeof(Request)),
+ &h);
+
+ const auto response = std::get<0>(r);
+ EXPECT_EQ(response, IPMI_CC_OK);
+
+ const auto payload = std::get<1>(r);
+ ASSERT_EQ(payload.has_value(), true);
+ const auto payload_tuple = payload.value();
+ const auto reply_cmd = std::get<0>(payload_tuple);
+ EXPECT_EQ(reply_cmd, SysAccelOobWrite);
+ const auto reply_buff = std::get<1>(payload_tuple);
+ ASSERT_GE(reply_buff.size(), sizeof(Reply));
+
+ auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+ EXPECT_EQ(reply->nameLength, kTestDeviceNameLength);
+ EXPECT_EQ(std::string_view(reply->name, reply->nameLength),
+ kTestDeviceNameStr);
+ EXPECT_EQ(reply->token, kTestToken);
+ EXPECT_EQ(reply->address, kTestAddress);
+ EXPECT_EQ(reply->num_bytes, kTestWriteSize);
+ EXPECT_EQ(reply->data, kTestData);
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index d393bde..2fb8e2a 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <string>
+#include <string_view>
#include <tuple>
#include <gmock/gmock.h>
@@ -53,6 +54,14 @@
MOCK_METHOD((std::tuple<std::uint32_t, std::string>), getI2cEntry,
(unsigned int), (const, override));
MOCK_METHOD(void, hostPowerOffDelay, (std::uint32_t), (const, override));
+
+ MOCK_METHOD(uint32_t, accelOobDeviceCount, (), (const, override));
+ MOCK_METHOD(std::string, accelOobDeviceName, (size_t), (const, override));
+ MOCK_METHOD(uint64_t, accelOobRead, (std::string_view, uint64_t, uint8_t),
+ (const, override));
+ MOCK_METHOD(void, accelOobWrite,
+ (std::string_view, uint64_t, uint8_t, uint64_t),
+ (const, override));
};
} // namespace ipmi
diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
index 1dda3ce..418ba46 100644
--- a/test/handler_unittest.cpp
+++ b/test/handler_unittest.cpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,8 +16,12 @@
#include "handler.hpp"
#include "handler_impl.hpp"
+#include <systemd/sd-bus.h>
+
#include <fstream>
#include <nlohmann/json.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/test/sdbus_mock.hpp>
#include <string>
#include <tuple>
@@ -106,6 +110,482 @@
(void)std::remove(testFilename);
}
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::IsNull;
+using ::testing::MatcherCast;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::Return;
+using ::testing::ReturnNull;
+using ::testing::SafeMatcherCast;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::StrictMock;
+using ::testing::StrNe;
+using ::testing::WithArg;
+
+class MockDbusHandler : public Handler
+{
+ public:
+ MockDbusHandler(sdbusplus::SdBusMock& mock,
+ const std::string& config = "") :
+ Handler(config),
+ mock_(&mock)
+ {
+ }
+
+ protected:
+ sdbusplus::bus::bus accelOobGetDbus() const override
+ {
+ return sdbusplus::get_mocked_new(
+ const_cast<sdbusplus::SdBusMock*>(mock_));
+ }
+
+ private:
+ sdbusplus::SdBusMock* mock_;
+};
+
+ACTION_TEMPLATE(AssignReadVal, HAS_1_TEMPLATE_PARAMS(typename, T),
+ AND_1_VALUE_PARAMS(val))
+{
+ *static_cast<T*>(arg2) = val;
+}
+
+ACTION_P(TraceDbus, msg)
+{
+ std::fprintf(stderr, "%s\n", msg);
+}
+
+ACTION_P(TraceDbus2, msg)
+{
+ std::fprintf(stderr, "%s(%02x)\n", msg, *static_cast<const uint8_t*>(arg2));
+}
+
+constexpr char object_path[] = "/com/google/customAccel/test/path";
+constexpr char property_grpc[] = "com.google.custom_accel.gRPC";
+constexpr char value_port[] = "Port";
+constexpr uint32_t port = 5000;
+
+constexpr char SD_BUS_TYPE_BYTE_STR[] = {SD_BUS_TYPE_BYTE, '\0'};
+
+// Returns an object that looks like:
+// "/com/google/customAccel/test/path": {
+// "com.google.custom_accel.gRPC" : {
+// "Port" : {
+// "type" : "u",
+// "data" : 5000
+// }
+// }
+// }
+void ExpectGetManagedObjects(StrictMock<sdbusplus::SdBusMock>& mock,
+ const char* obj_path = object_path)
+{
+ ::testing::InSequence s;
+
+ // These must be nullptr or sd_bus_message_unref will seg fault.
+ constexpr sd_bus_message* method = nullptr;
+ constexpr sd_bus_message* msg = nullptr;
+
+ EXPECT_CALL(mock, sd_bus_message_new_method_call(
+ _, // sd_bus *bus,
+ NotNull(), // sd_bus_message **m
+ StrEq("com.google.custom_accel"), StrEq("/"),
+ StrEq("org.freedesktop.DBus.ObjectManager"),
+ StrEq("GetManagedObjects")))
+ .WillOnce(DoAll(SetArgPointee<1>(method), Return(0)));
+
+ EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
+ method, // sd_bus_message *m
+ _, // uint64_t timeout
+ NotNull(), // sd_bus_error *ret_error
+ NotNull())) // sd_bus_message **reply
+ .WillOnce(DoAll(SetArgPointee<3>(SD_BUS_ERROR_NULL),
+ SetArgPointee<4>(msg), // reply
+ Return(0)));
+
+ EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+ StrEq("{oa{sa{sv}}}")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
+
+ EXPECT_CALL(mock, sd_bus_message_enter_container(
+ msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("oa{sa{sv}}")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_read_basic(msg, SD_BUS_TYPE_OBJECT_PATH,
+ NotNull()))
+ .WillOnce(DoAll(AssignReadVal<const char*>(obj_path), Return(1)));
+
+ EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+ StrEq("{sa{sv}}")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
+
+ EXPECT_CALL(mock, sd_bus_message_enter_container(
+ msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("sa{sv}")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock,
+ sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, NotNull()))
+ .WillOnce(DoAll(AssignReadVal<const char*>(property_grpc), Return(1)));
+
+ EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+ StrEq("{sv}")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
+
+ EXPECT_CALL(mock, sd_bus_message_enter_container(
+ msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("sv")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock,
+ sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, NotNull()))
+ .WillOnce(DoAll(AssignReadVal<const char*>(value_port), Return(1)));
+
+ EXPECT_CALL(
+ mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrNe("u")))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(0));
+
+ EXPECT_CALL(
+ mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrEq("u")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_VARIANT,
+ StrEq("u")))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock,
+ sd_bus_message_read_basic(msg, SD_BUS_TYPE_UINT32, NotNull()))
+ .WillOnce(DoAll(AssignReadVal<uint32_t>(port), Return(0)));
+
+ EXPECT_CALL(
+ mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrNe("u")))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(0));
+
+ EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+ .WillOnce(Return(1))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+ .WillOnce(Return(1))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+ .WillOnce(Return(1))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_exit_container(msg)).WillOnce(Return(1));
+}
+
+void ExpectSdBusError(StrictMock<sdbusplus::SdBusMock>& mock)
+{
+ EXPECT_CALL(mock, sd_bus_message_new_method_call(
+ _, // sd_bus *bus,
+ NotNull(), // sd_bus_message **m
+ StrEq("com.google.custom_accel"), StrEq("/"),
+ StrEq("org.freedesktop.DBus.ObjectManager"),
+ StrEq("GetManagedObjects")))
+ .WillOnce(Return(-ENOTCONN));
+}
+
+TEST(HandlerTest, accelOobDeviceCount_Success)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+ ExpectGetManagedObjects(mock);
+ EXPECT_EQ(1, h.accelOobDeviceCount());
+}
+
+TEST(HandlerTest, accelOobDeviceCount_Fail)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+ ExpectSdBusError(mock);
+ EXPECT_THROW(h.accelOobDeviceCount(), IpmiException);
+}
+
+TEST(HandlerTest, accelOobDeviceName_Success)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+ ExpectGetManagedObjects(mock);
+ EXPECT_EQ(std::string("test/path"), h.accelOobDeviceName(0));
+}
+
+TEST(HandlerTest, accelOobDeviceName_Fail)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+ ExpectSdBusError(mock);
+ EXPECT_THROW(h.accelOobDeviceName(0), IpmiException);
+}
+
+TEST(HandlerTest, accelOobDeviceName_OutOfRange)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+ ExpectGetManagedObjects(mock);
+ EXPECT_THROW(h.accelOobDeviceName(1), IpmiException);
+}
+
+TEST(HandlerTest, accelOobDeviceName_InvalidName)
+{
+ constexpr char bad_object_path[] = "/com/google/customAccel2/bad/path";
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+ ExpectGetManagedObjects(mock, bad_object_path);
+ EXPECT_THROW(h.accelOobDeviceName(0), IpmiException);
+}
+
+constexpr uint8_t NUM_BYTES_RETURNED_EQ_NUM_BYTES = 0xff;
+void ExpectRead(StrictMock<sdbusplus::SdBusMock>& mock, uint64_t address,
+ uint8_t num_bytes, uint64_t data, int sd_bus_call_return_value,
+ uint8_t num_bytes_returned = NUM_BYTES_RETURNED_EQ_NUM_BYTES)
+{
+ ::testing::InSequence s;
+
+ // These must be nullptr or sd_bus_message_unref will seg fault.
+ constexpr sd_bus_message* method = nullptr;
+ constexpr sd_bus_message* msg = nullptr;
+
+ EXPECT_CALL(mock, sd_bus_message_new_method_call(
+ _, // sd_bus *bus,
+ NotNull(), // sd_bus_message **m
+ StrEq("com.google.custom_accel"),
+ StrEq("/com/google/customAccel/test/path"),
+ StrEq("com.google.custom_accel.BAR"), StrEq("Read")))
+ .WillOnce(DoAll(SetArgPointee<1>(method), Return(0)));
+
+ EXPECT_CALL(
+ mock, sd_bus_message_append_basic(
+ method, SD_BUS_TYPE_UINT64,
+ MatcherCast<const void*>(
+ SafeMatcherCast<const uint64_t*>(Pointee(Eq(address))))))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock,
+ sd_bus_message_append_basic(
+ method, SD_BUS_TYPE_UINT64,
+ MatcherCast<const void*>(SafeMatcherCast<const uint64_t*>(
+ Pointee(Eq<uint64_t>(num_bytes))))))
+ .WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
+ method, // sd_bus_message *m
+ _, // uint64_t timeout
+ NotNull(), // sd_bus_error *ret_error
+ NotNull())) // sd_bus_message **reply
+ .WillOnce(DoAll(SetArgPointee<3>(SD_BUS_ERROR_NULL),
+ SetArgPointee<4>(msg), // reply
+ Return(sd_bus_call_return_value)));
+
+ if (sd_bus_call_return_value >= 0)
+ {
+ EXPECT_CALL(mock,
+ sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+ StrEq(SD_BUS_TYPE_BYTE_STR)))
+ .WillOnce(Return(1));
+
+ if (num_bytes_returned == NUM_BYTES_RETURNED_EQ_NUM_BYTES)
+ {
+ num_bytes_returned = num_bytes;
+ }
+ for (auto i = num_bytes_returned - 1; i >= 0; --i)
+ {
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0))
+ .WillOnce(Return(0));
+
+ const uint8_t byte = (data >> (8 * i)) & 0xff;
+ EXPECT_CALL(mock, sd_bus_message_read_basic(msg, SD_BUS_TYPE_BYTE,
+ NotNull()))
+ .WillOnce(DoAll(AssignReadVal<uint8_t>(byte), Return(1)));
+ }
+
+ EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+ EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+ .WillOnce(Return(1));
+ }
+}
+
+TEST(HandlerTest, accelOobRead_Success)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ constexpr uint64_t address = 0x123456789abcdef;
+ constexpr uint8_t num_bytes = sizeof(uint64_t);
+ constexpr int sd_bus_call_return_value = 1;
+ constexpr uint64_t data = 0x13579bdf02468ace;
+
+ ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value);
+ EXPECT_EQ(data, h.accelOobRead("test/path", address, num_bytes));
+}
+
+TEST(HandlerTest, accelOobRead_Fail)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ constexpr uint64_t address = 0x123456789abcdef;
+ constexpr uint8_t num_bytes = sizeof(uint64_t);
+ constexpr int sd_bus_call_return_value = -ENOTCONN;
+ constexpr uint64_t data = 0x13579bdf02468ace;
+
+ ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value);
+ EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
+ IpmiException);
+}
+
+TEST(HandlerTest, accelOobRead_TooFewBytesReturned)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ constexpr uint64_t address = 0x123456789abcdef;
+ constexpr uint8_t num_bytes = sizeof(uint64_t);
+ constexpr int sd_bus_call_return_value = 1;
+ constexpr uint64_t data = 0x13579bdf02468ace;
+ constexpr uint8_t num_bytes_returned = num_bytes - 1;
+
+ ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value,
+ num_bytes_returned);
+ EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
+ IpmiException);
+}
+
+TEST(HandlerTest, accelOobRead_TooManyBytesReturned)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ constexpr uint64_t address = 0x123456789abcdef;
+ constexpr uint8_t num_bytes = sizeof(uint64_t);
+ constexpr int sd_bus_call_return_value = 1;
+ constexpr uint64_t data = 0x13579bdf02468ace;
+ constexpr uint8_t num_bytes_returned = sizeof(uint64_t) + 1;
+
+ ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value,
+ num_bytes_returned);
+ EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
+ IpmiException);
+}
+
+void ExpectWrite(StrictMock<sdbusplus::SdBusMock>& mock, uint64_t address,
+ uint8_t num_bytes, uint64_t data, int sd_bus_call_return_value)
+{
+ ::testing::InSequence s;
+
+ // These must be nullptr or sd_bus_message_unref will seg fault.
+ constexpr sd_bus_message* method = nullptr;
+
+ EXPECT_CALL(mock, sd_bus_message_new_method_call(
+ _, // sd_bus *bus,
+ NotNull(), // sd_bus_message **m
+ StrEq("com.google.custom_accel"),
+ StrEq("/com/google/customAccel/test/path"),
+ StrEq("com.google.custom_accel.BAR"), StrEq("Write")))
+ .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
+ SetArgPointee<1>(method), Return(0)));
+
+ EXPECT_CALL(
+ mock, sd_bus_message_append_basic(
+ method, SD_BUS_TYPE_UINT64,
+ MatcherCast<const void*>(
+ SafeMatcherCast<const uint64_t*>(Pointee(Eq(address))))))
+ .WillOnce(DoAll(TraceDbus("sd_bus_message_append_basic(address) -> 1"),
+ Return(1)));
+
+ EXPECT_CALL(mock,
+ sd_bus_message_open_container(method, SD_BUS_TYPE_ARRAY,
+ StrEq(SD_BUS_TYPE_BYTE_STR)))
+ .WillOnce(DoAll(TraceDbus("sd_bus_message_open_container(a, y) -> 0"),
+ Return(0)));
+
+ for (auto i = 0; i < num_bytes; ++i)
+ {
+ const uint8_t byte = (data >> (8 * i)) & 0xff;
+
+ EXPECT_CALL(
+ mock, sd_bus_message_append_basic(
+ method, SD_BUS_TYPE_BYTE,
+ MatcherCast<const void*>(
+ SafeMatcherCast<const uint8_t*>(Pointee(Eq(byte))))))
+ .WillOnce(
+ DoAll(TraceDbus2("sd_bus_message_append_basic"), Return(1)));
+ }
+
+ EXPECT_CALL(mock, sd_bus_message_close_container(method))
+ .WillOnce(DoAll(TraceDbus("sd_bus_message_close_container() -> 0"),
+ Return(0)));
+
+ EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
+ method, // sd_bus_message *m
+ _, // uint64_t timeout
+ NotNull(), // sd_bus_error *ret_error
+ IsNull())) // sd_bus_message **reply
+ .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
+ SetArgPointee<3>(SD_BUS_ERROR_NULL),
+ Return(sd_bus_call_return_value)));
+}
+
+TEST(HandlerTest, accelOobWrite_Success)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ constexpr uint64_t address = 0x123456789abcdef;
+ constexpr uint8_t num_bytes = sizeof(uint64_t);
+ constexpr int sd_bus_call_return_value = 1;
+ constexpr uint64_t data = 0x13579bdf02468ace;
+
+ ExpectWrite(mock, address, num_bytes, data, sd_bus_call_return_value);
+ EXPECT_NO_THROW(h.accelOobWrite("test/path", address, num_bytes, data));
+}
+
+TEST(HandlerTest, accelOobRead_TooManyBytesRequested)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ constexpr uint64_t address = 0x123456789abcdef;
+ constexpr uint8_t num_bytes = sizeof(uint64_t) + 1;
+ constexpr uint64_t data = 0x13579bdf02468ace;
+
+ EXPECT_THROW(h.accelOobWrite("test/path", address, num_bytes, data),
+ IpmiException);
+}
+
+TEST(HandlerTest, accelOobWrite_Fail)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ constexpr uint64_t address = 0x123456789abcdef;
+ constexpr uint8_t num_bytes = sizeof(uint64_t);
+ constexpr int sd_bus_call_return_value = -ENOTCONN;
+ constexpr uint64_t data = 0x13579bdf02468ace;
+
+ ExpectWrite(mock, address, num_bytes, data, sd_bus_call_return_value);
+ EXPECT_THROW(h.accelOobWrite("test/path", address, num_bytes, data),
+ IpmiException);
+}
+
// TODO: Add checks for other functions of handler.
} // namespace ipmi
diff --git a/test/meson.build b/test/meson.build
index a9da8b9..9c579bd 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -21,6 +21,7 @@
'entity',
'eth',
'flash',
+ 'google_accel_oob',
'handler',
'machine',
'pcie',