diff --git a/Makefile b/Makefile index 79284bb..8cda53a 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ RS = src/*.rs #TOOLPREFIX = i386-elf- # Using native tools (e.g., on X86 Linux) -#TOOLPREFIX = +#TOOLPREFIX = MAC_CCFLAGS := $(shell if [ "$(shell uname -s)" = "Darwin" ] && [ "$(shell uname -m)" = "arm64" ]; then \ echo "-Wno-error=infinite-recursion -Wno-error=array-bounds"; \ @@ -26,18 +26,9 @@ TOOLPREFIX := $(shell if i386-jos-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/d then echo 'i386-elf-'; \ else echo "***" 1>&2; \ echo "*** Error: Couldn't find an i386-*-elf version of GCC/binutils." 1>&2; \ - echo "*** Is the directory with i386-jos-elf-gcc in your PATH?" 1>&2; \ - echo "*** If your i386-*-elf toolchain is installed with a command" 1>&2; \ - echo "*** prefix other than 'i386-jos-elf-', set your TOOLPREFIX" 1>&2; \ - echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ - echo "*** To turn off this error, run 'gmake TOOLPREFIX= ...'." 1>&2; \ - echo "***" 1>&2; exit 1; fi) + exit 1; fi) endif -# If the makefile can't find QEMU, specify its path here -# QEMU = qemu-system-i386 - -# Try to infer the correct QEMU ifndef QEMU QEMU = $(shell if which qemu > /dev/null; \ then echo qemu; exit; \ @@ -45,14 +36,7 @@ QEMU = $(shell if which qemu > /dev/null; \ then echo qemu-system-i386; exit; \ elif which qemu-system-x86_64 > /dev/null; \ then echo qemu-system-x86_64; exit; \ - else \ - qemu=/Applications/Q.app/Contents/MacOS/i386-softmmu.app/Contents/MacOS/i386-softmmu; \ - if test -x $$qemu; then echo $$qemu; exit; fi; fi; \ - echo "***" 1>&2; \ - echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ - echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ - echo "*** or have you tried setting the QEMU variable in Makefile?" 1>&2; \ - echo "***" 1>&2; exit 1) + else echo "*** Error: Couldn't find QEMU." 1>&2; exit 1; fi) endif CC = $(TOOLPREFIX)gcc @@ -60,14 +44,14 @@ AS = $(TOOLPREFIX)gas LD = $(TOOLPREFIX)ld OBJCOPY = $(TOOLPREFIX)objcopy OBJDUMP = $(TOOLPREFIX)objdump + CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer CFLAGS += $(MAC_CCFLAGS) CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) + ASFLAGS = -m32 -gdwarf-2 -Wa,-divide -# FreeBSD ld wants ``elf_i386_fbsd'' LDFLAGS += -m $(shell $(LD) -V | grep elf_i386 2>/dev/null | head -n 1) -# Disable PIE when possible (for Ubuntu 16.10 toolchain) ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),) CFLAGS += -fno-pie -no-pie endif @@ -75,11 +59,20 @@ ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]nopie'),) CFLAGS += -fno-pie -nopie endif +# Disk image with bootblock + kernel xv6.img: bootblock kernel dd if=/dev/zero of=xv6.img count=10000 dd if=bootblock of=xv6.img conv=notrunc dd if=kernel of=xv6.img seek=1 conv=notrunc +# Build mkfs utility and create filesystem image +mkfs: src/mkfs.rs src/fs_h.rs + rustc --edition=2021 -W warnings -o mkfs src/mkfs.rs + + +fs.img: mkfs *.txt + ./mkfs fs.img *.txt + bootblock: bootasm.S bootmain.c linkers/bootblock.ld $(CC) $(CFLAGS) -fno-pic -O -nostdinc -I. -c bootmain.c $(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c bootasm.S @@ -88,10 +81,15 @@ bootblock: bootasm.S bootmain.c linkers/bootblock.ld $(OBJCOPY) -S -O binary bootblock.o bootblock kernel.a: $(RS) - cargo rustc -Z build-std=core -Z build-std-features=compiler-builtins-mem --target ./targets/i686-stage-3.json --lib --release -- -A warnings --emit link=kernel.a + cargo +nightly rustc \ + -Z build-std=core \ + -Z build-std-features=compiler-builtins-mem \ + --target ./targets/i686-stage-3.json \ + --lib --release \ + -- -A warnings --emit link=kernel.a kernel: kernel.a $(OBJS) ./linkers/kernel.ld - ld -m elf_i386 -T ./linkers/kernel.ld -o kernel $(OBJS) kernel.a + $(LD) -m elf_i386 -T ./linkers/kernel.ld -o kernel $(OBJS) kernel.a $(OBJDUMP) -S -D kernel > kernel.asm $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym @@ -103,35 +101,32 @@ vectors.S: vectors.pl # details: # http://www.gnu.org/software/make/manual/html_node/Chained-Rules.html .PRECIOUS: %.o - -include *.d -clean: +clean: rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ - *.a *.o *.d *.asm *.sym bootblock kernel xv6.img .gdbinit + *.a *.o *.d *.asm *.sym bootblock kernel xv6.img fs.img mkfs .gdbinit vectors.S cargo clean -# run in emulators -# try to generate a unique GDB port GDBPORT = $(shell expr `id -u` % 5000 + 25000) -# QEMU's gdb stub command line changed in 0.11 QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \ then echo "-gdb tcp::$(GDBPORT)"; \ else echo "-s -p $(GDBPORT)"; fi) + ifndef CPUS - CPUS := 1 +CPUS := 1 endif -# For debugging -# QEMUEXTRA = -no-reboot -d int,cpu_reset -QEMUOPTS = -drive file=xv6.img,index=0,media=disk,format=raw -smp $(CPUS) -m 512 $(QEMUEXTRA) +QEMUOPTS = -drive file=xv6.img,index=0,media=disk,format=raw \ + -drive file=fs.img,index=1,media=disk,format=raw \ + -smp $(CPUS) -m 512 $(QEMUEXTRA) -qemu: xv6.img +qemu: xv6.img fs.img $(QEMU) -nographic $(QEMUOPTS) .gdbinit: .gdbinit.tmpl sed "s/localhost:1234/localhost:$(GDBPORT)/" < $^ > $@ -qemu-gdb: xv6.img .gdbinit +qemu-gdb: xv6.img .gdbinit fs.img @echo "*** Now run 'gdb'." 1>&2 $(QEMU) -nographic $(QEMUOPTS) -S $(QEMUGDB) diff --git a/main.c b/main.c deleted file mode 100644 index 808545d..0000000 --- a/main.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "types.h" -#include "x86.h" - -extern char end[]; // first address after kernel loaded from ELF file - -int -halt(void) -{ - outw(0x604, 0x2000); - // For older versions of QEMU, - outw(0xB004, 0x2000); - return 0; -} - -// Bootstrap processor starts running C code here. -int -main(void) -{ - halt(); -} \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5d56faf..87b5402 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,3 @@ [toolchain] channel = "nightly" +components = ["rust-src"] \ No newline at end of file diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..079b0db --- /dev/null +++ b/src/Makefile @@ -0,0 +1,130 @@ +OBJS = entry.o vectors.o trapasm.o +RS = src/*.rs + +# Cross-compiling (e.g., on Mac OS X) +#TOOLPREFIX = i386-jos-elf +#TOOLPREFIX = i386-elf- + +# Using native tools (e.g., on X86 Linux) +#TOOLPREFIX = + +# Try to infer the correct TOOLPREFIX if not set +ifndef TOOLPREFIX +TOOLPREFIX := $(shell if i386-jos-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-jos-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-*-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-jos-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-*-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-jos-elf-', set your TOOLPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake TOOLPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# If the makefile can't find QEMU, specify its path here +# QEMU = qemu-system-i386 + +# Try to infer the correct QEMU +ifndef QEMU +QEMU = $(shell if which qemu > /dev/null; \ + then echo qemu; exit; \ + elif which qemu-system-i386 > /dev/null; \ + then echo qemu-system-i386; exit; \ + elif which qemu-system-x86_64 > /dev/null; \ + then echo qemu-system-x86_64; exit; \ + else \ + qemu=/Applications/Q.app/Contents/MacOS/i386-softmmu.app/Contents/MacOS/i386-softmmu; \ + if test -x $$qemu; then echo $$qemu; exit; fi; fi; \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "*** or have you tried setting the QEMU variable in Makefile?" 1>&2; \ + echo "***" 1>&2; exit 1) +endif + +CC = $(TOOLPREFIX)gcc +AS = $(TOOLPREFIX)gas +LD = $(TOOLPREFIX)ld +OBJCOPY = $(TOOLPREFIX)objcopy +OBJDUMP = $(TOOLPREFIX)objdump +CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +ASFLAGS = -m32 -gdwarf-2 -Wa,-divide +LDFLAGS += -m $(shell $(LD) -V | grep elf_i386 2>/dev/null | head -n 1) + +# Disable PIE when possible (for Ubuntu 16.10 toolchain) +ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),) +CFLAGS += -fno-pie -no-pie +endif +ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]nopie'),) +CFLAGS += -fno-pie -nopie +endif + +# Disk image with bootblock + kernel +xv6.img: bootblock kernel + dd if=/dev/zero of=xv6.img count=10000 + dd if=bootblock of=xv6.img conv=notrunc + dd if=kernel of=xv6.img seek=1 conv=notrunc + +mkfs: ../../col331/mkfs.c ../../col331/fs.h ../../col331/types.h ../../col331/stat.h ../../col331/param.h + gcc -Werror -Wall -o mkfs ../../col331/mkfs.c + +fs: fs.img + +fs.img: mkfs ../welcome.txt + ./mkfs fs.img ../welcome.txt + +bootblock: bootasm.S bootmain.c + $(CC) $(CFLAGS) -fno-pic -O -nostdinc -I. -c bootmain.c + $(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c bootasm.S + $(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o + $(OBJDUMP) -S -D bootblock.o > bootblock.asm + $(OBJCOPY) -S -O binary -j .text bootblock.o bootblock + perl sign.pl bootblock + +kernel.a: $(RS) + cargo rustc -Z build-std=core -Z build-std-features=compiler-builtins-mem -Z json-target-spec --target ./targets/i686.json --lib --release -- -A warnings --emit link=kernel.a + + +kernel: kernel.a $(OBJS) ./linkers/kernel.ld + ld -m elf_i386 -T ./linkers/kernel.ld -o kernel $(OBJS) kernel.a + $(OBJDUMP) -S -D kernel > kernel.asm + $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym + +vectors.S: vectors.pl + ./vectors.pl > vectors.S + +.PRECIOUS: %.o +-include *.d + +clean: + rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ + *.a *.o *.d *.asm *.sym bootblock kernel xv6.img fs.img .gdbinit vectors.S mkfs + rm -rf target + +# run in emulators +GDBPORT = $(shell expr `id -u` % 5000 + 25000) +QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \ + then echo "-gdb tcp::$(GDBPORT)"; \ + else echo "-s -p $(GDBPORT)"; fi) +ifndef CPUS + CPUS := 1 +endif + +# Attach fs.img as disk1 (index=1), like the C version +QEMUOPTS = -drive file=xv6.img,index=0,media=disk,format=raw \ + -drive file=fs.img,index=1,media=disk,format=raw \ + -smp $(CPUS) -m 512 $(QEMUEXTRA) + +qemu: xv6.img fs.img + $(QEMU) -nographic $(QEMUOPTS) + +.gdbinit: .gdbinit.tmpl + sed "s/localhost:1234/localhost:$(GDBPORT)/" < $^ > $@ + +qemu-gdb: xv6.img fs .gdbinit + @echo "*** Now run 'gdb'." 1>&2 + $(QEMU) -nographic $(QEMUOPTS) -S $(QEMUGDB) diff --git a/src/bio.rs b/src/bio.rs new file mode 100644 index 0000000..30e6d81 --- /dev/null +++ b/src/bio.rs @@ -0,0 +1,148 @@ +use core::sync::atomic::Ordering; + +use crate::buf::{Buf, B_DIRTY, B_VALID, NBUF}; + +const HEAD: usize = NBUF; // sentinel index + +struct BCache { + buf: [Buf; NBUF], + head_prev: usize, + head_next: usize, +} + +impl BCache { + pub const fn new() -> Self { + Self { + buf: [const { Buf::new() }; NBUF], + head_prev: HEAD, + head_next: HEAD, + } + } +} + +static mut BCACHE: BCache = BCache::new(); + +pub fn binit() { + unsafe { + // empty list + BCACHE.head_prev = HEAD; + BCACHE.head_next = HEAD; + + // insert all buffers at head (MRU side) + for i in 0..NBUF { + insert_at_head(i); + } + } +} + +#[inline] +fn insert_at_head(i: usize) { + unsafe { + let first = BCACHE.head_next; + + BCACHE.buf[i].prev = HEAD; + BCACHE.buf[i].next = first; + + if first == HEAD { + // list was empty + BCACHE.head_prev = i; + } else { + BCACHE.buf[first].prev = i; + } + + BCACHE.head_next = i; + } +} + +#[inline] +fn remove_from_list(i: usize) { + unsafe { + let prev = BCACHE.buf[i].prev; + let next = BCACHE.buf[i].next; + + if prev == HEAD { + BCACHE.head_next = next; + } else { + BCACHE.buf[prev].next = next; + } + + if next == HEAD { + BCACHE.head_prev = prev; + } else { + BCACHE.buf[next].prev = prev; + } + } +} + +// Return mutable buf by index. +// Safe to call only when you “own” the buffer logically (like xv6 “locked buf”). +pub fn buf_mut(idx: usize) -> &'static mut Buf { + unsafe { &mut BCACHE.buf[idx] } +} + +// Look for cached block; else recycle an unused non-dirty buffer. +fn bget(dev: u32, blockno: u32) -> usize { + unsafe { + // Is the block already cached? + let mut b = BCACHE.head_next; + while b != HEAD { + if BCACHE.buf[b].dev == dev && BCACHE.buf[b].blockno == blockno { + BCACHE.buf[b].refcnt += 1; + return b; + } + b = BCACHE.buf[b].next; + } + + // Not cached; recycle from LRU end. + let mut b = BCACHE.head_prev; + while b != HEAD { + let flags = BCACHE.buf[b].flags.load(Ordering::Acquire); + if BCACHE.buf[b].refcnt == 0 && (flags & B_DIRTY) == 0 { + BCACHE.buf[b].dev = dev; + BCACHE.buf[b].blockno = blockno; + BCACHE.buf[b].flags.store(0, Ordering::Release); + BCACHE.buf[b].refcnt = 1; + BCACHE.buf[b].qnext = None; + return b; + } + b = BCACHE.buf[b].prev; + } + + panic!("bget: no buffers"); + } +} + +// Return buffer index with contents of block. +pub fn bread(dev: u32, blockno: u32) -> usize { + let idx = bget(dev, blockno); + + let flags = buf_mut(idx).flags.load(Ordering::Acquire); + if (flags & B_VALID) == 0 { + crate::ide::iderw(idx); + } + + idx +} + +// Mark dirty + write to disk. +pub fn bwrite(idx: usize) { + let b = buf_mut(idx); + b.flags.fetch_or(B_DIRTY, Ordering::AcqRel); + crate::ide::iderw(idx); +} + +// Release buffer. If refcnt hits 0, move to MRU head. +pub fn brelse(idx: usize) { + unsafe { + let b = &mut BCACHE.buf[idx]; + if b.refcnt == 0 { + panic!("brelse: refcnt underflow"); + } + + b.refcnt -= 1; + if b.refcnt == 0 { + remove_from_list(idx); + insert_at_head(idx); + } + } +} \ No newline at end of file diff --git a/src/buf.rs b/src/buf.rs new file mode 100644 index 0000000..3ffce79 --- /dev/null +++ b/src/buf.rs @@ -0,0 +1,41 @@ +use core::sync::atomic::AtomicU32; + +// Re-export filesystem constants from constants.rs for convenience +// (other modules currently import BSIZE and NBUF from buf) +pub use crate::constants::{BSIZE, NBUF}; + +// Buffer flags +pub const B_VALID: u32 = 0x2; // buffer has been read from disk +pub const B_DIRTY: u32 = 0x4; // buffer needs to be written to disk + +#[repr(C)] +pub struct Buf { + pub flags: AtomicU32, + pub dev: u32, + pub blockno: u32, + pub refcnt: u32, + + // LRU list (intrusive, by index) + pub prev: usize, + pub next: usize, + + // disk queue (by index) + pub qnext: Option, + + pub data: [u8; BSIZE], +} + +impl Buf { + pub const fn new() -> Self { + Self { + flags: AtomicU32::new(0), + dev: 0, + blockno: 0, + refcnt: 0, + prev: 0, + next: 0, + qnext: None, + data: [0; BSIZE], + } + } +} diff --git a/src/console.rs b/src/console.rs index 03f5920..bd0c2c6 100644 --- a/src/console.rs +++ b/src/console.rs @@ -5,12 +5,68 @@ pub struct Console {} impl Write for Console { fn write_str(&mut self, s: &str) -> Result { for c in s.chars() { - consputc(c); + consputc(c as u8); } Ok(()) } } +const BACKSPACE: u8 = '\x08' as u8; +const INPUT_BUF: usize = 128; + +const fn ctrl(x: char) -> u8 { // Control-x + (x as u8) - ('@' as u8) +} + +#[derive(Clone, Copy)] +struct Input { + buf: [u8; INPUT_BUF], + r: usize, + w: usize, + e: usize, +} + +static mut INPUT: Input = Input { + buf: [0; INPUT_BUF], + r: 0, + w: 0, + e: 0, +}; + +pub fn consoleintr(getc: fn() -> Option) { + loop { + let c_opt = getc(); + let input = unsafe { &mut INPUT }; + + if let Some(c) = c_opt { + match c { + x if x == ctrl('U') => { + while input.e != input.w && input.buf[(input.e - 1) % INPUT_BUF] != b'\n' { + input.e -= 1; + consputc(BACKSPACE); + } + } + x if x == ctrl('H') || x == 0x7f => { + if input.e != input.w { + input.e -= 1; + consputc(BACKSPACE); + } + } + _ => { + if c != 0 && input.e.wrapping_sub(input.r) < INPUT_BUF { + let c = if c == b'\r' { b'\n' } else { c }; + input.buf[input.e % INPUT_BUF] = c as u8; + input.e += 1; + consputc(c); + if c == b'\n' || c == ctrl('D') || input.e == input.r + INPUT_BUF { + input.w = input.e; + } + } + } + } + } + } +} #[macro_export] macro_rules! println { @@ -22,14 +78,12 @@ macro_rules! println { }); } -const BACKSPACE: char = '\x08'; - -fn consputc(c: char) { - if c == BACKSPACE { - uartputc(BACKSPACE); - uartputc(' '); - uartputc(BACKSPACE); - } else { - uartputc(c); - } +pub fn consputc(c: u8) { + if c == BACKSPACE { + uartputc(BACKSPACE as char); + uartputc(' '); + uartputc(BACKSPACE as char); + } else { + uartputc(c as char); + } } diff --git a/src/constants.rs b/src/constants.rs index d06b826..9b86c8d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -51,17 +51,13 @@ pub const T_SIMDERR: u32 = 19; // SIMD floating point error // processor defined exceptions or interrupt vectors pub const T_SYSCALL: u32 = 64; // system call pub const T_DEFAULT: u32 = 500; // catchall - -pub const T_IRQ0: u32 = 32; // IRQ 0 corresponds to int T_IRQ - +pub const T_IRQ0: u32 = 32; pub const IRQ_TIMER: u32 = 0; pub const IRQ_KBD: u32 = 1; pub const IRQ_COM1: u32 = 4; pub const IRQ_IDE: u32 = 14; pub const IRQ_ERROR: u32 = 19; pub const IRQ_SPURIOUS: u32 = 31; - - // ------------------------------------------------------ MP RELATED ------------------------------------------------------- // Processor flags @@ -116,4 +112,17 @@ pub const X1: u32 = 0x0000000B; // divide counts by 1 pub const PERIODIC: u32 = 0x00020000; // Periodic // Error handling -pub const MASKED: u32 = 0x00010000; // Interrupt masked \ No newline at end of file +pub const MASKED: u32 = 0x00010000; // Interrupt masked + +pub use crate::fs_h::{ + BPB, BSIZE, DINODE_SIZE, DIRSIZ, FSSIZE, IPB, LOGSIZE, MAXFILE, NDIRECT, NINDIRECT, NINODES, + ROOTINO, T_DEV, T_DIR, T_FILE, +}; + +// ------------------------------------------------------ SYSTEM PARAMETERS (param.h) ---------------------------------------------- +// System-wide parameters + +pub const MAXOPBLOCKS: usize = 10; // max # of blocks any FS op writes +pub const NINODE: usize = 50; // maximum number of active i-nodes +pub const ROOTDEV: u32 = 1; // device number of file system root disk +pub const NBUF: usize = MAXOPBLOCKS * 3; // size of disk block cache diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..7370890 --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,270 @@ +use core::cmp::min; + +use crate::bio; +use crate::buf::BSIZE; +use crate::fs_h::{self, Superblock, DINODE_SIZE, DIRSIZ, IPB, NDIRECT, NINDIRECT}; +use crate::param::NINODE; +use crate::println; + +pub use crate::fs_h::{Dirent, ROOTINO}; +pub const DIRENT_SIZE: usize = core::mem::size_of::(); + +#[repr(C)] +pub struct Inode { + pub dev: u32, + pub inum: u32, + pub refcnt: i32, + pub valid: i32, + + pub type_: i16, + pub major: i16, + pub minor: i16, + pub nlink: i16, + pub size: u32, + pub addrs: [u32; NDIRECT + 1], +} + +impl Inode { + pub const fn new() -> Self { + Self { + dev: 0, + inum: 0, + refcnt: 0, + valid: 0, + type_: 0, + major: 0, + minor: 0, + nlink: 0, + size: 0, + addrs: [0; NDIRECT + 1], + } + } +} + +#[repr(C)] +pub struct Stat { + pub type_: i16, + pub dev: i32, + pub ino: u32, + pub nlink: i16, + pub size: u32, +} + +impl Stat { + pub const fn new() -> Self { + Self { + type_: 0, + dev: 0, + ino: 0, + nlink: 0, + size: 0, + } + } +} + +struct ICache { + inode: [Inode; NINODE], +} + +impl ICache { + const fn new() -> Self { + Self { + inode: [const { Inode::new() }; NINODE], + } + } +} + +static mut SB: Superblock = Superblock::new(); +static mut ICACHE: ICache = ICache::new(); + +#[inline] +fn read_u16_le(data: &[u8], off: usize) -> u16 { + u16::from_le_bytes([data[off], data[off + 1]]) +} + +#[inline] +fn read_i16_le(data: &[u8], off: usize) -> i16 { + i16::from_le_bytes([data[off], data[off + 1]]) +} + +#[inline] +fn read_u32_le(data: &[u8], off: usize) -> u32 { + u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) +} + +#[inline] +fn iblock(inum: u32, sb: &Superblock) -> u32 { + fs_h::iblock(inum, sb) +} + +pub fn parse_dirent(raw: &[u8]) -> Dirent { + let mut de = Dirent::new(); + de.inum = read_u16_le(raw, 0); + de.name.copy_from_slice(&raw[2..2 + DIRSIZ]); + de +} + +pub fn readsb(dev: u32, sb: &mut Superblock) { + let bp = bio::bread(dev, 1); + let data = &bio::buf_mut(bp).data; + + sb.size = read_u32_le(data, 0); + sb.nblocks = read_u32_le(data, 4); + sb.ninodes = read_u32_le(data, 8); + sb.nlog = read_u32_le(data, 12); + sb.logstart = read_u32_le(data, 16); + sb.inodestart = read_u32_le(data, 20); + sb.bmapstart = read_u32_le(data, 24); + + bio::brelse(bp); +} + +pub fn iinit(dev: u32) { + unsafe { + readsb(dev, &mut *(&raw mut SB)); + let sb = &*(&raw const SB); + println!( + "sb: size {} nblocks {} ninodes {} nlog {} logstart {} inodestart {} bmap start {}", + sb.size, sb.nblocks, sb.ninodes, sb.nlog, sb.logstart, sb.inodestart, sb.bmapstart + ); + } +} + +pub fn iget(dev: u32, inum: u32) -> usize { + unsafe { + let mut empty: Option = None; + + for i in 0..NINODE { + let ip = &mut ICACHE.inode[i]; + + if ip.refcnt > 0 && ip.dev == dev && ip.inum == inum { + ip.refcnt += 1; + return i; + } + + if empty.is_none() && ip.refcnt == 0 { + empty = Some(i); + } + } + + let idx = empty.unwrap_or_else(|| panic!("iget: no inodes")); + let ip = &mut ICACHE.inode[idx]; + + ip.dev = dev; + ip.inum = inum; + ip.refcnt = 1; + ip.valid = 0; + + idx + } +} + +pub fn iread(idx: usize) { + unsafe { + if idx >= NINODE { + panic!("iread: bad inode index"); + } + + let ip = &mut ICACHE.inode[idx]; + if ip.refcnt < 1 { + panic!("iread"); + } + + if ip.valid == 0 { + let bp = bio::bread(ip.dev, iblock(ip.inum, &*(&raw const SB))); + let data = &bio::buf_mut(bp).data; + + let off = (ip.inum % (IPB as u32)) as usize * DINODE_SIZE; + ip.type_ = read_i16_le(data, off); + ip.major = read_i16_le(data, off + 2); + ip.minor = read_i16_le(data, off + 4); + ip.nlink = read_i16_le(data, off + 6); + ip.size = read_u32_le(data, off + 8); + + for i in 0..(NDIRECT + 1) { + ip.addrs[i] = read_u32_le(data, off + 12 + (i * 4)); + } + + bio::brelse(bp); + + ip.valid = 1; + if ip.type_ == 0 { + panic!("iread: no type"); + } + } + } +} + +fn bmap(ip: &Inode, bn: u32) -> u32 { + if (bn as usize) < NDIRECT { + return ip.addrs[bn as usize]; + } + + let bn = bn - (NDIRECT as u32); + if (bn as usize) < NINDIRECT { + let bp = bio::bread(ip.dev, ip.addrs[NDIRECT]); + let addr = { + let data = &bio::buf_mut(bp).data; + read_u32_le(data, (bn as usize) * 4) + }; + bio::brelse(bp); + return addr; + } + + panic!("bmap: out of range"); +} + +pub fn stati(idx: usize, st: &mut Stat) { + unsafe { + if idx >= NINODE { + panic!("stati: bad inode index"); + } + + let ip = &ICACHE.inode[idx]; + st.dev = ip.dev as i32; + st.ino = ip.inum; + st.type_ = ip.type_; + st.nlink = ip.nlink; + st.size = ip.size; + } +} + +pub fn readi(idx: usize, dst: &mut [u8], off: u32, n: u32) -> i32 { + unsafe { + if idx >= NINODE { + panic!("readi: bad inode index"); + } + + let ip = &ICACHE.inode[idx]; + if off > ip.size || off.checked_add(n).is_none() { + return -1; + } + + let mut n = n; + if off + n > ip.size { + n = ip.size - off; + } + + if (n as usize) > dst.len() { + panic!("readi: destination too small"); + } + + let mut tot: u32 = 0; + let mut cur_off = off; + + while tot < n { + let bp = bio::bread(ip.dev, bmap(ip, cur_off / (BSIZE as u32))); + + let m = min((n - tot) as usize, BSIZE - (cur_off as usize % BSIZE)); + let boff = cur_off as usize % BSIZE; + dst[tot as usize..tot as usize + m] + .copy_from_slice(&bio::buf_mut(bp).data[boff..boff + m]); + bio::brelse(bp); + + tot += m as u32; + cur_off += m as u32; + } + + n as i32 + } +} diff --git a/src/fs_h.rs b/src/fs_h.rs new file mode 100644 index 0000000..4ae4cf1 --- /dev/null +++ b/src/fs_h.rs @@ -0,0 +1,124 @@ +use core::mem::size_of; + +// On-disk file system format. +// Both the kernel and mkfs now use these definitions ! + +// Disk layout: +// [ boot block | super block | log | inode blocks | free bit map | data blocks ] +// +// mkfs computes the super block and builds an initial file system. The +// super block describes the disk layout: + +pub const ROOTINO: u32 = 1; // root i-number +pub const BSIZE: usize = 512; // block size + +pub const NDIRECT: usize = 12; // number of direct block pointers +pub const NINDIRECT: usize = BSIZE / size_of::(); // indirect pointers per block +pub const MAXFILE: usize = NDIRECT + NINDIRECT; // max file size in blocks + +pub const DIRSIZ: usize = 14; // directory name length + +pub const DINODE_SIZE: usize = size_of::(); +pub const IPB: usize = BSIZE / DINODE_SIZE; // inodes per block +pub const BPB: usize = BSIZE * 8; // bitmap bits per block + +pub const FSSIZE: u32 = 1000; // size of file system in blocks +pub const NINODES: u32 = 200; // number of inodes +pub const LOGSIZE: u32 = 0; // max data blocks in on-disk log + +pub const T_DIR: u16 = 1; // directory +pub const T_FILE: u16 = 2; // file +#[allow(dead_code)] +pub const T_DEV: u16 = 3; // device + +#[allow(dead_code)] +#[inline] +pub fn iblock(inum: u32, sb: &Superblock) -> u32 { + inum / (IPB as u32) + sb.inodestart +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Superblock { + pub size: u32, + pub nblocks: u32, + pub ninodes: u32, + pub nlog: u32, + pub logstart: u32, + pub inodestart: u32, + pub bmapstart: u32, +} + +impl Superblock { + pub const fn new() -> Self { + Self { + size: 0, + nblocks: 0, + ninodes: 0, + nlog: 0, + logstart: 0, + inodestart: 0, + bmapstart: 0, + } + } +} + +impl Default for Superblock { + fn default() -> Self { + Self::new() + } +} + +// On-disk inode structure. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Dinode { + pub typ: u16, + pub major: u16, + pub minor: u16, + pub nlink: u16, + pub size: u32, + pub addrs: [u32; NDIRECT + 1], +} + +impl Dinode { + pub const fn new() -> Self { + Self { + typ: 0, + major: 0, + minor: 0, + nlink: 0, + size: 0, + addrs: [0; NDIRECT + 1], + } + } +} + +impl Default for Dinode { + fn default() -> Self { + Self::new() + } +} + +// Directory is a file containing a sequence of dirent structures. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Dirent { + pub inum: u16, + pub name: [u8; DIRSIZ], +} + +impl Dirent { + pub const fn new() -> Self { + Self { + inum: 0, + name: [0; DIRSIZ], + } + } +} + +impl Default for Dirent { + fn default() -> Self { + Self::new() + } +} diff --git a/src/ide.rs b/src/ide.rs new file mode 100644 index 0000000..6822a78 --- /dev/null +++ b/src/ide.rs @@ -0,0 +1,177 @@ +use core::sync::atomic::Ordering; +use crate::constants::{BSIZE, FSSIZE}; +use crate::buf::{B_DIRTY, B_VALID}; +use crate::x86; + +const SECTOR_SIZE: usize = 512; + +const IDE_BSY: u8 = 0x80; +const IDE_DRDY: u8 = 0x40; +const IDE_DF: u8 = 0x20; +const IDE_ERR: u8 = 0x01; + +const IDE_CMD_READ: u8 = 0x20; +const IDE_CMD_WRITE: u8 = 0x30; +const IDE_CMD_RDMUL: u8 = 0xC4; +const IDE_CMD_WRMUL: u8 = 0xC5; + +static mut IDEQUEUE: Option = None; +static mut HAVEDISK1: bool = false; + +fn idewait(checkerr: bool) -> i32 { + let mut r: u8; + loop { + r = x86::inb(0x1F7); + if (r & (IDE_BSY | IDE_DRDY)) == IDE_DRDY { + break; + } + } + if checkerr && (r & (IDE_DF | IDE_ERR)) != 0 { + return -1; + } + 0 +} + +pub fn ideinit() { + // Route IDE IRQ somewhere; simplest is CPU 0 for now. + crate::ioapic::ioapic_enable(crate::constants::IRQ_IDE, 0); + + idewait(false); + + // Check if disk 1 is present + unsafe { + x86::outb(0x1F6, 0xE0 | (1 << 4)); + for _ in 0..1000 { + if x86::inb(0x1F7) != 0 { + HAVEDISK1 = true; + break; + } + } + // Switch back to disk 0 + x86::outb(0x1F6, 0xE0 | (0 << 4)); + } +} + +fn idestart(idx: usize) { + let b = crate::bio::buf_mut(idx); + + if b.blockno >= FSSIZE { + panic!("idestart: incorrect blockno"); + } + + let sector_per_block = BSIZE / SECTOR_SIZE; // usually 1 + let sector = (b.blockno as usize) * sector_per_block; + + let read_cmd = if sector_per_block == 1 { IDE_CMD_READ } else { IDE_CMD_RDMUL }; + let write_cmd = if sector_per_block == 1 { IDE_CMD_WRITE } else { IDE_CMD_WRMUL }; + + if sector_per_block > 7 { + panic!("idestart: sector_per_block > 7"); + } + + idewait(false); + x86::outb(0x3F6, 0); // generate interrupt + + x86::outb(0x1F2, sector_per_block as u8); // number of sectors + x86::outb(0x1F3, (sector & 0xFF) as u8); + x86::outb(0x1F4, ((sector >> 8) & 0xFF) as u8); + x86::outb(0x1F5, ((sector >> 16) & 0xFF) as u8); + x86::outb( + 0x1F6, + 0xE0 | (((b.dev & 1) as u8) << 4) | (((sector >> 24) & 0x0F) as u8), + ); + + let flags = b.flags.load(Ordering::Acquire); + if (flags & B_DIRTY) != 0 { + x86::outb(0x1F7, write_cmd); + + // write BSIZE bytes as u32 words + unsafe { + x86::outsl(0x1F0, b.data.as_ptr() as *const u32, BSIZE / 4); + } + } else { + x86::outb(0x1F7, read_cmd); + } +} + +// Interrupt handler. +pub fn ideintr() { + let idx = unsafe { + match IDEQUEUE { + None => return, + Some(i) => { + let b = crate::bio::buf_mut(i); + IDEQUEUE = b.qnext; + b.qnext = None; + i + } + } + }; + + let b = crate::bio::buf_mut(idx); + + let flags = b.flags.load(Ordering::Acquire); + + // Read data if needed. + if (flags & B_DIRTY) == 0 && idewait(true) >= 0 { + unsafe { + x86::insl(0x1F0, b.data.as_mut_ptr() as *mut u32, BSIZE / 4); + } + } + + b.flags.fetch_or(B_VALID, Ordering::AcqRel); + b.flags.fetch_and(!B_DIRTY, Ordering::AcqRel); + + // Start next buffer in queue. + unsafe { + if let Some(next) = IDEQUEUE { + idestart(next); + } + } +} + +// Sync buf with disk. +// If B_DIRTY is set, write buf to disk, clear B_DIRTY, set B_VALID. +// Else if B_VALID is not set, read buf from disk, set B_VALID. +pub fn iderw(idx: usize) { + let b = crate::bio::buf_mut(idx); + + let flags = b.flags.load(Ordering::Acquire); + if (flags & (B_VALID | B_DIRTY)) == B_VALID { + panic!("iderw: nothing to do"); + } + + unsafe { + if b.dev != 0 && !HAVEDISK1 { + panic!("iderw: ide disk 1 not present"); + } + } + + // Append b to idequeue. + b.qnext = None; + unsafe { + let mut pp: *mut Option = &raw mut IDEQUEUE; + while let Some(next_idx) = *pp { + pp = &raw mut crate::bio::buf_mut(next_idx).qnext; + } + *pp = Some(idx); + } + + // Start disk if necessary. + unsafe { + if IDEQUEUE == Some(idx) { + idestart(idx); + } + } + + // Wait for request to finish. + // The interrupt handler will set B_VALID when done. + loop { + let flags = b.flags.load(Ordering::Acquire); + if (flags & (B_VALID | B_DIRTY)) == B_VALID { + break; + } + // Force compiler to re-read b->flags which is modified by ideintr() + x86::noop(); + } +} diff --git a/src/ioapic.rs b/src/ioapic.rs index 15cf837..eaf9d70 100644 --- a/src/ioapic.rs +++ b/src/ioapic.rs @@ -1,7 +1,7 @@ use core::ptr::{read_volatile, write_volatile}; use crate::mp::MP_ONCE; -use crate::console::Console; -use core::fmt::Write; +// use crate::console::Console; +// use core::fmt::Write; use crate::println; // I/O APIC default physical address @@ -14,9 +14,9 @@ const REG_TABLE: u32 = 0x10; // Redirection table base (0x10 / 4) // Redirection table configuration bits const INT_DISABLED: u32 = 0x00010000; // Interrupt disabled -const INT_LEVEL: u32 = 0x00008000; // Level-triggered -const INT_ACTIVELOW: u32 = 0x00002000; // Active low -const INT_LOGICAL: u32 = 0x00000800; // Destination is CPU ID +// const INT_LEVEL: u32 = 0x00008000; // Unused in p3 - Level-triggered +// const INT_ACTIVELOW: u32 = 0x00002000; // Unused in p3 - Active low +// const INT_LOGICAL: u32 = 0x00000800; // Unused in p3 - Destination is CPU ID pub const T_IRQ0: u32 = 32; diff --git a/src/lapic.rs b/src/lapic.rs index 1ce725f..fc3dbca 100644 --- a/src/lapic.rs +++ b/src/lapic.rs @@ -1,6 +1,6 @@ use core::ptr::{read_volatile, write_volatile}; use crate::mp::MP_ONCE; -use crate::traps::{T_IRQ0, IRQ_TIMER, IRQ_SPURIOUS, IRQ_ERROR}; +use crate::constants::{IRQ_ERROR, IRQ_SPURIOUS, IRQ_TIMER, T_IRQ0}; const ID: isize = 0x0020 / 4; const VER: isize = 0x0030 / 4; @@ -14,14 +14,14 @@ const ESR: isize = 0x0280 / 4; const ICRLO: isize = 0x0300 / 4; const INIT: u32 = 0x00000500; -const STARTUP: u32 = 0x00000600; +// const STARTUP: u32 = 0x00000600; // Unused in p3 - for AP startup const DELIVS: u32 = 0x00001000; -const ASSERT: u32 = 0x00004000; -const DEASSERT: u32 = 0x00000000; +// const ASSERT: u32 = 0x00004000; // Unused in p3 - for IPI +// const DEASSERT: u32 = 0x00000000; // Unused in p3 - for IPI const LEVEL: u32 = 0x00008000; const BCAST: u32 = 0x00080000; -const BUSY: u32 = 0x00001000; -const FIXED: u32 = 0x00000000; +// const BUSY: u32 = 0x00001000; // Unused in p3 +// const FIXED: u32 = 0x00000000; // Unused in p3 const ICRHI: isize = 0x0310 / 4; const TIMER: isize = 0x0320 / 4; @@ -35,7 +35,7 @@ const ERROR: isize = 0x0370 / 4; const MASKED: u32 = 0x00010000; const TICR: isize = 0x0380 / 4; -const TCCR: isize = 0x0390 / 4; +// const TCCR: isize = 0x0390 / 4; // Unused in p3 - timer current count const TDCR: isize = 0x03E0 / 4; // Volatile write to LAPIC @@ -70,7 +70,7 @@ pub fn lapicinit() { // TICR would be calibrated using an external time source. lapicw(TDCR, X1); lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER)); - lapicw(TICR, 1000000000); + lapicw(TICR, 10000000); // Disable logical interrupt lines. diff --git a/src/main.rs b/src/main.rs index 91bce05..bd399ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ #![no_std] // No standard library #![no_main] // No main function +#![allow(dead_code)] use core::panic::PanicInfo; +use crate::x86::cli; +use crate::lapic::lapicid; mod param; mod x86; @@ -13,20 +16,58 @@ mod picirq; mod mp; mod proc; mod traps; -mod constants; - +mod fs_h; +mod constants; // Internal use only - no external crates +mod fs; // Internal use only - filesystem structures +mod buf; +mod bio; +mod ide; use crate::traps::*; fn halt() -> ! { println!("Bye COL{}\n\0", 331); loop { - x86::outw(0x604, 0x2000); - x86::outw(0xB004, 0x2000); + x86::outw(0x602, 0x2000); + x86::outw(0xB002, 0x2000); } } +fn welcome() { + const NDIR_READ: usize = 4; + + let root = fs::iget(param::ROOTDEV, fs::ROOTINO); + fs::iread(root); + + let mut raw_entries = [0u8; fs::DIRENT_SIZE * NDIR_READ]; + let entries_len = raw_entries.len() as u32; + let n = fs::readi(root, &mut raw_entries, 0, entries_len); + println!("Read {} bytes from inode of root directory", n); + + let mut entries = [fs::Dirent::new(); NDIR_READ]; + for i in 0..NDIR_READ { + let start = i * fs::DIRENT_SIZE; + let end = start + fs::DIRENT_SIZE; + entries[i] = fs::parse_dirent(&raw_entries[start..end]); + println!("name {} is at inum: {}", core::str::from_utf8(&entries[i].name).unwrap_or("?"), entries[i].inum); + } + + let wtxt = fs::iget(param::ROOTDEV, entries[2].inum as u32); + fs::iread(wtxt); + let mut st = fs::Stat::new(); + fs::stati(wtxt, &mut st); + println!( + "\nwelcome.txt stats: Device {}, inode number {}, type {}, number of links {}, size {}", + st.dev, st.ino, st.type_, st.nlink, st.size + ); + + let mut greet = [0u8; 512]; + let n = fs::readi(wtxt, &mut greet, 0, st.size); + println!("Read {} bytes from welcome.txt", n); + println!("{}\n", core::str::from_utf8(&greet[..n as usize]).unwrap_or("?")); +} + extern "C" { - pub static alltraps: fn(); + pub fn alltraps(); } #[no_mangle] @@ -36,9 +77,14 @@ pub extern "C" fn entryofrust() -> ! { picirq::picinit(); ioapic::ioapic_init(); uart::uartinit(); + ide::ideinit(); tvinit(); + bio::binit(); idtinit(); x86::sti(); + fs::iinit(param::ROOTDEV); + welcome(); + loop { x86::wfi(); } @@ -48,5 +94,5 @@ pub extern "C" fn entryofrust() -> ! { #[panic_handler] fn panic(info: &PanicInfo) -> ! { println!("Kernel Panic: {:?}", info); - loop {} -} \ No newline at end of file + halt() +} diff --git a/src/mkfs.rs b/src/mkfs.rs new file mode 100644 index 0000000..083e394 --- /dev/null +++ b/src/mkfs.rs @@ -0,0 +1,289 @@ +mod fs_h; + +use std::env; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::mem::size_of; +use std::path::Path; + +use fs_h::{BPB, BSIZE, DIRSIZ, Dinode, Dirent, FSSIZE, IPB, LOGSIZE, MAXFILE, NDIRECT, NINODES, ROOTINO, Superblock, T_DIR, T_FILE}; + +struct Mkfs { + fsfd: File, + sb: Superblock, + freeinode: u32, + freeblock: u32, +} + +fn as_bytes(val: &T) -> &[u8] { + unsafe { std::slice::from_raw_parts((val as *const T).cast::(), size_of::()) } +} + +fn from_bytes(bytes: &[u8]) -> T { + assert_eq!(bytes.len(), size_of::()); + let mut out = std::mem::MaybeUninit::::uninit(); + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), out.as_mut_ptr().cast::(), bytes.len()); + out.assume_init() + } +} + +fn name_to_dirent(name: &str, inum: u16) -> Dirent { + let mut de = Dirent { + inum: inum.to_le(), + ..Default::default() + }; + let name_bytes = name.as_bytes(); + let copy_len = name_bytes.len().min(DIRSIZ); + de.name[..copy_len].copy_from_slice(&name_bytes[..copy_len]); + de +} + +impl Mkfs { + fn new(fsfd: File, sb: Superblock, freeblock: u32) -> Self { + Self { + fsfd, + sb, + freeinode: 1, + freeblock, + } + } + + fn wsect(&mut self, sec: u32, buf: &[u8; BSIZE]) { + let off = (sec as u64) * (BSIZE as u64); + self.fsfd + .seek(SeekFrom::Start(off)) + .expect("lseek(write) failed"); + self.fsfd.write_all(buf).expect("write failed"); + } + + fn rsect(&mut self, sec: u32, buf: &mut [u8; BSIZE]) { + let off = (sec as u64) * (BSIZE as u64); + self.fsfd + .seek(SeekFrom::Start(off)) + .expect("lseek(read) failed"); + self.fsfd.read_exact(buf).expect("read failed"); + } + + fn iblock(&self, inum: u32) -> u32 { + (inum / IPB as u32) + u32::from_le(self.sb.inodestart) + } + + fn winode(&mut self, inum: u32, ip: &Dinode) { + let mut buf = [0u8; BSIZE]; + let bn = self.iblock(inum); + self.rsect(bn, &mut buf); + + let off = (inum as usize % IPB) * size_of::(); + let src = as_bytes(ip); + buf[off..off + size_of::()].copy_from_slice(src); + self.wsect(bn, &buf); + } + + fn rinode(&mut self, inum: u32) -> Dinode { + let mut buf = [0u8; BSIZE]; + let bn = self.iblock(inum); + self.rsect(bn, &mut buf); + + let off = (inum as usize % IPB) * size_of::(); + from_bytes::(&buf[off..off + size_of::()]) + } + + fn ialloc(&mut self, typ: u16) -> u32 { + let inum = self.freeinode; + self.freeinode += 1; + + let din = Dinode { + typ: typ.to_le(), + nlink: 1u16.to_le(), + size: 0u32.to_le(), + ..Default::default() + }; + self.winode(inum, &din); + inum + } + + fn balloc(&mut self, used: u32) { + assert!(used < BPB as u32); + println!("balloc: first {} blocks have been allocated", used); + + let mut buf = [0u8; BSIZE]; + for i in 0..used { + let idx = (i / 8) as usize; + let bit = (i % 8) as u8; + buf[idx] |= 1u8 << bit; + } + + let bmapstart = u32::from_le(self.sb.bmapstart); + println!("balloc: write bitmap block at sector {}", bmapstart); + self.wsect(bmapstart, &buf); + } + + fn iappend(&mut self, inum: u32, mut p: &[u8]) { + let mut din = self.rinode(inum); + let mut off = u32::from_le(din.size); + + while !p.is_empty() { + let fbn = (off as usize) / BSIZE; + assert!(fbn < MAXFILE); + + let x: u32; + if fbn < NDIRECT { + if u32::from_le(din.addrs[fbn]) == 0 { + din.addrs[fbn] = self.freeblock.to_le(); + self.freeblock += 1; + } + x = u32::from_le(din.addrs[fbn]); + } else { + if u32::from_le(din.addrs[NDIRECT]) == 0 { + din.addrs[NDIRECT] = self.freeblock.to_le(); + self.freeblock += 1; + } + + let mut indirect_sector = [0u8; BSIZE]; + let indirect_blockno = u32::from_le(din.addrs[NDIRECT]); + self.rsect(indirect_blockno, &mut indirect_sector); + + let indirect_idx = fbn - NDIRECT; + let byte_off = indirect_idx * size_of::(); + let mut indirect_entry = u32::from_le(from_bytes::( + &indirect_sector[byte_off..byte_off + size_of::()], + )); + + if indirect_entry == 0 { + indirect_entry = self.freeblock; + self.freeblock += 1; + let le = indirect_entry.to_le(); + indirect_sector[byte_off..byte_off + size_of::()].copy_from_slice(as_bytes(&le)); + self.wsect(indirect_blockno, &indirect_sector); + } + + x = indirect_entry; + } + + let mut buf = [0u8; BSIZE]; + self.rsect(x, &mut buf); + + let n1 = p + .len() + .min((fbn as u32 + 1) as usize * BSIZE - off as usize); + let block_off = off as usize - (fbn * BSIZE); + buf[block_off..block_off + n1].copy_from_slice(&p[..n1]); + self.wsect(x, &buf); + + off += n1 as u32; + p = &p[n1..]; + } + + din.size = off.to_le(); + self.winode(inum, &din); + } +} + +fn main() { + assert_eq!(size_of::(), 4, "Integers must be 4 bytes"); + assert_eq!(BSIZE % size_of::(), 0); + assert_eq!(BSIZE % size_of::(), 0); + + let mut args = env::args().collect::>(); + if args.len() < 2 { + eprintln!("Usage: mkfs fs.img files..."); + std::process::exit(1); + } + + let fs_img = args.remove(1); + + let nbitmap = FSSIZE / BPB as u32 + 1; + let ninodeblocks = NINODES / IPB as u32 + 1; + let nlog = LOGSIZE; + let nmeta = 2 + nlog + ninodeblocks + nbitmap; + let nblocks = FSSIZE - nmeta; + + let sb = Superblock { + size: FSSIZE.to_le(), + nblocks: nblocks.to_le(), + ninodes: NINODES.to_le(), + nlog: nlog.to_le(), + logstart: 2u32.to_le(), + inodestart: (2 + nlog).to_le(), + bmapstart: (2 + nlog + ninodeblocks).to_le(), + }; + + println!( + "nmeta {} (boot, super, log blocks {} inode blocks {}, bitmap blocks {}) blocks {} total {}", + nmeta, nlog, ninodeblocks, nbitmap, nblocks, FSSIZE + ); + + let fsfd = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(&fs_img) + .unwrap_or_else(|e| { + eprintln!("{}: {}", fs_img, e); + std::process::exit(1); + }); + + let mut mkfs = Mkfs::new(fsfd, sb, nmeta); + + let zeroes = [0u8; BSIZE]; + for i in 0..FSSIZE { + mkfs.wsect(i, &zeroes); + } + + let mut sb_buf = [0u8; BSIZE]; + sb_buf[..size_of::()].copy_from_slice(as_bytes(&mkfs.sb)); + mkfs.wsect(1, &sb_buf); + + let rootino = mkfs.ialloc(T_DIR); + assert_eq!(rootino, ROOTINO); + + let dot = name_to_dirent(".", rootino as u16); + mkfs.iappend(rootino, as_bytes(&dot)); + + let dotdot = name_to_dirent("..", rootino as u16); + mkfs.iappend(rootino, as_bytes(&dotdot)); + + for path in &args[1..] { + if path.contains('/') { + eprintln!("{}: must be a basename (no /)", path); + std::process::exit(1); + } + + let mut host_file = File::open(path).unwrap_or_else(|e| { + eprintln!("{}: {}", path, e); + std::process::exit(1); + }); + + let file_name = if let Some(stripped) = path.strip_prefix('_') { + stripped + } else { + Path::new(path) + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(path) + }; + + let inum = mkfs.ialloc(T_FILE); + let de = name_to_dirent(file_name, inum as u16); + mkfs.iappend(rootino, as_bytes(&de)); + + let mut buf = [0u8; BSIZE]; + loop { + let cc = host_file.read(&mut buf).expect("read input file failed"); + if cc == 0 { + break; + } + mkfs.iappend(inum, &buf[..cc]); + } + } + + let mut din = mkfs.rinode(rootino); + let mut off = u32::from_le(din.size); + off = ((off / BSIZE as u32) + 1) * BSIZE as u32; + din.size = off.to_le(); + mkfs.winode(rootino, &din); + + mkfs.balloc(mkfs.freeblock); +} diff --git a/src/mp.rs b/src/mp.rs index b731b0c..015968e 100644 --- a/src/mp.rs +++ b/src/mp.rs @@ -4,7 +4,8 @@ use crate::x86::{outb, inb}; use crate::proc::Cpu; use core::cell::OnceCell; -pub static mut IOAPICID: u8 = 0; +// Removed: replaced by MP_ONCE.ioapic_id OnceCell pattern +// pub static mut IOAPICID: u8 = 0; #[derive(Debug, Clone, Copy)] #[repr(C)] @@ -59,7 +60,7 @@ pub struct MpIoApic { } // Processor flags -pub const MPBOOT: u8 = 0x02; // This proc is the bootstrap processor +// pub const MPBOOT: u8 = 0x02; // Unused in p3 - This proc is the bootstrap processor // Table entry types pub const MPPROC: u8 = 0x00; // One per processor @@ -194,7 +195,7 @@ pub fn mpinit() { let conf = unsafe { *(mp.physaddr as *mut MpConf) }; let mut ismp = true; - MP_ONCE.lapic_base.set(conf.lapicaddr as *mut u32); + MP_ONCE.lapic_base.set(conf.lapicaddr as *mut u32).expect("lapic_base already initialized"); let mut p = (mp.physaddr as usize + mem::size_of::()) as *const u8; let e = (mp.physaddr as usize + conf.length as usize) as *const u8; @@ -216,7 +217,7 @@ pub fn mpinit() { MPIOAPIC => { let ioapic = p as *const MpIoApic; let ioapicid = unsafe { (*ioapic).apicno }; - MP_ONCE.ioapic_id.set(ioapicid); + MP_ONCE.ioapic_id.set(ioapicid) .expect("ioapic_id already initialized"); p = p.wrapping_add(mem::size_of::()); } MPBUS | MPIOINTR | MPLINTR => { @@ -232,7 +233,7 @@ pub fn mpinit() { if !ismp { panic!("Didn't find a suitable machine"); } - MP_ONCE.cpus.set(cpus); + MP_ONCE.cpus.set(cpus).expect("cpus array already initialized"); if mp.imcrp != 0 { // Bochs doesn't support IMCR, so this doesn't run on Bochs. diff --git a/src/param.rs b/src/param.rs index 4341329..cd53d79 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,2 +1,4 @@ -pub const KSTACKSIZE: usize = 4096; // size of per-process kernel stack +// pub const KSTACKSIZE: usize = 4096; // size of per-process kernel stack (unused in p3) pub const NCPU: usize = 8; // maximum number of CPUs +pub const NINODE: usize = 50; // maximum number of active i-nodes +pub const ROOTDEV: u32 = 1; // device number of file system root disk diff --git a/src/proc.rs b/src/proc.rs index 279364c..713ad58 100644 --- a/src/proc.rs +++ b/src/proc.rs @@ -1,5 +1,5 @@ use crate::mp::MP_ONCE; // Import the MP_ONCE static from mp.rs -use core::ptr; +// use core::ptr; use crate::x86::readeflags; use crate::constants::{FL_IF,NCPU}; use crate::lapic; diff --git a/src/traps.rs b/src/traps.rs index 0643fcc..1321c9b 100644 --- a/src/traps.rs +++ b/src/traps.rs @@ -1,27 +1,26 @@ use modular_bitfield::prelude::*; -use core::cell::{OnceCell, RefCell}; +use core::cell::OnceCell; +use core::sync::atomic::{AtomicU32, Ordering}; +use crate::ide::ideintr; use crate::proc::cpuid; use crate::println; use crate::lapic::lapiceoi; -use crate::x86::{lidt,rcr2}; +use crate::x86::{lidt, rcr2, TrapFrame}; use crate::lapic; +use crate::constants::{IRQ_COM1, IRQ_IDE, IRQ_SPURIOUS, IRQ_TIMER, T_IRQ0}; +use crate::uart::uartintr; const SEG_KCODE: u16 = 1; const STS_IG32: u8 = 0xE; // 32-bit Interrupt Gate const STS_TG32: u8 = 0xF; // 32-bit Trap Gate -pub const T_IRQ0: u32 = 32; -pub const IRQ_TIMER: u32 = 0; -pub const IRQ_ERROR: u32 = 19; -pub const IRQ_SPURIOUS: u32 = 31; - extern "C" { - static vectors: [usize; 256]; // remove assembly. + static vectors: [usize; 256]; // in vectors.S: array of 256 entry pointers } #[bitfield] #[repr(C, packed)] -#[derive(Clone, Copy, Default)] // debug can be removed ? do we need to ? +#[derive(Clone, Copy, Default)] pub struct GateDesc { off_15_0: B16, // low 16 bits of offset in segment cs: B16, // code segment selector @@ -40,7 +39,7 @@ impl GateDesc { self.set_args(0); self.set_rsv1(0); let typ = if is_trap { STS_TG32 } else { STS_IG32 }; - self.set_r_type(typ); // for an interrupt gate, for example. + self.set_r_type(typ); self.set_s(0); self.set_dpl(dpl); self.set_p(1); @@ -49,58 +48,28 @@ impl GateDesc { } -#[repr(C)] -pub struct IDTOnce { - pub idt: OnceCell<[GateDesc; 256]>, - pub ticks: RefCell -} -unsafe impl Sync for IDTOnce {} -pub static IDT: IDTOnce = IDTOnce { idt: OnceCell::new(), ticks: RefCell::new(0) }; - - -#[repr(C)] -pub struct TrapFrame { - // registers as pushed by pusha - pub edi: u32, - pub esi: u32, - pub ebp: u32, - pub oesp: u32, // useless & ignored - pub ebx: u32, - pub edx: u32, - pub ecx: u32, - pub eax: u32, - - pub trapno: u32, - - // below here defined by x86 hardware - pub err: u32, - pub eip: u32, - pub cs: u16, - pub padding5: u16, - pub eflags: u32, - - // below here only when crossing rings, such as from user to kernel - pub esp: u32, - pub ss: u16, - pub padding6: u16, -} +static mut IDT: OnceCell<[GateDesc; 256]> = OnceCell::new(); +pub static TICKS: AtomicU32 = AtomicU32::new(0); pub fn tvinit() { let mut arr = [GateDesc::default(); 256]; for i in 0..256 { arr[i].set_gate( - false, // Use an interrupt gate. - SEG_KCODE << 3, // Code segment selector (shifted as in the C code). - unsafe { vectors[i] }, // Offset from the external vector table. - 0 // Descriptor privilege level. + false, + SEG_KCODE << 3, + unsafe { vectors[i] }, + 0 ); } - IDT.idt.set(arr); + unsafe { + let _ = IDT.set(arr); + } } pub fn idtinit() { - lidt(IDT.idt.get().unwrap() , core::mem::size_of::<[GateDesc; 256]>() as usize); + let idt = unsafe { IDT.get().expect("IDT not initialized") }; + lidt(idt, core::mem::size_of::<[GateDesc; 256]>() as usize); } @@ -116,22 +85,29 @@ pub extern "C" fn trap(orig_tf: *mut TrapFrame) { const TIMER: u32 = T_IRQ0 + IRQ_TIMER; const SPURIOUS: u32 = T_IRQ0 + IRQ_SPURIOUS; const SEVEN: u32 = T_IRQ0 + 7; - + const IDE: u32 = T_IRQ0 + IRQ_IDE; match tf.trapno { - TIMER => { - *IDT.ticks.borrow_mut() += 1; - println!("Tick {}!", IDT.ticks.borrow()); - lapic::lapiceoi(); - } - SEVEN | SPURIOUS => { - println!( - "cpu{}: spurious interrupt at {}:{}\n", - cpuid() , - tf.cs, - tf.eip - ); + TIMER => { + TICKS.fetch_add(1, Ordering::Relaxed); + lapic::lapiceoi(); + } + x if x == T_IRQ0 + IRQ_COM1 => { + uartintr(); + lapiceoi(); + } + SEVEN | SPURIOUS => { + println!( + "cpu{}: spurious interrupt at {:x}:{:x}\n", + cpuid(), + tf.cs, + tf.eip + ); lapiceoi(); } + IDE => { + ideintr(); + lapiceoi(); + } _ => { println!( "unexpected trap {} from cpu {} eip {} (cr2=0x{:x})\n", @@ -140,7 +116,7 @@ pub extern "C" fn trap(orig_tf: *mut TrapFrame) { tf.eip, rcr2() ); - panic!("trap happened"); + panic!("trap"); } } -} \ No newline at end of file +} diff --git a/src/uart.rs b/src/uart.rs index af6b648..7b0342b 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -1,4 +1,4 @@ -use crate::x86::*; +use crate::{console::consoleintr, constants::IRQ_COM1, ioapic::ioapic_enable, x86::*}; const COM1: u16 = 0x3F8; // COM1 port address pub fn uartinit() { @@ -19,6 +19,12 @@ pub fn uartinit() { return; } + // Acknowledge pre-existing interrupt conditions; + // enable interrupts. + inb(COM1+2); + inb(COM1+0); + ioapic_enable(IRQ_COM1, 0); + // Announce that we're here. for p in "xv6...\n".chars() { uartputc(p); @@ -32,4 +38,14 @@ pub fn uartputc(c: char) { } } outb(COM1+0, c as u8); -} \ No newline at end of file +} +fn uartgetc() -> Option { + if (inb(COM1 + 5) & 0x01) == 0 { + return None; + } + Some(inb(COM1) as u8) +} + +pub fn uartintr() { + consoleintr(uartgetc); +} diff --git a/src/x86.rs b/src/x86.rs index 9392776..4f76a22 100644 --- a/src/x86.rs +++ b/src/x86.rs @@ -25,19 +25,6 @@ pub fn outb(port: u16, value: u8) { } } -pub fn inw(port: u16) -> u16 { - let result: u16; - unsafe { - asm!( - "in ax, dx", - in("dx") port, - out("ax") result, - options(nomem, nostack) - ); - result - } -} - pub fn outw(port: u16, value: u16) { unsafe { asm!( @@ -49,30 +36,6 @@ pub fn outw(port: u16, value: u16) { } } -pub fn inl(port: u16) -> u32 { - unsafe { - let result: u32; - asm!( - "in eax, dx", - in("dx") port, - out("eax") result, - options(nomem, nostack) - ); - result - } -} - -pub fn outl(port: u16, value: u32) { - unsafe { - asm!( - "out dx, eax", - in("dx") port, - in("eax") value, - options(nomem, nostack) - ); - } -} - pub fn readeflags() -> u32 { unsafe { let eflags: u32; @@ -112,6 +75,40 @@ pub fn lidt(gdt: *const [GateDesc; 256], size: usize) { } } +pub fn noop() { + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); +} + +// x86.rs +pub unsafe fn insl(port: u16, addr: *mut u32, cnt: usize) { + core::arch::asm!( + "cld", + "rep insd", + in("dx") port, + inout("edi") (addr as usize) => _, + inout("ecx") cnt => _, + options(nostack, preserves_flags), + ); +} + + +// Output string of dwords to port using rep outsd instruction. +// Matches the C implementation: outsl(port, addr, cnt) +// Note: ESI must be saved/restored as LLVM restricts its use in 32-bit mode +pub unsafe fn outsl(port: u16, addr: *const u32, cnt: usize) { + let addr_val = addr as u32; + core::arch::asm!( + "push esi", + "mov esi, {addr}", + "cld", + "rep outsd", + "pop esi", + addr = in(reg) addr_val, + in("dx") port, + inout("ecx") cnt => _, + ); +} + /// Halts the CPU until the next interrupt occurs. /// The `hlt` instruction: /// 1. Stops instruction execution and places the processor in a HALT state diff --git a/targets/i686-stage-3.json b/targets/i686-stage-3.json index 15167f1..78bfafc 100644 --- a/targets/i686-stage-3.json +++ b/targets/i686-stage-3.json @@ -7,4 +7,4 @@ "panic-strategy": "abort", "os": "none", "target-pointer-width": "32" -} \ No newline at end of file +} diff --git a/trapasm.S b/trapasm.S index 414dc20..477a5ee 100644 --- a/trapasm.S +++ b/trapasm.S @@ -17,3 +17,5 @@ trapret: popal addl $0x8, %esp # trapno and errcode iret + +.section .note.GNU-stack,"",@progbits \ No newline at end of file diff --git a/vectors.pl b/vectors.pl index 57b49dd..0d75a7a 100755 --- a/vectors.pl +++ b/vectors.pl @@ -26,6 +26,7 @@ print " .long vector$i\n"; } +print ".section .note.GNU-stack,\"\",%progbits\n"; # sample output: # # handlers # .globl alltraps diff --git a/welcome.txt b/welcome.txt new file mode 100644 index 0000000..5809c82 --- /dev/null +++ b/welcome.txt @@ -0,0 +1,7 @@ + ### + # # ###### # #### #### # # ###### ### + # # # # # # # # ## ## # ### + # # ##### # # # # # ## # ##### # + # ## # # # # # # # # # + ## ## # # # # # # # # # ### + # # ###### ###### #### #### # # ###### ###