mirror of
https://git.code.sf.net/p/fuse-emulator/fuse
synced 2026-01-28 14:20:54 +03:00
522 lines
14 KiB
C
522 lines
14 KiB
C
/* specplus3.c: Spectrum +2A/+3 specific routines
|
|
Copyright (c) 1999-2003 Philip Kendall, Darren Salt
|
|
|
|
$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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
Author contact information:
|
|
|
|
E-mail: pak21-fuse@srcf.ucam.org
|
|
Postal address: 15 Crescent Road, Wokingham, Berks, RG40 2DB, England
|
|
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#ifdef HAVE_765_H
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h> /* Needed to get PATH_MAX */
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_LIBDSK_H
|
|
#include <libdsk.h>
|
|
#endif /* #ifdef HAVE_LIBDSK_H */
|
|
|
|
#include <765.h>
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
#include <libspectrum.h>
|
|
|
|
#include "ay.h"
|
|
#include "display.h"
|
|
#include "fuse.h"
|
|
#include "joystick.h"
|
|
#include "machine.h"
|
|
#include "printer.h"
|
|
#include "settings.h"
|
|
#include "spec128.h"
|
|
#include "specplus2a.h"
|
|
#include "specplus3.h"
|
|
#include "spectrum.h"
|
|
#include "ui/ui.h"
|
|
|
|
static DWORD specplus3_contend_delay( void );
|
|
|
|
#ifdef HAVE_765_H
|
|
static void specplus3_fdc_reset( void );
|
|
static BYTE specplus3_fdc_status( WORD port );
|
|
static BYTE specplus3_fdc_read( WORD port );
|
|
static void specplus3_fdc_write( WORD port, BYTE data );
|
|
|
|
void specplus3_fdc_error( int debug, char *format, ... );
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
static int specplus3_shutdown( void );
|
|
|
|
spectrum_port_info specplus3_peripherals[] = {
|
|
{ 0x0001, 0x0000, spectrum_ula_read, spectrum_ula_write },
|
|
{ 0x00e0, 0x0000, joystick_kempston_read, spectrum_port_nowrite },
|
|
{ 0xc002, 0xc000, ay_registerport_read, ay_registerport_write },
|
|
{ 0xc002, 0x8000, spectrum_port_noread, ay_dataport_write },
|
|
{ 0xc002, 0x4000, spectrum_port_noread, spec128_memoryport_write },
|
|
|
|
#ifdef HAVE_765_H
|
|
{ 0xf002, 0x3000, specplus3_fdc_read, specplus3_fdc_write },
|
|
{ 0xf002, 0x2000, specplus3_fdc_status, spectrum_port_nowrite },
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
{ 0xf002, 0x1000, spectrum_port_noread, specplus3_memoryport_write },
|
|
{ 0xf002, 0x0000, printer_parallel_read, printer_parallel_write },
|
|
{ 0, 0, NULL, NULL } /* End marker. DO NOT REMOVE */
|
|
};
|
|
|
|
#if HAVE_765_H
|
|
static FDC_PTR fdc; /* The FDC */
|
|
static specplus3_drive_t drives[ SPECPLUS3_DRIVE_B+1 ]; /* Drives A: and B: */
|
|
static FDRV_PTR drive_null; /* A null drive for drives 2 and 3 of the
|
|
FDC */
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
BYTE
|
|
specplus3_unattached_port( void )
|
|
{
|
|
return 0xff;
|
|
}
|
|
|
|
BYTE specplus3_read_screen_memory(WORD offset)
|
|
{
|
|
return RAM[machine_current->ram.current_screen][offset];
|
|
}
|
|
|
|
DWORD specplus3_contend_memory( WORD address )
|
|
{
|
|
int bank;
|
|
|
|
/* Contention occurs in pages 4 to 7. If we're not in a special
|
|
RAM configuration, the logic is the same as for the 128K machine.
|
|
If we are, just enumerate the cases */
|
|
if( machine_current->ram.special ) {
|
|
|
|
switch( machine_current->ram.specialcfg ) {
|
|
|
|
case 0: /* Pages 0, 1, 2, 3 */
|
|
return 0;
|
|
case 1: /* Pages 4, 5, 6, 7 */
|
|
return specplus3_contend_delay();
|
|
case 2: /* Pages 4, 5, 6, 3 */
|
|
case 3: /* Pages 4, 7, 6, 3 */
|
|
bank = address / 0x4000;
|
|
switch( bank ) {
|
|
case 0: case 1: case 2:
|
|
return specplus3_contend_delay();
|
|
case 3:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if( ( address >= 0x4000 && address < 0x8000 ) ||
|
|
( address >= 0xc000 && machine_current->ram.current_page >= 4 )
|
|
)
|
|
return specplus3_contend_delay();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DWORD
|
|
specplus3_contend_port( WORD port GCC_UNUSED )
|
|
{
|
|
/* Contention does not occur for the ULA.
|
|
FIXME: Unknown for other ports, so let's assume it doesn't for now */
|
|
return 0;
|
|
}
|
|
|
|
static DWORD specplus3_contend_delay( void )
|
|
{
|
|
WORD tstates_through_line;
|
|
|
|
/* No contention in the upper border */
|
|
if( tstates < machine_current->line_times[ DISPLAY_BORDER_HEIGHT ] )
|
|
return 0;
|
|
|
|
/* Or the lower border */
|
|
if( tstates >= machine_current->line_times[ DISPLAY_BORDER_HEIGHT +
|
|
DISPLAY_HEIGHT ] )
|
|
return 0;
|
|
|
|
/* Work out where we are in this line */
|
|
tstates_through_line =
|
|
( tstates + machine_current->timings.left_border ) %
|
|
machine_current->timings.tstates_per_line;
|
|
|
|
/* No contention if we're in the left border */
|
|
if( tstates_through_line < machine_current->timings.left_border - 3 )
|
|
return 0;
|
|
|
|
/* Or the right border or retrace */
|
|
if( tstates_through_line >= machine_current->timings.left_border +
|
|
machine_current->timings.horizontal_screen - 3 )
|
|
return 0;
|
|
|
|
/* We now know the ULA is reading the screen, so put in the appropriate
|
|
delay */
|
|
switch( tstates_through_line % 8 ) {
|
|
case 5: return 1; break;
|
|
case 6: return 0; break;
|
|
case 7: return 7; break;
|
|
case 0: return 6; break;
|
|
case 1: return 5; break;
|
|
case 2: return 4; break;
|
|
case 3: return 3; break;
|
|
case 4: return 2; break;
|
|
}
|
|
|
|
return 0; /* Shut gcc up */
|
|
}
|
|
|
|
int specplus3_init( fuse_machine_info *machine )
|
|
{
|
|
int error;
|
|
#ifdef HAVE_765_H
|
|
int i;
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
machine->machine = LIBSPECTRUM_MACHINE_PLUS3;
|
|
machine->id = "plus3";
|
|
|
|
machine->reset = specplus3_reset;
|
|
|
|
error = machine_set_timings( machine ); if( error ) return error;
|
|
|
|
machine->timex = 0;
|
|
machine->ram.read_memory = specplus3_readbyte;
|
|
machine->ram.read_memory_internal = specplus3_readbyte_internal;
|
|
machine->ram.read_screen = specplus3_read_screen_memory;
|
|
machine->ram.write_memory = specplus3_writebyte;
|
|
machine->ram.write_memory_internal = specplus3_writebyte_internal;
|
|
machine->ram.contend_memory = specplus3_contend_memory;
|
|
machine->ram.contend_port = specplus3_contend_port;
|
|
|
|
error = machine_allocate_roms( machine, 4 );
|
|
if( error ) return error;
|
|
machine->rom_length[0] = machine->rom_length[1] =
|
|
machine->rom_length[2] = machine->rom_length[3] = 0x4000;
|
|
|
|
machine->peripherals=specplus3_peripherals;
|
|
machine->unattached_port = specplus3_unattached_port;
|
|
|
|
machine->ay.present=1;
|
|
|
|
#ifdef HAVE_765_H
|
|
|
|
/* Register lib765 error callback */
|
|
lib765_register_error_function( specplus3_fdc_error );
|
|
|
|
/* Create the FDC */
|
|
fdc = fdc_new();
|
|
|
|
/* Populate the drives */
|
|
for( i = SPECPLUS3_DRIVE_A; i <= SPECPLUS3_DRIVE_B; i++ ) {
|
|
|
|
#ifdef HAVE_LIBDSK_H
|
|
drives[i].drive = fd_newldsk();
|
|
#else /* #ifdef HAVE_LIBDSK_H */
|
|
drives[i].drive = fd_newdsk();
|
|
#endif /* #ifdef HAVE_LIBDSK_H */
|
|
|
|
fd_settype( drives[i].drive, FD_30 ); /* FD_30 => 3" drive */
|
|
fd_setheads( drives[i].drive, 1 );
|
|
fd_setcyls( drives[i].drive, 40 );
|
|
fd_setreadonly( drives[i].drive, 0 );
|
|
drives[i].fd = -1; /* Nothing inserted */
|
|
|
|
}
|
|
|
|
/* And a null drive to use for the other two drives lib765 supports */
|
|
drive_null = fd_new();
|
|
|
|
/* And reset the FDC */
|
|
specplus3_fdc_reset();
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
machine->shutdown = specplus3_shutdown;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int specplus3_reset(void)
|
|
{
|
|
int error;
|
|
|
|
machine_current->ram.current_page=0; machine_current->ram.current_rom=0;
|
|
machine_current->ram.current_screen=5;
|
|
machine_current->ram.locked=0;
|
|
machine_current->ram.special=0; machine_current->ram.specialcfg=0;
|
|
|
|
#ifdef HAVE_765_H
|
|
specplus3_fdc_reset();
|
|
#endif
|
|
|
|
error = machine_load_rom( &ROM[0], settings_current.rom_plus3_0,
|
|
machine_current->rom_length[0] );
|
|
if( error ) return error;
|
|
error = machine_load_rom( &ROM[1], settings_current.rom_plus3_1,
|
|
machine_current->rom_length[1] );
|
|
if( error ) return error;
|
|
error = machine_load_rom( &ROM[2], settings_current.rom_plus3_2,
|
|
machine_current->rom_length[2] );
|
|
if( error ) return error;
|
|
error = machine_load_rom( &ROM[3], settings_current.rom_plus3_3,
|
|
machine_current->rom_length[3] );
|
|
if( error ) return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
specplus3_memoryport_write( WORD port GCC_UNUSED, BYTE b )
|
|
{
|
|
/* Let the parallel printer code know about the strobe bit */
|
|
printer_parallel_strobe_write( b & 0x10 );
|
|
|
|
/* Do nothing else if we've locked the RAM configuration */
|
|
if( machine_current->ram.locked ) return;
|
|
|
|
/* Store the last byte written in case we need it */
|
|
machine_current->ram.last_byte2=b;
|
|
|
|
#ifdef HAVE_765_H
|
|
/* If this was called by a machine which has a +3-style disk (ie the +3
|
|
as opposed to the +2A), set the state of both ( 3 = ( 1<<0 ) + ( 1<<1 ) )
|
|
floppy drive motors */
|
|
if( libspectrum_machine_capabilities( machine_current->machine ) &&
|
|
LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_DISK )
|
|
fdc_set_motor( fdc, ( b & 0x08 ) ? 3 : 0 );
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
if( b & 0x01) { /* Check whether we want a special RAM configuration */
|
|
|
|
/* If so, select it */
|
|
machine_current->ram.special=1;
|
|
machine_current->ram.specialcfg= ( b & 0x06 ) >> 1;
|
|
|
|
} else {
|
|
|
|
/* If not, we're selecting the high bit of the current ROM */
|
|
machine_current->ram.special=0;
|
|
machine_current->ram.current_rom=(machine_current->ram.current_rom & 0x01) |
|
|
( (b & 0x04) >> 1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if HAVE_765_H
|
|
|
|
static void
|
|
specplus3_fdc_reset( void )
|
|
{
|
|
/* Reset the FDC and set up the four drives (of which only drives 0 and
|
|
1 exist on the +3 */
|
|
fdc_reset( fdc );
|
|
fdc_setdrive( fdc, 0, drives[ SPECPLUS3_DRIVE_A ].drive );
|
|
fdc_setdrive( fdc, 1, drives[ SPECPLUS3_DRIVE_B ].drive );
|
|
fdc_setdrive( fdc, 2, drive_null );
|
|
fdc_setdrive( fdc, 3, drive_null );
|
|
}
|
|
|
|
static BYTE
|
|
specplus3_fdc_status( WORD port GCC_UNUSED )
|
|
{
|
|
return fdc_read_ctrl( fdc );
|
|
}
|
|
|
|
static BYTE
|
|
specplus3_fdc_read( WORD port GCC_UNUSED )
|
|
{
|
|
return fdc_read_data( fdc );
|
|
}
|
|
|
|
static void
|
|
specplus3_fdc_write( WORD port GCC_UNUSED, BYTE data )
|
|
{
|
|
fdc_write_data( fdc, data );
|
|
}
|
|
|
|
/* FDC UI related functions */
|
|
|
|
/* Used as lib765's `print an error message' callback */
|
|
void
|
|
specplus3_fdc_error( int debug, char *format, ... )
|
|
{
|
|
va_list ap;
|
|
|
|
/* Report only serious errors */
|
|
if( debug != 0 ) return;
|
|
|
|
va_start( ap, format );
|
|
ui_verror( UI_ERROR_ERROR, format, ap );
|
|
va_end( ap );
|
|
}
|
|
|
|
int
|
|
specplus3_disk_insert( specplus3_drive_number which, const char *filename )
|
|
{
|
|
struct stat buf;
|
|
struct flock lock;
|
|
size_t i; int error;
|
|
|
|
if( which > SPECPLUS3_DRIVE_B ) {
|
|
ui_error( UI_ERROR_ERROR, "specplus3_disk_insert: unknown drive %d\n",
|
|
which );
|
|
fuse_abort();
|
|
}
|
|
|
|
/* Eject any disk already in the drive */
|
|
if( drives[which].fd != -1 ) specplus3_disk_eject( which );
|
|
|
|
/* Open the disk file */
|
|
drives[which].fd = open( filename, O_RDWR );
|
|
if( drives[which].fd == -1 ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't open '%s': %s", filename,
|
|
strerror( errno ) );
|
|
return 1;
|
|
}
|
|
|
|
/* We now have to do two sorts of locking:
|
|
|
|
1) stop the same disk being put into more than one drive on this
|
|
copy of Fuse. Do this by looking at the st_dev (device) and
|
|
st_ino (inode) results from fstat(2) and assuming that each file
|
|
returns a persistent unique pair. This assumption isn't quite
|
|
true (I can break it with smbfs, and nmm1@cam.ac.uk has pointed
|
|
out some other cases), but it's right most of the time, and it's
|
|
POSIX compliant.
|
|
|
|
2) stop the same disk being accessed by other programs. Without
|
|
mandatory locking (ugh), can't enforce this, so just lock the
|
|
entire file with fcntl. Therefore two copies of Fuse (or anything
|
|
else using fcntl) can't access the file whilst it's in a
|
|
drive. Again, this is POSIX compliant.
|
|
*/
|
|
|
|
error = fstat( drives[which].fd, &buf );
|
|
if( error == -1 ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't fstat '%s': %s", filename,
|
|
strerror( errno ) );
|
|
close( drives[which].fd ); drives[which].fd = -1;
|
|
return 1;
|
|
}
|
|
|
|
drives[which].device = buf.st_dev;
|
|
drives[which].inode = buf.st_ino;
|
|
|
|
for( i = SPECPLUS3_DRIVE_A; i <= SPECPLUS3_DRIVE_B; i++ ) {
|
|
|
|
/* Don't compare this drive with itself */
|
|
if( i == which ) continue;
|
|
|
|
/* If there's no file in this drive, it can't clash */
|
|
if( drives[i].fd == -1 ) continue;
|
|
|
|
if( drives[i].device == drives[which].device &&
|
|
drives[i].inode == drives[which].inode ) {
|
|
ui_error( UI_ERROR_ERROR, "'%s' is already in drive %c:", filename,
|
|
(char)( 'A' + i ) );
|
|
close( drives[which].fd ); drives[which].fd = -1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Exclusively lock the entire file */
|
|
lock.l_type = F_WRLCK;
|
|
lock.l_start = 0;
|
|
lock.l_whence = SEEK_SET;
|
|
lock.l_len = 0; /* Entire file */
|
|
|
|
error = fcntl( drives[which].fd, F_SETLK, &lock );
|
|
if( error == -1 ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't lock '%s': %s", filename,
|
|
strerror( errno ) );
|
|
close( drives[which].fd ); drives[which].fd = -1;
|
|
return 1;
|
|
}
|
|
|
|
/* And now insert the disk */
|
|
#ifdef HAVE_LIBDSK_H
|
|
fdl_settype( drives[which].drive, NULL ); /* Autodetect disk format */
|
|
fdl_setfilename( drives[which].drive, filename );
|
|
#else /* #ifdef HAVE_LIBDSK_H */
|
|
fdd_setfilename( drives[which].drive, filename );
|
|
#endif /* #ifdef HAVE_LIBDSK_H */
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
specplus3_disk_eject( specplus3_drive_number which )
|
|
{
|
|
int error;
|
|
|
|
if( which > SPECPLUS3_DRIVE_B ) {
|
|
ui_error( UI_ERROR_ERROR, "specplus3_disk_eject: unknown drive %d\n",
|
|
which );
|
|
fuse_abort();
|
|
}
|
|
|
|
/* NB: the fclose() called here will cause the lock on the file to
|
|
be released */
|
|
fd_eject( drives[which].drive );
|
|
|
|
if( drives[which].fd != -1 ) {
|
|
error = close( drives[which].fd );
|
|
if( error == -1 ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't close the disk: %s\n",
|
|
strerror( errno ) );
|
|
return 1;
|
|
}
|
|
drives[which].fd = -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
static int
|
|
specplus3_shutdown( void )
|
|
{
|
|
#ifdef HAVE_765_H
|
|
fd_destroy( &drives[ SPECPLUS3_DRIVE_A ].drive );
|
|
fd_destroy( &drives[ SPECPLUS3_DRIVE_B ].drive );
|
|
fd_destroy( &drive_null );
|
|
|
|
fdc_destroy( &fdc );
|
|
#endif /* #ifdef HAVE_765_H */
|
|
|
|
return 0;
|
|
}
|