diff --git a/Documentation/ABI/testing/sysfs-class-tsm b/Documentation/ABI/testing/sysfs-class-tsm
new file mode 100644
index 000000000000..304b50b53e65
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-tsm
@@ -0,0 +1,12 @@
+What:		/sys/class/tsm/tsm0/host
+Date:		January 2024
+Contact:	linux-coco@lists.linux.dev
+Description:
+		(RO) For hardware TSMs represented by a device in /sys/devices,
+		@host is a link to that device.
+		Links to hardware TSM sysfs ABIs:
+		- Documentation/ABI/testing/sysfs-driver-ccp
+
+		For software TSMs instantiated by a software module, @host is a
+		directory with attributes for that TSM, and those attributes are
+		documented below.
diff --git a/drivers/virt/coco/tsm/Kconfig b/drivers/virt/coco/tsm/Kconfig
index 69f04461c83e..595d86917462 100644
--- a/drivers/virt/coco/tsm/Kconfig
+++ b/drivers/virt/coco/tsm/Kconfig
@@ -5,3 +5,10 @@
 config TSM_REPORTS
 	select CONFIGFS_FS
 	tristate
+
+config ARCH_HAS_TSM
+	bool
+
+config TSM
+	depends on ARCH_HAS_TSM && SYSFS
+	tristate
diff --git a/drivers/virt/coco/tsm/Makefile b/drivers/virt/coco/tsm/Makefile
index b48504a3ccfd..f7561169faed 100644
--- a/drivers/virt/coco/tsm/Makefile
+++ b/drivers/virt/coco/tsm/Makefile
@@ -4,3 +4,6 @@
 
 obj-$(CONFIG_TSM_REPORTS) += tsm_reports.o
 tsm_reports-y := reports.o
+
+obj-$(CONFIG_TSM) += tsm.o
+tsm-y := class.o
diff --git a/drivers/virt/coco/tsm/class.c b/drivers/virt/coco/tsm/class.c
new file mode 100644
index 000000000000..a569fa6b09eb
--- /dev/null
+++ b/drivers/virt/coco/tsm/class.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2024 Intel Corporation. All rights reserved. */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/tsm.h>
+#include <linux/rwsem.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/cleanup.h>
+
+static DECLARE_RWSEM(tsm_core_rwsem);
+struct class *tsm_class;
+struct tsm_subsys {
+	struct device dev;
+	const struct tsm_info *info;
+} *tsm_subsys;
+
+int tsm_register(const struct tsm_info *info)
+{
+	struct device *dev __free(put_device) = NULL;
+	struct tsm_subsys *subsys;
+	int rc;
+
+	guard(rwsem_write)(&tsm_core_rwsem);
+	if (tsm_subsys) {
+		pr_warn("failed to register: \"%s\", \"%s\" already registered\n",
+			info->name, tsm_subsys->info->name);
+		return -EBUSY;
+	}
+
+	subsys = kzalloc(sizeof(*subsys), GFP_KERNEL);
+	if (!subsys)
+		return -ENOMEM;
+
+	subsys->info = info;
+	dev = &subsys->dev;
+	dev->class = tsm_class;
+	dev->groups = info->groups;
+	dev_set_name(dev, "tsm0");
+	rc = device_register(dev);
+	if (rc)
+		return rc;
+
+	if (info->host) {
+		rc = sysfs_create_link(&dev->kobj, &info->host->kobj, "host");
+		if (rc)
+			return rc;
+	}
+
+	/* don't auto-free @dev */
+	dev = NULL;
+	tsm_subsys = subsys;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(tsm_register);
+
+void tsm_unregister(const struct tsm_info *info)
+{
+	guard(rwsem_write)(&tsm_core_rwsem);
+	if (!tsm_subsys || info != tsm_subsys->info) {
+		pr_warn("failed to unregister: \"%s\", not currently registered\n",
+			info->name);
+		return;
+	}
+
+	if (info->host)
+		sysfs_remove_link(&tsm_subsys->dev.kobj, "host");
+	device_unregister(&tsm_subsys->dev);
+	tsm_subsys = NULL;
+}
+EXPORT_SYMBOL_GPL(tsm_unregister);
+
+static void tsm_release(struct device *dev)
+{
+	struct tsm_subsys *subsys = container_of(dev, typeof(*subsys), dev);
+
+	kfree(subsys);
+}
+
+static int __init tsm_init(void)
+{
+	tsm_class = class_create("tsm");
+	if (IS_ERR(tsm_class))
+		return PTR_ERR(tsm_class);
+
+	tsm_class->dev_release = tsm_release;
+	return 0;
+}
+module_init(tsm_init)
+
+static void __exit tsm_exit(void)
+{
+	class_destroy(tsm_class);
+}
+module_exit(tsm_exit)
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Trusted Security Module core device model");
diff --git a/include/linux/tsm.h b/include/linux/tsm.h
index 28753608fcf5..8cb8a661ba41 100644
--- a/include/linux/tsm.h
+++ b/include/linux/tsm.h
@@ -5,6 +5,12 @@
 #include <linux/sizes.h>
 #include <linux/types.h>
 
+struct tsm_info {
+	const char *name;
+	struct device *host;
+	const struct attribute_group **groups;
+};
+
 #define TSM_REPORT_INBLOB_MAX 64
 #define TSM_REPORT_OUTBLOB_MAX SZ_32K
 
@@ -66,4 +72,6 @@ extern const struct config_item_type tsm_report_extra_type;
 int tsm_report_register(const struct tsm_report_ops *ops, void *priv,
 			const struct config_item_type *type);
 int tsm_report_unregister(const struct tsm_report_ops *ops);
+int tsm_register(const struct tsm_info *info);
+void tsm_unregister(const struct tsm_info *info);
 #endif /* __TSM_H */
