[PATCH v1 2/2] tools: usb: UCSI command testing tool
From: Heikki Krogerus
Date: Thu Feb 06 2025 - 09:20:28 EST
A tool that can be used to send UCSI commands to UCSI
mailboxes. The tool outputs the contents of the mailbox that
result from the command execution.
On most systems there will be only one UCSI interface, but
if the system has for example discrete GPUs with USB Type-C
connectors, each GPU card may expose its own UCSI. All
detected UCSI interfaces can be listed with -l option.
The UCSI interface that the command is meant for can be
optionally specified with -d option. By default, when the
interface is not specified, the first found will used.
Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
---
tools/usb/.gitignore | 1 +
tools/usb/Build | 1 +
tools/usb/Makefile | 8 +-
tools/usb/ucsi.c | 250 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 259 insertions(+), 1 deletion(-)
create mode 100644 tools/usb/ucsi.c
diff --git a/tools/usb/.gitignore b/tools/usb/.gitignore
index fce1ef5a9267..4645c8a1ca15 100644
--- a/tools/usb/.gitignore
+++ b/tools/usb/.gitignore
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
ffs-test
testusb
+ucsi
diff --git a/tools/usb/Build b/tools/usb/Build
index 2ad6f9745816..f77e2ed05a63 100644
--- a/tools/usb/Build
+++ b/tools/usb/Build
@@ -1,2 +1,3 @@
testusb-y += testusb.o
ffs-test-y += ffs-test.o
+ucsi-y += ucsi.o
diff --git a/tools/usb/Makefile b/tools/usb/Makefile
index c6235667dd46..1670892a6d5a 100644
--- a/tools/usb/Makefile
+++ b/tools/usb/Makefile
@@ -16,7 +16,7 @@ MAKEFLAGS += -r
override CFLAGS += -O2 -Wall -Wextra -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(srctree)/tools/include
override LDFLAGS += -lpthread
-ALL_TARGETS := testusb ffs-test
+ALL_TARGETS := testusb ffs-test ucsi
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
all: $(ALL_PROGRAMS)
@@ -36,6 +36,12 @@ $(FFS_TEST_IN): FORCE
$(OUTPUT)ffs-test: $(FFS_TEST_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+UCSI_IN := $(OUTPUT)ucsi-in.o
+$(UCSI_IN): FORCE
+ $(Q)$(MAKE) $(build)=ucsi
+$(OUTPUT)ucsi: $(UCSI_IN)
+ $(QUIET_LINK)$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
clean:
rm -f $(ALL_PROGRAMS)
find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete -o -name '\.*.o.cmd' -delete
diff --git a/tools/usb/ucsi.c b/tools/usb/ucsi.c
new file mode 100644
index 000000000000..4649f3a80d62
--- /dev/null
+++ b/tools/usb/ucsi.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UCSI command testing tool
+ *
+ * Copyright (C) 2025, Intel Corporation
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/types.h>
+
+/* UCSI data structure field offsets */
+#define UCSI_VERSION 0
+#define UCSI_CCI 4
+#define UCSI_CONTROL 8
+#define UCSI_MESSAGE_IN 16
+
+#define CCI_DATA_LENGTH(cci) (((cci) >> 8) & 0xff)
+
+static const char *class = "/sys/class/typec";
+static char path[PATH_MAX];
+
+#define MAX_INTERFACES 10
+
+static const char *find_dev(const char *dev, char **arr, size_t arr_size)
+{
+ size_t i;
+
+ for (i = 0; i < arr_size; i++)
+ if (!strcmp(dev, arr[i]))
+ return arr[i];
+ return NULL;
+}
+
+static int ucsi_match(const struct dirent *entry)
+{
+ snprintf(path, sizeof(path), "%s/%s/device/ucsi", class, entry->d_name);
+
+ return !access(path, F_OK);
+}
+
+static int find_devices(char **devs)
+{
+ struct dirent **dirs;
+ int ndevs = 0;
+ char *rpath;
+ int n;
+
+ n = scandir(class, &dirs, ucsi_match, alphasort);
+ if (n <= 0)
+ return 0;
+
+ while (n--) {
+ snprintf(path, sizeof(path), "%s/%s/device",
+ class, dirs[n]->d_name);
+
+ rpath = realpath(path, NULL);
+
+ if (find_dev(rpath, devs, ndevs)) {
+ free(rpath);
+ continue;
+ }
+
+ devs[ndevs++] = rpath;
+
+ if (ndevs == MAX_INTERFACES) {
+ fprintf(stderr, "maximum number of interfaces reached\n");
+ break;
+ }
+ }
+
+ free(dirs);
+
+ return ndevs;
+}
+
+static int run_command(const char *mb, __u64 command)
+{
+ __u8 data[256] = { };
+ __u32 cci;
+ int ret;
+ int fd;
+ __u8 i;
+
+ snprintf(path, sizeof(path), "%s/ucsi", mb);
+
+ fd = open(path, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "failed to open \'%s\'\n", path);
+ return 1;
+ }
+
+ ret = pwrite(fd, &command, sizeof(command), UCSI_CONTROL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to send command (%d)\n", ret);
+ goto err;
+ }
+
+ ret = pread(fd, &cci, sizeof(cci), UCSI_CCI);
+ if (ret <= 0) {
+ fprintf(stderr, "failed to read CCI\n");
+ goto err;
+ }
+
+ if (CCI_DATA_LENGTH(cci)) {
+ ret = pread(fd, data, CCI_DATA_LENGTH(cci), UCSI_MESSAGE_IN);
+ if (ret <= 0) {
+ fprintf(stderr, "failed to read MESSAGE_IN (%d)\n", ret);
+ goto err;
+ }
+ }
+
+ /* Print everything. */
+
+ printf("\nCONTROL:\t0x%016llx\n", command);
+ printf("CCI:\t\t0x%08x\n", cci);
+
+ if (CCI_DATA_LENGTH(cci)) {
+ printf("MESSAGE_IN");
+ for (i = 0; i < CCI_DATA_LENGTH(cci); i++) {
+ if (!(i % 8)) {
+ if (i % 16)
+ printf(" ");
+ else
+ printf("\n%08d\t", i ? 10 * 16 / i : 0);
+ }
+ printf("%02x ", data[i]);
+ }
+ printf("\n");
+ }
+err:
+ close(fd);
+ return ret < 0 ? ret : 0;
+}
+
+static int print_version(const char *mb)
+{
+ __u16 version = 0;
+ int ret;
+ int fd;
+
+ snprintf(path, sizeof(path), "%s/ucsi", mb);
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "failed to open \'%s\'\n", path);
+ return fd;
+ }
+ ret = pread(fd, &version, sizeof(version), UCSI_VERSION);
+ close(fd);
+
+ if (ret <= 0) {
+ fprintf(stderr, "\'%s\' -- failed to read version (%d)\n", mb, ret);
+ return ret;
+ }
+
+ printf("%s - UCSI v%u.%u\n", mb,
+ version >> 8 & 0xff, /* Major */
+ version >> 4 & 0xf); /* Minor */
+ return 0;
+}
+
+static void usage(const char *name)
+{
+ fprintf(stderr,
+ "Usage: %s -l | [-d INTERFACE] COMMAND\n"
+ "Execute UCSI commands\n\n"
+ " -l\tlist interfaces (devices)\n"
+ " -d\tselect interface\n"
+ " -h\tdisplay this help and exit\n"
+ "\nIf no interface is supplied, the first found is used.\n",
+ name);
+ fprintf(stderr, "Examples:\n"
+ " %s 0x6 (GET_CAPABILITY)\n"
+ " %s 0x10007 (GET_CONNECTOR_CAPABILITY)\n",
+ name, name);
+}
+
+int main(int argc, char *argv[])
+{
+ char *devs[MAX_INTERFACES];
+ const char *mb = NULL;
+ __u64 command = 0;
+ int ret = -EINVAL;
+ int n = 0;
+ char *end;
+ int opt;
+ int i;
+
+ n = find_devices(devs);
+
+ while ((opt = getopt(argc, argv, "d:hl")) != -1) {
+ switch (opt) {
+ case 'h':
+ usage(argv[0]);
+ goto out_free;
+ case 'l':
+ for (i = 0; i < n; i++) {
+ print_version(devs[i]);
+ free(devs[i]);
+ }
+ return 0;
+ case 'd':
+ mb = optarg;
+ if (!find_dev(mb, devs, n)) {
+ fprintf(stderr, "\'%s\' is not UCSI\n", mb);
+ goto out_free;
+ }
+ break;
+ default:
+ usage(argv[0]);
+ goto out_free;
+ }
+ }
+
+ if (!n) {
+ fprintf(stderr, "No UCSI found\n");
+ return 1;
+ }
+
+ if (optind >= argc) {
+ usage(argv[0]);
+ goto out_free;
+ }
+
+ command = strtoll(argv[optind], &end, 16);
+ if (errno == ERANGE || end == argv[optind] || *end != '\0') {
+ fprintf(stderr, "invalid command -- \'%s\'\n", argv[optind]);
+ goto out_free;
+ }
+
+ if (!mb)
+ mb = devs[0];
+
+ ret = print_version(mb);
+ if (ret)
+ goto out_free;
+
+ ret = run_command(mb, command);
+out_free:
+ for (i = 0; i < n; i++)
+ free(devs[i]);
+
+ return !!ret;
+}
--
2.47.2