/*

sid.c - Silicon Disc

Its not clear whether setting up the low or high (or both) parts of the
sector address resets the 7 bit byte-within-sector counter, so we do both.

Its not clear whether the sector address could be read back or not,
but in this emulation we allow it.

Its also not clear whether the byte-within-sector counter carries
into the sector address, as it does in this emulation.

We mustn't let the user change the Silicon Disc content as the emulation runs
because the DPBs for the Silicon Disc type codes are set up assuming the
disks are not removable.
Changing the content would not be detected, and corruption would result.

*/

/*...sincludes:0:*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "types.h"
#include "diag.h"
#include "common.h"
#include "sid.h"

/*...vtypes\46\h:0:*/
/*...vdiag\46\h:0:*/
/*...vcommon\46\h:0:*/
/*...vsid\46\h:0:*/
/*...e*/

/*...svars:0:*/
static int sid_emu;

#define	SIDISC_SIZE (8*1024*1024)

static byte *memory[N_SIDISC] =
	{
	NULL,
	NULL,
	NULL,
	NULL
	};

static const char *fns[N_SIDISC] =
	{
	NULL,
	NULL,
	NULL,
	NULL
	};

static unsigned ptr[N_SIDISC] =
	{
	0,
	0,
	0,
	0
	};

static unsigned size[N_SIDISC] =
	{
	SIDISC_SIZE,
	SIDISC_SIZE,
	SIDISC_SIZE,
	SIDISC_SIZE
	};
/*...e*/

/*...ssid_out:0:*/
void sid_out(word port, byte value)
	{
	int drive = (port-0x50)/4;
	switch ( port & 0x03 )
		{
		case 0x00:
			/* Set low byte of sector address,
			   reset byte within sector */
			ptr[drive] = ( (ptr[drive]&~0x00007fff) | (unsigned)value <<  7 );
			diag_message(DIAG_SIDISC_ADDRESS, "set low byte of drive %d sector address, now 0x%06x", drive, ptr[drive]>>7);
			break;
		case 0x01:
			/* Set high byte of sector address,
			   reset byte within sector */
			ptr[drive] = ( (ptr[drive]&~0x007f807f) | (unsigned)value << 15 );
			diag_message(DIAG_SIDISC_ADDRESS, "set high byte of drive %d sector address, now 0x%06x", drive, ptr[drive]>>7);
			break;
		case 0x02:
			if ( sid_emu & SIDEMU_HUGE )
				{
				/* Set high byte of sector address,
				   reset byte within sector */
				ptr[drive] = ( (ptr[drive]&~0x7f80007f) | (unsigned)value << 23 );
				diag_message(DIAG_SIDISC_ADDRESS, "set huge byte of drive %d sector address, now 0x%06x", drive, ptr[drive]>>7);
				}
			break;
		case 0x03:
			if ( ptr[drive] >= size[drive] )
				ptr[drive] = 0;
			if ( memory[drive] == NULL )
				{
				memory[drive] = emalloc(size[drive]);
				memset(memory[drive], 0, size[drive]);
				}
			memory[drive][ptr[drive]] = value;
			diag_message(DIAG_SIDISC_DATA, "write to drive %d byte 0x%02x", drive, value);
			++ptr[drive];
			break;
		}
	}
/*...e*/
/*...ssid_in:0:*/
byte sid_in(word port)
	{
	int drive = (port-0x50)/4;
	switch ( port & 0x03 )
		{
		case 0x00:
			diag_message(DIAG_SIDISC_ADDRESS, "read low byte of drive %d sector address, return 0x%02x", drive, (ptr[drive]>>7)&0xff);
			return (byte) (ptr[drive]>> 7);
		case 0x01:
			diag_message(DIAG_SIDISC_ADDRESS, "read high byte of drive %d sector address, return 0x%02x", drive, (ptr[drive]>>15)&0xff);
			return (byte) (ptr[drive]>>15);
		case 0x02:
			if ( sid_emu & SIDEMU_HUGE )
				{
				diag_message(DIAG_SIDISC_ADDRESS, "read huge byte of drive %d sector address, return 0x%02x", drive, (ptr[drive]>>23)&0xff);
				return (byte) (ptr[drive]>>23);
				}
			break;
		case 0x03:
			{
			byte b;
			if ( ptr[drive] >= size[drive] )
				ptr[drive] = 0;
			if ( memory[drive] != NULL )
				b = memory[drive][ptr[drive]];
			else
				b = 0;
			++ptr[drive];
			diag_message(DIAG_SIDISC_DATA, "read from drive %d byte 0x%02x", drive, b);
			return b;
			}
		}
	return (byte) 0xff; /* Keep compiler happy */
	}
/*...e*/

/*...ssid_set_file:0:*/
void sid_set_file(int drive, const char *fn)
	{
	fns[drive] = fn;
	}
/*...e*/

/*...ssid_init:0:*/
void sid_init(int emu)
	{
	int i;
	sid_emu = emu;
	for ( i = 0; i < N_SIDISC; i++ )
		if ( fns[i] != NULL )
			{
			FILE *fp;
			if ( (fp = fopen(fns[i], "rb+")) != NULL )
				{
				size_t file_size;
				size_t n;
				fseek(fp, 0L, SEEK_END);
				file_size = (unsigned) ftell(fp);
				fseek(fp, 0L, SEEK_SET);
				size[i] = (int)( ( (sid_emu & SIDEMU_HUGE) != 0 && file_size > SIDISC_SIZE ) ? file_size : SIDISC_SIZE );
				memory[i] = emalloc(size[i]);
				n = fread(memory[i], 1, size[i], fp);
				fclose(fp);
				diag_message(DIAG_SIDISC_FILE, "loaded %d bytes of Silicon Disc %d content from %s", (int) n, i, fns[i]);
				}
			else
				diag_message(DIAG_SIDISC_FILE, "can't load Silicon Disc %d content from %s", i, fns[i]);
			}
	}
/*...e*/
/*...ssid_term:0:*/
void sid_term(void)
	{
	if ( (sid_emu & SIDEMU_NO_SAVE) == 0 )
		{
		int i;
		for ( i = 0; i < N_SIDISC; i++ )
			if ( fns[i] != NULL && memory[i] != NULL )
				{
				FILE *fp;
				if ( (fp = fopen(fns[i], "wb+")) != NULL )
					{
					size_t n = fwrite(memory[i], 1, size[i], fp);
					fclose(fp);
					diag_message(DIAG_SIDISC_FILE, "saved %d bytes of Silicon Disc %d content to %s", (int) n, i, fns[i]);
					}
				else
					diag_message(DIAG_SIDISC_FILE, "can't save Silicon Disc %d content to %s", i, fns[i]);
				}
		}
	}
/*...e*/
