Files
Deshab-OS/CODE/utsm-ipc/ipc_proto.h
T

440 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef UTSM_IPC_PROTO_H
#define UTSM_IPC_PROTO_H
/*
* UTSM ↔ Linux IPC Protocol — shared between both kernels.
*
* This header is included by:
* - UTSM kernel (CODE/UTSM/vmm/ipc_shm.c, hypercall.c)
* - Linux kernel module (CODE/linux/patches/utsm-ipc.patch → drivers/utsm/)
*
* Communication channels:
* 1. Shared memory ring buffer (async messages, bidirectional)
* 2. VMCALL hypercall (sync request/response)
* 3. IPI notification (UTSM → Linux via guest IRQ injection)
*
* Shared memory layout (at GPA 0x04000000, 1MB total, packed struct):
* 0x04000000: utsm_ipc_shm_header (64 bytes)
* 0x04000040: utsm_ipc_ring utsm_to_linux (UTSM writes, Linux reads, 16400 B)
* 0x04004050: utsm_ipc_ring linux_to_utsm (Linux writes, UTSM reads, 16400 B)
* 0x04008060: payload pool (UTSM_IPC_PAYLOAD_SIZE bytes, ends exactly at +1MB)
*/
/* Kernel-space uses kernel types, user-space uses <stdint.h>.
* UTSM kernel defines __UTSM_KERNEL__, Linux kernel auto-defines __KERNEL__. */
#if defined(__KERNEL__)
#include <linux/types.h>
/* Linux kernel uses __u32/__u64/__u8; map to stdint names for shared structs */
typedef __u8 uint8_t;
typedef __u32 uint32_t;
typedef __u64 uint64_t;
#elif defined(__UTSM_KERNEL__)
#include <utsm/types.h>
#else
#include <stdint.h>
#endif
/* ===== Constants ===== */
#define UTSM_IPC_MAGIC 0x5554534D48430000ULL /* "UTSMHC\00\00" */
#define UTSM_IPC_VERSION 1
#define UTSM_IPC_SHM_GPA 0x04000000ULL
#define UTSM_IPC_SHM_SIZE (1024 * 1024) /* 1MB */
#define UTSM_IPC_QUEUE_DEPTH 64 /* messages per ring */
#define UTSM_IPC_MSG_DATA_SIZE 240 /* inline data per message */
/* Derived region sizes (packed layout, both kernels must agree):
* message = 16-byte header + inline data
* ring = 16-byte header + QUEUE_DEPTH messages
* SHM = 64-byte header + 2 rings + payload pool = exactly 1MB */
#define UTSM_IPC_MSG_SIZE (16 + UTSM_IPC_MSG_DATA_SIZE) /* 256 */
#define UTSM_IPC_RING_SIZE (16 + UTSM_IPC_QUEUE_DEPTH * UTSM_IPC_MSG_SIZE) /* 16400 */
#define UTSM_IPC_PAYLOAD_SIZE (UTSM_IPC_SHM_SIZE - 64 - 2 * UTSM_IPC_RING_SIZE) /* 1015712 */
/* ===== Hypercall operations ===== */
enum utsm_hcall_op {
UTSM_HCALL_PING = 0x0001, /* Simple ping (returns PONG) */
UTSM_HCALL_HELLO = 0x0002, /* Hello handshake */
UTSM_HCALL_PARK = 0x0003, /* Park vCPU back to DSK (not HLT) */
UTSM_HCALL_SHM_INFO = 0x0010, /* Query shared memory GPA + size */
UTSM_HCALL_CAP_VALIDATE = 0x0020, /* Capability validation */
UTSM_HCALL_UTRW_READ = 0x0030, /* Sealed memory read (UTRW) */
UTSM_HCALL_UTRW_WRITE = 0x0031, /* Sealed memory write (UTRW) */
UTSM_HCALL_DRR_CHECKPOINT= 0x0040, /* Trigger DRR checkpoint */
UTSM_HCALL_CONSOLE_WRITE = 0x0050, /* Write to UTSM serial console */
UTSM_HCALL_CONSOLE_READ = 0x0051, /* Read from UTSM serial console */
/* Phase 3 file transfer: Linux guest ↔ UTSM FAT32 disk (payload_pool as
* the bulk data buffer; see "Linux ↔ UTSM FAT32 hypercalls" below) */
UTSM_HCALL_FILE_READ = 0x0060, /* Read UTSM FAT32 root file into payload_pool */
UTSM_HCALL_FILE_WRITE = 0x0061, /* Write payload_pool content to UTSM FAT32 root file */
/* VSCode integration Phase 1: query graphics surface pool (GPA + size).
* The surface pool is a separate DMA region EPT-mapped into the Linux
* guest at LINUX_GUEST_SURFACE_GPA. Linux mmaps it via /dev/utsm and
* exposes it to X server as the scanout framebuffer (Phase 3 virtio-gpu
* shadow buffer lives here; guest writes → host reads for blit). */
UTSM_HCALL_SURFACE_INFO = 0x0070, /* Query graphics surface pool GPA + size */
};
/* Hypercall return codes */
#define UTSM_HCALL_OK 0
#define UTSM_HCALL_INVAL (-1)
#define UTSM_HCALL_PERM (-2)
#define UTSM_HCALL_NOMEM (-3)
#define UTSM_HCALL_NOSYS (-4)
#define UTSM_HCALL_NOENT (-5) /* file not found */
#define UTSM_HCALL_IO (-6) /* disk / generic I/O error */
/* ===== Message types ===== */
enum utsm_ipc_msg_type {
UTSM_MSG_NONE = 0,
UTSM_MSG_HELLO = 1, /* Initial handshake */
UTSM_MSG_HELLO_ACK= 2, /* Handshake acknowledgment */
UTSM_MSG_PING = 3, /* Ping request */
UTSM_MSG_PONG = 4, /* Pong response */
UTSM_MSG_DATA = 5, /* Generic data message */
UTSM_MSG_ACK = 6, /* Acknowledgment */
UTSM_MSG_NACK = 7, /* Negative acknowledgment */
UTSM_MSG_SHUTDOWN = 8, /* Shutdown notification */
/* Linux compat exec protocol (UTSM → Linux → UTSM) */
UTSM_MSG_EXEC_REQUEST = 16, /* UTSM→Linux: exec a program (payload=ipc_exec_request) */
UTSM_MSG_EXEC_STDOUT = 17, /* Linux→UTSM: stdout chunk (payload=raw bytes) */
UTSM_MSG_EXEC_STDERR = 18, /* Linux→UTSM: stderr chunk (payload=raw bytes) */
UTSM_MSG_EXEC_EXIT = 19, /* Linux→UTSM: process exited (payload=ipc_exec_exit) */
UTSM_MSG_EXEC_READY = 20, /* Linux→UTSM: daemon ready to accept exec requests */
/* Linux compat file transfer protocol (payload via payload_pool) */
UTSM_MSG_FILE_LIST_REQUEST = 24, /* UTSM→Linux: list directory (payload=ipc_file_request) */
UTSM_MSG_FILE_READ_REQUEST = 25, /* UTSM→Linux: read file chunk (payload=ipc_file_request) */
UTSM_MSG_FILE_RESPONSE = 26, /* Linux→UTSM: result header (payload=ipc_file_response),
* bulk data already in payload_pool */
UTSM_MSG_FILE_WRITE_REQUEST= 27, /* UTSM→Linux: write file chunk (payload=ipc_file_request),
* bulk data pre-staged in payload_pool by UTSM;
* result via UTSM_MSG_FILE_RESPONSE */
};
/* ===== Linux compat exec payload structures =====
*
* Flow:
* 1. UTSM writes UTSM_MSG_EXEC_REQUEST into utsm_to_linux ring,
* then calls linux_resume() to wake the parked Linux guest.
* 2. Linux daemon reads the request, fork+exec's the program,
* streams stdout/stderr back via UTSM_MSG_EXEC_STDOUT/STDERR.
* 3. Linux daemon writes UTSM_MSG_EXEC_EXIT with exit code, then HLTs (park).
* 4. UTSM reads the responses from linux_to_utsm ring, returns to caller.
*
* All strings are NUL-terminated. argv_blob contains argv[0..argc-1] each
* NUL-terminated, concatenated. path is the program path to execute. */
#define UTSM_EXEC_PATH_MAX 128
#define UTSM_EXEC_ARGV_MAX 96 /* total argv blob size */
#define UTSM_EXEC_MAX_ARGS 16
/* VSCode Phase 5: exec flags (ipc_exec_request.flags) */
#define UTSM_EXEC_FLAG_ASYNC 0x00000001u /* daemon fork+setsid+detach, replies
* EXEC_EXIT(0) immediately after
* spawn; no stdout capture, no wait.
* Used to launch X server / VSCode
* without blocking the UTSM side. */
struct ipc_exec_request {
uint32_t argc; /* number of arguments */
uint32_t flags; /* UTSM_EXEC_FLAG_* (0 = sync) */
char path[UTSM_EXEC_PATH_MAX]; /* program path (NUL-terminated) */
char argv_blob[UTSM_EXEC_ARGV_MAX]; /* argv strings, each NUL-terminated */
} __attribute__((packed));
struct ipc_exec_exit {
uint32_t exit_code; /* process exit status */
uint32_t flags; /* UTSM_EXEC_EXIT_F_* (0 = 旧式 8B) */
uint32_t stdout_len; /* payload_pool 内有效字节数 */
uint32_t stdout_total; /* 截断前总输出字节数(诊断) */
} __attribute__((packed));
/* ipc_exec_exit.flags — 大输出/超时扩展(P3 补完)。
* 旧 daemon 只发 8B(exit_code+reserved=0),UTSM 按 data_len>=16 且
* flags&UTSM_EXEC_EXIT_F_POOL 判定新协议;旧式输出仍走 EXEC_STDOUT 内联。 */
#define UTSM_EXEC_EXIT_F_POOL 0x00000001u /* stdout 经 payload_pool 传输(offset 0) */
#define UTSM_EXEC_EXIT_F_TRUNC 0x00000002u /* 输出超 pool 容量被截断 */
#define UTSM_EXEC_EXIT_F_TIMEOUT 0x00000004u /* 子进程超时被 SIGKILL(exit_code=124) */
/* 同步 exec daemon 侧子进程超时(秒):park-and-resume 模型下 UTSM 在
* linux_resume() 期间不运行,无法宿主侧看门狗,必须 daemon alarm 兜底,
* 否则 guest 内长命令(sleep infinity 等)会永久冻结整系统。 */
#define UTSM_EXEC_TIMEOUT_SEC 15u
/* ===== Linux compat file transfer payload structures =====
*
* Flow (synchronous with park-and-resume, same as exec):
* 1. UTSM writes UTSM_MSG_FILE_LIST_REQUEST / FILE_READ_REQUEST into the
* utsm_to_linux ring, then linux_resume() wakes the daemon.
* 2. Linux daemon performs opendir/readdir or open/pread, writes the bulk
* result data DIRECTLY into payload_pool at req.pool_offset (via
* UTSM_IOCTL_WRITE_POOL), then enqueues UTSM_MSG_FILE_RESPONSE carrying
* ipc_file_response (status + data_len + total_size), then HLTs.
* 3. UTSM drains the ring, reads the response header, then copies
* resp.data_len bytes out of payload_pool + req.pool_offset.
*
* Write flow (UTSM → Linux, UTSM_MSG_FILE_WRITE_REQUEST):
* 1. UTSM stages the data into payload_pool at req.pool_offset (offset 0,
* exclusive use — the model is synchronous), sets req.pool_capacity to
* the staged byte count and req.file_offset to the write position.
* 2. Linux daemon reads req.pool_capacity bytes out of payload_pool (via
* UTSM_IOCTL_READ_POOL) and pwrite()s them to req.path at
* req.file_offset. Convention: file_offset == 0 opens with O_TRUNC
* (a push always replaces the file); offset > 0 writes in place.
* The file is created with mode 0644 if missing.
* 3. Daemon replies UTSM_MSG_FILE_RESPONSE with data_len = bytes written
* and total_size = resulting file size.
*
* payload_pool is ~1MB (UTSM_IPC_PAYLOAD_SIZE), so a single round trip can
* move up to that much data. For larger files the caller loops with
* increasing file_offset. Directory listings are formatted as plain text,
* one entry per line ("name" or "name/" for directories).
*
* All strings are NUL-terminated. */
#define UTSM_FILE_PATH_MAX 200
/* Status codes for ipc_file_response.status */
#define UTSM_FILE_OK 0
#define UTSM_FILE_ERR_NOENT 1 /* path does not exist */
#define UTSM_FILE_ERR_IO 2 /* generic I/O error */
#define UTSM_FILE_ERR_NOTDIR 3 /* LIST on a non-directory */
#define UTSM_FILE_ERR_ISDIR 4 /* READ/WRITE on a directory */
#define UTSM_FILE_ERR_PERM 5 /* permission denied */
#define UTSM_FILE_ERR_INVAL 6 /* malformed request */
#define UTSM_FILE_ERR_NOSPC 7 /* no space left on device / file too large */
struct ipc_file_request {
uint32_t pool_offset; /* payload_pool offset of bulk data */
uint32_t pool_capacity; /* LIST/READ: max bytes daemon may write;
* WRITE: staged data byte count */
uint64_t file_offset; /* READ: read offset; WRITE: write offset
* (0 also implies O_TRUNC); LIST: 0 */
char path[UTSM_FILE_PATH_MAX]; /* absolute Linux path (NUL-terminated) */
} __attribute__((packed));
struct ipc_file_response {
uint32_t status; /* UTSM_FILE_* */
uint32_t data_len; /* LIST/READ: bytes written into payload_pool;
* WRITE: bytes written to the file */
uint64_t total_size; /* READ: total file size;
* WRITE: resulting file size; LIST: 0 */
} __attribute__((packed));
/* ===== Linux ↔ UTSM FAT32 hypercalls (UTSM_HCALL_FILE_READ/WRITE) =====
*
* These give the Linux guest direct access to files on the UTSM-side FAT32
* system disk (root directory, 8.3-convertible names, whole-file semantics,
* max 256KB per file). payload_pool at offset 0 is the bulk data buffer.
*
* UTSM_HCALL_FILE_READ: a0 = GPA of NUL-terminated file name (max 63 chars)
* a1 = file offset (u64)
* a2 = max bytes to stage (clamped to pool capacity)
* UTSM reads the file via its block/FAT32 path, stages
* payload_pool[0..n) = file[a1 .. a1+n), and returns in RAX:
* low 20 bits = n (bytes staged; pool capacity < 1MB so 20 bits fit)
* high bits = total file size (RAX >> 20)
* Negative RAX = UTSM_HCALL_* error (INVAL bad name, NOENT missing,
* NOMEM pool unavailable, IO disk error). n == 0 with a1 >= size means EOF.
*
* UTSM_HCALL_FILE_WRITE: a0 = GPA of NUL-terminated file name (max 63 chars)
* a1 = reserved (0)
* a2 = byte count staged at payload_pool[0]
* Whole-file replace: the staged bytes become the new file content
* (create or replace). Returns bytes written (== a2) or a negative
* UTSM_HCALL_* error.
*
* Concurrency note: payload_pool is a single-writer channel; the guest must
* not run these hypercalls concurrently with a ring-based file transfer. */
/* ===== Ring buffer structures ===== */
/* Single IPC message (256 bytes, cache-line aligned) */
struct utsm_ipc_msg {
uint32_t type; /* enum utsm_ipc_msg_type */
uint32_t seq; /* Sequence number (monotonic) */
uint32_t data_len; /* Actual bytes in data[] */
uint32_t flags; /* Reserved for future use */
uint8_t data[UTSM_IPC_MSG_DATA_SIZE]; /* Inline payload */
} __attribute__((packed));
/* Ring buffer: single-producer, single-consumer queue.
*
* Producer writes at tail, then increments tail (with memory barrier).
* Consumer reads at head, then increments head (with memory barrier).
*
* Empty when head == tail.
* Full when (tail + 1) % capacity == head. */
struct utsm_ipc_ring {
volatile uint32_t head; /* Consumer updates (Linux or UTSM) */
volatile uint32_t tail; /* Producer updates (UTSM or Linux) */
uint32_t capacity; /* Max messages (UTSM_IPC_QUEUE_DEPTH) */
uint32_t reserved;
struct utsm_ipc_msg messages[UTSM_IPC_QUEUE_DEPTH];
} __attribute__((packed));
/* Shared memory header */
struct utsm_ipc_shm_header {
uint64_t magic; /* UTSM_IPC_MAGIC */
uint32_t version; /* UTSM_IPC_VERSION */
uint32_t header_size; /* sizeof(struct utsm_ipc_shm_header) */
uint64_t shm_phys; /* Physical address of SHM region */
uint64_t shm_size; /* Total SHM size */
volatile uint32_t utsm_ready; /* UTSM sets to 1 when initialized */
volatile uint32_t linux_ready; /* Linux sets to 1 when initialized */
uint8_t reserved[24];
} __attribute__((packed));
/* Full shared memory region */
struct utsm_ipc_shm {
struct utsm_ipc_shm_header header;
struct utsm_ipc_ring utsm_to_linux; /* UTSM writes, Linux reads */
struct utsm_ipc_ring linux_to_utsm; /* Linux writes, UTSM reads */
uint8_t payload_pool[UTSM_IPC_PAYLOAD_SIZE];
} __attribute__((packed));
/* Compile-time guard: the packed SHM layout must fit in the 1MB region
* (UTSM allocates exactly UTSM_IPC_SHM_SIZE bytes and EPT-maps only that). */
_Static_assert(sizeof(struct utsm_ipc_shm) <= UTSM_IPC_SHM_SIZE,
"utsm_ipc_shm exceeds the 1MB shared memory region");
/* ===== Ring buffer operations (inline, usable by both kernels) ===== */
/* Memory barriers — adapted for each kernel's environment.
* UTSM: use compiler barriers (__asm__ volatile("" ::: "memory"))
* Linux: use smp_store_release / smp_load_acquire
*
* For simplicity, we use full memory barriers here. */
static inline void utsm_ipc_mb(void) {
__asm__ volatile("mfence" ::: "memory");
}
/* Initialize a ring buffer */
static inline void utsm_ipc_ring_init(struct utsm_ipc_ring *ring) {
ring->head = 0;
ring->tail = 0;
ring->capacity = UTSM_IPC_QUEUE_DEPTH;
ring->reserved = 0;
utsm_ipc_mb();
}
/* Try to enqueue a message. Returns 0 on success, -1 if full. */
static inline int utsm_ipc_ring_push(struct utsm_ipc_ring *ring,
const struct utsm_ipc_msg *msg) {
uint32_t tail = ring->tail;
uint32_t next_tail = (tail + 1) % ring->capacity;
if (next_tail == ring->head) {
return -1; /* Full */
}
ring->messages[tail] = *msg;
utsm_ipc_mb(); /* Ensure message is visible before updating tail */
ring->tail = next_tail;
return 0;
}
/* Try to dequeue a message. Returns 0 on success, -1 if empty. */
static inline int utsm_ipc_ring_pop(struct utsm_ipc_ring *ring,
struct utsm_ipc_msg *msg) {
uint32_t head = ring->head;
if (head == ring->tail) {
return -1; /* Empty */
}
*msg = ring->messages[head];
utsm_ipc_mb(); /* Ensure we've read the message before updating head */
ring->head = (head + 1) % ring->capacity;
return 0;
}
/* Check if ring is empty */
static inline int utsm_ipc_ring_empty(const struct utsm_ipc_ring *ring) {
return ring->head == ring->tail;
}
/* Check if ring is full */
static inline int utsm_ipc_ring_full(const struct utsm_ipc_ring *ring) {
uint32_t next_tail = (ring->tail + 1) % ring->capacity;
return next_tail == ring->head;
}
/* ===== Hypercall ABI (Linux → UTSM) =====
*
* Linux calls VMCALL with:
* RAX = UTSM_IPC_MAGIC | op
* RDI = arg0
* RSI = arg1
* RDX = arg2
*
* UTSM handles the VMCALL VM-Exit and returns:
* RAX = return value (UTSM_HCALL_OK or error)
*
* The magic in RAX distinguishes UTSM hypercalls from other VMCALL users.
*
* Define UTSM_NO_INLINE_HCALL before including this header if you provide
* your own hypercall wrapper implementations (e.g. Linux utsm_hcall.c).
*/
#define UTSM_HCALL_MAGIC 0x5554534D48430000ULL /* "UTSMHC\00\00" */
#ifndef UTSM_NO_INLINE_HCALL
static inline long utsm_hcall(uint64_t op, uint64_t a0, uint64_t a1, uint64_t a2) {
long ret;
__asm__ volatile("vmcall"
: "=a"(ret)
: "a"(UTSM_HCALL_MAGIC | op), "D"(a0), "S"(a1), "d"(a2)
: "memory");
return ret;
}
/* Convenience hypercall wrappers */
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(uint64_t *gpa_out, uint64_t *size_out) {
uint64_t gpa = 0, size = 0;
long ret = utsm_hcall(UTSM_HCALL_SHM_INFO, (uint64_t)&gpa, (uint64_t)&size, 0);
if (ret == 0) {
if (gpa_out) *gpa_out = gpa;
if (size_out) *size_out = size;
}
return ret;
}
/* VSCode integration Phase 1: query graphics surface pool.
* Returns the surface pool GPA (visible inside the Linux guest) and size.
* Linux user-space mmaps /dev/utsm at the returned GPA to obtain a writable
* pointer that the host can scan for blitting to the Deshab framebuffer. */
static inline long utsm_hcall_surface_info(uint64_t *gpa_out, uint64_t *size_out) {
uint64_t gpa = 0, size = 0;
long ret = utsm_hcall(UTSM_HCALL_SURFACE_INFO, (uint64_t)&gpa, (uint64_t)&size, 0);
if (ret == 0) {
if (gpa_out) *gpa_out = gpa;
if (size_out) *size_out = size;
}
return ret;
}
static inline long utsm_hcall_console_write(const char *buf, uint64_t len) {
return utsm_hcall(UTSM_HCALL_CONSOLE_WRITE, (uint64_t)buf, len, 0);
}
#endif /* UTSM_NO_INLINE_HCALL */
#endif /* UTSM_IPC_PROTO_H */