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.
This commit is contained in:
@@ -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.
|
||||
|
||||
43
build.log
Normal file
43
build.log
Normal file
@@ -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
|
||||
@@ -24,6 +24,7 @@ add_executable(kernel
|
||||
ide.c
|
||||
mbr.c
|
||||
fat32.c
|
||||
floppy.c
|
||||
env.c
|
||||
keyboard.c
|
||||
interrupts.S
|
||||
|
||||
688
src/floppy.c
Normal file
688
src/floppy.c
Normal 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
122
src/floppy.h
Normal 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 */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user