Compare commits

5 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
10 changed files with 1987 additions and 6 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 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.
- [ ] 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.
- [ ] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
- [ ] 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.
- [ ] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem.
- [x] Create a FAT32 driver. It should allow reading and writing to and from a block device.
- [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.
- [x] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
- [x] Add support for character device to the devicefs subsystem.
- [x] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device.
- [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 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`.

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;
}

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

@@ -24,6 +24,7 @@ add_executable(kernel
ide.c
mbr.c
fat32.c
floppy.c
env.c
keyboard.c
interrupts.S

View File

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

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