diff --git a/.asm-lsp.toml b/.asm-lsp.toml new file mode 100644 index 0000000..e34e92b --- /dev/null +++ b/.asm-lsp.toml @@ -0,0 +1,21 @@ +[default_config] +version = "0.10.0" +assembler = "nasm" +instruction_set = "x86/x86-64" + +[default_config.opts] +compiler = "zig" +compile_flags_txt = [ + "cc", + "-x", + "assembler-with-cpp", + "-g", + "-Wall", + "-Wextra", + "-pedantic", + "-pedantic-errors", + "-std=c2y", + "-mllvm --x86-asm-syntax=intel", +] +diagnostics = true +default_diagnostics = false diff --git a/.gitignore b/.gitignore index 880cd5d..0ae2e6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ zig-out .zig-cache +.kate-swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a6beca --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +an operating system made in nasm assembly and (in the future) zig, using the zig build system. +this project is based on [this video series](https://www.youtube.com/playlist?list=PLFjM7v6KGMpiH2G-kT781ByCNC_0pKpPN) by [nanobyte](https://www.youtube.com/@nanobyte-dev) + +# HOWTO + +## dependencies + +this project requires the nasm assembler, mtools for floppy disk generation and zig to build. +this project uses qemu as the default emulator, but this is optional + +## building + +```bash +git clone https://dario48.site/git/Dario48/zos.git +cd zos +zig build +``` + +## running + +- with the default emulator + +```bash +cd zos +zig build run +``` + +- with a different emulator + +```bash +cd zos +emulator_cmd zig-out/bin/main_floppy.img +``` diff --git a/build.zig b/build.zig index 191e029..e4051ee 100644 --- a/build.zig +++ b/build.zig @@ -18,16 +18,40 @@ pub fn build(b: *std.Build) void { var floppy = createFloppy(b, "main_floppy.img"); floppy.run = formatFloppyFat12(b, floppy); + var installToFloppy = installImgToFloppy(b, floppy, initramfs, &.{ + NamedFile{ .file = .{ .runFile = kernel }, .name = "::kernel.bin" }, + NamedFile{ .file = .{ .simpleFile = b.path("src/reference_implementations/test.txt") }, .name = "::test.txt" }, + }); + const img_install = b.addInstallBinFile(floppy.obj, "main_floppy.img"); - img_install.step.dependOn(&installImgToFloppy(b, floppy, initramfs, kernel).step); + img_install.step.dependOn(&installToFloppy); b.getInstallStep().dependOn(&img_install.step); + const target = b.standardTargetOptions(.{}); + const fat_reference_mod = b.createModule(.{ + .root_source_file = b.path("src/reference_implementations/fat.zig"), + .target = target, + .link_libc = true, + }); + const run = b.addSystemCommand(&.{ "qemu-system-i386", "-fda" }); - run.addFileArg(b.path("zig-out/bin/initramfs.img")); + run.addFileArg(floppy.obj); const run_step = b.step("run", "run the os in qemu"); run_step.dependOn(&run.step); + + const fat_reference = b.addExecutable(.{ + .root_module = fat_reference_mod, + .name = "fat_reference_implementation", + .use_lld = false, + .use_llvm = false, + }); + + const fat_install = b.addInstallArtifact(fat_reference, .{}); + + const build_references = b.step("references", "build the reference implementations"); + build_references.dependOn(&fat_install.step); } const AddNasmFilesOptions = struct { @@ -43,6 +67,11 @@ const NasmFile = struct { const Floppydisk = NasmFile; +const NamedFile = struct { + file: union(enum) { runFile: NasmFile, simpleFile: std.Build.LazyPath }, + name: []const u8, +}; + fn createFloppy(b: *std.Build, name: []const u8) Floppydisk { const dd = b.addSystemCommand(&.{ "dd", "if=/dev/zero" }); const Floppy = dd.addPrefixedOutputFileArg("of=", name); @@ -63,7 +92,7 @@ fn formatFloppyFat12(b: *std.Build, floppy: Floppydisk) *std.Build.Step.Run { return mkfs; } -fn installImgToFloppy(b: *std.Build, floppy: Floppydisk, bootloader: NasmFile, kernel: NasmFile) *std.Build.Step.Run { +fn installImgToFloppy(b: *std.Build, floppy: Floppydisk, bootloader: NasmFile, additional_files: []const NamedFile) std.Build.Step { const dd = b.addSystemCommand(&.{"dd"}); dd.addPrefixedFileArg("if=", bootloader.obj); dd.addPrefixedFileArg("of=", floppy.obj); @@ -71,14 +100,22 @@ fn installImgToFloppy(b: *std.Build, floppy: Floppydisk, bootloader: NasmFile, k dd.step.dependOn(&floppy.run.step); dd.step.dependOn(&bootloader.run.step); - const mcopy = b.addSystemCommand(&.{"mcopy"}); - mcopy.addPrefixedFileArg("-i", floppy.obj); - mcopy.addFileArg(kernel.obj); - mcopy.addArg("::kernel.bin"); - mcopy.step.dependOn(&dd.step); - mcopy.step.dependOn(&kernel.run.step); - - return mcopy; + var mcopy_step = std.Build.Step.init(.{ .name = "mcopy", .owner = b, .id = .custom }); + mcopy_step.dependOn(&dd.step); + for (additional_files) |f| { + const mcopy = b.addSystemCommand(&.{"mcopy"}); + mcopy.addPrefixedFileArg("-i", floppy.obj); + switch (f.file) { + .runFile => |file| { + mcopy.addFileArg(file.obj); + mcopy_step.dependOn(&file.run.step); + }, + .simpleFile => |file| mcopy.addFileArg(file), + } + mcopy.addArg(f.name); + mcopy_step.dependOn(&mcopy.step); + } + return mcopy_step; } // adapted from https://codeberg.org/raddari/zig-nasm-lib.git diff --git a/build.zig.zon b/build.zig.zon index 365621b..f29dbdf 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -28,7 +28,7 @@ // Tracks the earliest Zig version that the package considers to be a // supported use case. - .minimum_zig_version = "0.14.1", + .minimum_zig_version = "0.15.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. diff --git a/src/initramfs.asm b/src/initramfs.asm index 6da1603..b2e02b3 100644 --- a/src/initramfs.asm +++ b/src/initramfs.asm @@ -2,8 +2,40 @@ org 0x7C00 bits 16 + %define ENDL 0x0D, 0x0A +; +; FAT12 Header +; +jmp short start +nop + +bdb_oem: db "mkfs.fat" +bdb_bytes_per_sector: dw 512 +bdb_sectors_per_cluster: db 1 +bdb_reserved_sectors: dw 1 +bdb_fat_count: db 2 +bdb_dir_entries_count: dw 0E0h +bdb_total_sectors: dw 2880 +bdb_media_descriptor_type: db 0F0h +bdb_sectors_per_fat: dw 9 +bdb_sectors_per_track: dw 18 +bdb_heads: dw 2 +bdb_hidden_sectors: dd 0 +bdb_large_sector_count: dd 0 + +; Extended boot record +ebr_drive_number: db 0 + db 0 +ebr_signature: db 29h +ebr_volume_id: db 68h, 6Fh, 6Dh, 6Fh +ebr_volume_laber: db 'zos disk ' +ebr_system_id: db 'FAT12 ' + + + + start: jmp main @@ -47,16 +79,139 @@ main: mov ss, ax mov sp, 0x7C00 ; stack grows downward from where we are loaded in memory - ; print the hello world - mov si, msg_hello + mov si, msg_startup call echo + mov si, msg_reading_from_disk + call echo + mov [ebr_drive_number], dl + + mov ax, 1 ; second sector from disk + mov cl, 1 ; 1 sector to read + mov bx, 0x7E00 ; data should be after the bootloader + call disk_read + + cli + hlt + +; errors + +floppy_error: + mov si, msg_read_failed + call echo + jmp .wait_and_reboot + +.wait_and_reboot: + mov ah, 0 + int 16h + + jmp 0FFFFh:0 ; jump to start of bios, should reboot + hlt .halt: + cli ; disable interrupts, this way we shouldn't be able to get out of halt + hlt jmp .halt -msg_hello: db 'Hello, world!', ENDL, 0 + +; +; args: +; ax = lba adress +; return: +; cx[0..5] = sector number +; cx[6-15] = cilinder number +; dh: head +; +lba_to_chs: + push ax + push dx + + xor dx, dx ; clear dw + div word [bdb_sectors_per_track] ; ax = LBA / SectorsPerTrack + ; dx = LBA % SectorsPerTrack + + inc dx + mov cx, dx ; cx = sector + + xor dx, dx ; clear dx + div word [bdb_heads] ; ax = (LBA / SectorsPerTrack) / Heads = cylinder + ; dx = (LBA / SectorsPerTrack) % Heads = head + mov dh, dl + mov ch, al + shl ah, 6 + or cl, ah ; put upper 2 bits of cylinder in CL + + pop ax + mov dl, al + pop ax + + ret + + +; +; args: +; ax = LBA adress +; cl = number of sectors to read +; dl = drive number +; es:bx = pointer to where to store the data +; + +disk_read: + push ax + push bx + push cx + push dx + push di + + push cx ; cx will be overridden + call lba_to_chs + pop ax ; al = number of sectors to read + mov ah, 02h + mov di, 3 ; hoe many times to try + +.retry: + pusha + stc ; set carry flag + int 13h ; if no carry flag => it work + + jnc .done + + ; read fail + popa + call disk_reset + dec di + + test di, di + jnz .retry + +.fail: + jmp floppy_error + +.done: + popa + + pop di + pop dx + pop cx + pop bx + pop ax + ret + +; arg: dl = drive number + +disk_reset: + pusha + mov ah, 0 + stc ; set carry + int 13h + jc floppy_error + popa + ret + +msg_startup: db 'starting zos, please hold while we check for any problems', ENDL, 0 +msg_reading_from_disk: db 'testing reading from disk', ENDL, 0 +msg_read_failed: db 'error when trying to read from floppy', ENDL, 0 times 510-($-$$) db 0 dw 0AA55h diff --git a/src/reference_implementations/fat.zig b/src/reference_implementations/fat.zig new file mode 100644 index 0000000..61d7a0f --- /dev/null +++ b/src/reference_implementations/fat.zig @@ -0,0 +1,117 @@ +const std = @import("std"); + +const Errors = error{ InvalidSyntax, ReadSectors, FileNotFound }; + +var buffer: [1024]u8 = undefined; + +var g_Fat: []u8 = undefined; + +const BootSector = extern struct { + BootJumpInstruction: [3]u8 align(1), + OemIdentifier: [8]u8 align(1), + BytesPerSector: u16 align(1), + SectorsPerCluster: u8 align(1), + ReservedSectors: u16 align(1), + FatCount: u8 align(1), + DirEntryCount: u16 align(1), + TotalSectors: u16 align(1), + MediaDescriptorType: u8 align(1), + SectorsPerFat: u16 align(1), + SectorsPerTrack: u16 align(1), + Heads: u16 align(1), + HiddenSectors: u32 align(1), + LargeSectorCount: u32 align(1), + + // ebr + DriveNumber: u8 align(1), + _Reserved: u8 align(1), + Signature: u8 align(1), + VolumeId: u32 align(1), + VolumeLaber: [11]u8 align(1), + SystemId: [8]u8 align(1), +}; + +var g_BootSector: BootSector = undefined; + +const DirectoryEntry = extern struct { + Name: [11]u8 align(1), + Attributes: u8 align(1), + _Reserved: u8 align(1), + CreatedTimeTenths: u8 align(1), + CreatedTime: u16 align(1), + CreatedDate: u16 align(1), + AccessedDate: u16 align(1), + FirstClusterHigh: u16 align(1), + ModifiedTime: u16 align(1), + ModifiedDate: u16 align(1), + FirstClusterLow: u16 align(1), + Size: u32 align(1), +}; + +var g_RootDirectory: []DirectoryEntry = undefined; + +fn readStruct(reader: *std.fs.File.Reader, T: type) !T { + var ret: [@sizeOf(T)]u8 = undefined; + _ = try reader.*.read(&ret); + return @bitCast(ret); +} + +fn readBootSector(disk: *std.fs.File) !void { + var reader = disk.*.reader(&buffer); + g_BootSector = try readStruct(&reader, BootSector); +} + +fn readSectors(disk: *std.fs.File, lba: u32, count: u32, bufferOut: *[]anyopaque) !void { + const dest: []u8 = try std.heap.smp_allocator.alloc(u8, g_BootSector.BytesPerSector * count); + defer std.heap.smp_allocator.free(dest); + var stream = disk.readerStreaming(&buffer); + try stream.seekTo(lba * g_BootSector.BytesPerSector); + if (try stream.readStreaming(dest) != g_BootSector.BytesPerSector * count) return Errors.ReadSectors; + @memcpy(@as(*[]u8, @ptrCast(bufferOut)).*, dest); +} + +fn readFat(disk: *std.fs.File) !void { + g_Fat = try std.heap.smp_allocator.alloc(u8, g_BootSector.SectorsPerFat * g_BootSector.BytesPerSector); + errdefer std.heap.smp_allocator.free(g_Fat); + try readSectors(disk, g_BootSector.ReservedSectors, g_BootSector.SectorsPerFat, &g_Fat); +} + +fn readRootDirectory(disk: std.fs.File) !void { + const lba: u32 = g_BootSector.ReservedSectors + g_BootSector.SectorsPerFat + g_BootSector.FatCount; + const size: u32 = @sizeOf(DirectoryEntry) * g_BootSector.DirEntryCount; + const sectors: u32 = (size / g_BootSector.BytesPerSector); + if (@rem(size, g_BootSector.BytesPerSector > 0)) + sectors += 1; + + g_RootDirectory = try std.heap.smp_allocator.alloc(DirectoryEntry, sectors * g_BootSector.BytesPerSector); + errdefer std.heap.smp_allocator.free(g_RootDirectory); + try readSectors(disk, lba, sectors, &g_RootDirectory); +} + +fn findFile(name: []const u8) !*DirectoryEntry { + for (g_RootDirectory, 0..) |Entry, i| + if (std.mem.eql(u8, Entry.Name, name)) + return &g_RootDirectory[i]; + return Errors.FileNotFound; +} + +pub fn main() !void { + if (std.os.argv.len < 3) { + std.log.err("syntax: {s} ", .{std.os.argv[0]}); + return Errors.InvalidSyntax; + } + + var disk = try std.fs.cwd().openFileZ(std.os.argv[1], .{ .mode = .read_only }); + + _ = &disk; + try readBootSector(&disk); + + try readFat(&disk); + defer std.heap.smp_allocator.free(g_Fat); + + try readRootDirectory(disk); + defer std.heap.smp_allocator.free(g_RootDirectory); + + const fileEntry = try findFile(std.os.argv[2]); + _ = fileEntry; +} diff --git a/src/reference_implementations/test.txt b/src/reference_implementations/test.txt new file mode 100644 index 0000000..20e13f9 --- /dev/null +++ b/src/reference_implementations/test.txt @@ -0,0 +1,3 @@ +testing file +aabaacaadaaeaafaagaahaaiaajaakaalaamaanaaoaapaaqaaraasaataauaavaawaaxaayaaz +tstngfl