[PATCH v7 23/23] [RFC] tools/scmi: Add SCMI Telemetry testing tool

Cristian Marussi <[email protected]> Sun, 2 Aug 2026 15:56:18 +0100
Newsgroups org.kernel.vger.arm-scmi,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Add a testing tool that exercises the SCMI ioctls UAPI interface: as of
now the tool simply queries the initial state of the SCMI Telemetry
subsystem, tries to enable all the existent Data Events and dumps all
the Telemetry data.

Signed-off-by: Cristian Marussi <[email protected]>
---
v6 --> v7
 - added monitor command for events
 - using and exposing DE's UUID/SID/OFFSET
 - use BATCH READ with status
 - use BATCH DE CONFIG GET/SET
 - use new ABI/SCMI feats flags
v5 --> v6
 - carry-on on error if feats NOT supported
 - build BATCH_READ with enumerated values
 - new UAPI naming
 - reworked UUID print
 - added info/shmti interactive cmds
 - added cfg/single/bulk/batch interactive cmds
 - added reset
 - added tlm enable
 - using new GET_ABI_INFO
 - moved DE_READ per-de sampling away from get_state
v4 --> v5
 - changes to adapt to new chardev/IOCTLs ABI
 - change IOCTLs param to avoid flexible arrays
 - added SHMTIs mmap dump
v3 --> v4
 - added generation file support
---
 tools/testing/scmi/Makefile |   25 +
 tools/testing/scmi/stlm.c   | 1371 +++++++++++++++++++++++++++++++++++
 2 files changed, 1396 insertions(+)
 create mode 100644 tools/testing/scmi/Makefile
 create mode 100644 tools/testing/scmi/stlm.c

diff --git a/tools/testing/scmi/Makefile b/tools/testing/scmi/Makefile
new file mode 100644
index 000000000000..a6a101f8398b
--- /dev/null
+++ b/tools/testing/scmi/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+CC?=$(CROSS_COMPILE)gcc
+OBJS = stlm.o
+
+CFLAGS=-Wall -static -std=gnu11 -I ../../../include/uapi/
+ifneq ($(DEBUG), )
+	CFLAGS+=-O0 -g -ggdb
+else
+	CFLAGS+=-static
+endif
+
+all: stlm
+
+stlm: $(OBJS)
+	$(CC) $(CFLAGS) $^ -o $@
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c $<
+
+clean:
+	rm -f *.o
+	rm -f stlm
+
+.PHONY: clean
diff --git a/tools/testing/scmi/stlm.c b/tools/testing/scmi/stlm.c
new file mode 100644
index 000000000000..9bdef26a4d95
--- /dev/null
+++ b/tools/testing/scmi/stlm.c
@@ -0,0 +1,1371 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/signalfd.h>
+
+#include <unistd.h>
+
+#include <linux/scmi.h>
+
+#define SLEEP_MS	3000
+
+#define IOCTL_ERR_STR(_ioctl)	"IOCTL:" #_ioctl
+
+#define USAGE_STRING \
+	"\t[OPTIONS]\n" \
+	"\t -i <instance_path>\t\tUse this device instance path. Default: /dev/scmi/tlm_0\n" \
+	"\t[COMMANDS]\n" \
+	"\t info [full] - Display summarized (or full) configuration and information data\n" \
+	"\t uuids - UUIDs dump\n" \
+	"\t monitor - Monitor Generation counter\n" \
+	"\t shmti [check][dump <sid>] - SHMTI operations\n" \
+	"\t\t check - Sanity check of all SHMTis\n" \
+	"\t\t dump [<sid>] - Binary dump of <sid> or all SHMTI areas\n" \
+	"\t des - Display detailed DEs info and config data\n" \
+	"\t groups - Display detailed GROUPS info and config data\n" \
+	"\t cfg [SUB-COMMANDS] - Configure Telemetry\n" \
+	"\t\t tlm on|off - Enable or disable Telemetry as a whole\n" \
+	"\t\t update <msecs> - Set global update interval\n" \
+	"\t\t all_des [[on|off]:[on|off]] - Enable or disable ALL DEs with or wout TS\n" \
+	"\t\t de [<de_id> [[on|off]:[on|off]]] - Get/Set configuration of one or ALL de\n" \
+	"\t\t group [<grp_id>] [[on|off]:[on|off]:[<update_msecs>]] - Get/Set GROUP configuration\n" \
+	"\t single - Perform a single sample read\n" \
+	"\t bulk - Perform a bulk read\n" \
+	"\t batch <de_id> [de_id [de_id]... ] - Perform a batch read of the DE IDs listed in input\n" \
+	"\t reset - Perform a Telemetry subsytem reset platform-side\n"
+
+struct tlm_de {
+	struct scmi_tlm_de_info *info;
+	struct scmi_tlm_de_config cfg;
+	struct scmi_tlm_de_sample sample;
+};
+
+struct tlm_group {
+	int fd;
+	struct scmi_tlm_grp_info *info;
+	struct scmi_tlm_grp_desc *desc;
+	struct scmi_tlm_intervals *ivs;
+};
+
+struct tlm_state {
+	int fd;
+	const char *path;
+	struct scmi_tlm_abi_info info;
+	struct scmi_tlm_config cfg;
+	struct scmi_tlm_intervals *ivs;
+	struct scmi_tlm_shmtis_list *ssl;
+	unsigned int num_des;
+	struct tlm_de *des;
+	unsigned int num_groups;
+	struct tlm_group *grps;
+};
+
+struct parsed_args {
+	const char *instance;
+	const char *cmd;
+	unsigned int cnt;
+	char **opts;
+};
+
+static inline void dump_des(struct tlm_state *st)
+{
+	fprintf(stdout, "\n+ Found #%d DEs:\n", st->num_des);
+	for (int i = 0; i < st->num_des; i++) {
+		fprintf(stdout, "\t[0x%08X][%s]\n",
+			st->des[i].info->id, st->des[i].info->name);
+		fprintf(stdout, "\t\tGRP_ID:0x%08X  SZ:%u  TYPE:%u  UNIT:%u  UNIT_EXP:%d  TS_RATE:%d\n",
+			st->des[i].info->grp_id, st->des[i].info->data_sz,
+			st->des[i].info->type, st->des[i].info->unit,
+			st->des[i].info->unit_exp, st->des[i].info->ts_rate);
+		fprintf(stdout, "\t\tINST_ID:%u  COMPO_ID:%u  COMPO_TYPE:%u  PERSIST:%u\n\n",
+			st->des[i].info->instance_id, st->des[i].info->compo_instance_id,
+			st->des[i].info->compo_type, st->des[i].info->persistent);
+	}
+	fprintf(stdout, "\n");
+
+	for (int i = 0; i < st->num_des; i++) {
+		struct tlm_de *de = &st->des[i];
+
+		/* Collect fresh sample */
+		if (de->cfg.enable) {
+			int ret;
+
+			de->sample.id = de->info->id;
+			ret = ioctl(st->fd, SCMI_TLM_DE_READ, &de->sample);
+			if (ret)
+				perror(IOCTL_ERR_STR(SCMI_TLM_DE_READ));
+		}
+
+		fprintf(stdout, "\t[0x%08X][%s]\t\t%s %s\n\t\t-->> TS:%16llu %016llX\n",
+			de->info->id, de->info->name,
+			de->cfg.enable ? "ON" : "OFF",
+			de->cfg.t_enable ? "/TS_ON " : "/TS_OFF",
+			de->sample.tstamp, de->sample.val);
+	}
+	fprintf(stdout, "\n");
+}
+
+static inline void dump_groups(struct tlm_state *st)
+{
+	fprintf(stdout, "\n+ Found %d GRPs: ", st->num_groups);
+	for (int i = 0; i < st->num_groups; i++) {
+		struct scmi_tlm_update_interval *grp_intrvs;
+		struct tlm_group *grp = &st->grps[i];
+		uint32_t *composing_des;
+
+		fprintf(stdout, "\n\tGRP_ID:%d  DES#:%d  INTRVS#:%d %s\n",
+			grp->info->grp_id, grp->info->num_des,
+			grp->info->num_intervals,
+			(grp->ivs->flags & SCMI_TLM_INTERV_DISCRETE) ? "(DISCRETE)" : "");
+
+		fprintf(stdout, "\tGRP Intervals:\n");
+		grp_intrvs = (struct scmi_tlm_update_interval *)grp->ivs->intervals;
+		for (int j = 0; j < grp->ivs->num_intervals; j++)
+			fprintf(stdout, "\t[%d]::%u,%d\n", j,
+				grp_intrvs[j].secs, grp_intrvs[j].exp);
+
+		composing_des = (uint32_t *)grp->desc->composing_des;
+		fprintf(stdout, "\tCOMPOSING_DES:");
+		for (int j = 0; j < grp->desc->num_des; j++)
+			fprintf(stdout, "0x%08X ", composing_des[j]);
+		fprintf(stdout, "\n");
+	}
+}
+
+static void print_uuid(const char *prefix, void *arg)
+{
+	unsigned char *uuid = arg;
+
+	fprintf(stdout, "%s", prefix);
+	for (int i = 0; i < SCMI_TLM_DE_IMPL_UUID_SZ; i++) {
+		fprintf(stdout, "%02X", uuid[i]);
+		switch (i) {
+		case 3:
+		case 5:
+		case 7:
+		case 9:
+			fprintf(stdout, "%c", '-');
+			break;
+		case 15:
+			fprintf(stdout, "%c", '\n');
+			break;
+		}
+	}
+}
+
+static inline void dump_state(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_update_interval *intervals;
+	struct scmi_tlm_shmti_info *shinfo;
+
+	fprintf(stdout, "- SYSTEM TELEMETRY @instance: %s\n\n", st->path);
+	fprintf(stdout, "+ ABI Version: %u\n", st->info.abi_version);
+	fprintf(stdout, "+ ABI Features: 0x%04X\n", st->info.abi_features);
+	fprintf(stdout, "\t (RESET:%u",
+		!!(st->info.abi_features & SCMI_TLM_ABI_FEAT_RESET));
+	fprintf(stdout, " EVENT:%u",
+		!!(st->info.abi_features & SCMI_TLM_ABI_FEAT_EVENT));
+	fprintf(stdout, " UUID_LIST:%u",
+		!!(st->info.abi_features & SCMI_TLM_ABI_FEAT_UUID_LIST));
+	fprintf(stdout, " BATCH_STATE:%u",
+		!!(st->info.abi_features & SCMI_TLM_ABI_FEAT_BATCH_STATE));
+	fprintf(stdout, " BATCHED_CFG:%u",
+		!!(st->info.abi_features & SCMI_TLM_ABI_FEAT_BATCHED_CFG));
+	fprintf(stdout, " DE_TRACKING:%u)\n",
+		!!(st->info.abi_features & SCMI_TLM_ABI_FEAT_DE_TRACKING));
+	fprintf(stdout, "+ SCMI Features: 0x%04X\n", st->info.features);
+	fprintf(stdout, "\t (RESET:%u",
+		!!(st->info.features & SCMI_TLM_SCMI_SUPPORT_RESET));
+	fprintf(stdout, " SINGLE_SAMPLE:%u",
+		!!(st->info.features & SCMI_TLM_SCMI_SUPPORT_SINGLE_SAMPLE));
+	fprintf(stdout, " GROUP_CONFIG:%u",
+		!!(st->info.features & SCMI_TLM_SCMI_SUPPORT_GROUP_CONFIG));
+	fprintf(stdout, " UPDATE_NOTIFICATIONS:%u)\n",
+		!!(st->info.features & SCMI_TLM_SCMI_SUPPORT_UPDATE_NOTIFICATION));
+	fprintf(stdout, "+ DEs#: %d\n", st->info.num_des);
+	fprintf(stdout, "+ GRPS#: %d\n", st->info.num_groups);
+	fprintf(stdout, "+ INTRV#: %d %s\n", st->info.num_intervals,
+		(st->ivs->flags & SCMI_TLM_INTERV_DISCRETE) ? "(DISCRETE)" : "");
+	fprintf(stdout, "+ SHMTIS#: %d\n", st->info.num_shmtis);
+
+	print_uuid("+ Primary UUID: ", &st->info.primary_de_impl_version[0]);
+	fprintf(stdout, "\n+ TLM_ENABLED: %d\n", st->cfg.enable);
+	fprintf(stdout, "+ CURRENT_UPDATE_INTERVAL: %u,%d\n",
+		st->cfg.active.secs, st->cfg.active.exp);
+
+	fprintf(stdout, "\n+ Found #%u Global Update Intervals\n",
+		st->ivs->num_intervals);
+	intervals = (struct scmi_tlm_update_interval *)st->ivs->intervals;
+	for (int i = 0; i < st->ivs->num_intervals; i++)
+		fprintf(stdout, "\t[%d]::%u,%d\n", i,
+			intervals[i].secs, intervals[i].exp);
+
+	fprintf(stdout, "\n+ Found #%u SHMTI areas\n", st->ssl->num_shmtis);
+	shinfo = (struct scmi_tlm_shmti_info *)st->ssl->shmtis;
+	for (int i = 0; i < st->ssl->num_shmtis; i++)
+		fprintf(stdout, "\t[%d]:: FD:%u  LEN:%u\n",
+			shinfo[i].sid, shinfo[i].fd, shinfo[i].len);
+
+	if (st->info.num_des != st->num_des) {
+		fprintf(stdout, "\n++++++ DES NOT FULLY_ENUMERATED ++++++\n");
+		fprintf(stdout, "+++ DECLARED:%u  ENUMERATED:%u +++\n",
+			st->info.num_des, st->num_des);
+	}
+
+	if (args && args->cnt && args->opts &&
+	    !strncmp(args->opts[0], "full", strlen(args->opts[0]))) {
+		dump_des(st);
+		dump_groups(st);
+	}
+}
+
+static int discover_base_info(int fd, struct scmi_tlm_abi_info *info)
+{
+	int ret;
+
+	info->size = sizeof(*info);
+	ret = ioctl(fd, SCMI_TLM_GET_ABI_INFO, info);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_ABI_INFO));
+		return ret;
+	}
+
+	return ret;
+}
+
+static struct scmi_tlm_des_list *scmi_get_des_list(int fd, int num_des)
+{
+	struct scmi_tlm_des_list *dsl;
+	struct scmi_tlm_de_info *dei;
+	size_t sz, d_sz;
+	int ret;
+
+	sz = sizeof(*dsl);
+	dsl = malloc(sz);
+	if (!dsl)
+		return NULL;
+
+	bzero(dsl, sz);
+
+	d_sz = sizeof(*dei) * num_des;
+	dei = malloc(d_sz);
+	if (!dei) {
+		free(dsl);
+		return NULL;
+	}
+
+	bzero(dei, d_sz);
+
+	dsl->num_des = num_des;
+	dsl->des = (uint64_t)dei;
+	ret = ioctl(fd, SCMI_TLM_GET_DE_LIST, dsl);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_LIST));
+		return NULL;
+	}
+
+	return dsl;
+}
+
+static struct tlm_de *enumerate_des(struct tlm_state *st)
+{
+	struct scmi_tlm_des_list *dsl;
+	struct tlm_de *des;
+
+	dsl = scmi_get_des_list(st->fd, st->info.num_des);
+	if (!dsl)
+		return NULL;
+
+	st->num_des = dsl->num_des;
+	des = malloc(sizeof(*des) * st->num_des);
+	if (!des)
+		return NULL;
+
+	bzero(des, sizeof(*des) * st->num_des);
+	for (int i = 0; i < st->num_des; i++) {
+		struct tlm_de *de = &des[i];
+		struct scmi_tlm_batch batch = {};
+		int ret;
+
+		de->info = &(((struct scmi_tlm_de_info *)dsl->des)[i]);
+		de->cfg.id = de->info->id;
+
+		batch.num_items = 1;
+		batch.item_sz = sizeof(de->cfg);
+		batch.items = (uint64_t)&de->cfg;
+
+		ret = ioctl(st->fd, SCMI_TLM_GET_DE_CFG, &batch);
+		if (ret) {
+			perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_CFG));
+			continue;
+		}
+	}
+
+	return des;
+}
+
+static int get_current_config(int fd, struct scmi_tlm_config *cfg)
+{
+	int ret;
+
+	ret = ioctl(fd, SCMI_TLM_GET_CFG, cfg);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_CFG));
+		return ret;
+	}
+
+	return ret;
+}
+
+static struct scmi_tlm_grps_list *scmi_get_grps_list(int fd, int num_groups)
+{
+	struct scmi_tlm_grps_list *gsl;
+	struct scmi_tlm_grp_info *grps;
+	size_t sz, grps_sz;
+	int ret;
+
+	sz = sizeof(*gsl);
+	gsl = malloc(sz);
+	if (!gsl)
+		return NULL;
+
+	bzero(gsl, sz);
+	grps_sz = sizeof(*grps) * num_groups;
+	grps = malloc(grps_sz);
+	if (!grps) {
+		free(gsl);
+		return NULL;
+	}
+
+	gsl->num_grps = num_groups;
+	gsl->grps = (uint64_t)grps;
+	ret = ioctl(fd, SCMI_TLM_GET_GRP_LIST, gsl);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_LIST));
+		free(grps);
+		free(gsl);
+		return NULL;
+	}
+
+	return gsl;
+}
+
+static struct scmi_tlm_intervals *
+enumerate_intervals(int fd, int num_intervals, unsigned int *grp_id)
+{
+	struct scmi_tlm_update_interval *intervs;
+	struct scmi_tlm_intervals *ivs;
+	size_t sz, i_sz;
+	int ret;
+
+	sz = sizeof(*ivs);
+	ivs = malloc(sz);
+	if (!ivs)
+		return NULL;
+
+	bzero(ivs, sz);
+
+	i_sz = sizeof(*intervs) * num_intervals;
+	intervs = malloc(i_sz);
+	if (!intervs) {
+		free(ivs);
+		return NULL;
+	}
+
+	bzero(intervs, i_sz);
+
+	ivs->num_intervals = num_intervals;
+	ivs->intervals = (uint64_t)intervs;
+	if (grp_id) {
+		ivs->grp_id = *grp_id;
+		ivs->flags = SCMI_TLM_INTERV_GROUP;
+	}
+
+	ret = ioctl(fd, SCMI_TLM_GET_INTRVS, ivs);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_INTRVS));
+		free(ivs);
+		free(intervs);
+		return NULL;
+	}
+
+	return ivs;
+}
+
+static struct scmi_tlm_shmtis_list *enumerate_shmtis(struct tlm_state *st)
+{
+	struct scmi_tlm_shmti_info *shmtis;
+	struct scmi_tlm_shmtis_list *ssl;
+	size_t sz, shmtis_sz;
+	int ret;
+
+	sz = sizeof(*ssl);
+	ssl = malloc(sz);
+	if (!ssl)
+		return NULL;
+
+	bzero(ssl, sz);
+	shmtis_sz = sizeof(*shmtis) * st->info.num_shmtis;
+	shmtis = malloc(shmtis_sz);
+	if (!shmtis) {
+		free(ssl);
+		return NULL;
+	}
+
+	ssl->num_shmtis = st->info.num_shmtis;
+	ssl->shmtis = (uint64_t)shmtis;
+	ret = ioctl(st->fd, SCMI_TLM_GET_SHMTI_LIST, ssl);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_SHMTI_LIST));
+		free(shmtis);
+		free(ssl);
+		return NULL;
+	}
+
+	return ssl;
+}
+
+static struct tlm_group *enumerate_groups(struct tlm_state *st)
+{
+	struct scmi_tlm_grps_list *gsl;
+	struct tlm_group *grps;
+
+	gsl = scmi_get_grps_list(st->fd, st->info.num_groups);
+	if (!gsl)
+		return NULL;
+
+	st->num_groups = gsl->num_grps;
+	grps = malloc(sizeof(*grps) * st->num_groups);
+	if (!grps)
+		return NULL;
+
+	bzero(grps, sizeof(*grps) * st->num_groups);
+	for (int i = 0; i < st->num_groups; i++) {
+		struct tlm_group *grp = &grps[i];
+		size_t size, composing_sz;
+		uint32_t *composing_des;
+		int ret;
+
+		grp->info = &(((struct scmi_tlm_grp_info *)gsl->grps)[i]);
+		size = sizeof(*grp->desc);
+		grp->desc = malloc(size);
+		if (!grp->desc)
+			return NULL;
+
+		bzero(grp->desc, size);
+		composing_sz = sizeof(uint32_t) * grp->info->num_des;
+		composing_des = malloc(composing_sz);
+		if (!composing_des) //XXX BAD Cleanup
+			return NULL;
+
+		grp->desc->grp_id = grp->info->grp_id;
+		grp->desc->num_des = grp->info->num_des;
+		grp->desc->composing_des = (uint64_t)composing_des;
+		ret = ioctl(st->fd, SCMI_TLM_GET_GRP_DESC, grp->desc);
+		if (ret) {
+			perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_DESC));
+			continue;
+		}
+
+		grp->ivs = enumerate_intervals(st->fd, grp->info->num_intervals,
+					       &grp->info->grp_id);
+	}
+
+	return grps;
+}
+
+static int gather_tlm_state(struct tlm_state *st)
+{
+	int ret;
+
+	ret = discover_base_info(st->fd, &st->info);
+	if (ret)
+		return ret;
+
+	st->ivs = enumerate_intervals(st->fd, st->info.num_intervals, NULL);
+	if (!st->ivs)
+		return -1;
+
+	ret = get_current_config(st->fd, &st->cfg);
+	if (ret)
+		return ret;
+
+	if (st->info.num_des)
+		st->des = enumerate_des(st);
+
+	if (st->info.num_groups)
+		st->grps = enumerate_groups(st);
+
+	if (st->info.num_shmtis)
+		st->ssl = enumerate_shmtis(st);
+
+	return 0;
+}
+
+static struct tlm_state *open_session(const char *path)
+{
+	struct tlm_state *st;
+
+	st = malloc(sizeof(*st));
+	if (!st)
+		return NULL;
+
+	st->fd = open(path, O_RDWR);
+	if (st->fd < 0) {
+		perror("open");
+		free(st);
+		return NULL;
+	}
+
+	st->path = path;
+
+	return st;
+}
+
+static int dump_shmti(struct scmi_tlm_shmti_info *shmti)
+{
+	void *tdcf;
+	ssize_t bytes = 0;
+
+	tdcf = mmap(NULL, shmti->len, PROT_READ, MAP_SHARED, shmti->fd, 0);
+	if (tdcf == MAP_FAILED) {
+		fprintf(stderr, "%s\n", strerror(errno));
+		return -1;
+	}
+
+	tdcf += shmti->offset;
+	do {
+		bytes += write(1, tdcf + bytes, shmti->len - bytes);
+		if (bytes < 0)
+			return -1;
+	} while (bytes < shmti->len);
+
+	return bytes;
+}
+
+static int shmti_dump(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_shmti_info *shmti;
+	unsigned long first, last;
+
+	if (args->cnt > 1) {
+		first = last = strtoul(args->opts[1], NULL, 0);
+		if (first == ULONG_MAX || errno == -EINVAL ||
+		    first >= st->ssl->num_shmtis)
+			return -1;
+	} else {
+		first = 0;
+		last = st->ssl->num_shmtis - 1;
+	}
+
+	for (int sid = first; sid <= last; sid++) {
+		int ret;
+
+		shmti = &(((struct scmi_tlm_shmti_info *)(st->ssl->shmtis))[sid]);
+		fprintf(stderr, "Binary DUMP of SHMTI[%u] LEN:%u\n",
+			shmti->sid, shmti->len);
+
+		ret = dump_shmti(shmti);
+		if (!ret)
+			fprintf(stderr, "...FAILED DUMP of SHMTI[%u] !!!\n", sid);
+	}
+
+	return 0;
+}
+
+static int shmti_check(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_shmti_info *shmtis;
+
+	shmtis = (struct scmi_tlm_shmti_info *)st->ssl->shmtis;
+
+	fprintf(stdout, "\n- SHMTI mmap SANITY CHECK -\n-------------------\n");
+
+	for (int i = 0; i < st->ssl->num_shmtis; i++) {
+		unsigned long *start, *end;
+		void *tdcf;
+		char *buf;
+
+		fprintf(stdout, "Mapping SHMTI[%u] - SID:%u  LEN:%u  FD:%u  OFFS:%u\n",
+			i, shmtis[i].sid, shmtis[i].len, shmtis[i].fd, shmtis[i].offset);
+
+		tdcf = mmap(NULL, shmtis[i].len, PROT_READ, MAP_SHARED,
+			    shmtis[i].fd, 0);
+		if (tdcf == MAP_FAILED) {
+			fprintf(stderr, "%s\n", strerror(errno));
+			return -1;
+		}
+
+		start = tdcf + shmtis[i].offset;
+		end = tdcf + shmtis[i].offset + shmtis[i].len;
+
+		fprintf(stdout, "-----------------------------------------------------\n");
+		fprintf(stdout, "Mapped SHMTI - SID:%u  %u@%p\n",
+			shmtis[i].sid, shmtis[i].offset, tdcf);
+
+		buf = (char *)start;
+		for (int j = 0; j < 4; j++)
+			fprintf(stdout, "%c ", buf[j]);
+
+		fprintf(stdout, " ===> ");
+
+		buf = ((char *)end) - 4;
+		for (int j = 0 ; j < 4; j++)
+			fprintf(stdout, "%c ", buf[j]);
+		fprintf(stdout, "\n-----------------------------------------------------\n");
+	}
+
+	return 0;
+}
+
+static int shmti_ops(struct tlm_state *st, struct parsed_args *args)
+{
+	if (!args->cnt || !args->opts)
+		return -1;
+
+	if (!strncmp(args->opts[0], "dump", strlen(args->opts[0])))
+		return shmti_dump(st, args);
+	if (!strncmp(args->opts[0], "check", strlen(args->opts[0])))
+		return shmti_check(st, args);
+
+	return -1;
+}
+
+static int parse_options(int argc, char **argv, struct parsed_args *args)
+{
+	int opt = 0;
+
+	/* Set defaults */
+	args->instance = "/dev/scmi/tlm_0";
+	while ((opt = getopt(argc, argv, "i:h")) != -1) {
+		switch (opt) {
+		case 'i':
+			args->instance = optarg;
+			break;
+		case 'h':
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (optind <= argc) {
+		if (optind < argc - 1) {
+			args->cnt = argc - 1 - optind;
+			args->opts = malloc(sizeof(*args->opts) * args->cnt);
+			if (!args->opts)
+				return -1;
+		}
+
+		args->cmd = argv[optind];
+		for (int i = 0, j = optind + 1; j < argc; i++, j++)
+			args->opts[i] = argv[j];
+	}
+
+	return 0;
+}
+
+static inline void usage(char *program_name)
+{
+	printf("Usage: %s [OPTIONS] [CMD [CMD_OPTS]...]\n", program_name);
+	printf("%s", USAGE_STRING);
+}
+
+static inline struct scmi_tlm_data_read *bulk_buffer_alloc(struct tlm_state *st)
+{
+	struct scmi_tlm_de_sample *samples;
+	struct scmi_tlm_data_read *bulk;
+	size_t bulk_sz, samples_sz;
+
+	bulk_sz = sizeof(*bulk);
+	bulk = malloc(bulk_sz);
+	if (!bulk)
+		return NULL;
+
+	bzero(bulk, bulk_sz);
+	samples_sz = st->info.num_des * sizeof(*samples);
+	samples = malloc(samples_sz);
+	if (!samples) {
+		free(bulk);
+		return NULL;
+	}
+
+	bzero(samples, samples_sz);
+	bulk->samples = (uint64_t)samples;
+	bulk->num_samples = st->info.num_des;
+
+	return bulk;
+}
+
+static int single_read(struct tlm_state *st)
+{
+	struct scmi_tlm_de_sample *samples;
+	struct scmi_tlm_data_read *bulk;
+	int ret;
+
+	bulk = bulk_buffer_alloc(st);
+	if (!bulk)
+		return -1;
+
+	ret = ioctl(st->fd, SCMI_TLM_SINGLE_READ, bulk);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_SINGLE_READ));
+		return -1;
+	}
+
+	fprintf(stdout, "\n- Single ASYNC read -\n-------------------\n");
+	samples = (struct scmi_tlm_de_sample *)bulk->samples;
+	for (int i = 0; i < bulk->num_samples; i++)
+		fprintf(stdout, "0x%08X %016llu %016llX\n",
+			samples[i].id, samples[i].tstamp, samples[i].val);
+
+	return 0;
+}
+
+static int bulk_read(struct tlm_state *st)
+{
+	struct scmi_tlm_de_sample *samples;
+	struct scmi_tlm_data_read *bulk;
+	int ret;
+
+	bulk = bulk_buffer_alloc(st);
+	if (!bulk)
+		return -1;
+
+	ret = ioctl(st->fd, SCMI_TLM_BULK_READ, bulk);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_BULK_READ));
+		return -1;
+	}
+
+	fprintf(stdout, "\n- BULK read -\n-------------------\n");
+	samples = (struct scmi_tlm_de_sample *)bulk->samples;
+	for (int i = 0; i < bulk->num_samples; i++)
+		fprintf(stdout, "0x%08X %016llu %016llX\n",
+			samples[i].id, samples[i].tstamp, samples[i].val);
+
+	return 0;
+}
+
+static int tlm_reset(struct tlm_state *st)
+{
+	int ret;
+
+	ret = ioctl(st->fd, SCMI_TLM_RESET);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_RESET));
+		return -1;
+	}
+
+	fprintf(stdout, "\n--- TLM RESET ISSUED\n");
+
+	return 0;
+}
+
+static inline struct scmi_tlm_batch *
+batch_buffer_alloc(int num_items, size_t item_sz, bool need_status)
+{
+	struct scmi_tlm_batch *batch;
+	size_t batch_sz, all_items_sz;
+	void *items;
+
+	batch_sz = sizeof(*batch);
+	batch = malloc(batch_sz);
+	if (!batch)
+		return NULL;
+
+	bzero(batch, batch_sz);
+	batch->num_items = num_items;
+
+	batch->item_sz = item_sz;
+	all_items_sz = batch->num_items * batch->item_sz;
+	items = malloc(all_items_sz);
+	if (!items) {
+		free(batch);
+		return NULL;
+	}
+
+	bzero(items, all_items_sz);
+	batch->items = (uint64_t)items;
+
+	if (need_status) {
+		int *states;
+		size_t states_sz = sizeof(*states) * batch->num_items;
+
+		states = malloc(states_sz);
+		if (!states)
+			return NULL;
+
+		bzero(states, states_sz);
+		batch->states = (uint64_t)states;
+	}
+
+	return batch;
+}
+
+static int batch_read(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_de_sample *samples;
+	struct scmi_tlm_batch *batch;
+	int ret;
+
+	if (!args->opts || !args->cnt)
+		return -1;
+
+	/* Allocate for full size with status */
+	batch = batch_buffer_alloc(st->info.num_des, sizeof(*samples), true);
+	if (!batch)
+		return -1;
+
+	samples = (struct scmi_tlm_de_sample *)batch->items;
+	batch->num_items = args->cnt;
+	for (int i = 0; i < batch->num_items; i++) {
+		unsigned long val;
+
+		val = strtoul(args->opts[i], NULL, 0);
+		if (val == ULONG_MAX || errno == -EINVAL)
+			continue;
+
+		samples[i].id = (unsigned int)val;
+	}
+
+	ret = ioctl(st->fd, SCMI_TLM_BATCH_READ, batch);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_BATCH_READ));
+		return -1;
+	}
+
+	fprintf(stdout, "\n- BATCH read -\n-------------------\n");
+	for (int i = 0; i < batch->num_items; i++)
+		fprintf(stdout, "STATUS[%d] => 0x%08X %016llu %016llX\n",
+			((uint32_t *)batch->states)[i],
+			samples[i].id, samples[i].tstamp, samples[i].val);
+
+	return 0;
+}
+
+/*
+ * <ena>:<t_ena>[:<update_msecs>]
+ */
+static void
+parse_item_cfg(char *str, bool *ena, bool *t_ena, unsigned int *update)
+{
+	char *tok;
+
+	if (!ena)
+		return;
+
+	tok = strtok(str, ":");
+	if (tok)
+		*ena = strncmp(tok, "on", strlen(tok)) == 0;
+
+	if (!t_ena)
+		return;
+
+	tok = strtok(NULL, ":");
+	if (tok)
+		*t_ena = strncmp(tok, "on", strlen(tok)) == 0;
+
+	if (!update)
+		return;
+
+	tok = strtok(NULL, ":");
+	if (tok) {
+		unsigned long val;
+
+		val = strtoul(tok, NULL, 0);
+		if (val == ULONG_MAX || errno == -EINVAL)
+			return;
+
+		*update = (unsigned int)val;
+	}
+}
+
+static int configure_all_des(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_de_config de_cfg = {};
+	bool ena, t_ena;
+	int ret;
+
+	if (!args->opts)
+		return -1;
+
+	ret = ioctl(st->fd, SCMI_TLM_GET_ALL_CFG, &de_cfg);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_ALL_CFG));
+		return ret;
+	}
+
+	fprintf(stdout, "--- [GET] ALL DEs ON:%d  T_ON:%d\n",
+		de_cfg.enable, de_cfg.t_enable);
+
+	if (args->cnt < 2)
+		return 0;
+
+	parse_item_cfg(args->opts[1], &ena, &t_ena, NULL);
+	de_cfg.enable = !!ena;
+	de_cfg.t_enable = !!t_ena;
+
+	fprintf(stdout, "--- [SET] ALL DEs ON:%d  T_ON:%d\n",
+		de_cfg.enable, de_cfg.t_enable);
+
+	ret = ioctl(st->fd, SCMI_TLM_SET_ALL_CFG, &de_cfg);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_SET_ALL_CFG));
+		return ret;
+	}
+
+	return 0;
+}
+
+static int de_config_batch_get(struct tlm_state *st, struct scmi_tlm_batch *batch)
+{
+	int ret;
+
+	/* Get current config */
+	ret = ioctl(st->fd, SCMI_TLM_GET_DE_CFG, batch);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_CFG));
+		return ret;
+	}
+
+	return 0;
+}
+
+static int configure_de(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_de_config *de_cfg;
+	struct scmi_tlm_batch *batch;
+	bool ena, t_ena;
+	unsigned long val;
+	int ret, *states;
+
+	if (!args->opts)
+		return -1;
+
+	if (args->cnt < 2) {
+		batch = batch_buffer_alloc(st->num_des, sizeof(*de_cfg), true);
+		if (!batch)
+			return -1;
+
+		/* Dump all DE configs */
+		de_cfg = (struct scmi_tlm_de_config *)batch->items;
+		for (int i = 0; i < st->num_des; i++)
+			de_cfg[i].id = st->des[i].info->id;
+
+		ret = de_config_batch_get(st, batch);
+		if (ret)
+			return ret;
+
+		states = (int *)batch->states;
+		for (int i = 0; i < batch->num_items; i++) {
+			fprintf(stdout,
+				"--- [GET] STATUS[%d] -> DE:0x%08X  ON:%d  T_ON:%d  SID:%u  OFFS:%u",
+				states[i], de_cfg[i].id, de_cfg[i].enable,
+				de_cfg[i].t_enable, de_cfg[i].sid, de_cfg[i].offset);
+				print_uuid("  UUID: ", &de_cfg[i].uuid[0]);
+				fprintf(stdout, "\n");
+		}
+
+		return 0;
+	}
+
+	/*Dump one DE config */
+	batch = batch_buffer_alloc(1, sizeof(*de_cfg), true);
+	if (!batch)
+		return -1;
+
+	states = (int *)batch->states;
+	de_cfg = (struct scmi_tlm_de_config *)batch->items;
+	val = strtoul(args->opts[1], NULL, 0);
+	if (val == ULONG_MAX || errno == -EINVAL)
+		return -1;
+
+	de_cfg[0].id = (unsigned int)val;
+	ret = de_config_batch_get(st, batch);
+	if (ret)
+		return ret;
+
+	fprintf(stdout, "--- [GET] STATUS[%d] -> DE:0x%08X  ON:%d  T_ON:%d  SID:%u  OFFS:%u",
+		states[0], de_cfg[0].id, de_cfg[0].enable,
+		de_cfg[0].t_enable, de_cfg[0].sid, de_cfg[0].offset);
+	print_uuid("  UUID: ", &de_cfg[0].uuid[0]);
+	fprintf(stdout, "\n");
+
+	if (args->cnt < 3)
+		return 0;
+
+	/* Configure one DE */
+	parse_item_cfg(args->opts[2], &ena, &t_ena, NULL);
+	de_cfg[0].enable = !!ena;
+	de_cfg[0].t_enable = !!t_ena;
+
+	ret = ioctl(st->fd, SCMI_TLM_SET_DE_CFG, batch);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_SET_DE_CFG));
+		return ret;
+	}
+
+	fprintf(stdout, "--- [SET] STATUS[%d] -> DE:0x%08X  ON:%d  T_ON:%d  SID:%u  OFFS:%u",
+		states[0], de_cfg[0].id, de_cfg[0].enable,
+		de_cfg[0].t_enable, de_cfg[0].sid, de_cfg[0].offset);
+	print_uuid("  UUID: ", &de_cfg[0].uuid[0]);
+	fprintf(stdout, "\n");
+
+	return 0;
+}
+
+static int grp_config_get(struct tlm_state *st, struct scmi_tlm_config *cfg)
+{
+	int ret;
+
+	/* Get current config */
+	ret = ioctl(st->fd, SCMI_TLM_GET_CFG, cfg);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_CFG));
+		return ret;
+	}
+
+	return 0;
+}
+
+static int configure_group(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_config cfg = {};
+	bool ena, t_ena;
+	unsigned long val;
+	int ret;
+
+	if (!args->opts)
+		return -1;
+
+	if (args->cnt < 2) {
+		/* Dump all GRPS configs */
+		for (int i = 0; i < st->num_groups; i++) {
+			cfg.grp_id = st->grps[i].info->grp_id;
+			cfg.active.exp = -3;
+			cfg.flags = SCMI_TLM_CONFIG_GROUP;
+			ret = grp_config_get(st, &cfg);
+			if (!ret)
+				fprintf(stdout,
+					"--- [GET] -> GROUP:0x%08X  ON:%d  T_ON:%d  INTRV:%u,%d\n",
+					cfg.grp_id, cfg.enable, cfg.t_enable,
+					cfg.active.secs, cfg.active.exp);
+		}
+
+		return 0;
+	}
+
+	/*Dump one GROUP config */
+	val = strtoul(args->opts[1], NULL, 0);
+	if (val == ULONG_MAX || errno == -EINVAL)
+		return -1;
+
+	cfg.grp_id = (unsigned int)val;
+	cfg.flags = SCMI_TLM_CONFIG_GROUP;
+	cfg.active.exp = -3;
+	ret = grp_config_get(st, &cfg);
+	if (ret)
+		return ret;
+
+	fprintf(stdout, "--- [GET] -> GROUP:0x%08X  ON:%d  T_ON:%d  INTRV:%u,%d\n",
+		cfg.grp_id, cfg.enable, cfg.t_enable, cfg.active.secs, cfg.active.exp);
+
+	if (args->cnt < 3)
+		return 0;
+
+	/* Configure one GROUP */
+	parse_item_cfg(args->opts[2], &ena, &t_ena, &cfg.active.secs);
+	cfg.enable = !!ena;
+	cfg.t_enable = !!t_ena;
+
+	fprintf(stdout, "--- [SET] -> GROUP:0x%08X  ON:%d  T_ON:%d  INTRV:%u,%d\n",
+		cfg.grp_id, cfg.enable, cfg.t_enable,
+		cfg.active.secs, cfg.active.exp);
+
+	ret = ioctl(st->fd, SCMI_TLM_SET_CFG, &cfg);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_SET_CFG));
+		return ret;
+	}
+
+	return 0;
+}
+
+static int configure_telemetry(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_config cfg = st->cfg;
+	bool val;
+	int ret;
+
+	if (args->cnt < 2 || !args->opts)
+		return -1;
+
+	val = strncmp(args->opts[1], "on", strlen(args->opts[1])) == 0;
+
+	cfg.enable = !!val;
+	cfg.flags = 0;
+
+	fprintf(stdout, "\n---%sabling Telemetry\n", val ? "En" : "Dis");
+	ret = ioctl(st->fd, SCMI_TLM_SET_CFG, &cfg);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_SET_CFG));
+		return ret;
+	}
+
+	return 0;
+}
+
+static int configure_update(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_config cfg = st->cfg;
+	unsigned long val;
+	int ret;
+
+	if (args->cnt < 2 || !args->opts)
+		return -1;
+
+	val = strtoul(args->opts[1], NULL, 0);
+	if (val == ULONG_MAX || errno == -EINVAL)
+		return -1;
+
+	cfg.flags = 0;
+	if (!val) {
+		cfg.enable = val;
+		fprintf(stdout, "\n---Disabling Telemetry\n");
+	} else {
+		cfg.enable = !!val;
+		cfg.active.secs = val;
+		cfg.active.exp = -3;
+		fprintf(stdout, "\n--- Setting update interval to %lu\n", val);
+	}
+
+	ret = ioctl(st->fd, SCMI_TLM_SET_CFG, &cfg);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_SET_CFG));
+		return ret;
+	}
+
+	return 0;
+}
+
+static int configure(struct tlm_state *st, struct parsed_args *args)
+{
+	if (!args->cnt || !args->opts)
+		return -1;
+
+	if (!strncmp(args->opts[0], "all_des", strlen(args->opts[0])))
+		return configure_all_des(st, args);
+	if (!strncmp(args->opts[0], "tlm", strlen(args->opts[0])))
+		return configure_telemetry(st, args);
+	if (!strncmp(args->opts[0], "update", strlen(args->opts[0])))
+		return configure_update(st, args);
+	if (!strncmp(args->opts[0], "de", strlen(args->opts[0])))
+		return configure_de(st, args);
+	if (!strncmp(args->opts[0], "group", strlen(args->opts[0])))
+		return configure_group(st, args);
+
+	return -1;
+}
+
+static inline void full_scan_and_dump(struct tlm_state *st)
+{
+	dump_state(st, NULL);
+	dump_des(st);
+	dump_groups(st);
+}
+
+static int uuids(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_uuid_list *udl;
+	struct scmi_tlm_uuid *uuids;
+	size_t sz, u_sz;
+	int ret;
+
+	sz = sizeof(*udl);
+	udl = malloc(sz);
+	if (!udl)
+		return -1;
+
+	/* Get size at first */
+	bzero(udl, sz);
+	ret = ioctl(st->fd, SCMI_TLM_GET_UUID_LIST, udl);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_UUID_LIST));
+		return -1;
+	}
+
+	u_sz = sizeof(*uuids) * udl->num_uuids;
+	uuids = malloc(u_sz);
+	if (!uuids) {
+		free(udl);
+		return -1;
+	}
+
+	bzero(uuids, u_sz);
+	udl->uuids = (uint64_t)uuids;
+	ret = ioctl(st->fd, SCMI_TLM_GET_UUID_LIST, udl);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_LIST));
+		return -1;
+	}
+
+	fprintf(stdout, "--- [GET] -> NUM_UUIDS: %u\n", udl->num_uuids);
+	for (int i = 0; i < udl->num_uuids; i++) {
+		print_uuid("  UUID: ",
+			   &(((struct scmi_tlm_uuid *)(udl->uuids))[i]));
+		fprintf(stdout, "\n");
+	}
+
+	return 0;
+}
+
+static int monitor(struct tlm_state *st, struct parsed_args *args)
+{
+	struct scmi_tlm_event evt = {};
+	struct pollfd fds[2];
+	sigset_t mask;
+	int ret, efd, sfd;
+
+	efd = eventfd(0, EFD_CLOEXEC);
+	if (efd < 0)
+		return -1;
+
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGINT);
+	sigaddset(&mask, SIGQUIT);
+	ret = sigprocmask(SIG_BLOCK, &mask, NULL);
+	if (ret) {
+		close(efd);
+		return -1;
+	}
+
+	sfd = signalfd(-1, &mask, 0);
+	if (sfd < 0) {
+		close(efd);
+		return -1;
+	}
+
+	fds[0].fd = sfd;
+	fds[0].events = POLLIN;
+
+	evt.type = SCMI_TLM_EVT_GENERATION;
+	evt.efd = efd;
+	fprintf(stdout, "==>> Registering for events of type %u\n", evt.type);
+	ret = ioctl(st->fd, SCMI_TLM_EVENT_SUBSCRIBE, &evt);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_EVENT_SUBSCRIBE));
+		return -1;
+	}
+
+	fds[1].fd = evt.efd;
+	fds[1].events = POLLIN;
+
+	fprintf(stdout, "==>> Waiting for events of type %u on cookie:%u\n",
+		evt.type, evt.cookie);
+	while (1) {
+		ret = poll(fds, 2, -1);
+		if (ret < 0)
+			return ret;
+
+		/* Break on SIGINT */
+		if (fds[0].revents & POLLIN) {
+			struct signalfd_siginfo  fdsi;
+
+			read(fds[0].fd, &fdsi, sizeof(fdsi));
+			if (fdsi.ssi_signo == SIGINT)
+				fprintf(stderr, "Got SIGINT\n");
+			else
+				fprintf(stderr, "Got signal\n");
+			break;
+		}
+
+		if (fds[1].revents & POLLIN) {
+			uint64_t val = 0;
+
+			read(fds[1].fd, &val, sizeof(val));
+			fprintf(stdout, "==>> EVENT READ on EFD:%d - VAL:%lu!\n",
+				evt.efd, val);
+		}
+	}
+
+	fprintf(stdout, "==>> DE-Registering for events of type %u on cookie:%u\n",
+		evt.type, evt.cookie);
+	ret = ioctl(st->fd, SCMI_TLM_EVENT_SUBSCRIBE, &evt);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_EVENT_SUBSCRIBE));
+		return -1;
+	}
+
+	close(sfd);
+	close(efd);
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	struct parsed_args args = {};
+	struct tlm_state *st;
+	int ret;
+
+	ret = parse_options(argc, argv, &args);
+	if (ret) {
+		usage(argv[0]);
+		return ret;
+	}
+
+	st = open_session(args.instance);
+	if (!st)
+		return -1;
+
+	ret = gather_tlm_state(st);
+	if (ret) {
+		free(st);
+		return ret;
+	}
+
+	if (!args.cmd) {
+		full_scan_and_dump(st);
+	} else if (!strncmp(args.cmd, "info", strlen(args.cmd))) {
+		dump_state(st, &args);
+	} else if (!strncmp(args.cmd, "uuids", strlen(args.cmd))) {
+		uuids(st, &args);
+	} else if (!strncmp(args.cmd, "monitor", strlen(args.cmd))) {
+		monitor(st, &args);
+	} else if (!strncmp(args.cmd, "shmti", strlen(args.cmd))) {
+		shmti_ops(st, &args);
+	} else if (!strncmp(args.cmd, "des", strlen(args.cmd))) {
+		dump_des(st);
+	} else if (!strncmp(args.cmd, "groups", strlen(args.cmd))) {
+		dump_groups(st);
+	} else if (!strncmp(args.cmd, "cfg", strlen(args.cmd))) {
+		configure(st, &args);
+	} else if (!strncmp(args.cmd, "single", strlen(args.cmd))) {
+		single_read(st);
+	} else if (!strncmp(args.cmd, "bulk", strlen(args.cmd))) {
+		bulk_read(st);
+	} else if (!strncmp(args.cmd, "batch", strlen(args.cmd))) {
+		batch_read(st, &args);
+	} else if (!strncmp(args.cmd, "reset", strlen(args.cmd))) {
+		tlm_reset(st);
+	} else {
+		fprintf(stderr, "Unknown command '%s'\n", args.cmd);
+		usage(argv[0]);
+		return -1;
+	}
+
+	return 0;
+}
-- 
2.54.0