Files
Deshab-OS/CODE/linux/patches/utsm_hcall.c
T

787 lines
24 KiB
C
Raw Normal View History

2026-07-28 12:00:02 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* UTSM hypercall driver — Linux guest side.
*
* Provides:
* 1. VMCALL hypercall interface to communicate with the UTSM monitor kernel
* 2. /dev/utsm character device for userspace IPC message passing
* 3. Shared memory ring buffer mapping (queried via UTSM_HCALL_SHM_INFO)
*
* The driver is platform-agnostic: it probes for UTSM by issuing a PING
* hypercall at init. If UTSM is not present (e.g. running on bare metal
* or a different hypervisor), the driver exits gracefully.
*
* Build: configured via CONFIG_UTSM_HCALL (built-in or module).
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/mm.h>
2026-07-28 14:35:14 +08:00
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/msr.h>
#ifndef MSR_IA32_U_CET
#define MSR_IA32_U_CET 0x000006a0
#endif
#ifndef MSR_IA32_S_CET
#define MSR_IA32_S_CET 0x000006a2
#endif
2026-07-28 12:00:02 +08:00
2026-07-29 10:26:25 +08:00
/* Shared IPC protocol header (included via build.sh -I flag).
* We provide our own hypercall wrappers, so skip the inline ones. */
#define UTSM_NO_INLINE_HCALL
2026-07-28 12:00:02 +08:00
#include <ipc_proto.h>
#ifndef UTSM_HCALL_PARK
#define UTSM_HCALL_PARK 0x0003
#endif
2026-07-28 12:00:02 +08:00
#define UTSM_DEV_NAME "utsm"
2026-07-28 14:35:14 +08:00
/* ===== Ioctl interface for exec daemon =====
*
* The exec daemon (userspace) uses these ioctls to:
* 1. RECV_MSG: poll utsm_to_linux ring for EXEC_REQUEST (non-blocking)
* 2. SEND_MSG: push STDOUT/STDERR/EXIT to linux_to_utsm ring
* 3. PARK: execute HLT to trigger VM-Exit → UTSM parks guest,
* returning control to the host. vmresume causes the
* ioctl to return, at which point the daemon re-checks
* the ring for new requests.
*
* This explicit-park model avoids the need for timer emulation or
* IRQ injection: the daemon HLTs only when it has nothing to do,
* and UTSM vmresumes only when there is work. */
#define UTSM_IOCTL_RECV_MSG _IOWR('U', 1, struct utsm_ioctl_msg)
#define UTSM_IOCTL_SEND_MSG _IOWR('U', 2, struct utsm_ioctl_msg)
#define UTSM_IOCTL_PARK _IO('U', 3)
#define UTSM_IOCTL_GET_READY _IOR('U', 4, int)
2026-07-31 13:53:49 +08:00
#define UTSM_IOCTL_WRITE_POOL _IOW('U', 5, struct utsm_ioctl_pool)
#define UTSM_IOCTL_READ_POOL _IOWR('U', 6, struct utsm_ioctl_pool)
#define UTSM_IOCTL_HOST_READ _IOWR('U', 7, struct utsm_ioctl_hostfile)
#define UTSM_IOCTL_HOST_WRITE _IOW('U', 8, struct utsm_ioctl_hostfile)
2026-08-09 22:14:01 +08:00
/* VSCode integration Phase 1: query graphics surface pool GPA + size.
* Userspace then mmaps /dev/utsm (offset 0, len = size) to obtain a
* writable scanout buffer shared with the UTSM host. */
#define UTSM_IOCTL_SURFACE_INFO _IOR('U', 9, struct utsm_ioctl_surface)
2026-07-28 14:35:14 +08:00
/* Ioctl message wrapper: carries type + data in a single call */
struct utsm_ioctl_msg {
u32 type; /* message type (in/out) */
u32 data_len; /* actual bytes in data (in/out) */
u32 reserved;
u32 buf_size; /* capacity of data buffer (in) */
u8 data[UTSM_IPC_MSG_DATA_SIZE]; /* payload */
};
2026-07-31 13:53:49 +08:00
/* Pool transfer descriptor for WRITE_POOL / READ_POOL ioctls.
* Used by the exec daemon to stage large file-transfer payloads into the
* shared payload_pool (bulk data channel for UTSM_MSG_FILE_RESPONSE). */
struct utsm_ioctl_pool {
u32 offset; /* byte offset into payload_pool */
u32 len; /* bytes to transfer */
u64 user_buf; /* userspace buffer pointer */
};
/* Host-file transfer descriptor for HOST_READ / HOST_WRITE ioctls.
* Gives Linux userspace access to files on the UTSM-side FAT32 system disk
* (root directory, 8.3-convertible names, whole-file semantics, max 256KB)
* via the UTSM_HCALL_FILE_READ / UTSM_HCALL_FILE_WRITE hypercalls.
* payload_pool at offset 0 is the bulk data buffer in both directions. */
struct utsm_ioctl_hostfile {
u64 offset; /* in: file offset (HOST_READ; must be 0 for HOST_WRITE) */
u32 len; /* in: requested length; out: bytes transferred */
u32 reserved;
u64 total; /* out: total file size (HOST_READ) */
u64 user_buf; /* userspace buffer pointer */
char name[64]; /* UTSM FAT32 root file name (NUL-terminated) */
};
2026-08-09 22:14:01 +08:00
/* VSCode integration Phase 1: graphics surface pool descriptor.
* Returned by UTSM_IOCTL_SURFACE_INFO. Userspace then mmaps /dev/utsm
* (offset 0, len = size) to obtain a writable scanout buffer that UTSM
* reads to blit guest graphics to the Deshab framebuffer. */
struct utsm_ioctl_surface {
u64 gpa; /* out: guest physical address of surface pool */
u64 size; /* out: surface pool size in bytes */
};
2026-07-28 12:00:02 +08:00
/* ===== Hypercall ABI =====
*
* Linux calls VMCALL with:
* RAX = UTSM_HCALL_MAGIC | op
* RDI = arg0, RSI = arg1, RDX = arg2
*
* UTSM handles the VMCALL VM-Exit and returns:
* RAX = return value
*
* Since we run in the guest, the VMCALL instruction triggers a VM-Exit
* to UTSM. The inline assembly must use the same register conventions.
*/
static inline long utsm_hcall(u64 op, u64 a0, u64 a1, u64 a2)
{
long ret;
register u64 rax asm("rax") = UTSM_HCALL_MAGIC | op;
register u64 rdi asm("rdi") = a0;
register u64 rsi asm("rsi") = a1;
register u64 rdx asm("rdx") = a2;
asm volatile("vmcall"
: "+r"(rax)
: "r"(rdi), "r"(rsi), "r"(rdx)
: "memory");
return (long)rax;
}
static inline long utsm_hcall_ping(void)
{
return utsm_hcall(UTSM_HCALL_PING, 0, 0, 0);
}
static inline long utsm_hcall_hello(void)
{
return utsm_hcall(UTSM_HCALL_HELLO, 0, 0, 0);
}
static inline long utsm_hcall_shm_info(u64 *gpa_out, u64 *size_out)
{
u64 gpa = 0, size = 0;
long ret;
/* Pass stack addresses (GVA=GPA due to identity mapping in early boot).
* UTSM translates GPA→HPA via EPT walk and writes the values there. */
ret = utsm_hcall(UTSM_HCALL_SHM_INFO, (u64)&gpa, (u64)&size, 0);
if (ret == 0) {
*gpa_out = gpa;
*size_out = size;
}
return ret;
}
2026-08-09 22:14:01 +08:00
/* VSCode integration Phase 1: query graphics surface pool.
* Same GVA==GPA convention as utsm_hcall_shm_info. Returns 0 on success
* and fills *gpa_out + *size_out; negative UTSM_HCALL_* on error. */
static inline long utsm_hcall_surface_info(u64 *gpa_out, u64 *size_out)
{
u64 gpa = 0, size = 0;
long ret;
ret = utsm_hcall(UTSM_HCALL_SURFACE_INFO, (u64)&gpa, (u64)&size, 0);
if (ret == 0) {
*gpa_out = gpa;
*size_out = size;
}
return ret;
}
2026-07-28 12:00:02 +08:00
static inline long utsm_hcall_console_write(const char *buf, u64 len)
{
return utsm_hcall(UTSM_HCALL_CONSOLE_WRITE, (u64)buf, len, 0);
}
2026-07-31 13:53:49 +08:00
/* UTSM FAT32 file read: stages file[offset..offset+len) into payload_pool.
* Returns positive packed value (low 20 bits = staged bytes, >> 20 = total
* file size) or a negative UTSM_HCALL_* error. The name pointer follows the
* same GVA==GPA identity-mapping convention as the other hypercalls. */
static inline long utsm_hcall_file_read(const char *name, u64 offset, u64 len)
{
return utsm_hcall(UTSM_HCALL_FILE_READ, (u64)name, offset, len);
}
/* UTSM FAT32 file write: writes len bytes staged at payload_pool[0] as the
* whole new content of the named root file (create or replace).
* Returns bytes written or a negative UTSM_HCALL_* error. */
static inline long utsm_hcall_file_write(const char *name, u64 len)
{
return utsm_hcall(UTSM_HCALL_FILE_WRITE, (u64)name, 0, len);
}
2026-07-28 12:00:02 +08:00
/* ===== Shared memory state ===== */
static struct utsm_ipc_shm *g_shm; /* kernel virtual address of SHM */
static u64 g_shm_gpa; /* guest physical address of SHM */
static u64 g_shm_size; /* size of SHM region */
static int g_utsm_present; /* 1 = UTSM detected */
static int g_shm_mapped;
2026-08-09 22:14:01 +08:00
/* VSCode integration Phase 1: graphics surface pool state.
* Queried via UTSM_HCALL_SURFACE_INFO at init; exposed to userspace via
* UTSM_IOCTL_SURFACE_INFO + mmap(/dev/utsm). g_surface_kaddr is the kernel
* virtual mapping used internally by the mmap handler to validate requests. */
static u64 g_surface_gpa; /* GPA of surface pool (0 = none) */
static u64 g_surface_size; /* size in bytes */
static int g_surface_present; /* 1 = surface pool available */
2026-07-28 12:00:02 +08:00
/* ===== /dev/utsm character device ===== */
static ssize_t utsm_dev_write(struct file *f, const char __user *buf,
size_t len, loff_t *off)
{
struct utsm_ipc_msg msg;
size_t copy_len;
ssize_t ret;
if (!g_shm_mapped)
return -ENODEV;
if (len == 0)
return 0;
/* Cap data to inline message size */
copy_len = min_t(size_t, len, UTSM_IPC_MSG_DATA_SIZE);
memset(&msg, 0, sizeof(msg));
msg.type = UTSM_MSG_DATA;
msg.data_len = (u32)copy_len;
if (copy_from_user(msg.data, buf, copy_len)) {
pr_err("[utsm] copy_from_user failed\n");
return -EFAULT;
}
/* Enqueue into linux_to_utsm ring (Linux produces, UTSM consumes) */
if (utsm_ipc_ring_push(&g_shm->linux_to_utsm, &msg) != 0) {
pr_warn("[utsm] linux_to_utsm ring full\n");
return -EAGAIN;
}
ret = (ssize_t)copy_len;
/* Also relay the data to the UTSM serial console via hypercall.
* This gives immediate visibility into Linux userspace output. */
if (copy_len > 0) {
char kbuf[UTSM_IPC_MSG_DATA_SIZE + 1];
memcpy(kbuf, msg.data, copy_len);
kbuf[copy_len] = '\0';
utsm_hcall_console_write(kbuf, copy_len);
}
return ret;
}
static ssize_t utsm_dev_read(struct file *f, char __user *buf,
size_t len, loff_t *off)
{
struct utsm_ipc_msg msg;
if (!g_shm_mapped)
return -ENODEV;
/* Dequeue from utsm_to_linux ring (UTSM produces, Linux consumes) */
if (utsm_ipc_ring_pop(&g_shm->utsm_to_linux, &msg) != 0) {
/* No message available */
return -EAGAIN; /* Non-blocking; userspace can poll */
}
if (msg.data_len == 0)
return 0;
if (len < msg.data_len)
return -EINVAL;
if (copy_to_user(buf, msg.data, msg.data_len))
return -EFAULT;
return (ssize_t)msg.data_len;
}
2026-07-28 14:35:14 +08:00
/* ===== Ioctl handlers for exec daemon ===== */
static long utsm_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
if (!g_shm_mapped)
return -ENODEV;
switch (cmd) {
case UTSM_IOCTL_RECV_MSG: {
/* Non-blocking dequeue from utsm_to_linux ring */
struct utsm_ioctl_msg umsg;
struct utsm_ipc_msg msg;
int ret;
if (copy_from_user(&umsg, (void __user *)arg, sizeof(umsg)))
return -EFAULT;
ret = utsm_ipc_ring_pop(&g_shm->utsm_to_linux, &msg);
if (ret != 0)
return -EAGAIN; /* ring empty */
umsg.type = msg.type;
umsg.data_len = msg.data_len;
if (umsg.buf_size < msg.data_len)
return -EINVAL;
/* umsg is already a kernel copy of the ioctl arg. Do not
* copy_to_user(umsg.data) — that treats a kernel address as
* a userspace pointer and returns -EFAULT, so the daemon
* parks without seeing EXEC_REQUEST (linux_compat exec -4). */
if (msg.data_len > 0)
memcpy(umsg.data, msg.data, msg.data_len);
2026-07-28 14:35:14 +08:00
if (copy_to_user((void __user *)arg, &umsg, sizeof(umsg)))
return -EFAULT;
return 0;
}
case UTSM_IOCTL_SEND_MSG: {
/* Enqueue to linux_to_utsm ring */
struct utsm_ioctl_msg umsg;
struct utsm_ipc_msg msg;
if (copy_from_user(&umsg, (void __user *)arg, sizeof(umsg)))
return -EFAULT;
memset(&msg, 0, sizeof(msg));
msg.type = umsg.type;
msg.data_len = min_t(u32, umsg.data_len, UTSM_IPC_MSG_DATA_SIZE);
if (msg.data_len > 0)
memcpy(msg.data, umsg.data, msg.data_len);
2026-07-28 14:35:14 +08:00
if (utsm_ipc_ring_push(&g_shm->linux_to_utsm, &msg) != 0) {
pr_warn("[utsm] linux_to_utsm ring full\n");
return -EAGAIN;
}
return 0;
}
case UTSM_IOCTL_PARK: {
/* VMCALL park. Idle HLT must stay in the guest (wait for IRQ).
* idle=poll + HLT-as-park panicked in cpu_idle_poll (current=NULL).
2026-07-28 14:35:14 +08:00
*/
mb();
if (utsm_hcall(UTSM_HCALL_PARK, 0, 0, 0) != 0)
return -EIO;
2026-07-28 14:35:14 +08:00
return 0;
}
case UTSM_IOCTL_GET_READY: {
int ready = (g_shm->header.utsm_ready && g_shm->header.linux_ready) ? 1 : 0;
if (copy_to_user((void __user *)arg, &ready, sizeof(ready)))
return -EFAULT;
return 0;
}
2026-07-31 13:53:49 +08:00
case UTSM_IOCTL_WRITE_POOL: {
/* Copy userspace data into payload_pool (daemon → UTSM bulk data) */
struct utsm_ioctl_pool p;
if (copy_from_user(&p, (void __user *)arg, sizeof(p)))
return -EFAULT;
if (p.offset > UTSM_IPC_PAYLOAD_SIZE ||
p.len > UTSM_IPC_PAYLOAD_SIZE - p.offset)
return -EINVAL;
if (p.len == 0)
return 0;
if (copy_from_user(g_shm->payload_pool + p.offset,
(void __user *)(uintptr_t)p.user_buf, p.len))
return -EFAULT;
mb(); /* ensure pool data visible to UTSM before response msg */
return 0;
}
case UTSM_IOCTL_READ_POOL: {
/* Copy payload_pool data to userspace (UTSM → daemon bulk data) */
struct utsm_ioctl_pool p;
if (copy_from_user(&p, (void __user *)arg, sizeof(p)))
return -EFAULT;
if (p.offset > UTSM_IPC_PAYLOAD_SIZE ||
p.len > UTSM_IPC_PAYLOAD_SIZE - p.offset)
return -EINVAL;
if (p.len == 0)
return 0;
if (copy_to_user((void __user *)(uintptr_t)p.user_buf,
g_shm->payload_pool + p.offset, p.len))
return -EFAULT;
return 0;
}
case UTSM_IOCTL_HOST_READ: {
/* Read a file from the UTSM FAT32 disk into userspace.
* Hypercall stages the chunk into payload_pool, we relay it out. */
struct utsm_ioctl_hostfile hf;
char kname[64];
long ret;
u32 n;
if (copy_from_user(&hf, (void __user *)arg, sizeof(hf)))
return -EFAULT;
if (hf.len == 0 || hf.len > UTSM_IPC_PAYLOAD_SIZE)
return -EINVAL;
if (!hf.user_buf)
return -EINVAL;
memcpy(kname, hf.name, sizeof(kname));
kname[sizeof(kname) - 1] = '\0';
ret = utsm_hcall_file_read(kname, hf.offset, hf.len);
if (ret < 0) {
if (ret == UTSM_HCALL_NOENT) return -ENOENT;
if (ret == UTSM_HCALL_INVAL) return -EINVAL;
if (ret == UTSM_HCALL_NOMEM) return -ENOMEM;
return -EIO;
}
n = (u32)(ret & 0xFFFFF);
hf.total = (u64)ret >> 20;
if (n > hf.len)
n = hf.len; /* defensive: never overflow the user buffer */
if (n > 0 &&
copy_to_user((void __user *)(uintptr_t)hf.user_buf,
g_shm->payload_pool, n))
return -EFAULT;
hf.len = n;
if (copy_to_user((void __user *)arg, &hf, sizeof(hf)))
return -EFAULT;
return 0;
}
case UTSM_IOCTL_HOST_WRITE: {
/* Write userspace data as the whole content of a UTSM FAT32 file.
* Stage into payload_pool first, then hypercall to commit. */
struct utsm_ioctl_hostfile hf;
char kname[64];
long ret;
if (copy_from_user(&hf, (void __user *)arg, sizeof(hf)))
return -EFAULT;
if (hf.len > UTSM_IPC_PAYLOAD_SIZE)
return -EINVAL;
if (hf.len > 0 && !hf.user_buf)
return -EINVAL;
memcpy(kname, hf.name, sizeof(kname));
kname[sizeof(kname) - 1] = '\0';
if (hf.len > 0 &&
copy_from_user(g_shm->payload_pool,
(void __user *)(uintptr_t)hf.user_buf, hf.len))
return -EFAULT;
mb(); /* ensure pool data visible to UTSM before the hypercall */
ret = utsm_hcall_file_write(kname, hf.len);
if (ret < 0) {
if (ret == UTSM_HCALL_INVAL) return -EINVAL;
if (ret == UTSM_HCALL_NOMEM) return -ENOMEM;
return -EIO;
}
return 0;
}
2026-08-09 22:14:01 +08:00
case UTSM_IOCTL_SURFACE_INFO: {
/* VSCode integration Phase 1: return surface pool GPA + size.
* Userspace then mmaps /dev/utsm (offset 0, len = size) to obtain
* a writable scanout buffer. */
struct utsm_ioctl_surface su;
if (!g_surface_present)
return -ENODEV;
su.gpa = g_surface_gpa;
su.size = g_surface_size;
if (copy_to_user((void __user *)arg, &su, sizeof(su)))
return -EFAULT;
return 0;
}
2026-07-28 14:35:14 +08:00
default:
return -ENOTTY;
}
}
static __poll_t utsm_dev_poll(struct file *f, struct poll_table_struct *wait)
{
__poll_t mask = 0;
if (!g_shm_mapped)
return EPOLLERR;
/* Report readable if utsm_to_linux ring has messages */
if (!utsm_ipc_ring_empty(&g_shm->utsm_to_linux))
mask |= EPOLLIN | EPOLLRDNORM;
/* Always writable (ring push may still fail with -EAGAIN) */
2026-08-09 22:14:01 +08:00
mask |= EPOLLOUT | EPOLLRDNORM;
2026-07-28 14:35:14 +08:00
return mask;
}
2026-08-09 22:14:01 +08:00
/* VSCode integration Phase 1: mmap the graphics surface pool into userspace.
*
* Userspace (e.g. the X server or a blit helper) calls:
* void *p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
* to obtain a writable scanout buffer whose contents UTSM reads and blits to
* the Deshab framebuffer. The surface pool is a separate DMA region EPT-mapped
* into the guest at LINUX_GUEST_SURFACE_GPA and marked E820_TYPE_RESERVED, so
* it has no struct page and we map it via remap_pfn_range.
*
* We allow mapping any sub-range of the pool (offset+size validated against
* g_surface_size). Cacheable access is used because the surface pool is
* regular RAM shared with the UTSM host over a coherent CPU cache domain;
* switch to pgprot_writecombine() if DMA coherency issues ever appear. */
static int utsm_dev_mmap(struct file *f, struct vm_area_struct *vma)
{
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
unsigned long size = vma->vm_end - vma->vm_start;
if (!g_surface_present)
return -ENODEV;
/* The pool is a single contiguous region; allow any sub-range within it. */
if (off >= g_surface_size || size > g_surface_size - off)
return -EINVAL;
if (remap_pfn_range(vma, vma->vm_start,
(g_surface_gpa + off) >> PAGE_SHIFT,
size, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
2026-07-28 12:00:02 +08:00
static const struct file_operations utsm_fops = {
2026-07-28 14:35:14 +08:00
.owner = THIS_MODULE,
.write = utsm_dev_write,
.read = utsm_dev_read,
.unlocked_ioctl = utsm_dev_ioctl,
.poll = utsm_dev_poll,
2026-08-09 22:14:01 +08:00
.mmap = utsm_dev_mmap,
2026-07-28 12:00:02 +08:00
};
static struct miscdevice utsm_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = UTSM_DEV_NAME,
.fops = &utsm_fops,
.mode = 0666,
};
/* ===== Early console redirect (optional) =====
*
* Redirect kernel printk output to UTSM serial console via hypercall.
* This is useful for debugging when the guest's own serial is not set up.
* Currently disabled; can be enabled via CONFIG_UTSM_EARLY_CONSOLE.
*/
#ifdef CONFIG_UTSM_EARLY_CONSOLE
static void utsm_console_write(struct console *con, const char *s, unsigned int n)
{
if (g_utsm_present) {
char buf[256];
unsigned int chunk;
while (n > 0) {
chunk = min_t(unsigned int, n, sizeof(buf) - 1);
memcpy(buf, s, chunk);
buf[chunk] = '\0';
utsm_hcall_console_write(buf, chunk);
s += chunk;
n -= chunk;
}
}
}
static struct console utsm_console = {
.name = "utsmcon",
.write = utsm_console_write,
.flags = CON_PRINTBUFFER | CON_ENABLED,
.index = -1,
};
#endif
/* ===== Initialization ===== */
/* Forward declaration: used by init before /dev/utsm is registered */
static inline int ipc_shm_send_to_utsm(u32 type, const void *data, u32 len);
static int __init utsm_hcall_init(void)
{
long ping_ret;
u64 shm_gpa = 0, shm_size = 0;
int ret;
pr_info("[utsm] probing for UTSM monitor...\n");
/* Nested KVM leaks host U_CET (IBT+SHSTK) into L2. L1 cannot
* WRMSR CET (CR4.CET=0). Clear it from the guest while the
* WRMSR is not intercepted. */
{
u64 ucet = 0;
int rd = rdmsrl_safe(MSR_IA32_U_CET, &ucet);
int wr = wrmsrl_safe(MSR_IA32_U_CET, 0);
pr_info("[utsm] CET U_CET rd=%d val=0x%llx wr0=%d\n",
rd, ucet, wr);
}
2026-07-28 12:00:02 +08:00
/* 1. Ping UTSM to verify it's present */
ping_ret = utsm_hcall_ping();
if (ping_ret != 0x504F4E47) { /* "PONG" */
pr_info("[utsm] UTSM not detected (ping returned 0x%lx)\n", ping_ret);
pr_info("[utsm] Driver loaded in passive mode (no /dev/utsm)\n");
return 0; /* Not an error: we might be running on bare metal */
}
g_utsm_present = 1;
pr_info("[utsm] UTSM monitor detected (PONG=0x%lx)\n", ping_ret);
/* 2. Say hello */
utsm_hcall_hello();
/* 3. Query shared memory info */
ret = utsm_hcall_shm_info(&shm_gpa, &shm_size);
if (ret != 0) {
2026-07-29 10:26:25 +08:00
pr_err("[utsm] SHM_INFO hypercall failed: %d\n", ret);
2026-07-28 12:00:02 +08:00
goto err_no_shm;
}
pr_info("[utsm] shared memory: gpa=0x%llx size=%llu KB\n",
shm_gpa, shm_size / 1024);
if (shm_gpa == 0 || shm_size == 0) {
pr_err("[utsm] invalid SHM info\n");
goto err_no_shm;
}
/* 4. Map the shared memory region.
* The GPA is a physical address from Linux's perspective.
* Use ioremap_cache for cacheable memory (the SHM region is
* configured as WB in EPT). */
g_shm = (struct utsm_ipc_shm *)ioremap_cache(shm_gpa, shm_size);
if (!g_shm) {
pr_err("[utsm] failed to ioremap SHM at 0x%llx\n", shm_gpa);
goto err_ioremap;
}
g_shm_gpa = shm_gpa;
g_shm_size = shm_size;
g_shm_mapped = 1;
/* 5. Verify SHM header */
if (g_shm->header.magic != UTSM_IPC_MAGIC) {
pr_err("[utsm] SHM magic mismatch: 0x%llx\n", g_shm->header.magic);
goto err_bad_magic;
}
pr_info("[utsm] SHM header: magic=OK version=%u utsm_ready=%u\n",
g_shm->header.version, g_shm->header.utsm_ready);
/* 6. Set linux_ready flag */
g_shm->header.linux_ready = 1;
mb(); /* Ensure visible to UTSM */
/* 7. Register /dev/utsm */
ret = misc_register(&utsm_miscdev);
if (ret) {
pr_err("[utsm] misc_register failed: %d\n", ret);
goto err_misc;
}
#ifdef CONFIG_UTSM_EARLY_CONSOLE
register_console(&utsm_console);
#endif
pr_info("[utsm] /dev/%s registered (IPC ready)\n", UTSM_DEV_NAME);
2026-08-09 22:14:01 +08:00
/* 8. VSCode integration Phase 1: query graphics surface pool.
* Non-fatal: if the surface pool is unavailable (older UTSM or alloc
* failure), /dev/utsm still works for IPC — only mmap/SURFACE_INFO
* ioctls return -ENODEV. On success, userspace can mmap(/dev/utsm)
* to obtain a writable scanout buffer shared with the UTSM host. */
{
u64 surf_gpa = 0, surf_size = 0;
long sret = utsm_hcall_surface_info(&surf_gpa, &surf_size);
if (sret == 0 && surf_gpa != 0 && surf_size != 0) {
g_surface_gpa = surf_gpa;
g_surface_size = surf_size;
g_surface_present = 1;
pr_info("[utsm] surface pool: gpa=0x%llx size=%llu KB\n",
surf_gpa, surf_size / 1024);
} else {
pr_info("[utsm] surface pool not available (ret=%ld)\n",
sret);
}
}
/* 9. Send a HELLO message via the ring buffer */
2026-07-28 12:00:02 +08:00
{
const char *hello_msg = "Linux booted OK";
ipc_shm_send_to_utsm(UTSM_MSG_DATA, hello_msg, 15);
}
return 0;
err_misc:
g_shm->header.linux_ready = 0;
err_bad_magic:
iounmap(g_shm);
g_shm = NULL;
g_shm_mapped = 0;
err_ioremap:
err_no_shm:
g_utsm_present = 0;
return -ENODEV;
}
/* Helper to send a message (used by init before /dev/utsm is available) */
static inline int ipc_shm_send_to_utsm(u32 type, const void *data, u32 len)
{
struct utsm_ipc_msg msg;
if (!g_shm_mapped || !g_shm)
return -1;
if (len > UTSM_IPC_MSG_DATA_SIZE)
len = UTSM_IPC_MSG_DATA_SIZE;
memset(&msg, 0, sizeof(msg));
msg.type = type;
msg.data_len = len;
if (data && len > 0)
memcpy(msg.data, data, len);
return utsm_ipc_ring_push(&g_shm->linux_to_utsm, &msg);
}
static void __exit utsm_hcall_exit(void)
{
if (g_shm_mapped) {
/* Clear linux_ready flag */
g_shm->header.linux_ready = 0;
mb();
/* Send shutdown notification */
ipc_shm_send_to_utsm(UTSM_MSG_SHUTDOWN, NULL, 0);
misc_deregister(&utsm_miscdev);
#ifdef CONFIG_UTSM_EARLY_CONSOLE
unregister_console(&utsm_console);
#endif
iounmap(g_shm);
g_shm = NULL;
g_shm_mapped = 0;
}
2026-08-09 22:14:01 +08:00
/* VSCode integration Phase 1: clear surface pool state.
* No kernel resource to free — the pool is owned by UTSM and only ever
* exposed via remap_pfn_range. Clearing the flag makes post-unload
* ioctls deterministically return -ENODEV. */
g_surface_present = 0;
g_surface_gpa = 0;
g_surface_size = 0;
2026-07-28 12:00:02 +08:00
pr_info("[utsm] driver unloaded\n");
}
module_init(utsm_hcall_init);
module_exit(utsm_hcall_exit);
MODULE_AUTHOR("Deshab Project");
MODULE_DESCRIPTION("UTSM dual-kernel hypercall and IPC driver");
MODULE_LICENSE("GPL");