FUCKKKK
This commit is contained in:
@@ -196,6 +196,30 @@ echo "[arch-utsm] Arch Linux rootfs booted"
|
||||
echo "[arch-utsm] Kernel: $(uname -r)"
|
||||
echo "[arch-utsm] Architecture: $(uname -m)"
|
||||
|
||||
# ===== VSCode Phase 4: persistent rw volume (/dev/vdc) =====
|
||||
# The extra-rootfs image is exposed as a WRITABLE memory-backed virtio-blk
|
||||
# device (slot6, GPA 0xF4006000, IRQ11) by the UTSM extra_rootfs backend.
|
||||
# We mount it at /mnt/persist and bind-mount its subtrees over the volatile
|
||||
# paths VSCode and user data live in, so they survive tmpfs cleanup and
|
||||
# (Phase 7) can be written back to the FAT32 ESP for true persistence.
|
||||
if [ -b /dev/vdc ]; then
|
||||
mkdir -p /mnt/persist
|
||||
if mount -t ext4 -o rw /dev/vdc /mnt/persist 2>/dev/null; then
|
||||
echo "[arch-utsm] /dev/vdc mounted rw at /mnt/persist (extra-rootfs)"
|
||||
# Persistent subtrees: /opt (VSCode install), /home (user data),
|
||||
# /root (daemon cwd), /var/lib/pacman is left on tmpfs (rebuildable).
|
||||
for d in opt home root; do
|
||||
mkdir -p "/mnt/persist/$d"
|
||||
mount --bind "/mnt/persist/$d" "/$d" 2>/dev/null && \
|
||||
echo "[arch-utsm] bind: /$d → persist" || true
|
||||
done
|
||||
else
|
||||
echo "[arch-utsm] WARNING: /dev/vdc mount failed (not ext4?) — rw layer disabled"
|
||||
fi
|
||||
else
|
||||
echo "[arch-utsm] no /dev/vdc — running fully volatile (tmpfs only)"
|
||||
fi
|
||||
|
||||
# Check if pacman is available
|
||||
if command -v pacman &>/dev/null; then
|
||||
echo "[arch-utsm] pacman available: $(pacman --version | head -1)"
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build the persistent rw ext4 volume for the UTSM Linux guest (VSCode Phase 4).
|
||||
#
|
||||
# Produces a sparse-friendly ext4 image exposed to the guest as a WRITABLE
|
||||
# virtio-blk device (/dev/vdc, slot6 @ GPA 0xF4006000 IRQ11) by the UTSM
|
||||
# extra_rootfs backend. The Arch /sbin/init mounts it at /mnt/persist and
|
||||
# bind-mounts its subtrees over /opt, /home and /root, giving VSCode and
|
||||
# user data a persistent home across the read-only rootfs.
|
||||
#
|
||||
# Output:
|
||||
# SYSTEM/boot/linux-extra-rootfs.img (ext4 image, default 1024MB)
|
||||
#
|
||||
# After generating, uncomment the two extra-rootfs module lines in
|
||||
# SYSTEM/limine/limine.conf so Limine preloads it as a boot module.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - WSL or native Linux with root access
|
||||
# - mkfs.ext4, losetup, mount (or debugfs for unprivileged population)
|
||||
#
|
||||
# Usage:
|
||||
# sudo bash CODE/linux/build_extra_rootfs.sh # 1GB default
|
||||
# sudo IMG_SIZE_MB=2048 bash CODE/linux/build_extra_rootfs.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot"
|
||||
OUTPUT_IMG="$OUTPUT_DIR/linux-extra-rootfs.img"
|
||||
IMG_SIZE_MB=${IMG_SIZE_MB:-1024}
|
||||
|
||||
WORK_DIR="${EXTRA_ROOTFS_WORK:-/root/extra_rootfs_work}"
|
||||
MNT_DIR="$WORK_DIR/mnt"
|
||||
|
||||
echo "[extra-rootfs] Building persistent rw volume"
|
||||
echo "[extra-rootfs] Work dir: $WORK_DIR (native FS)"
|
||||
echo "[extra-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)"
|
||||
|
||||
mkdir -p "$WORK_DIR" "$MNT_DIR" "$OUTPUT_DIR"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "[extra-rootfs] ERROR: must run as root (loop mount required)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Allocate the image file (seek-truncate: sparse on supporting filesystems)
|
||||
echo "[extra-rootfs] Allocating ${IMG_SIZE_MB}MB image..."
|
||||
dd if=/dev/zero of="$WORK_DIR/extra-rootfs.img" bs=1M count=0 seek="$IMG_SIZE_MB" status=none
|
||||
|
||||
# 2. Create the ext4 filesystem.
|
||||
# -N 1048576 caps inode count (1M inodes is plenty for VSCode + user data
|
||||
# and keeps the inode table small); -O ^has_journal skips the journal —
|
||||
# the volume is memory-backed (writes are volatile until Phase 7 writeback),
|
||||
# so journal overhead buys nothing.
|
||||
echo "[extra-rootfs] mkfs.ext4..."
|
||||
mkfs.ext4 -q -F -N 1048576 -O ^has_journal -L deshab-persist "$WORK_DIR/extra-rootfs.img"
|
||||
|
||||
# 3. Populate the base directory skeleton via loop mount.
|
||||
echo "[extra-rootfs] Populating directory skeleton..."
|
||||
LOOP_DEV="$(losetup --find --show "$WORK_DIR/extra-rootfs.img")"
|
||||
trap 'losetup -d "$LOOP_DEV" 2>/dev/null || true' EXIT
|
||||
|
||||
mount -t ext4 "$LOOP_DEV" "$MNT_DIR"
|
||||
mkdir -p "$MNT_DIR/opt" "$MNT_DIR/home" "$MNT_DIR/root" "$MNT_DIR/vscode"
|
||||
# /mnt/persist/vscode will hold the VSCode tarball extraction target marker;
|
||||
# /opt bind-mount covers /opt/vscode at runtime.
|
||||
touch "$MNT_DIR/vscode/.keep"
|
||||
sync
|
||||
umount "$MNT_DIR"
|
||||
losetup -d "$LOOP_DEV"
|
||||
trap - EXIT
|
||||
|
||||
# 4. Copy the final image to the Windows side (SYSTEM/boot).
|
||||
echo "[extra-rootfs] Copying to $OUTPUT_IMG ..."
|
||||
cp "$WORK_DIR/extra-rootfs.img" "$OUTPUT_IMG"
|
||||
|
||||
echo "[extra-rootfs] Done: $OUTPUT_IMG ($(du -h "$OUTPUT_IMG" | cut -f1) on disk)"
|
||||
echo "[extra-rootfs] Next: uncomment the extra-rootfs module lines in"
|
||||
echo "[extra-rootfs] SYSTEM/limine/limine.conf, then rebuild the ESP image."
|
||||
@@ -130,6 +130,34 @@ CONFIG_VIRTIO_CONSOLE=y
|
||||
CONFIG_HW_RANDOM=y
|
||||
CONFIG_HW_RANDOM_VIRTIO=y
|
||||
|
||||
# DRM + virtio-gpu (VSCode integration Phase 3: guest graphical output)
|
||||
# Provides /dev/dri/card0 for Xorg modesetting + scanout to host framebuffer
|
||||
CONFIG_DRM=y
|
||||
CONFIG_DRM_VIRTIO_GPU=y
|
||||
CONFIG_DRM_FBDEV_EMULATION=y
|
||||
CONFIG_FB=y
|
||||
CONFIG_FB_DEFERRED_IO=y
|
||||
CONFIG_FRAMEBUFFER_CONSOLE=y
|
||||
CONFIG_FB_TILEBLITTING=y
|
||||
|
||||
# VT + console (X server needs /dev/tty0 + VT_SWITCH)
|
||||
CONFIG_VT=y
|
||||
CONFIG_VT_CONSOLE=y
|
||||
CONFIG_HW_CONSOLE=y
|
||||
CONFIG_VT_HW_CONSOLE_BINDING=y
|
||||
|
||||
# Input evdev (VSCode integration Phase 2: virtio-input event devices)
|
||||
# Required for /dev/input/eventX — X server evdev driver reads these
|
||||
CONFIG_INPUT_EVDEV=y
|
||||
|
||||
# OverlayFS (VSCode integration Phase 4: rw layer over read-only rootfs)
|
||||
# /sbin/init mounts overlay -o lowerdir=/,upperdir=/tmp/rw/upper,workdir=/tmp/rw/work /
|
||||
CONFIG_OVERLAY_FS=y
|
||||
|
||||
# tmpfs xattr (overlayfs workdir needs xattr support)
|
||||
CONFIG_TMPFS_XATTR=y
|
||||
CONFIG_TMPFS_POSIX_ACL=y
|
||||
|
||||
# UTSM dual-kernel hypercall and IPC driver (Phase 1.3)
|
||||
CONFIG_UTSM_HCALL=y
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
|
||||
/* Shared IPC protocol — must match UTSM side */
|
||||
#include "../../../utsm-ipc/ipc_proto.h"
|
||||
@@ -57,6 +58,38 @@ struct utsm_ioctl_pool {
|
||||
|
||||
static int g_utsm_fd = -1;
|
||||
|
||||
/* pool_write 定义在文件传输段(下文),exec 同步路径复用同一通道 */
|
||||
static int pool_write(unsigned int offset, const void *buf, unsigned int len);
|
||||
static int send_msg(unsigned int type, const void *data, unsigned int len);
|
||||
static void err_write(const char *s, unsigned int len);
|
||||
|
||||
/* ===== exec 超时看门狗(P3 补完) =====
|
||||
* park-and-resume 模型下 UTSM 在 guest 运行期间完全不执行,宿主侧无法
|
||||
* 看门狗。子进程超时必须 guest 内兜底:alarm + SIGALRM → SIGKILL,
|
||||
* 保证 daemon 在 UTSM_EXEC_TIMEOUT_SEC 内一定 park,整系统不冻结。 */
|
||||
static volatile sig_atomic_t g_exec_timed_out = 0;
|
||||
static volatile sig_atomic_t g_exec_child = -1;
|
||||
|
||||
static void on_exec_alarm(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
g_exec_timed_out = 1;
|
||||
if (g_exec_child > 0)
|
||||
kill((pid_t)g_exec_child, SIGKILL);
|
||||
}
|
||||
|
||||
/* EXEC_EXIT 必须送达:ring 满(-EAGAIN)时短暂重试。新协议下 stdout
|
||||
* 走 payload_pool,ring 内通常只有这一条消息,满的概率极低。 */
|
||||
static void send_exit_msg(const struct ipc_exec_exit *ex)
|
||||
{
|
||||
for (int t = 0; t < 200; t++) {
|
||||
if (send_msg(UTSM_MSG_EXEC_EXIT, ex, sizeof(*ex)) == 0)
|
||||
return;
|
||||
usleep(1000);
|
||||
}
|
||||
err_write("utsm_exec_daemon: EXEC_EXIT send failed (ring full)\n", 52);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
@@ -111,8 +144,9 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
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));
|
||||
struct ipc_exec_exit ex = { .exit_code = 1, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,11 +163,49 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
}
|
||||
argv[argc] = NULL;
|
||||
|
||||
/* VSCode Phase 5: ASYNC launch path.
|
||||
* The caller (desktop IDE attach / app icon) wants to start a long-running
|
||||
* GUI program (X server, VSCode) without blocking the UTSM side for its
|
||||
* whole lifetime. We fork, detach the child into its own session with
|
||||
* stdio routed to /dev/null, reply EXEC_EXIT(0) immediately (0 = spawned
|
||||
* ok), and never waitpid — SIGCHLD is SIG_IGN so the kernel reaps it. */
|
||||
if (req->flags & UTSM_EXEC_FLAG_ASYNC) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
struct ipc_exec_exit ex = { .exit_code = 127, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
if (pid == 0) {
|
||||
/* Child: new session, no controlling tty, stdio → /dev/null */
|
||||
setsid();
|
||||
int nullfd = open("/dev/null", O_RDWR);
|
||||
if (nullfd >= 0) {
|
||||
dup2(nullfd, STDIN_FILENO);
|
||||
dup2(nullfd, STDOUT_FILENO);
|
||||
dup2(nullfd, STDERR_FILENO);
|
||||
if (nullfd > STDERR_FILENO) close(nullfd);
|
||||
}
|
||||
/* 与同步路径一致的库搜索路径;DISPLAY 指向 virtio-gpu X server */
|
||||
setenv("LD_LIBRARY_PATH", "/usr/lib/deshab", 1);
|
||||
setenv("DISPLAY", ":0", 1);
|
||||
execv(req->path, argv);
|
||||
_exit(127); /* execv 仅失败时返回 */
|
||||
}
|
||||
/* Parent: report successful spawn immediately */
|
||||
struct ipc_exec_exit ex = { .exit_code = 0, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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));
|
||||
struct ipc_exec_exit ex = { .exit_code = 126, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,8 +214,9 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
/* 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));
|
||||
struct ipc_exec_exit ex = { .exit_code = 127, .flags = 0,
|
||||
.stdout_len = 0, .stdout_total = 0 };
|
||||
send_exit_msg(&ex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,6 +227,9 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
|
||||
/* 设置 LD_LIBRARY_PATH,让 ld-linux.so 在 /usr/lib/deshab 查找 .so */
|
||||
setenv("LD_LIBRARY_PATH", "/usr/lib/deshab", 1);
|
||||
|
||||
execv(req->path, argv);
|
||||
/* execv only returns on failure */
|
||||
const char *err1 = "utsm_exec_daemon: exec failed: ";
|
||||
@@ -167,30 +243,80 @@ static void handle_exec_request(const struct ipc_exec_request *req)
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
/* Parent: read child output from pipe, stream to UTSM */
|
||||
/* Parent: read child output from pipe, stage into payload_pool.
|
||||
* P3 大输出改造:旧路径逐条 EXEC_STDOUT 入 ring(64 槽 × 240B ≈ 15KB,
|
||||
* 满则 -EAGAIN 静默丢弃);现直写 payload_pool(~1MB),EXEC_EXIT
|
||||
* 携带 stdout_len/stdout_total,UTSM 侧按新协议从 pool 拷出。 */
|
||||
close(pipefd[1]);
|
||||
|
||||
char buf[UTSM_IPC_MSG_DATA_SIZE];
|
||||
/* 安装超时看门狗:UTSM_EXEC_TIMEOUT_SEC 内子进程不退 → SIGKILL。
|
||||
* 不设 SA_RESTART:SIGALRM 中断 read(EINTR),随后子进程死亡 pipe EOF。 */
|
||||
g_exec_timed_out = 0;
|
||||
g_exec_child = pid;
|
||||
{
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = on_exec_alarm;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGALRM, &sa, NULL);
|
||||
alarm(UTSM_EXEC_TIMEOUT_SEC);
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
unsigned int pool_off = 0;
|
||||
unsigned int total = 0;
|
||||
int truncated = 0;
|
||||
ssize_t n;
|
||||
while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
|
||||
send_msg(UTSM_MSG_EXEC_STDOUT, buf, (unsigned int)n);
|
||||
for (;;) {
|
||||
n = read(pipefd[0], buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
total += (unsigned int)n;
|
||||
if (!truncated) {
|
||||
unsigned int room = (pool_off < UTSM_IPC_PAYLOAD_SIZE)
|
||||
? (UTSM_IPC_PAYLOAD_SIZE - pool_off) : 0;
|
||||
unsigned int chunk = ((unsigned int)n < room) ? (unsigned int)n : room;
|
||||
if (chunk > 0) {
|
||||
if (pool_write(pool_off, buf, chunk) != 0) {
|
||||
/* pool 写入失败:后续输出全部丢弃,记截断 */
|
||||
truncated = 1;
|
||||
} else {
|
||||
pool_off += chunk;
|
||||
}
|
||||
}
|
||||
if (chunk < (unsigned int)n)
|
||||
truncated = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue; /* SIGALRM 打断;子进程已被 SIGKILL,下轮 EOF */
|
||||
break; /* EOF 或真实错误 */
|
||||
}
|
||||
close(pipefd[0]);
|
||||
|
||||
/* Wait for child and send exit code */
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
alarm(0);
|
||||
g_exec_child = -1;
|
||||
|
||||
struct ipc_exec_exit ex;
|
||||
ex.reserved = 0;
|
||||
if (WIFEXITED(status)) {
|
||||
ex.flags = UTSM_EXEC_EXIT_F_POOL;
|
||||
ex.stdout_len = pool_off;
|
||||
ex.stdout_total = total;
|
||||
if (truncated)
|
||||
ex.flags |= UTSM_EXEC_EXIT_F_TRUNC;
|
||||
if (g_exec_timed_out) {
|
||||
ex.flags |= UTSM_EXEC_EXIT_F_TIMEOUT;
|
||||
ex.exit_code = 124; /* GNU timeout 惯例 */
|
||||
} else 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));
|
||||
send_exit_msg(&ex);
|
||||
}
|
||||
|
||||
/* ===== File transfer handlers (Phase 3) =====
|
||||
@@ -423,6 +549,10 @@ static void handle_file_write(const struct ipc_file_request *req)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* ASYNC children (VSCode Phase 5: X server / VSCode) are never waitpid'd;
|
||||
* SIG_IGN lets the kernel reap them automatically so they don't zombie. */
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
g_utsm_fd = open("/dev/utsm", O_RDWR);
|
||||
if (g_utsm_fd < 0) {
|
||||
/* Cannot communicate with UTSM — exit */
|
||||
|
||||
@@ -63,6 +63,36 @@ if [ $VDB_OK -eq 1 ]; then
|
||||
mount --move /dev /mnt/root/dev 2>/dev/null
|
||||
mount --move /proc /mnt/root/proc 2>/dev/null
|
||||
mount --move /sys /mnt/root/sys 2>/dev/null
|
||||
|
||||
# ===== VSCode Phase 4: overlay 持久化(/dev/vdc extra-rootfs 可写卷)=====
|
||||
# rootfs 以 ro 挂载;/opt(VSCode 安装位置)、/home、/root 通过
|
||||
# overlayfs 获得可写层,upperdir/workdir 落在 /dev/vdc(virtio-blk
|
||||
# slot6 可写后端,内容跨启动持久)。vdc 缺失时保持 ro,安全降级。
|
||||
if [ -b /dev/vdc ]; then
|
||||
echo "[utsm-linux] /dev/vdc found, setting up overlay persistence..."
|
||||
modprobe overlay 2>/dev/null
|
||||
mkdir -p /mnt/persist
|
||||
if mount -t ext4 -o rw /dev/vdc /mnt/persist 2>/dev/null; then
|
||||
for d in opt home root; do
|
||||
mkdir -p /mnt/persist/overlay/$d/upper \
|
||||
/mnt/persist/overlay/$d/work \
|
||||
/mnt/root/$d
|
||||
if mount -t overlay overlay \
|
||||
-o lowerdir=/mnt/root/$d,upperdir=/mnt/persist/overlay/$d/upper,workdir=/mnt/persist/overlay/$d/work \
|
||||
/mnt/root/$d 2>/dev/null; then
|
||||
echo "[utsm-linux] overlay: /$d rw (persist)"
|
||||
else
|
||||
echo "[utsm-linux] WARNING: overlay /$d failed (stays ro)"
|
||||
fi
|
||||
done
|
||||
# persist 卷搬进新 root,switch_root 后可在 /mnt/persist 访问
|
||||
mkdir -p /mnt/root/mnt/persist
|
||||
mount --move /mnt/persist /mnt/root/mnt/persist 2>/dev/null
|
||||
else
|
||||
echo "[utsm-linux] WARNING: /dev/vdc mount failed (no persistence)"
|
||||
fi
|
||||
fi
|
||||
|
||||
exec switch_root /mnt/root /sbin/init
|
||||
# switch_root does not return
|
||||
else
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
#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)
|
||||
/* 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)
|
||||
|
||||
/* Ioctl message wrapper: carries type + data in a single call */
|
||||
struct utsm_ioctl_msg {
|
||||
@@ -89,6 +93,15 @@ struct utsm_ioctl_hostfile {
|
||||
char name[64]; /* UTSM FAT32 root file name (NUL-terminated) */
|
||||
};
|
||||
|
||||
/* 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 */
|
||||
};
|
||||
|
||||
/* ===== Hypercall ABI =====
|
||||
*
|
||||
* Linux calls VMCALL with:
|
||||
@@ -143,6 +156,22 @@ static inline long utsm_hcall_shm_info(u64 *gpa_out, u64 *size_out)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
static inline long utsm_hcall_console_write(const char *buf, u64 len)
|
||||
{
|
||||
return utsm_hcall(UTSM_HCALL_CONSOLE_WRITE, (u64)buf, len, 0);
|
||||
@@ -173,6 +202,14 @@ static u64 g_shm_size; /* size of SHM region */
|
||||
static int g_utsm_present; /* 1 = UTSM detected */
|
||||
static int g_shm_mapped;
|
||||
|
||||
/* 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 */
|
||||
|
||||
/* ===== /dev/utsm character device ===== */
|
||||
|
||||
static ssize_t utsm_dev_write(struct file *f, const char __user *buf,
|
||||
@@ -433,6 +470,21 @@ static long utsm_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
@@ -450,17 +502,51 @@ static __poll_t utsm_dev_poll(struct file *f, struct poll_table_struct *wait)
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
|
||||
/* Always writable (ring push may still fail with -EAGAIN) */
|
||||
mask |= EPOLLOUT | EPOLLWRNORM;
|
||||
mask |= EPOLLOUT | EPOLLRDNORM;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
static const struct file_operations utsm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = utsm_dev_write,
|
||||
.read = utsm_dev_read,
|
||||
.unlocked_ioctl = utsm_dev_ioctl,
|
||||
.poll = utsm_dev_poll,
|
||||
.mmap = utsm_dev_mmap,
|
||||
};
|
||||
|
||||
static struct miscdevice utsm_miscdev = {
|
||||
@@ -585,7 +671,28 @@ static int __init utsm_hcall_init(void)
|
||||
|
||||
pr_info("[utsm] /dev/%s registered (IPC ready)\n", UTSM_DEV_NAME);
|
||||
|
||||
/* 8. Send a HELLO message via the ring buffer */
|
||||
/* 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 */
|
||||
{
|
||||
const char *hello_msg = "Linux booted OK";
|
||||
ipc_shm_send_to_utsm(UTSM_MSG_DATA, hello_msg, 15);
|
||||
@@ -646,6 +753,14 @@ static void __exit utsm_hcall_exit(void)
|
||||
g_shm_mapped = 0;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
pr_info("[utsm] driver unloaded\n");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user