diff --git a/disk/.gitkeep b/disk/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/disk/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/disk/hello.txt b/disk/hello.txt new file mode 100644 index 0000000..064c73f --- /dev/null +++ b/disk/hello.txt @@ -0,0 +1 @@ +Can you see me? Ah, there you are! You've unlocked the achievement "Virtio Newbie!" diff --git a/disk/lorem.txt b/disk/lorem.txt deleted file mode 100644 index d1578fe..0000000 --- a/disk/lorem.txt +++ /dev/null @@ -1 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut magna consequat, cursus velit aliquam, scelerisque odio. Ut lorem eros, feugiat quis bibendum vitae, malesuada ac orci. Praesent eget quam non nunc fringilla cursus imperdiet non tellus. diff --git a/disk/meow.txt b/disk/meow.txt new file mode 100644 index 0000000..e69de29 diff --git a/run.sh b/run.sh index 89c246e..9795302 100755 --- a/run.sh +++ b/run.sh @@ -10,8 +10,10 @@ qemu_default_bios_path="${QEMU_DEFAULT_BIOS_PATH:-/usr/share/qemu/opensbi-riscv3 qemu_allow_bios_none_fallback="${QEMU_ALLOW_BIOS_NONE_FALLBACK:-0}" qemu_input="${QEMU_INPUT:-}" qemu_input_delay_seconds="${QEMU_INPUT_DELAY_SECONDS:-1}" -qemu_disk_source="${QEMU_DISK_SOURCE:-${repo_root}/disk/lorem.txt}" +qemu_disk_dir="${QEMU_DISK_DIR:-${repo_root}/disk}" +qemu_disk_tar="${QEMU_DISK_TAR:-${repo_root}/zig-out/disk.tar}" qemu_disk_image="${QEMU_DISK_IMAGE:-${repo_root}/zig-out/disk.img}" +qemu_force_timeout="${QEMU_FORCE_TIMEOUT:-0}" required_tools=( qemu-system-riscv32 @@ -54,14 +56,18 @@ if [[ ! -f "${kernel_elf}" ]]; then exit 0 fi -if [[ ! -f "${qemu_disk_source}" ]]; then - echo "missing disk source: ${qemu_disk_source}" >&2 +if [[ ! -d "${qemu_disk_dir}" ]]; then + echo "missing disk dir: ${qemu_disk_dir}" >&2 exit 1 fi +mkdir -p "$(dirname "${qemu_disk_tar}")" mkdir -p "$(dirname "${qemu_disk_image}")" -cp "${qemu_disk_source}" "${qemu_disk_image}" -truncate -s 1024 "${qemu_disk_image}" +( + cd "${qemu_disk_dir}" + tar cf "${qemu_disk_tar}" --format=ustar -- *.txt +) +cp "${qemu_disk_tar}" "${qemu_disk_image}" qemu_cmd=( qemu-system-riscv32 @@ -80,19 +86,55 @@ qemu_cmd=( "virtio-blk-device,drive=drive0,bus=virtio-mmio-bus.0" ) +use_timeout=1 +display_to_terminal=0 +if [[ "${qemu_timeout_seconds}" == "0" ]]; then + use_timeout=0 +elif [[ -z "${qemu_input}" && -t 0 && -t 1 && "${qemu_force_timeout}" != "1" ]]; then + use_timeout=0 +fi + +if [[ -z "${qemu_input}" && -t 0 && -t 1 ]]; then + display_to_terminal=1 +fi + set +e if [[ -n "${qemu_input}" ]]; then - { - sleep "${qemu_input_delay_seconds}" - printf '%b' "${qemu_input}" - } | - timeout --foreground --kill-after=1s "${qemu_timeout_seconds}s" "${qemu_cmd[@]}" \ - >"${qemu_log}" 2>&1 - status=${PIPESTATUS[1]} + if [[ ${use_timeout} -eq 1 ]]; then + { + sleep "${qemu_input_delay_seconds}" + printf '%b' "${qemu_input}" + } | + timeout --foreground --kill-after=1s "${qemu_timeout_seconds}s" "${qemu_cmd[@]}" \ + >"${qemu_log}" 2>&1 + status=${PIPESTATUS[1]} + else + { + sleep "${qemu_input_delay_seconds}" + printf '%b' "${qemu_input}" + } | + "${qemu_cmd[@]}" >"${qemu_log}" 2>&1 + status=${PIPESTATUS[1]} + fi else - timeout --foreground --kill-after=1s "${qemu_timeout_seconds}s" "${qemu_cmd[@]}" \ - >"${qemu_log}" 2>&1 - status=$? + if [[ ${display_to_terminal} -eq 1 ]]; then + if [[ ${use_timeout} -eq 1 ]]; then + timeout --foreground --kill-after=1s "${qemu_timeout_seconds}s" "${qemu_cmd[@]}" 2>&1 | tee "${qemu_log}" + status=${PIPESTATUS[0]} + else + "${qemu_cmd[@]}" 2>&1 | tee "${qemu_log}" + status=${PIPESTATUS[0]} + fi + else + if [[ ${use_timeout} -eq 1 ]]; then + timeout --foreground --kill-after=1s "${qemu_timeout_seconds}s" "${qemu_cmd[@]}" \ + >"${qemu_log}" 2>&1 + status=$? + else + "${qemu_cmd[@]}" >"${qemu_log}" 2>&1 + status=$? + fi + fi fi set -e diff --git a/src/common.zig b/src/common.zig index 3428738..7fb20c8 100644 --- a/src/common.zig +++ b/src/common.zig @@ -8,6 +8,8 @@ pub const PAGE_SIZE: usize = 4096; pub const SYS_PUTCHAR: u32 = 1; pub const SYS_GETCHAR: u32 = 2; pub const SYS_EXIT: u32 = 3; +pub const SYS_READFILE: u32 = 4; +pub const SYS_WRITEFILE: u32 = 5; pub fn alignUp(value: usize, alignment: usize) usize { return (value + alignment - 1) & ~(alignment - 1); @@ -43,6 +45,14 @@ pub fn memcpy(dst: [*]u8, src: [*]const u8, len: size_t) [*]u8 { return dst; } +pub fn memEql(lhs: [*]const u8, rhs: []const u8) bool { + var index: usize = 0; + while (index < rhs.len) : (index += 1) { + if (lhs[index] != rhs[index]) return false; + } + return true; +} + pub fn strcpy(dst: [*]u8, src: [*:0]const u8) [*]u8 { @setRuntimeSafety(false); diff --git a/src/kernel/fs.zig b/src/kernel/fs.zig new file mode 100644 index 0000000..55ced14 --- /dev/null +++ b/src/kernel/fs.zig @@ -0,0 +1,167 @@ +const common = @import("common"); +const panic_mod = @import("panic.zig"); +const sbi = @import("sbi.zig"); +const virtio_blk = @import("virtio_blk.zig"); + +pub const FILES_MAX: usize = 4; +pub const FILE_DATA_MAX: usize = 1024; +pub const DISK_MAX_SIZE: usize = common.alignUp(@sizeOf(File) * FILES_MAX, virtio_blk.SECTOR_SIZE); + +pub const TarHeader = extern struct { + name: [100]u8, + mode: [8]u8, + uid: [8]u8, + gid: [8]u8, + size: [12]u8, + mtime: [12]u8, + checksum: [8]u8, + typeflag: u8, + linkname: [100]u8, + magic: [6]u8, + version: [2]u8, + uname: [32]u8, + gname: [32]u8, + devmajor: [8]u8, + devminor: [8]u8, + prefix: [155]u8, + padding: [12]u8, +}; + +pub const File = extern struct { + in_use: bool, + name: [100]u8, + data: [FILE_DATA_MAX]u8, + size: usize, +}; + +pub var files: [FILES_MAX]File = [_]File{.{ + .in_use = false, + .name = [_]u8{0} ** 100, + .data = [_]u8{0} ** FILE_DATA_MAX, + .size = 0, +}} ** FILES_MAX; +pub var disk: [DISK_MAX_SIZE]u8 = [_]u8{0} ** DISK_MAX_SIZE; + +pub fn oct2int(oct: [*]const u8, len: usize) usize { + var dec: usize = 0; + var index: usize = 0; + while (index < len) : (index += 1) { + if (oct[index] < '0' or oct[index] > '7') { + break; + } + dec = dec * 8 + (oct[index] - '0'); + } + return dec; +} + +pub fn init() void { + var sector: usize = 0; + while (sector < disk.len / virtio_blk.SECTOR_SIZE) : (sector += 1) { + virtio_blk.readWriteDisk(@ptrCast(&disk[sector * virtio_blk.SECTOR_SIZE]), @intCast(sector), false); + } + + var off: usize = 0; + var file_index: usize = 0; + while (file_index < FILES_MAX and off + @sizeOf(TarHeader) <= disk.len) : (file_index += 1) { + const header: *TarHeader = @ptrCast(&disk[off]); + if (header.name[0] == 0) { + break; + } + if (!common.memEql(&header.magic, "ustar")) { + panic_mod.PANIC(@src(), "invalid tar header: magic=\"%s\"", .{@as([*:0]const u8, @ptrCast(&header.magic))}); + } + + const file_size = oct2int(&header.size, header.size.len); + const file = &files[file_index]; + file.* = .{ + .in_use = true, + .name = [_]u8{0} ** 100, + .data = [_]u8{0} ** FILE_DATA_MAX, + .size = @min(file_size, FILE_DATA_MAX), + }; + _ = common.strcpy(&file.name, @ptrCast(&header.name)); + const data_offset = off + @sizeOf(TarHeader); + _ = common.memcpy(&file.data, @ptrCast(&disk[data_offset]), file.size); + common.printf(sbi.consolePutchar, "file: %s, size=%d\n", .{ @as([*:0]const u8, @ptrCast(&file.name)), @as(u32, @intCast(file.size)) }); + + off += common.alignUp(@sizeOf(TarHeader) + file_size, virtio_blk.SECTOR_SIZE); + } +} + +fn writeOctalField(field: []u8, value: usize) void { + _ = common.memset(field.ptr, '0', field.len); + if (field.len == 0) return; + + var index = field.len - 1; + field[index] = 0; + if (index == 0) return; + + var remaining = value; + while (index > 0) { + index -= 1; + field[index] = @intCast((remaining % 8) + '0'); + remaining /= 8; + if (remaining == 0) { + while (index > 0) { + index -= 1; + field[index] = '0'; + } + break; + } + } +} + +pub fn flush() void { + _ = common.memset(&disk, 0, disk.len); + + var off: usize = 0; + var file_index: usize = 0; + while (file_index < FILES_MAX) : (file_index += 1) { + const file = &files[file_index]; + if (!file.in_use) continue; + + const header: *TarHeader = @ptrCast(&disk[off]); + _ = common.memset(@ptrCast(header), 0, @sizeOf(TarHeader)); + _ = common.strcpy(&header.name, @ptrCast(&file.name)); + writeOctalField(&header.mode, 0o644); + writeOctalField(&header.uid, 0); + writeOctalField(&header.gid, 0); + writeOctalField(&header.size, file.size); + writeOctalField(&header.mtime, 0); + _ = common.strcpy(&header.magic, "ustar"); + _ = common.strcpy(&header.version, "00"); + header.typeflag = '0'; + + _ = common.memset(&header.checksum, ' ', header.checksum.len); + var checksum: usize = 0; + const header_bytes: [*]u8 = @ptrCast(header); + var header_index: usize = 0; + while (header_index < @sizeOf(TarHeader)) : (header_index += 1) { + checksum += header_bytes[header_index]; + } + _ = common.memset(&header.checksum, 0, header.checksum.len); + writeOctalField(header.checksum[0..6], checksum); + header.checksum[6] = 0; + header.checksum[7] = ' '; + + _ = common.memcpy(@ptrCast(&disk[off + @sizeOf(TarHeader)]), &file.data, file.size); + off += common.alignUp(@sizeOf(TarHeader) + file.size, virtio_blk.SECTOR_SIZE); + } + + var sector: usize = 0; + while (sector < disk.len / virtio_blk.SECTOR_SIZE) : (sector += 1) { + virtio_blk.readWriteDisk(@ptrCast(&disk[sector * virtio_blk.SECTOR_SIZE]), @intCast(sector), true); + } + common.printf(sbi.consolePutchar, "wrote %d bytes to disk\n", .{@as(u32, @intCast(disk.len))}); +} + +pub fn lookup(filename: [*:0]const u8) ?*File { + var index: usize = 0; + while (index < FILES_MAX) : (index += 1) { + const file = &files[index]; + if (file.in_use and common.strcmp(@ptrCast(&file.name), filename) == 0) { + return file; + } + } + return null; +} diff --git a/src/kernel/main.zig b/src/kernel/main.zig index 5b76eb3..74d7c18 100644 --- a/src/kernel/main.zig +++ b/src/kernel/main.zig @@ -1,5 +1,6 @@ const boot = @import("boot.zig"); const common = @import("common"); +const fs = @import("fs.zig"); const process = @import("process.zig"); const panic_mod = @import("panic.zig"); const riscv = @import("riscv.zig"); @@ -23,13 +24,7 @@ pub export fn kernel_main() noreturn { riscv.writeStvec(@intFromPtr(&trap.kernel_entry)); virtio_blk.init(); - var read_buf = [_]u8{0} ** virtio_blk.SECTOR_SIZE; - virtio_blk.readWriteDisk(&read_buf, 0, false); - common.printf(sbi.consolePutchar, "first sector: %s\n", .{@as([*:0]const u8, @ptrCast(&read_buf))}); - - var write_buf = [_]u8{0} ** virtio_blk.SECTOR_SIZE; - _ = common.strcpy(&write_buf, "hello from kernel!!!\n"); - virtio_blk.readWriteDisk(&write_buf, 0, true); + fs.init(); process.idle_proc = process.createProcess(null); process.idle_proc.pid = 0; diff --git a/src/kernel/process.zig b/src/kernel/process.zig index 947ecdb..8986495 100644 --- a/src/kernel/process.zig +++ b/src/kernel/process.zig @@ -142,7 +142,7 @@ fn userEntry() callconv(.naked) noreturn { \\ sret : : [sepc] "r" (paging.USER_BASE), - [sstatus] "r" (riscv.SSTATUS_SPIE), + [sstatus] "r" (riscv.SSTATUS_SPIE | riscv.SSTATUS_SUM), ); } diff --git a/src/kernel/trap.zig b/src/kernel/trap.zig index 77aab0b..cc6917b 100644 --- a/src/kernel/trap.zig +++ b/src/kernel/trap.zig @@ -1,4 +1,5 @@ const common = @import("common"); +const fs = @import("fs.zig"); const panic_mod = @import("panic.zig"); const process = @import("process.zig"); const riscv = @import("riscv.zig"); @@ -155,6 +156,26 @@ fn handleSyscall(frame: *TrapFrame) void { process.yield(); panic_mod.PANIC(@src(), "unreachable", .{}); }, + common.SYS_READFILE, common.SYS_WRITEFILE => { + const filename: [*:0]const u8 = @ptrFromInt(frame.a0); + const buf: [*]u8 = @ptrFromInt(frame.a1); + var len: usize = frame.a2; + const file = fs.lookup(filename) orelse { + common.printf(sbi.consolePutchar, "file not found: %s\n", .{filename}); + frame.a0 = @bitCast(@as(i32, -1)); + return; + }; + len = @min(len, file.size); + if (frame.a3 == common.SYS_WRITEFILE) { + len = @min(len, fs.FILE_DATA_MAX); + _ = common.memcpy(&file.data, buf, len); + file.size = len; + fs.flush(); + } else { + _ = common.memcpy(buf, &file.data, len); + } + frame.a0 = @intCast(len); + }, else => panic_mod.PANIC(@src(), "unexpected syscall a3=%x", .{frame.a3}), } } diff --git a/src/user/shell.zig b/src/user/shell.zig index d18f361..4b8ead1 100644 --- a/src/user/shell.zig +++ b/src/user/shell.zig @@ -29,11 +29,19 @@ pub export fn main() callconv(.c) noreturn { cmdline[index] = ch_u8; } - if (restart_prompt) { - continue; - } + if (restart_prompt) continue; + if (common.strcmp(@ptrCast(&cmdline), "hello") == 0) { common.printf(start_mod.putchar, "Hello world from shell!\n", .{}); + } else if (common.strcmp(@ptrCast(&cmdline), "readfile") == 0) { + var buf = [_]u8{0} ** 128; + const len = start_mod.readfile("hello.txt", &buf, buf.len - 1); + if (len >= 0) { + buf[@intCast(len)] = 0; + common.printf(start_mod.putchar, "%s\n", .{@as([*:0]const u8, @ptrCast(&buf))}); + } + } else if (common.strcmp(@ptrCast(&cmdline), "writefile") == 0) { + _ = start_mod.writefile("hello.txt", "Hello from shell!\n", 18); } else if (common.strcmp(@ptrCast(&cmdline), "exit") == 0) { start_mod.exit(); } else { diff --git a/src/user/start.zig b/src/user/start.zig index 306c710..d34e94c 100644 --- a/src/user/start.zig +++ b/src/user/start.zig @@ -21,6 +21,14 @@ pub fn getchar() i32 { return @bitCast(syscall(common.SYS_GETCHAR, 0, 0, 0)); } +pub fn readfile(filename: [*:0]const u8, buf: [*]u8, len: u32) i32 { + return @bitCast(syscall(common.SYS_READFILE, @intFromPtr(filename), @intFromPtr(buf), len)); +} + +pub fn writefile(filename: [*:0]const u8, buf: [*]const u8, len: u32) i32 { + return @bitCast(syscall(common.SYS_WRITEFILE, @intFromPtr(filename), @intFromPtr(buf), len)); +} + pub export fn exit() noreturn { _ = syscall(common.SYS_EXIT, 0, 0, 0); while (true) {