libpldm: Add PLDM control responder
This will respond to the mandatory PLDM control command types.
Change-Id: I483bfdb6513cc6ec77a04480397993e42160c0ae
Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index adcbb06..5d21dea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -67,6 +67,9 @@
10. Add firmware update FD responder
+11. Add PLDM control responder. PLDM types and support commands/versions can be
+ registered.
+
### Fixed
1. dsp: platform: Fix location of closing paren in overflow detection
diff --git a/include/libpldm/base.h b/include/libpldm/base.h
index 5b31d41..5c44b99 100644
--- a/include/libpldm/base.h
+++ b/include/libpldm/base.h
@@ -108,7 +108,9 @@
#define PLDM_GET_VERSION_REQ_BYTES 6
/* Response lengths are inclusive of completion code */
+#define PLDM_GET_TYPES_REQ_BYTES 0
#define PLDM_GET_TYPES_RESP_BYTES 9
+#define PLDM_GET_TID_REQ_BYTES 0
#define PLDM_GET_TID_RESP_BYTES 2
#define PLDM_SET_TID_RESP_BYTES 1
#define PLDM_GET_COMMANDS_RESP_BYTES 33
diff --git a/include/libpldm/control.h b/include/libpldm/control.h
new file mode 100644
index 0000000..fbe7104
--- /dev/null
+++ b/include/libpldm/control.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <libpldm/pldm.h>
+#include <libpldm/base.h>
+#include <libpldm/utils.h>
+
+enum pldm_control_completion_codes {
+ PLDM_CONTROL_INVALID_DATA_TRANSFER_HANDLE = 0x80,
+ PLDM_CONTROL_INVALID_TRANSFER_OPERATION_FLAG = 0x81,
+ PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA = 0x83,
+ PLDM_CONTROL_INVALID_PLDM_VERSION_IN_REQUEST_DATA = 0x84,
+};
+
+// Static storage can be allocated with PLDM_SIZEOF_CONTROL macro */
+struct pldm_control;
+
+/** @brief Handle a PLDM Control message
+ *
+ * @param[in] control
+ * @param[in] req_msg - PLDM incoming request message payload
+ * @param[in] req_len - length of req_msg buffer
+ * @param[out] resp_msg - PLDM outgoing response message payload buffer
+ * @param[inout] resp_len - length of available resp_msg buffer, will be updated
+ * with the length written to resp_msg.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ *
+ * Will provide a response to send when resp_len > 0 and returning 0.
+ */
+int pldm_control_handle_msg(struct pldm_control *control, const void *req_msg,
+ size_t req_len, void *resp_msg, size_t *resp_len);
+
+/** @brief Initialise a struct pldm_control
+ *
+ * @param[in] control
+ * @param[in] pldm_control_size - pass PLDM_SIZEOF_CONTROL
+ *
+ * @return 0 on success, a negative errno value on failure.
+ */
+int pldm_control_setup(struct pldm_control *control, size_t pldm_control_size);
+
+/** @brief Add a PLDM type to report.
+ *
+ * @param[in] control
+ * @param[in] type - PLDM type, enum pldm_supported_types
+ * @param[in] versions - list of versions for GetPLDMVersion response.
+ * This is an array of 32-bit version values, followed by
+ * a CRC32 over the version values. The size of this buffer
+ * is 4*versions_count. The versions buffer must remain
+ * present for the duration of the pldm_control's lifetime.
+ * @param[in] versions_count - number of entries in versions, including the trailing CRC32.
+ * @param[in] commands - pointer to an array of bitfield8_t[8], for GetPLDMCommands
+ * response for this type. The buffer must remain
+ * present for the duration of the pldm_control's lifetime.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ */
+int pldm_control_add_type(struct pldm_control *control, uint8_t pldm_type,
+ const void *versions, size_t versions_count,
+ const bitfield8_t *commands);
diff --git a/include/libpldm/meson.build b/include/libpldm/meson.build
index a95cb56..2887f0b 100644
--- a/include/libpldm/meson.build
+++ b/include/libpldm/meson.build
@@ -4,6 +4,7 @@
'bios.h',
'bios_table.h',
'compiler.h',
+ 'control.h',
'entity.h',
'firmware_fd.h',
'firmware_update.h',
@@ -47,8 +48,16 @@
prefix: '#include "firmware_device/fd-internal.h"',
include_directories: [include_src, libpldm_include_dir],
)
+sizeof_pldm_control = compiler.sizeof(
+ 'struct pldm_control',
+ prefix: '#include "control-internal.h"',
+ include_directories: [include_src, libpldm_include_dir],
+)
sizes_h = configure_file(
- configuration: {'sizeof_pldm_fd': sizeof_pldm_fd},
+ configuration: {
+ 'sizeof_pldm_fd': sizeof_pldm_fd,
+ 'sizeof_pldm_control': sizeof_pldm_control,
+ },
input: 'sizes.h.in',
output: 'sizes.h',
install: true,
diff --git a/include/libpldm/sizes.h.in b/include/libpldm/sizes.h.in
index 85c0330..b6287cf 100644
--- a/include/libpldm/sizes.h.in
+++ b/include/libpldm/sizes.h.in
@@ -2,3 +2,6 @@
/* sizeof(struct pldm_fd) */
#define PLDM_SIZEOF_PLDM_FD @sizeof_pldm_fd@
+
+/* sizeof(struct pldm_control) */
+#define PLDM_SIZEOF_PLDM_CONTROL @sizeof_pldm_control@
diff --git a/src/control-internal.h b/src/control-internal.h
new file mode 100644
index 0000000..00b05b7
--- /dev/null
+++ b/src/control-internal.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <libpldm/pldm.h>
+#include <libpldm/utils.h>
+#include <compiler.h>
+#include <msgbuf.h>
+
+#ifndef PLDM_CONTROL_MAX_VERSION_TYPES
+#define PLDM_CONTROL_MAX_VERSION_TYPES 6
+#endif
+
+struct pldm_type_versions {
+ /* A buffer of ver32_t/uint32_t of version values, followed by crc32 */
+ /* NULL for unused entries */
+ const void *versions;
+ /* Includes the trailing crc32 entry */
+ uint8_t versions_count;
+
+ /* A buffer of 32 entries, for commands 0-0xff */
+ const bitfield8_t *commands;
+
+ uint8_t pldm_type;
+};
+
+struct pldm_control {
+ struct pldm_type_versions types[PLDM_CONTROL_MAX_VERSION_TYPES];
+};
diff --git a/src/control.c b/src/control.c
new file mode 100644
index 0000000..f9b1c59
--- /dev/null
+++ b/src/control.c
@@ -0,0 +1,340 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <libpldm/pldm.h>
+#include <libpldm/utils.h>
+#include <libpldm/platform.h>
+#include <libpldm/control.h>
+#include <compiler.h>
+#include <msgbuf.h>
+
+#include "control-internal.h"
+
+#define PLDM_BASE_VERSIONS_COUNT 2
+static const uint32_t PLDM_BASE_VERSIONS[PLDM_BASE_VERSIONS_COUNT] = {
+ /* PLDM 1.1.0 is current implemented. */
+ 0xf1f1f000,
+ /* CRC. Calculated with python:
+ hex(crccheck.crc.Crc32.calc(struct.pack('<I', 0xf1f1f000)))
+ */
+ 0x539dbeba,
+};
+const bitfield8_t PLDM_CONTROL_COMMANDS[32] = {
+ // 0x00..0x07
+ { .byte = (1 << PLDM_GET_TID | 1 << PLDM_GET_PLDM_VERSION |
+ 1 << PLDM_GET_PLDM_TYPES | 1 << PLDM_GET_PLDM_COMMANDS) }
+};
+
+static int pldm_control_reply_error(uint8_t ccode,
+ const struct pldm_header_info *req_hdr,
+ struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ int rc;
+
+ /* 1 byte completion code */
+ if (*resp_payload_len < 1) {
+ return -EOVERFLOW;
+ }
+ *resp_payload_len = 1;
+
+ rc = encode_cc_only_resp(req_hdr->instance, PLDM_FWUP, req_hdr->command,
+ ccode, resp);
+ if (rc != PLDM_SUCCESS) {
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pldm_control_get_tid(const struct pldm_header_info *hdr,
+ const struct pldm_msg *req LIBPLDM_CC_UNUSED,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ if (req_payload_len != PLDM_GET_TID_REQ_BYTES) {
+ return pldm_control_reply_error(PLDM_ERROR_INVALID_LENGTH, hdr,
+ resp, resp_payload_len);
+ }
+
+ if (*resp_payload_len <= PLDM_GET_TID_RESP_BYTES) {
+ return -EOVERFLOW;
+ }
+ *resp_payload_len = PLDM_GET_TID_RESP_BYTES;
+
+ uint8_t cc = encode_get_tid_resp(hdr->instance, PLDM_SUCCESS,
+ PLDM_TID_UNASSIGNED, resp);
+ if (cc) {
+ return pldm_control_reply_error(cc, hdr, resp,
+ resp_payload_len);
+ }
+ return 0;
+}
+
+static int pldm_control_get_version(struct pldm_control *control,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req,
+ size_t req_payload_len,
+ struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ uint8_t cc;
+
+ uint32_t handle;
+ uint8_t opflag;
+ uint8_t type;
+ cc = decode_get_version_req(req, req_payload_len, &handle, &opflag,
+ &type);
+ if (cc) {
+ return pldm_control_reply_error(cc, hdr, resp,
+ resp_payload_len);
+ }
+
+ /* Response is always sent as a single transfer */
+ if (opflag != PLDM_GET_FIRSTPART) {
+ return pldm_control_reply_error(
+ PLDM_CONTROL_INVALID_TRANSFER_OPERATION_FLAG, hdr, resp,
+ resp_payload_len);
+ }
+
+ const struct pldm_type_versions *v = NULL;
+ for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
+ if (control->types[i].pldm_type == type &&
+ control->types[i].versions) {
+ v = &control->types[i];
+ break;
+ }
+ }
+
+ if (!v) {
+ return pldm_control_reply_error(
+ PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA, hdr,
+ resp, resp_payload_len);
+ }
+
+ /* encode_get_version_resp doesn't have length checking */
+ uint32_t required_resp_payload =
+ 1 + 4 + 1 + v->versions_count * sizeof(ver32_t);
+ if (*resp_payload_len < required_resp_payload) {
+ return -EOVERFLOW;
+ }
+ *resp_payload_len = required_resp_payload;
+
+ /* crc32 is included in the versions buffer */
+ cc = encode_get_version_resp(hdr->instance, PLDM_SUCCESS, 0,
+ PLDM_START_AND_END, v->versions,
+ v->versions_count * sizeof(ver32_t), resp);
+ if (cc) {
+ return pldm_control_reply_error(cc, hdr, resp,
+ resp_payload_len);
+ }
+ return 0;
+}
+
+static int pldm_control_get_types(struct pldm_control *control,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req LIBPLDM_CC_UNUSED,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ uint8_t cc;
+
+ if (req_payload_len != PLDM_GET_TYPES_REQ_BYTES) {
+ return pldm_control_reply_error(PLDM_ERROR_INVALID_LENGTH, hdr,
+ resp, resp_payload_len);
+ }
+
+ bitfield8_t types[8];
+ memset(types, 0, sizeof(types));
+ for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
+ uint8_t ty = control->types[i].pldm_type;
+ if (ty < 64 && control->types[i].versions) {
+ uint8_t bit = 1 << (ty % 8);
+ types[ty / 8].byte |= bit;
+ }
+ }
+
+ /* encode_get_types_resp doesn't have length checking */
+ uint32_t required_resp_payload = 1 + 8;
+ if (*resp_payload_len < required_resp_payload) {
+ return -EOVERFLOW;
+ }
+ *resp_payload_len = required_resp_payload;
+
+ cc = encode_get_types_resp(hdr->instance, PLDM_SUCCESS, types, resp);
+ if (cc) {
+ return pldm_control_reply_error(cc, hdr, resp,
+ resp_payload_len);
+ }
+ return 0;
+}
+
+static int pldm_control_get_commands(struct pldm_control *control,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req,
+ size_t req_payload_len,
+ struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ uint8_t cc;
+
+ uint8_t ty;
+ // version in request is ignored, since SelectPLDMVersion isn't supported currently
+ ver32_t version;
+ cc = decode_get_commands_req(req, req_payload_len, &ty, &version);
+ if (cc) {
+ return pldm_control_reply_error(cc, hdr, resp,
+ resp_payload_len);
+ }
+
+ const struct pldm_type_versions *v = NULL;
+ for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
+ if (control->types[i].pldm_type == ty &&
+ control->types[i].versions && control->types[i].commands) {
+ v = &control->types[i];
+ break;
+ }
+ }
+
+ if (!v) {
+ return pldm_control_reply_error(
+ PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA, hdr,
+ resp, resp_payload_len);
+ }
+
+ /* encode_get_commands_resp doesn't have length checking */
+ uint32_t required_resp_payload = 1 + 32;
+ if (*resp_payload_len < required_resp_payload) {
+ return -EOVERFLOW;
+ }
+ *resp_payload_len = required_resp_payload;
+
+ cc = encode_get_commands_resp(hdr->instance, PLDM_SUCCESS, v->commands,
+ resp);
+ if (cc) {
+ return pldm_control_reply_error(cc, hdr, resp,
+ resp_payload_len);
+ }
+ return 0;
+}
+
+/* A response should only be used when this returns 0, and *resp_len > 0 */
+LIBPLDM_ABI_TESTING
+int pldm_control_handle_msg(struct pldm_control *control, const void *req_msg,
+ size_t req_len, void *resp_msg, size_t *resp_len)
+{
+ int rc;
+
+ /* Space for header plus completion code */
+ if (*resp_len < sizeof(struct pldm_msg_hdr) + 1) {
+ return -EOVERFLOW;
+ }
+ size_t resp_payload_len = *resp_len - sizeof(struct pldm_msg_hdr);
+ struct pldm_msg *resp = resp_msg;
+
+ if (req_len < sizeof(struct pldm_msg_hdr)) {
+ return -EOVERFLOW;
+ }
+ size_t req_payload_len = req_len - sizeof(struct pldm_msg_hdr);
+ const struct pldm_msg *req = req_msg;
+
+ struct pldm_header_info hdr;
+ rc = unpack_pldm_header(&req->hdr, &hdr);
+ if (rc != PLDM_SUCCESS) {
+ return -EINVAL;
+ }
+
+ if (hdr.pldm_type != PLDM_BASE) {
+ /* Caller should not have passed non-control */
+ return -ENOMSG;
+ }
+
+ if (hdr.msg_type != PLDM_REQUEST) {
+ return -EINVAL;
+ }
+
+ /* Dispatch command */
+ switch (hdr.command) {
+ case PLDM_GET_TID:
+ rc = pldm_control_get_tid(&hdr, req, req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_GET_PLDM_VERSION:
+ rc = pldm_control_get_version(control, &hdr, req,
+ req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_GET_PLDM_TYPES:
+ rc = pldm_control_get_types(control, &hdr, req, req_payload_len,
+ resp, &resp_payload_len);
+ break;
+ case PLDM_GET_PLDM_COMMANDS:
+ rc = pldm_control_get_commands(control, &hdr, req,
+ req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ default:
+ rc = pldm_control_reply_error(PLDM_ERROR_UNSUPPORTED_PLDM_CMD,
+ &hdr, resp, &resp_payload_len);
+ }
+
+ if (rc == 0) {
+ *resp_len = resp_payload_len + sizeof(struct pldm_msg_hdr);
+ }
+
+ return rc;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_control_setup(struct pldm_control *control, size_t pldm_control_size)
+{
+ int rc;
+
+ if (pldm_control_size < sizeof(struct pldm_control)) {
+ return -EINVAL;
+ }
+
+ memset(control, 0, sizeof(struct pldm_control));
+
+ rc = pldm_control_add_type(control, PLDM_BASE, &PLDM_BASE_VERSIONS,
+ PLDM_BASE_VERSIONS_COUNT,
+ PLDM_CONTROL_COMMANDS);
+ if (rc) {
+ return rc;
+ }
+
+ return 0;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_control_add_type(struct pldm_control *control, uint8_t pldm_type,
+ const void *versions, size_t versions_count,
+ const bitfield8_t *commands)
+{
+ if (versions_count < 2) {
+ /* At least one version must be provided, along with a CRC32 */
+ return -EINVAL;
+ }
+
+ struct pldm_type_versions *v = NULL;
+ for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
+ if (control->types[i].versions == NULL ||
+ (control->types[i].versions != NULL &&
+ control->types[i].pldm_type == pldm_type)) {
+ v = &control->types[i];
+ break;
+ }
+ }
+
+ if (!v) {
+ return -ENOMEM;
+ // No spare slots
+ }
+
+ v->pldm_type = pldm_type;
+ v->versions = versions;
+ v->versions_count = versions_count;
+ v->commands = commands;
+
+ return 0;
+}
diff --git a/src/meson.build b/src/meson.build
index 3c8afc1..7803d91 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,4 +1,4 @@
-libpldm_sources = files('responder.c', 'utils.c')
+libpldm_sources = files('control.c', 'responder.c', 'utils.c')
subdir('dsp')