From d495fccb718a6674954c3a5b50f8066ce3d2a1ee Mon Sep 17 00:00:00 2001 From: Nicola Ramacciotti Date: Tue, 23 Jun 2026 11:09:45 +0200 Subject: [PATCH 1/4] x86/sev: Add SVSM OCP protocol handling Add and export some utitily functions to interact with svsm as defined in the ocp draft protocol. Signed-off-by: Nicola Ramacciotti --- arch/x86/coco/sev/svsm.c | 96 ++++++++++++++++++++++++++++++++++++++ arch/x86/include/asm/sev.h | 39 ++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/arch/x86/coco/sev/svsm.c b/arch/x86/coco/sev/svsm.c index 2acf4a76afe7a9..8b97bcbd728aea 100644 --- a/arch/x86/coco/sev/svsm.c +++ b/arch/x86/coco/sev/svsm.c @@ -360,3 +360,99 @@ bool snp_svsm_vtpm_probe(void) /* Check platform commands contains TPM_SEND_COMMAND - platform command 8 */ return call.rcx_out & BIT_ULL(8); } + +int snp_svsm_ocp_list_objects(u8 *buffer, u64 first_entry, u64 num_entries, + u32 *entries_returned) +{ + struct svsm_call call = {}; + int ret; + + call.caa = svsm_get_caa(); + call.rax = SVSM_OCP_CALL(SVSM_OCP_LIST_OBJECTS); + call.rcx = first_entry; + call.rdx = __pa(buffer); + call.r8 = num_entries; + + ret = svsm_perform_call_protocol(&call); + + if (ret < 0) { + return ret; + } + + *entries_returned = call.rcx_out; + return 0; +} +EXPORT_SYMBOL_GPL(snp_svsm_ocp_list_objects); + +int snp_svsm_ocp_list_object_sources(u8 *buffer, u32 object_entry, + u32 first_entry, u64 num_entries, + u32 *entries_returned) +{ + struct svsm_call call = {}; + int ret; + + call.caa = svsm_get_caa(); + call.rax = SVSM_OCP_CALL(SVSM_OCP_LIST_OBJECT_SOURCES); + call.rcx = ((u64)object_entry << 32) | first_entry; + call.rdx = __pa(buffer); + call.r8 = num_entries; + + ret = svsm_perform_call_protocol(&call); + + if (ret < 0) { + return ret; + } + + *entries_returned = call.rcx_out; + return 0; +} +EXPORT_SYMBOL_GPL(snp_svsm_ocp_list_object_sources); + +int snp_svsm_ocp_read_source(u8 *buffer, u32 object_entry, u32 source_idx, + u64 bytes_to_read, u64 offset, u32 *bytes_read) +{ + struct svsm_call call = {}; + int ret; + + call.caa = svsm_get_caa(); + call.rax = SVSM_OCP_CALL(SVSM_OCP_READ); + call.rcx = ((u64)object_entry << 32) | source_idx; + call.rdx = __pa(buffer); + call.r8 = bytes_to_read; + call.r9 = offset; + + ret = svsm_perform_call_protocol(&call); + + if (ret < 0) { + return ret; + } + + *bytes_read = call.r8_out; + return 0; +} +EXPORT_SYMBOL_GPL(snp_svsm_ocp_read_source); + +int snp_svsm_ocp_write_source(u8 *buffer, u32 object_entry, u32 source_idx, + u64 bytes_to_write, u64 offset, + u32 *bytes_written) +{ + struct svsm_call call = {}; + int ret; + + call.caa = svsm_get_caa(); + call.rax = SVSM_OCP_CALL(SVSM_OCP_WRITE); + call.rcx = ((u64)object_entry << 32) | source_idx; + call.rdx = __pa(buffer); + call.r8 = bytes_to_write; + call.r9 = offset; + + ret = svsm_perform_call_protocol(&call); + + if (ret < 0) { + return ret; + } + + *bytes_written = call.r8_out; + return 0; +} +EXPORT_SYMBOL_GPL(snp_svsm_ocp_write_source); diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h index 09e605c85de4cd..2f3e3764086e72 100644 --- a/arch/x86/include/asm/sev.h +++ b/arch/x86/include/asm/sev.h @@ -435,6 +435,12 @@ struct svsm_call { #define SVSM_VTPM_QUERY 0 #define SVSM_VTPM_CMD 1 +#define SVSM_OCP_CALL(x) ((5ULL << 32) | (x)) +#define SVSM_OCP_LIST_OBJECTS 0 +#define SVSM_OCP_LIST_OBJECT_SOURCES 1 +#define SVSM_OCP_READ 2 +#define SVSM_OCP_WRITE 3 + #ifdef CONFIG_AMD_MEM_ENCRYPT extern u8 snp_vmpl; @@ -528,6 +534,16 @@ void snp_msg_free(struct snp_msg_desc *mdesc); int snp_send_guest_request(struct snp_msg_desc *mdesc, struct snp_guest_req *req); int snp_svsm_vtpm_send_command(u8 *buffer); +int snp_svsm_ocp_list_objects(u8 *buffer, u64 first_entry, u64 num_entries, + u32 *entries_returned); +int snp_svsm_ocp_list_object_sources(u8 *buffer, u32 object_entry, + u32 first_entry, u64 num_entries, + u32 *entries_returned); +int snp_svsm_ocp_read_source(u8 *buffer, u32 object_entry, u32 source_idx, + u64 bytes_to_read, u64 offset, u32 *bytes_read); +int snp_svsm_ocp_write_source(u8 *buffer, u32 object_entry, u32 source_idx, + u64 bytes_to_write, u64 offset, + u32 *bytes_written); void __init snp_secure_tsc_prepare(void); void __init snp_secure_tsc_init(void); @@ -636,6 +652,29 @@ static inline void snp_msg_free(struct snp_msg_desc *mdesc) { } static inline int snp_send_guest_request(struct snp_msg_desc *mdesc, struct snp_guest_req *req) { return -ENODEV; } static inline int snp_svsm_vtpm_send_command(u8 *buffer) { return -ENODEV; } +static int snp_svsm_ocp_list_objects(u8 *buffer, u64 first_entry, + u64 num_entries, u32 *entries_returned) +{ + return -ENODEV; +} +static int snp_svsm_ocp_list_object_sources(u8 *buffer, u32 object_entry, + u32 first_entry, u64 num_entries, + u32 *entries_returned) +{ + return -ENODEV; +} +static int snp_svsm_ocp_read_source(u8 *buffer, u32 object_entry, + u32 source_idx, u64 bytes_to_read, + u64 offset, u32 *bytes_read) +{ + return -ENODEV; +} +static int snp_svsm_ocp_write_source(u8 *buffer, u32 object_entry, + u32 source_idx, u64 bytes_to_write, + u64 offset, u32 *bytes_written) +{ + return -ENODEV; +} static inline void __init snp_secure_tsc_prepare(void) { } static inline void __init snp_secure_tsc_init(void) { } static inline void sev_evict_cache(void *va, int npages) {} From 758e7338bbcc4d9b3f126791d419c3aafd80b90e Mon Sep 17 00:00:00 2001 From: Nicola Ramacciotti Date: Tue, 23 Jun 2026 15:22:30 +0200 Subject: [PATCH 2/4] drivers/char: Add OCP driver Add a minimal driver for svsm ocp protocol. Signed-off-by: Nicola Ramacciotti --- drivers/char/Kconfig | 10 +++ drivers/char/Makefile | 2 + drivers/char/ocp_svsm.c | 152 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 drivers/char/ocp_svsm.c diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 2a3a37b2cf3cb9..1ff677cdb90f19 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -397,4 +397,14 @@ config ADI and SSM (Silicon Secured Memory). Intended consumers of this driver include crash and makedumpfile. +config OCP_SVSM + tristate "SNP SVSM OCP interface" + depends on AMD_MEM_ENCRYPT + default m + help + This is a driver for the AMD SVSM OCP protocol that a SEV-SNP guest + OS can use to retrieve or configure state information of the Secure + VM Service Module (SVSM) in the guest context. To compile this + driver, choose M here. + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 47bdc882797adc..5273b099fc8b85 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -43,3 +43,5 @@ obj-$(CONFIG_PS3_FLASH) += ps3flash.o obj-$(CONFIG_XILLYBUS_CLASS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o obj-$(CONFIG_ADI) += adi.o + +obj-$(CONFIG_OCP_SVSM) += ocp_svsm.o diff --git a/drivers/char/ocp_svsm.c b/drivers/char/ocp_svsm.c new file mode 100644 index 00000000000000..a2c6e79e3d6fbd --- /dev/null +++ b/drivers/char/ocp_svsm.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OCP_CLASS "ocp" +#define OCP_DEVICE "ocp" + +#define OCP_BUF_SIZE 4096 + +static struct class *cl; + +struct ocp_dev { + struct device dev; + struct cdev cdev; + dev_t dev_num; + + struct mutex buffer_mutex; + + char *buffer; +}; + +static void ocp_dev_release(struct device *dev) +{ + struct ocp_dev *ocp = container_of(dev, struct ocp_dev, dev); + + mutex_destroy(&ocp->buffer_mutex); + + kfree(ocp->buffer); + + kfree(ocp); + pr_info("released ocp driver dev"); +} + +static int ocp_open(struct inode *inode, struct file *file) +{ + struct ocp_dev *ocp; + + ocp = container_of(inode->i_cdev, struct ocp_dev, cdev); + + file->private_data = ocp; + + return 0; +} + +static const struct file_operations ocp_fops = { + .owner = THIS_MODULE, + .open = ocp_open, +}; + +static int __init ocp_svsm_probe(struct platform_device *pdev) +{ + int ret; + struct ocp_dev *ocp; + + cl = class_create(OCP_CLASS); + if (IS_ERR(cl)) + return PTR_ERR(cl); + + ocp = kzalloc_obj(*ocp); + if (!ocp) { + class_destroy(cl); + return -ENOMEM; + } + + ocp->buffer = kzalloc(OCP_BUF_SIZE, GFP_KERNEL); + if (!ocp->buffer) { + kfree(ocp); + class_destroy(cl); + return -ENOMEM; + } + + mutex_init(&ocp->buffer_mutex); + + ret = alloc_chrdev_region(&ocp->dev_num, 0, 1, OCP_DEVICE); + if (ret < 0) { + kfree(ocp->buffer); + kfree(ocp); + class_destroy(cl); + return ret; + } + + device_initialize(&ocp->dev); + ocp->dev.class = cl; + ocp->dev.devt = ocp->dev_num; + ocp->dev.parent = &pdev->dev; + ocp->dev.release = ocp_dev_release; + + ret = dev_set_name(&ocp->dev, OCP_DEVICE); + if (ret) { + unregister_chrdev_region(ocp->dev_num, 1); + put_device(&ocp->dev); + class_destroy(cl); + return ret; + } + + cdev_init(&ocp->cdev, &ocp_fops); + + platform_set_drvdata(pdev, ocp); + + ret = cdev_device_add(&ocp->cdev, &ocp->dev); + + if (ret) { + unregister_chrdev_region(ocp->dev_num, 1); + put_device(&ocp->dev); + class_destroy(cl); + return ret; + } + + pr_info("successfully loaded ocp driver\n"); + return 0; +} + +static void __exit ocp_svsm_remove(struct platform_device *pdev) +{ + struct ocp_dev *ocp = platform_get_drvdata(pdev); + + cdev_device_del(&ocp->cdev, &ocp->dev); + + unregister_chrdev_region(ocp->dev_num, 1); + + put_device(&ocp->dev); + + class_destroy(cl); + pr_info("successfully removed ocp driver\n"); +} + +/* + * ocp_svsm_remove() lives in .exit.text. For drivers registered via + * module_platform_driver_probe() this is ok because they cannot get unbound + * at runtime. So mark the driver struct with __refdata to prevent modpost + * triggering a section mismatch warning. + */ +static struct platform_driver ocp_svsm_driver __refdata = { + .remove = __exit_p(ocp_svsm_remove), + .driver = { + .name = "ocp-svsm", + }, +}; + +module_platform_driver_probe(ocp_svsm_driver, ocp_svsm_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nicola Ramacciotti"); +MODULE_DESCRIPTION("SNP SVSM OCP Driver"); From 0e80fd87ddffdd9f06394fdc28c9831d27ae99d2 Mon Sep 17 00:00:00 2001 From: Nicola Ramacciotti Date: Wed, 22 Jul 2026 12:05:24 +0200 Subject: [PATCH 3/4] x86/sev: Register ocp-svsm platform device Add `ocp-svsm` device that can be handled by the platform driver added in the previous commits in `drivers/char/ocp_svsm.c` TODO: Register the platform device only when SVSM is available and it supports OCP calls. Signed-off-by: Nicola Ramacciotti --- arch/x86/coco/sev/core.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c index 7ed3da998489d9..8d3d1f893ebd4c 100644 --- a/arch/x86/coco/sev/core.c +++ b/arch/x86/coco/sev/core.c @@ -1395,6 +1395,11 @@ static struct platform_device tpm_svsm_device = { .id = -1, }; +static struct platform_device ocp_svsm_device = { + .name = "ocp-svsm", + .id = -1, +}; + static int __init snp_init_platform_device(void) { if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP)) @@ -1407,6 +1412,10 @@ static int __init snp_init_platform_device(void) platform_device_register(&tpm_svsm_device)) return -ENODEV; + // TODO: add probe like above + if (platform_device_register(&ocp_svsm_device)) + return -ENODEV; + pr_info("SNP guest platform devices initialized.\n"); return 0; } From 749b2ba6e1548a091ceff8f5bef50d464863a47b Mon Sep 17 00:00:00 2001 From: Nicola Ramacciotti Date: Thu, 23 Jul 2026 11:55:21 +0200 Subject: [PATCH 4/4] drivers/ocp_svsm: Introduce ioctl interface todo Signed-off-by: Nicola Ramacciotti --- .../userspace-api/ioctl/ioctl-number.rst | 1 + drivers/char/ocp_svsm.c | 95 ++++++++++++++++++ include/uapi/linux/ocp_svsm.h | 96 +++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 include/uapi/linux/ocp_svsm.h diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 331223761ffffd..f8b92b10149ffe 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -412,4 +412,5 @@ Code Seq# Include File Comments 0xFD all linux/dm-ioctl.h 0xFE all linux/isst_if.h +0xFF 00-0F uapi/linux/ocp_svsm.h ==== ===== ========================================================= ================================================================ diff --git a/drivers/char/ocp_svsm.c b/drivers/char/ocp_svsm.c index a2c6e79e3d6fbd..dc21392d87e445 100644 --- a/drivers/char/ocp_svsm.c +++ b/drivers/char/ocp_svsm.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #define OCP_CLASS "ocp" #define OCP_DEVICE "ocp" @@ -50,9 +52,102 @@ static int ocp_open(struct inode *inode, struct file *file) return 0; } +static long ocp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ocp_dev *ocp = file->private_data; + struct ocp_svsm_list_objects list_objects; + struct ocp_svsm_list_object_sources list_sources; + struct ocp_svsm_read_source read_source; + struct ocp_svsm_write_source write_source; + void __user *argp = (void __user *)arg; + u32 value_moved = 0; + unsigned long write_size = 0; + //TODO: move value written/read as return of ocp_ioctl insted of copying it into arg? + + mutex_lock(&ocp->buffer_mutex); + + switch (cmd) { + case OCP_SVSM_IOCTL_LIST_OBJECTS: + if (copy_from_user(&list_objects, argp, sizeof(list_objects))) + goto unlock_mutex; + if (snp_svsm_ocp_list_objects( + ocp->buffer, list_objects.first_entry, + list_objects.num_entries, &value_moved)) + goto unlock_mutex; + if (copy_to_user(u64_to_user_ptr(list_objects.buf_ptr), + ocp->buffer, + value_moved * OCP_OBJECT_DETAILS_SIZE)) + goto unlock_mutex; + list_objects.entries_returned = value_moved; + if (copy_to_user(argp, &list_objects, sizeof(list_objects))) + goto unlock_mutex; + break; + case OCP_SVSM_IOCTL_LIST_OBJECT_SOURCES: + if (copy_from_user(&list_sources, argp, sizeof(list_sources))) + goto unlock_mutex; + if (snp_svsm_ocp_list_object_sources( + ocp->buffer, list_sources.object_index, + list_sources.first_entry, list_sources.num_entries, + &value_moved)) + goto unlock_mutex; + if (copy_to_user(u64_to_user_ptr(list_sources.buf_ptr), + ocp->buffer, + value_moved * OCP_SOURCE_ENTRY_SIZE)) + goto unlock_mutex; + list_sources.entries_returned = value_moved; + if (copy_to_user(argp, &list_sources, sizeof(list_sources))) + goto unlock_mutex; + break; + case OCP_SVSM_IOCTL_READ_SOURCE: + if (copy_from_user(&read_source, argp, sizeof(read_source))) + goto unlock_mutex; + if (snp_svsm_ocp_read_source( + ocp->buffer, read_source.object_index, + read_source.source_index, read_source.bytes_to_read, + read_source.offset, &value_moved)) + goto unlock_mutex; + if (copy_to_user(u64_to_user_ptr(read_source.buf_ptr), + ocp->buffer, value_moved)) + goto unlock_mutex; + read_source.bytes_read = value_moved; + if (copy_to_user(argp, &read_source, sizeof(read_source))) + goto unlock_mutex; + break; + case OCP_SVSM_IOCTL_WRITE_SOURCE: + if (copy_from_user(&write_source, argp, sizeof(write_source))) + goto unlock_mutex; + write_size = OCP_BUF_SIZE > write_source.bytes_to_write ? + write_source.bytes_to_write : + OCP_BUF_SIZE; + if (copy_from_user(ocp->buffer, + u64_to_user_ptr(write_source.buf_ptr), + write_size)) + goto unlock_mutex; + if (snp_svsm_ocp_write_source( + ocp->buffer, write_source.object_index, + write_source.source_index, write_size, + write_source.offset, &value_moved)) + goto unlock_mutex; + write_source.bytes_written = value_moved; + if (copy_to_user(argp, &write_source, sizeof(write_source))) + goto unlock_mutex; + break; + default: + goto unlock_mutex; + } + + mutex_unlock(&ocp->buffer_mutex); + return 0; + +unlock_mutex: + mutex_unlock(&ocp->buffer_mutex); + return -1; +} + static const struct file_operations ocp_fops = { .owner = THIS_MODULE, .open = ocp_open, + .unlocked_ioctl = ocp_ioctl, }; static int __init ocp_svsm_probe(struct platform_device *pdev) diff --git a/include/uapi/linux/ocp_svsm.h b/include/uapi/linux/ocp_svsm.h new file mode 100644 index 00000000000000..2c18db7f87fc33 --- /dev/null +++ b/include/uapi/linux/ocp_svsm.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Userspace interface for /dev/ocp_svsm - SVSM observability and configuration protocol + * + * This header provides the IOCTL commands and data structures that enable programs + * to interact with the SVSM OCP driver. + * + */ + +#ifndef _UAPI_OCP_SVSM_H +#define _UAPI_OCP_SVSM_H + +#include +#include + +#define OCP_OBJECT_DETAILS_SIZE 12 +#define OCP_SOURCE_ENTRY_SIZE 128 +#define OCP_MAX_BUFFER_SIZE 4096 +#define OCP_SOURCE_NAME 112 +#define OCP_SOURCE_FLAGS_SIZE 4 + +enum ocp_svsm_object_type { SVSM = 0 }; + +struct ocp_svsm_object_details { + __u32 category; + __u32 index; + __u32 count; +}; + +enum ocp_svsm_source_type { SVSM_STATIC_STRING = 0, SVSM_INTEGER = 1 }; + +struct ocp_svsm_source_flags { + __u32 writable : 1; + __u32 _reserved : 31; +}; + +struct ocp_svsm_source { + __u32 sup_index; + __u32 sub_index; + __u32 kind; + __u32 flags; + __u8 name[OCP_SOURCE_NAME]; +}; + +struct ocp_svsm_list_objects { + __u64 buf_ptr; + __u32 first_entry; + __u32 num_entries; + __u32 entries_returned; +}; + +struct ocp_svsm_list_object_sources { + __u64 buf_ptr; + __u32 object_index; + __u32 first_entry; + __u32 num_entries; + __u32 entries_returned; +}; + +struct ocp_svsm_read_source { + __u64 buf_ptr; + __u32 object_index; + __u32 source_index; + __u32 offset; + __u32 bytes_to_read; + __u32 bytes_read; +}; + +struct ocp_svsm_write_source { + __u64 buf_ptr; + __u32 object_index; + __u32 source_index; + __u32 offset; + __u32 bytes_to_write; + __u32 bytes_written; +}; + +/* The IOCTL code defined in ioctl-number.rst */ +#define OCP_SVSM_IOCTL_TYPE 0xFF + +/* The available IOCTL commands */ +#define OCP_SVSM_IOCTL_LIST_OBJECTS \ + _IOWR(OCP_SVSM_IOCTL_TYPE, 0x01, struct ocp_svsm_list_objects) +#define OCP_SVSM_IOCTL_LIST_OBJECT_SOURCES \ + _IOWR(OCP_SVSM_IOCTL_TYPE, 0x02, struct ocp_svsm_list_object_sources) +#define OCP_SVSM_IOCTL_READ_SOURCE \ + _IOWR(OCP_SVSM_IOCTL_TYPE, 0x03, struct ocp_svsm_read_source) +#define OCP_SVSM_IOCTL_WRITE_SOURCE \ + _IOWR(OCP_SVSM_IOCTL_TYPE, 0x04, struct ocp_svsm_write_source) + +static_assert(sizeof(struct ocp_svsm_object_details) == + OCP_OBJECT_DETAILS_SIZE); +static_assert(sizeof(struct ocp_svsm_source_flags) == OCP_SOURCE_FLAGS_SIZE); +static_assert(sizeof(struct ocp_svsm_source) == OCP_SOURCE_ENTRY_SIZE); + +#endif /* _UAPI_OCP_SVSM_H */