Bochs/bochs/iodev/floppy.cc
2001-05-24 14:08:54 +00:00

1298 lines
41 KiB
C++

// Copyright (C) 2001 MandrakeSoft S.A.
//
// MandrakeSoft S.A.
// 43, rue d'Aboukir
// 75002 Paris - France
// http://www.linux-mandrake.com/
// http://www.mandrakesoft.com/
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
extern "C" {
#include <errno.h>
}
#include "bochs.h"
#define LOG_THIS bx_floppy.
bx_floppy_ctrl_c bx_floppy;
#if BX_USE_FD_SMF
#define this (&bx_floppy)
#endif
/* for main status register */
#define FD_MS_MRQ 0x80
#define FD_MS_DIO 0x40
#define FD_MS_NDMA 0x20
#define FD_MS_BUSY 0x10
#define FD_MS_ACTD 0x08
#define FD_MS_ACTC 0x04
#define FD_MS_ACTB 0x02
#define FD_MS_ACTA 0x01
#define FROM_FLOPPY 10
#define TO_FLOPPY 11
#define FLOPPY_DMA_CHAN 2
bx_floppy_ctrl_c::bx_floppy_ctrl_c(void)
{
setprefix("[FDD ]");
settype(FDLOG);
BX_DEBUG(("Init.\n"));
}
bx_floppy_ctrl_c::~bx_floppy_ctrl_c(void)
{
// nothing for now
BX_DEBUG(("Exit.\n"));
}
void
bx_floppy_ctrl_c::init(bx_devices_c *d, bx_cmos_c *cmos)
{
BX_FD_THIS devices = d;
BX_FD_THIS devices->register_irq(6, "Floppy Drive");
for (unsigned addr=0x03F2; addr<=0x03F7; addr++) {
BX_FD_THIS devices->register_io_read_handler(this, read_handler,
addr, "Floppy Drive");
BX_FD_THIS devices->register_io_write_handler(this, write_handler,
addr, "Floppy Drive");
}
cmos->s.reg[0x10] = 0x00; /* start out with: no drive 0, no drive 1 */
BX_FD_THIS s.num_supported_floppies = 0;
//
// Floppy A setup
//
BX_FD_THIS s.media[0].sectors_per_track = 0;
BX_FD_THIS s.media[0].tracks = 0;
BX_FD_THIS s.media[0].heads = 0;
BX_FD_THIS s.media[0].sectors = 0;
BX_FD_THIS s.media[0].type = BX_FLOPPY_NONE;
BX_FD_THIS s.media[0].fd = -1;
BX_FD_THIS s.media_present[0] = 0;
switch (bx_options.floppya.type) {
case BX_FLOPPY_NONE:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0x0f) | 0x00;
break;
case BX_FLOPPY_1_2:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0x0f) | 0x20;
break;
case BX_FLOPPY_720K:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0x0f) | 0x30;
break;
case BX_FLOPPY_1_44:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0x0f) | 0x40;
break;
case BX_FLOPPY_2_88:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0x0f) | 0x50;
break;
default:
BX_PANIC(( "unknown floppya type\n" ));
}
if (bx_options.floppya.type != BX_FLOPPY_NONE) {
BX_FD_THIS s.num_supported_floppies++;
if ( bx_options.floppya.initial_status == BX_INSERTED) {
if (evaluate_media(bx_options.floppya.type, bx_options.floppya.path,
& BX_FD_THIS s.media[0]))
BX_FD_THIS s.media_present[0] = 1;
}
}
//
// Floppy B setup
//
BX_FD_THIS s.media[1].sectors_per_track = 0;
BX_FD_THIS s.media[1].tracks = 0;
BX_FD_THIS s.media[1].heads = 0;
BX_FD_THIS s.media[1].sectors = 0;
BX_FD_THIS s.media[1].type = BX_FLOPPY_NONE;
BX_FD_THIS s.media[1].fd = -1;
BX_FD_THIS s.media_present[1] = 0;
switch (bx_options.floppyb.type) {
case BX_FLOPPY_NONE:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0xf0) | 0x00;
break;
case BX_FLOPPY_1_2:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0xf0) | 0x02;
break;
case BX_FLOPPY_720K:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0xf0) | 0x03;
break;
case BX_FLOPPY_1_44:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0xf0) | 0x04;
break;
case BX_FLOPPY_2_88:
cmos->s.reg[0x10] = (cmos->s.reg[0x10] & 0xf0) | 0x05;
break;
default:
BX_PANIC(("unknown floppyb type\n"));
}
if (bx_options.floppyb.type != BX_FLOPPY_NONE) {
BX_FD_THIS s.num_supported_floppies++;
if ( bx_options.floppyb.initial_status == BX_INSERTED) {
if (evaluate_media(bx_options.floppyb.type, bx_options.floppyb.path,
& BX_FD_THIS s.media[1]))
BX_FD_THIS s.media_present[1] = 1;
}
}
/* CMOS Equipment Byte register */
if (BX_FD_THIS s.num_supported_floppies > 0)
cmos->s.reg[0x14] = (cmos->s.reg[0x14] & 0x3e) |
((BX_FD_THIS s.num_supported_floppies-1) << 6) |
1;
else
cmos->s.reg[0x14] = (cmos->s.reg[0x14] & 0x3e);
BX_FD_THIS s.floppy_timer_index =
bx_pc_system.register_timer( this, timer_handler,
bx_options.floppy_command_delay, 0,0);
BX_INFO(("bx_options.floppy_command_delay = %u\n",
(unsigned) bx_options.floppy_command_delay));
}
void
bx_floppy_ctrl_c::reset(unsigned source)
{
Bit32u i;
BX_FD_THIS s.data_rate = 0; /* 500 Kbps */
BX_FD_THIS s.command_complete = 1; /* waiting for new command */
BX_FD_THIS s.command_index = 0;
BX_FD_THIS s.command_size = 0;
BX_FD_THIS s.pending_command = 0;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.result_size = 0;
/* data register ready, not in DMA mode */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ;
BX_FD_THIS s.status_reg0 = 0;
BX_FD_THIS s.status_reg1 = 0;
BX_FD_THIS s.status_reg2 = 0;
BX_FD_THIS s.status_reg3 = 0;
// software reset (via DOR port 0x3f2 bit 2) does not change DOR
if (source == BX_RESET_HARDWARE) {
BX_FD_THIS s.DOR = 0x0c;
// motor off, drive 3..0
// DMA/INT enabled
// normal operation
// drive select 0
// DIR affected only by hard reset
BX_FD_THIS s.DIR |= 0x80; // disk changed
}
for (i=0; i<4; i++) {
BX_FD_THIS s.cylinder[i] = 0;
BX_FD_THIS s.head[i] = 0;
BX_FD_THIS s.sector[i] = 0;
}
BX_FD_THIS s.floppy_buffer_index = 0;
}
// static IO port read callback handler
// redirects to non-static class handler to avoid virtual functions
Bit32u
bx_floppy_ctrl_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
{
#if !BX_USE_FD_SMF
bx_floppy_ctrl_c *class_ptr = (bx_floppy_ctrl_c *) this_ptr;
return( class_ptr->read(address, io_len) );
}
/* reads from the floppy io ports */
Bit32u
bx_floppy_ctrl_c::read(Bit32u address, unsigned io_len)
{
#else
UNUSED(this_ptr);
#endif // !BX_USE_FD_SMF
Bit8u status, value;
if (io_len > 1)
BX_PANIC(("io read from address %08x, len=%u\n",
(unsigned) address, (unsigned) io_len));
// ???
//if (bx_cpu.cs.selector.value != 0xf000) {
// BX_INFO(("BIOS: floppy: read access to port %04x\n", (unsigned) address);
// }
if (bx_dbg.floppy)
BX_INFO(("read access to port %04x\n", (unsigned) address));
switch (address) {
#if BX_DMA_FLOPPY_IO
case 0x3F2: // diskette controller digital output register
value = BX_FD_THIS s.DOR;
return(value);
break;
case 0x3F4: /* diskette controller main status register */
status = BX_FD_THIS s.main_status_reg;
return(status);
break;
case 0x3F5: /* diskette controller data */
if (BX_FD_THIS s.result_size == 0) {
BX_PANIC(("diskette controller:port3f5: no results to read\n"));
}
value = BX_FD_THIS s.result[BX_FD_THIS s.result_index++];
if (BX_FD_THIS s.result_index >= BX_FD_THIS s.result_size) {
BX_FD_THIS s.result_size = 0;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ;
}
return(value);
break;
#endif // #if BX_DMA_FLOPPY_IO
case 0x3F6: // Reserved for future floppy controllers
// This address shared with the hard drive controller
value = BX_FD_THIS devices->hard_drive->read_handler(BX_FD_THIS devices->hard_drive, address, io_len);
return( value );
break;
case 0x3F7: // diskette controller digital input register
// This address shared with the hard drive controller:
// Bit 7 : floppy
// Bits 6..0: hard drive
value = BX_FD_THIS devices->hard_drive->read_handler(BX_FD_THIS devices->hard_drive, address, io_len);
value &= 0x7f;
// add in diskette change line
value |= (BX_FD_THIS s.DIR & 0x80);
return( value );
break;
}
#if BX_DMA_FLOPPY_IO
BX_PANIC(("io_read: bailing\n"));
return(0);
#endif // #if BX_DMA_FLOPPY_IO
}
// static IO port write callback handler
// redirects to non-static class handler to avoid virtual functions
void
bx_floppy_ctrl_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len)
{
#if !BX_USE_FD_SMF
bx_floppy_ctrl_c *class_ptr = (bx_floppy_ctrl_c *) this_ptr;
class_ptr->write(address, value, io_len);
}
/* writes to the floppy io ports */
void
bx_floppy_ctrl_c::write(Bit32u address, Bit32u value, unsigned io_len)
{
#else
UNUSED(this_ptr);
#endif // !BX_USE_FD_SMF
Bit8u dma_and_interrupt_enable;
Bit8u normal_operation, prev_normal_operation;
Bit8u drive_select;
Bit8u motor_on_drive0, motor_on_drive1;
if (io_len > 1)
BX_PANIC(("io write to address %08x, len=%u\n",
(unsigned) address, (unsigned) io_len));
// ???
//if (bx_cpu.cs.selector.value != 0xf000) {
// BX_INFO(("BIOS: floppy: write access to port %04x, value=%02x\n",
// (unsigned) address, (unsigned) value));
// }
if (bx_dbg.floppy)
BX_INFO(("write access to port %04x, value=%02x\n",
(unsigned) address, (unsigned) value));
switch (address) {
#if BX_DMA_FLOPPY_IO
case 0x3F2: /* diskette controller digital output register */
motor_on_drive1 = value & 0x20;
motor_on_drive0 = value & 0x10;
dma_and_interrupt_enable = value & 0x08;
if (!dma_and_interrupt_enable)
BX_DEBUG(("DMA and interrupt capabilities disabled\n"));
normal_operation = value & 0x04;
drive_select = value & 0x03;
prev_normal_operation = BX_FD_THIS s.DOR & 0x04;
BX_FD_THIS s.DOR = value;
if (prev_normal_operation==0 && normal_operation) {
// transition from RESET to NORMAL
#if 0
BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
BX_FD_THIS s.pending_command = 0xfe; // RESET pending
bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
bx_options.floppy_command_delay, 0 );
#endif
}
else if (prev_normal_operation && normal_operation==0) {
// transition from NORMAL to RESET
BX_FD_THIS s.main_status_reg = FD_MS_BUSY;
BX_FD_THIS s.pending_command = 0xfe; // RESET pending
bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
bx_options.floppy_command_delay, 0 );
}
if (bx_dbg.floppy) {
BX_INFO(("io_write: digital output register\n"));
BX_INFO((" motor on, drive1 = %d\n", motor_on_drive1 > 0));
BX_INFO((" motor on, drive0 = %d\n", motor_on_drive0 > 0));
BX_INFO((" dma_and_interrupt_enable=%02x\n",
(unsigned) dma_and_interrupt_enable));
BX_INFO((" normal_operation=%02x\n",
(unsigned) normal_operation));
BX_INFO((" drive_select=%02x\n",
(unsigned) drive_select));
}
if (drive_select>1) {
BX_PANIC(("io_write: drive_select>1\n"));
}
break;
case 0x3f4: /* diskette controller data rate select register */
BX_PANIC(("io_write: data rate select register\n"));
break;
case 0x3F5: /* diskette controller data */
if (bx_dbg.floppy)
BX_INFO(("command = %02x\n", (unsigned) value));
if (BX_FD_THIS s.command_complete) {
if (BX_FD_THIS s.pending_command!=0)
BX_PANIC(("io: 3f5: receiving new comm, old one (%02x) pending\n",
(unsigned) BX_FD_THIS s.pending_command));
BX_FD_THIS s.command[0] = value;
BX_FD_THIS s.command_complete = 0;
BX_FD_THIS s.command_index = 1;
/* read/write command in progress */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_BUSY;
switch (value) {
case 0x03: /* specify */
BX_FD_THIS s.command_size = 3;
break;
case 0x04: // get status
BX_FD_THIS s.command_size = 2;
break;
case 0x07: /* recalibrate */
BX_FD_THIS s.command_size = 2;
break;
case 0x08: /* sense interrupt status */
BX_FD_THIS s.command_size = 1;
floppy_command();
BX_FD_THIS s.command_complete = 1;
break;
case 0x0f: /* seek */
BX_FD_THIS s.command_size = 3;
break;
case 0x4a: /* read ID */
BX_FD_THIS s.command_size = 2;
break;
case 0xc5: /* write normal data */
BX_FD_THIS s.command_size = 9;
break;
case 0xe6: /* read normal data */
BX_FD_THIS s.command_size = 9;
break;
case 0x13: // Configure command (Enhanced)
BX_FD_THIS s.command_size = 3;
break;
case 0x0e: // dump registers (Enhanced drives)
case 0x10: // Version command, standard controller returns 80h
case 0x18: // National Semiconductor version command; return 80h
// These commands are not implemented on the standard
// controller and return an error. They are available on
// the enhanced controller.
BX_FD_THIS s.command_size = 1;
BX_FD_THIS s.result[0] = 0x80;
BX_FD_THIS s.result_size = 1;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.command_complete = 1;
break;
default:
BX_PANIC(("io write:3f5: unsupported case 0x%02x\n",
(unsigned) value));
break;
}
}
else {
BX_FD_THIS s.command[BX_FD_THIS s.command_index++] =
value;
if (BX_FD_THIS s.command_index ==
BX_FD_THIS s.command_size) {
/* read/write command not in progress any more */
floppy_command();
BX_FD_THIS s.command_complete = 1;
}
}
if (bx_dbg.floppy)
BX_INFO(("io_write: diskette controller data\n"));
return;
break;
#endif // #if BX_DMA_FLOPPY_IO
case 0x3F6: /* diskette controller (reserved) */
if (bx_dbg.floppy)
BX_INFO(("io_write: reserved register unsupported\n"));
// this address shared with the hard drive controller
BX_FD_THIS devices->hard_drive->write_handler(BX_FD_THIS devices->hard_drive, address, value, io_len);
break;
#if BX_DMA_FLOPPY_IO
case 0x3F7: /* diskette controller configuration control register */
if (bx_dbg.floppy)
BX_INFO(("io_write: config control register\n"));
BX_FD_THIS s.data_rate = value & 0x03;
if (bx_dbg.floppy)
switch (BX_FD_THIS s.data_rate) {
case 0: BX_INFO((" 500 Kbps\n")); break;
case 1: BX_INFO((" 300 Kbps\n")); break;
case 2: BX_INFO((" 250 Kbps\n")); break;
case 3: BX_INFO((" 1 Mbps\n")); break;
}
return;
break;
default:
BX_PANIC(("io_write: unknown port %04h\n", (unsigned) address));
break;
#endif // #if BX_DMA_FLOPPY_IO
}
}
void
bx_floppy_ctrl_c::floppy_command(void)
{
#if BX_PROVIDE_CPU_MEMORY==0
BX_PANIC(("floppy_command(): uses DMA: not supported for"
" external environment\n"));
#else
unsigned i;
Bit8u step_rate_time;
Bit8u head_unload_time;
Bit8u head_load_time;
Bit8u motor_on;
Bit8u head, drive, cylinder, sector, eot;
Bit8u sector_size, data_length;
Bit32u logical_sector;
if (bx_dbg.floppy) {
BX_INFO(("FLOPPY COMMAND: "));
for (i=0; i<BX_FD_THIS s.command_size; i++)
BX_INFO(("[%02x] ", (unsigned) BX_FD_THIS s.command[i]));
BX_INFO(("\n"));
}
#if 0
/* execute phase of command is in progress (non DMA mode) */
BX_FD_THIS s.main_status_reg |= 20;
#endif
switch (BX_FD_THIS s.command[0]) {
case 0x03: // specify
//BX_INFO(("floppy_command specify\n"));
// execution: specified parameters are loaded
// result: no result bytes, no interrupt
step_rate_time = BX_FD_THIS s.command[1] >> 4;
head_unload_time = BX_FD_THIS s.command[1] & 0x0f;
head_load_time = BX_FD_THIS s.command[2] >> 1;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ;
return;
break;
case 0x04: // get status
BX_FD_THIS s.result[0] = 0x00;
BX_FD_THIS s.result_size = 1;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
return;
break;
case 0x07: // recalibrate
//BX_INFO(("floppy_command recalibrate\n"));
drive = (BX_FD_THIS s.command[1] & 0x03);
BX_FD_THIS s.DOR &= 0xfc;
BX_FD_THIS s.DOR |= drive;
if (bx_dbg.floppy)
BX_INFO(("floppy_command(): recalibrate drive %u\n",
(unsigned) drive));
if (drive > 1)
BX_PANIC(("floppy_command(): drive > 1\n"));
//motor_on = BX_FD_THIS s.DOR & 0xf0;
motor_on = ( (BX_FD_THIS s.DOR>>(drive+4))
& 0x01 );
if (motor_on == 0) {
BX_INFO(("floppy_command(): recal drive with motor off\n"));
}
if (drive==0)
BX_FD_THIS s.DOR |= 0x10; // turn on MOTA
else
BX_FD_THIS s.DOR |= 0x20; // turn on MOTB
BX_FD_THIS s.cylinder[drive] = 0;
bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
bx_options.floppy_command_delay, 0 );
/* command head to track 0
* controller set to non-busy
* error condition noted in Status reg 0's equipment check bit
* seek end bit set to 1 in Status reg 0 regardless of outcome
*/
/* data reg not ready, controller busy */
BX_FD_THIS s.main_status_reg = FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.pending_command = 0x07; // recalibrate pending
return;
break;
case 0x08: /* sense interrupt status */
//BX_INFO(("floppy_command sense interrupt status\n"));
/* execution:
* get status
* result:
* no interupt
* byte0 = status reg0
* byte1 = current cylinder number (0 to 79)
*/
/*BX_FD_THIS s.status_reg0 = ;*/
drive = BX_FD_THIS s.DOR & 0x03;
BX_FD_THIS s.result[0] = 0x20 | drive;
BX_FD_THIS s.result[1] = BX_FD_THIS s.cylinder[drive];
BX_FD_THIS s.result_size = 2;
BX_FD_THIS s.result_index = 0;
/* read ready */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
if (bx_dbg.floppy)
BX_INFO(("sense interrupt status\n"));
return;
break;
case 0x0f: /* seek */
//BX_INFO(("floppy_command seek\n"));
/* command:
* byte0 = 0F
* byte1 = drive & head select
* byte2 = cylinder number
* execution:
* postion head over specified cylinder
* result:
* no result bytes, issues an interrupt
*/
drive = BX_FD_THIS s.command[1] & 0x03;
BX_FD_THIS s.DOR &= 0xfc;
BX_FD_THIS s.DOR |= drive;
BX_FD_THIS s.head[drive] = (BX_FD_THIS s.command[1] >> 2) & 0x01;
BX_FD_THIS s.cylinder[drive] = BX_FD_THIS s.command[2];
if (drive > 1)
BX_PANIC(("floppy_command(): seek: drive>1\n"));
/* ??? should also check cylinder validity */
bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
bx_options.floppy_command_delay, 0 );
/* data reg not ready, controller busy */
BX_FD_THIS s.main_status_reg = FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.pending_command = 0x0f; /* seek pending */
return;
break;
case 0x13: // Configure
BX_DEBUG(("io: configure (mode=%02xh, pretrack=%02xh)\n",
(unsigned)(BX_FD_THIS s.command[2]), (unsigned)(BX_FD_THIS s.command[3]) ));
BX_FD_THIS s.result_size = 0;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.pending_command = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
return;
break;
case 0x4a: // read ID
//BX_INFO(("floppy_command read ID\n")); // ???
drive = BX_FD_THIS s.command[1] & 0x03;
BX_FD_THIS s.DOR &= 0xfc;
BX_FD_THIS s.DOR |= drive;
motor_on = (BX_FD_THIS s.DOR>>(drive+4)) & 0x01;
if (motor_on == 0)
BX_PANIC(("floppy_command(): 4a: motor not on\n"));
if (drive > 1)
BX_PANIC(("io: 4a: bad drive #\n"));
BX_FD_THIS s.result_size = 7;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.result[0] = 0; /* ??? */
BX_FD_THIS s.result[1] = 0;
BX_FD_THIS s.result[2] = 0;
BX_FD_THIS s.result[3] = BX_FD_THIS s.cylinder[drive];
BX_FD_THIS s.result[4] = 0; /* head */
BX_FD_THIS s.result[5] = 0; /* sector at completion */
BX_FD_THIS s.result[6] = 2;
bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
bx_options.floppy_command_delay, 0 );
/* data reg not ready, controller busy */
BX_FD_THIS s.main_status_reg = FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.pending_command = 0x4a; /* read ID pending */
return;
break;
case 0xe6: // read normal data
//BX_INFO(("floppy_command read normal data\n"));
case 0xc5: // write normal data
//BX_INFO(("floppy_command write normal data\n"));
if ( (BX_FD_THIS s.DOR & 0x08) == 0 )
BX_PANIC(("read/write command with DMA and int disabled\n"));
drive = BX_FD_THIS s.command[1] & 0x03;
BX_FD_THIS s.DOR &= 0xfc;
BX_FD_THIS s.DOR |= drive;
motor_on = (BX_FD_THIS s.DOR>>(drive+4)) & 0x01;
if (motor_on == 0)
BX_PANIC(("floppy_command(): read/write: motor not on\n"));
head = BX_FD_THIS s.command[3] & 0x01;
cylinder = BX_FD_THIS s.command[2]; /* 0..79 depending */
sector = BX_FD_THIS s.command[4]; /* 1..36 depending */
eot = BX_FD_THIS s.command[6]; /* 1..36 depending */
sector_size = BX_FD_THIS s.command[5];
data_length = BX_FD_THIS s.command[8];
if (bx_dbg.floppy) {
BX_INFO(("\n\nread/write normal data\n"));
BX_INFO(("BEFORE\n"));
BX_INFO((" drive = %u\n", (unsigned) drive));
BX_INFO((" head = %u\n", (unsigned) head));
BX_INFO((" cylinder = %u\n", (unsigned) cylinder));
BX_INFO((" sector = %u\n", (unsigned) sector));
BX_INFO((" eot = %u\n", (unsigned) eot));
}
if (drive > 1)
BX_PANIC(("io: bad drive #\n"));
if (head > 1)
BX_PANIC(("io: bad head #\n"));
if ( BX_FD_THIS s.media_present[drive] == 0 ) {
// media not in drive, return error
BX_INFO(("attempt to read/write sector %u,"
" sectors/track=%u\n", (unsigned) sector,
(unsigned) BX_FD_THIS s.media[drive].sectors_per_track));
BX_FD_THIS s.result_size = 7;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.result[0] = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive; // abnormal termination
BX_FD_THIS s.result[1] = 0x25; // 0010 0101
BX_FD_THIS s.result[2] = 0x31; // 0011 0001
BX_FD_THIS s.result[3] = BX_FD_THIS s.cylinder[drive];
BX_FD_THIS s.result[4] = BX_FD_THIS s.head[drive];
BX_FD_THIS s.result[5] = BX_FD_THIS s.sector[drive];
BX_FD_THIS s.result[6] = 2; // sector size = 512
BX_FD_THIS s.pending_command = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS devices->pic->trigger_irq(6);
return;
}
if (sector_size != 0x02) { // 512 bytes
BX_PANIC(("sector_size not 512\n"));
}
if ( cylinder >= BX_FD_THIS s.media[drive].tracks ) {
BX_INFO(("\nio: normal read/write: params out of range\n"));
BX_INFO(("*** sector # %02xh\n", (unsigned) sector));
BX_INFO(("*** cylinder #%02xh\n", (unsigned) cylinder));
BX_INFO(("*** eot #%02xh\n", (unsigned) eot));
BX_INFO(("*** head #%02xh\n", (unsigned) head));
BX_PANIC(("bailing\n"));
return;
}
if (sector > BX_FD_THIS s.media[drive].sectors_per_track) {
// requested sector > last sector on track
BX_INFO(("attempt to read/write sector %u,"
" sectors/track=%u\n", (unsigned) sector,
(unsigned) BX_FD_THIS s.media[drive].sectors_per_track));
// set controller to where drive would have left off
// after it discovered the sector was past EOT
BX_FD_THIS s.cylinder[drive] = cylinder;
BX_FD_THIS s.head[drive] = head;
BX_FD_THIS s.sector[drive] = BX_FD_THIS s.media[drive].sectors_per_track;
BX_FD_THIS s.result_size = 7;
BX_FD_THIS s.result_index = 0;
// 0100 0HDD abnormal termination
BX_FD_THIS s.result[0] = 0x40 | (BX_FD_THIS s.head[drive]<<2) | drive;
// 1000 0101 end of cyl/NDAT/NID
BX_FD_THIS s.result[1] = 0x86;
// 0000 0000
BX_FD_THIS s.result[2] = 0x00;
BX_FD_THIS s.result[3] = BX_FD_THIS s.cylinder[drive];
BX_FD_THIS s.result[4] = BX_FD_THIS s.head[drive];
BX_FD_THIS s.result[5] = BX_FD_THIS s.sector[drive];
BX_FD_THIS s.result[6] = 2; // sector size = 512
bx_pc_system.activate_timer( BX_FD_THIS s.floppy_timer_index,
bx_options.floppy_command_delay, 0 );
/* data reg not ready, controller busy */
BX_FD_THIS s.main_status_reg = FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.pending_command = BX_FD_THIS s.command[0];
return;
}
#if 0
if (eot != BX_FD_THIS s.media[drive].sectors_per_track)
BX_DEBUG(("io: bad eot #%02xh\n", (unsigned) eot));
#endif
if (cylinder != BX_FD_THIS s.cylinder[drive])
BX_DEBUG(("io: cylinder request != current cylinder\n"));
logical_sector = (cylinder * 2 * BX_FD_THIS s.media[drive].sectors_per_track) +
(head * BX_FD_THIS s.media[drive].sectors_per_track) +
(sector - 1);
if (logical_sector >= BX_FD_THIS s.media[drive].sectors) {
BX_PANIC(("io: logical sector out of bounds\n"));
}
BX_FD_THIS s.cylinder[drive] = cylinder;
BX_FD_THIS s.sector[drive] = sector;
BX_FD_THIS s.head[drive] = head;
if (BX_FD_THIS s.command[0] == 0xe6) { // read
floppy_xfer(drive, logical_sector*512, BX_FD_THIS s.floppy_buffer,
512, FROM_FLOPPY);
BX_FD_THIS s.floppy_buffer_index = 0;
bx_pc_system.set_DRQ(FLOPPY_DMA_CHAN, 1);
/* data reg not ready, controller busy */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.pending_command = BX_FD_THIS s.command[0];
return;
}
else if (BX_FD_THIS s.command[0] == 0xc5) { // write
BX_FD_THIS s.floppy_buffer_index = 0;
bx_pc_system.set_DRQ(FLOPPY_DMA_CHAN, 1);
/* data reg not ready, controller busy */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.pending_command = BX_FD_THIS s.command[0];
return;
}
else
BX_PANIC(("floppy_command(): unknown read/write command\n"));
return;
break;
default:
BX_PANIC(("floppy_command(): unknown function\n"));
}
BX_PANIC(("\nfloppy_command()\n"));
#endif
}
void
bx_floppy_ctrl_c::floppy_xfer(Bit8u drive, Bit32u offset, Bit8u *buffer,
Bit32u bytes, Bit8u direction)
{
int ret;
if (drive > 1)
BX_PANIC(("floppy_xfer: drive > 1\n"));
if (bx_dbg.floppy) {
BX_INFO(("drive=%u\n", (unsigned) drive));
BX_INFO(("offset=%u\n", (unsigned) offset));
BX_INFO(("bytes=%u\n", (unsigned) bytes));
BX_INFO(("direction=%s\n", (direction==FROM_FLOPPY)? "from" : "to"));
}
#ifdef macintosh
if (strcmp(bx_options.floppya.path, SuperDrive))
#endif
{
ret = lseek(BX_FD_THIS s.media[drive].fd, offset, SEEK_SET);
if (ret < 0) {
BX_PANIC(("could not perform lseek() on floppy image file\n"));
}
}
if (direction == FROM_FLOPPY) {
#ifdef macintosh
if (!strcmp(bx_options.floppya.path, SuperDrive))
ret = fd_read((char *) buffer, offset, bytes);
else
#endif
ret = ::read(BX_FD_THIS s.media[drive].fd, (bx_ptr_t) buffer, bytes);
if (ret < int(bytes)) {
/* ??? */
if (ret > 0) {
BX_INFO(("partial read() on floppy image returns %u/%u\n",
(unsigned) ret, (unsigned) bytes));
memset(buffer + ret, 0, bytes - ret);
}
else {
BX_INFO(("read() on floppy image returns 0\n"));
memset(buffer, 0, bytes);
}
}
}
else { // TO_FLOPPY
#ifdef macintosh
if (!strcmp(bx_options.floppya.path, SuperDrive))
ret = fd_write((char *) buffer, offset, bytes);
else
#endif
ret = ::write(BX_FD_THIS s.media[drive].fd, (bx_ptr_t) buffer, bytes);
if (ret < int(bytes)) {
BX_PANIC(("could not perform write() on floppy image file\n"));
}
}
}
void
bx_floppy_ctrl_c::timer_handler(void *this_ptr)
{
#if defined(SIMX86)
printf("Floppy timer\n");
#endif
bx_floppy_ctrl_c *class_ptr = (bx_floppy_ctrl_c *) this_ptr;
class_ptr->timer();
}
void
bx_floppy_ctrl_c::timer()
{
switch ( BX_FD_THIS s.pending_command ) {
case 0x07: // recal
BX_FD_THIS s.pending_command = 0;
/* write ready, not busy */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ;
BX_FD_THIS devices->pic->trigger_irq(6);
goto reset_changeline;
break;
case 0x0f: // seek
BX_FD_THIS s.pending_command = 0;
/* write ready, not busy */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ;
BX_FD_THIS devices->pic->trigger_irq(6);
goto reset_changeline;
break;
case 0x4a: /* read ID */
case 0xc5: // write normal data
case 0xe6: // read normal data
BX_FD_THIS s.pending_command = 0;
/* read ready, busy */
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO;
BX_FD_THIS devices->pic->trigger_irq(6);
break;
case 0xfe: // (contrived) RESET
reset(BX_RESET_SOFTWARE);
BX_FD_THIS s.pending_command = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ;
BX_FD_THIS devices->pic->trigger_irq(6);
break;
default:
BX_PANIC(("floppy:timer(): unknown case %02x\n",
(unsigned) BX_FD_THIS s.pending_command));
}
return;
reset_changeline:
unsigned drive = BX_FD_THIS s.DOR & 0x3;
if (drive > 1) return;
if (BX_FD_THIS s.media_present[drive])
BX_FD_THIS s.DIR &= ~0x80; // clear disk change line
}
void
bx_floppy_ctrl_c::dma_write(Bit8u *data_byte)
{
// A DMA write is from I/O to Memory
// We need to return then next data byte from the floppy buffer
// to be transfered via the DMA to memory. (read block from floppy)
*data_byte = BX_FD_THIS s.floppy_buffer[BX_FD_THIS s.floppy_buffer_index++];
if (BX_FD_THIS s.floppy_buffer_index >= 512) {
Bit8u drive;
drive = BX_FD_THIS s.DOR & 0x03;
increment_sector(); // increment to next sector before retrieving next one
BX_FD_THIS s.floppy_buffer_index = 0;
if (bx_pc_system.TC) { // Terminal Count line, done
BX_FD_THIS s.pending_command = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.result_size = 7;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.result[0] = (BX_FD_THIS s.head[drive] << 2) | drive;
BX_FD_THIS s.result[1] = 0;
BX_FD_THIS s.result[2] = 0;
BX_FD_THIS s.result[3] = BX_FD_THIS s.cylinder[drive];
BX_FD_THIS s.result[4] = BX_FD_THIS s.head[drive];
BX_FD_THIS s.result[5] = BX_FD_THIS s.sector[drive];
BX_FD_THIS s.result[6] = 2;
if (bx_dbg.floppy) {
BX_INFO(("<<READ DONE>>\n"));
BX_INFO(("AFTER\n"));
BX_INFO((" drive = %u\n", (unsigned) drive));
BX_INFO((" head = %u\n", (unsigned) BX_FD_THIS s.head[drive]));
BX_INFO((" cylinder = %u\n", (unsigned) BX_FD_THIS s.cylinder[drive]));
BX_INFO((" sector = %u\n", (unsigned) BX_FD_THIS s.sector[drive]));
}
BX_FD_THIS devices->pic->trigger_irq(6);
bx_pc_system.set_DRQ(FLOPPY_DMA_CHAN, 0);
}
else { // more data to transfer
Bit32u logical_sector;
logical_sector = (BX_FD_THIS s.cylinder[drive] * 2 *
BX_FD_THIS s.media[drive].sectors_per_track) +
(BX_FD_THIS s.head[drive] *
BX_FD_THIS s.media[drive].sectors_per_track) +
(BX_FD_THIS s.sector[drive] - 1);
floppy_xfer(drive, logical_sector*512, BX_FD_THIS s.floppy_buffer,
512, FROM_FLOPPY);
}
}
}
void
bx_floppy_ctrl_c::dma_read(Bit8u *data_byte)
{
// A DMA read is from Memory to I/O
// We need to write the data_byte which was already transfered from memory
// via DMA to I/O (write block to floppy)
Bit8u drive;
Bit32u logical_sector;
BX_FD_THIS s.floppy_buffer[BX_FD_THIS s.floppy_buffer_index++] = *data_byte;
if (BX_FD_THIS s.floppy_buffer_index >= 512) {
drive = BX_FD_THIS s.DOR & 0x03;
logical_sector = (BX_FD_THIS s.cylinder[drive] * 2 * BX_FD_THIS s.media[drive].sectors_per_track) +
(BX_FD_THIS s.head[drive] * BX_FD_THIS s.media[drive].sectors_per_track) +
(BX_FD_THIS s.sector[drive] - 1);
floppy_xfer(drive, logical_sector*512, BX_FD_THIS s.floppy_buffer,
512, TO_FLOPPY);
increment_sector(); // increment to next sector after writing current one
BX_FD_THIS s.floppy_buffer_index = 0;
if (bx_pc_system.TC) { // Terminal Count line, done
BX_FD_THIS s.pending_command = 0;
BX_FD_THIS s.main_status_reg = FD_MS_MRQ | FD_MS_DIO | FD_MS_BUSY;
BX_FD_THIS s.result_size = 7;
BX_FD_THIS s.result_index = 0;
BX_FD_THIS s.result[0] = (BX_FD_THIS s.head[drive] << 2) | drive;
BX_FD_THIS s.result[1] = 0;
BX_FD_THIS s.result[2] = 0;
BX_FD_THIS s.result[3] = BX_FD_THIS s.cylinder[drive];
BX_FD_THIS s.result[4] = BX_FD_THIS s.head[drive];
BX_FD_THIS s.result[5] = BX_FD_THIS s.sector[drive];
BX_FD_THIS s.result[6] = 2;
if (bx_dbg.floppy) {
BX_INFO(("<<WRITE DONE>>\n"));
BX_INFO(("AFTER\n"));
BX_INFO((" drive = %u\n", (unsigned) drive));
BX_INFO((" head = %u\n", (unsigned) BX_FD_THIS s.head[drive]));
BX_INFO((" cylinder = %u\n", (unsigned) BX_FD_THIS s.cylinder[drive]));
BX_INFO((" sector = %u\n", (unsigned) BX_FD_THIS s.sector[drive]));
}
BX_FD_THIS devices->pic->trigger_irq(6);
bx_pc_system.set_DRQ(FLOPPY_DMA_CHAN, 0);
}
else { // more data to transfer
} // else
} // if BX_FD_THIS s.floppy_buffer_index >= 512
}
void
bx_floppy_ctrl_c::increment_sector(void)
{
Bit8u drive;
drive = BX_FD_THIS s.DOR & 0x03;
// values after completion of data xfer
// ??? calculation depends on base_count being multiple of 512
BX_FD_THIS s.sector[drive] ++;
if (BX_FD_THIS s.sector[drive] > BX_FD_THIS s.media[drive].sectors_per_track) {
BX_FD_THIS s.sector[drive] -= BX_FD_THIS s.media[drive].sectors_per_track;
BX_FD_THIS s.head[drive] ++;
if (BX_FD_THIS s.head[drive] > 1) {
BX_FD_THIS s.head[drive] = 0;
BX_FD_THIS s.cylinder[drive] ++;
if (BX_FD_THIS s.cylinder[drive] >= BX_FD_THIS s.media[drive].tracks) {
// Set to 1 past last possible cylinder value.
// I notice if I set it to tracks-1, prama linux won't boot.
BX_FD_THIS s.cylinder[drive] = BX_FD_THIS s.media[drive].tracks;
BX_INFO(("increment_sector: clamping cylinder to max\n"));
}
}
}
}
unsigned
bx_floppy_ctrl_c::set_media_status(unsigned drive, unsigned status)
{
char *path;
unsigned type;
// if setting to the current value, nothing to do
if (status == BX_FD_THIS s.media_present[drive])
return(status);
if (status == 0) {
// eject floppy
if (BX_FD_THIS s.media[drive].fd >= 0) {
close( BX_FD_THIS s.media[drive].fd );
BX_FD_THIS s.media[drive].fd = -1;
}
BX_FD_THIS s.media_present[drive] = 0;
BX_FD_THIS s.DIR |= 0x80; // disk changed line
return(0);
}
else {
// insert floppy
if (drive == 0) {
path = bx_options.floppya.path;
type = bx_options.floppya.type;
}
else {
path = bx_options.floppyb.path;
type = bx_options.floppyb.type;
}
if (evaluate_media(type, path, & BX_FD_THIS s.media[drive])) {
BX_FD_THIS s.media_present[drive] = 1;
BX_FD_THIS s.DIR |= 0x80; // disk changed line
return(1);
}
else {
BX_FD_THIS s.media_present[drive] = 0;
return(0);
}
}
}
unsigned
bx_floppy_ctrl_c::get_media_status(unsigned drive)
{
return( BX_FD_THIS s.media_present[drive] );
}
Boolean
bx_floppy_ctrl_c::evaluate_media(unsigned type, char *path, floppy_t *media)
{
struct stat stat_buf;
int ret;
char sTemp[1024];
if (type == BX_FLOPPY_NONE)
return(0);
// open media file (image file or device)
#ifdef macintosh
media->fd = 0;
if (strcmp(bx_options.floppya.path, SuperDrive))
#endif
media->fd = open(path, O_RDWR
#ifdef O_BINARY
| O_BINARY
#endif
);
if (media->fd < 0) {
BX_INFO(( "floppy open of %s:\n",path,strerror(errno) ));
return(0);
}
#if BX_WITH_MACOS
if (!strcmp(bx_options.floppya.path, SuperDrive))
ret = fd_stat(&stat_buf);
else
ret = fstat(media->fd, &stat_buf);
#elif BX_WITH_WIN32
stat_buf.st_mode = S_IFCHR;
// maybe replace with code that sets ret to -1 if the disk is not available
ret = 0;
#else
// unix
ret = fstat(media->fd, &stat_buf);
#endif
if (ret) {
BX_PANIC(("fstat()'ing floppy 0 drive image file returns error!\n"));
return(0);
}
if ( S_ISREG(stat_buf.st_mode) ) {
// regular file
switch (type) {
case BX_FLOPPY_720K: // 720K 3.5"
media->type = BX_FLOPPY_720K;
media->sectors_per_track = 9;
media->tracks = 80;
media->heads = 2;
break;
case BX_FLOPPY_1_2: // 1.2M 5.25"
media->type = BX_FLOPPY_1_2;
media->sectors_per_track = 15;
media->tracks = 80;
media->heads = 2;
break;
case BX_FLOPPY_1_44: // 1.44M 3.5"
media->type = BX_FLOPPY_1_44;
if (stat_buf.st_size <= 1474560) {
media->sectors_per_track = 18;
media->tracks = 80;
media->heads = 2;
}
else if (stat_buf.st_size == 1720320) {
media->sectors_per_track = 21;
media->tracks = 80;
media->heads = 2;
}
else if (stat_buf.st_size == 1763328) {
media->sectors_per_track = 21;
media->tracks = 82;
media->heads = 2;
}
else {
BX_INFO(("evaluate_media: file '%s' of unknown size %lu\n",
path, (unsigned long) stat_buf.st_size));
return(0);
}
break;
case BX_FLOPPY_2_88: // 2.88M 3.5"
media->type = BX_FLOPPY_2_88;
media->sectors_per_track = 36;
media->tracks = 80;
media->heads = 2;
break;
default:
BX_PANIC(("evaluate_media: unknown media type\n"));
}
media->sectors = media->heads * media->tracks * media->sectors_per_track;
return(1); // success
}
else if ( S_ISCHR(stat_buf.st_mode)
#if BX_WITH_MACOS == 0
#ifdef S_ISBLK
|| S_ISBLK(stat_buf.st_mode)
#endif
#endif
) {
// character or block device
// assume media is formatted to typical geometry for drive
switch (type) {
case BX_FLOPPY_720K: // 720K 3.5"
media->type = BX_FLOPPY_720K;
media->sectors_per_track = 9;
media->tracks = 80;
media->heads = 2;
break;
case BX_FLOPPY_1_2: // 1.2M 5.25"
media->type = BX_FLOPPY_1_2;
media->sectors_per_track = 15;
media->tracks = 80;
media->heads = 2;
break;
case BX_FLOPPY_1_44: // 1.44M 3.5"
media->type = BX_FLOPPY_1_44;
media->sectors_per_track = 18;
media->tracks = 80;
media->heads = 2;
break;
case BX_FLOPPY_2_88: // 2.88M 3.5"
media->type = BX_FLOPPY_2_88;
media->sectors_per_track = 36;
media->tracks = 80;
media->heads = 2;
break;
default:
BX_PANIC(("evaluate_media: unknown media type\n"));
}
media->sectors = media->heads * media->tracks * media->sectors_per_track;
return(1); // success
}
else {
// unknown file type
BX_INFO(("unknown mode type\n"));
return(0);
}
}