tools: Add pd, a test tool for libpldm
As per the comment in the implementation:
> `grep 'p.*l.*d.*m' /usr/share/dict/words` found 'palladium', which has
> element symbol Pd.
Change-Id: Idbfb15e3382cccb0fde48dc2d5ee8de14fc30816
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
diff --git a/meson.build b/meson.build
index 6f2b11f..f50975c 100644
--- a/meson.build
+++ b/meson.build
@@ -77,6 +77,7 @@
subdir('include')
subdir('src')
+subdir('tools')
doxygen = find_program('doxygen', required: false)
if doxygen.found()
diff --git a/tests/data/test.hex b/tests/data/test.hex
new file mode 100644
index 0000000..d53b202
--- /dev/null
+++ b/tests/data/test.hex
@@ -0,0 +1,10 @@
+12 44 d2 64 8d 7d 47 18 a0 30 fc 8a 56 58 7d 5a
+02 94 00 00 e9 07 03 0b 16 03 00 00 00 00 76 02
+08 00 01 04 74 65 73 74 01 18 00 01 00 00 00 00
+01 04 00 00 01 76 30 2e 31 01 00 04 00 9c 01 00
+00 01 18 00 01 00 00 00 00 01 04 00 00 02 76 31
+2e 30 01 00 04 00 9c 01 00 00 02 00 0a 00 00 00
+ff ff ff ff 00 00 01 00 94 00 00 00 01 00 00 00
+01 04 76 30 2e 32 0a 00 00 00 ff ff ff ff 00 00
+01 00 95 00 00 00 01 00 00 00 01 04 76 32 2e 30
+d3 5c 1c 8a 5a a5
diff --git a/tools/meson.build b/tools/meson.build
new file mode 100644
index 0000000..d047267
--- /dev/null
+++ b/tools/meson.build
@@ -0,0 +1,3 @@
+if get_option('abi').contains('testing')
+ executable('pd', 'pd.c', dependencies: [libpldm_dep])
+endif
diff --git a/tools/pd.c b/tools/pd.c
new file mode 100644
index 0000000..fe63a70
--- /dev/null
+++ b/tools/pd.c
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+/* Copyright 2025 Code Construct */
+
+/*
+ * `grep 'p.*l.*d.*m' /usr/share/dict/words` found 'palladium', which has
+ * element symbol Pd.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <inttypes.h>
+#include <libpldm/firmware_update.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define PD_PACKAGE_BUFFER (1ul * 1024ul * 1024ul)
+
+static void pd_print_bytes(const char *head, const void *_buf, size_t len,
+ const char *tail)
+{
+ const uint8_t *buf = _buf;
+
+ if (head) {
+ printf("%s", head);
+ }
+
+ while (len-- > 1) {
+ printf("%02" PRIx8 " ", *buf++);
+ }
+
+ printf("%02" PRIx8, *buf++);
+
+ if (tail) {
+ printf("%s", tail);
+ }
+}
+
+static void pd_print_variable_field(const char *head,
+ const struct variable_field *field,
+ const char *tail)
+{
+ if (head) {
+ printf("%s", head);
+ }
+
+ if (field && field->ptr) {
+ fwrite(field->ptr, 1, field->length, stdout);
+ }
+
+ if (tail) {
+ printf("%s", tail);
+ }
+}
+
+static void pd_print_typed_string(const char *head, size_t type,
+ const struct variable_field *string,
+ const char *tail)
+{
+ switch (type) {
+ case 1:
+ pd_print_variable_field(head, string, tail);
+ break;
+ default:
+ printf("Unsupported string type: %zu\n", type);
+ break;
+ }
+}
+
+static void pd_print_descriptor(const char *head,
+ const struct pldm_descriptor *desc,
+ const char *tail)
+{
+ if (head) {
+ printf("%s", head);
+ }
+
+ assert(desc);
+ switch (desc->descriptor_type) {
+ case 1: {
+ uint32_t pen;
+
+ memcpy(&pen, desc->descriptor_data, sizeof(pen));
+ pen = le32toh(pen);
+ printf("IANA PEN: %" PRIu32, pen);
+ break;
+ }
+ default:
+ printf("Unsupported descriptor type: %" PRIu16,
+ desc->descriptor_type);
+ break;
+ }
+
+ if (tail) {
+ printf("%s", tail);
+ }
+}
+
+int main(void)
+{
+ struct pldm_package_downstream_device_id_record ddrec;
+ struct pldm_package_component_image_information info;
+ struct pldm_package_firmware_device_id_record fdrec;
+ DEFINE_PLDM_PACKAGE_FORMAT_PIN_FR02H(pin);
+ pldm_package_header_information_pad hdr;
+ struct pldm_package_iter iter;
+ size_t nr_fdrecs = 0;
+ size_t nr_ddrecs = 0;
+ size_t nr_infos = 0;
+ void *package;
+ int status;
+ size_t in;
+ int rc;
+
+ status = EXIT_SUCCESS;
+ package = calloc(PD_PACKAGE_BUFFER, 1);
+ if (!package) {
+ err(EXIT_FAILURE, "malloc");
+ }
+
+ in = fread(package, 1, PD_PACKAGE_BUFFER, stdin);
+ rc = decode_pldm_firmware_update_package(package, in, &pin, &hdr,
+ &iter);
+ if (rc < 0) {
+ warnx("Failed to parse PLDM package: %s\n",
+ strerrorname_np(-rc));
+ status = EXIT_FAILURE;
+ goto cleanup_package;
+ }
+
+ printf("Package header\n");
+ pd_print_bytes("\tIdentifier: 0x [ ", hdr.package_header_identifier,
+ sizeof(hdr.package_header_identifier), " ]\n");
+ printf("\tFormat revision: %" PRIu8 "\n",
+ hdr.package_header_format_revision);
+ fwrite("\n", 1, 1, stdout);
+
+ foreach_pldm_package_firmware_device_id_record(iter, fdrec, rc)
+ {
+ struct pldm_descriptor desc;
+
+ printf("Firmware device ID record: %zu\n", nr_fdrecs++);
+ printf("\tDevice update option flags: %#.8" PRIx32 "\n",
+ fdrec.device_update_option_flags.value);
+ pd_print_typed_string(
+ "\tComponent image set version: ",
+ fdrec.component_image_set_version_string_type,
+ &fdrec.component_image_set_version_string, "\n");
+ pd_print_bytes("\tApplicable components: 0x [ ",
+ fdrec.applicable_components.bitmap.ptr,
+ fdrec.applicable_components.bitmap.length,
+ " ]\n");
+
+ printf("\tDescriptors:\n");
+ foreach_pldm_package_firmware_device_id_record_descriptor(
+ iter, fdrec, desc, rc)
+ {
+ pd_print_descriptor("\t\t", &desc, "\n");
+ }
+ if (rc) {
+ warnx("Failed parsing firmware device ID record descriptors: %s\n",
+ strerrorname_np(-rc));
+ status = EXIT_FAILURE;
+ goto cleanup_package;
+ }
+ fwrite("\n", 1, 1, stdout);
+ }
+ if (rc) {
+ warnx("Failed parsing firmware device ID records: %s\n",
+ strerrorname_np(-rc));
+ status = EXIT_FAILURE;
+ goto cleanup_package;
+ }
+
+ foreach_pldm_package_downstream_device_id_record(iter, ddrec, rc)
+ {
+ struct pldm_descriptor desc;
+
+ printf("Downstream device ID record: %zu\n", nr_ddrecs++);
+ printf("\tDevice update option flags: %#.4" PRIx32 "\n",
+ ddrec.update_option_flags.value);
+ pd_print_typed_string(
+ "\tSelf-contained activation min version: ",
+ ddrec.self_contained_activation_min_version_string_type,
+ &ddrec.self_contained_activation_min_version_string,
+ "\n");
+ pd_print_bytes("\tApplicable components: 0x [ ",
+ ddrec.applicable_components.bitmap.ptr,
+ ddrec.applicable_components.bitmap.length,
+ " ]\n");
+ printf("\tDescriptors:\n");
+ foreach_pldm_package_downstream_device_id_record_descriptor(
+ iter, ddrec, desc, rc)
+ {
+ pd_print_descriptor("\t\t", &desc, "\n");
+ }
+ if (rc) {
+ warnx("Failed parsing downstream device ID record descriptors: %s\n",
+ strerrorname_np(-rc));
+ status = EXIT_FAILURE;
+ goto cleanup_package;
+ }
+ fwrite("\n", 1, 1, stdout);
+ }
+ if (rc) {
+ warnx("Failed parsing downstream device ID records: %s\n",
+ strerrorname_np(-rc));
+ status = EXIT_FAILURE;
+ goto cleanup_package;
+ }
+
+ foreach_pldm_package_component_image_information(iter, info, rc)
+ {
+ printf("Component image info: %zu\n", nr_infos++);
+ printf("\tComponent classification: %" PRIu16 "\n",
+ info.component_classification);
+ printf("\tComponent identifier: %" PRIu16 "\n",
+ info.component_identifier);
+ printf("\tComponent comparison stamp: %" PRIu32 "\n",
+ info.component_comparison_stamp);
+ printf("\tComponent options: %#.4" PRIx16 "\n",
+ info.component_options.value);
+ printf("\tRequested activation method: %#.4" PRIx16 "\n",
+ info.requested_component_activation_method.value);
+ printf("\tComponent image: %p (%zu)\n",
+ (void *)info.component_image.ptr,
+ info.component_image.length);
+ pd_print_typed_string("\tComponent version: ",
+ info.component_version_string_type,
+ &info.component_version_string, "\n");
+ fwrite("\n", 1, 1, stdout);
+ }
+ if (rc) {
+ warnx("Failed parsing component image information: %s\n",
+ strerrorname_np(-rc));
+ status = EXIT_FAILURE;
+ goto cleanup_package;
+ }
+
+cleanup_package:
+ free(package);
+
+ exit(status);
+}