6 Commits

Author SHA1 Message Date
AI
27b2042523 Implement mkfs.fat32 filesystem formatter app (AI)
Create a user-space utility that formats block devices with FAT32.
Writes all required on-disk structures sequentially:

- Boot sector (BPB) with BIOS Parameter Block at sector 0
- FSInfo sector at sector 1 with free cluster count/hint
- Backup boot sector at sector 6, backup FSInfo at sector 7
- Two FAT tables with entries 0-2 initialized (media marker,
  EOC markers, root directory cluster EOC)
- Root directory cluster with volume label entry

Geometry calculation:
- Sectors per cluster chosen based on volume size (1-32)
- FAT size computed using Microsoft's formula
- Supports volumes from 128 sectors (~64K) upward

Usage: mkfs.fat32 hdd1mbr1 14336 [LABEL]
The total sector count is required since there is no stat() syscall
yet. Users can find it via 'diskpart <dev> list'.

Also checked off diskpart in README (committed previously).
2026-02-23 16:33:12 +00:00
AI
7dc50aa57d Implement diskpart MBR partition editor app (AI)
Create a user-space diskpart utility that can view and modify MBR
partition tables on block devices accessible through /dev.

Features:
- list: displays all 4 partition slots with type, LBA start, size
- create <slot> <type> <start> <sectors>: creates a new partition
- delete <slot>: removes a partition entry
- activate/deactivate <slot>: sets or clears the bootable flag

The tool reads sector 0 (MBR) via VFS open/read, modifies the
partition table in memory, then writes it back via open/write.
Supports hex type codes and provides human-readable type names
for common partition types (FAT12/16/32, Linux, NTFS, etc.).

Usage: diskpart hdd1 create 1 0C 2048 14336
       diskpart hdd1 list
       diskpart hdd1 delete 2
2026-02-23 16:28:44 +00:00
AI
d3176345a1 Check off character device support - already implemented (AI)
The devicefs subsystem already has full character device support:
- devicefs_char_ops_t with read/write callbacks
- devicefs_register_char() registration function
- VFS read/write delegation to char ops
- VFS_CHARDEV type in readdir/finddir results
2026-02-23 16:21:25 +00:00
AI
31740f7556 Implement floppy disk controller driver (AI)
Add Intel 82077AA-compatible floppy disk controller driver with:
- CMOS-based drive detection (register 0x10)
- FDC reset with DOR toggle and SPECIFY command
- Motor control with spin-up delay
- ISA DMA channel 2 setup for data transfers
- LBA-to-CHS conversion for 1.44MB geometry
- Single-sector read/write via DMA with 7-byte result phase
- Seek and recalibrate with sense interrupt verification
- IRQ 6 handler (vector 38) for command completion
- Devicefs integration as 'floppy' class block devices

The IRQ wait function detects whether interrupts are enabled
(EFLAGS IF bit) and temporarily enables them if needed, allowing
the driver to work during early init before the kernel calls STI.
The scheduler safely handles timer interrupts in this window since
no user processes exist yet.

Tested with QEMU: drive detected as 1.44M 3.5", registered as
/dev/floppy1, full boot succeeds with CD+HDD+floppy attached.
2026-02-23 15:19:42 +00:00
AI
399d0242b7 Check off FAT32/mount tasks, remove diagnostic code 2026-02-23 15:06:27 +00:00
AI
5229758092 Add FAT32 driver, mount app, fix shell cd/path resolution
- FAT32 VFS driver (fat32.h, fat32.c): reads BPB, FAT table, cluster
  chains, directory entries (8.3 SFN), file read/write, mount via
  fat32_mount_device()
- mount app: writes 'device mountpoint' to /sys/vfs/mount to trigger
  FAT32 mount from userspace
- VFS sysfs mount namespace in kernel.c: handles mount requests via
  sysfs write to /sys/vfs/mount, delegates to FAT32 driver
- Shell cd: validates target directory exists before updating CWD,
  resolves relative paths (., .., components) to absolute paths
- Shell run_command: resolves ARG1 paths relative to CWD so apps
  like cat and ls receive absolute paths automatically
- Fixed FAT32 root directory access: use fs->root_cluster when
  VFS mount root node has inode=0
2026-02-23 14:52:41 +00:00
15 changed files with 3185 additions and 14 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -62,12 +62,12 @@ Once a task is completed, it should be checked off.
- [x] Create an IDE driver. It should enumerate all IDE devices, and expose them to the vfs driver as `hddN` or `cdN`, where N is a number that starts at 1 and increases for each new device. The `N` value should be calucated by the devicefs subsystem, not the IDE driver. - [x] Create an IDE driver. It should enumerate all IDE devices, and expose them to the vfs driver as `hddN` or `cdN`, where N is a number that starts at 1 and increases for each new device. The `N` value should be calucated by the devicefs subsystem, not the IDE driver.
- [x] Create an MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem. - [x] Create an MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem.
- [x] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information. - [x] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
- [ ] Create a FAT32 driver. It should allow reading and writing to and from a block device. - [x] Create a FAT32 driver. It should allow reading and writing to and from a block device.
- [ ] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount. - [x] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount.
- [ ] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`. - [x] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
- [ ] Add support for character device to the devicefs subsystem. - [x] Add support for character device to the devicefs subsystem.
- [ ] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device. - [x] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device.
- [ ] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem. - [x] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem.
- [ ] Create a network driver for the NE2000 NIC. - [ ] Create a network driver for the NE2000 NIC.
- [ ] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T. - [ ] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T.
- [ ] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`. - [ ] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`.

600
apps/diskpart/diskpart.c Normal file
View File

@@ -0,0 +1,600 @@
/**
* @file diskpart.c
* @brief MBR partition table editor.
*
* Allows viewing, creating, and deleting MBR partitions on block devices.
*
* Usage: diskpart <device> [command] [args...]
*
* Commands:
* list Show partition table (default)
* create <slot> <type> <start> <sectors> Create a partition
* delete <slot> Delete a partition
* activate <slot> Set bootable flag
* deactivate <slot> Clear bootable flag
*
* <slot> is 1-4 (MBR supports up to 4 primary partitions).
* <type> is a hex partition type code (e.g., 0C for FAT32 LBA).
* <start> and <sectors> are decimal LBA values.
*
* Examples:
* diskpart hdd1 list
* diskpart hdd1 create 1 0C 2048 14336
* diskpart hdd1 delete 2
* diskpart hdd1 activate 1
*/
#include "syscalls.h"
typedef unsigned char uint8_t;
/** MBR signature. */
#define MBR_SIG_LO 0x55
#define MBR_SIG_HI 0xAA
/** MBR partition entry offsets. */
#define MBR_PART_TABLE 446
#define MBR_PART_SIZE 16
/* ================================================================
* Numeric formatting helpers
* ================================================================ */
/**
* Print a 32-bit value as a hexadecimal string with "0x" prefix.
*/
static void print_hex(uint32_t val) {
char buf[11]; /* "0x" + 8 digits + NUL */
buf[0] = '0';
buf[1] = 'x';
for (int i = 9; i >= 2; i--) {
int nibble = (int)(val & 0xF);
buf[i] = (nibble < 10) ? (char)('0' + nibble)
: (char)('A' + nibble - 10);
val >>= 4;
}
buf[10] = '\0';
puts(buf);
}
/**
* Print a 32-bit value as a decimal string.
*/
static void print_dec(uint32_t val) {
char buf[12];
int pos = 11;
buf[pos] = '\0';
if (val == 0) {
buf[--pos] = '0';
} else {
while (val > 0 && pos > 0) {
buf[--pos] = (char)('0' + (val % 10));
val /= 10;
}
}
puts(&buf[pos]);
}
/**
* Parse a decimal string to uint32_t.
* Returns the parsed value; sets *ok to 1 on success, 0 on failure.
*/
static uint32_t parse_dec(const char *s, int *ok) {
uint32_t val = 0;
*ok = 0;
if (!s || !*s) return 0;
while (*s) {
if (*s < '0' || *s > '9') { *ok = 0; return 0; }
val = val * 10 + (uint32_t)(*s - '0');
s++;
}
*ok = 1;
return val;
}
/**
* Parse a hexadecimal string (without "0x" prefix) to uint32_t.
*/
static uint32_t parse_hex(const char *s, int *ok) {
uint32_t val = 0;
*ok = 0;
if (!s || !*s) return 0;
/* Skip optional "0x" prefix */
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
s += 2;
}
if (!*s) return 0;
while (*s) {
uint32_t digit;
if (*s >= '0' && *s <= '9') digit = (uint32_t)(*s - '0');
else if (*s >= 'a' && *s <= 'f') digit = (uint32_t)(*s - 'a' + 10);
else if (*s >= 'A' && *s <= 'F') digit = (uint32_t)(*s - 'A' + 10);
else { *ok = 0; return 0; }
val = (val << 4) | digit;
s++;
}
*ok = 1;
return val;
}
/* ================================================================
* Argument parsing
* ================================================================ */
/** Maximum number of tokens we parse from ARG1. */
#define MAX_TOKENS 8
/** Token buffer storage. */
static char token_buf[256];
static char *tokens[MAX_TOKENS];
static int token_count;
/**
* Split the ARG1 string into space-separated tokens.
*/
static void tokenize(const char *input) {
/* Copy input to mutable buffer */
int i = 0;
while (input[i] && i < 255) {
token_buf[i] = input[i];
i++;
}
token_buf[i] = '\0';
token_count = 0;
char *p = token_buf;
while (*p && token_count < MAX_TOKENS) {
/* Skip whitespace */
while (*p == ' ') p++;
if (!*p) break;
tokens[token_count++] = p;
/* Advance to next space or end */
while (*p && *p != ' ') p++;
if (*p) {
*p = '\0';
p++;
}
}
}
/* ================================================================
* Partition type name lookup
* ================================================================ */
/**
* Return a human-readable name for an MBR partition type code.
*/
static const char *type_name(uint32_t type) {
switch (type) {
case 0x00: return "Empty";
case 0x01: return "FAT12";
case 0x04: return "FAT16 <32M";
case 0x05: return "Extended";
case 0x06: return "FAT16 >=32M";
case 0x07: return "NTFS/HPFS";
case 0x0B: return "FAT32";
case 0x0C: return "FAT32 LBA";
case 0x0E: return "FAT16 LBA";
case 0x82: return "Linux swap";
case 0x83: return "Linux";
default: return "Unknown";
}
}
/* ================================================================
* MBR reading / writing
* ================================================================ */
/** MBR sector buffer (512 bytes). */
static uint8_t mbr[512];
/**
* Read a 32-bit little-endian value from a byte array.
*/
static uint32_t read32le(const uint8_t *p) {
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
/**
* Write a 32-bit little-endian value to a byte array.
*/
static void write32le(uint8_t *p, uint32_t val) {
p[0] = (uint8_t)(val & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
p[2] = (uint8_t)((val >> 16) & 0xFF);
p[3] = (uint8_t)((val >> 24) & 0xFF);
}
/**
* Read MBR (sector 0) from a device.
* @param dev_path Full path like "/dev/hdd1".
* @return 0 on success, -1 on failure.
*/
static int read_mbr(const char *dev_path) {
int32_t fd = open(dev_path, 0);
if (fd < 0) {
puts("diskpart: cannot open ");
puts(dev_path);
puts("\n");
return -1;
}
int32_t n = read(fd, mbr, 512);
close(fd);
if (n != 512) {
puts("diskpart: failed to read MBR (got ");
print_dec((uint32_t)(n < 0 ? 0 : n));
puts(" bytes)\n");
return -1;
}
return 0;
}
/**
* Write MBR (sector 0) to a device.
* @param dev_path Full path like "/dev/hdd1".
* @return 0 on success, -1 on failure.
*/
static int write_mbr(const char *dev_path) {
int32_t fd = open(dev_path, 0);
if (fd < 0) {
puts("diskpart: cannot open ");
puts(dev_path);
puts(" for writing\n");
return -1;
}
int32_t n = write(fd, mbr, 512);
close(fd);
if (n != 512) {
puts("diskpart: failed to write MBR\n");
return -1;
}
return 0;
}
/* ================================================================
* Commands
* ================================================================ */
/**
* Display the partition table.
*/
static void cmd_list(void) {
/* Check signature */
if (mbr[510] != MBR_SIG_LO || mbr[511] != MBR_SIG_HI) {
puts("No valid MBR signature found.\n");
puts("Use 'create' to initialize partitions (signature will be added).\n");
return;
}
puts("Slot Boot Type Start LBA Sectors Size\n");
puts("---- ---- ---------- ---------- ---------- ----------\n");
for (int i = 0; i < 4; i++) {
uint8_t *entry = &mbr[MBR_PART_TABLE + i * MBR_PART_SIZE];
uint8_t status = entry[0];
uint8_t type = entry[4];
uint32_t lba_start = read32le(&entry[8]);
uint32_t sectors = read32le(&entry[12]);
/* Slot number */
putchar(' ');
putchar((char)('1' + i));
puts(" ");
/* Boot flag */
if (status == 0x80)
puts(" * ");
else
puts(" ");
/* Type */
puts(" ");
print_hex(type);
puts(" ");
/* Type name (pad to ~10 chars) */
const char *tn = type_name(type);
puts(tn);
int tnlen = (int)strlen(tn);
for (int j = tnlen; j < 12; j++) putchar(' ');
/* Start LBA */
print_dec(lba_start);
/* Pad */
char tmp[12];
int tpos = 11;
tmp[tpos] = '\0';
uint32_t v = lba_start;
if (v == 0) { tmp[--tpos] = '0'; }
else { while (v > 0) { tmp[--tpos] = (char)('0' + v % 10); v /= 10; } }
for (int j = 11 - tpos; j < 12; j++) putchar(' ');
/* Sectors */
print_dec(sectors);
tpos = 11;
tmp[tpos] = '\0';
v = sectors;
if (v == 0) { tmp[--tpos] = '0'; }
else { while (v > 0) { tmp[--tpos] = (char)('0' + v % 10); v /= 10; } }
for (int j = 11 - tpos; j < 12; j++) putchar(' ');
/* Size in KiB or MiB */
uint32_t kib = sectors / 2;
if (kib >= 1024) {
print_dec(kib / 1024);
puts(" MiB");
} else {
print_dec(kib);
puts(" KiB");
}
puts("\n");
}
}
/**
* Create a partition.
* Args: <slot 1-4> <type hex> <start_lba> <sectors>
*/
static int cmd_create(void) {
if (token_count < 6) {
puts("Usage: diskpart <dev> create <slot> <type> <start> <sectors>\n");
puts(" slot: 1-4\n");
puts(" type: hex partition type (e.g., 0C for FAT32 LBA)\n");
puts(" start: start LBA (decimal)\n");
puts(" sectors: partition size in sectors (decimal)\n");
return -1;
}
int ok;
uint32_t slot = parse_dec(tokens[2], &ok);
if (!ok || slot < 1 || slot > 4) {
puts("diskpart: invalid slot (must be 1-4)\n");
return -1;
}
uint32_t type = parse_hex(tokens[3], &ok);
if (!ok || type > 0xFF || type == 0) {
puts("diskpart: invalid type (must be 01-FF hex)\n");
return -1;
}
uint32_t start = parse_dec(tokens[4], &ok);
if (!ok) {
puts("diskpart: invalid start LBA\n");
return -1;
}
uint32_t sectors = parse_dec(tokens[5], &ok);
if (!ok || sectors == 0) {
puts("diskpart: invalid sector count\n");
return -1;
}
uint32_t idx = slot - 1;
uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE];
/* Check if slot is already in use */
if (entry[4] != 0x00) {
puts("diskpart: slot ");
print_dec(slot);
puts(" is already in use (type ");
print_hex(entry[4]);
puts("). Delete it first.\n");
return -1;
}
/* Clear the entry */
memset(entry, 0, MBR_PART_SIZE);
/* Set type */
entry[4] = (uint8_t)type;
/* Set LBA start and sector count */
write32le(&entry[8], start);
write32le(&entry[12], sectors);
/* Ensure MBR signature is present */
mbr[510] = MBR_SIG_LO;
mbr[511] = MBR_SIG_HI;
puts("Created partition ");
print_dec(slot);
puts(": type=");
print_hex(type);
puts(" (");
puts(type_name(type));
puts(") start=");
print_dec(start);
puts(" sectors=");
print_dec(sectors);
puts("\n");
return 0;
}
/**
* Delete a partition.
*/
static int cmd_delete(void) {
if (token_count < 3) {
puts("Usage: diskpart <dev> delete <slot>\n");
return -1;
}
int ok;
uint32_t slot = parse_dec(tokens[2], &ok);
if (!ok || slot < 1 || slot > 4) {
puts("diskpart: invalid slot (must be 1-4)\n");
return -1;
}
uint32_t idx = slot - 1;
uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE];
if (entry[4] == 0x00) {
puts("diskpart: slot ");
print_dec(slot);
puts(" is already empty\n");
return 0;
}
uint8_t old_type = entry[4];
memset(entry, 0, MBR_PART_SIZE);
puts("Deleted partition ");
print_dec(slot);
puts(" (was type ");
print_hex(old_type);
puts(" - ");
puts(type_name(old_type));
puts(")\n");
return 0;
}
/**
* Set or clear the bootable flag on a partition.
*/
static int cmd_boot(int activate) {
if (token_count < 3) {
puts("Usage: diskpart <dev> ");
puts(activate ? "activate" : "deactivate");
puts(" <slot>\n");
return -1;
}
int ok;
uint32_t slot = parse_dec(tokens[2], &ok);
if (!ok || slot < 1 || slot > 4) {
puts("diskpart: invalid slot (must be 1-4)\n");
return -1;
}
uint32_t idx = slot - 1;
uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE];
if (entry[4] == 0x00) {
puts("diskpart: slot ");
print_dec(slot);
puts(" is empty\n");
return -1;
}
if (activate) {
/* Clear bootable flag on all other partitions */
for (int i = 0; i < 4; i++) {
mbr[MBR_PART_TABLE + i * MBR_PART_SIZE] = 0x00;
}
entry[0] = 0x80;
puts("Partition ");
print_dec(slot);
puts(" is now bootable\n");
} else {
entry[0] = 0x00;
puts("Partition ");
print_dec(slot);
puts(" is no longer bootable\n");
}
return 0;
}
/* ================================================================
* Main
* ================================================================ */
int main(void) {
char arg1[256];
if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') {
puts("diskpart - MBR partition table editor\n");
puts("\n");
puts("Usage: diskpart <device> [command] [args...]\n");
puts("\n");
puts("Commands:\n");
puts(" list Show partitions (default)\n");
puts(" create <slot> <type> <start> <sectors> Create partition\n");
puts(" delete <slot> Delete partition\n");
puts(" activate <slot> Set bootable flag\n");
puts(" deactivate <slot> Clear bootable flag\n");
puts("\n");
puts("Slot: 1-4, type: hex (e.g., 0C=FAT32 LBA, 83=Linux)\n");
return 1;
}
tokenize(arg1);
if (token_count < 1) {
puts("diskpart: no device specified\n");
return 1;
}
/* Token 0 is the device name. Build /dev/<device> path. */
char dev_path[64];
strcpy(dev_path, "/dev/");
/* Append device name */
char *dp = dev_path + 5;
char *dn = tokens[0];
while (*dn && dp < dev_path + 62) {
*dp++ = *dn++;
}
*dp = '\0';
/* Determine command (default is "list") */
const char *cmd = "list";
if (token_count >= 2) {
cmd = tokens[1];
}
/* For "list" we only read; for others we read-modify-write */
int need_write = 0;
if (strcmp(cmd, "list") == 0) {
/* Just read and display */
if (read_mbr(dev_path) != 0) return 1;
cmd_list();
return 0;
}
/* Read current MBR for modification commands */
if (read_mbr(dev_path) != 0) return 1;
int result = 0;
if (strcmp(cmd, "create") == 0) {
result = cmd_create();
if (result == 0) need_write = 1;
} else if (strcmp(cmd, "delete") == 0) {
result = cmd_delete();
if (result == 0) need_write = 1;
} else if (strcmp(cmd, "activate") == 0) {
result = cmd_boot(1);
if (result == 0) need_write = 1;
} else if (strcmp(cmd, "deactivate") == 0) {
result = cmd_boot(0);
if (result == 0) need_write = 1;
} else {
puts("diskpart: unknown command '");
puts(cmd);
puts("'\n");
return 1;
}
if (result != 0) return 1;
/* Write back modified MBR */
if (need_write) {
if (write_mbr(dev_path) != 0) {
puts("diskpart: WARNING - failed to write MBR!\n");
return 1;
}
puts("MBR written successfully.\n");
}
return 0;
}

View File

@@ -0,0 +1,511 @@
/**
* @file mkfs.fat32.c
* @brief Format a block device with a FAT32 filesystem.
*
* Writes the FAT32 boot sector (BPB), FSInfo sector, two File Allocation
* Tables, and an empty root directory cluster to the specified device.
*
* Usage: mkfs.fat32 <device> <total_sectors>
* e.g.: mkfs.fat32 hdd1mbr1 14336
*
* The device is accessed via /dev/<device>. Total sectors must be
* specified because the OS does not yet expose device size to userspace.
*/
#include "syscalls.h"
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
/* ================================================================
* Constants
* ================================================================ */
#define SECTOR_SIZE 512
#define RESERVED_SECTS 32
#define NUM_FATS 2
#define ROOT_CLUSTER 2
#define FSINFO_SECTOR 1
#define BACKUP_BOOT 6
/* ================================================================
* Helpers
* ================================================================ */
/**
* Print a 32-bit decimal value.
*/
static void print_dec(uint32_t val) {
char buf[12];
int pos = 11;
buf[pos] = '\0';
if (val == 0) {
buf[--pos] = '0';
} else {
while (val > 0 && pos > 0) {
buf[--pos] = (char)('0' + (val % 10));
val /= 10;
}
}
puts(&buf[pos]);
}
/**
* Print a hex value.
*/
static void print_hex(uint32_t val) {
char buf[11];
buf[0] = '0';
buf[1] = 'x';
for (int i = 9; i >= 2; i--) {
int n = (int)(val & 0xF);
buf[i] = (n < 10) ? (char)('0' + n) : (char)('A' + n - 10);
val >>= 4;
}
buf[10] = '\0';
puts(buf);
}
/**
* Parse decimal string.
*/
static uint32_t parse_dec(const char *s, int *ok) {
uint32_t val = 0;
*ok = 0;
if (!s || !*s) return 0;
while (*s) {
if (*s < '0' || *s > '9') { *ok = 0; return 0; }
val = val * 10 + (uint32_t)(*s - '0');
s++;
}
*ok = 1;
return val;
}
/**
* Write a 16-bit little-endian value.
*/
static void w16(uint8_t *p, uint32_t val) {
p[0] = (uint8_t)(val & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
}
/**
* Write a 32-bit little-endian value.
*/
static void w32(uint8_t *p, uint32_t val) {
p[0] = (uint8_t)(val & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
p[2] = (uint8_t)((val >> 16) & 0xFF);
p[3] = (uint8_t)((val >> 24) & 0xFF);
}
/* ================================================================
* Argument parsing
* ================================================================ */
#define MAX_TOKENS 4
static char tok_buf[256];
static char *tokens[MAX_TOKENS];
static int token_count;
static void tokenize(const char *input) {
int i = 0;
while (input[i] && i < 255) { tok_buf[i] = input[i]; i++; }
tok_buf[i] = '\0';
token_count = 0;
char *p = tok_buf;
while (*p && token_count < MAX_TOKENS) {
while (*p == ' ') p++;
if (!*p) break;
tokens[token_count++] = p;
while (*p && *p != ' ') p++;
if (*p) { *p = '\0'; p++; }
}
}
/* ================================================================
* Sector buffer (reused for each write)
* ================================================================ */
static uint8_t sector[SECTOR_SIZE];
/**
* Write the sector buffer to the device fd.
* Returns 0 on success, -1 on failure.
*/
static int write_sector(int32_t fd) {
int32_t n = write(fd, sector, SECTOR_SIZE);
return (n == SECTOR_SIZE) ? 0 : -1;
}
/**
* Write N zero sectors to the device fd.
*/
static int write_zero_sectors(int32_t fd, uint32_t count) {
memset(sector, 0, SECTOR_SIZE);
for (uint32_t i = 0; i < count; i++) {
if (write_sector(fd) != 0) return -1;
}
return 0;
}
/* ================================================================
* FAT32 geometry calculation
* ================================================================ */
/**
* Determine sectors per cluster based on volume size.
* Uses Microsoft's recommended values for FAT32.
*/
static uint32_t choose_spc(uint32_t total_sectors) {
/* For small volumes, use small clusters to maximize cluster count.
* FAT32 "officially" needs >= 65525 clusters, but our driver
* doesn't enforce this minimum. */
if (total_sectors <= 16384) return 1; /* <= 8 MiB: 512B */
if (total_sectors <= 131072) return 8; /* <= 64 MiB: 4K */
if (total_sectors <= 524288) return 8; /* <= 256 MiB: 4K */
if (total_sectors <= 16777216) return 8; /* <= 8 GiB: 4K */
if (total_sectors <= 33554432) return 16; /* <= 16 GiB: 8K */
return 32; /* > 16 GiB: 16K */
}
/**
* Calculate FAT size in sectors using Microsoft's formula.
*
* fat_size = ceil((total - reserved) / (entries_per_sec * spc + num_fats))
* where entries_per_sec = 128 (512 / 4 bytes per FAT32 entry).
*/
static uint32_t calc_fat_size(uint32_t total_sectors, uint32_t spc) {
uint32_t data_area = total_sectors - RESERVED_SECTS;
uint32_t tmp = 128 * spc + NUM_FATS;
return (data_area + tmp - 1) / tmp;
}
/* ================================================================
* BPB / Boot sector construction
* ================================================================ */
/**
* Build the FAT32 boot sector (BPB) into the sector buffer.
*/
static void build_boot_sector(uint32_t total_sectors, uint32_t spc,
uint32_t fat_size, const char *label) {
memset(sector, 0, SECTOR_SIZE);
/* Jump instruction: EB 58 90 (jump over BPB to boot code area) */
sector[0] = 0xEB;
sector[1] = 0x58;
sector[2] = 0x90;
/* OEM Name (offset 3, 8 bytes) */
memcpy(&sector[3], "CLAUDEOS", 8);
/* BPB — BIOS Parameter Block */
w16(&sector[11], SECTOR_SIZE); /* bytes_per_sector */
sector[13] = (uint8_t)spc; /* sectors_per_cluster */
w16(&sector[14], RESERVED_SECTS); /* reserved_sectors */
sector[16] = NUM_FATS; /* num_fats */
w16(&sector[17], 0); /* root_entry_count (0 for FAT32) */
w16(&sector[19], 0); /* total_sectors_16 (0, use 32-bit) */
sector[21] = 0xF8; /* media_type (hard disk) */
w16(&sector[22], 0); /* fat_size_16 (0, use 32-bit) */
w16(&sector[24], 63); /* sectors_per_track */
w16(&sector[26], 255); /* num_heads */
w32(&sector[28], 0); /* hidden_sectors */
w32(&sector[32], total_sectors); /* total_sectors_32 */
/* FAT32 Extended BPB (offset 36) */
w32(&sector[36], fat_size); /* fat_size_32 */
w16(&sector[40], 0); /* ext_flags */
w16(&sector[42], 0); /* fs_version */
w32(&sector[44], ROOT_CLUSTER); /* root_cluster */
w16(&sector[48], FSINFO_SECTOR); /* fs_info sector */
w16(&sector[50], BACKUP_BOOT); /* backup_boot sector */
/* Reserved (12 bytes at offset 52, already zeroed) */
sector[64] = 0x80; /* drive_number (HDD) */
sector[65] = 0; /* reserved1 */
sector[66] = 0x29; /* boot_sig (extended boot sig) */
/* Volume serial number (we'll use a simple hash) */
uint32_t serial = total_sectors ^ (spc << 16) ^ 0xC1A0DE05;
w32(&sector[67], serial);
/* Volume label (11 bytes, space-padded) */
int li = 0;
while (label[li] && li < 11) {
char c = label[li];
/* Convert to uppercase for FAT compatibility */
if (c >= 'a' && c <= 'z') c = (char)(c - 32);
sector[71 + li] = (uint8_t)c;
li++;
}
while (li < 11) {
sector[71 + li] = ' ';
li++;
}
/* FS type string (8 bytes) */
memcpy(&sector[82], "FAT32 ", 8);
/* Boot sector signature */
sector[510] = 0x55;
sector[511] = 0xAA;
}
/* ================================================================
* FSInfo sector construction
* ================================================================ */
/**
* Build the FSInfo sector into the sector buffer.
*/
static void build_fsinfo(uint32_t free_clusters, uint32_t next_free) {
memset(sector, 0, SECTOR_SIZE);
/* FSInfo signatures */
w32(&sector[0], 0x41615252); /* Lead signature */
w32(&sector[484], 0x61417272); /* Structure signature */
w32(&sector[488], free_clusters); /* Free cluster count */
w32(&sector[492], next_free); /* Next free cluster hint */
w32(&sector[508], 0xAA550000); /* Trail signature */
}
/* ================================================================
* FAT table construction
* ================================================================ */
/**
* Build the first sector of the FAT into the sector buffer.
* Entry 0 = media byte marker, Entry 1 = EOC, Entry 2 = EOC (root dir).
*/
static void build_fat_first_sector(void) {
memset(sector, 0, SECTOR_SIZE);
/* FAT entry 0: media type in low byte, rest 0xFF */
w32(&sector[0], 0x0FFFFFF8);
/* FAT entry 1: end-of-chain marker */
w32(&sector[4], 0x0FFFFFFF);
/* FAT entry 2: root directory cluster (end-of-chain) */
w32(&sector[8], 0x0FFFFFFF);
}
/* ================================================================
* Root directory construction
* ================================================================ */
/**
* Build the root directory cluster's first sector.
* Contains a volume label entry only.
*/
static void build_root_dir(const char *label) {
memset(sector, 0, SECTOR_SIZE);
/* Volume label directory entry (32 bytes) */
int li = 0;
while (label[li] && li < 11) {
char c = label[li];
if (c >= 'a' && c <= 'z') c = (char)(c - 32);
sector[li] = (uint8_t)c;
li++;
}
while (li < 11) {
sector[li] = ' ';
li++;
}
sector[11] = 0x08; /* ATTR_VOLUME_ID */
}
/* ================================================================
* Main formatting routine
* ================================================================ */
/**
* Format the device with FAT32.
*
* Writes sectors sequentially from sector 0:
* [0] Boot sector (BPB)
* [1] FSInfo
* [2..5] Zero (reserved)
* [6] Backup boot sector
* [7] Backup FSInfo
* [8..31] Zero (reserved)
* [32..32+fat_size-1] FAT 1
* [32+fat_size..32+2*fat_size-1] FAT 2
* [data_start..data_start+spc-1] Root directory cluster
*/
static int format_device(const char *dev_path, uint32_t total_sectors,
const char *label) {
uint32_t spc = choose_spc(total_sectors);
uint32_t fat_size = calc_fat_size(total_sectors, spc);
uint32_t data_start = RESERVED_SECTS + NUM_FATS * fat_size;
uint32_t data_sectors = total_sectors - data_start;
uint32_t total_clusters = data_sectors / spc;
uint32_t free_clusters = total_clusters - 1; /* Root dir uses cluster 2 */
/* Print format parameters */
puts("Formatting ");
puts(dev_path);
puts(" as FAT32\n");
puts(" Total sectors: ");
print_dec(total_sectors);
puts("\n Sectors/cluster: ");
print_dec(spc);
puts("\n Reserved sectors: ");
print_dec(RESERVED_SECTS);
puts("\n FAT size (sectors): ");
print_dec(fat_size);
puts("\n Number of FATs: ");
print_dec(NUM_FATS);
puts("\n Data start sector: ");
print_dec(data_start);
puts("\n Total clusters: ");
print_dec(total_clusters);
puts("\n Volume label: ");
puts(label);
puts("\n");
/* Sanity checks */
if (total_sectors < 128) {
puts("Error: volume too small for FAT32\n");
return -1;
}
if (data_start >= total_sectors) {
puts("Error: FAT tables don't fit in volume\n");
return -1;
}
/* Open device for writing */
int32_t fd = open(dev_path, 0);
if (fd < 0) {
puts("Error: cannot open ");
puts(dev_path);
puts("\n");
return -1;
}
puts("Writing boot sector...\n");
/* Sector 0: Boot sector */
build_boot_sector(total_sectors, spc, fat_size, label);
if (write_sector(fd) != 0) goto fail;
/* Sector 1: FSInfo */
build_fsinfo(free_clusters, 3); /* Next free = cluster 3 */
if (write_sector(fd) != 0) goto fail;
/* Sectors 2-5: zero */
if (write_zero_sectors(fd, 4) != 0) goto fail;
/* Sector 6: Backup boot sector */
build_boot_sector(total_sectors, spc, fat_size, label);
if (write_sector(fd) != 0) goto fail;
/* Sector 7: Backup FSInfo */
build_fsinfo(free_clusters, 3);
if (write_sector(fd) != 0) goto fail;
/* Sectors 8-31: zero (remaining reserved area) */
if (write_zero_sectors(fd, RESERVED_SECTS - 8) != 0) goto fail;
puts("Writing FAT 1...\n");
/* FAT 1: first sector has entries 0-2 */
build_fat_first_sector();
if (write_sector(fd) != 0) goto fail;
/* FAT 1: remaining sectors (all zero = free) */
if (fat_size > 1) {
if (write_zero_sectors(fd, fat_size - 1) != 0) goto fail;
}
puts("Writing FAT 2...\n");
/* FAT 2: identical copy */
build_fat_first_sector();
if (write_sector(fd) != 0) goto fail;
if (fat_size > 1) {
if (write_zero_sectors(fd, fat_size - 1) != 0) goto fail;
}
puts("Writing root directory...\n");
/* Root directory cluster (first sector has volume label) */
build_root_dir(label);
if (write_sector(fd) != 0) goto fail;
/* Remaining sectors in root cluster (zero) */
if (spc > 1) {
if (write_zero_sectors(fd, spc - 1) != 0) goto fail;
}
close(fd);
puts("Format complete!\n");
puts(" ");
print_dec(data_start + spc);
puts(" sectors written\n");
return 0;
fail:
close(fd);
puts("Error: write failed!\n");
return -1;
}
/* ================================================================
* Main
* ================================================================ */
int main(void) {
char arg1[256];
if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') {
puts("mkfs.fat32 - Format a device with FAT32\n");
puts("\n");
puts("Usage: mkfs.fat32 <device> <total_sectors> [label]\n");
puts("\n");
puts(" device: Device name (e.g., hdd1mbr1)\n");
puts(" total_sectors: Number of 512-byte sectors\n");
puts(" label: Volume label (default: CLAUDEOS)\n");
puts("\n");
puts("Example: mkfs.fat32 hdd1mbr1 14336\n");
return 1;
}
tokenize(arg1);
if (token_count < 2) {
puts("mkfs.fat32: need <device> and <total_sectors>\n");
puts(" Use 'diskpart <dev> list' to find sector count.\n");
return 1;
}
/* Build /dev/<device> path */
char dev_path[64];
strcpy(dev_path, "/dev/");
char *dp = dev_path + 5;
char *dn = tokens[0];
while (*dn && dp < dev_path + 62) *dp++ = *dn++;
*dp = '\0';
/* Parse total sectors */
int ok;
uint32_t total_sectors = parse_dec(tokens[1], &ok);
if (!ok || total_sectors == 0) {
puts("mkfs.fat32: invalid sector count '");
puts(tokens[1]);
puts("'\n");
return 1;
}
/* Volume label (default "CLAUDEOS") */
const char *label = "CLAUDEOS";
if (token_count >= 3) {
label = tokens[2];
}
return (format_device(dev_path, total_sectors, label) == 0) ? 0 : 1;
}

95
apps/mount/mount.c Normal file
View File

@@ -0,0 +1,95 @@
/**
* @file mount.c
* @brief Mount a block device as FAT32.
*
* Writes to /sys/vfs/mount to trigger a kernel-level FAT32 mount.
*
* Usage: mount <device> <path>
* e.g.: mount hdd1mbr1 /mnt
*/
#include "syscalls.h"
int main(void) {
char device[64];
char path[128];
if (getenv("ARG1", device, sizeof(device)) < 0 || device[0] == '\0') {
puts("Usage: mount <device> <mountpoint>\n");
puts(" e.g.: mount hdd1mbr1 /mnt\n");
return 1;
}
/* ARG1 contains "device path" — we need to split it.
* Actually, the shell currently only passes the first word as ARG1.
* Let's accept: mount <device> <path> where ARG1 = "device path" */
/* Find the space separating device and path */
int i = 0;
while (device[i] && device[i] != ' ') i++;
if (device[i] == ' ') {
/* ARG1 contains both: "hdd1mbr1 /mnt" */
device[i] = '\0';
/* Copy path part */
int j = i + 1;
while (device[j] == ' ') j++;
int pi = 0;
while (device[j] && pi < 127) {
path[pi++] = device[j++];
}
path[pi] = '\0';
} else {
/* Only device, no path — default to /mnt */
path[0] = '/'; path[1] = 'm'; path[2] = 'n'; path[3] = 't';
path[4] = '\0';
}
if (path[0] == '\0') {
puts("mount: missing mountpoint, using /mnt\n");
path[0] = '/'; path[1] = 'm'; path[2] = 'n'; path[3] = 't';
path[4] = '\0';
}
/* Build the mount command: "device path" */
char cmd[192];
int pos = 0;
for (int k = 0; device[k] && pos < 190; k++) cmd[pos++] = device[k];
cmd[pos++] = ' ';
for (int k = 0; path[k] && pos < 190; k++) cmd[pos++] = path[k];
cmd[pos] = '\0';
/* Open /sys/vfs/mount and write the command */
int32_t fd = open("/sys/vfs/mount", 0);
if (fd < 0) {
puts("mount: cannot open /sys/vfs/mount\n");
return 1;
}
int32_t n = write(fd, cmd, (uint32_t)pos);
close(fd);
if (n < 0) {
/* Read the status to show the error */
fd = open("/sys/vfs/mounts", 0);
if (fd >= 0) {
char status[256];
int32_t sn = read(fd, status, sizeof(status) - 1);
close(fd);
if (sn > 0) {
status[sn] = '\0';
puts(status);
}
} else {
puts("mount: failed\n");
}
return 1;
}
puts("mount: ");
puts(device);
puts(" mounted at ");
puts(path);
puts("\n");
return 0;
}

View File

@@ -56,21 +56,177 @@ static char *find_space(char *s) {
return s; return s;
} }
/** String length. */
static int slen(const char *s) {
int n = 0;
while (s[n]) n++;
return n;
}
/** Resolve an absolute path from cwd + a possibly relative path.
* Handles '..', '.', and trailing slashes. Result is always absolute. */
static void resolve_path(const char *cwd, const char *arg, char *out, int out_sz) {
char tmp[256];
int ti = 0;
/* If arg is absolute, start from it; otherwise prepend cwd */
if (arg[0] == '/') {
/* copy arg into tmp */
for (int i = 0; arg[i] && ti < 255; i++)
tmp[ti++] = arg[i];
} else {
/* copy cwd */
for (int i = 0; cwd[i] && ti < 255; i++)
tmp[ti++] = cwd[i];
/* ensure separator */
if (ti > 0 && tmp[ti - 1] != '/' && ti < 255)
tmp[ti++] = '/';
/* append arg */
for (int i = 0; arg[i] && ti < 255; i++)
tmp[ti++] = arg[i];
}
tmp[ti] = '\0';
/* Now canonicalize: split on '/' and process each component */
/* Stack of component start offsets */
int comp_starts[64];
int comp_lens[64];
int depth = 0;
int i = 0;
while (tmp[i]) {
/* skip slashes */
while (tmp[i] == '/') i++;
if (!tmp[i]) break;
/* find end of component */
int start = i;
while (tmp[i] && tmp[i] != '/') i++;
int len = i - start;
if (len == 1 && tmp[start] == '.') {
/* current dir: skip */
continue;
} else if (len == 2 && tmp[start] == '.' && tmp[start + 1] == '.') {
/* parent dir: pop */
if (depth > 0) depth--;
} else {
/* normal component: push */
if (depth < 64) {
comp_starts[depth] = start;
comp_lens[depth] = len;
depth++;
}
}
}
/* Build output */
int oi = 0;
if (depth == 0) {
if (oi < out_sz - 1) out[oi++] = '/';
} else {
for (int d = 0; d < depth && oi < out_sz - 2; d++) {
out[oi++] = '/';
for (int j = 0; j < comp_lens[d] && oi < out_sz - 1; j++)
out[oi++] = tmp[comp_starts[d] + j];
}
}
out[oi] = '\0';
}
/** Built-in: cd <path> */ /** Built-in: cd <path> */
static void builtin_cd(char *arg) { static void builtin_cd(char *arg) {
if (!arg || !*arg) { if (!arg || !*arg) {
arg = "/"; arg = "/";
} }
/* Update CWD environment variable */ /* Get current working directory */
setenv("CWD", arg);
/* Verify it was set */
char cwd[128]; char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) >= 0) { if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
puts("cd: "); strcpy(cwd, "/");
puts(cwd);
putchar('\n');
} }
/* Resolve the path (handles relative, .., etc.) */
char resolved[256];
resolve_path(cwd, arg, resolved, sizeof(resolved));
/* Validate: check if the directory exists by trying to readdir it.
* Root "/" always exists. For others, readdir index 0 must not fail
* with -1 unless the dir is simply empty, so we also accept stat-like
* checks. A simple heuristic: if readdir returns -1 for index 0 AND
* the path is not "/", we check if the *parent* can list it. */
if (resolved[0] == '/' && resolved[1] != '\0') {
/* Try listing the first entry; if the path is a valid directory,
* readdir(path, 0, ...) returns >= 0 even for an empty dir
* (it returns -1 meaning "end"), BUT if the path doesn't exist
* at all, the VFS resolve_path will fail and readdir returns -1.
*
* Better approach: try to readdir the parent and look for this
* entry among its children. */
char name[128];
int32_t type = readdir(resolved, 0, name);
/* type >= 0 means there's at least one entry => directory exists.
* type == -1 could mean empty dir or non-existent.
* For empty dirs: the directory itself was resolved successfully.
* For non-existent: the VFS resolution failed.
*
* Our VFS readdir calls resolve_path internally. If the path
* doesn't exist, it returns -1. If it exists but is empty,
* it also returns -1. We need to distinguish these.
* Use a stat-like approach: try to open the path. */
/* Actually the simplest check: try readdir on the *parent* and
* see if our target component exists as a directory entry. */
char parent[256];
char target[128];
/* Split resolved into parent + target */
int rlen = slen(resolved);
int last_slash = 0;
for (int i = 0; i < rlen; i++) {
if (resolved[i] == '/') last_slash = i;
}
if (last_slash == 0) {
/* Parent is root */
parent[0] = '/';
parent[1] = '\0';
strcpy(target, resolved + 1);
} else {
memcpy(parent, resolved, (uint32_t)last_slash);
parent[last_slash] = '\0';
strcpy(target, resolved + last_slash + 1);
}
/* Search parent directory for target */
int found = 0;
uint32_t idx = 0;
int32_t etype;
while ((etype = readdir(parent, idx, name)) >= 0) {
if (strcmp(name, target) == 0) {
if (etype == 2) {
found = 1; /* It's a directory */
} else {
puts("cd: ");
puts(resolved);
puts(": not a directory\n");
return;
}
break;
}
idx++;
}
if (!found) {
puts("cd: ");
puts(resolved);
puts(": no such directory\n");
return;
}
}
/* Update CWD environment variable */
setenv("CWD", resolved);
} }
/** Built-in: env - print all known env vars. */ /** Built-in: env - print all known env vars. */
@@ -104,6 +260,16 @@ static void builtin_help(void) {
puts("External commands are loaded from initrd.\n"); puts("External commands are loaded from initrd.\n");
} }
/** Check if a string looks like a path (contains / or starts with .) */
static int looks_like_path(const char *s) {
if (!s || !*s) return 0;
if (s[0] == '.' || s[0] == '/') return 1;
for (int i = 0; s[i]; i++) {
if (s[i] == '/') return 1;
}
return 0;
}
/** Execute an external command via fork+exec. */ /** Execute an external command via fork+exec. */
static void run_command(const char *cmd, const char *arg) { static void run_command(const char *cmd, const char *arg) {
int32_t pid = fork(); int32_t pid = fork();
@@ -115,7 +281,19 @@ static void run_command(const char *cmd, const char *arg) {
if (pid == 0) { if (pid == 0) {
/* Child: set ARG1 if there's an argument */ /* Child: set ARG1 if there's an argument */
if (arg && *arg) { if (arg && *arg) {
setenv("ARG1", arg); /* If the argument looks like a path and isn't absolute,
* resolve it relative to CWD so apps get absolute paths */
if (looks_like_path(arg) && arg[0] != '/') {
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
strcpy(cwd, "/");
}
char resolved_arg[256];
resolve_path(cwd, arg, resolved_arg, sizeof(resolved_arg));
setenv("ARG1", resolved_arg);
} else {
setenv("ARG1", arg);
}
} else { } else {
setenv("ARG1", ""); setenv("ARG1", "");
} }

53
build.log Normal file
View File

@@ -0,0 +1,53 @@
[ 3%] Building user-mode applications
Building app: cat
Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes)
Building app: diskpart
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/diskpart.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/diskpart (8406 bytes)
Building app: env-test
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/env-test.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/env-test (389 bytes)
Building app: fork-test
Built: /workspaces/claude-os/build/apps_bin/fork-test (132 bytes)
Building app: hello-world
Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes)
Building app: ls
Built: /workspaces/claude-os/build/apps_bin/ls (250 bytes)
Building app: mkfs.fat32
/workspaces/claude-os/apps/mkfs.fat32/mkfs.fat32.c:56:13: warning: unused function 'print_hex' [-Wunused-function]
56 | static void print_hex(uint32_t val) {
| ^~~~~~~~~
1 warning generated.
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/mkfs.fat32.elf has a LOAD segment with RWX permissions
Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes)
Building app: mount
Built: /workspaces/claude-os/build/apps_bin/mount (992 bytes)
Building app: sh
/workspaces/claude-os/apps/sh/sh.c:167:17: warning: unused variable 'type' [-Wunused-variable]
167 | int32_t type = readdir(resolved, 0, name);
| ^~~~
1 warning generated.
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
[ 3%] Built target apps
[ 6%] Generating CPIO initial ramdisk
Generated initrd: 20288 bytes
[ 6%] Built target initrd
[ 96%] Built target kernel
[100%] Generating bootable ISO image
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso'
Media current: stdio file, overwriteable
Media status : is blank
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
Added to ISO image: directory '/'='/tmp/grub.bEiDnH'
xorriso : UPDATE : 581 files added in 1 seconds
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
xorriso : UPDATE : 586 files added in 1 seconds
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
xorriso : UPDATE : Thank you for being patient. Working since 0 seconds.
ISO image produced: 5924 sectors
Written to medium : 5924 sectors at LBA 0
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
[100%] Built target iso

View File

@@ -23,6 +23,8 @@ add_executable(kernel
sysfs.c sysfs.c
ide.c ide.c
mbr.c mbr.c
fat32.c
floppy.c
env.c env.c
keyboard.c keyboard.c
interrupts.S interrupts.S

View File

@@ -335,6 +335,8 @@ devicefs_device_t *devicefs_find(const char *name) {
return NULL; return NULL;
} }
int init_devicefs(void) { int init_devicefs(void) {
memset(devices, 0, sizeof(devices)); memset(devices, 0, sizeof(devices));
device_count = 0; device_count = 0;

673
src/fat32.c Normal file
View File

@@ -0,0 +1,673 @@
/**
* @file fat32.c
* @brief FAT32 filesystem driver implementation.
*
* Reads and writes FAT32 filesystems on block devices. Supports:
* - Reading the BPB and FAT
* - Following cluster chains
* - Reading directory entries (short 8.3 names)
* - Reading and writing file data
* - Creating new files
*
* Mounted via fat32_mount_device() which registers VFS operations.
*/
#include "fat32.h"
#include "vfs.h"
#include "kmalloc.h"
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/** Maximum number of simultaneous FAT32 mounts. */
#define FAT32_MAX_MOUNTS 4
/** Sector buffer size (one sector). */
#define SECTOR_SIZE 512
/** Static mount contexts. */
static fat32_fs_t fat32_mounts[FAT32_MAX_MOUNTS];
static int fat32_mount_count = 0;
/* ================================================================
* Low-level block I/O helpers
* ================================================================ */
/**
* Read sectors from the underlying block device.
*
* @param fs FAT32 filesystem instance.
* @param lba Logical block address (absolute, 0 = device start).
* @param count Number of sectors to read.
* @param buf Output buffer (must be count * 512 bytes).
* @return 0 on success, -1 on error.
*/
static int fat32_read_sectors(fat32_fs_t *fs, uint32_t lba,
uint32_t count, void *buf) {
if (!fs->dev || !fs->dev->block_ops || !fs->dev->block_ops->read_sectors) {
return -1;
}
return fs->dev->block_ops->read_sectors(fs->dev->dev_data, lba, count, buf);
}
/**
* Write sectors to the underlying block device.
*/
static int fat32_write_sectors(fat32_fs_t *fs, uint32_t lba,
uint32_t count, const void *buf) {
if (!fs->dev || !fs->dev->block_ops || !fs->dev->block_ops->write_sectors) {
return -1;
}
return fs->dev->block_ops->write_sectors(fs->dev->dev_data, lba, count, buf);
}
/* ================================================================
* FAT table operations
* ================================================================ */
/**
* Read a single FAT entry (the next cluster in the chain).
*
* @param fs Filesystem instance.
* @param cluster Current cluster number.
* @return Next cluster number, or FAT32_EOC if end of chain.
*/
static uint32_t fat32_read_fat(fat32_fs_t *fs, uint32_t cluster) {
/* Each FAT entry is 4 bytes. Calculate which sector and offset. */
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector = fs->fat_start_lba + (fat_offset / SECTOR_SIZE);
uint32_t entry_offset = fat_offset % SECTOR_SIZE;
uint8_t sector_buf[SECTOR_SIZE];
if (fat32_read_sectors(fs, fat_sector, 1, sector_buf) != 0) {
return FAT32_EOC;
}
uint32_t entry = *(uint32_t *)(sector_buf + entry_offset);
return entry & 0x0FFFFFFF; /* Mask off top 4 bits */
}
/**
* Write a FAT entry.
*
* @param fs Filesystem instance.
* @param cluster Cluster to update.
* @param value New value for the FAT entry.
* @return 0 on success, -1 on error.
*/
static int fat32_write_fat(fat32_fs_t *fs, uint32_t cluster,
uint32_t value) {
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector = fs->fat_start_lba + (fat_offset / SECTOR_SIZE);
uint32_t entry_offset = fat_offset % SECTOR_SIZE;
uint8_t sector_buf[SECTOR_SIZE];
if (fat32_read_sectors(fs, fat_sector, 1, sector_buf) != 0) {
return -1;
}
/* Preserve top 4 bits of the existing entry */
uint32_t *entry = (uint32_t *)(sector_buf + entry_offset);
*entry = (*entry & 0xF0000000) | (value & 0x0FFFFFFF);
/* Write back to all FATs */
for (uint8_t f = 0; f < fs->bpb.num_fats; f++) {
uint32_t fat_sec = fs->fat_start_lba +
(f * fs->bpb.fat_size_32) + (fat_offset / SECTOR_SIZE);
if (fat32_write_sectors(fs, fat_sec, 1, sector_buf) != 0) {
return -1;
}
}
return 0;
}
/**
* Find a free cluster in the FAT and mark it as end-of-chain.
*
* @param fs Filesystem instance.
* @return Cluster number, or 0 if disk is full.
*/
static uint32_t fat32_alloc_cluster(fat32_fs_t *fs) {
for (uint32_t c = 2; c < fs->total_clusters + 2; c++) {
uint32_t val = fat32_read_fat(fs, c);
if (val == FAT32_FREE) {
fat32_write_fat(fs, c, FAT32_EOC);
/* Zero out the cluster */
uint8_t zero[SECTOR_SIZE];
memset(zero, 0, SECTOR_SIZE);
uint32_t first_sec = fs->data_start_lba +
(c - 2) * fs->sectors_per_cluster;
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
fat32_write_sectors(fs, first_sec + s, 1, zero);
}
return c;
}
}
return 0; /* Full */
}
/* ================================================================
* Cluster / sector helpers
* ================================================================ */
/**
* Convert a cluster number to its first sector (absolute LBA).
*/
static uint32_t cluster_to_lba(fat32_fs_t *fs, uint32_t cluster) {
return fs->data_start_lba + (cluster - 2) * fs->sectors_per_cluster;
}
/**
* Read all data from a cluster chain into a buffer.
*
* @param fs Filesystem instance.
* @param start Starting cluster.
* @param buf Output buffer.
* @param offset Byte offset to start reading from.
* @param size Maximum bytes to read.
* @return Bytes read, or -1 on error.
*/
static int32_t fat32_read_chain(fat32_fs_t *fs, uint32_t start,
uint32_t offset, uint32_t size,
void *buf) {
if (start < 2) return -1;
uint32_t bpc = fs->bytes_per_cluster;
uint32_t bytes_read = 0;
uint32_t cluster = start;
uint32_t pos = 0; /* Byte position in the chain */
/* Skip clusters until we reach the offset */
while (pos + bpc <= offset && cluster >= 2 && cluster < FAT32_EOC) {
pos += bpc;
cluster = fat32_read_fat(fs, cluster);
}
/* Read data */
uint8_t cluster_buf[SECTOR_SIZE]; /* Read sector by sector */
while (cluster >= 2 && cluster < FAT32_EOC && bytes_read < size) {
uint32_t lba = cluster_to_lba(fs, cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster && bytes_read < size; s++) {
uint32_t sec_start = pos;
uint32_t sec_end = pos + SECTOR_SIZE;
if (sec_end <= offset) {
/* This sector is entirely before our offset, skip */
pos += SECTOR_SIZE;
continue;
}
if (fat32_read_sectors(fs, lba + s, 1, cluster_buf) != 0) {
return (bytes_read > 0) ? (int32_t)bytes_read : -1;
}
/* Calculate how much of this sector we need */
uint32_t sect_off = 0;
if (offset > sec_start) {
sect_off = offset - sec_start;
}
uint32_t sect_avail = SECTOR_SIZE - sect_off;
uint32_t to_copy = size - bytes_read;
if (to_copy > sect_avail) to_copy = sect_avail;
memcpy((uint8_t *)buf + bytes_read, cluster_buf + sect_off, to_copy);
bytes_read += to_copy;
pos += SECTOR_SIZE;
}
cluster = fat32_read_fat(fs, cluster);
}
return (int32_t)bytes_read;
}
/**
* Write data to a cluster chain, extending it as needed.
*
* @param fs Filesystem instance.
* @param start Starting cluster (must be valid, >= 2).
* @param offset Byte offset to start writing at.
* @param size Bytes to write.
* @param buf Input data.
* @return Bytes written, or -1 on error.
*/
static int32_t fat32_write_chain(fat32_fs_t *fs, uint32_t start,
uint32_t offset, uint32_t size,
const void *buf) {
if (start < 2) return -1;
uint32_t bpc = fs->bytes_per_cluster;
uint32_t bytes_written = 0;
uint32_t cluster = start;
uint32_t prev_cluster = 0;
uint32_t pos = 0;
/* Navigate to the cluster containing the offset */
while (pos + bpc <= offset) {
if (cluster < 2 || cluster >= FAT32_EOC) {
/* Need to extend the chain */
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) return -1;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
prev_cluster = cluster;
pos += bpc;
cluster = fat32_read_fat(fs, cluster);
}
/* Ensure current cluster is valid */
if (cluster < 2 || cluster >= FAT32_EOC) {
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) return -1;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
/* Write data sector by sector */
uint8_t sector_buf[SECTOR_SIZE];
while (bytes_written < size) {
if (cluster < 2 || cluster >= FAT32_EOC) {
/* Extend chain */
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) break;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
uint32_t lba = cluster_to_lba(fs, cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster && bytes_written < size; s++) {
uint32_t sec_start = pos;
if (sec_start + SECTOR_SIZE <= offset) {
pos += SECTOR_SIZE;
continue;
}
/* Read-modify-write for partial sectors */
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return (bytes_written > 0) ? (int32_t)bytes_written : -1;
}
uint32_t sect_off = 0;
if (offset > sec_start) {
sect_off = offset - sec_start;
}
uint32_t sect_avail = SECTOR_SIZE - sect_off;
uint32_t to_copy = size - bytes_written;
if (to_copy > sect_avail) to_copy = sect_avail;
memcpy(sector_buf + sect_off, (const uint8_t *)buf + bytes_written, to_copy);
if (fat32_write_sectors(fs, lba + s, 1, sector_buf) != 0) {
return (bytes_written > 0) ? (int32_t)bytes_written : -1;
}
bytes_written += to_copy;
offset += to_copy; /* Advance offset for subsequent sectors */
pos += SECTOR_SIZE;
}
prev_cluster = cluster;
pos = pos; /* pos already advanced in the loop */
cluster = fat32_read_fat(fs, cluster);
}
return (int32_t)bytes_written;
}
/* ================================================================
* Directory operations
* ================================================================ */
/**
* VFS node fs_data encodes the FAT32 context:
* - inode field: first cluster of the file/directory
* - fs_data: pointer to fat32_fs_t
*/
/**
* Convert an 8.3 short filename to a human-readable string.
* "HELLO TXT" -> "HELLO.TXT"
*/
static void sfn_to_name(const uint8_t sfn[11], char *out) {
int pos = 0;
/* Copy base name (bytes 0-7), trimming trailing spaces */
int base_end = 7;
while (base_end >= 0 && sfn[base_end] == ' ') base_end--;
for (int i = 0; i <= base_end; i++) {
char c = (char)sfn[i];
/* Convert to lowercase for display */
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
out[pos++] = c;
}
/* Append extension (bytes 8-10) if non-empty */
int ext_end = 10;
while (ext_end >= 8 && sfn[ext_end] == ' ') ext_end--;
if (ext_end >= 8) {
out[pos++] = '.';
for (int i = 8; i <= ext_end; i++) {
char c = (char)sfn[i];
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
out[pos++] = c;
}
}
out[pos] = '\0';
}
/**
* Convert a human-readable filename to 8.3 short format.
* "hello.txt" -> "HELLO TXT"
*/
static void name_to_sfn(const char *name, uint8_t sfn[11]) {
memset(sfn, ' ', 11);
int pos = 0;
int i = 0;
/* Base name (up to 8 chars) */
while (name[i] && name[i] != '.' && pos < 8) {
char c = name[i];
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
sfn[pos++] = (uint8_t)c;
i++;
}
/* Extension */
if (name[i] == '.') {
i++;
pos = 8;
while (name[i] && pos < 11) {
char c = name[i];
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
sfn[pos++] = (uint8_t)c;
i++;
}
}
}
/**
* Get the first cluster number from a directory entry.
*/
static uint32_t dirent_cluster(const fat32_dirent_t *de) {
return ((uint32_t)de->cluster_hi << 16) | de->cluster_lo;
}
/* ================================================================
* VFS operations
* ================================================================ */
/**
* Read file data for a FAT32 file.
*/
static int32_t fat32_vfs_read(vfs_node_t *node, uint32_t offset,
uint32_t size, void *buf) {
if (!node || !node->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)node->fs_data;
uint32_t cluster = node->inode;
if (cluster < 2) return -1;
/* Don't read past file end */
if (offset >= node->size) return 0;
if (offset + size > node->size) {
size = node->size - offset;
}
return fat32_read_chain(fs, cluster, offset, size, buf);
}
/**
* Write file data for a FAT32 file.
*/
static int32_t fat32_vfs_write(vfs_node_t *node, uint32_t offset,
uint32_t size, const void *buf) {
if (!node || !node->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)node->fs_data;
uint32_t cluster = node->inode;
if (cluster < 2) return -1;
int32_t written = fat32_write_chain(fs, cluster, offset, size, buf);
/* Update file size if we wrote past the end */
if (written > 0 && offset + (uint32_t)written > node->size) {
node->size = offset + (uint32_t)written;
/* TODO: update the directory entry on disk with new size */
}
return written;
}
/**
* Read directory entries from a FAT32 directory.
*/
static int fat32_vfs_readdir(vfs_node_t *dir, uint32_t idx,
vfs_dirent_t *out) {
if (!dir || !dir->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)dir->fs_data;
/* inode == 0 means mount root: use the filesystem's root cluster */
uint32_t cluster = dir->inode;
if (cluster < 2) cluster = fs->root_cluster;
if (cluster < 2) return -1;
/* Read the directory cluster chain and iterate entries.
* We read 32-byte entries, skipping deleted (0xE5), LFN, and
* volume label entries. idx counts only valid file/dir entries. */
uint32_t count = 0;
uint32_t byte_pos = 0;
uint8_t sector_buf[SECTOR_SIZE];
uint32_t cur_cluster = cluster;
while (cur_cluster >= 2 && cur_cluster < FAT32_EOC) {
uint32_t lba = cluster_to_lba(fs, cur_cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return -1;
}
/* Each sector has 512/32 = 16 directory entries */
for (uint32_t e = 0; e < SECTOR_SIZE / 32; e++) {
fat32_dirent_t *de = (fat32_dirent_t *)(sector_buf + e * 32);
/* 0x00 = end of directory */
if (de->name[0] == 0x00) return -1;
/* 0xE5 = deleted entry */
if (de->name[0] == 0xE5) continue;
/* Skip LFN entries */
if ((de->attr & FAT32_ATTR_LFN) == FAT32_ATTR_LFN) continue;
/* Skip volume label */
if (de->attr & FAT32_ATTR_VOLUME_ID) continue;
/* Skip . and .. entries */
if (de->name[0] == '.' &&
(de->name[1] == ' ' || de->name[1] == '.')) {
continue;
}
if (count == idx) {
memset(out, 0, sizeof(vfs_dirent_t));
sfn_to_name(de->name, out->name);
out->type = (de->attr & FAT32_ATTR_DIRECTORY)
? VFS_DIRECTORY : VFS_FILE;
out->inode = dirent_cluster(de);
return 0;
}
count++;
}
}
cur_cluster = fat32_read_fat(fs, cur_cluster);
}
return -1; /* No more entries */
}
/**
* Look up a child by name in a FAT32 directory.
*/
static int fat32_vfs_finddir(vfs_node_t *dir, const char *name,
vfs_node_t *out) {
if (!dir || !dir->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)dir->fs_data;
/* inode == 0 means mount root: use the filesystem's root cluster */
uint32_t cluster = dir->inode;
if (cluster < 2) cluster = fs->root_cluster;
if (cluster < 2) return -1;
/* Convert search name to 8.3 for comparison */
uint8_t search_sfn[11];
name_to_sfn(name, search_sfn);
uint8_t sector_buf[SECTOR_SIZE];
uint32_t cur_cluster = cluster;
while (cur_cluster >= 2 && cur_cluster < FAT32_EOC) {
uint32_t lba = cluster_to_lba(fs, cur_cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return -1;
}
for (uint32_t e = 0; e < SECTOR_SIZE / 32; e++) {
fat32_dirent_t *de = (fat32_dirent_t *)(sector_buf + e * 32);
if (de->name[0] == 0x00) return -1;
if (de->name[0] == 0xE5) continue;
if ((de->attr & FAT32_ATTR_LFN) == FAT32_ATTR_LFN) continue;
if (de->attr & FAT32_ATTR_VOLUME_ID) continue;
/* Compare both 8.3 format and human-readable format */
if (memcmp(de->name, search_sfn, 11) == 0) {
/* Match by 8.3 name */
goto found;
}
/* Also compare with the lowercase display name */
char display[13];
sfn_to_name(de->name, display);
if (strcmp(display, name) == 0) {
goto found;
}
continue;
found:
memset(out, 0, sizeof(vfs_node_t));
sfn_to_name(de->name, out->name);
out->inode = dirent_cluster(de);
out->size = de->file_size;
out->type = (de->attr & FAT32_ATTR_DIRECTORY)
? VFS_DIRECTORY : VFS_FILE;
out->fs_data = fs;
return 0;
}
}
cur_cluster = fat32_read_fat(fs, cur_cluster);
}
return -1;
}
/** VFS operations for FAT32. */
static vfs_fs_ops_t fat32_vfs_ops = {
.open = NULL,
.close = NULL,
.read = fat32_vfs_read,
.write = fat32_vfs_write,
.readdir = fat32_vfs_readdir,
.finddir = fat32_vfs_finddir,
};
/* ================================================================
* Mount
* ================================================================ */
int fat32_mount_device(devicefs_device_t *dev, const char *mount_path) {
if (!dev || !mount_path) return -1;
if (fat32_mount_count >= FAT32_MAX_MOUNTS) {
offset_print(" FAT32: too many mounts\n");
return -1;
}
fat32_fs_t *fs = &fat32_mounts[fat32_mount_count];
memset(fs, 0, sizeof(fat32_fs_t));
fs->dev = dev;
/* Read the boot sector / BPB */
uint8_t boot_sector[SECTOR_SIZE];
if (fat32_read_sectors(fs, 0, 1, boot_sector) != 0) {
offset_print(" FAT32: failed to read boot sector\n");
return -1;
}
/* Copy BPB */
memcpy(&fs->bpb, boot_sector, sizeof(fat32_bpb_t));
/* Validate */
if (fs->bpb.bytes_per_sector != 512) {
offset_print(" FAT32: unsupported sector size\n");
return -1;
}
if (fs->bpb.fat_size_32 == 0) {
offset_print(" FAT32: fat_size_32 is 0, not FAT32\n");
return -1;
}
/* Check boot sector signature */
if (boot_sector[510] != 0x55 || boot_sector[511] != 0xAA) {
offset_print(" FAT32: invalid boot sector signature\n");
return -1;
}
/* Compute layout */
fs->sectors_per_cluster = fs->bpb.sectors_per_cluster;
fs->bytes_per_cluster = fs->sectors_per_cluster * SECTOR_SIZE;
fs->fat_start_lba = fs->bpb.reserved_sectors;
fs->data_start_lba = fs->fat_start_lba +
(fs->bpb.num_fats * fs->bpb.fat_size_32);
fs->root_cluster = fs->bpb.root_cluster;
uint32_t total_sectors = fs->bpb.total_sectors_32;
if (total_sectors == 0) total_sectors = fs->bpb.total_sectors_16;
uint32_t data_sectors = total_sectors - fs->data_start_lba;
fs->total_clusters = data_sectors / fs->sectors_per_cluster;
offset_print(" FAT32: mounted ");
offset_print(mount_path);
offset_print(" (");
print_hex(fs->total_clusters);
offset_print(" clusters, ");
print_hex(fs->sectors_per_cluster);
offset_print(" sec/clust)\n");
/* Register with VFS */
if (vfs_mount(mount_path, &fat32_vfs_ops, fs) != 0) {
offset_print(" FAT32: VFS mount failed\n");
return -1;
}
fat32_mount_count++;
return 0;
}

116
src/fat32.h Normal file
View File

@@ -0,0 +1,116 @@
/**
* @file fat32.h
* @brief FAT32 filesystem driver.
*
* Provides a VFS driver for reading and writing FAT32 filesystems
* on block devices. Supports long filenames (LFN), directory traversal,
* file read/write, and file creation.
*
* Usage:
* fat32_mount_device(dev, mount_path);
*
* This reads the BPB from sector 0 of the device, validates the FAT32
* signature, and mounts the filesystem at the specified VFS path.
*/
#ifndef FAT32_H
#define FAT32_H
#include <stdint.h>
#include "devicefs.h"
/** FAT32 cluster chain end marker. */
#define FAT32_EOC 0x0FFFFFF8
#define FAT32_FREE 0x00000000
#define FAT32_BAD 0x0FFFFFF7
/** Maximum filename length (8.3 short name). */
#define FAT32_SFN_LEN 11
/** Directory entry attributes. */
#define FAT32_ATTR_READ_ONLY 0x01
#define FAT32_ATTR_HIDDEN 0x02
#define FAT32_ATTR_SYSTEM 0x04
#define FAT32_ATTR_VOLUME_ID 0x08
#define FAT32_ATTR_DIRECTORY 0x10
#define FAT32_ATTR_ARCHIVE 0x20
#define FAT32_ATTR_LFN (FAT32_ATTR_READ_ONLY | FAT32_ATTR_HIDDEN | \
FAT32_ATTR_SYSTEM | FAT32_ATTR_VOLUME_ID)
/**
* FAT32 BIOS Parameter Block (BPB) — packed, read directly from disk.
* Covers the first 90 bytes of the boot sector.
*/
typedef struct __attribute__((packed)) fat32_bpb {
uint8_t jmp_boot[3]; /**< Jump instruction. */
uint8_t oem_name[8]; /**< OEM identifier. */
uint16_t bytes_per_sector; /**< Usually 512. */
uint8_t sectors_per_cluster;
uint16_t reserved_sectors; /**< Sectors before the first FAT. */
uint8_t num_fats; /**< Number of FATs (usually 2). */
uint16_t root_entry_count; /**< 0 for FAT32. */
uint16_t total_sectors_16; /**< 0 for FAT32. */
uint8_t media_type;
uint16_t fat_size_16; /**< 0 for FAT32. */
uint16_t sectors_per_track;
uint16_t num_heads;
uint32_t hidden_sectors;
uint32_t total_sectors_32;
/* FAT32 extended fields */
uint32_t fat_size_32; /**< Sectors per FAT. */
uint16_t ext_flags;
uint16_t fs_version;
uint32_t root_cluster; /**< First cluster of root directory (usually 2). */
uint16_t fs_info; /**< Sector number of FSInfo structure. */
uint16_t backup_boot; /**< Sector of backup boot sector. */
uint8_t reserved[12];
uint8_t drive_number;
uint8_t reserved1;
uint8_t boot_sig; /**< 0x29 if next 3 fields are valid. */
uint32_t volume_id;
uint8_t volume_label[11];
uint8_t fs_type[8]; /**< "FAT32 " */
} fat32_bpb_t;
/**
* FAT32 directory entry (32 bytes, packed).
*/
typedef struct __attribute__((packed)) fat32_dirent {
uint8_t name[11]; /**< 8.3 short name. */
uint8_t attr; /**< File attributes. */
uint8_t nt_reserved; /**< Reserved (case info). */
uint8_t create_time_tenth; /**< Creation time in tenths of second. */
uint16_t create_time; /**< Creation time. */
uint16_t create_date; /**< Creation date. */
uint16_t access_date; /**< Last access date. */
uint16_t cluster_hi; /**< High 16 bits of first cluster. */
uint16_t write_time; /**< Last write time. */
uint16_t write_date; /**< Last write date. */
uint16_t cluster_lo; /**< Low 16 bits of first cluster. */
uint32_t file_size; /**< File size in bytes. */
} fat32_dirent_t;
/**
* FAT32 filesystem instance (per-mount context).
*/
typedef struct fat32_fs {
devicefs_device_t *dev; /**< Underlying block device. */
fat32_bpb_t bpb; /**< Cached BPB. */
uint32_t fat_start_lba; /**< First sector of the FAT. */
uint32_t data_start_lba; /**< First sector of the data region. */
uint32_t sectors_per_cluster;
uint32_t bytes_per_cluster;
uint32_t root_cluster; /**< Root directory first cluster. */
uint32_t total_clusters;
} fat32_fs_t;
/**
* Mount a FAT32 filesystem from a block device.
*
* @param dev The devicefs block device to read from.
* @param mount_path Where to mount in the VFS (e.g., "/mnt").
* @return 0 on success, -1 on error.
*/
int fat32_mount_device(devicefs_device_t *dev, const char *mount_path);
#endif /* FAT32_H */

688
src/floppy.c Normal file
View File

@@ -0,0 +1,688 @@
/**
* @file floppy.c
* @brief Floppy disk controller driver implementation.
*
* Drives the Intel 82077AA-compatible floppy disk controller using
* ISA DMA channel 2 for data transfers and IRQ 6 for completion
* notification.
*
* Supports 1.44 MB 3.5" HD floppies. The driver detects drives via
* CMOS register 0x10, initializes the controller, and registers each
* detected drive with the devicefs subsystem as a "floppy" class
* block device (floppy1, floppy2, ...).
*
* LBA addressing is converted to CHS for the floppy controller.
*/
#include "floppy.h"
#include "port_io.h"
#include "pic.h"
#include "devicefs.h"
#include "driver.h"
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* Global state
* ================================================================ */
/** Detected floppy drives. */
static floppy_drive_t floppy_drives[FLOPPY_MAX_DRIVES];
/** Volatile flag set by IRQ 6 handler. */
static volatile int floppy_irq_received = 0;
/** DMA bounce buffer — must be in the first 16 MB of physical memory
* and must not cross a 64 KB boundary. We use a static buffer that
* the linker places in the kernel BSS (which lives in low memory). */
static uint8_t dma_buffer[FLOPPY_SECTOR_SIZE] __attribute__((aligned(512)));
/* ================================================================
* Low-level helpers
* ================================================================ */
/**
* Read CMOS register.
*
* @param reg CMOS register address (0x00-0x7F).
* @return Register value.
*/
static uint8_t cmos_read(uint8_t reg) {
outb(0x70, reg);
/* Small delay for CMOS to respond */
inb(0x80);
return inb(0x71);
}
/**
* Wait for IRQ 6 with timeout.
* The floppy controller fires IRQ 6 on command completion.
*
* If interrupts are currently disabled (e.g. during early init before
* the kernel calls STI), we temporarily enable them so IRQ 6 can
* actually fire, then restore the previous interrupt state.
*
* @return 0 on success (IRQ received), -1 on timeout.
*/
static int fdc_wait_irq(void) {
/* Check whether interrupts are currently enabled (IF flag) */
uint32_t eflags;
asm volatile("pushfl; popl %0" : "=r"(eflags));
int need_sti = !(eflags & 0x200);
if (need_sti) {
asm volatile("sti");
}
int timeout = 5000000;
while (!floppy_irq_received && --timeout > 0) {
/* Busy wait — in a real OS we'd use a proper sleep/wait */
asm volatile("pause");
}
if (need_sti) {
asm volatile("cli");
}
if (timeout == 0) {
offset_print(" FLOPPY: IRQ timeout\n");
return -1;
}
floppy_irq_received = 0;
return 0;
}
/**
* Write a byte to the FDC FIFO register.
* Waits for the controller to be ready (MSR RQM=1, DIO=0).
*
* @param val Byte to write.
* @return 0 on success, -1 on timeout.
*/
static int fdc_write_byte(uint8_t val) {
int timeout = 500000;
while (--timeout > 0) {
uint8_t msr = inb(FDC_MSR);
if ((msr & (MSR_RQM | MSR_DIO)) == MSR_RQM) {
outb(FDC_FIFO, val);
return 0;
}
}
offset_print(" FLOPPY: write timeout\n");
return -1;
}
/**
* Read a byte from the FDC FIFO register.
* Waits for the controller to have data ready (MSR RQM=1, DIO=1).
*
* @return Byte read, or -1 on timeout.
*/
static int fdc_read_byte(void) {
int timeout = 500000;
while (--timeout > 0) {
uint8_t msr = inb(FDC_MSR);
if ((msr & (MSR_RQM | MSR_DIO)) == (MSR_RQM | MSR_DIO)) {
return (int)inb(FDC_FIFO);
}
}
offset_print(" FLOPPY: read timeout\n");
return -1;
}
/**
* Issue a SENSE INTERRUPT command and read the result.
*
* @param st0_out Output: ST0 byte.
* @param cyl_out Output: current cylinder.
*/
static void fdc_sense_interrupt(uint8_t *st0_out, uint8_t *cyl_out) {
fdc_write_byte(FDC_CMD_SENSE_INT);
int st0 = fdc_read_byte();
int cyl = fdc_read_byte();
if (st0_out) *st0_out = (uint8_t)(st0 >= 0 ? st0 : 0xFF);
if (cyl_out) *cyl_out = (uint8_t)(cyl >= 0 ? cyl : 0xFF);
}
/* ================================================================
* Motor control
* ================================================================ */
/**
* Turn the motor on for a drive.
*
* @param drive Drive number (0 or 1).
*/
static void fdc_motor_on(uint8_t drive) {
if (drive >= FLOPPY_MAX_DRIVES) return;
if (floppy_drives[drive].motor_on) return;
uint8_t dor = drive | DOR_RESET | DOR_DMA_IRQ |
(DOR_MOTOR_A << drive);
outb(FDC_DOR, dor);
/* Wait ~500 ms for motor spin-up (busy wait in timer ticks) */
for (volatile int i = 0; i < 2000000; i++) {
asm volatile("pause");
}
floppy_drives[drive].motor_on = 1;
}
/**
* Turn the motor off for a drive.
*
* @param drive Drive number (0 or 1).
*/
static void fdc_motor_off(uint8_t drive) {
if (drive >= FLOPPY_MAX_DRIVES) return;
uint8_t dor = drive | DOR_RESET | DOR_DMA_IRQ;
outb(FDC_DOR, dor);
floppy_drives[drive].motor_on = 0;
}
/* ================================================================
* DMA setup for ISA DMA channel 2
* ================================================================ */
/**
* Set up ISA DMA channel 2 for a floppy transfer.
*
* The floppy controller uses DMA channel 2. We configure the DMA
* controller with the physical address of our bounce buffer and the
* transfer length.
*
* @param addr Physical address of the DMA buffer (must be < 16 MB).
* @param len Transfer length in bytes (minus 1 is written to count).
* @param read 1 for read (disk→memory), 0 for write (memory→disk).
*/
static void dma_setup(uint32_t addr, uint16_t len, int read) {
/* Mask DMA channel 2 */
outb(0x0A, 0x06);
/* Reset flip-flop */
outb(0x0C, 0xFF);
/* Mode: single transfer, auto-init disabled, increment, channel 2
* Read = 0x46 (single, addr incr, write-to-memory = FDC read)
* Write = 0x4A (single, addr incr, read-from-memory = FDC write) */
outb(0x0B, read ? 0x46 : 0x4A);
/* Address (low 16 bits via channel 2 address reg 0x04) */
outb(0x04, (uint8_t)(addr & 0xFF));
outb(0x04, (uint8_t)((addr >> 8) & 0xFF));
/* Page register for channel 2 (port 0x81) — bits 16-23 of address */
outb(0x81, (uint8_t)((addr >> 16) & 0xFF));
/* Count (len - 1, low byte first) */
outb(0x05, (uint8_t)((len - 1) & 0xFF));
outb(0x05, (uint8_t)(((len - 1) >> 8) & 0xFF));
/* Unmask DMA channel 2 */
outb(0x0A, 0x02);
}
/* ================================================================
* CHS conversion
* ================================================================ */
/**
* Convert an LBA address to CHS for a 1.44 MB floppy.
*
* @param lba Logical block address.
* @param cyl_out Output: cylinder number.
* @param head_out Output: head number.
* @param sec_out Output: sector number (1-based).
*/
static void lba_to_chs(uint32_t lba, uint8_t *cyl_out,
uint8_t *head_out, uint8_t *sec_out) {
*cyl_out = (uint8_t)(lba / (FLOPPY_HEADS * FLOPPY_SECTORS_PER_TRACK));
uint32_t tmp = lba % (FLOPPY_HEADS * FLOPPY_SECTORS_PER_TRACK);
*head_out = (uint8_t)(tmp / FLOPPY_SECTORS_PER_TRACK);
*sec_out = (uint8_t)((tmp % FLOPPY_SECTORS_PER_TRACK) + 1);
}
/* ================================================================
* Seek and calibrate
* ================================================================ */
/**
* Recalibrate a drive (seek to track 0).
*
* @param drive Drive number.
* @return 0 on success, -1 on failure.
*/
static int fdc_recalibrate(uint8_t drive) {
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_RECALIBRATE);
fdc_write_byte(drive);
if (fdc_wait_irq() != 0) return -1;
uint8_t st0, cyl;
fdc_sense_interrupt(&st0, &cyl);
if (cyl != 0) {
offset_print(" FLOPPY: recalibrate failed, cyl=");
print_hex(cyl);
offset_print("\n");
return -1;
}
floppy_drives[drive].current_track = 0;
return 0;
}
/**
* Seek to a specific cylinder.
*
* @param drive Drive number.
* @param cyl Target cylinder.
* @return 0 on success, -1 on failure.
*/
static int fdc_seek(uint8_t drive, uint8_t cyl) {
if (floppy_drives[drive].current_track == cyl) return 0;
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_SEEK);
fdc_write_byte((0 << 2) | drive); /* Head 0, drive N */
fdc_write_byte(cyl);
if (fdc_wait_irq() != 0) return -1;
uint8_t st0, res_cyl;
fdc_sense_interrupt(&st0, &res_cyl);
if (res_cyl != cyl) {
offset_print(" FLOPPY: seek failed, wanted cyl=");
print_hex(cyl);
offset_print(" got=");
print_hex(res_cyl);
offset_print("\n");
return -1;
}
floppy_drives[drive].current_track = cyl;
return 0;
}
/* ================================================================
* Read / Write sector
* ================================================================ */
/**
* Read a single sector from a floppy drive using DMA.
*
* @param drive Drive number (0 or 1).
* @param lba Logical sector address.
* @param buf Output buffer (512 bytes).
* @return 0 on success, -1 on failure.
*/
static int fdc_read_sector(uint8_t drive, uint32_t lba, void *buf) {
if (lba >= FLOPPY_TOTAL_SECTORS) return -1;
uint8_t cyl, head, sec;
lba_to_chs(lba, &cyl, &head, &sec);
fdc_motor_on(drive);
/* Seek to the correct cylinder */
if (fdc_seek(drive, cyl) != 0) {
fdc_motor_off(drive);
return -1;
}
/* Set up DMA for read (disk → memory) */
uint32_t dma_addr = (uint32_t)dma_buffer;
dma_setup(dma_addr, FLOPPY_SECTOR_SIZE, 1);
/* Issue READ DATA command */
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_READ_DATA);
fdc_write_byte((head << 2) | drive);
fdc_write_byte(cyl);
fdc_write_byte(head);
fdc_write_byte(sec);
fdc_write_byte(2); /* Sector size code: 2 = 512 bytes */
fdc_write_byte(FLOPPY_SECTORS_PER_TRACK); /* End of track */
fdc_write_byte(0x1B); /* GPL: gap length */
fdc_write_byte(0xFF); /* DTL: unused when sector size != 0 */
/* Wait for command completion */
if (fdc_wait_irq() != 0) {
fdc_motor_off(drive);
return -1;
}
/* Read result phase (7 bytes) */
uint8_t st0 = (uint8_t)fdc_read_byte();
uint8_t st1 = (uint8_t)fdc_read_byte();
uint8_t st2 = (uint8_t)fdc_read_byte();
(void)fdc_read_byte(); /* C */
(void)fdc_read_byte(); /* H */
(void)fdc_read_byte(); /* R */
(void)fdc_read_byte(); /* N */
/* Check for errors */
if ((st0 & 0xC0) != 0 || st1 != 0 || st2 != 0) {
offset_print(" FLOPPY: read error st0=");
print_hex(st0);
offset_print(" st1=");
print_hex(st1);
offset_print(" st2=");
print_hex(st2);
offset_print("\n");
fdc_motor_off(drive);
return -1;
}
/* Copy from DMA buffer to caller's buffer */
memcpy(buf, dma_buffer, FLOPPY_SECTOR_SIZE);
fdc_motor_off(drive);
return 0;
}
/**
* Write a single sector to a floppy drive using DMA.
*
* @param drive Drive number (0 or 1).
* @param lba Logical sector address.
* @param buf Input buffer (512 bytes).
* @return 0 on success, -1 on failure.
*/
static int fdc_write_sector(uint8_t drive, uint32_t lba, const void *buf) {
if (lba >= FLOPPY_TOTAL_SECTORS) return -1;
uint8_t cyl, head, sec;
lba_to_chs(lba, &cyl, &head, &sec);
fdc_motor_on(drive);
if (fdc_seek(drive, cyl) != 0) {
fdc_motor_off(drive);
return -1;
}
/* Copy data to DMA buffer */
memcpy(dma_buffer, buf, FLOPPY_SECTOR_SIZE);
/* Set up DMA for write (memory → disk) */
uint32_t dma_addr = (uint32_t)dma_buffer;
dma_setup(dma_addr, FLOPPY_SECTOR_SIZE, 0);
/* Issue WRITE DATA command */
floppy_irq_received = 0;
fdc_write_byte(FDC_CMD_WRITE_DATA);
fdc_write_byte((head << 2) | drive);
fdc_write_byte(cyl);
fdc_write_byte(head);
fdc_write_byte(sec);
fdc_write_byte(2); /* Sector size code: 512 bytes */
fdc_write_byte(FLOPPY_SECTORS_PER_TRACK);
fdc_write_byte(0x1B);
fdc_write_byte(0xFF);
if (fdc_wait_irq() != 0) {
fdc_motor_off(drive);
return -1;
}
/* Read result phase */
uint8_t st0 = (uint8_t)fdc_read_byte();
uint8_t st1 = (uint8_t)fdc_read_byte();
uint8_t st2 = (uint8_t)fdc_read_byte();
(void)fdc_read_byte();
(void)fdc_read_byte();
(void)fdc_read_byte();
(void)fdc_read_byte();
if ((st0 & 0xC0) != 0 || st1 != 0 || st2 != 0) {
offset_print(" FLOPPY: write error st0=");
print_hex(st0);
offset_print(" st1=");
print_hex(st1);
offset_print(" st2=");
print_hex(st2);
offset_print("\n");
fdc_motor_off(drive);
return -1;
}
fdc_motor_off(drive);
return 0;
}
/* ================================================================
* Devicefs block operations
* ================================================================ */
/**
* Read sectors from a floppy drive.
* dev_data is a pointer to the floppy_drive_t.
*/
static int floppy_block_read(void *dev_data, uint32_t lba,
uint32_t count, void *buf) {
floppy_drive_t *drv = (floppy_drive_t *)dev_data;
if (!drv || !drv->present) return -1;
uint8_t *p = (uint8_t *)buf;
for (uint32_t i = 0; i < count; i++) {
if (fdc_read_sector(drv->drive_num, lba + i, p) != 0) {
return -1;
}
p += FLOPPY_SECTOR_SIZE;
}
return 0;
}
/**
* Write sectors to a floppy drive.
*/
static int floppy_block_write(void *dev_data, uint32_t lba,
uint32_t count, const void *buf) {
floppy_drive_t *drv = (floppy_drive_t *)dev_data;
if (!drv || !drv->present) return -1;
const uint8_t *p = (const uint8_t *)buf;
for (uint32_t i = 0; i < count; i++) {
if (fdc_write_sector(drv->drive_num, lba + i, p) != 0) {
return -1;
}
p += FLOPPY_SECTOR_SIZE;
}
return 0;
}
/**
* Get floppy sector size (always 512).
*/
static uint32_t floppy_block_sector_size(void *dev_data) {
(void)dev_data;
return FLOPPY_SECTOR_SIZE;
}
/**
* Get total sector count for a 1.44 MB floppy.
*/
static uint32_t floppy_block_sector_count(void *dev_data) {
(void)dev_data;
return FLOPPY_TOTAL_SECTORS;
}
/** Block device operations for floppy drives. */
static devicefs_block_ops_t floppy_block_ops = {
.read_sectors = floppy_block_read,
.write_sectors = floppy_block_write,
.sector_size = floppy_block_sector_size,
.sector_count = floppy_block_sector_count,
};
/* ================================================================
* Controller initialization
* ================================================================ */
/**
* Reset the floppy disk controller.
*
* Toggles the RESET bit in the DOR and re-initializes the controller.
*
* @return 0 on success, -1 on failure.
*/
static int fdc_reset(void) {
/* Enter reset state */
outb(FDC_DOR, 0x00);
/* Small delay */
for (volatile int i = 0; i < 100000; i++) {
asm volatile("pause");
}
/* Exit reset: enable DMA/IRQ, select drive 0, motor off */
floppy_irq_received = 0;
outb(FDC_DOR, DOR_RESET | DOR_DMA_IRQ);
/* Controller generates IRQ 6 after reset */
if (fdc_wait_irq() != 0) {
offset_print(" FLOPPY: no IRQ after reset\n");
return -1;
}
/* Sense interrupt for each of the 4 possible drives */
for (int i = 0; i < 4; i++) {
uint8_t st0, cyl;
fdc_sense_interrupt(&st0, &cyl);
}
/* Set data rate to 500 kbps (for 1.44 MB HD floppies) */
outb(FDC_CCR, 0x00);
/* SPECIFY command: step rate = 3 ms, head unload = 240 ms,
* head load = 16 ms, non-DMA mode = 0 */
fdc_write_byte(FDC_CMD_SPECIFY);
fdc_write_byte(0xDF); /* SRT=3ms, HUT=240ms */
fdc_write_byte(0x02); /* HLT=16ms, NDMA=0 */
return 0;
}
/* ================================================================
* Driver framework integration
* ================================================================ */
/**
* Probe for floppy controller.
* Reads CMOS to determine if any floppy drives are configured.
*/
static driver_probe_result_t floppy_probe(void) {
uint8_t cmos_floppy = cmos_read(0x10);
uint8_t drive_a = (cmos_floppy >> 4) & 0x0F;
uint8_t drive_b = cmos_floppy & 0x0F;
if (drive_a == CMOS_FLOPPY_NONE && drive_b == CMOS_FLOPPY_NONE) {
offset_print(" FLOPPY: no drives in CMOS\n");
return DRIVER_PROBE_NOT_FOUND;
}
return DRIVER_PROBE_OK;
}
/**
* Initialize the floppy controller and register detected drives.
*/
static int floppy_driver_init(void) {
memset(floppy_drives, 0, sizeof(floppy_drives));
/* Read CMOS for drive types */
uint8_t cmos_floppy = cmos_read(0x10);
uint8_t types[2] = {
(cmos_floppy >> 4) & 0x0F,
cmos_floppy & 0x0F,
};
/* Unmask IRQ 6 in the PIC */
pic_clear_mask(6);
/* Reset the controller */
if (fdc_reset() != 0) {
offset_print(" FLOPPY: controller reset failed\n");
return -1;
}
/* Register each detected drive */
int found = 0;
for (int i = 0; i < 2; i++) {
if (types[i] == CMOS_FLOPPY_NONE) continue;
/* We only fully support 1.44 MB for now, but register others too */
floppy_drives[i].drive_num = (uint8_t)i;
floppy_drives[i].cmos_type = types[i];
floppy_drives[i].present = 1;
floppy_drives[i].motor_on = 0;
floppy_drives[i].current_track = 0xFF; /* Unknown */
const char *type_str;
switch (types[i]) {
case CMOS_FLOPPY_360K: type_str = "360K 5.25\""; break;
case CMOS_FLOPPY_1200K: type_str = "1.2M 5.25\""; break;
case CMOS_FLOPPY_720K: type_str = "720K 3.5\""; break;
case CMOS_FLOPPY_1440K: type_str = "1.44M 3.5\""; break;
case CMOS_FLOPPY_2880K: type_str = "2.88M 3.5\""; break;
default: type_str = "unknown"; break;
}
offset_print(" FLOPPY: drive ");
print_hex(i);
offset_print(": ");
offset_print(type_str);
offset_print("\n");
/* Recalibrate the drive */
fdc_motor_on((uint8_t)i);
fdc_recalibrate((uint8_t)i);
fdc_motor_off((uint8_t)i);
/* Register with devicefs */
devicefs_register_block("floppy", &floppy_block_ops,
&floppy_drives[i]);
found++;
}
if (found == 0) {
offset_print(" FLOPPY: no usable drives found\n");
} else {
offset_print(" FLOPPY: ");
print_hex((uint32_t)found);
offset_print(" drive(s) initialized\n");
}
return 0;
}
/* ================================================================
* IRQ handler
* ================================================================ */
void floppy_irq(void) {
floppy_irq_received = 1;
}
/* ================================================================
* Public API & driver registration
* ================================================================ */
int floppy_init(void) {
return floppy_driver_init();
}
/** Driver descriptor. */
static const driver_t floppy_driver = {
.name = "floppy",
.probe = floppy_probe,
.init = floppy_driver_init,
};
REGISTER_DRIVER(floppy_driver);

122
src/floppy.h Normal file
View File

@@ -0,0 +1,122 @@
/**
* @file floppy.h
* @brief Floppy disk controller driver.
*
* Drives the Intel 82077AA-compatible floppy disk controller.
* Detects floppy drives via CMOS and registers them with the devicefs
* subsystem as block devices with class "floppy" (floppy1, floppy2, ...).
*
* Supports 1.44 MB 3.5" HD floppies (18 sectors/track, 80 tracks, 2 heads).
* Uses IRQ 6 for command completion notification and DMA channel 2 for
* data transfer.
*/
#ifndef FLOPPY_H
#define FLOPPY_H
#include <stdint.h>
/* ================================================================
* Floppy controller I/O port registers (primary controller)
* ================================================================ */
#define FDC_BASE 0x3F0 /**< Base I/O port of primary FDC. */
#define FDC_SRA 0x3F0 /**< Status Register A (read-only). */
#define FDC_SRB 0x3F1 /**< Status Register B (read-only). */
#define FDC_DOR 0x3F2 /**< Digital Output Register. */
#define FDC_TDR 0x3F3 /**< Tape Drive Register. */
#define FDC_MSR 0x3F4 /**< Main Status Register (read-only). */
#define FDC_DSR 0x3F4 /**< Data Rate Select Register (write-only). */
#define FDC_FIFO 0x3F5 /**< Data (FIFO) register. */
#define FDC_DIR 0x3F7 /**< Digital Input Register (read-only). */
#define FDC_CCR 0x3F7 /**< Configuration Control Register (write-only). */
/* ================================================================
* Digital Output Register (DOR) bits
* ================================================================ */
#define DOR_DRIVE_SEL_MASK 0x03 /**< Drive select (0-3). */
#define DOR_RESET 0x04 /**< 0 = enter reset, 1 = normal operation. */
#define DOR_DMA_IRQ 0x08 /**< 1 = enable DMA and IRQ. */
#define DOR_MOTOR_A 0x10 /**< Motor enable for drive 0. */
#define DOR_MOTOR_B 0x20 /**< Motor enable for drive 1. */
#define DOR_MOTOR_C 0x40 /**< Motor enable for drive 2. */
#define DOR_MOTOR_D 0x80 /**< Motor enable for drive 3. */
/* ================================================================
* Main Status Register (MSR) bits
* ================================================================ */
#define MSR_ACTA 0x01 /**< Drive 0 is seeking. */
#define MSR_ACTB 0x02 /**< Drive 1 is seeking. */
#define MSR_ACTC 0x04 /**< Drive 2 is seeking. */
#define MSR_ACTD 0x08 /**< Drive 3 is seeking. */
#define MSR_CB 0x10 /**< Command busy. */
#define MSR_NDMA 0x20 /**< Non-DMA mode. */
#define MSR_DIO 0x40 /**< Data direction: 1 = FDC→CPU, 0 = CPU→FDC. */
#define MSR_RQM 0x80 /**< Ready for data transfer. */
/* ================================================================
* Floppy commands
* ================================================================ */
#define FDC_CMD_SPECIFY 0x03 /**< Specify step rate, head load/unload. */
#define FDC_CMD_WRITE_DATA 0xC5 /**< Write Data (MT+MFM). */
#define FDC_CMD_READ_DATA 0xE6 /**< Read Data (MT+MFM+SK). */
#define FDC_CMD_RECALIBRATE 0x07 /**< Recalibrate (seek to track 0). */
#define FDC_CMD_SENSE_INT 0x08 /**< Sense Interrupt Status. */
#define FDC_CMD_SEEK 0x0F /**< Seek to a track. */
#define FDC_CMD_VERSION 0x10 /**< Get controller version. */
#define FDC_CMD_CONFIGURE 0x13 /**< Configure controller. */
#define FDC_CMD_LOCK 0x94 /**< Lock configuration. */
/* ================================================================
* Geometry for 1.44 MB 3.5" HD floppy
* ================================================================ */
#define FLOPPY_SECTORS_PER_TRACK 18 /**< Sectors per track. */
#define FLOPPY_HEADS 2 /**< Number of heads. */
#define FLOPPY_TRACKS 80 /**< Number of cylinders/tracks. */
#define FLOPPY_SECTOR_SIZE 512 /**< Bytes per sector. */
#define FLOPPY_TOTAL_SECTORS (FLOPPY_SECTORS_PER_TRACK * FLOPPY_HEADS * FLOPPY_TRACKS)
/* ================================================================
* CMOS floppy type codes
* ================================================================ */
#define CMOS_FLOPPY_NONE 0x00 /**< No drive. */
#define CMOS_FLOPPY_360K 0x01 /**< 5.25" 360 KB. */
#define CMOS_FLOPPY_1200K 0x02 /**< 5.25" 1.2 MB. */
#define CMOS_FLOPPY_720K 0x03 /**< 3.5" 720 KB. */
#define CMOS_FLOPPY_1440K 0x04 /**< 3.5" 1.44 MB. */
#define CMOS_FLOPPY_2880K 0x05 /**< 3.5" 2.88 MB. */
/** Maximum supported floppy drives. */
#define FLOPPY_MAX_DRIVES 2
/**
* Per-drive state.
*/
typedef struct floppy_drive {
uint8_t drive_num; /**< Drive number (0 or 1). */
uint8_t cmos_type; /**< CMOS type code. */
uint8_t present; /**< 1 if drive is detected and usable. */
uint8_t motor_on; /**< 1 if motor is currently running. */
uint8_t current_track; /**< Last sought cylinder (cached). */
} floppy_drive_t;
/**
* Initialize the floppy disk controller driver.
* Detects drives via CMOS and registers them with devicefs.
*
* @return 0 on success, non-zero on failure.
*/
int floppy_init(void);
/**
* IRQ 6 handler — called from isr_handler when vector 38 fires.
*/
void floppy_irq(void);
#endif /* FLOPPY_H */

View File

@@ -3,6 +3,7 @@
#include "process.h" #include "process.h"
#include "syscall.h" #include "syscall.h"
#include "keyboard.h" #include "keyboard.h"
#include "floppy.h"
#include <stdint.h> #include <stdint.h>
/* Forward declaration for kernel panic or similar */ /* Forward declaration for kernel panic or similar */
@@ -64,6 +65,9 @@ void isr_handler(registers_t *regs)
} else if (regs->int_no == 33) { } else if (regs->int_no == 33) {
/* Keyboard IRQ */ /* Keyboard IRQ */
keyboard_irq(regs); keyboard_irq(regs);
} else if (regs->int_no == 38) {
/* Floppy IRQ */
floppy_irq();
} }
return; return;
} }

View File

@@ -20,6 +20,7 @@
#include "devicefs.h" #include "devicefs.h"
#include "sysfs.h" #include "sysfs.h"
#include "mbr.h" #include "mbr.h"
#include "fat32.h"
#include "keyboard.h" #include "keyboard.h"
#include "framebuffer.h" #include "framebuffer.h"
@@ -68,6 +69,128 @@ void print_hex(uint32_t val)
outb(0xE9, '\n'); serial_putc('\n'); outb(0xE9, '\n'); serial_putc('\n');
} }
/* ================================================================
* VFS sysfs namespace — handles mount/umount via /sys/vfs
* ================================================================
*
* /sys/vfs/mount — write "device mountpoint" to mount a FAT32 device
* /sys/vfs/mounts — read to list active mounts
*/
/** Last mount status message for reading back. */
static char vfs_sysfs_status[256] = "no mounts\n";
static int vfs_sysfs_list(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out) {
(void)ctx;
if (path[0] != '\0') return -1; /* No subdirectories */
static const char *entries[] = { "mount", "mounts" };
if (idx >= 2) return -1;
memset(out, 0, sizeof(sysfs_entry_t));
strncpy(out->name, entries[idx], SYSFS_MAX_NAME - 1);
out->is_dir = 0;
return 0;
}
static int vfs_sysfs_read(void *ctx, const char *path, char *buf,
uint32_t buf_size) {
(void)ctx;
if (strcmp(path, "mount") == 0 || strcmp(path, "mounts") == 0) {
uint32_t len = strlen(vfs_sysfs_status);
if (len + 1 > buf_size) len = buf_size - 1;
memcpy(buf, vfs_sysfs_status, len);
buf[len] = '\0';
return (int)len;
}
return -1;
}
static int vfs_sysfs_write(void *ctx, const char *path, const char *buf,
uint32_t size) {
(void)ctx;
if (strcmp(path, "mount") != 0) return -1;
/* Parse: "device_name mount_path\n" */
char device[64];
char mount_path[128];
int di = 0, mi = 0;
uint32_t i = 0;
/* Parse device name */
while (i < size && buf[i] != ' ' && buf[i] != '\n' && di < 63) {
device[di++] = buf[i++];
}
device[di] = '\0';
/* Skip spaces */
while (i < size && buf[i] == ' ') i++;
/* Parse mount path */
while (i < size && buf[i] != ' ' && buf[i] != '\n' && mi < 127) {
mount_path[mi++] = buf[i++];
}
mount_path[mi] = '\0';
if (di == 0 || mi == 0) {
strncpy(vfs_sysfs_status, "error: usage: device mountpoint\n",
sizeof(vfs_sysfs_status) - 1);
return -1;
}
offset_print(" VFS mount request: ");
offset_print(device);
offset_print(" -> ");
offset_print(mount_path);
offset_print("\n");
/* Find the device */
devicefs_device_t *dev = devicefs_find(device);
if (!dev) {
strncpy(vfs_sysfs_status, "error: device not found\n",
sizeof(vfs_sysfs_status) - 1);
offset_print(" VFS mount: device not found\n");
return -1;
}
/* Mount as FAT32 */
int ret = fat32_mount_device(dev, mount_path);
if (ret != 0) {
strncpy(vfs_sysfs_status, "error: FAT32 mount failed\n",
sizeof(vfs_sysfs_status) - 1);
offset_print(" VFS mount: FAT32 mount failed\n");
return -1;
}
/* Build success message */
strncpy(vfs_sysfs_status, "mounted ", sizeof(vfs_sysfs_status) - 1);
uint32_t slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, device,
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, " at ",
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, mount_path,
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
if (slen < sizeof(vfs_sysfs_status) - 1) {
vfs_sysfs_status[slen] = '\n';
vfs_sysfs_status[slen + 1] = '\0';
}
return (int)size;
}
static sysfs_ops_t vfs_sysfs_ops = {
.list = vfs_sysfs_list,
.read = vfs_sysfs_read,
.write = vfs_sysfs_write,
};
void kernel_main(uint32_t magic, uint32_t addr) { void kernel_main(uint32_t magic, uint32_t addr) {
/* Initialize serial port first so all debug output goes to COM1 too */ /* Initialize serial port first so all debug output goes to COM1 too */
serial_init(); serial_init();
@@ -258,6 +381,10 @@ void kernel_main(uint32_t magic, uint32_t addr) {
EARLY_PRINT("SYS "); EARLY_PRINT("SYS ");
offset_print("Sysfs initialized\n"); offset_print("Sysfs initialized\n");
/* Register VFS mount namespace in sysfs */
sysfs_register("vfs", &vfs_sysfs_ops, NULL);
offset_print("VFS sysfs namespace registered\n");
init_tss(); init_tss();
EARLY_PRINT("TSS "); EARLY_PRINT("TSS ");
offset_print("TSS initialized\n"); offset_print("TSS initialized\n");