mirror of
https://git.code.sf.net/p/fuse-emulator/fuse
synced 2026-01-28 14:20:54 +03:00
408 lines
9.9 KiB
C
408 lines
9.9 KiB
C
/* fdd.c: Routines for emulating floppy disk drives
|
|
Copyright (c) 2007-2010 Gergely Szasz
|
|
|
|
$Id$
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
Author contact information:
|
|
|
|
Philip: philip-fuse@shadowmagic.org.uk
|
|
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <libspectrum.h>
|
|
|
|
#include "bitmap.h"
|
|
#include "compat.h"
|
|
#include "event.h"
|
|
#include "fdd.h"
|
|
#include "machine.h"
|
|
#include "spectrum.h"
|
|
#include "settings.h"
|
|
#include "upd_fdc.h"
|
|
#include "wd_fdc.h"
|
|
|
|
#define FDD_LOAD_FACT 2
|
|
#define FDD_HEAD_FACT 16 /* load head */
|
|
#define FDD_STEP_FACT 34
|
|
#define FDD_MAX_TRACK 99 /* absolute maximum number of track*/
|
|
#define FDD_TRACK_TRESHOLD 10 /* unreadable disk*/
|
|
|
|
static const char *fdd_error[] = {
|
|
"OK",
|
|
"invalid disk geometry",
|
|
"read only disk",
|
|
"disk not exist (disabled)",
|
|
|
|
"unknown error code" /* will be the last */
|
|
};
|
|
|
|
const fdd_params_t fdd_params[] = {
|
|
{ 0, 0, 0 }, /* Disabled */
|
|
{ 1, 1, 40 }, /* Single-sided 40 track */
|
|
{ 1, 2, 40 }, /* Double-sided 80 track */
|
|
{ 1, 1, 80 }, /* Single-sided 40 track */
|
|
{ 1, 2, 80 } /* Double-sided 80 track */
|
|
};
|
|
|
|
static void
|
|
fdd_event( libspectrum_dword last_tstates, int event, void *user_data );
|
|
|
|
static int motor_event;
|
|
|
|
int
|
|
fdd_init_events( void )
|
|
{
|
|
int error;
|
|
|
|
motor_event = event_register( fdd_event, "FDD motor on" );
|
|
if( motor_event == -1 ) return 1;
|
|
|
|
error = upd_fdc_init_events();
|
|
if( error ) return error;
|
|
|
|
error = wd_fdc_init_events();
|
|
if( error ) return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *
|
|
fdd_strerror( int error )
|
|
{
|
|
if( error >= FDD_LAST_ERROR )
|
|
error = FDD_LAST_ERROR;
|
|
return fdd_error[ error ];
|
|
}
|
|
|
|
/*
|
|
* disk->sides 1 2 1 2 1 2 1 2
|
|
* d->c_head 0 0 1 1 0 0 1 1
|
|
* d->upside 0 0 0 0 1 1 1 1
|
|
*
|
|
* UNREADABLE 0 0 1 0 1 0 0 0
|
|
*/
|
|
|
|
static void
|
|
fdd_set_data( fdd_t *d, int fact )
|
|
{
|
|
int head = d->upsidedown ? 1 - d->c_head : d->c_head;
|
|
|
|
if( !d->loaded )
|
|
return;
|
|
|
|
if( d->unreadable || ( d->disk->sides == 1 && head == 1 ) ||
|
|
d->c_cylinder >= d->disk->cylinders ) {
|
|
d->disk->track = NULL;
|
|
d->disk->clocks = NULL;
|
|
d->disk->fm = NULL;
|
|
d->disk->weak = NULL;
|
|
return;
|
|
}
|
|
|
|
DISK_SET_TRACK( d->disk, head, d->c_cylinder );
|
|
d->c_bpt = d->disk->track[-3] + 256 * d->disk->track[-2];
|
|
if( fact > 0 ) {
|
|
/* this generate a bpt/fact +-10% triangular distribution skip in bytes
|
|
i know, we should use the higher bits of rand(), but we not
|
|
keen on _real_ (pseudo)random numbers... ;)
|
|
*/
|
|
d->disk->i += d->c_bpt / fact + d->c_bpt *
|
|
( rand() % 10 + rand() % 10 - 9 ) / fact / 100;
|
|
while( d->disk->i >= d->c_bpt )
|
|
d->disk->i -= d->c_bpt;
|
|
}
|
|
d->index = d->disk->i ? 0 : 1;
|
|
}
|
|
|
|
/* initialise fdd */
|
|
int
|
|
fdd_init( fdd_t *d, fdd_type_t type, const fdd_params_t *dt, int reinit )
|
|
{
|
|
int upsidedown = d->upsidedown;
|
|
int selected = d->selected;
|
|
int do_read_weak = d->do_read_weak;
|
|
disk_t *disk = d->disk;
|
|
if( dt == NULL ) dt = &fdd_params[0];
|
|
|
|
d->fdd_heads = d->fdd_cylinders = d->c_head = d->c_cylinder = 0;
|
|
d->upsidedown = d->unreadable = d->loaded = d->auto_geom = d->selected = 0;
|
|
d->do_read_weak = 0;
|
|
if( type == FDD_TYPE_NONE )
|
|
d->index = d->tr00 = d->wrprot = 0;
|
|
else
|
|
d->index = d->tr00 = d->wrprot = 1;
|
|
d->type = type;
|
|
|
|
if( dt->heads < 0 || dt->heads > 2 || dt->cylinders < 0 || dt->cylinders > FDD_MAX_TRACK )
|
|
return d->status = FDD_GEOM;
|
|
|
|
if( dt->heads == 0 )
|
|
d->auto_geom = 1;
|
|
d->fdd_heads = dt->heads;
|
|
d->fdd_cylinders = dt->cylinders == 80 ? settings_current.drive_80_max_track : settings_current.drive_40_max_track;
|
|
if( reinit ) {
|
|
d->selected = selected;
|
|
d->do_read_weak = do_read_weak;
|
|
}
|
|
if( reinit && disk ) {
|
|
fdd_unload( d );
|
|
fdd_load( d, disk, upsidedown );
|
|
} else
|
|
d->disk = NULL;
|
|
|
|
return d->status = FDD_OK;
|
|
}
|
|
|
|
void
|
|
fdd_motoron( fdd_t *d, int on )
|
|
{
|
|
if( !d->loaded )
|
|
return;
|
|
on = on > 0 ? 1 : 0;
|
|
if( d->motoron == on )
|
|
return;
|
|
d->motoron = on;
|
|
/*
|
|
TEAC FD55 Spec:
|
|
(13) READY output signal
|
|
i) The FDD is powered on.
|
|
ii) Disk is installed.
|
|
iii)The disk rotates at more than 50% of the rated speed.
|
|
iv) Two index pulses have been counted after item iii) is
|
|
satisfied
|
|
|
|
Note: Pre-ready is the state that at least one INDEX
|
|
pulse has been detected after item iii) is satisfied
|
|
*/
|
|
event_remove_type_user_data( motor_event, d ); /* remove pending motor-on event for *this* drive */
|
|
if( on ) {
|
|
event_add_with_data( tstates + 4 * /* 2 revolution: 2 * 200 / 1000 */
|
|
machine_current->timings.processor_speed / 10,
|
|
motor_event, d );
|
|
} else {
|
|
event_add_with_data( tstates + 3 * /* 1.5 revolution */
|
|
machine_current->timings.processor_speed / 10,
|
|
motor_event, d );
|
|
}
|
|
}
|
|
|
|
void
|
|
fdd_head_load( fdd_t *d, int load )
|
|
{
|
|
if( !d->loaded )
|
|
return;
|
|
load = load > 0 ? 1 : 0;
|
|
if( d->loadhead == load )
|
|
return;
|
|
d->loadhead = load;
|
|
fdd_set_data( d, FDD_HEAD_FACT );
|
|
}
|
|
|
|
void
|
|
fdd_select( fdd_t *d, int select )
|
|
{
|
|
d->selected = select > 0 ? 1 : 0;
|
|
/*
|
|
... Drive Select when activated to a logical
|
|
zero level, will load the R/W head against the
|
|
diskette enabling contact of the R/W head against
|
|
the media. ...
|
|
*/
|
|
if( d->type == FDD_SHUGART )
|
|
fdd_head_load( d, d->selected );
|
|
}
|
|
|
|
|
|
/* load a disk into fdd */
|
|
int
|
|
fdd_load( fdd_t *d, disk_t *disk, int upsidedown )
|
|
{
|
|
if( d->type == FDD_TYPE_NONE )
|
|
return d->status = FDD_NONE;
|
|
|
|
if( disk->sides < 0 || disk->sides > 2 ||
|
|
disk->cylinders < 0 || disk->cylinders > FDD_MAX_TRACK )
|
|
return d->status = FDD_GEOM;
|
|
|
|
if( d->auto_geom )
|
|
d->fdd_heads = disk->sides; /* 1 or 2 */
|
|
if( d->auto_geom )
|
|
d->fdd_cylinders = disk->cylinders > settings_current.drive_40_max_track ?
|
|
settings_current.drive_80_max_track : settings_current.drive_40_max_track;
|
|
|
|
if( disk->cylinders > d->fdd_cylinders + FDD_TRACK_TRESHOLD )
|
|
d->unreadable = 1;
|
|
|
|
d->disk = disk;
|
|
d->upsidedown = upsidedown > 0 ? 1 : 0;
|
|
d->wrprot = d->disk->wrprot; /* write protect */
|
|
d->loaded = 1;
|
|
if( d->type == FDD_SHUGART && d->selected )
|
|
fdd_head_load( d, 1 );
|
|
|
|
d->do_read_weak = disk->have_weak;
|
|
fdd_set_data( d, FDD_LOAD_FACT );
|
|
return d->status = FDD_OK;
|
|
}
|
|
|
|
void
|
|
fdd_unload( fdd_t *d )
|
|
{
|
|
d->ready = d->loaded = 0;
|
|
d->index = d->wrprot = 1;
|
|
d->disk = NULL;
|
|
fdd_motoron( d, 0 );
|
|
if( d->type == FDD_SHUGART && d->selected )
|
|
fdd_head_load( d, 0 );
|
|
}
|
|
|
|
/* change current head */
|
|
void
|
|
fdd_set_head( fdd_t *d, int head )
|
|
{
|
|
if( d->fdd_heads == 1 )
|
|
return;
|
|
|
|
head = head > 0 ? 1 : 0;
|
|
if( d->c_head == head )
|
|
return;
|
|
|
|
d->c_head = head;
|
|
fdd_set_data( d, 0 );
|
|
}
|
|
|
|
/* change current track dir = 1 / -1 */
|
|
void
|
|
fdd_step( fdd_t *d, fdd_dir_t direction )
|
|
{
|
|
if( direction == FDD_STEP_OUT ) {
|
|
if( d->c_cylinder > 0 )
|
|
d->c_cylinder--;
|
|
} else { /* direction == FDD_STEP_IN */
|
|
if( d->c_cylinder < d->fdd_cylinders - 1 )
|
|
d->c_cylinder++;
|
|
}
|
|
if( d->c_cylinder == 0 )
|
|
d->tr00 = 1;
|
|
else
|
|
d->tr00 = 0;
|
|
|
|
fdd_set_data( d, FDD_STEP_FACT );
|
|
}
|
|
|
|
/* read/write next byte from/to sector */
|
|
int
|
|
fdd_read_write_data( fdd_t *d, fdd_write_t write )
|
|
{
|
|
if( !d->selected || !d->ready || !d->loadhead || d->disk->track == NULL ) {
|
|
if( d->loaded && d->motoron ) { /* spin the disk */
|
|
if( d->disk->i >= d->c_bpt ) { /* next data byte */
|
|
d->disk->i = 0;
|
|
}
|
|
if( !write )
|
|
d->data = 0x100; /* no data */
|
|
d->disk->i++;
|
|
d->index = d->disk->i >= d->c_bpt ? 1 : 0;
|
|
}
|
|
return d->status = FDD_OK;
|
|
}
|
|
|
|
if( d->disk->i >= d->c_bpt ) { /* next data byte */
|
|
d->disk->i = 0;
|
|
}
|
|
if( write ) {
|
|
if( d->disk->wrprot ) {
|
|
d->disk->i++;
|
|
d->index = d->disk->i >= d->c_bpt ? 1 : 0;
|
|
return d->status = FDD_RDONLY;
|
|
}
|
|
d->disk->track[ d->disk->i ] = d->data & 0x00ff;
|
|
if( d->data & 0xff00 )
|
|
bitmap_set( d->disk->clocks, d->disk->i );
|
|
else
|
|
bitmap_reset( d->disk->clocks, d->disk->i );
|
|
|
|
if( d->marks & 0x01 )
|
|
bitmap_set( d->disk->fm, d->disk->i );
|
|
else
|
|
bitmap_reset( d->disk->fm, d->disk->i );
|
|
#if 0 /* hmm... we cannot write weak data with 'standard' hardware */
|
|
if( d->marks & 0x02 )
|
|
bitmap_set( d->disk->weak, d->disk->i );
|
|
else
|
|
bitmap_reset( d->disk->weak, d->disk->i );
|
|
#else
|
|
bitmap_reset( d->disk->weak, d->disk->i );
|
|
#endif
|
|
d->disk->dirty = 1;
|
|
} else { /* read */
|
|
d->data = d->disk->track[ d->disk->i ];
|
|
if( bitmap_test( d->disk->clocks, d->disk->i ) )
|
|
d->data |= 0xff00;
|
|
d->marks = 0;
|
|
if( bitmap_test( d->disk->fm, d->disk->i ) )
|
|
d->marks |= 0x01;
|
|
if( bitmap_test( d->disk->weak, d->disk->i ) ) {
|
|
d->marks |= 0x02;
|
|
/* mess up data byte */
|
|
d->data &= rand() % 0xff, d->data |= rand() % 0xff;
|
|
}
|
|
}
|
|
d->disk->i++;
|
|
d->index = d->disk->i >= d->c_bpt ? 1 : 0;
|
|
|
|
return d->status = FDD_OK;
|
|
}
|
|
|
|
void fdd_flip( fdd_t *d, int upsidedown )
|
|
{
|
|
if( !d->loaded )
|
|
return;
|
|
|
|
d->upsidedown = upsidedown > 0 ? 1 : 0;
|
|
fdd_set_data( d, FDD_LOAD_FACT );
|
|
}
|
|
|
|
void
|
|
fdd_wrprot( fdd_t *d, int wrprot )
|
|
{
|
|
if( !d->loaded )
|
|
return;
|
|
|
|
d->wrprot = d->disk->wrprot = wrprot;
|
|
}
|
|
|
|
void
|
|
fdd_wait_index_hole( fdd_t *d )
|
|
{
|
|
if( !d->selected || !d->ready )
|
|
return;
|
|
|
|
d->disk->i = 0;
|
|
d->index = 1;
|
|
}
|
|
|
|
static void
|
|
fdd_event( libspectrum_dword last_tstates GCC_UNUSED, int event,
|
|
void *user_data )
|
|
{
|
|
fdd_t *d = user_data;
|
|
d->ready = ( d->motoron & d->loaded ); /* 0x01 & 0x01 */
|
|
}
|