// 16bit code to access floppy drives. // // Copyright (C) 2008 Kevin O'Connor // Copyright (C) 2002 MandrakeSoft S.A. // // This file may be distributed under the terms of the GNU GPLv3 license. #include "types.h" // u8 #include "disk.h" // DISK_RET_SUCCESS #include "config.h" // CONFIG_FLOPPY_SUPPORT #include "biosvar.h" // struct bregs #include "util.h" // irq_disable #include "cmos.h" // inb_cmos #define DEBUGF1(fmt, args...) bprintf(0, fmt , ##args) #define DEBUGF(fmt, args...) #define BX_FLOPPY_ON_CNT 37 /* 2 seconds */ // New diskette parameter table adding 3 parameters from IBM // Since no provisions are made for multiple drive types, most // values in this table are ignored. I set parameters for 1.44M // floppy here struct floppy_ext_dbt_s diskette_param_table2 VISIBLE16 = { .dbt = { .specify1 = 0xAF, .specify2 = 0x02, // head load time 0000001, DMA used .shutoff_ticks = 0x25, .bps_code = 0x02, .sectors = 18, .interblock_len = 0x1B, .data_len = 0xFF, .gap_len = 0x6C, .fill_byte = 0xF6, .settle_time = 0x0F, .startup_time = 0x08, }, .max_track = 79, // maximum track .data_rate = 0, // data transfer rate .drive_type = 4, // drive type in cmos }; void floppy_drive_setup() { u8 type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE); u8 out = 0; if (type & 0xf0) out |= 0x07; if (type & 0x0f) out |= 0x70; SET_BDA(floppy_harddisk_info, out); outb(0x02, PORT_DMA1_MASK_REG); } // Oddities: // Return codes vary greatly - AL not cleared consistenlty, BDA return // status not set consistently, sometimes panics. // Extra outb(0x000a, 0x02) in read? // Does not disable interrupts on failure paths. // numfloppies used before set in int_1308 // int_1305 verifies track but doesn't use it? static inline void set_diskette_current_cyl(u8 drive, u8 cyl) { if (drive) SET_BDA(floppy_track1, cyl); else SET_BDA(floppy_track0, cyl); } static u16 get_drive_type(u8 drive) { // check CMOS to see if drive exists u8 drive_type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE); if (drive == 0) drive_type >>= 4; else drive_type &= 0x0f; return drive_type; } static u16 floppy_media_known(u8 drive) { if (!(GET_BDA(floppy_recalibration_status) & (1<es >> 12; // upper 4 bits u16 base_es = regs->es << 4; // lower 16bits contributed by ES u16 base_address = base_es + regs->bx; // lower 16 bits of address // contributed by ES:BX if (base_address < base_es) // in case of carry, adjust page by 1 page++; // check for 64K boundary overrun u16 last_addr = base_address + count; if (last_addr < base_address) return DISK_RET_EBOUNDARY; u8 mode_register = 0x4a; // single mode, increment, autoinit disable, if (cmd[0] == 0xe6) // read mode_register = 0x46; //DEBUGF("floppy dma c2\n"); outb(0x06, PORT_DMA1_MASK_REG); outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop outb(base_address, PORT_DMA_ADDR_2); outb(base_address>>8, PORT_DMA_ADDR_2); outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop outb(count, PORT_DMA_CNT_2); outb(count>>8, PORT_DMA_CNT_2); // port 0b: DMA-1 Mode Register // transfer type=write, channel 2 outb(mode_register, PORT_DMA1_MODE_REG); // port 81: DMA-1 Page Register, channel 2 outb(page, PORT_DMA_PAGE_2); outb(0x02, PORT_DMA1_MASK_REG); // unmask channel 2 u8 ret = floppy_pio(cmd, cmdlen); if (ret) return ret; // check port 3f4 for accessibility to status bytes if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0) BX_PANIC("int13_diskette: ctrl not ready\n"); // read 7 return status bytes from controller u8 i; for (i=0; i<7; i++) { u8 v = inb(PORT_FD_DATA); cmd[i] = v; SET_BDA(floppy_return_status[i], v); } return 0; } static void floppy_drive_recal(u8 drive) { // send Recalibrate command (2 bytes) to controller u8 data[12]; data[0] = 0x07; // 07: Recalibrate data[1] = drive; // 0=drive0, 1=drive1 floppy_pio(data, 2); SETBITS_BDA(floppy_recalibration_status, 1<al = 0; // no sectors read } static u16 check_drive(struct bregs *regs, u8 drive) { // see if drive exists if (drive > 1 || !get_drive_type(drive)) { // XXX - return type doesn't match floppy_fail(regs, DISK_RET_ETIMEOUT); return 1; } // see if media in drive, and type is known if (floppy_media_known(drive) == 0 && floppy_media_sense(drive) == 0) { floppy_fail(regs, DISK_RET_EMEDIA); return 1; } return 0; } // diskette controller reset static void floppy_1300(struct bregs *regs, u8 drive) { if (drive > 1) { floppy_ret(regs, DISK_RET_EPARAM); return; } if (!get_drive_type(drive)) { floppy_ret(regs, DISK_RET_ETIMEOUT); return; } set_diskette_current_cyl(drive, 0); // current cylinder floppy_ret(regs, DISK_RET_SUCCESS); } // Read Diskette Status static void floppy_1301(struct bregs *regs, u8 drive) { u8 v = GET_BDA(floppy_last_status); regs->ah = v; set_cf(regs, v); } // Read Diskette Sectors static void floppy_1302(struct bregs *regs, u8 drive) { if (check_drive(regs, drive)) return; u8 num_sectors = regs->al; u8 track = regs->ch; u8 sector = regs->cl; u8 head = regs->dh; if (head > 1 || sector == 0 || num_sectors == 0 || track > 79 || num_sectors > 72) { BX_INFO("int13_diskette: read/write/verify: parameter out of range\n"); floppy_fail(regs, DISK_RET_EPARAM); return; } // send read-normal-data command (9 bytes) to controller u8 data[12]; data[0] = 0xe6; // e6: read normal data data[1] = (head << 2) | drive; // HD DR1 DR2 data[2] = track; data[3] = head; data[4] = sector; data[5] = 2; // 512 byte sector size data[6] = sector + num_sectors - 1; // last sector to read on track data[7] = 0; // Gap length data[8] = 0xff; // Gap length u16 ret = floppy_cmd(regs, (num_sectors * 512) - 1, data, 9); if (ret) { floppy_fail(regs, ret); return; } if (data[0] & 0xc0) { floppy_fail(regs, DISK_RET_ECONTROLLER); return; } // ??? should track be new val from return_status[3] ? set_diskette_current_cyl(drive, track); // AL = number of sectors read (same value as passed) floppy_ret(regs, DISK_RET_SUCCESS); } // Write Diskette Sectors static void floppy_1303(struct bregs *regs, u8 drive) { if (check_drive(regs, drive)) return; u8 num_sectors = regs->al; u8 track = regs->ch; u8 sector = regs->cl; u8 head = regs->dh; if (head > 1 || sector == 0 || num_sectors == 0 || track > 79 || num_sectors > 72) { BX_INFO("int13_diskette: read/write/verify: parameter out of range\n"); floppy_fail(regs, DISK_RET_EPARAM); return; } // send write-normal-data command (9 bytes) to controller u8 data[12]; data[0] = 0xc5; // c5: write normal data data[1] = (head << 2) | drive; // HD DR1 DR2 data[2] = track; data[3] = head; data[4] = sector; data[5] = 2; // 512 byte sector size data[6] = sector + num_sectors - 1; // last sector to write on track data[7] = 0; // Gap length data[8] = 0xff; // Gap length u8 ret = floppy_cmd(regs, (num_sectors * 512) - 1, data, 9); if (ret) { floppy_fail(regs, ret); return; } if (data[0] & 0xc0) { if (data[1] & 0x02) { set_fail(regs); regs->ax = 0x0300; return; } BX_PANIC("int13_diskette_function: read error\n"); } // ??? should track be new val from return_status[3] ? set_diskette_current_cyl(drive, track); // AL = number of sectors read (same value as passed) floppy_ret(regs, DISK_RET_SUCCESS); } // Verify Diskette Sectors static void floppy_1304(struct bregs *regs, u8 drive) { if (check_drive(regs, drive)) return; u8 num_sectors = regs->al; u8 track = regs->ch; u8 sector = regs->cl; u8 head = regs->dh; if (head > 1 || sector == 0 || num_sectors == 0 || track > 79 || num_sectors > 72) { BX_INFO("int13_diskette: read/write/verify: parameter out of range\n"); floppy_fail(regs, DISK_RET_EPARAM); return; } // ??? should track be new val from return_status[3] ? set_diskette_current_cyl(drive, track); // AL = number of sectors verified (same value as passed) floppy_ret(regs, DISK_RET_SUCCESS); } // format diskette track static void floppy_1305(struct bregs *regs, u8 drive) { DEBUGF("floppy f05\n"); if (check_drive(regs, drive)) return; u8 num_sectors = regs->al; u8 head = regs->dh; if (head > 1 || num_sectors == 0 || num_sectors > 18) { BX_INFO("int13_diskette: read/write/verify: parameter out of range\n"); floppy_fail(regs, DISK_RET_EPARAM); return; } // send format-track command (6 bytes) to controller u8 data[12]; data[0] = 0x4d; // 4d: format track data[1] = (head << 2) | drive; // HD DR1 DR2 data[2] = 2; // 512 byte sector size data[3] = num_sectors; // number of sectors per track data[4] = 0; // Gap length data[5] = 0xf6; // Fill byte u8 ret = floppy_cmd(regs, (num_sectors * 4) - 1, data, 6); if (ret) { floppy_fail(regs, ret); return; } if (data[0] & 0xc0) { if (data[1] & 0x02) { set_fail(regs); regs->ax = 0x0300; return; } BX_PANIC("int13_diskette_function: read error\n"); } set_diskette_current_cyl(drive, 0); floppy_ret(regs, 0); } // read diskette drive parameters static void floppy_1308(struct bregs *regs, u8 drive) { DEBUGF("floppy f08\n"); u8 drive_type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE); u8 num_floppies = 0; if (drive_type & 0xf0) num_floppies++; if (drive_type & 0x0f) num_floppies++; if (drive > 1) { regs->ax = 0; regs->bx = 0; regs->cx = 0; regs->dx = 0; regs->es = 0; regs->di = 0; regs->dl = num_floppies; set_success(regs); return; } if (drive == 0) drive_type >>= 4; else drive_type &= 0x0f; regs->bh = 0; regs->bl = drive_type; regs->ah = 0; regs->al = 0; regs->dl = num_floppies; switch (drive_type) { case 0: // none regs->cx = 0; regs->dh = 0; // max head # break; case 1: // 360KB, 5.25" regs->cx = 0x2709; // 40 tracks, 9 sectors regs->dh = 1; // max head # break; case 2: // 1.2MB, 5.25" regs->cx = 0x4f0f; // 80 tracks, 15 sectors regs->dh = 1; // max head # break; case 3: // 720KB, 3.5" regs->cx = 0x4f09; // 80 tracks, 9 sectors regs->dh = 1; // max head # break; case 4: // 1.44MB, 3.5" regs->cx = 0x4f12; // 80 tracks, 18 sectors regs->dh = 1; // max head # break; case 5: // 2.88MB, 3.5" regs->cx = 0x4f24; // 80 tracks, 36 sectors regs->dh = 1; // max head # break; case 6: // 160k, 5.25" regs->cx = 0x2708; // 40 tracks, 8 sectors regs->dh = 0; // max head # break; case 7: // 180k, 5.25" regs->cx = 0x2709; // 40 tracks, 9 sectors regs->dh = 0; // max head # break; case 8: // 320k, 5.25" regs->cx = 0x2708; // 40 tracks, 8 sectors regs->dh = 1; // max head # break; default: // ? BX_PANIC("floppy: int13: bad floppy type\n"); } /* set es & di to point to 11 byte diskette param table in ROM */ regs->es = SEG_BIOS; regs->di = (u32)&diskette_param_table2; /* disk status not changed upon success */ } // read diskette drive type static void floppy_1315(struct bregs *regs, u8 drive) { DEBUGF("floppy f15\n"); if (drive > 1) { set_fail(regs); regs->ah = 0; // only 2 drives supported // set_diskette_ret_status here ??? return; } u8 drive_type = get_drive_type(drive); regs->ah = (drive_type != 0); set_success(regs); } // get diskette change line status static void floppy_1316(struct bregs *regs, u8 drive) { DEBUGF("floppy f16\n"); if (drive > 1) { floppy_ret(regs, DISK_RET_EPARAM); return; } floppy_ret(regs, DISK_RET_ECHANGED); } static void floppy_13XX(struct bregs *regs, u8 drive) { BX_INFO("int13_diskette: unsupported AH=%02x\n", regs->ah); floppy_ret(regs, DISK_RET_EPARAM); } void floppy_13(struct bregs *regs, u8 drive) { if (! CONFIG_FLOPPY_SUPPORT) { // Minimal stubs switch (regs->ah) { case 0x01: floppy_1301(regs, drive); break; default: floppy_13XX(regs, drive); break; } return; } switch (regs->ah) { case 0x00: floppy_1300(regs, drive); break; case 0x01: floppy_1301(regs, drive); break; case 0x02: floppy_1302(regs, drive); break; case 0x03: floppy_1303(regs, drive); break; case 0x04: floppy_1304(regs, drive); break; case 0x05: floppy_1305(regs, drive); break; case 0x08: floppy_1308(regs, drive); break; case 0x15: floppy_1315(regs, drive); break; case 0x16: floppy_1316(regs, drive); break; default: floppy_13XX(regs, drive); break; } } // INT 0Eh Diskette Hardware ISR Entry Point void VISIBLE16 handle_0e() { //debug_isr(); if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0) { outb(0x08, PORT_FD_DATA); // sense interrupt status while ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0) ; do { inb(PORT_FD_DATA); } while ((inb(PORT_FD_STATUS) & 0xc0) == 0xc0); } eoi_master_pic(); // diskette interrupt has occurred SETBITS_BDA(floppy_recalibration_status, FRS_TIMEOUT); } // Called from int08 handler. void floppy_tick() { // time to turn off drive(s)? u8 fcount = GET_BDA(floppy_motor_counter); if (fcount) { fcount--; SET_BDA(floppy_motor_counter, fcount); if (fcount == 0) // turn motor(s) off outb(inb(PORT_FD_DOR) & 0xcf, PORT_FD_DOR); } }