This commit is contained in:
xyou
2026-07-31 13:53:49 +08:00
parent 5ea84fac97
commit f52157b7af
241 changed files with 26843 additions and 8155 deletions
+51
View File
@@ -112,3 +112,54 @@ Limine (UEFI)
→ virtio-fs mount /utsm
→ User space (init/shell)
```
## Guest Networking (virtio-net)
UTSM vmm 侧模拟一块 virtio-net 网卡(virtio-mmio 模型,GPA `0xF4001000`,
guest ISA IRQ6),数据路径桥接到 host 真实网卡(e1000/virtio-net DKM 驱动,
经 `kernel_api.net` 的 `tx`/`rx_poll` 收发帧)。guest 内核 cmdline 已自动注入:
```
virtio_mmio.device=4K@0xF4001000:6
```
`utsm_x86_64_defconfig` 已启用 `CONFIG_VIRTIO_NET=y`(内置,非模块),
内核启动后应出现 `eth0`(virtio-mmio 无 PCI 拓扑,网卡名固定为 eth0):
```bash
# 确认网卡已识别(dmesg 应能看到 virtio_net probe 日志)
ip link show
# 拉起接口
ip link set eth0 up
# 方式一:DHCP(QEMU user-net/slirp 环境,网关 10.0.2.2,DNS 10.0.2.3)
dhcpcd eth0
# 或:dhclient eth0
# 方式二:静态地址(无 DHCP 服务器时)
ip addr add 10.0.2.15/24 dev eth0
ip route add default via 10.0.2.2
echo "nameserver 10.0.2.3" > /etc/resolv.conf
# 验证连通性
ping -c3 10.0.2.2 # QEMU slirp 网关(永远在线,适合冒烟测试)
ping -c3 10.0.2.3 # slirp 内建 DNS
pacman -Sy # 联网同步包数据库
```
注意事项:
- **MAC 共用**:guest virtio-net 的 MAC 与 host e1000 相同(UTSM 桥接模型,
config 空间直接回填 host 网卡 MAC)。QEMU user-net 下 slirp 按 MAC 分配
`10.0.2.15`;若 host Deshab 原生网络栈同时用该网卡联网,两者会抢同一
DHCP 租约,实测时请只让一侧做 DHCP(或 host 完全不用网络)。
- **MTU**:后端按标准 1500 MTU 转发,不支持 GSO/TSO(feature 未协商,
guest 自动走软件分片/checksum 路径)。
- **无中断收包**:RX 走 UTSM 周期轮询(VMX preemption timer,~1kHz)
填充 guest RX ring 并注入 IRQ6;大流量下串口统计日志每 512 帧打印一行
`[VNET] stat ...`(rx_filled / rx_drop_nobuf / tx_sent / tx_drop),
可用于判断包在哪一段丢失。
- 若 `ip link` 看不到 eth0:检查 dmesg 中 `virtio_mmio` probe 是否成功,
以及 host 串口日志是否有 `[VNET] bound to UTSM net device 0`
(host 侧 e1000/virtio-net DKM 驱动需先注册 netdev)。
+287
View File
@@ -0,0 +1,287 @@
#!/bin/bash
#
# Build Arch Linux rootfs ext4 image for UTSM Linux guest.
#
# Downloads the Arch Linux bootstrap rootfs, installs pacman with Chinese
# mirror configuration, bakes in utsm_exec_daemon, and packs everything
# into an ext4 image exposed to the Linux guest via virtio-blk (/dev/vdb).
#
# Output:
# SYSTEM/boot/linux-rootfs.img (ext4 image, ~512MB)
#
# Performance note:
# All intermediate work (extraction, ext4 build) happens on the native
# Linux filesystem (WORK_DIR) — NOT on /mnt/d (9p). The 9p mount is
# catastrophically slow for thousands of small files. Only the final
# image is copied to the Windows side.
#
# Prerequisites:
# - WSL or native Linux with root access
# - curl, mkfs.ext4, losetup, mount, tar (with zstd), rsync, cc
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-rootfs.img"
IMG_SIZE_MB=${IMG_SIZE_MB:-512}
# Work on native Linux FS for speed (NOT /mnt/d which is 9p — extremely slow).
WORK_DIR="${ARCH_ROOTFS_WORK:-/root/arch_rootfs_work}"
ROOTFS_DIR="$WORK_DIR/rootfs"
OLD_CACHE="$SCRIPT_DIR/.arch_rootfs_work/archlinux-bootstrap.tar.zst"
echo "[arch-rootfs] Building Arch Linux rootfs image"
echo "[arch-rootfs] Work dir: $WORK_DIR (native FS)"
echo "[arch-rootfs] Output: $OUTPUT_IMG (${IMG_SIZE_MB}MB)"
mkdir -p "$WORK_DIR" "$ROOTFS_DIR" "$OUTPUT_DIR"
# 1. Download Arch bootstrap rootfs if not cached (.tar.zst format)
BOOTSTRAP_TAR="$WORK_DIR/archlinux-bootstrap.tar.zst"
# Migrate old cache from /mnt/d if present (saves re-download)
if [ ! -f "$BOOTSTRAP_TAR" ] && [ -f "$OLD_CACHE" ]; then
echo "[arch-rootfs] Migrating cached tarball from /mnt/d → native FS..."
cp "$OLD_CACHE" "$BOOTSTRAP_TAR"
rm -rf "$SCRIPT_DIR/.arch_rootfs_work"
fi
download_bootstrap() {
local url="$1"
echo "[arch-rootfs] Trying: $url"
curl -fL --retry 3 --connect-timeout 20 -o "$BOOTSTRAP_TAR" "$url"
}
if [ ! -f "$BOOTSTRAP_TAR" ]; then
echo "[arch-rootfs] Downloading Arch bootstrap rootfs (.tar.zst)..."
download_bootstrap "https://mirrors.tuna.tsinghua.edu.cn/archlinux/iso/latest/archlinux-bootstrap-x86_64.tar.zst" || \
download_bootstrap "https://mirrors.ustc.edu.cn/archlinux/iso/latest/archlinux-bootstrap-x86_64.tar.zst" || \
download_bootstrap "https://mirrors.aliyun.com/archlinux/iso/latest/archlinux-bootstrap-x86_64.tar.zst" || \
download_bootstrap "https://mirrors.cloud.tencent.com/archlinux/iso/latest/archlinux-bootstrap-x86_64.tar.zst" || {
echo "[arch-rootfs] ERROR: Failed to download bootstrap rootfs from all mirrors"
exit 1
}
fi
echo "[arch-rootfs] Bootstrap tarball: $(du -h "$BOOTSTRAP_TAR" | cut -f1)"
# 2. Extract bootstrap rootfs directly to ROOTFS_DIR (--strip-components=1
# drops the root.x86_64/ wrapper, eliminating a costly rsync copy).
if [ ! -f "$ROOTFS_DIR/bin/bash" ]; then
echo "[arch-rootfs] Extracting bootstrap rootfs (native FS, --strip-components=1)..."
rm -rf "$ROOTFS_DIR"
mkdir -p "$ROOTFS_DIR"
tar --zstd -xf "$BOOTSTRAP_TAR" --strip-components=1 -C "$ROOTFS_DIR"
fi
if [ ! -f "$ROOTFS_DIR/bin/bash" ]; then
echo "[arch-rootfs] ERROR: Extraction failed, /bin/bash not found"
exit 1
fi
echo "[arch-rootfs] Rootfs extracted: $(du -sh "$ROOTFS_DIR" | cut -f1)"
# 3. Configure pacman mirrors (Chinese mirrors for faster access)
echo "[arch-rootfs] Configuring pacman mirrors..."
mkdir -p "$ROOTFS_DIR/etc/pacman.d"
cat > "$ROOTFS_DIR/etc/pacman.d/mirrorlist" <<'MIRRORS'
# China mirrors (sorted by speed)
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
Server = https://mirrors.aliyun.com/archlinux/$repo/os/$arch
Server = https://mirrors.cloud.tencent.com/archlinux/$repo/os/$arch
Server = https://mirrors.huaweicloud.com/archlinux/$repo/os/$arch
MIRRORS
# 4. Ensure pacman config exists (always rewrite: SigLevel=Never avoids
# gnupg keyring writes on the read-only rootfs — pacman -Sy then works
# without pacman-key init).
cat > "$ROOTFS_DIR/etc/pacman.conf" <<'PACMANCONF'
[options]
HoldPkg = pacman glibc
Architecture = auto
SigLevel = Never
LocalFileSigLevel = Never
[core]
Include = /etc/pacman.d/mirrorlist
[extra]
Include = /etc/pacman.d/mirrorlist
[community]
Include = /etc/pacman.d/mirrorlist
PACMANCONF
# 5. Create essential directories and device nodes
echo "[arch-rootfs] Creating device nodes and directories..."
mkdir -p "$ROOTFS_DIR"/{dev,proc,sys,tmp,run,mnt,root,home}
mkdir -p "$ROOTFS_DIR/etc" "$ROOTFS_DIR/var/log" "$ROOTFS_DIR/var/lib/pacman"
mknod -m 600 "$ROOTFS_DIR/dev/console" c 5 1 2>/dev/null || true
mknod -m 666 "$ROOTFS_DIR/dev/null" c 1 3 2>/dev/null || true
mknod -m 666 "$ROOTFS_DIR/dev/zero" c 1 5 2>/dev/null || true
mknod -m 666 "$ROOTFS_DIR/dev/ttyS0" c 4 64 2>/dev/null || true
# 5.5 Strip unnecessary files to reduce image size (~597M → ~300M).
# The rootfs is a RAM-resident boot module, so every MB counts.
# Removes: man pages, docs, info, dev headers, static libs, non-en/zh locales.
echo "[arch-rootfs] Stripping non-essential files..."
echo "[arch-rootfs] before: $(du -sh "$ROOTFS_DIR" | cut -f1)"
rm -rf "$ROOTFS_DIR/usr/share/man" "$ROOTFS_DIR/usr/share/doc" \
"$ROOTFS_DIR/usr/share/info" "$ROOTFS_DIR/usr/share/gtk-doc" \
"$ROOTFS_DIR/usr/share/help" "$ROOTFS_DIR/usr/include" \
"$ROOTFS_DIR/usr/share/readline" "$ROOTFS_DIR/usr/share/gdb" \
"$ROOTFS_DIR/usr/share/bug" "$ROOTFS_DIR/usr/share/bash-completion"
# Keep only en and zh locales (strip the other ~100MB)
if [ -d "$ROOTFS_DIR/usr/share/locale" ]; then
find "$ROOTFS_DIR/usr/share/locale" -maxdepth 1 -type d \
! -name 'locale' ! -name 'en*' ! -name 'zh*' ! -name 'C*' \
-exec rm -rf {} + 2>/dev/null || true
fi
# Remove static libraries (not needed at runtime; save ~10M)
find "$ROOTFS_DIR/usr/lib" -name '*.a' -delete 2>/dev/null || true
# Remove pacman package cache
rm -rf "$ROOTFS_DIR/var/cache/pacman/pkg"/* 2>/dev/null || true
echo "[arch-rootfs] after: $(du -sh "$ROOTFS_DIR" | cut -f1)"
# 5.6 Redirect writable paths into tmpfs (rootfs itself mounts read-only —
# the UTSM virtio-blk rootfs backend is a read-only memory image).
# /tmp is a tmpfs mounted by /sbin/init, so symlinking these paths
# makes pacman -Sy and DNS resolution work in the guest:
# /var/lib/pacman → pacman sync databases (pacman -Sy writes here)
# /var/cache/pacman → downloaded package cache
# /etc/resolv.conf → DNS config written by /sbin/init
echo "[arch-rootfs] Redirecting writable paths to /tmp (tmpfs)..."
rm -rf "$ROOTFS_DIR/var/lib/pacman" "$ROOTFS_DIR/var/cache/pacman"
ln -sfn /tmp/pacman/lib "$ROOTFS_DIR/var/lib/pacman"
ln -sfn /tmp/pacman/cache "$ROOTFS_DIR/var/cache/pacman"
rm -f "$ROOTFS_DIR/etc/resolv.conf"
ln -sfn /tmp/resolv.conf "$ROOTFS_DIR/etc/resolv.conf"
# 6. Create /sbin/init — runs AFTER switch_root from initramfs.
# Starts utsm_exec_daemon (UTSM Linux compat layer), then drops to shell.
cat > "$ROOTFS_DIR/sbin/init" <<'INITSCRIPT'
#!/bin/bash
# Arch rootfs init - runs after switch_root from initramfs
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
mount -t proc none /proc 2>/dev/null
mount -t sysfs none /sys 2>/dev/null
mount -t devtmpfs none /dev 2>/dev/null
mount -t tmpfs none /tmp 2>/dev/null
mount -t tmpfs none /run 2>/dev/null
# Writable dirs for pacman on the read-only rootfs (see build script 5.6:
# /var/lib/pacman and /var/cache/pacman are symlinks into this tmpfs).
mkdir -p /tmp/pacman/lib/sync /tmp/pacman/cache/pkg
echo "arch-utsm" > /etc/hostname
# Configure network (virtio-net should be eth0)
# UTSM host bridges virtio-net → QEMU e1000 → slirp user networking,
# whose subnet is fixed at 10.0.2.0/24 (gw 10.0.2.2, DNS 10.0.2.3).
# Use a static config so pacman works without a DHCP client in rootfs.
ip link set lo up 2>/dev/null
if ip link set eth0 up 2>/dev/null; then
ip addr add 10.0.2.15/24 dev eth0 2>/dev/null
ip route add default via 10.0.2.2 dev eth0 2>/dev/null
echo "nameserver 10.0.2.3" > /etc/resolv.conf
echo "[arch-utsm] eth0: 10.0.2.15/24 gw 10.0.2.2 (slirp static)"
else
echo "[arch-utsm] WARNING: eth0 not found — network unavailable"
fi
echo "[arch-utsm] Arch Linux rootfs booted"
echo "[arch-utsm] Kernel: $(uname -r)"
echo "[arch-utsm] Architecture: $(uname -m)"
# Check if pacman is available
if command -v pacman &>/dev/null; then
echo "[arch-utsm] pacman available: $(pacman --version | head -1)"
else
echo "[arch-utsm] WARNING: pacman not found"
fi
# Check if running under UTSM
if [ -e /dev/utsm ]; then
echo "[arch-utsm] UTSM device detected, compat layer active"
fi
# Start exec daemon (UTSM Linux compat layer).
# The daemon sends EXEC_READY then ioctl(PARK)→HLT, which parks the guest
# and lets linux_launch() return to UTSM. On `linux <prog>` from Deshab
# shell, UTSM vmresume's the guest and the daemon executes the program.
if [ -x /usr/local/bin/utsm_exec_daemon ]; then
echo "[arch-utsm] Starting UTSM exec daemon..."
/usr/local/bin/utsm_exec_daemon &
DAEMON_PID=$!
echo "[arch-utsm] exec daemon PID=$DAEMON_PID"
else
echo "[arch-utsm] WARNING: utsm_exec_daemon not found — compat layer disabled"
fi
# Interactive shell on console (keeps PID 1 alive).
# If the daemon is running, this shell coexists; the daemon parks via HLT
# and UTSM resumes it on demand. This shell is for direct guest debugging.
echo "[arch-utsm] Starting shell on console"
exec /bin/bash -l </dev/console >/dev/console 2>&1
INITSCRIPT
chmod +x "$ROOTFS_DIR/sbin/init"
# 7. Create /etc/fstab
cat > "$ROOTFS_DIR/etc/fstab" <<'FSTAB'
# /etc/fstab - Arch UTSM guest
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
devtmpfs /dev devtmpfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
tmpfs /run tmpfs defaults 0 0
FSTAB
# 7.5 Build & install utsm_exec_daemon into the Arch rootfs
# Static binary — runs as PID 2 after Arch /sbin/init. Parks the guest
# via ioctl(PARK)→HLT and handles UTSM exec requests on vmresume.
DAEMON_SRC="$SCRIPT_DIR/initramfs/bin/utsm_exec_daemon.c"
IPC_INCLUDE="$PROJECT_ROOT/CODE/utsm-ipc"
if [ -f "$DAEMON_SRC" ]; then
echo "[arch-rootfs] Compiling utsm_exec_daemon (static)..."
mkdir -p "$ROOTFS_DIR/usr/local/bin"
if cc -static -O2 -Wall "-I$IPC_INCLUDE" \
-o "$ROOTFS_DIR/usr/local/bin/utsm_exec_daemon" "$DAEMON_SRC" 2>"$WORK_DIR/daemon_build.log"; then
strip "$ROOTFS_DIR/usr/local/bin/utsm_exec_daemon" 2>/dev/null || true
chmod +x "$ROOTFS_DIR/usr/local/bin/utsm_exec_daemon"
echo "[arch-rootfs] daemon installed: $(ls -lh "$ROOTFS_DIR/usr/local/bin/utsm_exec_daemon" | awk '{print $5}')"
else
echo "[arch-rootfs] WARNING: daemon build failed:"
cat "$WORK_DIR/daemon_build.log"
fi
else
echo "[arch-rootfs] WARNING: daemon source not found at $DAEMON_SRC"
fi
# 8. Create ext4 image on native FS, populate, then copy to /mnt/d.
echo "[arch-rootfs] Creating ext4 image (${IMG_SIZE_MB}MB) on native FS..."
NATIVE_IMG="$WORK_DIR/linux-rootfs.img"
rm -f "$NATIVE_IMG"
dd if=/dev/zero of="$NATIVE_IMG" bs=1M count="$IMG_SIZE_MB" 2>/dev/null
mkfs.ext4 -F -q -L "ARCH_ROOTFS" "$NATIVE_IMG" >/dev/null
mkdir -p "$WORK_DIR/mnt"
LOOP_DEV=$(losetup -f --show "$NATIVE_IMG")
mount "$LOOP_DEV" "$WORK_DIR/mnt"
echo "[arch-rootfs] Copying rootfs into ext4 image (native FS)..."
rsync -a "$ROOTFS_DIR/" "$WORK_DIR/mnt/"
sync
umount "$WORK_DIR/mnt"
losetup -d "$LOOP_DEV"
# 9. Copy the finished image to the Windows side (single large-file write).
echo "[arch-rootfs] Copying image to $OUTPUT_IMG ..."
cp "$NATIVE_IMG" "$OUTPUT_IMG"
echo "[arch-rootfs] Image created: $OUTPUT_IMG ($(du -h "$OUTPUT_IMG" | cut -f1))"
echo "[arch-rootfs] Done. rootfs.img is a limine boot module (linux:rootfs)."
echo "[arch-rootfs] Linux guest init mounts /dev/vdb and switch_root's into it."
+2 -2
View File
@@ -6,7 +6,7 @@ SCRIPT_DIR="/mnt/d/Code/Deshab/CODE/linux"
PROJECT_ROOT="/mnt/d/Code/Deshab"
OUTPUT_DIR="$PROJECT_ROOT/SYSTEM/boot"
INITRAMFS_DIR="$SCRIPT_DIR/initramfs"
LINUX_SRC="$HOME/linux-6.6"
LINUX_SRC="${LINUX_SRC:-$HOME/linux-6.6}"
INITRAMFS_TMP="$SCRIPT_DIR/initramfs.tmp"
rm -rf "$INITRAMFS_TMP"
@@ -57,7 +57,7 @@ SPEC="$SCRIPT_DIR/initramfs.tmp.spec"
if [ -f "$INITRAMFS_TMP/bin/utsm_exec_daemon" ]; then
echo "file /bin/utsm_exec_daemon $INITRAMFS_TMP/bin/utsm_exec_daemon 755 0 0"
fi
for app in sh mount umount echo sleep uname ls cat ps setsid cttyhack; do
for app in sh mount umount echo sleep uname ls cat ps mkdir switch_root setsid cttyhack; do
echo "slink /bin/$app /bin/busybox 777 0 0"
done
} > "$SPEC"
+262 -10
View File
@@ -23,9 +23,11 @@
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
/* Shared IPC protocol — must match UTSM side */
#include "../../../utsm-ipc/ipc_proto.h"
@@ -35,6 +37,8 @@
#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)
#define UTSM_IOCTL_WRITE_POOL _IOW('U', 5, struct utsm_ioctl_pool)
#define UTSM_IOCTL_READ_POOL _IOWR('U', 6, struct utsm_ioctl_pool)
struct utsm_ioctl_msg {
unsigned int type;
@@ -44,6 +48,13 @@ struct utsm_ioctl_msg {
unsigned char data[UTSM_IPC_MSG_DATA_SIZE];
};
/* Pool transfer descriptor — must match utsm_hcall.c driver */
struct utsm_ioctl_pool {
unsigned int offset;
unsigned int len;
unsigned long long user_buf;
};
static int g_utsm_fd = -1;
/* Send a typed message to UTSM via the linux_to_utsm ring */
@@ -77,6 +88,14 @@ static int park(void)
return ioctl(g_utsm_fd, UTSM_IOCTL_PARK);
}
/* Best-effort write to the child's stderr (exec failure path);
* the result is intentionally ignored. */
static void err_write(const char *s, unsigned int len)
{
ssize_t n = write(STDERR_FILENO, s, len);
(void)n;
}
/* ===== Exec request handler =====
*
* Fork a child to exec the requested program. Parent reads stdout/stderr
@@ -138,13 +157,13 @@ static void handle_exec_request(const struct ipc_exec_request *req)
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));
err_write(err1, (unsigned int)strlen(err1));
err_write(req->path, (unsigned int)strnlen(req->path, UTSM_EXEC_PATH_MAX));
const char *err2 = ": ";
write(STDERR_FILENO, err2, strlen(err2));
err_write(err2, (unsigned int)strlen(err2));
const char *emsg = strerror(errno);
write(STDERR_FILENO, emsg, strlen(emsg));
write(STDERR_FILENO, "\n", 1);
err_write(emsg, (unsigned int)strlen(emsg));
err_write("\n", 1);
_exit(127);
}
@@ -174,6 +193,232 @@ static void handle_exec_request(const struct ipc_exec_request *req)
send_msg(UTSM_MSG_EXEC_EXIT, &ex, sizeof(ex));
}
/* ===== File transfer handlers (Phase 3) =====
*
* UTSM sends FILE_LIST_REQUEST / FILE_READ_REQUEST with an ipc_file_request
* payload. Bulk result data is staged into the shared payload_pool via
* UTSM_IOCTL_WRITE_POOL, then a FILE_RESPONSE message (ipc_file_response)
* carries status + data_len + total_size back to UTSM.
*
* Directory listing format: one entry per line, directories suffixed '/'. */
/* Staging buffer for file reads / listings (capped by pool capacity ~1MB) */
#define FT_STAGE_SIZE (768 * 1024)
static unsigned char g_ft_stage[FT_STAGE_SIZE];
static int pool_write(unsigned int offset, const void *buf, unsigned int len)
{
struct utsm_ioctl_pool p;
p.offset = offset;
p.len = len;
p.user_buf = (unsigned long long)(uintptr_t)buf;
return ioctl(g_utsm_fd, UTSM_IOCTL_WRITE_POOL, &p);
}
static int pool_read(unsigned int offset, void *buf, unsigned int len)
{
struct utsm_ioctl_pool p;
p.offset = offset;
p.len = len;
p.user_buf = (unsigned long long)(uintptr_t)buf;
return ioctl(g_utsm_fd, UTSM_IOCTL_READ_POOL, &p);
}
static unsigned int errno_to_file_status(int e)
{
switch (e) {
case ENOENT: return UTSM_FILE_ERR_NOENT;
case ENOTDIR: return UTSM_FILE_ERR_NOTDIR;
case EISDIR: return UTSM_FILE_ERR_ISDIR;
case EACCES: return UTSM_FILE_ERR_PERM;
case ENOSPC:
case EFBIG: return UTSM_FILE_ERR_NOSPC;
default: return UTSM_FILE_ERR_IO;
}
}
static void send_file_response(unsigned int status, unsigned int data_len,
unsigned long long total_size)
{
struct ipc_file_response resp;
memset(&resp, 0, sizeof(resp));
resp.status = status;
resp.data_len = data_len;
resp.total_size = total_size;
send_msg(UTSM_MSG_FILE_RESPONSE, &resp, sizeof(resp));
}
/* FILE_LIST_REQUEST: list directory entries as "name\n" ("name/\n" for dirs) */
static void handle_file_list(const struct ipc_file_request *req)
{
char path[UTSM_FILE_PATH_MAX + 1];
memcpy(path, req->path, UTSM_FILE_PATH_MAX);
path[UTSM_FILE_PATH_MAX] = '\0';
if (req->pool_capacity == 0 || req->pool_offset >= UTSM_IPC_PAYLOAD_SIZE) {
send_file_response(UTSM_FILE_ERR_INVAL, 0, 0);
return;
}
DIR *d = opendir(path);
if (!d) {
send_file_response(errno_to_file_status(errno), 0, 0);
return;
}
unsigned int cap = req->pool_capacity;
if (cap > FT_STAGE_SIZE) cap = FT_STAGE_SIZE;
unsigned int used = 0;
struct dirent *de;
while ((de = readdir(d)) != NULL) {
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
/* Determine directory flag: d_type if available, else stat */
int is_dir = 0;
if (de->d_type == DT_DIR) {
is_dir = 1;
} else if (de->d_type == DT_UNKNOWN || de->d_type == DT_LNK) {
struct stat st;
char full[512];
snprintf(full, sizeof(full), "%s/%s", path, de->d_name);
if (stat(full, &st) == 0 && S_ISDIR(st.st_mode))
is_dir = 1;
}
int nl = snprintf((char *)g_ft_stage + used, cap - used,
"%s%s\n", de->d_name, is_dir ? "/" : "");
if (nl < 0) break;
if ((unsigned int)nl >= cap - used) {
/* 缓冲已满:本条未完整写入,截断结束 */
used = cap;
break;
}
used += (unsigned int)nl;
}
closedir(d);
if (used > 0 && pool_write(req->pool_offset, g_ft_stage, used) != 0) {
send_file_response(UTSM_FILE_ERR_IO, 0, 0);
return;
}
send_file_response(UTSM_FILE_OK, used, 0);
}
/* FILE_READ_REQUEST: read up to pool_capacity bytes at file_offset */
static void handle_file_read(const struct ipc_file_request *req)
{
char path[UTSM_FILE_PATH_MAX + 1];
memcpy(path, req->path, UTSM_FILE_PATH_MAX);
path[UTSM_FILE_PATH_MAX] = '\0';
if (req->pool_capacity == 0 || req->pool_offset >= UTSM_IPC_PAYLOAD_SIZE) {
send_file_response(UTSM_FILE_ERR_INVAL, 0, 0);
return;
}
struct stat st;
if (stat(path, &st) != 0) {
send_file_response(errno_to_file_status(errno), 0, 0);
return;
}
if (S_ISDIR(st.st_mode)) {
send_file_response(UTSM_FILE_ERR_ISDIR, 0, (unsigned long long)st.st_size);
return;
}
int fd = open(path, O_RDONLY);
if (fd < 0) {
send_file_response(errno_to_file_status(errno), 0,
(unsigned long long)st.st_size);
return;
}
unsigned int cap = req->pool_capacity;
if (cap > FT_STAGE_SIZE) cap = FT_STAGE_SIZE;
off_t off = (off_t)req->file_offset;
ssize_t n = pread(fd, g_ft_stage, cap, off);
int saved = errno;
close(fd);
if (n < 0) {
send_file_response(errno_to_file_status(saved), 0,
(unsigned long long)st.st_size);
return;
}
if (n > 0 && pool_write(req->pool_offset, g_ft_stage, (unsigned int)n) != 0) {
send_file_response(UTSM_FILE_ERR_IO, 0, (unsigned long long)st.st_size);
return;
}
send_file_response(UTSM_FILE_OK, (unsigned int)n,
(unsigned long long)st.st_size);
}
/* FILE_WRITE_REQUEST: write payload_pool data to file at file_offset.
* UTSM pre-stages req->pool_capacity bytes at req->pool_offset in the pool.
* Convention: file_offset == 0 truncates the file first (push = replace);
* the file is created with mode 0644 if missing. Data is moved in
* FT_STAGE_SIZE pieces so a full pool (~1MB) can be consumed per request. */
static void handle_file_write(const struct ipc_file_request *req)
{
char path[UTSM_FILE_PATH_MAX + 1];
memcpy(path, req->path, UTSM_FILE_PATH_MAX);
path[UTSM_FILE_PATH_MAX] = '\0';
if (req->pool_offset >= UTSM_IPC_PAYLOAD_SIZE ||
req->pool_capacity > UTSM_IPC_PAYLOAD_SIZE - req->pool_offset) {
send_file_response(UTSM_FILE_ERR_INVAL, 0, 0);
return;
}
struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
send_file_response(UTSM_FILE_ERR_ISDIR, 0, 0);
return;
}
int flags = O_WRONLY | O_CREAT;
if (req->file_offset == 0)
flags |= O_TRUNC;
int fd = open(path, flags, 0644);
if (fd < 0) {
send_file_response(errno_to_file_status(errno), 0, 0);
return;
}
unsigned int remaining = req->pool_capacity;
unsigned int done = 0;
while (remaining > 0) {
unsigned int chunk = remaining;
if (chunk > FT_STAGE_SIZE) chunk = FT_STAGE_SIZE;
if (pool_read(req->pool_offset + done, g_ft_stage, chunk) != 0) {
close(fd);
send_file_response(UTSM_FILE_ERR_IO, 0, 0);
return;
}
ssize_t n = pwrite(fd, g_ft_stage, chunk,
(off_t)(req->file_offset + done));
if (n < 0) {
int saved = errno;
close(fd);
send_file_response(errno_to_file_status(saved), done, 0);
return;
}
done += (unsigned int)n;
remaining -= (unsigned int)n;
if ((unsigned int)n < chunk)
break; /* short write (e.g. ENOSPC on next call would fail) */
}
unsigned long long total = 0;
if (fstat(fd, &st) == 0)
total = (unsigned long long)st.st_size;
close(fd);
send_file_response(UTSM_FILE_OK, done, total);
}
/* ===== Main loop ===== */
int main(void)
@@ -197,19 +442,26 @@ int main(void)
/* 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;
} else if (umsg.type == UTSM_MSG_FILE_LIST_REQUEST &&
umsg.data_len >= sizeof(struct ipc_file_request)) {
handle_file_list((const struct ipc_file_request *)umsg.data);
} else if (umsg.type == UTSM_MSG_FILE_READ_REQUEST &&
umsg.data_len >= sizeof(struct ipc_file_request)) {
handle_file_read((const struct ipc_file_request *)umsg.data);
} else if (umsg.type == UTSM_MSG_FILE_WRITE_REQUEST &&
umsg.data_len >= sizeof(struct ipc_file_request)) {
handle_file_write((const struct ipc_file_request *)umsg.data);
}
/* 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. */
/* Responses (if any) are already queued. Park to let UTSM read
* them and return to the shell; a spurious wakeup with no
* request simply parks again. */
park();
}
+69 -28
View File
@@ -2,17 +2,21 @@
#
# 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.
# Boot sequence (phase 2 — Arch rootfs via virtio-blk /dev/vdb):
# 1. Mount essential filesystems (proc/sys/dev)
# 2. Wait for /dev/vdb (Arch rootfs virtio-blk device) to appear
# 3. Mount /dev/vdb as ext4 at /mnt/root
# 4. Copy /dev nodes + daemon (already baked into rootfs) into /mnt/root
# 5. switch_root /mnt/root /sbin/init
# 6. Arch /sbin/init starts utsm_exec_daemon → EXEC_READY → HLT (park)
# → linux_launch() returns to UTSM
#
# 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.
# Fallback (phase 1 — no rootfs, daemon in initramfs):
# - If /dev/vdb never appears, start daemon directly from initramfs.
# - If no daemon either, drop to interactive debug shell on ttyS0.
#
# The daemon runs forever, parking between requests. After switch_root,
# Arch /sbin/init owns PID 1 and must not exit.
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PATH
@@ -30,27 +34,64 @@ 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.
# ===== Phase 2: switch_root to Arch rootfs on /dev/vdb =====
# The rootfs is exposed as a memory-backed virtio-blk device (GPA 0xF4002000,
# IRQ7). Wait up to ~5s for the kernel to discover it via devtmpfs.
echo "[utsm-linux] waiting for /dev/vdb (Arch rootfs)..."
VDB_OK=0
i=0
while [ $i -lt 50 ]; do
if [ -b /dev/vdb ]; then
VDB_OK=1
break
fi
# 100ms sleep (busybox sleep supports fractional seconds)
sleep 0.1
i=$((i + 1))
done
if [ $VDB_OK -eq 1 ]; then
echo "[utsm-linux] /dev/vdb found, attempting ext4 mount..."
mkdir -p /mnt/root
# Try ext4 (preferred); fall back to ext2 if ext4 module absent.
if mount -t ext4 -o ro /dev/vdb /mnt/root 2>/dev/null || \
mount -t ext2 -o ro /dev/vdb /mnt/root 2>/dev/null; then
echo "[utsm-linux] rootfs mounted, verifying /sbin/init..."
if [ -x /mnt/root/sbin/init ]; then
echo "[utsm-linux] switch_root → Arch /sbin/init"
# Move dev/proc/sys into new root so the Arch init can re-mount them.
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
exec switch_root /mnt/root /sbin/init
# switch_root does not return
else
echo "[utsm-linux] WARNING: /mnt/root/sbin/init missing, not switching"
umount /mnt/root 2>/dev/null
fi
else
echo "[utsm-linux] WARNING: ext4/ext2 mount of /dev/vdb failed"
echo "[utsm-linux] available block devices:"
ls -l /dev/vd* /dev/sd* 2>/dev/null
fi
else
echo "[utsm-linux] /dev/vdb not found after 5s — phase-1 fallback"
fi
# ===== Fallback: daemon directly from initramfs (phase 1) =====
if [ -x /bin/utsm_exec_daemon ]; then
echo "[utsm-linux] starting utsm_exec_daemon..."
echo "[utsm-linux] starting utsm_exec_daemon from initramfs..."
/bin/utsm_exec_daemon &
DAEMON_PID=$!
echo "[utsm-linux] daemon PID=$DAEMON_PID"
else
# 无 daemon(第一阶段 bring-up 冒烟测试构建):
# 直接在 ttyS0 上起交互 shell,验证 console 输入输出。
# 注意:此模式下 guest 不会 park,UTSM linux_launch() 不返回,
# 仅用于验证"Linux guest 进入 shell 提示符、可执行 uname -a"。
echo "[utsm-linux] WARNING: no daemon, starting interactive debug shell on ttyS0"
exec /bin/setsid /bin/cttyhack /bin/sh
# init must not exit — sleep forever to keep the kernel happy.
echo "[utsm-linux] init: daemon running, entering idle hold"
while true; do
sleep 3600
done
fi
# 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
# ===== Last resort: interactive debug shell on ttyS0 =====
# No daemon and no rootfs — bring-up smoke test only.
echo "[utsm-linux] WARNING: no daemon, no rootfs — starting interactive debug shell on ttyS0"
exec /bin/setsid /bin/cttyhack /bin/sh
+149
View File
@@ -52,6 +52,10 @@
#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)
#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)
/* Ioctl message wrapper: carries type + data in a single call */
struct utsm_ioctl_msg {
@@ -62,6 +66,29 @@ struct utsm_ioctl_msg {
u8 data[UTSM_IPC_MSG_DATA_SIZE]; /* payload */
};
/* 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) */
};
/* ===== Hypercall ABI =====
*
* Linux calls VMCALL with:
@@ -121,6 +148,23 @@ static inline long utsm_hcall_console_write(const char *buf, u64 len)
return utsm_hcall(UTSM_HCALL_CONSOLE_WRITE, (u64)buf, len, 0);
}
/* 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);
}
/* ===== Shared memory state ===== */
static struct utsm_ipc_shm *g_shm; /* kernel virtual address of SHM */
@@ -284,6 +328,111 @@ static long utsm_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
return 0;
}
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;
}
default:
return -ENOTTY;
}