$ 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 )"
[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
{
"taskId": "3",
"status": "completed"
}Updated task #3 status
{
"taskId": "4",
"status": "in_progress"
}Updated task #4 status
$ mkdir -p /home/martin/workspace/prototypes/boot-to-claude/kernel/fs /home/martin/workspace/prototypes/boot-to-claude/docs
(Bash completed with no output)
#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);
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)
#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();
}
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)
#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);
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)
#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;
}
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)
#pragma once /* Mounts tinyfs on top of the kernel's ATA driver. Returns 0 on success. */ int tinyfs_disk_init(void);
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)
#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();
}
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)
/* 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;
}
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)
- #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"
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)
- 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));
+ }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)
- .bss : {
- *(.bss*)
- *(COMMON)
- }
+ .bss : {
+ _bss_start = .;
+ *(.bss*)
+ *(COMMON)
+ _bss_end = .;
+ }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)
- 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
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)
- #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();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)
- #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"
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)
- 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();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)
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
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)
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)
# 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.
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)
$ make clean >/dev/null && make 2>&1
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)
$ timeout 5 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial stdio -no-reboot; echo "exit=$?"
tiny-os: kernel entered selftest: running SELFTEST PASS qemu-system-i386: terminating on signal 15 from pid 1875491 (timeout) exit=124
$ git add -A && git status --short
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