Phase 3: ATA driver, tinyfs

Bash · Jul 17, 19:10:58 · toolu_01MXe3rtPm5bQTSEb77zfZdu
Commit Phase 2 kernel core
$ git commit -m "$(cat <<'EOF'
Phase 2: kernel core - GDT/IDT, allocator, self-test, test harness

Kernel now owns its GDT instead of relying on bootloader memory, and
installs a minimal IDT covering only CPU exceptions (0-31) - no PIC
remap or IRQ handling since the OS is single-tasking/polling-only and
never enables interrupts. Adds a freestanding string.c subset and a
free-list kmalloc/kfree allocator over a fixed 1MB heap at 1MB phys.

kmain now runs a self-test (string ops + allocator incl. coalescing)
and reports SELFTEST PASS/FAIL over serial, which tools/run_tests.sh
uses as the automated pass/fail oracle for headless QEMU runs. This
harness will be extended by later phases with fixture-based checks.
EOF
)"
result
[main 1a67c58] Phase 2: kernel core - GDT/IDT, allocator, self-test, test harness
 14 files changed, 486 insertions(+), 5 deletions(-)
 create mode 100644 kernel/gdt.c
 create mode 100644 kernel/gdt.h
 create mode 100644 kernel/idt.c
 create mode 100644 kernel/idt.h
 create mode 100644 kernel/idt_stubs.S
 create mode 100644 kernel/memory.c
 create mode 100644 kernel/memory.h
 create mode 100644 kernel/selftest.c
 create mode 100644 kernel/selftest.h
 create mode 100644 kernel/string.c
 create mode 100644 kernel/string.h
 create mode 100755 tools/run_tests.sh
TaskUpdate · Jul 17, 19:11:00 · toolu_01PHkwiqUp7qQxPZZwZjqhfB
{
 "taskId": "3",
 "status": "completed"
}
result
Updated task #3 status
TaskUpdate · Jul 17, 19:11:00 · toolu_01Lvhp6sUE1zVn1Rxkv4jfQi
{
 "taskId": "4",
 "status": "in_progress"
}
result
Updated task #4 status
Bash · Jul 17, 19:14:03 · toolu_01Cz5RwCk2S5szLoXc7bmmfe
Create fs and docs directories
$ mkdir -p /home/martin/workspace/prototypes/boot-to-claude/kernel/fs /home/martin/workspace/prototypes/boot-to-claude/docs
result
(Bash completed with no output)
agent · Jul 17, 19:14:05
First, the ATA driver:
Write · Jul 17, 19:14:07 · toolu_015njduw4HJgUxLyGzuzQbPg
/home/martin/workspace/prototypes/boot-to-claude/kernel/ata.h
#pragma once

#include <stdint.h>

/* Polled PIO access to the primary ATA master (LBA28). This is the same
 * disk QEMU exposes via -drive file=...,format=raw - the one the boot
 * sector was read from via BIOS INT13h. */
void ata_read_sector(uint32_t lba, void *buf);
void ata_write_sector(uint32_t lba, const void *buf);
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/ata.h (file state is current in your context — no need to Read it back)
Write · Jul 17, 19:14:17 · toolu_01AAFEgcGaUqcduBFurd735F
/home/martin/workspace/prototypes/boot-to-claude/kernel/ata.c
84 lines
#include "ata.h"

#define ATA_IO_BASE   0x1F0
#define ATA_DATA      (ATA_IO_BASE + 0)
#define ATA_SECCOUNT  (ATA_IO_BASE + 2)
#define ATA_LBA_LO    (ATA_IO_BASE + 3)
#define ATA_LBA_MID   (ATA_IO_BASE + 4)
#define ATA_LBA_HI    (ATA_IO_BASE + 5)
#define ATA_DRIVE     (ATA_IO_BASE + 6)
#define ATA_STATUS    (ATA_IO_BASE + 7)
#define ATA_COMMAND   (ATA_IO_BASE + 7)

#define ATA_CMD_READ        0x20
#define ATA_CMD_WRITE       0x30
#define ATA_CMD_CACHE_FLUSH 0xE7

#define ATA_SR_BSY 0x80
#define ATA_SR_DRQ 0x08

static inline void outb(uint16_t port, uint8_t val) {
    __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}

static inline uint8_t inb(uint16_t port) {
    uint8_t ret;
    __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
    return ret;
}

static inline void outw(uint16_t port, uint16_t val) {
    __asm__ volatile ("outw %0, %1" : : "a"(val), "Nd"(port));
}

static inline uint16_t inw(uint16_t port) {
    uint16_t ret;
    __asm__ volatile ("inw %1, %0" : "=a"(ret) : "Nd"(port));
    return ret;
}

static void ata_wait_bsy(void) {
    while (inb(ATA_STATUS) & ATA_SR_BSY) {}
}

static void ata_wait_drq(void) {
    while (!(inb(ATA_STATUS) & ATA_SR_DRQ)) {}
}

static void ata_select(uint32_t lba) {
    outb(ATA_DRIVE, (uint8_t)(0xE0 | ((lba >> 24) & 0x0F)));
    outb(ATA_SECCOUNT, 1);
    outb(ATA_LBA_LO, (uint8_t)(lba & 0xFF));
    outb(ATA_LBA_MID, (uint8_t)((lba >> 8) & 0xFF));
    outb(ATA_LBA_HI, (uint8_t)((lba >> 16) & 0xFF));
}

void ata_read_sector(uint32_t lba, void *buf) {
    ata_wait_bsy();
    ata_select(lba);
    outb(ATA_COMMAND, ATA_CMD_READ);
    ata_wait_bsy();
    ata_wait_drq();

    uint16_t *p = buf;
    for (int i = 0; i < 256; i++) {
        p[i] = inw(ATA_DATA);
    }
}

void ata_write_sector(uint32_t lba, const void *buf) {
    ata_wait_bsy();
    ata_select(lba);
    outb(ATA_COMMAND, ATA_CMD_WRITE);
    ata_wait_bsy();
    ata_wait_drq();

    const uint16_t *p = buf;
    for (int i = 0; i < 256; i++) {
        outw(ATA_DATA, p[i]);
    }

    outb(ATA_COMMAND, ATA_CMD_CACHE_FLUSH);
    ata_wait_bsy();
}
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/ata.c (file state is current in your context — no need to Read it back)
agent · Jul 17, 19:14:20
Now the shared tinyfs format definitions and core logic:
Write · Jul 17, 19:14:30 · toolu_01VJTGcy9BYmy7wJgDAEmtZH
/home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs.h
74 lines
#pragma once

/* tinyfs: a minimal flat-namespace filesystem. See docs/tinyfs.md for the
 * on-disk layout. This header is included by both the kernel (freestanding
 * build) and tools/mkfs_tinyfs.c (native host build) - it must only use
 * portable, standard C. */

#include <stdint.h>

#define TINYFS_BLOCK_SIZE 512
#define TINYFS_MAGIC       0x54465331u /* "TFS1" */

/* Disk layout constants. TINYFS_DISK_SECTORS must match the disk image
 * size the Makefile builds (DISK_SECTORS). */
#define TINYFS_DISK_SECTORS 4096
#define TINYFS_FS_START_LBA 128 /* leaves 64KB for boot sector + kernel */

#define TINYFS_NUM_INODES    64
#define TINYFS_NAME_MAX      27 /* 26 chars + NUL */
#define TINYFS_DIRECT_BLOCKS 8

#define TINYFS_SB_BLOCK       0
#define TINYFS_BITMAP_BLOCK   1
#define TINYFS_BITMAP_BLOCKS  1 /* 4096 bits = up to 4096 data blocks (2MB) */
#define TINYFS_INODE_BLOCK    (TINYFS_BITMAP_BLOCK + TINYFS_BITMAP_BLOCKS)
#define TINYFS_INODE_BLOCKS   8 /* 64 inodes * 64 bytes / 512-byte block */
#define TINYFS_DATA_BLOCK     (TINYFS_INODE_BLOCK + TINYFS_INODE_BLOCKS)

#define TINYFS_TOTAL_FS_BLOCKS (TINYFS_DISK_SECTORS - TINYFS_FS_START_LBA)
#define TINYFS_DATA_BLOCKS     (TINYFS_TOTAL_FS_BLOCKS - TINYFS_DATA_BLOCK)

#define TINYFS_MAX_FILE_SIZE (TINYFS_DIRECT_BLOCKS * TINYFS_BLOCK_SIZE)

typedef struct {
    uint32_t magic;
    uint32_t total_blocks;
    uint32_t num_inodes;
    uint32_t data_blocks;
} __attribute__((packed)) tinyfs_superblock_t;

typedef struct {
    uint8_t  used;
    char     name[TINYFS_NAME_MAX];
    uint32_t size; /* bytes */
    uint32_t blocks[TINYFS_DIRECT_BLOCKS]; /* fs-relative block numbers, 0 = none */
} __attribute__((packed)) tinyfs_inode_t;

typedef struct {
    char     name[TINYFS_NAME_MAX];
    uint32_t size;
} tinyfs_dirent_t;

typedef void (*tinyfs_block_read_fn)(uint32_t block, void *buf);
typedef void (*tinyfs_block_write_fn)(uint32_t block, const void *buf);

void tinyfs_init(tinyfs_block_read_fn read_fn, tinyfs_block_write_fn write_fn);

/* Formats a blank fs region: zeroes the bitmap/inode table and writes a
 * fresh superblock. Used only by tools/mkfs_tinyfs at image-build time. */
int tinyfs_format(void);

/* Loads the superblock/bitmap/inode table into memory. Must be called
 * before any other operation other than tinyfs_format. */
int tinyfs_mount(void);

int tinyfs_create(const char *name);
int tinyfs_delete(const char *name);
int tinyfs_write(const char *name, const void *data, uint32_t size);
int tinyfs_read(const char *name, void *out, uint32_t max_len, uint32_t *out_len);

/* Fills out[] with up to max_entries directory entries, returns the count
 * written. */
int tinyfs_list(tinyfs_dirent_t *out, int max_entries);
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs.h (file state is current in your context — no need to Read it back)
Write · Jul 17, 19:14:52 · toolu_01EXjPQpSszXUmdqhrmHP2kX
/home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs.c
240 lines
#include "tinyfs.h"

/* Resolves to kernel/string.h under the freestanding kernel build
 * (-Ikernel puts it ahead of the system header) and to the host libc
 * <string.h> when this file is compiled natively for tools/mkfs_tinyfs -
 * same declarations (memcpy/memset/strlen/strcmp) either way. */
#include "string.h"

static tinyfs_block_read_fn  block_read;
static tinyfs_block_write_fn block_write;

static tinyfs_superblock_t sb;
static uint8_t bitmap[TINYFS_BITMAP_BLOCKS * TINYFS_BLOCK_SIZE];
static tinyfs_inode_t inode_table[TINYFS_NUM_INODES];

void tinyfs_init(tinyfs_block_read_fn read_fn, tinyfs_block_write_fn write_fn) {
    block_read = read_fn;
    block_write = write_fn;
}

static void load_bitmap(void) {
    for (uint32_t i = 0; i < TINYFS_BITMAP_BLOCKS; i++) {
        block_read(TINYFS_BITMAP_BLOCK + i, bitmap + (size_t)i * TINYFS_BLOCK_SIZE);
    }
}

static void save_bitmap(void) {
    for (uint32_t i = 0; i < TINYFS_BITMAP_BLOCKS; i++) {
        block_write(TINYFS_BITMAP_BLOCK + i, bitmap + (size_t)i * TINYFS_BLOCK_SIZE);
    }
}

static void load_inodes(void) {
    uint8_t *p = (uint8_t *)inode_table;
    for (uint32_t i = 0; i < TINYFS_INODE_BLOCKS; i++) {
        block_read(TINYFS_INODE_BLOCK + i, p + (size_t)i * TINYFS_BLOCK_SIZE);
    }
}

static void save_inodes(void) {
    const uint8_t *p = (const uint8_t *)inode_table;
    for (uint32_t i = 0; i < TINYFS_INODE_BLOCKS; i++) {
        block_write(TINYFS_INODE_BLOCK + i, p + (size_t)i * TINYFS_BLOCK_SIZE);
    }
}

static int bitmap_test(uint32_t idx) {
    return (bitmap[idx / 8] >> (idx % 8)) & 1;
}

static void bitmap_set(uint32_t idx, int val) {
    if (val) {
        bitmap[idx / 8] = (uint8_t)(bitmap[idx / 8] | (1u << (idx % 8)));
    } else {
        bitmap[idx / 8] = (uint8_t)(bitmap[idx / 8] & ~(1u << (idx % 8)));
    }
}

static int alloc_data_block(uint32_t *out_block) {
    for (uint32_t i = 0; i < sb.data_blocks; i++) {
        if (!bitmap_test(i)) {
            bitmap_set(i, 1);
            *out_block = TINYFS_DATA_BLOCK + i;
            return 0;
        }
    }
    return -1;
}

static void free_data_block(uint32_t block) {
    bitmap_set(block - TINYFS_DATA_BLOCK, 0);
}

static tinyfs_inode_t *find_inode(const char *name) {
    for (int i = 0; i < TINYFS_NUM_INODES; i++) {
        if (inode_table[i].used && strcmp(inode_table[i].name, name) == 0) {
            return &inode_table[i];
        }
    }
    return NULL;
}

static int find_free_inode(void) {
    for (int i = 0; i < TINYFS_NUM_INODES; i++) {
        if (!inode_table[i].used) {
            return i;
        }
    }
    return -1;
}

int tinyfs_format(void) {
    memset(bitmap, 0, sizeof(bitmap));
    memset(inode_table, 0, sizeof(inode_table));

    sb.magic = TINYFS_MAGIC;
    sb.total_blocks = TINYFS_TOTAL_FS_BLOCKS;
    sb.num_inodes = TINYFS_NUM_INODES;
    sb.data_blocks = TINYFS_DATA_BLOCKS;

    uint8_t sbbuf[TINYFS_BLOCK_SIZE];
    memset(sbbuf, 0, sizeof(sbbuf));
    memcpy(sbbuf, &sb, sizeof(sb));
    block_write(TINYFS_SB_BLOCK, sbbuf);

    save_bitmap();
    save_inodes();
    return 0;
}

int tinyfs_mount(void) {
    uint8_t sbbuf[TINYFS_BLOCK_SIZE];
    block_read(TINYFS_SB_BLOCK, sbbuf);
    memcpy(&sb, sbbuf, sizeof(sb));
    if (sb.magic != TINYFS_MAGIC) {
        return -1;
    }
    load_bitmap();
    load_inodes();
    return 0;
}

int tinyfs_create(const char *name) {
    if (find_inode(name)) {
        return -1;
    }
    int idx = find_free_inode();
    if (idx < 0) {
        return -1;
    }

    tinyfs_inode_t *ino = &inode_table[idx];
    memset(ino, 0, sizeof(*ino));
    ino->used = 1;

    int i = 0;
    while (name[i] && i < TINYFS_NAME_MAX - 1) {
        ino->name[i] = name[i];
        i++;
    }
    ino->name[i] = '\0';

    save_inodes();
    return 0;
}

int tinyfs_delete(const char *name) {
    tinyfs_inode_t *ino = find_inode(name);
    if (!ino) {
        return -1;
    }
    for (int b = 0; b < TINYFS_DIRECT_BLOCKS; b++) {
        if (ino->blocks[b]) {
            free_data_block(ino->blocks[b]);
        }
    }
    ino->used = 0;
    save_bitmap();
    save_inodes();
    return 0;
}

int tinyfs_write(const char *name, const void *data, uint32_t size) {
    tinyfs_inode_t *ino = find_inode(name);
    if (!ino || size > TINYFS_MAX_FILE_SIZE) {
        return -1;
    }

    for (int b = 0; b < TINYFS_DIRECT_BLOCKS; b++) {
        if (ino->blocks[b]) {
            free_data_block(ino->blocks[b]);
            ino->blocks[b] = 0;
        }
    }

    const uint8_t *p = data;
    uint32_t remaining = size;
    int b = 0;
    while (remaining > 0) {
        uint32_t block;
        if (alloc_data_block(&block) != 0) {
            save_bitmap();
            save_inodes();
            return -1;
        }

        uint8_t buf[TINYFS_BLOCK_SIZE];
        uint32_t chunk = remaining < TINYFS_BLOCK_SIZE ? remaining : TINYFS_BLOCK_SIZE;
        memset(buf, 0, sizeof(buf));
        memcpy(buf, p, chunk);
        block_write(block, buf);

        ino->blocks[b++] = block;
        p += chunk;
        remaining -= chunk;
    }

    ino->size = size;
    save_bitmap();
    save_inodes();
    return 0;
}

int tinyfs_read(const char *name, void *out, uint32_t max_len, uint32_t *out_len) {
    tinyfs_inode_t *ino = find_inode(name);
    if (!ino) {
        return -1;
    }

    uint32_t n = ino->size < max_len ? ino->size : max_len;
    uint8_t *p = out;
    uint32_t copied = 0;
    int b = 0;
    while (copied < n) {
        uint8_t buf[TINYFS_BLOCK_SIZE];
        block_read(ino->blocks[b], buf);
        uint32_t chunk = (n - copied) < TINYFS_BLOCK_SIZE ? (n - copied) : TINYFS_BLOCK_SIZE;
        memcpy(p + copied, buf, chunk);
        copied += chunk;
        b++;
    }

    if (out_len) {
        *out_len = ino->size;
    }
    return 0;
}

int tinyfs_list(tinyfs_dirent_t *out, int max_entries) {
    int n = 0;
    for (int i = 0; i < TINYFS_NUM_INODES && n < max_entries; i++) {
        if (inode_table[i].used) {
            memcpy(out[n].name, inode_table[i].name, TINYFS_NAME_MAX);
            out[n].size = inode_table[i].size;
            n++;
        }
    }
    return n;
}
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs.c (file state is current in your context — no need to Read it back)
agent · Jul 17, 19:14:56
Now the kernel-side disk wiring:
Write · Jul 17, 19:14:57 · toolu_01VBsLtDsXA9FjiBYXh5rMrt
/home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs_disk.h
#pragma once

/* Mounts tinyfs on top of the kernel's ATA driver. Returns 0 on success. */
int tinyfs_disk_init(void);
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs_disk.h (file state is current in your context — no need to Read it back)
Write · Jul 17, 19:15:01 · toolu_01PsePDD2MuvYrPQaBwGaAMB
/home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs_disk.c
#include "tinyfs_disk.h"

#include "../ata.h"
#include "tinyfs.h"

static void disk_block_read(uint32_t block, void *buf) {
    ata_read_sector(TINYFS_FS_START_LBA + block, buf);
}

static void disk_block_write(uint32_t block, const void *buf) {
    ata_write_sector(TINYFS_FS_START_LBA + block, buf);
}

int tinyfs_disk_init(void) {
    tinyfs_init(disk_block_read, disk_block_write);
    return tinyfs_mount();
}
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs_disk.c (file state is current in your context — no need to Read it back)
agent · Jul 17, 19:15:03
Now the host-side `mkfs_tinyfs` tool:
Write · Jul 17, 19:15:11 · toolu_01NHfSkbaPohGvoCEXT8vJVh
/home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c
69 lines
/* Host-side build tool: formats the tinyfs region of a disk image and
 * seeds it with initial files. Not part of the OS - infrastructure, like
 * the assembler/linker/QEMU, run at build time on the host. Reuses
 * kernel/fs/tinyfs.c verbatim (compiled natively here, freestanding in
 * the kernel) so there is exactly one implementation of the fs format. */

#include <stdio.h>
#include <stdlib.h>

#include "../kernel/fs/tinyfs.h"

static FILE *disk;

static void disk_block_read(uint32_t block, void *buf) {
    long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
    if (fseek(disk, offset, SEEK_SET) != 0 ||
        fread(buf, 1, TINYFS_BLOCK_SIZE, disk) != TINYFS_BLOCK_SIZE) {
        fprintf(stderr, "mkfs_tinyfs: read failed at block %u\n", block);
        exit(1);
    }
}

static void disk_block_write(uint32_t block, const void *buf) {
    long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
    if (fseek(disk, offset, SEEK_SET) != 0 ||
        fwrite(buf, 1, TINYFS_BLOCK_SIZE, disk) != TINYFS_BLOCK_SIZE) {
        fprintf(stderr, "mkfs_tinyfs: write failed at block %u\n", block);
        exit(1);
    }
}

static void seed_file(const char *name, const char *contents) {
    tinyfs_create(name);
    tinyfs_write(name, contents, (uint32_t)strlen_local(contents));
}

/* Avoids pulling in <string.h> just for one call - keeps the include list
 * minimal since tinyfs.h already brought in stdint.h. */
static size_t strlen_local(const char *s) {
    size_t n = 0;
    while (s[n]) {
        n++;
    }
    return n;
}

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s <disk-image>\n", argv[0]);
        return 1;
    }

    disk = fopen(argv[1], "r+b");
    if (!disk) {
        perror("fopen");
        return 1;
    }

    tinyfs_init(disk_block_read, disk_block_write);
    tinyfs_format();

    seed_file("hello.txt", "Hello, tinyfs!\n");

    fclose(disk);
    printf("mkfs_tinyfs: formatted %s (%u data blocks available)\n",
           argv[1], (unsigned)TINYFS_DATA_BLOCKS);
    return 0;
}
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c (file state is current in your context — no need to Read it back)
Edit · Jul 17, 19:15:16 · toolu_0143fkityFWmCCFMJaEvocSb
/home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c
- #include <stdio.h>
- #include <stdlib.h>
- 
- #include "../kernel/fs/tinyfs.h"
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ 
+ #include "../kernel/fs/tinyfs.h"
result
The file /home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c has been updated successfully. (file state is current in your context — no need to Read it back)
Edit · Jul 17, 19:15:20 · toolu_01JvVbACDWwkFrNzArvmHVm3
/home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c
- static void seed_file(const char *name, const char *contents) {
-     tinyfs_create(name);
-     tinyfs_write(name, contents, (uint32_t)strlen_local(contents));
- }
- 
- /* Avoids pulling in <string.h> just for one call - keeps the include list
-  * minimal since tinyfs.h already brought in stdint.h. */
- static size_t strlen_local(const char *s) {
-     size_t n = 0;
-     while (s[n]) {
-         n++;
-     }
-     return n;
- }
+ static void seed_file(const char *name, const char *contents) {
+     tinyfs_create(name);
+     tinyfs_write(name, contents, (uint32_t)strlen(contents));
+ }
result
The file /home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c has been updated successfully. (file state is current in your context — no need to Read it back)
Edit · Jul 17, 19:15:25 · toolu_01N2gGJV86EbHknsKb4pjThS
/home/martin/workspace/prototypes/boot-to-claude/kernel/link.ld
-     .bss : {
-         *(.bss*)
-         *(COMMON)
-     }
+     .bss : {
+         _bss_start = .;
+         *(.bss*)
+         *(COMMON)
+         _bss_end = .;
+     }
result
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/link.ld has been updated successfully. (file state is current in your context — no need to Read it back)
Edit · Jul 17, 19:15:30 · toolu_014xDNRhEzTKCQRuN9wDKqzy
/home/martin/workspace/prototypes/boot-to-claude/kernel/entry.S
32 lines
- section .text.entry
- global _start
- extern kmain
- 
- _start:
-     mov esp, stack_top
-     call kmain
- .hang:
-     hlt
-     jmp .hang
+ section .text.entry
+ global _start
+ extern kmain
+ extern _bss_start
+ extern _bss_end
+ 
+ _start:
+     mov esp, stack_top
+ 
+     ; Zero .bss: the raw disk-loaded kernel image has no zero-fill
+     ; guarantee (objcopy -O binary drops NOBITS sections entirely).
+     mov edi, _bss_start
+     mov ecx, _bss_end
+     sub ecx, edi
+     xor eax, eax
+     cld
+     rep stosb
+ 
+     call kmain
+ .hang:
+     hlt
+     jmp .hang
result
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/entry.S has been updated successfully. (file state is current in your context — no need to Read it back)
agent · Jul 17, 19:15:34
Now wire tinyfs into `kmain.c` and extend the self-test:
Edit · Jul 17, 19:15:37 · toolu_01BLa8wKNUptBw95krG17ZqS
/home/martin/workspace/prototypes/boot-to-claude/kernel/kmain.c
44 lines
- #include "console.h"
- #include "gdt.h"
- #include "idt.h"
- #include "memory.h"
- #include "selftest.h"
- #include "serial.h"
- 
- void kmain(void) {
-     serial_init();
-     console_clear();
- 
-     gdt_init();
-     idt_init();
-     memory_init();
- 
-     console_write("tiny-os: kernel entered\n");
-     serial_write("tiny-os: kernel entered\n");
- 
-     selftest_run();
+ #include "console.h"
+ #include "fs/tinyfs_disk.h"
+ #include "gdt.h"
+ #include "idt.h"
+ #include "memory.h"
+ #include "selftest.h"
+ #include "serial.h"
+ 
+ void kmain(void) {
+     serial_init();
+     console_clear();
+ 
+     gdt_init();
+     idt_init();
+     memory_init();
+ 
+     console_write("tiny-os: kernel entered\n");
+     serial_write("tiny-os: kernel entered\n");
+ 
+     if (tinyfs_disk_init() != 0) {
+         console_write("tinyfs: mount FAILED\n");
+         serial_write("tinyfs: mount FAILED\n");
+     }
+ 
+     selftest_run();
result
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/kmain.c has been updated successfully. (file state is current in your context — no need to Read it back)
agent · Jul 17, 19:15:40
Now extend `selftest.c` with an fs test:
Edit · Jul 17, 19:15:41 · toolu_013yhgBL8z9CbezSoWpCLWPT
/home/martin/workspace/prototypes/boot-to-claude/kernel/selftest.c
- #include "console.h"
- #include "memory.h"
- #include "serial.h"
- #include "string.h"
+ #include "console.h"
+ #include "fs/tinyfs.h"
+ #include "memory.h"
+ #include "serial.h"
+ #include "string.h"
result
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/selftest.c has been updated successfully. (file state is current in your context — no need to Read it back)
Edit · Jul 17, 19:15:48 · toolu_019KaV4nZ8kXKDGojzN2tM7N
/home/martin/workspace/prototypes/boot-to-claude/kernel/selftest.c
49 lines
- int selftest_run(void) {
-     ok = 1;
-     console_write("selftest: running\n");
-     serial_write("selftest: running\n");
- 
-     test_string();
-     test_memory();
+ static void test_fs(void) {
+     tinyfs_dirent_t entries[TINYFS_NUM_INODES];
+     int n = tinyfs_list(entries, TINYFS_NUM_INODES);
+     check(n >= 1, "tinyfs_list finds seeded files");
+ 
+     int found = 0;
+     for (int i = 0; i < n; i++) {
+         if (strcmp(entries[i].name, "hello.txt") == 0) {
+             found = 1;
+         }
+     }
+     check(found, "tinyfs_list contains hello.txt");
+ 
+     char buf[64];
+     uint32_t len = 0;
+     int rc = tinyfs_read("hello.txt", buf, sizeof(buf) - 1, &len);
+     check(rc == 0, "tinyfs_read hello.txt succeeds");
+     if (rc == 0) {
+         buf[len] = '\0';
+         check(strcmp(buf, "Hello, tinyfs!\n") == 0, "hello.txt content matches");
+     }
+ 
+     check(tinyfs_create("selftest.tmp") == 0, "tinyfs_create new file");
+     check(tinyfs_write("selftest.tmp", "abc123", 6) == 0, "tinyfs_write new file");
+     len = 0;
+     rc = tinyfs_read("selftest.tmp", buf, sizeof(buf) - 1, &len);
+     check(rc == 0 && len == 6, "tinyfs_read roundtrip length");
+     if (rc == 0) {
+         buf[len] = '\0';
+         check(strcmp(buf, "abc123") == 0, "tinyfs_read roundtrip content");
+     }
+     check(tinyfs_delete("selftest.tmp") == 0, "tinyfs_delete");
+ }
+ 
+ int selftest_run(void) {
+     ok = 1;
+     console_write("selftest: running\n");
+     serial_write("selftest: running\n");
+ 
+     test_string();
+     test_memory();
+     test_fs();
result
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/selftest.c has been updated successfully. (file state is current in your context — no need to Read it back)
Read · Jul 17, 19:15:54 · toolu_01ED3UkprD2E6uATCGLd6ygM
/home/martin/workspace/prototypes/boot-to-claude/Makefile
result
60 lines
1	AS      := nasm
2	CC      := gcc
3	LD      := ld
4	OBJCOPY := objcopy
5	QEMU    := qemu-system-i386
6	BUILD   := build
7	
8	CFLAGS  := -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib \
9	           -Wall -Wextra -O2 -std=c11 -Ikernel
10	LDFLAGS := -m elf_i386 -T kernel/link.ld -nostdlib
11	
12	BOOT_BIN   := $(BUILD)/boot.bin
13	STAGE2_BIN := $(BUILD)/stage2.bin
14	DISK_IMG   := $(BUILD)/disk.img
15	
16	KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \
17	                 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c
18	KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S
19	KERNEL_OBJS   := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \
20	                 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS))
21	KERNEL_ELF    := $(BUILD)/kernel.elf
22	KERNEL_BIN    := $(BUILD)/kernel.bin
23	
24	.PHONY: all run clean
25	
26	all: $(DISK_IMG)
27	
28	$(BUILD):
29		mkdir -p $(BUILD)
30	
31	$(BOOT_BIN): boot/boot.S | $(BUILD)
32		$(AS) -f bin -o $@ $<
33	
34	$(STAGE2_BIN): boot/stage2.S | $(BUILD)
35		$(AS) -f bin -o $@ $<
36	
37	$(BUILD)/%.o: kernel/%.S | $(BUILD)
38		$(AS) -f elf32 -o $@ $<
39	
40	$(BUILD)/%.o: kernel/%.c | $(BUILD)
41		$(CC) $(CFLAGS) -c -o $@ $<
42	
43	$(KERNEL_ELF): $(KERNEL_OBJS) kernel/link.ld
44		$(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS)
45	
46	$(KERNEL_BIN): $(KERNEL_ELF)
47		$(OBJCOPY) -O binary $< $@
48	
49	$(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN)
50		dd if=/dev/zero of=$@ bs=512 count=2880 status=none
51		dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none
52		dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none
53		dd if=$(KERNEL_BIN) of=$@ bs=512 seek=5 conv=notrunc status=none
54	
55	run: $(DISK_IMG)
56		$(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot
57	
58	clean:
59		rm -rf $(BUILD)
60	
Write · Jul 17, 19:16:08 · toolu_01VPhZLrf3pzHGZfcsWryAj9
/home/martin/workspace/prototypes/boot-to-claude/Makefile
81 lines
AS      := nasm
CC      := gcc
HOSTCC  := gcc
LD      := ld
OBJCOPY := objcopy
QEMU    := qemu-system-i386
BUILD   := build

CFLAGS  := -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib \
           -Wall -Wextra -O2 -std=c11 -Ikernel
LDFLAGS := -m elf_i386 -T kernel/link.ld -nostdlib

# Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h
DISK_SECTORS  := 4096
KERNEL_START_SECTOR := 5

BOOT_BIN   := $(BUILD)/boot.bin
STAGE2_BIN := $(BUILD)/stage2.bin
DISK_IMG   := $(BUILD)/disk.img

KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \
                 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \
                 kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c
KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S
KERNEL_OBJS   := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \
                 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS))
KERNEL_ELF    := $(BUILD)/kernel.elf
KERNEL_BIN    := $(BUILD)/kernel.bin

MKFS_BIN := $(BUILD)/mkfs_tinyfs
MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o

.PHONY: all run clean

all: $(DISK_IMG)

$(BUILD) $(BUILD)/fs:
	mkdir -p $@

$(BOOT_BIN): boot/boot.S | $(BUILD)
	$(AS) -f bin -o $@ $<

$(STAGE2_BIN): boot/stage2.S | $(BUILD)
	$(AS) -f bin -o $@ $<

$(BUILD)/%.o: kernel/%.S | $(BUILD) $(BUILD)/fs
	$(AS) -f elf32 -o $@ $<

$(BUILD)/%.o: kernel/%.c | $(BUILD) $(BUILD)/fs
	$(CC) $(CFLAGS) -c -o $@ $<

$(KERNEL_ELF): $(KERNEL_OBJS) kernel/link.ld
	$(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS)

$(KERNEL_BIN): $(KERNEL_ELF)
	$(OBJCOPY) -O binary $< $@

# --- host-side mkfs tool (build-time infrastructure, not kernel code) ---

$(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD)
	$(HOSTCC) -Wall -Wextra -O2 -c -o $@ $<

$(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD)
	$(HOSTCC) -Wall -Wextra -O2 -c -o $@ $<

$(MKFS_BIN): $(MKFS_OBJS)
	$(HOSTCC) -o $@ $(MKFS_OBJS)

$(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN)
	dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none
	dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none
	dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none
	dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none
	$(MKFS_BIN) $@

run: $(DISK_IMG)
	$(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot

clean:
	rm -rf $(BUILD)
result
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
agent · Jul 17, 19:16:10
Now the design doc, then build/test.
Write · Jul 17, 19:16:19 · toolu_01JAHMJ24WNPBkvQWunbY9qp
/home/martin/workspace/prototypes/boot-to-claude/docs/tinyfs.md
52 lines
# tinyfs

A minimal flat-namespace filesystem for tiny-os. Block size is 512 bytes,
matching the disk sector size, so no separate sector-buffering layer is
needed.

Constants live in `kernel/fs/tinyfs.h`, shared verbatim by the kernel
(freestanding build) and `tools/mkfs_tinyfs` (native host build, run at
image-build time to format and seed the disk).

## Disk layout

The fs region starts at LBA `TINYFS_FS_START_LBA` (128, i.e. 64KB into the
disk image), leaving room before it for the boot sector, stage2, and the
kernel image. All block numbers below are relative to that offset ("fs
block 0" = disk LBA 128).

| fs block(s) | contents |
|---|---|
| 0 | superblock |
| 1 | free-block bitmap (1 block = 4096 bits = up to 4096 data blocks / 2MB) |
| 2-9 | inode table (64 inodes x 64 bytes = 8 blocks) |
| 10.. | data blocks |

### Superblock (`tinyfs_superblock_t`)

`magic` (must equal `TINYFS_MAGIC`), `total_blocks`, `num_inodes`,
`data_blocks`.

### Inode (`tinyfs_inode_t`, 64 bytes, 8 per block)

`used` flag, `name` (26 chars + NUL, flat namespace - no directories),
`size` in bytes, and 8 direct data block numbers (`0` = unallocated). No
indirect blocks, so max file size is `8 * 512 = 4096` bytes
(`TINYFS_MAX_FILE_SIZE`) - deliberately small, in keeping with "tiny".

### Free-block bitmap

One bit per data block, index 0 = first data block (fs block 10).

## API (`kernel/fs/tinyfs.c`)

`tinyfs_init(read_fn, write_fn)` wires in the block I/O backend (ATA PIO in
the kernel, `fopen`/`fseek`/`fwrite` on the host). `tinyfs_format` is only
ever called by `mkfs_tinyfs`. The kernel calls `tinyfs_mount`, then
`tinyfs_create` / `tinyfs_read` / `tinyfs_write` / `tinyfs_delete` /
`tinyfs_list`.

There is exactly one implementation of the format - `tinyfs.c` is compiled
twice (once freestanding for the kernel, once natively for the host tool)
rather than duplicated.
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/docs/tinyfs.md (file state is current in your context — no need to Read it back)
Bash · Jul 17, 19:16:21 · toolu_01Tj3YbMwtkwU4ox3u5NDp6N
Rebuild with Phase 3 disk/filesystem additions
$ make clean >/dev/null && make 2>&1
result
mkdir -p build
nasm -f bin -o build/boot.bin boot/boot.S
nasm -f bin -o build/stage2.bin boot/stage2.S
mkdir -p build/fs
nasm -f elf32 -o build/entry.o kernel/entry.S
nasm -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/kmain.o kernel/kmain.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/console.o kernel/console.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/serial.o kernel/serial.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/gdt.o kernel/gdt.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/idt.o kernel/idt.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/string.o kernel/string.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/memory.o kernel/memory.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/selftest.o kernel/selftest.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/ata.o kernel/ata.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs.o kernel/fs/tinyfs.c
gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs_disk.o kernel/fs/tinyfs_disk.c
ld -m elf_i386 -T kernel/link.ld -nostdlib -o build/kernel.elf build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o
ld: warning: build/kernel.elf has a LOAD segment with RWX permissions
objcopy -O binary build/kernel.elf build/kernel.bin
gcc -Wall -Wextra -O2 -c -o build/host_tinyfs.o kernel/fs/tinyfs.c
gcc -Wall -Wextra -O2 -c -o build/host_mkfs_tinyfs.o tools/mkfs_tinyfs.c
gcc -o build/mkfs_tinyfs build/host_tinyfs.o build/host_mkfs_tinyfs.o
dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none
dd if=build/boot.bin of=build/disk.img conv=notrunc status=none
dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none
dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none
build/mkfs_tinyfs build/disk.img
mkfs_tinyfs: formatted build/disk.img (3958 data blocks available)
agent · Jul 17, 19:16:25
Clean build all around, mkfs formatted 3958 data blocks as expected. Now booting.
Bash · Jul 17, 19:16:26 · toolu_01DAFhdrTSQEyGsDrT2faZhz
Boot and verify self-test including fs checks
$ timeout 5 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial stdio -no-reboot; echo "exit=$?"
result
tiny-os: kernel entered
selftest: running
SELFTEST PASS
qemu-system-i386: terminating on signal 15 from pid 1875491 (timeout)
exit=124
agent · Jul 17, 19:16:33
All fs tests pass (list, read seeded file, create/write/read/delete roundtrip, coalescing). Committing Phase 3.
Bash · Jul 17, 19:16:34 · toolu_01KofWaVmxCQSMP3Jd7Ykd6z
Stage Phase 3 files
$ git add -A && git status --short
result
M  Makefile
A  docs/tinyfs.md
A  kernel/ata.c
A  kernel/ata.h
M  kernel/entry.S
A  kernel/fs/tinyfs.c
A  kernel/fs/tinyfs.h
A  kernel/fs/tinyfs_disk.c
A  kernel/fs/tinyfs_disk.h
M  kernel/kmain.c
M  kernel/link.ld
M  kernel/selftest.c
A  tools/mkfs_tinyfs.c