ZXdroid/jni/fuse-0.10.0.2/disk/fdd.c

342 lines
8.0 KiB
C

/* fdd.c: Routines for emulating floppy disk drives
Copyright (c) 2007 Gergely Szasz
$Id: fdd.c 3942 2009-01-10 14:18:46Z pak21 $
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 "disk/upd_fdc.h"
#include "disk/wd_fdc.h"
#include "event.h"
#include "fdd.h"
#include "machine.h"
#include "spectrum.h"
#define FDD_LOAD_FACT 2
#define FDD_HEAD_FACT 16 /* load head */
#define FDD_STEP_FACT 34
static const char *fdd_error[] = {
"OK",
"invalid disk geometry",
"read only disk",
"unknown error code" /* will be the last */
};
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;
return;
}
d->disk->track = d->disk->data +
( d->disk->sides * d->c_cylinder + head ) * d->disk->tlen;
d->disk->clocks = d->disk->track + d->disk->bpt;
if( fact > 0 ) {
/* this generate a bpt/fact +-10% rectangular 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->disk->bpt / fact + d->disk->bpt *
( rand() % 10 + rand() % 10 - 9 ) / fact / 100;
while( d->disk->i >= d->disk->bpt )
d->disk->i -= d->disk->bpt;
}
d->index = d->disk->i ? 0 : 1;
}
/* initialise fdd */
int
fdd_init( fdd_t *d, fdd_type_t type, int heads, int cyls )
{
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->index = d->tr00 = d->wrprot = 1;
d->disk = NULL;
d->type = type;
if( heads < 0 || heads > 2 || cyls < 0 || cyls > 83 )
return d->status = FDD_GEOM;
if( heads == 0 || cyls == 0 )
d->auto_geom = 1;
d->fdd_heads = heads;
d->fdd_cylinders = cyls;
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( disk->sides < 0 || disk->sides > 2 ||
disk->cylinders < 0 || disk->cylinders > 83 )
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 > 42 ? 83 : 42;
if( disk->cylinders > d->fdd_cylinders )
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 );
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->disk->bpt ) { /* next data byte */
d->disk->i = 0;
}
if( !write )
d->data = 0x100; /* no data */
d->disk->i++;
d->index = d->disk->i >= d->disk->bpt ? 1 : 0;
}
return d->status = FDD_OK;
}
if( d->disk->i >= d->disk->bpt ) { /* next data byte */
d->disk->i = 0;
}
if( write ) {
if( d->disk->wrprot ) {
d->disk->i++;
d->index = d->disk->i >= d->disk->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 );
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->disk->i++;
d->index = d->disk->i >= d->disk->bpt ? 1 : 0;
return d->status = FDD_OK;
}
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 */
}