diff options
Diffstat (limited to 'kernel/fd.c')
| -rw-r--r-- | kernel/fd.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/kernel/fd.c b/kernel/fd.c new file mode 100644 index 0000000..19068c4 --- /dev/null +++ b/kernel/fd.c @@ -0,0 +1,360 @@ +#include <asm/interrupt.h> +#include <asm/io.h> +#include <errno.h> +#include <kernel/con.h> +#include <kernel/fs.h> +#include <kernel/kernel.h> +#include <kernel/sched.h> +#include <limits.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> + +#define FDBASE 0x3F0 + +#define FDSRA FDBASE +#define FDSRB (FDBASE + 1) +#define FDDOR (FDBASE + 2) +#define FDTDR (FDBASE + 3) +#define FDMSR (FDBASE + 4) +#define FDDRS (FDBASE + 4) +#define FDDATA (FDBASE + 5) +#define FDDIR (FDBASE + 7) +#define FDCCR (FDBASE + 7) + +#define SECTS(n) ((n) * (BLOCK_SIZE / 512)) +#define BLOCKS(n) ((n) / (BLOCK_SIZE / 512)) + +#define NTYPES (sizeof(ftypes) / sizeof(struct ftype)) +#define NFDS (sizeof(fd) / sizeof(struct fdrive)) + +/* helper macros for converting LBA into CHS */ +#define C(lba, ft) \ + (lba / (ft->h * ft->s)) +#define H(lba, ft) \ + ((lba / ft->s) % ft->h) +#define S(lba, ft) \ + ((lba % ft->s) + 1) + +#define PRESENT(fd) ((fd.type) && (fd.type != ftypes)) + +/* TODO: replace any delay loops with sleep_on() */ +/* TODO: implement a head scheduling algorithm */ +/* TODO: implement write functionality */ +/* TODO: implement access to multiple drives */ +/* TODO: seek before reading */ +/* TODO: check drive ready state in floppy driver (ensure the drive door is closed before any operation) */ +/* TODO: cut back on printk() usage */ + +/* TODO: implement a timeout to prevent system hangs */ +#define WAITIRQ(x) ({ \ + irq_fired = 0; \ + (x); \ + while(!irq_fired); \ + irq_fired = 0; \ + }) + +extern void fd_isr(void); + +struct sense_result { + uint8_t st0; + uint8_t cyl; +} __attribute__((packed)); + +struct ftype { + uint16_t c; + uint8_t h; + uint8_t s; + uint8_t drate; /* 0: 500Kb/s, 1: 300Kb/s, 2: 250Kb/s, 3: 1Mb/s */ + int delay; /* time to spin up (ms) */ + char *name; +}; + +struct fdrive { + struct ftype *type; + struct task_struct *seek_wait; + uint8_t cyl; + uint8_t st0; +}; + +/* possible floppy disk/drive configurations */ +static struct ftype ftypes[] = { + { 0, 0, 0, 0, 0, "none" }, + { 40, 2, 9, 1, 500, "360KB 5 1/4\"" }, + { 80, 2, 15, 0, 500, "1.2MB 5 1/4\"" }, + { 80, 2, 9, 2, 300, "720KB 3 1/2\"" }, + { 80, 2, 18, 0, 300, "1.44MB 3 1/2\"" }, + { 80, 2, 36, 3, 300, "2.88MB 3 1/2\"" } +}; + +static volatile int irq_fired; + +static struct fdrive fd[4]; + +static char buffer[512] __attribute__((aligned (0x10000))); + +static struct task_struct *read_wait; + +static ssize_t read_data(void *buf, size_t len) { + ssize_t c = 0; + uint8_t *p = buf; + + if(!buf) + return 0; + + if(len > SSIZE_MAX) + len = SSIZE_MAX; + + /* + * TODO: fix this. + * This entire routine really is garbage. + * It doesn't use a timeout (potentially + * hanging the entire system) and doesn't + * handle the DIO flag properly. + */ + while(len--) { + while(!(inb(FDMSR) & 0x80)); + + if(!(inb(FDMSR) & 0x40)) + break; + + *p++ = inb(FDDATA); + c++; + + /* delay */ + inb(0x80); + } + + /* empty out any remaining bytes the FDC may have for us */ + while((inb(FDMSR) & 0xC0) == 0xC0) + inb(FDDATA); + + if(!c) + return -1; + return c; +} + +static void write_data(void *buf, size_t len) { + uint8_t *p = buf; + + if(!buf) + return; + + /* TODO: timeout waiting for RQM flag */ + while(len--) { + while(!(inb(FDMSR) & 0x80)); + + if((inb(FDMSR) & 0x40)) { + printk("[fd] Expected DIO=0. Drive not ready for data!\n"); + panic(); + } + + outb(FDDATA, *p++); + + /* delay */ + inb(0x80); + } +} + +static int seek(uint8_t drive, int cyl) { + drive &= 3; + + char cmd_seek[] = { + 0x0F, + drive, + cyl + }; + + if(!PRESENT(fd[drive])) + return -EINVAL; + + WAITIRQ(write_data(cmd_seek, sizeof(cmd_seek))); + + /* wait for the seek command to finish */ + while((inb(FDMSR) & (1 << drive))); + + return 0; +} + +static int sense_intr(struct sense_result *sr) { + ssize_t in; + char cmd_sense[] = { 0x08 }; + + /* send the sense interrupt command */ + write_data(cmd_sense, sizeof(cmd_sense)); + in = read_data(sr, sizeof(struct sense_result)); + if(in < sizeof(struct sense_result)) + return -1; + + printk("[fd] Sense command success (Drive: %c:, CYL: 0x%02x, ST0: 0x%02x)\n", 'A' + (sr->st0 & 3), sr->cyl, sr->st0); + return 0; +} + +static void reset(void) { + int i; + int wait = 0; + ssize_t in; + struct sense_result sr; + + /* TODO: set timings depending on the drive */ + char cmd_specify[] = { + 0x03, + 0xDF, /* SRT: 3ms, HUT: 240ms */ + 0x02 /* HLT: 16ms, NDMA: 0 */ + }; + + char cmd_recalibrate[] = { 0x07, 0x00 }; + + /* disable interrupts and put the FDC into reset */ + /* TODO: store FDDOR as static variable as the register is write-only */ + printk("[fd] Resetting FDC...\n"); + outb(FDDOR, inb(FDDOR) & 0xF3); + + /* program the data-rate */ + /* TODO: set this depending on floppy type */ + outb(FDDRS, 0); + outb(FDCCR, 0); + + /* bring the FDC out of reset and re-enable interrupts */ + outb(FDDOR, inb(FDDOR) | 0x0C); + + /* the IRQ handler will issue a sense-interrupt for us */ + WAITIRQ(NULL); + + /* send the specify command */ + write_data(cmd_specify, sizeof(cmd_specify)); + + for(i = 0; i < NFDS; i++) { + if(!PRESENT(fd[i])) + continue; + printk("[fd] Recalibrating Drive %c:...\n", 'A' + i); + cmd_recalibrate[1] = i & 3; + write_data(cmd_recalibrate, sizeof(cmd_recalibrate)); + wait |= 1 << i; + } + + /* wait for all drives to finish recalibrating */ + while((inb(FDMSR) & wait)); +} + +int fd_read_sect(int sect) { + ssize_t in; + struct { + uint8_t st0; + uint8_t st1; + uint8_t st2; + uint8_t c; + uint8_t h; + uint8_t r; + uint8_t n; + } __attribute__((packed)) res; + + char cmd[] = { + 0x46, + H(sect, fd[0].type) << 2, + C(sect, fd[0].type), + H(sect, fd[0].type), + S(sect, fd[0].type), + 0x02, + S(sect, fd[0].type), + 0x1B, + 0xFF + }; + + cli(); + if(read_wait) + sleep_on(&read_wait); + sti(); + + printk("[fd] Reading sector 0x%01x (C: 0x%01x, H: 0x%01x, S: 0x%01x)\n", sect, cmd[2], cmd[3], cmd[4]); + + WAITIRQ(write_data(cmd, sizeof(cmd))); + + in = read_data(&res, sizeof(res)); + if(in == sizeof(res) && !(res.st0 & 0xC0)) { + printk("[fd] Successfully read a sector (ST0: 0x%02x, N: 0x%02x)!\n", res.st0, res.n); + printk(" DATA: %s\n", buffer); + return 0; + } + + printk("[fd] Read command failed!\n"); + return -1; +} + +void fd_interrupt(void) { + struct sense_result sr; + irq_fired = 1; + + wake_up(&read_wait); + + if((inb(FDMSR) & 0xC0) != 0x80) + return; + if(sense_intr(&sr) < sizeof(sr)) + return; + /* deposit result into current drive struct */ + fd[sr.cyl & 3].cyl = sr.cyl; + fd[sr.st0 & 3].st0 = sr.st0; +} + +void fd_block_read(struct buffer *b) { + int i; + void *p = b->b_data; + + for(i = SECTS(b->b_block); i < SECTS(b->b_block + 1); i++) { + if(fd_read_sect(i)) { + b->b_device = 0; + return; + } + memcpy(p, buffer, sizeof(buffer)); + p += 512; + } + + b->b_present = 1; +} + +void fd_init(void) { + int i; + int delay = 0; + uint8_t tmp; + + /* put the controller into reset */ + outb(FDDOR, 0); + + outb(0x70, 0x10); + tmp = inb(0x71); + + fd[0].type = &ftypes[(tmp >> 4) > NTYPES ? 0 : (tmp >> 4)]; + fd[1].type = &ftypes[(tmp & 0xF) > NTYPES ? 0 : (tmp & 0xF)]; + fd[2].type = ftypes; + fd[3].type = ftypes; + + /* setup the drive (motors A and B on) */ + /* TODO: motor timeout */ + outb(FDDOR, 0x3C); + + /* wait for all drives to spin up */ + for(i = 0; i < NFDS; i++) + if(PRESENT(fd[i])) + delay = fd[i].type->delay > delay ? fd[i].type->delay : delay; + delay = (delay / 10) + ticks; + while(ticks < delay); + + /* initialize the DMA controller */ + outb(0x0F, 0x0F); + outb(0x0C, 0xFF); + outb(0x04, (uint32_t) buffer); + outb(0x04, (uint32_t) buffer >> 8); + outb(0x0C, 0xFF); + outb(0x05, sizeof(buffer)); + outb(0x05, sizeof(buffer) >> 8); + outb(0x81, (uint32_t) buffer >> 16); + outb(0x0B, 0x56); + outb(0x0A, 0x02); + + register_isr(0x26, 0, &fd_isr); + irq_enable(0x40); + + reset(); + + printk("[fd] Floppy subsystem initialized (A: %s, B: %s)\n", fd[0].type->name, fd[1].type->name); +} |
