FUCKYOU
This commit is contained in:
@@ -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)。
|
||||
|
||||
@@ -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."
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user