diff --git a/README.md b/README.md index 6739928..593b8f9 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Once a task is completed, it should be checked off. - [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 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. -- [ ] 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. - [ ] 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. diff --git a/build.log b/build.log new file mode 100644 index 0000000..c4969ff --- /dev/null +++ b/build.log @@ -0,0 +1,43 @@ +[ 3%] Building user-mode applications +Building app: cat + Built: /workspaces/claude-os/build/apps_bin/cat (310 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: 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%] Built target initrd +[ 9%] Building C object src/CMakeFiles/kernel.dir/floppy.c.o +[ 12%] Linking C executable ../bin/kernel +[ 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, 127g free +Added to ISO image: directory '/'='/tmp/grub.oPDKGd' +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: 5918 sectors +Written to medium : 5918 sectors at LBA 0 +Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully. + +[100%] Built target iso diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 156935e..d60e26f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(kernel ide.c mbr.c fat32.c + floppy.c env.c keyboard.c interrupts.S diff --git a/src/floppy.c b/src/floppy.c new file mode 100644 index 0000000..1341124 --- /dev/null +++ b/src/floppy.c @@ -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 + +/* 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); diff --git a/src/floppy.h b/src/floppy.h new file mode 100644 index 0000000..bb5bd58 --- /dev/null +++ b/src/floppy.h @@ -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 + +/* ================================================================ + * 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 */ diff --git a/src/isr.c b/src/isr.c index 6e94d99..19fd221 100644 --- a/src/isr.c +++ b/src/isr.c @@ -3,6 +3,7 @@ #include "process.h" #include "syscall.h" #include "keyboard.h" +#include "floppy.h" #include /* 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; }