This commit is contained in:
xyou
2026-07-28 14:35:14 +08:00
parent 17a577f2ed
commit 537890df9e
44 changed files with 4903 additions and 404 deletions
+11
View File
@@ -109,6 +109,17 @@ if [ -d "$INITRAMFS_DIR" ]; then
# Copy initramfs contents
cp -a "$INITRAMFS_DIR"/* "$INITRAMFS_TMP"/
# Build utsm_exec_daemon (static binary for initramfs)
DAEMON_SRC="$INITRAMFS_DIR/bin/utsm_exec_daemon.c"
if [ -f "$DAEMON_SRC" ]; then
echo "[build] Compiling utsm_exec_daemon (static)..."
mkdir -p "$INITRAMFS_TMP/bin"
cc -static -O2 -Wall -I"$PROJECT_ROOT/CODE/utsm-ipc" \
-o "$INITRAMFS_TMP/bin/utsm_exec_daemon" "$DAEMON_SRC"
strip "$INITRAMFS_TMP/bin/utsm_exec_daemon" 2>/dev/null || true
echo "[build] daemon: $(du -h "$INITRAMFS_TMP/bin/utsm_exec_daemon" | cut -f1)"
fi
# Create init script if not present
if [ ! -f "$INITRAMFS_TMP/init" ]; then
cat > "$INITRAMFS_TMP/init" <<'INITEOF'
+218
View File
@@ -0,0 +1,218 @@
// SPDX-License-Identifier: GPL-2.0
/*
* utsm_exec_daemon — Linux guest side exec daemon for Deshab Linux compat layer.
*
* Runs as the first userspace process (PID 2, after init) in the Linux guest.
* Communicates with UTSM via /dev/utsm ioctl interface:
*
* 1. Send EXEC_READY, then ioctl(PARK) → HLT → guest parks
* (this is the first HLT; linux_launch() returns to UTSM)
* 2. On vmresume: ioctl(RECV_MSG) → EXEC_REQUEST
* 3. fork + exec the requested program, capture stdout/stderr via pipe
* 4. Stream output back via ioctl(SEND_MSG, EXEC_STDOUT/STDERR)
* 5. Send EXEC_EXIT with waitpid exit code
* 6. Loop: ioctl(PARK) → wait for next request
*
* Build: static musl/glibc binary, stripped, copied into initramfs /bin/.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/types.h>
/* Shared IPC protocol — must match UTSM side */
#include "../../../utsm-ipc/ipc_proto.h"
/* Ioctl definitions — must match utsm_hcall.c driver */
#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)
struct utsm_ioctl_msg {
unsigned int type;
unsigned int data_len;
unsigned int reserved;
unsigned int buf_size;
unsigned char data[UTSM_IPC_MSG_DATA_SIZE];
};
static int g_utsm_fd = -1;
/* Send a typed message to UTSM via the linux_to_utsm ring */
static int send_msg(unsigned int type, const void *data, unsigned int len)
{
struct utsm_ioctl_msg umsg;
memset(&umsg, 0, sizeof(umsg));
umsg.type = type;
umsg.data_len = len;
umsg.buf_size = UTSM_IPC_MSG_DATA_SIZE;
if (data && len > 0) {
if (len > UTSM_IPC_MSG_DATA_SIZE)
len = UTSM_IPC_MSG_DATA_SIZE;
memcpy(umsg.data, data, len);
umsg.data_len = len;
}
return ioctl(g_utsm_fd, UTSM_IOCTL_SEND_MSG, &umsg);
}
/* Receive a message from UTSM (non-blocking). Returns 0 on success, -EAGAIN if empty. */
static int recv_msg(struct utsm_ioctl_msg *umsg)
{
memset(umsg, 0, sizeof(*umsg));
umsg->buf_size = UTSM_IPC_MSG_DATA_SIZE;
return ioctl(g_utsm_fd, UTSM_IOCTL_RECV_MSG, umsg);
}
/* Park: HLT → VM-Exit → UTSM parks guest. Returns on vmresume. */
static int park(void)
{
return ioctl(g_utsm_fd, UTSM_IOCTL_PARK);
}
/* ===== Exec request handler =====
*
* Fork a child to exec the requested program. Parent reads stdout/stderr
* from a pipe and streams chunks back to UTSM via EXEC_STDOUT/EXEC_STDERR
* messages. Finally sends EXEC_EXIT with the child's exit status.
*
* argv parsing: argv_blob contains NUL-terminated argv[0..argc-1]
* concatenated. We split it into a char* array for execv. */
static void handle_exec_request(const struct ipc_exec_request *req)
{
/* Parse argv_blob into argv[] array */
char *argv[UTSM_EXEC_MAX_ARGS + 1];
int argc = (int)req->argc;
if (argc > UTSM_EXEC_MAX_ARGS) argc = UTSM_EXEC_MAX_ARGS;
if (argc < 1) {
struct ipc_exec_exit ex = { .exit_code = 1, .reserved = 0 };
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
return;
}
const char *blob = req->argv_blob;
int blob_consumed = 0;
for (int i = 0; i < argc; i++) {
if (blob_consumed >= UTSM_EXEC_ARGV_MAX) {
argv[i] = (char *)"";
continue;
}
argv[i] = (char *)(blob + blob_consumed);
int slen = strnlen(blob + blob_consumed, UTSM_EXEC_ARGV_MAX - blob_consumed);
blob_consumed += slen + 1;
}
argv[argc] = NULL;
/* Create a pipe for child stdout+stderr */
int pipefd[2];
if (pipe(pipefd) != 0) {
struct ipc_exec_exit ex = { .exit_code = 126, .reserved = 0 };
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
return;
}
pid_t pid = fork();
if (pid < 0) {
/* fork failed */
close(pipefd[0]);
close(pipefd[1]);
struct ipc_exec_exit ex = { .exit_code = 127, .reserved = 0 };
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
return;
}
if (pid == 0) {
/* Child: redirect stdout + stderr to pipe write end, then exec */
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
execv(req->path, argv);
/* execv only returns on failure */
const char *err1 = "utsm_exec_daemon: exec failed: ";
write(STDERR_FILENO, err1, strlen(err1));
write(STDERR_FILENO, req->path, strnlen(req->path, UTSM_EXEC_PATH_MAX));
const char *err2 = ": ";
write(STDERR_FILENO, err2, strlen(err2));
const char *emsg = strerror(errno);
write(STDERR_FILENO, emsg, strlen(emsg));
write(STDERR_FILENO, "\n", 1);
_exit(127);
}
/* Parent: read child output from pipe, stream to UTSM */
close(pipefd[1]);
char buf[UTSM_IPC_MSG_DATA_SIZE];
ssize_t n;
while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
send_msg(UTSM_MSG_EXEC_STDOUT, buf, (unsigned int)n);
}
close(pipefd[0]);
/* Wait for child and send exit code */
int status = 0;
waitpid(pid, &status, 0);
struct ipc_exec_exit ex;
ex.reserved = 0;
if (WIFEXITED(status)) {
ex.exit_code = (unsigned int)WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
ex.exit_code = 128u + (unsigned int)WTERMSIG(status);
} else {
ex.exit_code = 1;
}
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
}
/* ===== Main loop ===== */
int main(void)
{
g_utsm_fd = open("/dev/utsm", O_RDWR);
if (g_utsm_fd < 0) {
/* Cannot communicate with UTSM — exit */
return 1;
}
/* Announce daemon ready. UTSM's linux_launch() will see the subsequent
* HLT (via PARK ioctl) and return, marking the compat layer ready. */
send_msg(UTSM_MSG_EXEC_READY, NULL, 0);
/* First park: this is the HLT that causes linux_launch() to return. */
park();
/* Main loop: process exec requests */
for (;;) {
struct utsm_ioctl_msg umsg;
/* Drain any pending messages (there may be multiple if UTSM
* queued several before vmresume). */
int got_request = 0;
while (recv_msg(&umsg) == 0) {
if (umsg.type == UTSM_MSG_EXEC_REQUEST &&
umsg.data_len >= sizeof(struct ipc_exec_request)) {
handle_exec_request((const struct ipc_exec_request *)umsg.data);
got_request = 1;
}
/* Ignore other message types (HELLO, PING, etc.) */
}
/* If we processed a request, the response is already sent.
* Park to let UTSM read the response and return to shell.
* If no request (spurious wakeup), just park again. */
park();
}
close(g_utsm_fd);
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
#
# init — Linux guest init script for UTSM dual-kernel Linux compat layer.
#
# Boot sequence:
# 1. Mount essential filesystems
# 2. Load UTSM driver (built-in, /dev/utsm auto-created)
# 3. Start utsm_exec_daemon (background)
# 4. Daemon sends EXEC_READY, then HLT (park) → linux_launch() returns to UTSM
# 5. UTSM proceeds to load DSK; Linux guest stays parked until shell
# invokes `linux <prog>` which triggers vmresume.
#
# The daemon runs forever, parking between requests. This init script
# should NOT exit (would panic the kernel); it sleeps indefinitely after
# starting the daemon so the kernel has a runnable init task.
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PATH
# Mount essential filesystems
mount -t proc none /proc 2>/dev/null
mount -t sysfs none /sys 2>/dev/null
mount -t devtmpfs none /dev 2>/dev/null
echo "[utsm-linux] init started"
echo "[utsm-linux] kernel: $(uname -r)"
# Verify /dev/utsm exists (driver is built-in, should be auto-created)
if [ ! -e /dev/utsm ]; then
echo "[utsm-linux] WARNING: /dev/utsm not found — UTSM driver not loaded"
fi
# Start the exec daemon in the background.
# The daemon sends EXEC_READY then HLTs (parks the guest).
# UTSM's linux_launch() returns after this first HLT.
echo "[utsm-linux] starting utsm_exec_daemon..."
/bin/utsm_exec_daemon &
DAEMON_PID=$!
echo "[utsm-linux] daemon PID=$DAEMON_PID"
# init must not exit — sleep forever to keep the kernel happy.
# The daemon handles all UTSM exec requests; init just keeps the
# root task alive so the kernel doesn't panic.
echo "[utsm-linux] init: daemon running, entering idle hold"
while true; do
sleep 3600
done
+140 -3
View File
@@ -23,12 +23,43 @@
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
/* Shared IPC protocol header (included via build.sh -I flag) */
#include <ipc_proto.h>
#define UTSM_DEV_NAME "utsm"
/* ===== 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)
/* 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 */
};
/* ===== Hypercall ABI =====
*
* Linux calls VMCALL with:
@@ -169,10 +200,116 @@ static ssize_t utsm_dev_read(struct file *f, char __user *buf,
return (ssize_t)msg.data_len;
}
/* ===== 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;
if (msg.data_len > 0) {
if (copy_to_user(umsg.data, msg.data, msg.data_len))
return -EFAULT;
}
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) {
if (copy_from_user(msg.data, umsg.data, msg.data_len))
return -EFAULT;
}
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: {
/* Execute HLT to trigger VM-Exit → UTSM parks guest.
* Interrupts must be enabled so that on vmresume the guest
* can continue (HLT with interrupts disabled would hang).
*
* Flow:
* 1. daemon calls ioctl(PARK) → driver executes HLT
* 2. HLT → VM-Exit (CPU_BASED_HLT_EXITING) → UTSM handle_hlt
* 3. UTSM advances RIP past HLT, exits to host (park)
* 4. UTSM writes request + vmresume
* 5. Guest continues from after HLT → ioctl returns
* 6. Daemon re-checks ring for new EXEC_REQUEST
*/
mb(); /* ensure prior ring writes are visible before HLT */
asm volatile("hlt" ::: "memory");
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;
}
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) */
mask |= EPOLLOUT | EPOLLWRNORM;
return mask;
}
static const struct file_operations utsm_fops = {
.owner = THIS_MODULE,
.write = utsm_dev_write,
.read = utsm_dev_read,
.owner = THIS_MODULE,
.write = utsm_dev_write,
.read = utsm_dev_read,
.unlocked_ioctl = utsm_dev_ioctl,
.poll = utsm_dev_poll,
};
static struct miscdevice utsm_miscdev = {