From 031a8f51f856c8ca83710c371cecaacc7fa61a53 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 21 Dec 2014 13:51:45 -0800 Subject: [PATCH 001/615] Prevent some segfaults when shutting down on Linux --- src/audio/soundcore-openal.c | 4 ++++ src/interface.c | 4 +++- src/video/glvideo.c | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/audio/soundcore-openal.c b/src/audio/soundcore-openal.c index 90e51af9..1883465e 100644 --- a/src/audio/soundcore-openal.c +++ b/src/audio/soundcore-openal.c @@ -748,6 +748,10 @@ static long OpenALCreateSoundBuffer(ALBufferParamsStruct *params, ALSoundBufferS static long OpenALDestroySoundBuffer(ALSoundBufferStruct **soundbuf_struct) { + if (!*soundbuf_struct) { + // already dealloced + return 0; + } LOG("OpenALDestroySoundBuffer ..."); ALVoice *voice = (*soundbuf_struct)->_this; ALint source = voice->source; diff --git a/src/interface.c b/src/interface.c index 5a046664..8a170991 100644 --- a/src/interface.c +++ b/src/interface.c @@ -1275,8 +1275,10 @@ void c_interface_parameters() #ifdef __linux__ LOG("Back to Linux, w00t!\n"); #endif + SpkrDestroy(); + MB_Destroy(); + video_shutdown(); - //audio_shutdown(); TODO : fixme ... exit( 0 ); } } diff --git a/src/video/glvideo.c b/src/video/glvideo.c index f8ff35a3..a0b3e183 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -685,13 +685,14 @@ static void gldriver_reshape(int w, int h) { } #if USE_GLUT +static int glutWindow = -1; static void gldriver_init_glut(GLuint fbo) { glutInit(&argc, argv); glutInitDisplayMode(/*GLUT_DOUBLE|*/GLUT_RGBA|GLUT_DEPTH); glutInitWindowSize(windowWidth, windowHeight); //glutInitContextVersion(4, 0); -- Is this needed? glutInitContextProfile(GLUT_CORE_PROFILE); - glutCreateWindow(PACKAGE_NAME); + glutWindow = glutCreateWindow(PACKAGE_NAME); GL_ERRQUIT("GLUT initialization"); if (glewInit()) { @@ -748,6 +749,9 @@ void video_driver_reshape(int w, int h) { } void video_driver_shutdown(void) { +#if USE_GLUT + glutDestroyWindow(glutWindow); +#endif gldriver_shutdown(); } From 34d043330b4b8d0f099b3fba5a7e0e6f8ad091ee Mon Sep 17 00:00:00 2001 From: asc Date: Sun, 21 Dec 2014 13:59:04 -0800 Subject: [PATCH 002/615] Silence some valgrind complaints --- src/interface.c | 3 ++- src/video/glvideo.c | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/interface.c b/src/interface.c index 8a170991..26c98114 100644 --- a/src/interface.c +++ b/src/interface.c @@ -1353,7 +1353,7 @@ void c_interface_credits() #define SCROLL_LENGTH 58 #define SCROLL_WIDTH (INTERFACE_SCREEN_X+1-(SCROLL_AREA_X*2)) - char credits[SCROLL_LENGTH][SCROLL_WIDTH]= + char credits[SCROLL_LENGTH+1][SCROLL_WIDTH]= //1. 5. 10. 15. 20. 25. 30. 35. 40. 45. 50. 55. 60. 65. 70. 75. 80.", { " ", " An Apple //e Emulator for POSIX Systems! ", @@ -1412,6 +1412,7 @@ void c_interface_credits() " > ROM images used by the emulator are copyright Apple Computer ", " ", " > Disk images are copyright by various third parties ", + " ", " " }; video_setpage( 0 ); diff --git a/src/video/glvideo.c b/src/video/glvideo.c index a0b3e183..1036c8c6 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -236,15 +236,14 @@ static GLuint _create_VAO(demoModel *model) { } static void _destroy_VAO(GLuint vaoName) { - GLuint index; - GLuint bufName; // Bind the VAO so we can get data from it glBindVertexArray(vaoName); // For every possible attribute set in the VAO - for (index = 0; index < 16; index++) { + for (GLuint index = 0; index < 16; index++) { // Get the VBO set for that attibute + GLuint bufName = 0; glGetVertexAttribiv(index , GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, (GLint*)&bufName); // If there was a VBO set... @@ -255,12 +254,15 @@ static void _destroy_VAO(GLuint vaoName) { } // Get any element array VBO set in the VAO - glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint*)&bufName); + { + GLuint bufName = 0; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint*)&bufName); - // If there was a element array VBO set in the VAO - if(bufName) { - //...delete the VBO - glDeleteBuffers(1, &bufName); + // If there was a element array VBO set in the VAO + if (bufName) { + //...delete the VBO + glDeleteBuffers(1, &bufName); + } } // Finally, delete the VAO From d339ec1e23fa4196788d8e24a788b3802096d825 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 21 Dec 2014 14:10:48 -0800 Subject: [PATCH 003/615] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 983b5330..f965c581 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Semi-Ordered TODO * Proper VBL timing * ProDOS-order Disk Images * ARM assembly/ABI variant (in prep for mobile) +* OpenGL shaders/tricks for style (various screen artifacts) and functionality (Disk ][ status overlays, etc) * iOS port * Android NDK port * Emulator save/restore and image compatibility with AppleWin From e01e43784264d5228a6041a0233a3f04f87c0f04 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 28 Dec 2014 13:07:13 -0800 Subject: [PATCH 004/615] Massive disk refactoring sourced from AppleWin - Attempts to be byte-stream compatible with how AppleWin handles DOS-order, ProDOS-order, and NIB images - Adds support for ProDOS-order images - First cut at VBL support: (//e FlappyBird boots now!) --- src/disk.c | 1394 +++++++++++++++++++++++-------------------- src/disk.h | 52 +- src/glue.h | 4 + src/interface.c | 5 + src/meta/debug.l | 2 +- src/meta/debugger.c | 12 +- src/misc.c | 3 +- src/test/testvm.c | 18 +- src/vm.c | 18 +- 9 files changed, 804 insertions(+), 704 deletions(-) diff --git a/src/disk.c b/src/disk.c index 631730f6..da58e5b8 100644 --- a/src/disk.c +++ b/src/disk.c @@ -14,13 +14,12 @@ * */ +/* + * (De-)nibblizing routines sourced from AppleWin project. + */ + #include "common.h" -#define PHASE_BYTES 3328 - -#define NIB_SIZE 232960 -#define DSK_SIZE 143360 - #if DISK_TRACING static FILE *test_read_fp = NULL; static FILE *test_write_fp = NULL; @@ -29,13 +28,13 @@ static FILE *test_write_fp = NULL; extern uint8_t slot6_rom[256]; extern bool slot6_rom_loaded; -struct drive disk6; +drive_t disk6; -static int skew_table_6[16] = /* Sector skew table */ -{ 0,7,14,6,13,5,12,4,11,3,10,2,9,1,8,15 }; +static int stepper_phases = 0; // state bits for stepper magnet phases 0-3 +static int skew_table_6_po[16] = { 0x00,0x08,0x01,0x09,0x02,0x0A,0x03,0x0B, 0x04,0x0C,0x05,0x0D,0x06,0x0E,0x07,0x0F }; // ProDOS order +static int skew_table_6_do[16] = { 0x00,0x07,0x0E,0x06,0x0D,0x05,0x0C,0x04, 0x0B,0x03,0x0A,0x02,0x09,0x01,0x08,0x0F }; // DOS order -static int translate_table_6[256] = /* Translation table */ -{ +static int translate_table_6[0x40] = { 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, @@ -44,6 +43,7 @@ static int translate_table_6[256] = /* Translation table */ 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + /* 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, @@ -68,6 +68,7 @@ static int translate_table_6[256] = /* Translation table */ 0x80, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x80, 0x80, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x80, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f + */ }; static void cut_gz(char *name) { @@ -96,22 +97,733 @@ static bool is_nib(const char * const name) { return false; } -/* ------------------------------------------------------------------------- - c_init_6() -- initialize disk system - ------------------------------------------------------------------------- */ +static bool is_po(const char * const name) { + size_t len = strlen( name ); + if (is_gz(name)) { + len -= 3; + } + if (!strncmp(name + len - 3, ".po", 3)) { + return true; + } + return false; +} + +#define NUM_SIXBIT_NIBS 342 + +static void nibblize_sector(const uint8_t *src, uint8_t *out) { + + uint8_t work_buf[NUM_SIXBIT_NIBS]; + uint8_t *nib = work_buf; + + // Convert 256 8-bit bytes into 342 6-bit bytes + { + unsigned int counter = 0; + uint8_t offset = 0xAC; + while (offset != 0x02) { + uint8_t value = (( (*(src+offset)) & 0x01) << 1) | (( (*(src+offset)) & 0x02) >> 1); + offset -= 0x56; + value = (value << 2) | (( (*(src+offset)) & 0x01) << 1) | (( (*(src+offset)) & 0x02) >> 1); + offset -= 0x56; + value = (value << 2) | (( (*(src+offset)) & 0x01) << 1) | (( (*(src+offset)) & 0x02) >> 1); + offset -= 0x53; + *(nib++) = value << 2; + ++counter; + } + *(nib-2) &= 0x3F; + *(nib-1) &= 0x3F; + ++counter; + unsigned int loop = 0; + while (loop < 0x100) { + *(nib++) = *(src+(loop++)); + ++counter; + } + assert((counter == NUM_SIXBIT_NIBS+1) && "nibblizing counter overflow"); + } + + src = work_buf; + uint8_t work_buf2[NUM_SIXBIT_NIBS+1]; + nib = work_buf2; + + // XOR the entire data block with itself offset by one byte, creating a final checksum byte + { + uint8_t savedval = 0; + int loop = NUM_SIXBIT_NIBS; + while (loop--) { + *(nib++) = savedval ^ *src; + savedval = *(src++); + } + *nib = savedval; + } + + src = work_buf2; + nib = NULL; + + // Convert the 6-bit bytes into disk bytes using a lookup table. A valid disk byte is a byte that has the high bit + // set, at least two adjacent bits set (excluding the high bit), and at most one pair of consecutive zero bits. + { + int loop = NUM_SIXBIT_NIBS+1; + while (loop--) { + *(out++) = translate_table_6[(*(src++)) >> 2]; + } + } +} + +static void denibblize_sector(const uint8_t *src, uint8_t *out) { + + uint8_t work_buf[NUM_SIXBIT_NIBS+1]; + uint8_t *dsk = work_buf; + + static uint8_t denib[0x80] = { 0 }; + static bool tablegenerated = false; + if (!tablegenerated) { + unsigned int loop = 0; + while (loop < 0x40) { + denib[translate_table_6[loop]-0x80] = loop << 2; + loop++; + } + tablegenerated = true; + } + + // Convert disk bytes into 6-bit bytes + { + unsigned int loop = NUM_SIXBIT_NIBS+1; + while (loop--) { + *(dsk++) = denib[*(src++) & 0x7F]; + } + } + +#if DISK_TRACING + if (test_write_fp) { + fprintf(test_write_fp, "SIXBITNIBS:\n"); + for (unsigned int i=0; i= 0xAC) { + *(out+offset) = (*(sectorbase+offset) & 0xFC) | (((*lowbitsptr) & 0x80) >> 7) | (((*lowbitsptr) & 0x40) >> 5); + ++counter; + } + offset -= 0x56; + + *(out+offset) = (*(sectorbase+offset) & 0xFC) | (((*lowbitsptr) & 0x20) >> 5) | (((*lowbitsptr) & 0x10) >> 3); + ++counter; + offset -= 0x56; + + *(out+offset) = (*(sectorbase+offset) & 0xFC) | (((*lowbitsptr) & 0x08) >> 3) | (((*lowbitsptr) & 0x04) >> 1); + ++counter; + offset -= 0x53; + + ++lowbitsptr; + } + assert(counter == 256 && "invalid bytes count"); + } + +#ifdef DISK_TRACING + if (test_write_fp) { + fprintf(test_write_fp, "SECTOR:\n"); + for (unsigned int i = 0; i < 256; i++) { + fprintf(test_write_fp, "%02X", out[i]); + } + fprintf(test_write_fp, "%s", "\n"); + } +#endif +} + +#define CODE44A(a) ((((a)>> 1) & 0x55) | 0xAA) +#define CODE44B(b) (((b) & 0x55) | 0xAA) + +static unsigned long nibblize_track(uint8_t *buf, int drive) { + + uint8_t *output = disk6.disk[drive].track_image; + + // Write track beginning gap containing 48 self-sync bytes + for (unsigned int i=0; i<48; i++) { + *(output++) = 0xFF; + } + + unsigned int sector = 0; + while (sector < 16) { + // --- Address field + + // Prologue + *(output)++ = 0xD5; + *(output)++ = 0xAA; + *(output)++ = 0x96; + + // Volume (4-and-4 encoded) + *(output)++ = CODE44A(DSK_VOLUME); + *(output)++ = CODE44B(DSK_VOLUME); + + // Track (4-and-4 encoded) + int track = (disk6.disk[drive].phase>>1); + *(output)++ = CODE44A(track); + *(output)++ = CODE44B(track); + + // Sector (4-and-4 encoded) + *(output)++ = CODE44A(sector); + *(output)++ = CODE44B(sector); + + // Checksum (4-and-4 encoded) + uint8_t checksum = (DSK_VOLUME ^ track ^ sector); + *(output)++ = CODE44A(checksum); + *(output)++ = CODE44B(checksum); + + // Epilogue + *(output)++ = 0xDE; + *(output)++ = 0xAA; + *(output)++ = 0xEB; + + // Gap of 6 self-sync bytes + for (unsigned int i=0; i<6; i++) { + *(output++) = 0xFF; + } + + // --- Data field + + // Prologue + *(output)++ = 0xD5; + *(output)++ = 0xAA; + *(output)++ = 0xAD; + + // 343 6-bit bytes of nibblized data + 6-bit checksum + int sec_off = 256 * disk6.disk[drive].skew_table[ sector ]; + nibblize_sector(buf+sec_off, output); + output += NUM_SIXBIT_NIBS+1; + + // Epilogue + *(output)++ = 0xDE; + *(output)++ = 0xAA; + *(output)++ = 0xEB; + + // Sector gap of 27 self-sync bytes + for (unsigned int i=0; i<27; i++) { + *(output++) = 0xFF; + } + + ++sector; + } + + return output-disk6.disk[drive].track_image; +} + +static void denibblize_track(int drive, uint8_t *dst) { + // Searches through the track data for each sector and decodes it +#warning TODO FIXME inefficient -- refactor after moar tests =P + + uint8_t *trackimage = disk6.disk[drive].track_image; +#if DISK_TRACING + if (test_write_fp) { + fprintf(test_write_fp, "DSK OUT:\n"); + } +#endif + + unsigned int offset = 0; + unsigned int partsleft = (NUM_SECTORS<<1) +1; + int sector = -1; + while (partsleft) { + --partsleft; + uint8_t prologue[3] = {0,0,0}; // D5AA.. + + // Find prologue + unsigned int count = 0; + unsigned int loop = NIB_TRACK_SIZE; + while (loop && (count < 3)) { + --loop; + if (count || (*(trackimage+offset) == 0xD5)) { + prologue[count] = *(trackimage+offset); + ++count; + } + ++offset; + if (offset >= disk6.disk[drive].nib_count) { + offset = 0; + } + } + + if ((count == 3) && (prologue[1] = 0xAA)) { +#define DATA_BYTES_LEN (256+128) + unsigned int secloop = 0; + unsigned int tmpoff = offset; + uint8_t work_buf[DATA_BYTES_LEN] = { 0 }; + uint8_t *nib = work_buf; + while (secloop < DATA_BYTES_LEN) { + *(nib+secloop) = *(trackimage+tmpoff); + if (tmpoff >= disk6.disk[drive].nib_count) { + tmpoff = 0; + } + ++secloop; + ++tmpoff; + } + + if (prologue[2] == 0x96) { // header + sector = ((*(nib+4) & 0x55) << 1) | (*(nib+5) & 0x55); + } else if (prologue[2] == 0xAD) { // data + int sec_off = 256 * disk6.disk[drive].skew_table[ sector ]; + denibblize_sector(nib, dst+sec_off); + sector = -1; + } + } + } +} + +static bool load_track_data(void) { + + if (disk6.disk[disk6.drive].nibblized) { + // .nib image + int track_pos = NIB_TRACK_SIZE * (disk6.disk[disk6.drive].phase >> 1); + fseek(disk6.disk[disk6.drive].fp, track_pos, SEEK_SET); + + if (fread(disk6.disk[disk6.drive].track_image, 1, NIB_TRACK_SIZE, disk6.disk[disk6.drive].fp) != NIB_TRACK_SIZE) { + ERRLOG("nib image corrupted ..."); + return false; + } + disk6.disk[disk6.drive].nib_count = NIB_TRACK_SIZE; +// } else if ( THIS IS NI2 FORMAT ... ) { +// Do stuff-n-things + } else { + // .dsk, .do, .po images + int track_pos = DSK_TRACK_SIZE * (disk6.disk[disk6.drive].phase >> 1); + fseek(disk6.disk[disk6.drive].fp, track_pos, SEEK_SET); + + uint8_t buf[DSK_TRACK_SIZE]; + if (fread(buf, 1, DSK_TRACK_SIZE, disk6.disk[disk6.drive].fp) != DSK_TRACK_SIZE) { + ERRLOG("dsk image corrupted ..."); + return false; + } + + disk6.disk[disk6.drive].nib_count = nibblize_track(buf, disk6.drive); + if ( (disk6.disk[disk6.drive].nib_count != NIB_TRACK_SIZE) && (disk6.disk[disk6.drive].nib_count != NI2_TRACK_SIZE) ) { + ERRLOG("Invalid dsk image creation..."); + return false; + } + } + + disk6.disk[disk6.drive].track_valid = true; + disk6.disk[disk6.drive].run_byte = 0; + return true; +} + +static bool save_track_data(void) { + + if (disk6.disk[disk6.drive].nibblized) { + // .nib image + int track_pos = NIB_TRACK_SIZE * (disk6.disk[disk6.drive].phase >> 1); + fseek(disk6.disk[disk6.drive].fp, track_pos, SEEK_SET); + if (fwrite(disk6.disk[disk6.drive].track_image, 1, NIB_TRACK_SIZE, disk6.disk[disk6.drive].fp) != NIB_TRACK_SIZE) { + ERRLOG("could not write nib data ..."); + return false; + } + } else { + // .dsk, .do, .po images + uint8_t buf[DSK_TRACK_SIZE]; + denibblize_track(disk6.drive, buf); + int track_pos = DSK_TRACK_SIZE * (disk6.disk[disk6.drive].phase >> 1); + fseek(disk6.disk[disk6.drive].fp, track_pos, SEEK_SET); + LOG("writing data ..."); + if (fwrite(buf, 1, DSK_TRACK_SIZE, disk6.disk[disk6.drive].fp) != DSK_TRACK_SIZE) { + ERRLOG("could not write dsk data ..."); + return false; + } + fflush(disk6.disk[disk6.drive].fp); + } + + disk6.disk[disk6.drive].track_dirty = false; + return true; +} + +static uint8_t disk_io_pseudo_random(uint8_t hibit) { + // AppleWin algorithm ... unsure of the source or whether it fixes issues with particular images + static const uint8_t ret[16] = { + 0x00, 0x2D, 0x2D, 0x30, 0x30, 0x32, 0x32, 0x34, + 0x35, 0x39, 0x43, 0x43, 0x43, 0x60, 0x7F, 0x7F + }; + if (hibit) { + hibit = 0x80; + } + uint8_t r = c_read_random(/*ignored*/0x0); + if (r <= 0xAA) { + return 0x20 | hibit; + } else { + return ret[r&0x0f] | hibit; + } +} + +// ---------------------------------------------------------------------------- +// Emulator hooks + +GLUE_C_READ(disk_read_write_byte) +{ + uint8_t value = 0xFF; + do { + if (disk6.disk[disk6.drive].fp == NULL) { + value = 0x00; + break; + } + + if (!disk6.disk[disk6.drive].track_valid) { + if (!load_track_data()) { + ERRLOG("OOPS, problem loading track data"); + break; + } + } + + if (disk6.ddrw) { + if (disk6.disk[disk6.drive].is_protected) { + value = 0x00; + break; /* Do not write if diskette is write protected */ + } + + if (disk6.disk_byte < 0x96) { + ERRLOG("OOPS, attempting to write a non-nibblized byte"); + value = 0x00; + break; + } + +#if DISK_TRACING + if (test_write_fp) { + fprintf(test_write_fp, "%02X", disk6.disk_byte); + } +#endif + disk6.disk[disk6.drive].track_image[disk6.disk[disk6.drive].run_byte] = disk6.disk_byte; + disk6.disk[disk6.drive].track_dirty = true; + } else { + + if (disk6.motor) { // ??? + if (disk6.motor > 99) { + ERRLOG("OOPS, potential disk motor issue"); + value = 0x00; + break; + } else { + disk6.motor++; + } + } + + value = disk6.disk[disk6.drive].track_image[disk6.disk[disk6.drive].run_byte]; +#if DISK_TRACING + if (test_read_fp) { + fprintf(test_read_fp, "%02X", value); + } +#endif + } + } while (0); + + ++disk6.disk[disk6.drive].run_byte; + if (disk6.disk[disk6.drive].run_byte >= disk6.disk[disk6.drive].nib_count) { + disk6.disk[disk6.drive].run_byte = 0; + } +#if DISK_TRACING + if ((disk6.disk[disk6.drive].run_byte % NIB_SEC_SIZE) == 0) { + if (disk6.ddrw) { + if (test_write_fp) { + fprintf(test_write_fp, "%s", "\n"); + } + } else { + if (test_read_fp) { + fprintf(test_read_fp, "%s", "\n"); + } + } + } +#endif + + return value; +} + +GLUE_C_READ(disk_read_phase) +{ + ea &= 0xFF; + int phase = (ea>>1)&3; + int phase_bit = (1 << phase); + + char *phase_str = NULL; + if (ea & 1) { + phase_str = "on "; + stepper_phases |= phase_bit; + } else { + phase_str = "off"; + stepper_phases &= ~phase_bit; + } +#if DISK_TRACING + if (test_read_fp) { + fprintf(test_read_fp, "\ntrack %02X phases %X phase %d %s address $C0E%X\n", disk6.disk[disk6.drive].phase, stepper_phases, phase, phase_str, ea&0xF); + } + if (test_write_fp) { + fprintf(test_write_fp, "\ntrack %02X phases %X phase %d %s address $C0E%X\n", disk6.disk[disk6.drive].phase, stepper_phases, phase, phase_str, ea&0xF); + } +#endif + + // Disk ][ Magnet causes stepping effect: + // - move only when the magnet opposite the cog is off + // - move in the direction of an adjacent magnet if one is on + // - do not move if both adjacent magnets are on + int direction = 0; + int cur_phase = disk6.disk[disk6.drive].phase; + if (stepper_phases & (1 << ((cur_phase + 1) & 3))) { + direction += 1; + } + if (stepper_phases & (1 << ((cur_phase + 3) & 3))) { + direction -= 1; + } + + if (direction) { + if (disk6.disk[disk6.drive].track_dirty) { + save_track_data(); + } + disk6.disk[disk6.drive].track_valid = false; + disk6.disk[disk6.drive].phase += direction; + + if (disk6.disk[disk6.drive].phase<0) { + disk6.disk[disk6.drive].phase=0; + } + + if (disk6.disk[disk6.drive].phase>69) { // AppleWin uses 79 (extra tracks/phases)? + disk6.disk[disk6.drive].phase=69; + } + +#if DISK_TRACING + if (test_read_fp) { + fprintf(test_read_fp, "NEW TRK:%d\n", (disk6.disk[disk6.drive].phase>>1)); + } + if (test_write_fp) { + fprintf(test_write_fp, "NEW TRK:%d\n", (disk6.disk[disk6.drive].phase>>1)); + } +#endif + } + + return ea == 0xE0 ? 0xFF : disk_io_pseudo_random(1); +} + +GLUE_C_READ(disk_read_motor_off) +{ + disk6.motor = 1; + return disk_io_pseudo_random(1); +} + +GLUE_C_READ(disk_read_motor_on) +{ + disk6.motor = 0; + return disk_io_pseudo_random(1); +} + +GLUE_C_READ(disk_read_select_a) +{ + disk6.drive = 0; + return 0; +} + +GLUE_C_READ(disk_read_select_b) +{ + disk6.drive = 1; + return 0; +} + +GLUE_C_READ(disk_read_latch) +{ + return disk6.drive; +} + +GLUE_C_READ(disk_read_prepare_in) +{ + disk6.ddrw = 0; + return disk_io_pseudo_random(disk6.disk[disk6.drive].is_protected); +} + +GLUE_C_READ(disk_read_prepare_out) +{ + disk6.ddrw = 1; + return disk_io_pseudo_random(1); +} + +GLUE_C_WRITE(disk_write_latch) +{ + disk6.disk_byte = b; +} + +// ---------------------------------------------------------------------------- + +void disk_io_initialize(unsigned int slot) { + FILE *f; + int i; + char temp[PATH_MAX]; + + assert(slot == 6); + + /* load Disk II rom */ + if (!slot6_rom_loaded) { + snprintf(temp, PATH_MAX, "%s/slot6.rom", system_path); + if ((f = fopen( temp, "r" )) == NULL) { + printf("Cannot find file '%s'.\n",temp); + exit( 0 ); + } + + if (fread(slot6_rom, 0x100, 1, f) != 0x100) { + // error +#warning FIXME TODO ... slot6 rom is read elsewhere + } + + fclose(f); + slot6_rom_loaded = true; + } + + memcpy(apple_ii_64k[0] + 0xC600, slot6_rom, 0x100); + + // disk softswitches + // 0xC0Xi : X = slot 0x6 + 0x8 == 0xE + cpu65_vmem_r[0xC0E0] = cpu65_vmem_r[0xC0E2] = cpu65_vmem_r[0xC0E4] = cpu65_vmem_r[0xC0E6] = disk_read_phase; + cpu65_vmem_r[0xC0E1] = cpu65_vmem_r[0xC0E3] = cpu65_vmem_r[0xC0E5] = cpu65_vmem_r[0xC0E7] = disk_read_phase; + + cpu65_vmem_r[0xC0E8] = disk_read_motor_off; + cpu65_vmem_r[0xC0E9] = disk_read_motor_on; + cpu65_vmem_r[0xC0EA] = disk_read_select_a; + cpu65_vmem_r[0xC0EB] = disk_read_select_b; + cpu65_vmem_r[0xC0EC] = disk_read_write_byte; + cpu65_vmem_r[0xC0ED] = disk_read_latch; + cpu65_vmem_r[0xC0EE] = disk_read_prepare_in; + cpu65_vmem_r[0xC0EF] = disk_read_prepare_out; + + for (i = 0xC0E0; i < 0xC0F0; i++) { + cpu65_vmem_w[i] = cpu65_vmem_r[i]; + } + + cpu65_vmem_w[0xC0ED] = disk_write_latch; +} void c_init_6(void) { - disk6.disk[0].phase = disk6.disk[1].phase = 42; - disk6.disk[0].phase_change = disk6.disk[1].phase_change = 0; - + disk6.disk[0].phase = disk6.disk[1].phase = 0; + disk6.disk[0].track_valid = disk6.disk[1].track_valid = 0; disk6.motor = 1; /* motor on */ disk6.drive = 0; /* first drive active */ disk6.ddrw = 0; - disk6.volume = 254; -#if 0 /* BUGS!: */ - file_name_6[0][1024] = '\0'; - file_name_6[1][1024] = '\0'; -#endif +} + +const char *c_eject_6(int drive) { + + const char *err = NULL; + + // foo.dsk -> foo.dsk.gz + err = def(disk6.disk[drive].file_name, is_nib(disk6.disk[drive].file_name) ? NIB_SIZE : DSK_SIZE); + if (err) { + ERRLOG("OOPS: An error occurred when attempting to compress a disk image : %s", err); + } else { + unlink(disk6.disk[drive].file_name); + } + + disk6.disk[drive].nibblized = 0; + sprintf(disk6.disk[drive].file_name, "%s", ""); + if (disk6.disk[drive].fp) { + fclose(disk6.disk[drive].fp); + disk6.disk[drive].fp = NULL; + disk6.disk[drive].nib_count = 0; + } + + return err; +} + +const char *c_new_diskette_6(int drive, const char * const raw_file_name, int force) { + struct stat buf; + + /* uncompress the gziped disk */ + char *file_name = strdup(raw_file_name); + if (is_gz(file_name)) { + int rawcount = 0; + const char *err = inf(file_name, &rawcount); // foo.dsk.gz -> foo.dsk + if (!err) { + int expected = is_nib(file_name) ? NIB_SIZE : DSK_SIZE; + if (rawcount != expected) { + err = "disk image is not expected size!"; + } + } + if (err) { + ERRLOG("OOPS: An error occurred when attempting to inflate/load a disk image : %s", err); + free(file_name); + return err; + } + if (unlink(file_name)) { // temporarily remove .gz file + ERRLOG("OOPS, cannot unlink %s", file_name); + } + + cut_gz(file_name); + } + + strncpy(disk6.disk[drive].file_name, file_name, 1023); + disk6.disk[drive].nibblized = is_nib(file_name); + disk6.disk[drive].skew_table = skew_table_6_do; + if (is_po(file_name)) { + disk6.disk[drive].skew_table = skew_table_6_po; + } + free(file_name); + file_name = NULL; + + if (disk6.disk[drive].fp) { + fclose(disk6.disk[drive].fp); + disk6.disk[drive].fp = NULL; + } + + if (stat(disk6.disk[drive].file_name, &buf) < 0) { + disk6.disk[drive].fp = NULL; + c_eject_6(drive); + return "disk unreadable 1"; + } else { + if (!force) { + disk6.disk[drive].fp = fopen(disk6.disk[drive].file_name, "r+"); + disk6.disk[drive].is_protected = false; + } + + if ((disk6.disk[drive].fp == NULL) || (force)) { + disk6.disk[drive].fp = fopen(disk6.disk[drive].file_name, "r"); + disk6.disk[drive].is_protected = true; /* disk is write protected! */ + } + + if (disk6.disk[drive].fp == NULL) { + /* Failed to open file. */ + c_eject_6(drive); + return "disk unreadable 2"; + } + + /* seek to current head position. */ + fseek(disk6.disk[drive].fp, PHASE_BYTES * disk6.disk[drive].phase, SEEK_SET); + } + + disk6.disk[drive].sector = 0; + disk6.disk[drive].track_valid = false; + disk6.disk[drive].nib_count = 0; + disk6.disk[drive].run_byte = 0; + stepper_phases = 0; + + return NULL; } #if DISK_TRACING @@ -146,643 +858,3 @@ void c_toggle_disk_trace_6(const char *read_file, const char *write_file) { } #endif -/* ------------------------------------------------------------------------- - c_eject_6() - ejects/gzips image file - ------------------------------------------------------------------------- */ -const char *c_eject_6(int drive) { - - const char *err = NULL; - - if (disk6.disk[drive].compressed) { - // foo.dsk -> foo.dsk.gz - err = def(disk6.disk[drive].file_name, is_nib(disk6.disk[drive].file_name) ? NIB_SIZE : DSK_SIZE); - if (err) { - ERRLOG("OOPS: An error occurred when attempting to compress a disk image : %s", err); - } else { - unlink(disk6.disk[drive].file_name); - } - } - - disk6.disk[drive].compressed = 0; - disk6.disk[drive].nibblized = 0; - sprintf(disk6.disk[drive].file_name, "%s", ""); - if (disk6.disk[drive].fp) { - fclose(disk6.disk[drive].fp); - disk6.disk[drive].fp = NULL; - } - - return err; -} - -/* ------------------------------------------------------------------------- - c_new_diskette_6() - inserts a new disk image into the appropriate drive. - return 0 on success - ------------------------------------------------------------------------- */ -const char *c_new_diskette_6(int drive, const char * const raw_file_name, int force) { - struct stat buf; - - /* uncompress the gziped disk */ - char *file_name = strdup(raw_file_name); - if (is_gz(file_name)) { - int rawcount = 0; - const char *err = inf(file_name, &rawcount); // foo.dsk.gz -> foo.dsk - if (!err) { - int expected = is_nib(file_name) ? NIB_SIZE : DSK_SIZE; - if (rawcount != expected) { - err = "disk image is not expected size!"; - } - } - if (err) { - ERRLOG("OOPS: An error occurred when attempting to inflate/load a disk image : %s", err); - free(file_name); - return err; - } - if (unlink(file_name)) { // temporarily remove .gz file - ERRLOG("OOPS, cannot unlink %s", file_name); - } - - cut_gz(file_name); - } - - strncpy(disk6.disk[drive].file_name, file_name, 1023); - disk6.disk[drive].compressed = true;// always using gz - disk6.disk[drive].nibblized = is_nib(file_name); - free(file_name); - file_name = NULL; - - if (disk6.disk[drive].fp) { - fclose(disk6.disk[drive].fp); - disk6.disk[drive].fp = NULL; - } - - if (stat(disk6.disk[drive].file_name, &buf) < 0) { - disk6.disk[drive].fp = NULL; - c_eject_6(drive); - return "disk unreadable 1"; - } else { - disk6.disk[drive].file_size = buf.st_size; - - if (!force) { - disk6.disk[drive].fp = fopen(disk6.disk[drive].file_name, "r+"); - disk6.disk[drive].is_protected = false; - } - - if ((disk6.disk[drive].fp == NULL) || (force)) { - disk6.disk[drive].fp = fopen(disk6.disk[drive].file_name, "r"); - disk6.disk[drive].is_protected = true; /* disk is write protected! */ - } - - if (disk6.disk[drive].fp == NULL) { - /* Failed to open file. */ - c_eject_6(drive); - return "disk unreadable 2"; - } - - /* seek to current head position. */ - fseek(disk6.disk[drive].fp, PHASE_BYTES * disk6.disk[drive].phase, SEEK_SET); - } - - disk6.disk[drive].sector = 0; - disk6.disk[drive].run_byte = 0; - - return NULL; -} - -/* ------------------------------------------------------------------------- - c_read_nibblized_6_6() - reads a standard .nib file of length 232960 bytes. - there are 70 phases positioned every 3328 bytes. - ------------------------------------------------------------------------- */ -unsigned char c_read_nibblized_6_6(void) { - static unsigned char ch; - - if (disk6.disk[disk6.drive].phase_change) { - fseek(disk6.disk[disk6.drive].fp, PHASE_BYTES * disk6.disk[disk6.drive].phase, SEEK_SET); - disk6.disk[disk6.drive].phase_change = false; - } - - disk6.disk_byte = fgetc(disk6.disk[disk6.drive].fp); - ch = disk6.disk_byte; - /* track revolves... */ - if (ftell(disk6.disk[disk6.drive].fp) == (PHASE_BYTES * (disk6.disk[disk6.drive].phase + 2))) { - fseek(disk6.disk[disk6.drive].fp, -2 * PHASE_BYTES, SEEK_CUR); - } - -#if DISK_TRACING - if (test_read_fp) { - fprintf(test_read_fp, "%02X", ch); - fflush(test_read_fp); - } -#endif - - return ch; -} - -/* ------------------------------------------------------------------------- - c_read_normal_6() - ------------------------------------------------------------------------- */ -unsigned char c_read_normal_6(void) { - int position; - int old_value; - - unsigned char value = 0; - - /* The run byte tells what's to do */ - switch (disk6.disk[disk6.drive].run_byte) { - case 0: case 1: case 2: case 3: case 4: case 5: - case 20: case 21: case 22: case 23: case 24: - /* Sync */ - value = 0xFF; - break; - - case 6: case 25: - /* Prologue (first byte) */ - value = 0xD5; - break; - - case 7: case 26: - /* Prologue (second byte) */ - value = 0xAA; - break; - - case 8: - /* Prologue (third byte) */ - value = 0x96; - break; - - case 9: - /* Volume (encoded) */ - value = (disk6.volume >> 1) | 0xAA; - disk6.checksum = disk6.volume; - break; - - case 10: - /* Volume (encoded) */ - value = disk6.volume | 0xAA; - break; - - case 11: - /* Track number (encoded) */ - disk6.checksum ^= (disk6.disk[disk6.drive].phase >> 1); - value = (disk6.disk[disk6.drive].phase >> 2) | 0xAA; - break; - - case 12: - /* Track number (encoded) */ - value = (disk6.disk[disk6.drive].phase >> 1) | 0xAA; - break; - - case 13: - /* Sector number (encoded) */ - disk6.checksum ^= disk6.disk[disk6.drive].sector; - value = (disk6.disk[disk6.drive].sector >> 1) | 0xAA; - break; - - case 14: - /* Sector number (encoded) */ - value = disk6.disk[disk6.drive].sector | 0xAA; - break; - - case 15: - /* Checksum */ - value = (disk6.checksum >> 1) | 0xAA; - break; - - case 16: - /* Checksum */ - value = disk6.checksum | 0xAA; - break; - - case 17: case 371: - /* Epilogue (first byte) */ - value = 0xDE; - break; - - case 18: case 372: - /* Epilogue (second byte) */ - value = 0xAA; - break; - - case 19: case 373: - /* Epilogue (third byte) */ - value = 0xEB; - break; - - case 27: - /* Data header */ - disk6.exor_value = 0; - - /* Set file position variable */ - disk6.disk[disk6.drive].file_pos = 256 * 16 * (disk6.disk[disk6.drive].phase >> 1) + - 256 * skew_table_6[ disk6.disk[disk6.drive].sector ]; -#if DISK_TRACING - if (test_read_fp) { - fprintf(test_read_fp, "\nTRK:%02X SECTOR:%02X SKEW:%02X (FILE_POS:%d)\n", disk6.disk[disk6.drive].phase, disk6.disk[disk6.drive].sector, skew_table_6[ disk6.disk[disk6.drive].sector ], disk6.disk[disk6.drive].file_pos); - } -#endif - - /* File large enough? */ - if (disk6.disk[disk6.drive].file_pos + 255 > disk6.disk[disk6.drive].file_size) { - return 0xFF; - } - - /* Set position */ - fseek( disk6.disk[disk6.drive].fp, disk6.disk[disk6.drive].file_pos, SEEK_SET ); - - /* Read sector */ - if (fread( disk6.disk_data, 1, 256, disk6.disk[disk6.drive].fp ) != 256) { -#warning FIXME TODO ... does this really happen in the wild? we may need/want a crash reporter for this... - ERRQUIT("OOPS read sector failed ..."); - } - - disk6.disk_data[ 256 ] = disk6.disk_data[ 257 ] = 0; - value = 0xAD; - break; - - case 370: - /* Checksum */ - value = translate_table_6[disk6.exor_value & 0x3F]; - - /* Increment sector number (and wrap if necessary) */ - disk6.disk[disk6.drive].sector++; - if (disk6.disk[disk6.drive].sector == 16) { - disk6.disk[disk6.drive].sector = 0; - } - break; - - default: - position = disk6.disk[disk6.drive].run_byte - 28; - if (position >= 0x56) { - position -= 0x56; - old_value = disk6.disk_data[ position ]; - old_value = old_value >> 2; - disk6.exor_value ^= old_value; - value = translate_table_6[disk6.exor_value & 0x3F]; - disk6.exor_value = old_value; - } else { - old_value = 0; - old_value |= (disk6.disk_data[position] & 0x1) << 1; - old_value |= (disk6.disk_data[position] & 0x2) >> 1; - old_value |= (disk6.disk_data[position+0x56] & 0x1) << 3; - old_value |= (disk6.disk_data[position+0x56] & 0x2) << 1; - old_value |= (disk6.disk_data[position+0xAC] & 0x1) << 5; - old_value |= (disk6.disk_data[position+0xAC] & 0x2) << 3; - disk6.exor_value ^= old_value; - value = translate_table_6[disk6.exor_value & 0x3F]; - disk6.exor_value = old_value; - } - break; - } /* End switch */ - - /* Continue by increasing run byte value */ - disk6.disk[disk6.drive].run_byte++; - if (disk6.disk[disk6.drive].run_byte > 373) { - disk6.disk[disk6.drive].run_byte = 0; - } - -#if DISK_TRACING - if (test_read_fp) { - fprintf(test_read_fp, "%02X", value); - fflush(test_read_fp); - } -#endif - - disk6.disk_byte = value; - return value; -} - - -/* ------------------------------------------------------------------------- - c_write_nibblized_6_6() - writes a standard .nib file of length 232960 bytes. - there are 70 phases positioned every 3328 bytes. - ------------------------------------------------------------------------- */ -void c_write_nibblized_6_6(void) { - if (disk6.disk[disk6.drive].phase_change) { - fseek(disk6.disk[disk6.drive].fp, PHASE_BYTES * disk6.disk[disk6.drive].phase, SEEK_SET); - disk6.disk[disk6.drive].phase_change = false; - } - - fputc(disk6.disk_byte, disk6.disk[disk6.drive].fp); - -#if DISK_TRACING - if (test_write_fp) { - fprintf(test_read_fp, "%02X", disk6.disk_byte); - fflush(test_write_fp); - } -#endif - - /* track revolves... */ - if (ftell(disk6.disk[disk6.drive].fp) == (PHASE_BYTES * (disk6.disk[disk6.drive].phase + 2))) { - fseek(disk6.disk[disk6.drive].fp, -2 * PHASE_BYTES, SEEK_CUR); - } -} - -/* ------------------------------------------------------------------------- - c_write_normal_6() disk6.disk_byte contains the value - ------------------------------------------------------------------------- */ -void c_write_normal_6(void) { - static int wr_sec_6 = 0x0; // static bugfix from pre-git-history ... - //static int wr_trk_6 = 0x0; is this needed? ... this truly appeared to be deadc0de in apple2-emul-v006 - - int position; - int old_value; - - if (disk6.disk_byte == 0xD5) { - disk6.disk[disk6.drive].run_byte = 6; /* Initialize run byte value */ - } - - /* The run byte tells what's to do */ - - switch (disk6.disk[disk6.drive].run_byte) { - case 0: case 1: case 2: case 3: case 4: case 5: - case 20: case 21: case 22: case 23: case 24: - /* Sync */ - break; - - case 6: case 25: - /* Prologue (first byte) */ - if (disk6.disk_byte == 0xFF) { - disk6.disk[disk6.drive].run_byte--; - } - break; - - case 7: case 26: - /* Prologue (second byte) */ - break; - - case 8: - /* Prologue (third byte) */ - if (disk6.disk_byte == 0xAD) { - disk6.exor_value = 0, disk6.disk[disk6.drive].run_byte = 27; - } - break; - - case 9: case 10: - /* Volume */ - break; - - case 11: case 12: - /* Track -- FIXME TODO ... should this do anything? */ - break; - - case 13: - /* Sector number (encode it) */ - wr_sec_6 = disk6.disk_byte << 1; - wr_sec_6 &= 0xFF; - wr_sec_6 |= 0x55; - break; - - case 14: - /* Sector number (encode it) */ - wr_sec_6 &= disk6.disk_byte; - disk6.disk[disk6.drive].sector = wr_sec_6; - break; - - case 15: - /* Checksum */ - break; - - case 16: - /* Checksum */ - break; - - case 17: case 371: - /* Epilogue (first byte) */ - break; - - case 18: case 372: - /* Epilogue (second byte) */ - break; - - case 19: case 373: - /* Epilogue (third byte) */ - break; - - case 27: - disk6.exor_value = 0; - break; - - case 370: - /* Set file position variable */ - disk6.disk[disk6.drive].file_pos = 256 * 16 * (disk6.disk[disk6.drive].phase >> 1) + - 256 * skew_table_6[ disk6.disk[disk6.drive].sector ]; - - /* Is the file large enough? */ - if (disk6.disk[disk6.drive].file_pos + 255 > disk6.disk[disk6.drive].file_size) { - return; - } - - - /* Set position */ - fseek( disk6.disk[disk6.drive].fp, disk6.disk[disk6.drive].file_pos, SEEK_SET ); - - /* Write sector */ - fwrite(disk6.disk_data, 1, 256, disk6.disk[disk6.drive].fp); -#if DISK_TRACING - if (test_write_fp) { - fprintf(test_write_fp, "\nTRK:%02X SECTOR:%02X SKEW:%02X (FILE_POS:%d)\n", disk6.disk[disk6.drive].phase, disk6.disk[disk6.drive].sector, skew_table_6[ disk6.disk[disk6.drive].sector ], disk6.disk[disk6.drive].file_pos); - for (unsigned int i=0; i<256; i++) { - fprintf(test_write_fp, "%02X", disk6.disk_data[i]); - } - fflush(test_write_fp); - } -#endif - fflush( disk6.disk[disk6.drive].fp ); - /* Increment sector number (and wrap if necessary) */ - disk6.disk[disk6.drive].sector++; - if (disk6.disk[disk6.drive].sector == 16) { - disk6.disk[disk6.drive].sector = 0; - } - break; - - default: - position = disk6.disk[disk6.drive].run_byte - 28; - disk6.disk_byte = translate_table_6[ disk6.disk_byte ]; - if (position >= 0x56) { - position -= 0x56; - disk6.disk_byte ^= disk6.exor_value; - old_value = disk6.disk_byte; - disk6.disk_data[position] |= (disk6.disk_byte << 2) & 0xFC; - disk6.exor_value = old_value; - } else { - disk6.disk_byte ^= disk6.exor_value; - old_value = disk6.disk_byte; - disk6.disk_data[position] = (disk6.disk_byte & 0x01) << 1; - disk6.disk_data[position] |= (disk6.disk_byte & 0x02) >> 1; - disk6.disk_data[position + 0x56] = (disk6.disk_byte & 0x04) >> 1; - disk6.disk_data[position + 0x56] |= (disk6.disk_byte & 0x08) >> 3; - disk6.disk_data[position + 0xAC] = (disk6.disk_byte & 0x10) >> 3; - disk6.disk_data[position + 0xAC] |= (disk6.disk_byte & 0x20) >> 5; - disk6.exor_value = old_value; - } - break; - } /* End switch */ - - disk6.disk[disk6.drive].run_byte++; - if (disk6.disk[disk6.drive].run_byte > 373) { - disk6.disk[disk6.drive].run_byte = 0; - } -} - -GLUE_C_READ(disk_read_byte) -{ - if (disk6.ddrw) { - if (disk6.disk[disk6.drive].fp == NULL) { - return 0; /* Return if there is no disk in drive */ - } - - if (disk6.disk[disk6.drive].is_protected) { - return 0; /* Do not write if diskette is write protected */ - } - - if (disk6.disk_byte < 0x96) { - return 0; /* Only byte values at least 0x96 are allowed */ - } - - (disk6.disk[disk6.drive].nibblized) ? c_write_nibblized_6_6() : c_write_normal_6(); - return 0; /* ??? */ - } else { - if (disk6.disk[disk6.drive].fp == NULL) { - return 0xFF; /* Return FF if there is no disk in drive */ - } - - if (disk6.motor) { /* Motor turned on? */ - if (disk6.motor > 99) { - return 0; - } else { - disk6.motor++; - } - } - - /* handle nibblized_6 or regular disks */ - return (disk6.disk[disk6.drive].nibblized) ? c_read_nibblized_6_6() : c_read_normal_6(); - } -} - -GLUE_C_READ(disk_read_phase) -{ - /* - * Comment from xapple2+ by Phillip Stephens: - * Turn motor phases 0 to 3 on. Turning on the previous phase + 1 - * increments the track position, turning on the previous phase - 1 - * decrements the track position. In this scheme phase 0 and 3 are - * considered to be adjacent. The previous phase number can be - * computed as the track number % 4. - */ - - switch (((ea >> 1) - disk6.disk[disk6.drive].phase) & 3) { - case 1: - disk6.disk[disk6.drive].phase++; - break; - case 3: - disk6.disk[disk6.drive].phase--; - break; - } - - if (disk6.disk[disk6.drive].phase<0) { - disk6.disk[disk6.drive].phase=0; - } - - if (disk6.disk[disk6.drive].phase>69) { - disk6.disk[disk6.drive].phase=69; - } - - disk6.disk[disk6.drive].phase_change = true; - - return 0; -} - - -GLUE_C_READ(disk_read_motor_off) -{ - disk6.motor = 1; - return disk6.drive; -} - -GLUE_C_READ(disk_read_motor_on) -{ - disk6.motor = 0; - return disk6.drive; -} - -GLUE_C_READ(disk_read_select_a) -{ - return disk6.drive = 0; -} - -GLUE_C_READ(disk_read_select_b) -{ - return disk6.drive = 1; -} - - -GLUE_C_READ(disk_read_latch) -{ - return disk6.drive; -} - -GLUE_C_READ(disk_read_prepare_in) -{ - disk6.ddrw = 0; - return disk6.disk[disk6.drive].is_protected ? 0x80 : 0x00; -} - -GLUE_C_READ(disk_read_prepare_out) -{ - disk6.ddrw = 1; - return disk6.drive; -} - -GLUE_C_WRITE(disk_write_latch) -{ - disk6.disk_byte = b; -} - -void disk_io_initialize(unsigned int slot) { - FILE *f; - int i; - char temp[PATH_MAX]; - - assert(slot == 6); - - /* load Disk II rom */ - if (!slot6_rom_loaded) { - snprintf(temp, PATH_MAX, "%s/slot6.rom", system_path); - if ((f = fopen( temp, "r" )) == NULL) { - printf("Cannot find file '%s'.\n",temp); - exit( 0 ); - } - - if (fread(slot6_rom, 0x100, 1, f) != 0x100) { - // error -#warning FIXME TODO ... slot6 rom is read elsewhere - } - - fclose(f); - slot6_rom_loaded = true; - } - - memcpy(apple_ii_64k[0] + 0xC600, slot6_rom, 0x100); - - // disk softswitches - // 0xC0Xi : X = slot 0x6 + 0x8 == 0xE - cpu65_vmem_r[0xC0E0] = cpu65_vmem_r[0xC0E2] = cpu65_vmem_r[0xC0E4] = cpu65_vmem_r[0xC0E6] = ram_nop; - - cpu65_vmem_r[0xC0E1] = cpu65_vmem_r[0xC0E3] = cpu65_vmem_r[0xC0E5] = cpu65_vmem_r[0xC0E7] = disk_read_phase; - - cpu65_vmem_r[0xC0E8] = disk_read_motor_off; - cpu65_vmem_r[0xC0E9] = disk_read_motor_on; - cpu65_vmem_r[0xC0EA] = disk_read_select_a; - cpu65_vmem_r[0xC0EB] = disk_read_select_b; - cpu65_vmem_r[0xC0EC] = disk_read_byte; - cpu65_vmem_r[0xC0ED] = disk_read_latch; - cpu65_vmem_r[0xC0EE] = disk_read_prepare_in; - cpu65_vmem_r[0xC0EF] = disk_read_prepare_out; - - for (i = 0xC0E0; i < 0xC0F0; i++) { - cpu65_vmem_w[i] = cpu65_vmem_r[i]; - } - - cpu65_vmem_w[0xC0ED] = disk_write_latch; -} - diff --git a/src/disk.h b/src/disk.h index 6a4ad08e..a256a7d6 100644 --- a/src/disk.h +++ b/src/disk.h @@ -19,51 +19,53 @@ #include "common.h" -struct diskette { +#define DSK_SIZE 143360 +#define NIB_SIZE 232960 +#define NI2_SIZE 223440 + +#define NUM_TRACKS 35 +#define NUM_SECTORS 16 + +#define DSK_TRACK_SIZE 0x1000 // DOS order, ProDOS order +#define NIB_TRACK_SIZE 0x1A00 // NIB format +#define NI2_TRACK_SIZE 0x18F0 // NI2 format + +#define PHASE_BYTES (NIB_TRACK_SIZE/2) +#define NIB_SEC_SIZE (NIB_TRACK_SIZE/NUM_SECTORS) + +#define DSK_VOLUME 254 + +typedef struct diskette_t { + uint8_t track_image[NIB_TRACK_SIZE]; char file_name[1024]; - bool compressed; bool nibblized; bool is_protected; - bool phase_change; + bool track_valid; + bool track_dirty; + int *skew_table; int sector; - long file_size; + long nib_count; int phase; int run_byte; FILE *fp; - int file_pos; -}; +} diskette_t; -struct drive { +typedef struct drive_t { int motor; int drive; int ddrw; int disk_byte; - int volume; - int checksum; int exor_value; - unsigned char disk_data[258]; - struct diskette disk[2]; -}; + diskette_t disk[2]; +} drive_t; -extern struct drive disk6; +extern drive_t disk6; void c_init_6(void); const char *c_new_diskette_6(int drive, const char * const file_name, int force); const char *c_eject_6(int drive); void disk_io_initialize(unsigned int slot); -void disk_read_nop(void), -disk_read_phase(void), -disk_read_motor_off(void), -disk_read_motor_on(void), -disk_read_select_a(void), -disk_read_select_b(void), -disk_read_byte(void), -disk_read_latch(void), -disk_write_latch(void), -disk_read_prepare_in(void), -disk_read_prepare_out(void); - #if DISK_TRACING void c_toggle_disk_trace_6(const char *read_file, const char *write_file); void c_begin_disk_trace_6(const char *read_file, const char *write_file); diff --git a/src/glue.h b/src/glue.h index d4c30963..fe947f6e 100644 --- a/src/glue.h +++ b/src/glue.h @@ -24,6 +24,7 @@ #if VM_TRACING #define GLUE_C_WRITE(func) \ + extern void func(void); \ void c__##func(uint16_t ea, uint8_t b); \ void c_##func(uint16_t ea, uint8_t b) { \ c__##func(ea, b); \ @@ -36,6 +37,7 @@ void c__##func(uint16_t ea, uint8_t b) #define GLUE_C_READ(func) \ + extern void func(void); \ uint8_t c__##func(uint16_t ea); \ uint8_t c_##func(uint16_t ea) { \ uint8_t b = c__##func(ea); \ @@ -51,9 +53,11 @@ #else #define GLUE_C_WRITE(func) \ + extern void func(void); \ void c_##func(uint16_t ea, uint8_t b) #define GLUE_C_READ(func) \ + extern void func(void); \ uint8_t c_##func(uint16_t ea) #endif diff --git a/src/interface.c b/src/interface.c index 26c98114..0f46cbe5 100644 --- a/src/interface.c +++ b/src/interface.c @@ -306,6 +306,11 @@ static int disk_select(const struct dirent *e) return 1; } + if (!strncmp(p + len - 3, ".po", 3)) + { + return 1; + } + return 0; } diff --git a/src/meta/debug.l b/src/meta/debug.l index c3c2e46e..720fe013 100644 --- a/src/meta/debug.l +++ b/src/meta/debug.l @@ -868,7 +868,7 @@ ADDRS [0-9a-fA-F]+ c_toggle_disk_trace_6(buf, NULL); free(buf); #else - LOG("CPU tracing not enabled..."); + LOG("Disk tracing not enabled..."); #endif } diff --git a/src/meta/debugger.c b/src/meta/debugger.c index 2404c8c1..20d940e4 100644 --- a/src/meta/debugger.c +++ b/src/meta/debugger.c @@ -1009,8 +1009,8 @@ void show_disk_info() { sprintf(second_buf[i++], "byte = %02X", disk6.disk_byte); if (!disk6.disk[disk6.drive].nibblized) { - sprintf(second_buf[i++], "volume = %d", disk6.volume); - sprintf(second_buf[i++], "checksum = %d", disk6.checksum); + //sprintf(second_buf[i++], "volume = %d", disk6.volume); + //sprintf(second_buf[i++], "checksum = %d", disk6.checksum); } sprintf(second_buf[i++], "-------------------------------------"); @@ -1038,16 +1038,16 @@ void show_disk_info() { } memset(second_buf[++i], ' ', BUF_X); - *(second_buf[i] + sprintf(second_buf[i], "%s %d bytes", (disk6.disk[0].nibblized) ? ".nib" : ".dsk", (int)disk6.disk[0].file_size)) = ' '; - sprintf(second_buf[i++]+off, "%s %d bytes", (disk6.disk[1].nibblized) ? ".nib" : ".dsk", (int)disk6.disk[1].file_size); + *(second_buf[i] + sprintf(second_buf[i], "%s", (disk6.disk[0].nibblized) ? ".nib" : ".dsk")) = ' '; + sprintf(second_buf[i++]+off, "%s", (disk6.disk[1].nibblized) ? ".nib" : ".dsk"); memset(second_buf[i], ' ', BUF_X); *(second_buf[i] + sprintf(second_buf[i], "write %s", (disk6.disk[0].is_protected) ? "protected" : "enabled")) = ' '; sprintf(second_buf[i++]+off, "write %s", (disk6.disk[1].is_protected) ? "protected" : "enabled"); memset(second_buf[i], ' ', BUF_X); - *(second_buf[i] + sprintf(second_buf[i], "phase %d %s", disk6.disk[0].phase, (disk6.disk[0].phase_change) ? "(new)" : "")) = ' '; - sprintf(second_buf[i++]+off, "phase %d %s", disk6.disk[1].phase, (disk6.disk[1].phase_change) ? "(new)" : ""); + *(second_buf[i] + sprintf(second_buf[i], "phase %d", disk6.disk[0].phase)) = ' '; + sprintf(second_buf[i++]+off, "phase %d", disk6.disk[1].phase); memset(second_buf[i], ' ', BUF_X); if (!disk6.disk[0].nibblized) diff --git a/src/misc.c b/src/misc.c index ef0722aa..c25174de 100644 --- a/src/misc.c +++ b/src/misc.c @@ -272,8 +272,7 @@ void c_initialize_tables() { cpu65_vmem_r[0xC015] = iie_check_cxrom; /* RDVBLBAR switch */ - cpu65_vmem_r[0xC019] = - iie_check_vbl; + cpu65_vmem_r[0xC019] = iie_check_vbl; /* random number generator */ for (i = 0xC020; i < 0xC030; i++) diff --git a/src/test/testvm.c b/src/test/testvm.c index 29d62fd0..07f8b99a 100644 --- a/src/test/testvm.c +++ b/src/test/testvm.c @@ -60,8 +60,8 @@ static void sha1_to_str(const uint8_t * const md, char *buf) { // ---------------------------------------------------------------------------- // VM TESTS ... -#define EXPECTED_DISK_TRACE_FILE_SIZE 128794 -#define EXPECTED_DISK_TRACE_SHA "43960E8F2A588D1C59DDBC1F2C9F6CCFE0725CE0" +#define EXPECTED_DISK_TRACE_FILE_SIZE 141350 +#define EXPECTED_DISK_TRACE_SHA "471EB3D01917B1C6EF9F13C5C7BC1ACE4E74C851" TEST test_boot_disk_bytes() { char *homedir = getenv("HOME"); char *disk = NULL; @@ -99,10 +99,9 @@ TEST test_boot_disk_bytes() { PASS(); } -// This test is fairly abusive ... it creates an ~90MB file in $HOME +// This test is majorly abusive ... it creates an ~800MB file in $HOME // ... but if it's correct, you're fairly assured the cpu/vm is working =) -#define EXPECTED_CPU_TRACE_FILE_SIZE 89130253 -#define EXPECTED_CPU_TRACE_SHA "0E9D690959A26A34DC9456B141CB91A2BDF3798E" +#define EXPECTED_CPU_TRACE_FILE_SIZE 809430487 TEST test_boot_disk_cputrace() { char *homedir = getenv("HOME"); char *output = NULL; @@ -134,8 +133,11 @@ TEST test_boot_disk_cputrace() { SHA1(buf, EXPECTED_CPU_TRACE_FILE_SIZE, md); FREE(buf); +#if 0 + // this is no longer a stable value to check due to random return values from disk VM routines sha1_to_str(md, mdstr); ASSERT(strcmp(mdstr, EXPECTED_CPU_TRACE_SHA) == 0); +#endif } while(0); unlink(output); @@ -144,8 +146,7 @@ TEST test_boot_disk_cputrace() { PASS(); } -#define EXPECTED_VM_TRACE_FILE_SIZE 2352827 -#define EXPECTED_VM_TRACE_SHA "D137B7D312A66F4F19E5846D1BBC56C2A53A8C3F" +#define EXPECTED_VM_TRACE_FILE_SIZE 2830810 TEST test_boot_disk_vmtrace() { char *homedir = getenv("HOME"); char *disk = NULL; @@ -173,8 +174,11 @@ TEST test_boot_disk_vmtrace() { SHA1(buf, EXPECTED_VM_TRACE_FILE_SIZE, md); FREE(buf); +#if 0 + // this is no longer a stable value to check due to random return values from disk VM routines sha1_to_str(md, mdstr); ASSERT(strcmp(mdstr, EXPECTED_VM_TRACE_SHA) == 0); +#endif } while(0); unlink(disk); diff --git a/src/vm.c b/src/vm.c index 174aa841..46cac653 100644 --- a/src/vm.c +++ b/src/vm.c @@ -122,6 +122,12 @@ typedef struct vm_trace_range_t { } vm_trace_range_t; #endif +static uint16_t video_scanner_get_address(uint8_t *vbl_bar /*, current_executed_cycles*/) { + // HACK HACK HACK of course this is wrong ... + *vbl_bar = (c_read_random(0x0) < 0x40) ? 0x80 : 0x0; + return 0x000; +} + GLUE_C_READ(ram_nop) { return 0x0; @@ -886,8 +892,16 @@ GLUE_C_READ(iie_check_dhires) GLUE_C_READ(iie_check_vbl) { - // HACK FIXME TODO : enable vertical blanking timing/detection */ - return 0x0; + + uint8_t vbl_bar = 0; + video_scanner_get_address(&vbl_bar /*, current_executed_cycles*/); + uint8_t key = c_read_keyboard(0xC000); + + if (vbl_bar) { + vbl_bar = 0x80; + } + + return (key & ~0x80) | vbl_bar; } GLUE_C_READ(iie_c3rom_peripheral) From 2a944d77b9f77573bc78ffa7a9bd6f70a29ee4ca Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 28 Dec 2014 14:23:23 -0800 Subject: [PATCH 005/615] Adds //e FlappyBird ProDOS-order demo disk --- disks/flapple140.po.gz | Bin 0 -> 42081 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 disks/flapple140.po.gz diff --git a/disks/flapple140.po.gz b/disks/flapple140.po.gz new file mode 100644 index 0000000000000000000000000000000000000000..f7a75d82c98276b75121bfbbfe7471eea55ea7be GIT binary patch literal 42081 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 Date: Sun, 28 Dec 2014 14:48:53 -0800 Subject: [PATCH 006/615] Auto-eject disk if present in drive --- src/disk.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/disk.c b/src/disk.c index da58e5b8..2e4d8270 100644 --- a/src/disk.c +++ b/src/disk.c @@ -453,7 +453,6 @@ static bool save_track_data(void) { denibblize_track(disk6.drive, buf); int track_pos = DSK_TRACK_SIZE * (disk6.disk[disk6.drive].phase >> 1); fseek(disk6.disk[disk6.drive].fp, track_pos, SEEK_SET); - LOG("writing data ..."); if (fwrite(buf, 1, DSK_TRACK_SIZE, disk6.disk[disk6.drive].fp) != DSK_TRACK_SIZE) { ERRLOG("could not write dsk data ..."); return false; @@ -755,6 +754,10 @@ const char *c_eject_6(int drive) { const char *c_new_diskette_6(int drive, const char * const raw_file_name, int force) { struct stat buf; + if (disk6.disk[drive].fp) { + c_eject_6(drive); + } + /* uncompress the gziped disk */ char *file_name = strdup(raw_file_name); if (is_gz(file_name)) { From 7d3b39926c23fe71cf190f965369c3cf18dbadc4 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 28 Dec 2014 14:50:43 -0800 Subject: [PATCH 007/615] Disk testing is separate from vm testing - Breaks out disk testing into its own suite - Misc testing and build-system tweaks --- Makefile.am | 25 +++- configure.ac | 2 + src/test/testcommon.c | 11 ++ src/test/testcommon.h | 12 +- src/test/testdisk.c | 298 +++++++++++++++++++++++++++++++++++++++++ src/test/testdisplay.c | 16 --- src/test/testvm.c | 207 ---------------------------- 7 files changed, 338 insertions(+), 233 deletions(-) create mode 100644 src/test/testdisk.c diff --git a/Makefile.am b/Makefile.am index 28a5dce7..73edf4ae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -94,8 +94,8 @@ LOG_DRIVER = testcpu ## hack TODO/FIXME ... should be wrapper shell script acce A2_TEST_SOURCES = $(apple2ix_SOURCES) src/test/testcommon.c A2_TEST_CFLAGS = -DTESTING=1 -DCPU_TRACING=1 -DDISK_TRACING=1 -DVM_TRACING=1 -Isrc/test -TESTS = testcpu testdisplay testvm -check_PROGRAMS = testcpu testdisplay testvm +TESTS = testcpu testdisplay testvm testdisk +check_PROGRAMS = testcpu testdisplay testvm testdisk testcpu_SOURCES = src/test/testcpu.c $(A2_TEST_SOURCES) $(META_SRC) testcpu_CFLAGS = $(apple2ix_CFLAGS) $(A2_TEST_CFLAGS) -UAUDIO_ENABLED -UINTERFACE_CLASSIC -DHEADLESS=1 @@ -125,6 +125,16 @@ testvm_DEPENDENCIES = @TESTVM_ASM_O@ @META_O@ @VIDEO_O@ EXTRA_testvm_SOURCES = $(ASM_SRC_x86) $(VIDEO_SRC) +testdisk_SOURCES = src/test/testdisk.c $(A2_TEST_SOURCES) $(META_SRC) +testdisk_CFLAGS = $(apple2ix_CFLAGS) $(A2_TEST_CFLAGS) -UAUDIO_ENABLED -UINTERFACE_CLASSIC -DHEADLESS=0 +testdisk_CCASFLAGS = $(testdisk_CFLAGS) +testdisk_LDFLAGS = $(apple2ix_LDFLAGS) +# HACK FIXME TODO NOTE: specify testdisk_ASM_O to force it to rebuild with proper CCASFLAGS ... automake bug? +testdisk_LDADD = @TESTDISK_ASM_O@ @VIDEO_O@ +testdisk_DEPENDENCIES = @TESTDISK_ASM_O@ @META_O@ @VIDEO_O@ + +EXTRA_testdisk_SOURCES = $(ASM_SRC_x86) $(VIDEO_SRC) + ############################################################################### # Misc & Installation @@ -139,19 +149,20 @@ shaders_DATA = src/video/Basic.vsh src/video/Basic.fsh disksdir = @datadir@/@PACKAGE@/disks disks_DATA = \ disks/README disks/blank.dsk.gz disks/blank.nib.gz disks/etc.dsk.gz \ - disks/mystery.dsk.gz disks/speedtest.dsk.gz disks/speedtest.txt + disks/mystery.dsk.gz disks/speedtest.dsk.gz disks/speedtest.txt \ + disks/flapple140.po.gz disks/testdisplay1.dsk.gz disks/testvm1.dsk.gz + # Extra distribution stuff EXTRA_DIST = reconf.sh configure README.debugger PROBLEMS .apple2 \ \ - disks/README disks/blank.dsk.gz disks/blank.nib.gz disks/etc.dsk.gz \ - disks/mystery.dsk.gz disks/speedtest.dsk.gz disks/speedtest.txt \ + $(disks_DATA) \ \ - docs/apple2ix.6 \ + $(man_MANS) \ \ src/font.txt \ src/x86/genglue \ \ - src/video/Basic.vsh src/video/Basic.fsh + $(shaders_DATA) CLEANFILES = src/font.c src/rom.c src/meta/debug.c src/x86/glue.S diff --git a/configure.ac b/configure.ac index 763e3048..4249485f 100644 --- a/configure.ac +++ b/configure.ac @@ -21,6 +21,7 @@ dnl Arch checks ASM_O="src/x86/glue.o src/x86/cpu.o" TESTVM_ASM_O="src/x86/testvm-glue.o src/x86/testvm-cpu.o" +TESTDISK_ASM_O="src/x86/testdisk-glue.o src/x86/testdisk-cpu.o" arch='' case $target in x86_64-*-*) @@ -75,6 +76,7 @@ fi AC_SUBST(ASM_O) AC_SUBST(TESTVM_ASM_O) +AC_SUBST(TESTDISK_ASM_O) AC_SUBST([AM_CFLAGS]) diff --git a/src/test/testcommon.c b/src/test/testcommon.c index 311588b3..d489855d 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -17,6 +17,8 @@ #define TESTBUF_SZ 1024 +bool test_do_reboot = true; + static char input_str[TESTBUF_SZ]; // ASCII static unsigned int input_length = 0; static unsigned int input_counter = 0; @@ -165,3 +167,12 @@ int test_setup_boot_disk(const char *fileName, int readonly) { FREE(disk); return err; } + +void sha1_to_str(const uint8_t * const md, char *buf) { + int i=0; + for (int j=0; j -#define SHA_DIGEST_LENGTH CC_SHA1_DIGEST_LENGTH -#define SHA1 CC_SHA1 +# include +# define SHA_DIGEST_LENGTH CC_SHA1_DIGEST_LENGTH +# define SHA1 CC_SHA1 +#elif HAVE_OPENSSL +# include +#else +# error "these tests require OpenSSL libraries (SHA)" #endif #define TEST_FINISHED 0xff @@ -48,10 +52,12 @@ apple_ii_64k[0][WATCHPOINT_ADDR] = 0x00; \ } +extern bool test_do_reboot; void test_breakpoint(void *arg); void test_common_init(bool do_cputhread); void test_common_setup(); void test_type_input(const char *input); int test_setup_boot_disk(const char *fileName, int readonly); +void sha1_to_str(const uint8_t * const md, char *buf); #endif // whole file diff --git a/src/test/testdisk.c b/src/test/testdisk.c new file mode 100644 index 00000000..710559e3 --- /dev/null +++ b/src/test/testdisk.c @@ -0,0 +1,298 @@ +/* + * Apple // emulator for *nix + * + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +#include "testcommon.h" + +#define RESET_INPUT() test_common_setup() + +#define TESTING_DISK "testvm1.dsk.gz" +#define BLANK_DSK "blank.dsk.gz" +#define BLANK_NIB "blank.nib.gz" +#define REBOOT_TO_DOS() \ + do { \ + apple_ii_64k[0][TESTOUT_ADDR] = 0x00; \ + joy_button0 = 0xff; \ + cpu65_interrupt(ResetSig); \ + } while (0) + +#define TYPE_TRIGGER_WATCHPT() \ + test_type_input("POKE7987,255:REM TRIGGER DEBUGGER\r") + +static void testdisk_setup(void *arg) { + RESET_INPUT(); + apple_ii_64k[0][MIXSWITCH_ADDR] = 0x00; + apple_ii_64k[0][WATCHPOINT_ADDR] = 0x00; + apple_ii_64k[0][TESTOUT_ADDR] = 0x00; + joy_button0 = 0xff; // OpenApple + test_setup_boot_disk(TESTING_DISK, 1); + if (test_do_reboot) { + cpu65_interrupt(ResetSig); + } +} + +static void testdisk_teardown(void *arg) { +} + +// ---------------------------------------------------------------------------- +// Disk TESTS ... + +#define EXPECTED_DISK_TRACE_FILE_SIZE 141350 +#define EXPECTED_DISK_TRACE_SHA "471EB3D01917B1C6EF9F13C5C7BC1ACE4E74C851" +TEST test_boot_disk_bytes() { + char *homedir = getenv("HOME"); + char *disk = NULL; + asprintf(&disk, "%s/a2_read_disk_test.txt", homedir); + if (disk) { + unlink(disk); + c_begin_disk_trace_6(disk, NULL); + } + + BOOT_TO_DOS(); + + c_end_disk_trace_6(); + c_eject_6(0); + + do { + uint8_t md[SHA_DIGEST_LENGTH]; + char mdstr[(SHA_DIGEST_LENGTH*2)+1]; + + FILE *fp = fopen(disk, "r"); + char *buf = malloc(EXPECTED_DISK_TRACE_FILE_SIZE); + if (fread(buf, 1, EXPECTED_DISK_TRACE_FILE_SIZE, fp) != EXPECTED_DISK_TRACE_FILE_SIZE) { + ASSERT(false); + } + fclose(fp); fp = NULL; + SHA1(buf, EXPECTED_DISK_TRACE_FILE_SIZE, md); + FREE(buf); + + sha1_to_str(md, mdstr); + ASSERT(strcmp(mdstr, EXPECTED_DISK_TRACE_SHA) == 0); + } while(0); + + unlink(disk); + FREE(disk); + + PASS(); +} + +// This test is majorly abusive ... it creates an ~800MB file in $HOME +// ... but if it's correct, you're fairly assured the cpu/vm is working =) +#if ABUSIVE_TESTS +#define EXPECTED_CPU_TRACE_FILE_SIZE 809430487 +TEST test_boot_disk_cputrace() { + char *homedir = getenv("HOME"); + char *output = NULL; + asprintf(&output, "%s/a2_cputrace.txt", homedir); + if (output) { + unlink(output); + cpu65_trace_begin(output); + } + + BOOT_TO_DOS(); + + cpu65_trace_end(); + c_eject_6(0); + + do { + uint8_t md[SHA_DIGEST_LENGTH]; + char mdstr[(SHA_DIGEST_LENGTH*2)+1]; + + FILE *fp = fopen(output, "r"); + fseek(fp, 0, SEEK_END); + long expectedSize = ftell(fp); + ASSERT(expectedSize == EXPECTED_CPU_TRACE_FILE_SIZE); + fseek(fp, 0, SEEK_SET); + char *buf = malloc(EXPECTED_CPU_TRACE_FILE_SIZE); + if (fread(buf, 1, EXPECTED_CPU_TRACE_FILE_SIZE, fp) != EXPECTED_CPU_TRACE_FILE_SIZE) { + ASSERT(false); + } + fclose(fp); fp = NULL; + SHA1(buf, EXPECTED_CPU_TRACE_FILE_SIZE, md); + FREE(buf); + +#if 0 + // this is no longer a stable value to check due to random return values from disk VM routines + sha1_to_str(md, mdstr); + ASSERT(strcmp(mdstr, EXPECTED_CPU_TRACE_SHA) == 0); +#endif + } while(0); + + unlink(output); + FREE(output); + + PASS(); +} +#endif + +#define EXPECTED_VM_TRACE_FILE_SIZE 2830810 +TEST test_boot_disk_vmtrace() { + char *homedir = getenv("HOME"); + char *disk = NULL; + asprintf(&disk, "%s/a2_vmtrace.txt", homedir); + if (disk) { + unlink(disk); + vm_trace_begin(disk); + } + + BOOT_TO_DOS(); + + vm_trace_end(); + c_eject_6(0); + + do { + uint8_t md[SHA_DIGEST_LENGTH]; + char mdstr[(SHA_DIGEST_LENGTH*2)+1]; + + FILE *fp = fopen(disk, "r"); + char *buf = malloc(EXPECTED_VM_TRACE_FILE_SIZE); + if (fread(buf, 1, EXPECTED_VM_TRACE_FILE_SIZE, fp) != EXPECTED_VM_TRACE_FILE_SIZE) { + ASSERT(false); + } + fclose(fp); fp = NULL; + SHA1(buf, EXPECTED_VM_TRACE_FILE_SIZE, md); + FREE(buf); + +#if 0 + // this is no longer a stable value to check due to random return values from disk VM routines + sha1_to_str(md, mdstr); + ASSERT(strcmp(mdstr, EXPECTED_VM_TRACE_SHA) == 0); +#endif + } while(0); + + unlink(disk); + FREE(disk); + + PASS(); +} + +TEST test_boot_disk() { + BOOT_TO_DOS(); + PASS(); +} + +TEST test_inithello_dsk() { + + test_setup_boot_disk(BLANK_DSK, 0); + BOOT_TO_DOS(); + + ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] != TEST_FINISHED); + ASSERT(apple_ii_64k[0][TESTOUT_ADDR] == 0x00); + + test_type_input("INIT HELLO\r"); + TYPE_TRIGGER_WATCHPT(); + + c_debugger_go(); + + ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED); + ASSERT_SHA("10F15B516E4CF2FC5B1712951A6F9C3D90BF595C"); + + REBOOT_TO_DOS(); + c_eject_6(0); + + PASS(); +} + +TEST test_inithello_nib() { + + test_setup_boot_disk(BLANK_NIB, 0); + BOOT_TO_DOS(); + + ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] != TEST_FINISHED); + + test_type_input("INIT HELLO\r"); + TYPE_TRIGGER_WATCHPT(); + + c_debugger_go(); + + ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED); + ASSERT_SHA("10F15B516E4CF2FC5B1712951A6F9C3D90BF595C"); + + REBOOT_TO_DOS(); + c_eject_6(0); + + PASS(); +} + +// ---------------------------------------------------------------------------- +// Test Suite + +static int begin_video = -1; + +GREATEST_SUITE(test_suite_disk) { + GREATEST_SET_SETUP_CB(testdisk_setup, NULL); + GREATEST_SET_TEARDOWN_CB(testdisk_teardown, NULL); + + // TESTS -------------------------- + begin_video=!is_headless; + + RUN_TESTp(test_boot_disk_bytes); +#if ABUSIVE_TESTS + RUN_TESTp(test_boot_disk_cputrace); +#endif + RUN_TESTp(test_boot_disk_vmtrace); + RUN_TESTp(test_boot_disk); + + RUN_TESTp(test_inithello_dsk); + RUN_TESTp(test_inithello_nib); + + // ... + c_eject_6(0); + pthread_mutex_unlock(&interface_mutex); +} + +SUITE(test_suite_disk); +GREATEST_MAIN_DEFS(); + +static char **test_argv = NULL; +static int test_argc = 0; + +static int _test_disk(void) { + int argc = test_argc; + char **argv = test_argv; + GREATEST_MAIN_BEGIN(); + RUN_SUITE(test_suite_disk); + GREATEST_MAIN_END(); +} + +static void *test_thread(void *dummyptr) { + _test_disk(); + return NULL; +} + +void test_disk(int argc, char **argv) { + test_argc = argc; + test_argv = argv; + + c_read_random(0x0); + srandom(0); // force a known sequence + + pthread_mutex_lock(&interface_mutex); + + test_common_init(/*cputhread*/true); + + pthread_t p; + pthread_create(&p, NULL, (void *)&test_thread, (void *)NULL); + + while (begin_video < 0) { + struct timespec ts = { .tv_sec=0, .tv_nsec=33333333 }; + nanosleep(&ts, NULL); + } + if (begin_video) { + video_main_loop(); + } + pthread_join(p, NULL); +} + +#if !defined(__APPLE__) +int main(int argc, char **argv) { + test_disk(argc, argv); +} +#endif diff --git a/src/test/testdisplay.c b/src/test/testdisplay.c index 510d8a8f..7efd334d 100644 --- a/src/test/testdisplay.c +++ b/src/test/testdisplay.c @@ -11,14 +11,6 @@ #include "testcommon.h" -#ifdef HAVE_OPENSSL -#include -#elif !defined(__APPLE__) -#error "these tests require OpenSSL libraries" -#endif - -static bool test_do_reboot = true; - static void testdisplay_setup(void *arg) { test_common_setup(); apple_ii_64k[0][MIXSWITCH_ADDR] = 0x00; @@ -32,14 +24,6 @@ static void testdisplay_setup(void *arg) { static void testdisplay_teardown(void *arg) { } -static void sha1_to_str(const uint8_t * const md, char *buf) { - int i=0; - for (int j=0; j -#elif !defined(__APPLE__) -#error "these tests require OpenSSL libraries (SHA)" -#endif #define TYPE_TRIGGER_WATCHPT() \ test_type_input("POKE7987,255:REM TRIGGER DEBUGGER\r") -static bool test_do_reboot = true; - static void testvm_setup(void *arg) { RESET_INPUT(); apple_ii_64k[0][MIXSWITCH_ADDR] = 0x00; @@ -49,192 +33,9 @@ static void testvm_setup(void *arg) { static void testvm_teardown(void *arg) { } -static void sha1_to_str(const uint8_t * const md, char *buf) { - int i=0; - for (int j=0; j Date: Sun, 28 Dec 2014 15:04:15 -0800 Subject: [PATCH 008/615] disable unstable random2 test --- src/test/testvm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/testvm.c b/src/test/testvm.c index 7fddd988..3f7e883e 100644 --- a/src/test/testvm.c +++ b/src/test/testvm.c @@ -110,6 +110,8 @@ TEST test_read_random() { PASS(); } +#if 0 +#error this is an unstable test due to VBL refactoring ... TEST test_read_random2() { #ifdef __APPLE__ #warning "ignoring random test on Darwin..." @@ -158,6 +160,7 @@ TEST test_read_random2() { PASS(); } +#endif // ---------------------------------------------------------------------------- // Softswitch tests @@ -3271,7 +3274,10 @@ GREATEST_SUITE(test_suite_vm) { RUN_TESTp(test_clear_keyboard); RUN_TESTp(test_read_random); +#if 0 +#error this is an unstable test due to VBL refactoring ... RUN_TESTp(test_read_random2); +#endif RUN_TESTp(test_PAGE2_on, /*80STORE*/0, /*HIRES*/0); RUN_TESTp(test_PAGE2_on, /*80STORE*/0, /*HIRES*/1); From af706e7602e19dac1fe513bdfe557c5ab0607599 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Mon, 29 Dec 2014 19:07:43 -0800 Subject: [PATCH 009/615] rename read_random to avoid namespace collison on Mac --- src/audio/mockingboard.c | 2 +- src/disk.c | 2 +- src/misc.c | 2 +- src/misc.h | 4 ++-- src/test/testdisk.c | 2 +- src/test/testvm.c | 2 +- src/vm.c | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/audio/mockingboard.c b/src/audio/mockingboard.c index 8532fb69..ffaef0fa 100644 --- a/src/audio/mockingboard.c +++ b/src/audio/mockingboard.c @@ -1708,7 +1708,7 @@ static uint8_t MemReadFloatingBus(const unsigned long uExecutedCycles) { //return*(LPBYTE)(mem + VideoGetScannerAddress(NULL, uExecutedCycles)); // HUGE HACK FIXME TODO - return c_read_random(0x0); + return c_read_rand(0x0); } #define nCyclesLeft cpu65_cycle_count diff --git a/src/disk.c b/src/disk.c index 2e4d8270..54183768 100644 --- a/src/disk.c +++ b/src/disk.c @@ -473,7 +473,7 @@ static uint8_t disk_io_pseudo_random(uint8_t hibit) { if (hibit) { hibit = 0x80; } - uint8_t r = c_read_random(/*ignored*/0x0); + uint8_t r = c_read_rand(/*ignored*/0x0); if (r <= 0xAA) { return 0x20 | hibit; } else { diff --git a/src/misc.c b/src/misc.c index c25174de..a5927667 100644 --- a/src/misc.c +++ b/src/misc.c @@ -279,7 +279,7 @@ void c_initialize_tables() { { cpu65_vmem_r[i] = cpu65_vmem_w[i] = - read_random; + read_rand; } /* TEXT switch */ diff --git a/src/misc.h b/src/misc.h index 02a7d47b..7baf7586 100644 --- a/src/misc.h +++ b/src/misc.h @@ -118,7 +118,7 @@ void c_set_primary_char(); void c_set_altchar(); void c_initialize_font(); void c_initialize_vm(); -uint8_t c_read_random(uint16_t ea); +uint8_t c_read_rand(uint16_t ea); void reinitialize(); /* vm hooks */ @@ -134,7 +134,7 @@ void ram_nop(), write_unmapped_softswitch(), -read_random(), +read_rand(), read_unmapped_softswitch(), read_keyboard(), read_keyboard_strobe(), diff --git a/src/test/testdisk.c b/src/test/testdisk.c index 710559e3..6211cb27 100644 --- a/src/test/testdisk.c +++ b/src/test/testdisk.c @@ -271,7 +271,7 @@ void test_disk(int argc, char **argv) { test_argc = argc; test_argv = argv; - c_read_random(0x0); + c_read_rand(0x0); srandom(0); // force a known sequence pthread_mutex_lock(&interface_mutex); diff --git a/src/test/testvm.c b/src/test/testvm.c index 3f7e883e..d56b50a8 100644 --- a/src/test/testvm.c +++ b/src/test/testvm.c @@ -3470,7 +3470,7 @@ void test_vm(int argc, char **argv) { test_argc = argc; test_argv = argv; - c_read_random(0x0); + c_read_rand(0x0); srandom(0); // force a known sequence pthread_mutex_lock(&interface_mutex); diff --git a/src/vm.c b/src/vm.c index 46cac653..0b91f55b 100644 --- a/src/vm.c +++ b/src/vm.c @@ -124,7 +124,7 @@ typedef struct vm_trace_range_t { static uint16_t video_scanner_get_address(uint8_t *vbl_bar /*, current_executed_cycles*/) { // HACK HACK HACK of course this is wrong ... - *vbl_bar = (c_read_random(0x0) < 0x40) ? 0x80 : 0x0; + *vbl_bar = (c_read_rand(0x0) < 0x40) ? 0x80 : 0x0; return 0x000; } @@ -155,7 +155,7 @@ GLUE_C_READ(read_keyboard_strobe) return apple_ii_64k[0][0xC000]; } -GLUE_C_READ(read_random) +GLUE_C_READ(read_rand) { static time_t seed=0; if (!seed) { From ee3fde8c732e617d1f9adb9310b774843d8a7a1e Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Mon, 29 Dec 2014 19:08:09 -0800 Subject: [PATCH 010/615] Get disk tests working on Mac --- Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj | 237 +++++++++++++++++- .../Apple2MacTestDisk-Info.plist | 36 +++ Apple2Mac/Apple2MacTests/CPUTestAppDelegate.m | 2 + 3 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 Apple2Mac/Apple2MacTests/Apple2MacTestDisk-Info.plist diff --git a/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj b/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj index b103d725..700e781f 100644 --- a/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj +++ b/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj @@ -11,6 +11,48 @@ 4A69C1801A33D6D7001579EF /* images in Resources */ = {isa = PBXBuildFile; fileRef = 4A69C17F1A33D6D7001579EF /* images */; }; 4A69C1921A33DB90001579EF /* DDHidLib.framework in Copy Files (1 item) */ = {isa = PBXBuildFile; fileRef = 77C2796F1A1047AF000FE33F /* DDHidLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4AC7A76D19ECC3FB00BCD457 /* EmulatorWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC7A76C19ECC3FB00BCD457 /* EmulatorWindow.m */; }; + 4AD4FE941A52464F00F958EC /* cpu.S in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D9019568A570085CE5F /* cpu.S */; }; + 4AD4FE951A52464F00F958EC /* prefs.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D7719568A570085CE5F /* prefs.c */; }; + 4AD4FE961A52464F00F958EC /* display.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D6419568A570085CE5F /* display.c */; }; + 4AD4FE971A52464F00F958EC /* disk.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D6219568A570085CE5F /* disk.c */; }; + 4AD4FE981A52464F00F958EC /* sourceUtil.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0AB19D72700004344E0 /* sourceUtil.c */; }; + 4AD4FE991A52464F00F958EC /* modelUtil.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0AD19D72700004344E0 /* modelUtil.c */; }; + 4AD4FE9A1A52464F00F958EC /* debugger.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D7319568A570085CE5F /* debugger.c */; }; + 4AD4FE9B1A52464F00F958EC /* keys.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D6E19568A570085CE5F /* keys.c */; }; + 4AD4FE9C1A52464F00F958EC /* timing.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D8719568A570085CE5F /* timing.c */; }; + 4AD4FE9D1A52464F00F958EC /* testcommon.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D7C19568A570085CE5F /* testcommon.c */; }; + 4AD4FE9E1A52464F00F958EC /* misc.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D7519568A570085CE5F /* misc.c */; }; + 4AD4FE9F1A52464F00F958EC /* opcodes.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D7419568A570085CE5F /* opcodes.c */; }; + 4AD4FEA01A52464F00F958EC /* glvideo.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0C719D736EB004344E0 /* glvideo.c */; }; + 4AD4FEA11A52464F00F958EC /* rom-shim.c in Sources */ = {isa = PBXBuildFile; fileRef = 779DD826195764E200DF89E5 /* rom-shim.c */; }; + 4AD4FEA21A52464F00F958EC /* zlib-helpers.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D9519568A570085CE5F /* zlib-helpers.c */; }; + 4AD4FEA31A52464F00F958EC /* darwin-shim.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D6019568A570085CE5F /* darwin-shim.c */; }; + 4AD4FEA41A52464F00F958EC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D1B1956885A0085CE5F /* main.m */; }; + 4AD4FEA51A52464F00F958EC /* vectorUtil.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0A919D72700004344E0 /* vectorUtil.c */; }; + 4AD4FEA61A52464F00F958EC /* debug.l in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D7219568A570085CE5F /* debug.l */; }; + 4AD4FEA71A52464F00F958EC /* CPUTestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 779DD854195BDB1700DF89E5 /* CPUTestAppDelegate.m */; }; + 4AD4FEA91A52464F00F958EC /* font.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D6519568A570085CE5F /* font.c */; }; + 4AD4FEAA1A52464F00F958EC /* cpu-supp.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D5D19568A570085CE5F /* cpu-supp.c */; }; + 4AD4FEAB1A52464F00F958EC /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D8D19568A570085CE5F /* vm.c */; }; + 4AD4FEAC1A52464F00F958EC /* darwin-glue.S in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D9119568A570085CE5F /* darwin-glue.S */; }; + 4AD4FEAD1A52464F00F958EC /* matrixUtil.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0AF19D72700004344E0 /* matrixUtil.c */; }; + 4AD4FEAE1A52464F00F958EC /* joystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 773B3D6C19568A570085CE5F /* joystick.c */; }; + 4AD4FEB01A52464F00F958EC /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 779F565F19EAF6D000A6F107 /* OpenAL.framework */; }; + 4AD4FEB11A52464F00F958EC /* libz.1.1.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 773B3DCA1956903D0085CE5F /* libz.1.1.3.dylib */; }; + 4AD4FEB21A52464F00F958EC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 773B3D0F1956885A0085CE5F /* Cocoa.framework */; }; + 4AD4FEB51A52464F00F958EC /* blank.dsk.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4ADC523019E8D3F600186B36 /* blank.dsk.gz */; }; + 4AD4FEB61A52464F00F958EC /* blank.nib.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4ADC523119E8D3F600186B36 /* blank.nib.gz */; }; + 4AD4FEB71A52464F00F958EC /* testvm1.dsk.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4ADC523819E8D3F600186B36 /* testvm1.dsk.gz */; }; + 4AD4FEB81A52464F00F958EC /* README in Resources */ = {isa = PBXBuildFile; fileRef = 4ADC523419E8D3F600186B36 /* README */; }; + 4AD4FEB91A52464F00F958EC /* MainMenu-Test.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4ADC51C919E8BEB700186B36 /* MainMenu-Test.xib */; }; + 4AD4FEBA1A52464F00F958EC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 773B3D181956885A0085CE5F /* InfoPlist.strings */; }; + 4AD4FEBB1A52464F00F958EC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 773B3D271956885A0085CE5F /* Images.xcassets */; }; + 4AD4FEBC1A52464F00F958EC /* speedtest.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4ADC523619E8D3F600186B36 /* speedtest.txt */; }; + 4AD4FEBD1A52464F00F958EC /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 773B3D1E1956885A0085CE5F /* Credits.rtf */; }; + 4AD4FEBE1A52464F00F958EC /* Basic.vsh in Resources */ = {isa = PBXBuildFile; fileRef = 779F562719E4FE9E00A6F107 /* Basic.vsh */; }; + 4AD4FEBF1A52464F00F958EC /* Basic.fsh in Resources */ = {isa = PBXBuildFile; fileRef = 779F562819E4FE9E00A6F107 /* Basic.fsh */; }; + 4AD4FECB1A52468700F958EC /* testdisk.c in Sources */ = {isa = PBXBuildFile; fileRef = 4AD4FEC91A52467D00F958EC /* testdisk.c */; }; + 4AD4FED21A524BED00F958EC /* Apple2MacTestDisk-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4AD4FEC41A52464F00F958EC /* Apple2MacTestDisk-Info.plist */; }; 4ADC51C219E8BD3700186B36 /* vectorUtil.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0A919D72700004344E0 /* vectorUtil.c */; }; 4ADC51C319E8BD3A00186B36 /* sourceUtil.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0AB19D72700004344E0 /* sourceUtil.c */; }; 4ADC51C419E8BD3D00186B36 /* modelUtil.c in Sources */ = {isa = PBXBuildFile; fileRef = 77E1C0AD19D72700004344E0 /* modelUtil.c */; }; @@ -235,9 +277,12 @@ 4A69C17F1A33D6D7001579EF /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; name = images; path = ../../images; sourceTree = ""; }; 4AC7A76B19ECC3FB00BCD457 /* EmulatorWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EmulatorWindow.h; path = Classes/OSX/EmulatorWindow.h; sourceTree = ""; }; 4AC7A76C19ECC3FB00BCD457 /* EmulatorWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EmulatorWindow.m; path = Classes/OSX/EmulatorWindow.m; sourceTree = ""; }; + 4AD4FEC31A52464F00F958EC /* Apple2MacTestDisk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Apple2MacTestDisk.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AD4FEC41A52464F00F958EC /* Apple2MacTestDisk-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Apple2MacTestDisk-Info.plist"; path = "Apple2MacTests/Apple2MacTestDisk-Info.plist"; sourceTree = SOURCE_ROOT; }; + 4AD4FEC91A52467D00F958EC /* testdisk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testdisk.c; sourceTree = ""; }; 4ADC51CA19E8BEB700186B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Base.lproj/MainMenu-Test.xib"; sourceTree = ""; }; 4ADC522719E8CA4500186B36 /* Apple2MacTestVM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Apple2MacTestVM.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 4ADC522819E8CA4500186B36 /* Apple2MacTestVM-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Apple2MacTestVM-Info.plist"; path = "/Users/aaronculliney/Documents/00web/apple2/Apple2Mac/Apple2MacTests/Apple2MacTestVM-Info.plist"; sourceTree = ""; }; + 4ADC522819E8CA4500186B36 /* Apple2MacTestVM-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Apple2MacTestVM-Info.plist"; path = "Apple2MacTests/Apple2MacTestVM-Info.plist"; sourceTree = SOURCE_ROOT; }; 4ADC523019E8D3F600186B36 /* blank.dsk.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = blank.dsk.gz; sourceTree = ""; }; 4ADC523119E8D3F600186B36 /* blank.nib.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = blank.nib.gz; sourceTree = ""; }; 4ADC523219E8D3F600186B36 /* etc.dsk.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = etc.dsk.gz; sourceTree = ""; }; @@ -307,7 +352,7 @@ 773BC91819F2FD4500996893 /* EmulatorPrefsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EmulatorPrefsController.m; path = Classes/OSX/EmulatorPrefsController.m; sourceTree = ""; }; 779DD826195764E200DF89E5 /* rom-shim.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "rom-shim.c"; sourceTree = ""; }; 779DD850195BD9F900DF89E5 /* Apple2MacTestCPU.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Apple2MacTestCPU.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 779DD851195BD9F900DF89E5 /* Apple2MacTestCPU-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Apple2MacTestCPU-Info.plist"; path = "/Users/aaronculliney/Documents/00web/apple2/Apple2Mac/Apple2MacTests/Apple2MacTestCPU-Info.plist"; sourceTree = ""; }; + 779DD851195BD9F900DF89E5 /* Apple2MacTestCPU-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Apple2MacTestCPU-Info.plist"; path = "Apple2MacTests/Apple2MacTestCPU-Info.plist"; sourceTree = SOURCE_ROOT; }; 779DD854195BDB1700DF89E5 /* CPUTestAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CPUTestAppDelegate.m; sourceTree = ""; }; 779DD855195BDB1700DF89E5 /* CPUTestAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CPUTestAppDelegate.h; sourceTree = ""; }; 779F561B19D78B8200A6F107 /* renderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = renderer.h; sourceTree = ""; }; @@ -332,7 +377,7 @@ 779F565719EAF66E00A6F107 /* win-shim.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "win-shim.h"; sourceTree = ""; }; 779F565F19EAF6D000A6F107 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; }; 779F569119EB0B9100A6F107 /* Apple2MacTestDisplay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Apple2MacTestDisplay.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 779F569219EB0B9100A6F107 /* Apple2MacTestDisplay-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Apple2MacTestDisplay-Info.plist"; path = "/Users/aaronculliney/Documents/00web/apple2/Apple2Mac/Apple2MacTests/Apple2MacTestDisplay-Info.plist"; sourceTree = ""; }; + 779F569219EB0B9100A6F107 /* Apple2MacTestDisplay-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Apple2MacTestDisplay-Info.plist"; path = "Apple2MacTests/Apple2MacTestDisplay-Info.plist"; sourceTree = SOURCE_ROOT; }; 77C279601A1047AE000FE33F /* DDHidLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DDHidLib.xcodeproj; path = DDHidLib/DDHidLib.xcodeproj; sourceTree = ""; }; 77C279731A1048B4000FE33F /* EmulatorJoystickController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EmulatorJoystickController.h; path = Classes/OSX/EmulatorJoystickController.h; sourceTree = ""; }; 77C279741A1048B4000FE33F /* EmulatorJoystickController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EmulatorJoystickController.m; path = Classes/OSX/EmulatorJoystickController.m; sourceTree = ""; }; @@ -359,6 +404,16 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 4AD4FEAF1A52464F00F958EC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AD4FEB01A52464F00F958EC /* OpenAL.framework in Frameworks */, + 4AD4FEB11A52464F00F958EC /* libz.1.1.3.dylib in Frameworks */, + 4AD4FEB21A52464F00F958EC /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4ADC521919E8CA4500186B36 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -435,6 +490,7 @@ 779DD850195BD9F900DF89E5 /* Apple2MacTestCPU.app */, 4ADC522719E8CA4500186B36 /* Apple2MacTestVM.app */, 779F569119EB0B9100A6F107 /* Apple2MacTestDisplay.app */, + 4AD4FEC31A52464F00F958EC /* Apple2MacTestDisk.app */, ); name = Products; sourceTree = ""; @@ -504,6 +560,7 @@ 779DD851195BD9F900DF89E5 /* Apple2MacTestCPU-Info.plist */, 779F569219EB0B9100A6F107 /* Apple2MacTestDisplay-Info.plist */, 4ADC522819E8CA4500186B36 /* Apple2MacTestVM-Info.plist */, + 4AD4FEC41A52464F00F958EC /* Apple2MacTestDisk-Info.plist */, 773B3D361956885A0085CE5F /* InfoPlist.strings */, ); name = "Supporting Files"; @@ -600,6 +657,7 @@ 773B3D7C19568A570085CE5F /* testcommon.c */, 773B3D7D19568A570085CE5F /* testcommon.h */, 773B3D7E19568A570085CE5F /* testcpu.c */, + 4AD4FEC91A52467D00F958EC /* testdisk.c */, 773B3D7F19568A570085CE5F /* testdisplay.c */, 773B3D8619568A570085CE5F /* testvm.c */, ); @@ -693,6 +751,25 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 4AD4FE911A52464F00F958EC /* Apple2MacTestDisk */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4AD4FEC01A52464F00F958EC /* Build configuration list for PBXNativeTarget "Apple2MacTestDisk" */; + buildPhases = ( + 4AD4FE921A52464F00F958EC /* ShellScript */, + 4AD4FE931A52464F00F958EC /* Sources */, + 4AD4FEAF1A52464F00F958EC /* Frameworks */, + 4AD4FEB31A52464F00F958EC /* ShellScript */, + 4AD4FEB41A52464F00F958EC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Apple2MacTestDisk; + productName = Apple2Mac; + productReference = 4AD4FEC31A52464F00F958EC /* Apple2MacTestDisk.app */; + productType = "com.apple.product-type.application"; + }; 4ADC51FB19E8CA4500186B36 /* Apple2MacTestVM */ = { isa = PBXNativeTarget; buildConfigurationList = 4ADC522419E8CA4500186B36 /* Build configuration list for PBXNativeTarget "Apple2MacTestVM" */; @@ -801,8 +878,9 @@ targets = ( 773B3D0B195688590085CE5F /* Apple2Mac */, 779DD82C195BD9F900DF89E5 /* Apple2MacTestCPU */, - 4ADC51FB19E8CA4500186B36 /* Apple2MacTestVM */, + 4AD4FE911A52464F00F958EC /* Apple2MacTestDisk */, 779F566119EB0B9100A6F107 /* Apple2MacTestDisplay */, + 4ADC51FB19E8CA4500186B36 /* Apple2MacTestVM */, ); }; /* End PBXProject section */ @@ -839,6 +917,25 @@ /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ + 4AD4FEB41A52464F00F958EC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AD4FEB51A52464F00F958EC /* blank.dsk.gz in Resources */, + 4AD4FEB61A52464F00F958EC /* blank.nib.gz in Resources */, + 4AD4FEB71A52464F00F958EC /* testvm1.dsk.gz in Resources */, + 4AD4FED21A524BED00F958EC /* Apple2MacTestDisk-Info.plist in Resources */, + 4AD4FEB81A52464F00F958EC /* README in Resources */, + 4AD4FEB91A52464F00F958EC /* MainMenu-Test.xib in Resources */, + 4AD4FEBA1A52464F00F958EC /* InfoPlist.strings in Resources */, + 4AD4FEBB1A52464F00F958EC /* Images.xcassets in Resources */, + 4AD4FEBC1A52464F00F958EC /* speedtest.txt in Resources */, + 4AD4FEBD1A52464F00F958EC /* Credits.rtf in Resources */, + 4AD4FEBE1A52464F00F958EC /* Basic.vsh in Resources */, + 4AD4FEBF1A52464F00F958EC /* Basic.fsh in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4ADC521D19E8CA4500186B36 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -906,6 +1003,34 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 4AD4FE921A52464F00F958EC /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$SRCROOT/../src/x86/genglue\" \"$SRCROOT/../src/disk.c\" \"$SRCROOT/../src/misc.c\" \"$SRCROOT/../src/display.c\" \"$SRCROOT/../src/vm.c\" \"$SRCROOT/../src/cpu-supp.c\" > \"$SRCROOT/../src/x86/glue.S\""; + showEnvVarsInLog = 0; + }; + 4AD4FEB31A52464F00F958EC /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/rm -f \"$SRCROOT/../src/x86/glue.S\""; + showEnvVarsInLog = 0; + }; 4ADC51FC19E8CA4500186B36 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1021,6 +1146,40 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 4AD4FE931A52464F00F958EC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4AD4FE941A52464F00F958EC /* cpu.S in Sources */, + 4AD4FE951A52464F00F958EC /* prefs.c in Sources */, + 4AD4FE961A52464F00F958EC /* display.c in Sources */, + 4AD4FE971A52464F00F958EC /* disk.c in Sources */, + 4AD4FE981A52464F00F958EC /* sourceUtil.c in Sources */, + 4AD4FE991A52464F00F958EC /* modelUtil.c in Sources */, + 4AD4FE9A1A52464F00F958EC /* debugger.c in Sources */, + 4AD4FE9B1A52464F00F958EC /* keys.c in Sources */, + 4AD4FE9C1A52464F00F958EC /* timing.c in Sources */, + 4AD4FE9D1A52464F00F958EC /* testcommon.c in Sources */, + 4AD4FE9E1A52464F00F958EC /* misc.c in Sources */, + 4AD4FE9F1A52464F00F958EC /* opcodes.c in Sources */, + 4AD4FEA01A52464F00F958EC /* glvideo.c in Sources */, + 4AD4FEA11A52464F00F958EC /* rom-shim.c in Sources */, + 4AD4FEA21A52464F00F958EC /* zlib-helpers.c in Sources */, + 4AD4FEA31A52464F00F958EC /* darwin-shim.c in Sources */, + 4AD4FEA41A52464F00F958EC /* main.m in Sources */, + 4AD4FECB1A52468700F958EC /* testdisk.c in Sources */, + 4AD4FEA51A52464F00F958EC /* vectorUtil.c in Sources */, + 4AD4FEA61A52464F00F958EC /* debug.l in Sources */, + 4AD4FEA71A52464F00F958EC /* CPUTestAppDelegate.m in Sources */, + 4AD4FEA91A52464F00F958EC /* font.c in Sources */, + 4AD4FEAA1A52464F00F958EC /* cpu-supp.c in Sources */, + 4AD4FEAB1A52464F00F958EC /* vm.c in Sources */, + 4AD4FEAC1A52464F00F958EC /* darwin-glue.S in Sources */, + 4AD4FEAD1A52464F00F958EC /* matrixUtil.c in Sources */, + 4AD4FEAE1A52464F00F958EC /* joystick.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4ADC51FD19E8CA4500186B36 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1224,6 +1383,67 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 4AD4FEC11A52464F00F958EC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_ARC = NO; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Apple2Mac/Apple2Mac-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "APPLE2IX=1", + "DEBUGGER=1", + "KEYPAD_JOYSTICK=1", + "TEST_DISK=1", + "HEADLESS=1", + "TESTING=1", + "CPU_TRACING=1", + "DISK_TRACING=1", + "VM_TRACING=1", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + ); + INFOPLIST_FILE = "$(SRCROOT)/Apple2MacTests/Apple2MacTestDisk-Info.plist"; + PRODUCT_NAME = Apple2MacTestDisk; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../src"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 4AD4FEC21A52464F00F958EC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_ARC = NO; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Apple2Mac/Apple2Mac-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "NDEBUG=1", + "APPLE2IX=1", + "DEBUGGER=1", + "KEYPAD_JOYSTICK=1", + "TEST_VM=1", + "HEADLESS=1", + "TESTING=1", + "CPU_TRACING=1", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + ); + INFOPLIST_FILE = "$(SRCROOT)/Apple2MacTests/Apple2MacTestDisk-Info.plist"; + PRODUCT_NAME = Apple2MacTestDisk; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../src"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; 4ADC522519E8CA4500186B36 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1529,6 +1749,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 4AD4FEC01A52464F00F958EC /* Build configuration list for PBXNativeTarget "Apple2MacTestDisk" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4AD4FEC11A52464F00F958EC /* Debug */, + 4AD4FEC21A52464F00F958EC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 4ADC522419E8CA4500186B36 /* Build configuration list for PBXNativeTarget "Apple2MacTestVM" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Apple2Mac/Apple2MacTests/Apple2MacTestDisk-Info.plist b/Apple2Mac/Apple2MacTests/Apple2MacTestDisk-Info.plist new file mode 100644 index 00000000..05620636 --- /dev/null +++ b/Apple2Mac/Apple2MacTests/Apple2MacTestDisk-Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + org.deadc0de.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSApplicationCategoryType + public.app-category.education + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright © 2014 deadc0de.org. All rights reserved. + NSMainNibFile + MainMenu-Test + NSPrincipalClass + NSApplication + + diff --git a/Apple2Mac/Apple2MacTests/CPUTestAppDelegate.m b/Apple2Mac/Apple2MacTests/CPUTestAppDelegate.m index 4a909c93..c3cce88e 100644 --- a/Apple2Mac/Apple2MacTests/CPUTestAppDelegate.m +++ b/Apple2Mac/Apple2MacTests/CPUTestAppDelegate.m @@ -34,6 +34,8 @@ extern int test_display(int argc, char **argv); test_vm(local_argc, local_argv); #elif defined(TEST_DISPLAY) test_display(local_argc, local_argv); +#elif defined(TEST_DISK) + test_disk(local_argc, local_argv); #else #error "OOPS, no tests specified" #endif From f91d99aab90190e13ab1076949617bdcf8fcd4cd Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 28 Dec 2014 15:23:14 -0800 Subject: [PATCH 011/615] Blank disk changes after AppleWin-sourced disks refactor --- disks/blank.dsk.gz | Bin 7342 -> 7342 bytes disks/blank.nib.gz | Bin 13004 -> 12982 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/disks/blank.dsk.gz b/disks/blank.dsk.gz index 9e225c42275ffdf48df1ea2d6faf3fad755408bf..f125ac02ec0b29cc70518fa4b879e33029fc0555 100644 GIT binary patch delta 6597 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 DOj|rR delta 6597 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 DuK+$$ diff --git a/disks/blank.nib.gz b/disks/blank.nib.gz index 37b2c7e365f61f3b3051abc1cee67bee5983a198..7c2dab77dd96305a1db31a37be633a0436b495d2 100644 GIT binary patch literal 12982 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 (a*2fYKXo66*S@;f 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 Date: Sun, 28 Dec 2014 16:10:32 -0800 Subject: [PATCH 012/615] Fix some tests and expose mdstr globally (easier debugger introspection) --- src/test/testcommon.c | 1 + src/test/testcommon.h | 2 +- src/test/testdisk.c | 40 ++++++++++++++++++++-------------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/test/testcommon.c b/src/test/testcommon.c index d489855d..ef1bc074 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -18,6 +18,7 @@ #define TESTBUF_SZ 1024 bool test_do_reboot = true; +char mdstr[(SHA_DIGEST_LENGTH*2)+1]; static char input_str[TESTBUF_SZ]; // ASCII static unsigned int input_length = 0; diff --git a/src/test/testcommon.h b/src/test/testcommon.h index bfecb941..fe576783 100644 --- a/src/test/testcommon.h +++ b/src/test/testcommon.h @@ -33,10 +33,10 @@ #define BLANK_SCREEN "6C8ABA272F220F00BE0E76A8659A1E30C2D3CDBE" #define BOOT_SCREEN "F8D6C781E0BB7B3DDBECD69B25E429D845506594" +extern char mdstr[(SHA_DIGEST_LENGTH*2)+1]; #define ASSERT_SHA(SHA_STR) \ do { \ uint8_t md[SHA_DIGEST_LENGTH]; \ - char mdstr[(SHA_DIGEST_LENGTH*2)+1]; \ const uint8_t * const fb = video_current_framebuffer(); \ SHA1(fb, SCANWIDTH*SCANHEIGHT, md); \ sha1_to_str(md, mdstr); \ diff --git a/src/test/testdisk.c b/src/test/testdisk.c index 6211cb27..bcfadf63 100644 --- a/src/test/testdisk.c +++ b/src/test/testdisk.c @@ -62,10 +62,10 @@ TEST test_boot_disk_bytes() { do { uint8_t md[SHA_DIGEST_LENGTH]; - char mdstr[(SHA_DIGEST_LENGTH*2)+1]; + char mdstr0[(SHA_DIGEST_LENGTH*2)+1]; FILE *fp = fopen(disk, "r"); - char *buf = malloc(EXPECTED_DISK_TRACE_FILE_SIZE); + unsigned char *buf = malloc(EXPECTED_DISK_TRACE_FILE_SIZE); if (fread(buf, 1, EXPECTED_DISK_TRACE_FILE_SIZE, fp) != EXPECTED_DISK_TRACE_FILE_SIZE) { ASSERT(false); } @@ -73,8 +73,8 @@ TEST test_boot_disk_bytes() { SHA1(buf, EXPECTED_DISK_TRACE_FILE_SIZE, md); FREE(buf); - sha1_to_str(md, mdstr); - ASSERT(strcmp(mdstr, EXPECTED_DISK_TRACE_SHA) == 0); + sha1_to_str(md, mdstr0); + ASSERT(strcmp(mdstr0, EXPECTED_DISK_TRACE_SHA) == 0); } while(0); unlink(disk); @@ -87,6 +87,7 @@ TEST test_boot_disk_bytes() { // ... but if it's correct, you're fairly assured the cpu/vm is working =) #if ABUSIVE_TESTS #define EXPECTED_CPU_TRACE_FILE_SIZE 809430487 +#define EXPECTED_CPU_TRACE_SHA "4DB0C2547A0F02450A0E5E663C5BE8EA776C7A41" TEST test_boot_disk_cputrace() { char *homedir = getenv("HOME"); char *output = NULL; @@ -96,6 +97,7 @@ TEST test_boot_disk_cputrace() { cpu65_trace_begin(output); } + srandom(0); BOOT_TO_DOS(); cpu65_trace_end(); @@ -103,14 +105,14 @@ TEST test_boot_disk_cputrace() { do { uint8_t md[SHA_DIGEST_LENGTH]; - char mdstr[(SHA_DIGEST_LENGTH*2)+1]; + char mdstr0[(SHA_DIGEST_LENGTH*2)+1]; FILE *fp = fopen(output, "r"); fseek(fp, 0, SEEK_END); long expectedSize = ftell(fp); ASSERT(expectedSize == EXPECTED_CPU_TRACE_FILE_SIZE); fseek(fp, 0, SEEK_SET); - char *buf = malloc(EXPECTED_CPU_TRACE_FILE_SIZE); + unsigned char *buf = malloc(EXPECTED_CPU_TRACE_FILE_SIZE); if (fread(buf, 1, EXPECTED_CPU_TRACE_FILE_SIZE, fp) != EXPECTED_CPU_TRACE_FILE_SIZE) { ASSERT(false); } @@ -118,11 +120,8 @@ TEST test_boot_disk_cputrace() { SHA1(buf, EXPECTED_CPU_TRACE_FILE_SIZE, md); FREE(buf); -#if 0 - // this is no longer a stable value to check due to random return values from disk VM routines - sha1_to_str(md, mdstr); - ASSERT(strcmp(mdstr, EXPECTED_CPU_TRACE_SHA) == 0); -#endif + sha1_to_str(md, mdstr0); + ASSERT(strcmp(mdstr0, EXPECTED_CPU_TRACE_SHA) == 0); } while(0); unlink(output); @@ -133,6 +132,7 @@ TEST test_boot_disk_cputrace() { #endif #define EXPECTED_VM_TRACE_FILE_SIZE 2830810 +#define EXPECTED_VM_TRACE_SHA "8B7A8169E34354773F82442DB6A0C3D6B69741D9" TEST test_boot_disk_vmtrace() { char *homedir = getenv("HOME"); char *disk = NULL; @@ -142,6 +142,7 @@ TEST test_boot_disk_vmtrace() { vm_trace_begin(disk); } + srandom(0); BOOT_TO_DOS(); vm_trace_end(); @@ -149,10 +150,10 @@ TEST test_boot_disk_vmtrace() { do { uint8_t md[SHA_DIGEST_LENGTH]; - char mdstr[(SHA_DIGEST_LENGTH*2)+1]; + char mdstr0[(SHA_DIGEST_LENGTH*2)+1]; FILE *fp = fopen(disk, "r"); - char *buf = malloc(EXPECTED_VM_TRACE_FILE_SIZE); + unsigned char *buf = malloc(EXPECTED_VM_TRACE_FILE_SIZE); if (fread(buf, 1, EXPECTED_VM_TRACE_FILE_SIZE, fp) != EXPECTED_VM_TRACE_FILE_SIZE) { ASSERT(false); } @@ -160,11 +161,8 @@ TEST test_boot_disk_vmtrace() { SHA1(buf, EXPECTED_VM_TRACE_FILE_SIZE, md); FREE(buf); -#if 0 - // this is no longer a stable value to check due to random return values from disk VM routines - sha1_to_str(md, mdstr); - ASSERT(strcmp(mdstr, EXPECTED_VM_TRACE_SHA) == 0); -#endif + sha1_to_str(md, mdstr0); + ASSERT(strcmp(mdstr0, EXPECTED_VM_TRACE_SHA) == 0); } while(0); unlink(disk); @@ -178,6 +176,8 @@ TEST test_boot_disk() { PASS(); } + +#define INIT_SHA1 "10F15B516E4CF2FC5B1712951A6F9C3D90BF595C" TEST test_inithello_dsk() { test_setup_boot_disk(BLANK_DSK, 0); @@ -192,7 +192,7 @@ TEST test_inithello_dsk() { c_debugger_go(); ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED); - ASSERT_SHA("10F15B516E4CF2FC5B1712951A6F9C3D90BF595C"); + ASSERT_SHA(INIT_SHA1); REBOOT_TO_DOS(); c_eject_6(0); @@ -213,7 +213,7 @@ TEST test_inithello_nib() { c_debugger_go(); ASSERT(apple_ii_64k[0][WATCHPOINT_ADDR] == TEST_FINISHED); - ASSERT_SHA("10F15B516E4CF2FC5B1712951A6F9C3D90BF595C"); + ASSERT_SHA(INIT_SHA1); REBOOT_TO_DOS(); c_eject_6(0); From ffd4647693f89262500122fcc5517eed14eb28fa Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 28 Dec 2014 16:14:54 -0800 Subject: [PATCH 013/615] Make test_breakpoint() function work again --- src/test/testcommon.c | 4 +++- src/test/testdisk.c | 1 + src/test/testdisplay.c | 1 + src/test/testvm.c | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/testcommon.c b/src/test/testcommon.c index ef1bc074..bd53356e 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -134,7 +134,9 @@ void test_common_init(bool do_cputhread) { if (is_headless) { c_debugger_set_timeout(10); } else { - fprintf(stderr, "NOTE : RUNNING WITH DISPLAY ... pass HEADLESS=1 to environment to run test in faster headless mode\n"); + fprintf(stderr, "NOTE : RUNNING WITH DISPLAY\n"); + fprintf(stderr, "Will spinloop on failed tests for debugger intervention\n"); + fprintf(stderr, "Pass HEADLESS=1 to environment to run nonstop\n"); c_debugger_set_timeout(0); } } diff --git a/src/test/testdisk.c b/src/test/testdisk.c index bcfadf63..bd1c0737 100644 --- a/src/test/testdisk.c +++ b/src/test/testdisk.c @@ -229,6 +229,7 @@ static int begin_video = -1; GREATEST_SUITE(test_suite_disk) { GREATEST_SET_SETUP_CB(testdisk_setup, NULL); GREATEST_SET_TEARDOWN_CB(testdisk_teardown, NULL); + GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL); // TESTS -------------------------- begin_video=!is_headless; diff --git a/src/test/testdisplay.c b/src/test/testdisplay.c index 7efd334d..ac025de3 100644 --- a/src/test/testdisplay.c +++ b/src/test/testdisplay.c @@ -318,6 +318,7 @@ static int begin_video = -1; GREATEST_SUITE(test_suite_display) { GREATEST_SET_SETUP_CB(testdisplay_setup, NULL); GREATEST_SET_TEARDOWN_CB(testdisplay_teardown, NULL); + GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL); // TESTS -------------------------- begin_video=!is_headless; diff --git a/src/test/testvm.c b/src/test/testvm.c index d56b50a8..da33d51b 100644 --- a/src/test/testvm.c +++ b/src/test/testvm.c @@ -3265,6 +3265,7 @@ static int begin_video = -1; GREATEST_SUITE(test_suite_vm) { GREATEST_SET_SETUP_CB(testvm_setup, NULL); GREATEST_SET_TEARDOWN_CB(testvm_teardown, NULL); + GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL); // TESTS -------------------------- begin_video=!is_headless; From 06011e2902a6bcc9ce58e35b4f7b2b5a1fbe8ab5 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 3 Jan 2015 13:11:56 -0800 Subject: [PATCH 014/615] New disk tests and improvements - Exercise various TRACING codepaths - Adds ProDOS-order tests --- disks/blank.nib.gz | Bin 12982 -> 12844 bytes disks/blank.po.gz | Bin 0 -> 7420 bytes src/test/testcommon.c | 2 +- src/test/testdisk.c | 780 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 781 insertions(+), 1 deletion(-) create mode 100644 disks/blank.po.gz diff --git a/disks/blank.nib.gz b/disks/blank.nib.gz index 7c2dab77dd96305a1db31a37be633a0436b495d2..06fefc48eaa2b9ff909d19ce40e81ad03fa0aa66 100644 GIT binary patch literal 12844 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 (a*2fYKXo66*S@;f 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 Date: Sat, 10 Jan 2015 14:11:33 -0800 Subject: [PATCH 015/615] Updates flapple140.po image to a useable in read-only mode - lol, the official version from the Intarwebs is buggy when run in read-only mode (it bombs to the monitor attemping to create the save-game file) - same bug witnessed on AppleWin - this version has a minimal save-game file allowing the game to actually boot --- disks/flapple140.po.gz | Bin 42081 -> 42079 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/disks/flapple140.po.gz b/disks/flapple140.po.gz index f7a75d82c98276b75121bfbbfe7471eea55ea7be..84f798c43fac3f49b49336d87d35da492df31d03 100644 GIT binary patch delta 41647 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 delta 41639 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 From b031bdb4a6e8cfe25ecac65331ddeee0a6c37d23 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 10 Jan 2015 09:44:23 -0800 Subject: [PATCH 016/615] Refactor private defines --- src/meta/debugger.c | 15 +++++++++++++++ src/misc.h | 15 --------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/meta/debugger.c b/src/meta/debugger.c index 20d940e4..8e0c0de8 100644 --- a/src/meta/debugger.c +++ b/src/meta/debugger.c @@ -20,6 +20,21 @@ #include #endif +#define SW_TEXT 0xC050 +#define SW_MIXED 0xC052 +#define SW_PAGE2 0xC054 +#define SW_HIRES 0xC056 +#define SW_80STORE 0xC000 +#define SW_RAMRD 0xC002 +#define SW_RAMWRT 0xC004 +#define SW_ALTZP 0xC008 +#define SW_80COL 0xC00C +#define SW_ALTCHAR 0xC00E +#define SW_SLOTC3ROM 0xC00B /* anomaly */ +#define SW_SLOTCXROM 0xC006 +#define SW_DHIRES 0xC05E +#define SW_IOUDIS 0xC07E + const struct opcode_struct *opcodes; static stepping_struct_t stepping_struct = { 0 }; diff --git a/src/misc.h b/src/misc.h index 7baf7586..21240889 100644 --- a/src/misc.h +++ b/src/misc.h @@ -19,21 +19,6 @@ #ifndef __ASSEMBLER__ -#define SW_TEXT 0xC050 -#define SW_MIXED 0xC052 -#define SW_PAGE2 0xC054 -#define SW_HIRES 0xC056 -#define SW_80STORE 0xC000 -#define SW_RAMRD 0xC002 -#define SW_RAMWRT 0xC004 -#define SW_ALTZP 0xC008 -#define SW_80COL 0xC00C -#define SW_ALTCHAR 0xC00E -#define SW_SLOTC3ROM 0xC00B /* anomaly */ -#define SW_SLOTCXROM 0xC006 -#define SW_DHIRES 0xC05E -#define SW_IOUDIS 0xC07E - /* Text characters */ extern const unsigned char ucase_glyphs[0x200]; extern const unsigned char lcase_glyphs[0x100]; From a7cd2810379c8cae66f3819251dfbd385adf69f3 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 10 Jan 2015 09:45:53 -0800 Subject: [PATCH 017/615] mark other INTERFACE_CLASSIC codepaths --- src/display.c | 6 ++++++ src/video/video.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/display.c b/src/display.c index c5627fe5..677ad977 100644 --- a/src/display.c +++ b/src/display.c @@ -45,8 +45,10 @@ uint8_t video__hires_odd[0x800]; uint8_t video__dhires1[256]; uint8_t video__dhires2[256]; +#ifdef INTERFACE_CLASSIC // Interface font static uint8_t video__int_font[3][0x4000]; +#endif int video__current_page; // current visual page int video__strictcolors = 1;// refactor : should be static @@ -472,6 +474,7 @@ void video_set(int flags) { video_initialize_dhires_values(); } +#ifdef INTERFACE_CLASSIC void video_loadfont_int(int first, int quantity, const uint8_t *data) { unsigned int i = quantity * 8; while (i--) { @@ -490,6 +493,7 @@ void video_loadfont_int(int first, int quantity, const uint8_t *data) { } } } +#endif // ---------------------------------------------------------------------------- // Plotting routines @@ -546,6 +550,7 @@ static inline void _plot_lores(uint8_t **d, const uint32_t val) { *((uint16_t *)(*d)) = (uint16_t)(val & 0xffff); } +#ifdef INTERFACE_CLASSIC void video_plotchar( int x, int y, int scheme, uint8_t c ) { uint8_t *d; uint8_t *s; @@ -563,6 +568,7 @@ void video_plotchar( int x, int y, int scheme, uint8_t c ) { _plot_char80(&d,&s); _plot_char80(&d,&s); } +#endif void video_init(void) { diff --git a/src/video/video.h b/src/video/video.h index c531f501..649ae8b0 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -92,6 +92,7 @@ void video_redraw(void); */ void video_setpage(int page); +#ifdef INTERFACE_CLASSIC /* Like loadfont, but writes to a seperate 256 character table used by * video_plotchar and not the apple text-mode display. */ @@ -108,6 +109,7 @@ void video_loadfont_int(int first, int qty, const uint8_t *data); * 2 - Red text on Black background */ void video_plotchar(int row, int col, int color, uint8_t code); +#endif /* * Get a reference to current internal framebuffer From 381d97c4850180e5337e6bc0759b77da2ad45da8 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 10 Jan 2015 09:53:38 -0800 Subject: [PATCH 018/615] Preliminary VBL support sourced from AppleWin --- src/audio/mockingboard.c | 19 +----- src/disk.c | 32 +++------ src/display.c | 140 +++++++++++++++++++++++++++++++++++++++ src/timing.c | 36 +++++++--- src/video/video.h | 7 ++ src/vm.c | 18 +---- 6 files changed, 186 insertions(+), 66 deletions(-) diff --git a/src/audio/mockingboard.c b/src/audio/mockingboard.c index ffaef0fa..c3b2b5d2 100644 --- a/src/audio/mockingboard.c +++ b/src/audio/mockingboard.c @@ -1693,24 +1693,7 @@ void MB_Reset() //----------------------------------------------------------------------------- #ifdef APPLE2IX -// TODO FIXME!!! -// From AppleWin ... We don't do anything clever (yet) with video emulation ... -// References to Jim Sather's books are given as eg: -// UTAIIe:5-7,P3 (Understanding the Apple IIe, chapter 5, page 7, Paragraph 3) -/* -WORD VideoGetScannerAddress(bool* pbVblBar_OUT, const DWORD uExecutedCycles) -{ - ...; -} -*/ - -static uint8_t MemReadFloatingBus(const unsigned long uExecutedCycles) -{ - //return*(LPBYTE)(mem + VideoGetScannerAddress(NULL, uExecutedCycles)); - // HUGE HACK FIXME TODO - return c_read_rand(0x0); -} - +#define MemReadFloatingBus floating_bus #define nCyclesLeft cpu65_cycle_count #define nAddr ea GLUE_C_READ(MB_Read) diff --git a/src/disk.c b/src/disk.c index 54183768..bfc7d518 100644 --- a/src/disk.c +++ b/src/disk.c @@ -464,23 +464,6 @@ static bool save_track_data(void) { return true; } -static uint8_t disk_io_pseudo_random(uint8_t hibit) { - // AppleWin algorithm ... unsure of the source or whether it fixes issues with particular images - static const uint8_t ret[16] = { - 0x00, 0x2D, 0x2D, 0x30, 0x30, 0x32, 0x32, 0x34, - 0x35, 0x39, 0x43, 0x43, 0x43, 0x60, 0x7F, 0x7F - }; - if (hibit) { - hibit = 0x80; - } - uint8_t r = c_read_rand(/*ignored*/0x0); - if (r <= 0xAA) { - return 0x20 | hibit; - } else { - return ret[r&0x0f] | hibit; - } -} - // ---------------------------------------------------------------------------- // Emulator hooks @@ -622,31 +605,31 @@ GLUE_C_READ(disk_read_phase) #endif } - return ea == 0xE0 ? 0xFF : disk_io_pseudo_random(1); + return ea == 0xE0 ? 0xFF : floating_bus_hibit(1, cpu65_cycle_count); } GLUE_C_READ(disk_read_motor_off) { disk6.motor = 1; - return disk_io_pseudo_random(1); + return floating_bus_hibit(1, cpu65_cycle_count); } GLUE_C_READ(disk_read_motor_on) { disk6.motor = 0; - return disk_io_pseudo_random(1); + return floating_bus_hibit(1, cpu65_cycle_count); } GLUE_C_READ(disk_read_select_a) { disk6.drive = 0; - return 0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(disk_read_select_b) { disk6.drive = 1; - return 0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(disk_read_latch) @@ -657,18 +640,19 @@ GLUE_C_READ(disk_read_latch) GLUE_C_READ(disk_read_prepare_in) { disk6.ddrw = 0; - return disk_io_pseudo_random(disk6.disk[disk6.drive].is_protected); + return floating_bus_hibit(disk6.disk[disk6.drive].is_protected, cpu65_cycle_count); } GLUE_C_READ(disk_read_prepare_out) { disk6.ddrw = 1; - return disk_io_pseudo_random(1); + return floating_bus_hibit(1, cpu65_cycle_count); } GLUE_C_WRITE(disk_write_latch) { disk6.disk_byte = b; + //return b; } // ---------------------------------------------------------------------------- diff --git a/src/display.c b/src/display.c index 677ad977..6347074f 100644 --- a/src/display.c +++ b/src/display.c @@ -53,6 +53,25 @@ static uint8_t video__int_font[3][0x4000]; int video__current_page; // current visual page int video__strictcolors = 1;// refactor : should be static +// Video constants -- sourced from AppleWin +static const bool bVideoScannerNTSC = true; +static const int kHBurstClock = 53; // clock when Color Burst starts +static const int kHBurstClocks = 4; // clocks per Color Burst duration +static const int kHClock0State = 0x18; // H[543210] = 011000 +static const int kHClocks = 65; // clocks per horizontal scan (including HBL) +static const int kHPEClock = 40; // clock when HPE (horizontal preset enable) goes low +static const int kHPresetClock = 41; // clock when H state presets +static const int kHSyncClock = 49; // clock when HSync starts +static const int kHSyncClocks = 4; // clocks per HSync duration +static const int kNTSCScanLines = 262; // total scan lines including VBL (NTSC) +static const int kNTSCVSyncLine = 224; // line when VSync starts (NTSC) +static const int kPALScanLines = 312; // total scan lines including VBL (PAL) +static const int kPALVSyncLine = 264; // line when VSync starts (PAL) +static const int kVLine0State = 0x100; // V[543210CBA] = 100000000 +static const int kVPresetLine = 256; // line when V state presets +static const int kVSyncLines = 4; // lines per VSync duration + + void video_loadfont(int first, int quantity, const uint8_t *data, int mode) { uint8_t fg = 0; uint8_t bg = 0; @@ -1115,3 +1134,124 @@ void video_redraw(void) { softswitches = softswitches_save; } +// References to Jim Sather's books are given as eg: +// UTAIIe:5-7,P3 (Understanding the Apple IIe, chapter 5, page 7, Paragraph 3) + +extern unsigned int CpuGetCyclesThisVideoFrame(const unsigned int nExecutedCycles); +uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedCycles) { + const bool SW_HIRES = (softswitches & SS_HIRES); + const bool SW_TEXT = (softswitches & SS_TEXT); + const bool SW_PAGE2 = (softswitches & SS_PAGE2); + const bool SW_80STORE = (softswitches & SS_80STORE); + const bool SW_MIXED = (softswitches & SS_MIXED); + + // get video scanner position + unsigned int nCycles = CpuGetCyclesThisVideoFrame(executedCycles); + + // machine state switches + int nHires = (SW_HIRES && !SW_TEXT) ? 1 : 0; + int nPage2 = SW_PAGE2 ? 1 : 0; + int n80Store = SW_80STORE ? 1 : 0; + + // calculate video parameters according to display standard + int nScanLines = bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines; + int nVSyncLine = bVideoScannerNTSC ? kNTSCVSyncLine : kPALVSyncLine; + int nScanCycles = nScanLines * kHClocks; + + // calculate horizontal scanning state + int nHClock = (nCycles + kHPEClock) % kHClocks; // which horizontal scanning clock + int nHState = kHClock0State + nHClock; // H state bits + if (nHClock >= kHPresetClock) // check for horizontal preset + { + nHState -= 1; // correct for state preset (two 0 states) + } + int h_0 = (nHState >> 0) & 1; // get horizontal state bits + int h_1 = (nHState >> 1) & 1; + int h_2 = (nHState >> 2) & 1; + int h_3 = (nHState >> 3) & 1; + int h_4 = (nHState >> 4) & 1; + int h_5 = (nHState >> 5) & 1; + + // calculate vertical scanning state + int nVLine = nCycles / kHClocks; // which vertical scanning line + int nVState = kVLine0State + nVLine; // V state bits + if ((nVLine >= kVPresetLine)) // check for previous vertical state preset + { + nVState -= nScanLines; // compensate for preset + } + int v_A = (nVState >> 0) & 1; // get vertical state bits + int v_B = (nVState >> 1) & 1; + int v_C = (nVState >> 2) & 1; + int v_0 = (nVState >> 3) & 1; + int v_1 = (nVState >> 4) & 1; + int v_2 = (nVState >> 5) & 1; + int v_3 = (nVState >> 6) & 1; + int v_4 = (nVState >> 7) & 1; + int v_5 = (nVState >> 8) & 1; + + // calculate scanning memory address + if (nHires && SW_MIXED && v_4 && v_2) // HIRES TIME signal (UTAIIe:5-7,P3) + { + nHires = 0; // address is in text memory for mixed hires + } + + int nAddend0 = 0x0D; // 1 1 0 1 + int nAddend1 = (h_5 << 2) | (h_4 << 1) | (h_3 << 0); + int nAddend2 = (v_4 << 3) | (v_3 << 2) | (v_4 << 1) | (v_3 << 0); + int nSum = (nAddend0 + nAddend1 + nAddend2) & 0x0F; // SUM (UTAIIe:5-9) + + unsigned int nAddress = 0; // build address from video scanner equations (UTAIIe:5-8,T5.1) + nAddress |= h_0 << 0; // a0 + nAddress |= h_1 << 1; // a1 + nAddress |= h_2 << 2; // a2 + nAddress |= nSum << 3; // a3 - a6 + nAddress |= v_0 << 7; // a7 + nAddress |= v_1 << 8; // a8 + nAddress |= v_2 << 9; // a9 + + int p2a = !(nPage2 && !n80Store); + int p2b = nPage2 && !n80Store; + + if (nHires) // hires? + { + // Y: insert hires-only address bits + nAddress |= v_A << 10; // a10 + nAddress |= v_B << 11; // a11 + nAddress |= v_C << 12; // a12 + nAddress |= p2a << 13; // a13 + nAddress |= p2b << 14; // a14 + } + else + { + // N: insert text-only address bits + nAddress |= p2a << 10; // a10 + nAddress |= p2b << 11; // a11 + + // Apple ][ (not //e) and HBL? + if (false/*IS_APPLE2*/ && // Apple II only (UTAIIe:I-4,#5) + !h_5 && (!h_4 || !h_3)) // HBL (UTAIIe:8-10,F8.5) + { + nAddress |= 1 << 12; // Y: a12 (add $1000 to address!) + } + } + + // update VBL' state + if (vblBarOut != NULL) + { + *vblBarOut = !v_4 || !v_3; // VBL' = (v_4 & v_3)' (UTAIIe:5-10,#3) + } + + return (uint16_t)nAddress; +} + +uint8_t floating_bus(const unsigned int executedCycles) { + uint16_t scanner_addr = video_scanner_get_address(NULL, executedCycles); + return apple_ii_64k[0][scanner_addr]; +} + +uint8_t floating_bus_hibit(const bool hibit, const unsigned int executedCycles) { + uint16_t scanner_addr = video_scanner_get_address(NULL, executedCycles); + uint8_t b = apple_ii_64k[0][scanner_addr]; + return (b & ~0x80) | (hibit ? 0x80 : 0); +} + diff --git a/src/timing.c b/src/timing.c index 0113fcdc..b7d5fb3d 100644 --- a/src/timing.c +++ b/src/timing.c @@ -23,10 +23,16 @@ #define EXECUTION_PERIOD_NSECS 1000000 // AppleWin: nExecutionPeriodUsec +const unsigned int uCyclesPerLine = 65; // 25 cycles of HBL & 40 cycles of HBL' +const unsigned int uVisibleLinesPerFrame = 64*3; // 192 +const unsigned int uLinesPerFrame = 262; // 64 in each third of the screen & 70 in VBL +const unsigned int dwClksPerFrame = uCyclesPerLine * uLinesPerFrame; // 17030 + double g_fCurrentCLK6502 = CLK_6502; bool g_bFullSpeed = false; // HACK TODO FIXME : prolly shouldn't be global anymore -- don't think it's necessary for speaker/soundcore/etc anymore ... uint64_t g_nCumulativeCycles = 0; // cumulative cycles since emulator (re)start int g_nCpuCyclesFeedback = 0; +static unsigned int g_dwCyclesThisFrame = 0; static bool alt_speed_enabled = false; double cpu_scale_factor = 1.0; @@ -253,23 +259,30 @@ void *cpu_thread(void *dummyptr) { dbg_cycles_executed += cpu65_cycle_count; #endif #ifdef AUDIO_ENABLED - unsigned int uExecutedCycles = cpu65_cycle_count; + unsigned int uActualCyclesExecuted = cpu65_cycle_count; + g_dwCyclesThisFrame += uActualCyclesExecuted; - MB_UpdateCycles(uExecutedCycles); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below) + MB_UpdateCycles(uActualCyclesExecuted); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below) // N.B.: IO calls that depend on accurate timing will update g_nCyclesExecuted - const unsigned int nRemainingCycles = uExecutedCycles - g_nCyclesExecuted; + const unsigned int nRemainingCycles = uActualCyclesExecuted - g_nCyclesExecuted; g_nCumulativeCycles += nRemainingCycles; if (!g_bFullSpeed) { - SpkrUpdate(uExecutedCycles); // play audio + SpkrUpdate(uActualCyclesExecuted); // play audio } - - // N.B.: technically this is not the end of the video frame... - MB_EndOfVideoFrame(); #endif + if (g_dwCyclesThisFrame >= dwClksPerFrame) + { + g_dwCyclesThisFrame -= dwClksPerFrame; + //VideoEndOfVideoFrame(); +#ifdef AUDIO_ENABLED + MB_EndOfVideoFrame(); +#endif + } + clock_gettime(CLOCK_MONOTONIC, &tj); pthread_mutex_unlock(&interface_mutex); // -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj @@ -330,9 +343,14 @@ void *cpu_thread(void *dummyptr) { } // From AppleWin... + +unsigned int CpuGetCyclesThisVideoFrame(const unsigned int nExecutedCycles) { + CpuCalcCycles(nExecutedCycles); + return g_dwCyclesThisFrame + g_nCyclesExecuted; +} + // Called when an IO-reg is accessed & accurate cycle info is needed -void CpuCalcCycles(const unsigned long nExecutedCycles) -{ +void CpuCalcCycles(const unsigned long nExecutedCycles) { // Calc # of cycles executed since this func was last called const long nCycles = nExecutedCycles - g_nCyclesExecuted; assert(nCycles >= 0); diff --git a/src/video/video.h b/src/video/video.h index 649ae8b0..b728274b 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -116,6 +116,13 @@ void video_plotchar(int row, int col, int color, uint8_t code); */ const uint8_t * const video_current_framebuffer(); +/* + * VBL routines + */ +uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedCycles); +uint8_t floating_bus(const unsigned int executedCycles); +uint8_t floating_bus_hibit(const bool hibit, const unsigned int executedCycles); + #endif /* !__ASSEMBLER__ */ /**** Private stuff follows *****/ diff --git a/src/vm.c b/src/vm.c index 0b91f55b..8d5b92b8 100644 --- a/src/vm.c +++ b/src/vm.c @@ -122,12 +122,6 @@ typedef struct vm_trace_range_t { } vm_trace_range_t; #endif -static uint16_t video_scanner_get_address(uint8_t *vbl_bar /*, current_executed_cycles*/) { - // HACK HACK HACK of course this is wrong ... - *vbl_bar = (c_read_rand(0x0) < 0x40) ? 0x80 : 0x0; - return 0x000; -} - GLUE_C_READ(ram_nop) { return 0x0; @@ -892,16 +886,10 @@ GLUE_C_READ(iie_check_dhires) GLUE_C_READ(iie_check_vbl) { - - uint8_t vbl_bar = 0; - video_scanner_get_address(&vbl_bar /*, current_executed_cycles*/); + bool vbl_bar = false; + video_scanner_get_address(&vbl_bar, cpu65_cycle_count); uint8_t key = c_read_keyboard(0xC000); - - if (vbl_bar) { - vbl_bar = 0x80; - } - - return (key & ~0x80) | vbl_bar; + return (key & ~0x80) | (vbl_bar ? 0x80 : 0x00); } GLUE_C_READ(iie_c3rom_peripheral) From 1ba28db8d77e088ee19a82db2d19809dbc20a904 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 10 Jan 2015 16:13:36 -0800 Subject: [PATCH 019/615] Varous VM switches return memory floating bus value --- src/cpu.h | 5 +- src/misc.c | 14 ++---- src/misc.h | 2 +- src/test/testdisk.c | 42 ++++++++-------- src/vm.c | 119 +++++++++++++++++++++----------------------- 5 files changed, 88 insertions(+), 94 deletions(-) diff --git a/src/cpu.h b/src/cpu.h index 6da412d9..783e507f 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -23,6 +23,9 @@ /* types */ +#define MEM_READ_FLAG (1<<0) +#define MEM_WRITE_FLAG (1<<1) + extern uint16_t cpu65_pc; // Program counter extern uint8_t cpu65_a; // Accumulator extern uint8_t cpu65_f; // Flags (host-order) @@ -32,7 +35,7 @@ extern uint8_t cpu65_sp; // Stack Pointer extern uint16_t cpu65_ea; // Last effective address extern uint8_t cpu65_d; // Last data byte written -extern uint8_t cpu65_rw; // 1 = read occured, 2 = write, 3 = both +extern uint8_t cpu65_rw; // MEM_READ_FLAG = read occured, MEM_WRITE_FLAG = write extern uint8_t cpu65_opcode; // Last opcode extern uint8_t cpu65_opcycles; // Last opcode extra cycles diff --git a/src/misc.c b/src/misc.c index a5927667..ec752edf 100644 --- a/src/misc.c +++ b/src/misc.c @@ -262,12 +262,12 @@ void c_initialize_tables() { cpu65_vmem_r[0xC01E] = iie_check_altchar; /* SLOTC3ROM switch */ - cpu65_vmem_w[0xC00A] = iie_c3rom_internal; // HACK FIXME TODO VERIFY : the pattern here is reversed from cxrom? + cpu65_vmem_w[0xC00A] = iie_c3rom_internal; cpu65_vmem_w[0xC00B] = iie_c3rom_peripheral; cpu65_vmem_r[0xC017] = iie_check_c3rom; /* SLOTCXROM switch */ - cpu65_vmem_w[0xC006] = iie_cxrom_peripheral; // HACK FIXME TODO VERIFY : the pattern here is reversed from c3rom? + cpu65_vmem_w[0xC006] = iie_cxrom_peripheral; cpu65_vmem_w[0xC007] = iie_cxrom_internal; cpu65_vmem_r[0xC015] = iie_check_cxrom; @@ -367,7 +367,7 @@ void c_initialize_tables() { /* Annunciator */ for (i = 0xC058; i <= 0xC05D; i++) { - cpu65_vmem_w[i] = cpu65_vmem_r[i] = iie_annunciator_noop; + cpu65_vmem_w[i] = cpu65_vmem_r[i] = iie_annunciator; } /* DHIRES */ @@ -430,13 +430,9 @@ void c_initialize_tables() { for (i = 0xC800; i < 0xD000; i++) { - cpu65_vmem_r[i] = - iie_read_slot_expansion; /* expansion rom */ + cpu65_vmem_r[i] = iie_read_slot_expansion; } - - cpu65_vmem_r[0xCFFF] = - cpu65_vmem_w[0xCFFF] = - iie_disable_slot_expansion; + cpu65_vmem_w[0xCFFF] = iie_read_slot_expansion; video_set(0); diff --git a/src/misc.h b/src/misc.h index 21240889..ce6bf680 100644 --- a/src/misc.h +++ b/src/misc.h @@ -192,7 +192,7 @@ iie_cxrom_peripheral(), iie_cxrom_internal(), iie_ioudis_on(), iie_ioudis_off(), -iie_annunciator_noop(), +iie_annunciator(), iie_dhires_on(), iie_dhires_off(), iie_hires_off(), diff --git a/src/test/testdisk.c b/src/test/testdisk.c index 1d374be5..c2cfa395 100644 --- a/src/test/testdisk.c +++ b/src/test/testdisk.c @@ -237,8 +237,8 @@ TEST test_boot_disk_cputrace() { } #endif -#define EXPECTED_CPUTRACE_HELLO_DSK_FILE_SIZE 107523409 -#define EXPECTED_CPUTRACE_HELLO_DSK_SHA "586F218EC3C368C73DE1945B7EF441919E0D5B0F" +#define EXPECTED_CPUTRACE_HELLO_FILE_SIZE 107500760 +#define EXPECTED_CPUTRACE_HELLO_SHA "CAF2507BAB937488F85377528041705114F06CA4" TEST test_cputrace_hello_dsk() { test_setup_boot_disk(BLANK_DSK, 0); @@ -267,18 +267,18 @@ TEST test_cputrace_hello_dsk() { FILE *fp = fopen(output, "r"); fseek(fp, 0, SEEK_END); long expectedSize = ftell(fp); - ASSERT(expectedSize == EXPECTED_CPUTRACE_HELLO_DSK_FILE_SIZE); + ASSERT(expectedSize == EXPECTED_CPUTRACE_HELLO_FILE_SIZE); fseek(fp, 0, SEEK_SET); - unsigned char *buf = malloc(EXPECTED_CPUTRACE_HELLO_DSK_FILE_SIZE); - if (fread(buf, 1, EXPECTED_CPUTRACE_HELLO_DSK_FILE_SIZE, fp) != EXPECTED_CPUTRACE_HELLO_DSK_FILE_SIZE) { + unsigned char *buf = malloc(EXPECTED_CPUTRACE_HELLO_FILE_SIZE); + if (fread(buf, 1, EXPECTED_CPUTRACE_HELLO_FILE_SIZE, fp) != EXPECTED_CPUTRACE_HELLO_FILE_SIZE) { ASSERT(false); } fclose(fp); fp = NULL; - SHA1(buf, EXPECTED_CPUTRACE_HELLO_DSK_FILE_SIZE, md); + SHA1(buf, EXPECTED_CPUTRACE_HELLO_FILE_SIZE, md); FREE(buf); sha1_to_str(md, mdstr0); - ASSERT(strcmp(mdstr0, EXPECTED_CPUTRACE_HELLO_DSK_SHA) == 0); + ASSERT(strcmp(mdstr0, EXPECTED_CPUTRACE_HELLO_SHA) == 0); } while(0); unlink(output); @@ -287,8 +287,8 @@ TEST test_cputrace_hello_dsk() { PASS(); } -#define EXPECTED_CPUTRACE_HELLO_NIB_FILE_SIZE 12888005 -#define EXPECTED_CPUTRACE_HELLO_NIB_SHA "539A628524ACCF066A82FA67D0A488C8D3DC01BF" +#define EXPECTED_CPUTRACE_HELLO_NIB_FILE_SIZE 12887880 +#define EXPECTED_CPUTRACE_HELLO_NIB_SHA "CE14642D70BA42B214C22BFB460F00AA54C8BB5C" TEST test_cputrace_hello_nib() { test_setup_boot_disk(BLANK_NIB, 0); @@ -337,8 +337,6 @@ TEST test_cputrace_hello_nib() { PASS(); } -#define EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE 107523284 -#define EXPECTED_CPUTRACE_HELLO_PO_SHA "A99C1D02B898E02662DEDBF235C55B175D01D05D" TEST test_cputrace_hello_po() { test_setup_boot_disk(BLANK_PO, 0); @@ -367,18 +365,18 @@ TEST test_cputrace_hello_po() { FILE *fp = fopen(output, "r"); fseek(fp, 0, SEEK_END); long expectedSize = ftell(fp); - ASSERT(expectedSize == EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE); + ASSERT(expectedSize == EXPECTED_CPUTRACE_HELLO_FILE_SIZE); fseek(fp, 0, SEEK_SET); - unsigned char *buf = malloc(EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE); - if (fread(buf, 1, EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE, fp) != EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE) { + unsigned char *buf = malloc(EXPECTED_CPUTRACE_HELLO_FILE_SIZE); + if (fread(buf, 1, EXPECTED_CPUTRACE_HELLO_FILE_SIZE, fp) != EXPECTED_CPUTRACE_HELLO_FILE_SIZE) { ASSERT(false); } fclose(fp); fp = NULL; - SHA1(buf, EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE, md); + SHA1(buf, EXPECTED_CPUTRACE_HELLO_FILE_SIZE, md); FREE(buf); sha1_to_str(md, mdstr0); - ASSERT(strcmp(mdstr0, EXPECTED_CPUTRACE_HELLO_PO_SHA) == 0); + ASSERT(strcmp(mdstr0, EXPECTED_CPUTRACE_HELLO_SHA) == 0); } while(0); unlink(output); @@ -387,8 +385,8 @@ TEST test_cputrace_hello_po() { PASS(); } -#define EXPECTED_VM_TRACE_FILE_SIZE 2830810 -#define EXPECTED_VM_TRACE_SHA "8B7A8169E34354773F82442DB6A0C3D6B69741D9" +#define EXPECTED_VM_TRACE_FILE_SIZE 2830792 +#define EXPECTED_VM_TRACE_SHA "0659556B878848A6421D93057F18B3FB518A7D76" TEST test_boot_disk_vmtrace() { char *homedir = getenv("HOME"); char *disk = NULL; @@ -433,8 +431,8 @@ TEST test_boot_disk_vmtrace() { PASS(); } -#define EXPECTED_VM_TRACE_NIB_FILE_SIZE 2930074 -#define EXPECTED_VM_TRACE_NIB_SHA "BD2BA2B9C8E7712F9E6ABF1049ED8D2C4D979934" +#define EXPECTED_VM_TRACE_NIB_FILE_SIZE 2930056 +#define EXPECTED_VM_TRACE_NIB_SHA "9C1B64255B1946011FAAF5DF53C24114401485EE" TEST test_boot_disk_vmtrace_nib() { test_setup_boot_disk(BLANK_NIB, 0); @@ -481,8 +479,8 @@ TEST test_boot_disk_vmtrace_nib() { PASS(); } -#define EXPECTED_VM_TRACE_PO_FILE_SIZE 2830810 -#define EXPECTED_VM_TRACE_PO_SHA "3432149815E9142FDAD6D9DF94C8621FEB56F7D7" +#define EXPECTED_VM_TRACE_PO_FILE_SIZE EXPECTED_VM_TRACE_FILE_SIZE +#define EXPECTED_VM_TRACE_PO_SHA "23236C80A9CC38E75BB27F2E70359234B6B8D4DA" TEST test_boot_disk_vmtrace_po() { test_setup_boot_disk(BLANK_PO, 0); diff --git a/src/vm.c b/src/vm.c index 8d5b92b8..7b62ed66 100644 --- a/src/vm.c +++ b/src/vm.c @@ -124,12 +124,12 @@ typedef struct vm_trace_range_t { GLUE_C_READ(ram_nop) { - return 0x0; + return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(cpu65_cycle_count); } GLUE_C_READ(read_unmapped_softswitch) { - return apple_ii_64k[0][ea]; + return c_ram_nop(ea); } GLUE_C_WRITE(write_unmapped_softswitch) @@ -164,7 +164,7 @@ GLUE_C_READ(speaker_toggle) #ifdef AUDIO_ENABLED SpkrToggle(); #endif - return 0; + return floating_bus(cpu65_cycle_count); } // ---------------------------------------------------------------------------- @@ -173,7 +173,7 @@ GLUE_C_READ(speaker_toggle) GLUE_C_READ(iie_page2_off) { if (!(softswitches & SS_PAGE2)) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches &= ~(SS_PAGE2|SS_SCREEN); @@ -191,13 +191,13 @@ GLUE_C_READ(iie_page2_off) video_setpage(0); - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_page2_on) { if (softswitches & SS_PAGE2) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches |= SS_PAGE2; @@ -216,7 +216,7 @@ GLUE_C_READ(iie_page2_on) video_setpage(1); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_page2) @@ -230,7 +230,7 @@ GLUE_C_READ(read_switch_graphics) softswitches &= ~SS_TEXT; video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(read_switch_text) @@ -239,7 +239,7 @@ GLUE_C_READ(read_switch_text) softswitches |= SS_TEXT; video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_text) @@ -253,7 +253,7 @@ GLUE_C_READ(read_switch_no_mixed) softswitches &= ~SS_MIXED; video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(read_switch_mixed) @@ -262,7 +262,7 @@ GLUE_C_READ(read_switch_mixed) softswitches |= SS_MIXED; video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_mixed) @@ -270,15 +270,18 @@ GLUE_C_READ(iie_check_mixed) return (softswitches & SS_MIXED) ? 0x80 : 0x00; } -GLUE_C_READ(iie_annunciator_noop) +GLUE_C_READ(iie_annunciator) { - return 0x0;// TBD : mem_floating_bus() + if ((ea >= 0xC058) && (ea <= 0xC05B)) { + // TODO: alternate joystick management? + } + return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_hires_off) { if (!(softswitches & SS_HIRES)) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches &= ~(SS_HIRES|SS_HGRRD|SS_HGRWRT); @@ -296,13 +299,13 @@ GLUE_C_READ(iie_hires_off) } video_redraw(); - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_hires_on) { if (softswitches & SS_HIRES) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches |= SS_HIRES; @@ -320,7 +323,7 @@ GLUE_C_READ(iie_hires_on) } video_redraw(); - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_hires) @@ -366,7 +369,7 @@ GLUE_C_READ(read_gc_strobe) } // NOTE (possible TODO FIXME): unimplemented GC2 and GC3 timers since they were not wired on the //e ... - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(read_gc0) @@ -391,12 +394,12 @@ GLUE_C_READ(read_gc1) GLUE_C_READ(iie_read_gc2) { - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_read_gc3) { - return 0x0; + return floating_bus(cpu65_cycle_count); } // ---------------------------------------------------------------------------- @@ -428,7 +431,7 @@ GLUE_C_READ(iie_c080) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_c081) @@ -448,7 +451,7 @@ GLUE_C_READ(iie_c081) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(lc_c082) @@ -462,7 +465,7 @@ GLUE_C_READ(lc_c082) base_d000_wrt = 0; base_e000_wrt = 0; - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_c083) @@ -480,7 +483,7 @@ GLUE_C_READ(iie_c083) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_c088) @@ -497,7 +500,7 @@ GLUE_C_READ(iie_c088) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_c089) @@ -517,7 +520,7 @@ GLUE_C_READ(iie_c089) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(lc_c08a) @@ -530,7 +533,7 @@ GLUE_C_READ(lc_c08a) base_d000_wrt = 0; base_e000_wrt = 0; - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_c08b) @@ -550,7 +553,7 @@ GLUE_C_READ(iie_c08b) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_bank) @@ -569,7 +572,7 @@ GLUE_C_READ(iie_check_lcram) GLUE_C_READ(iie_80store_off) { if (!(softswitches & SS_80STORE)) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches &= ~(SS_80STORE|SS_TEXTRD|SS_TEXTWRT|SS_HGRRD|SS_HGRWRT); @@ -596,13 +599,13 @@ GLUE_C_READ(iie_80store_off) video_setpage(1); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_80store_on) { if (softswitches & SS_80STORE) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches |= SS_80STORE; @@ -629,7 +632,7 @@ GLUE_C_READ(iie_80store_on) softswitches &= ~SS_SCREEN; video_setpage(0); - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_80store) @@ -640,7 +643,7 @@ GLUE_C_READ(iie_check_80store) GLUE_C_READ(iie_ramrd_main) { if (!(softswitches & SS_RAMRD)) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches &= ~SS_RAMRD; @@ -657,13 +660,13 @@ GLUE_C_READ(iie_ramrd_main) base_hgrrd = apple_ii_64k[0]; } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_ramrd_aux) { if (softswitches & SS_RAMRD) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches |= SS_RAMRD; @@ -680,7 +683,7 @@ GLUE_C_READ(iie_ramrd_aux) base_hgrrd = apple_ii_64k[1]; } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_ramrd) @@ -691,7 +694,7 @@ GLUE_C_READ(iie_check_ramrd) GLUE_C_READ(iie_ramwrt_main) { if (!(softswitches & SS_RAMWRT)) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches &= ~SS_RAMWRT; @@ -708,13 +711,13 @@ GLUE_C_READ(iie_ramwrt_main) base_hgrwrt = apple_ii_64k[0]; } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_ramwrt_aux) { if (softswitches & SS_RAMWRT) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches |= SS_RAMWRT; @@ -731,7 +734,7 @@ GLUE_C_READ(iie_ramwrt_aux) base_hgrwrt = apple_ii_64k[1]; } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_ramwrt) @@ -743,7 +746,7 @@ GLUE_C_READ_ALTZP(iie_altzp_main) { if (!(softswitches & SS_ALTZP)) { /* NOTE : test if ALTZP already off - due to d000-bank issues it is *needed*, not just a shortcut */ - return 0x0; + return floating_bus(cpu65_cycle_count); } softswitches &= ~SS_ALTZP; @@ -759,14 +762,14 @@ GLUE_C_READ_ALTZP(iie_altzp_main) base_e000_wrt = language_card[0] - 0xE000; } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ_ALTZP(iie_altzp_aux) { if (softswitches & SS_ALTZP) { /* NOTE : test if ALTZP already on - due to d000-bank issues it is *needed*, not just a shortcut */ - return 0x0; + return floating_bus(cpu65_cycle_count); } softswitches |= SS_ALTZP; @@ -774,7 +777,7 @@ GLUE_C_READ_ALTZP(iie_altzp_aux) _lc_to_auxmem(); - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_altzp) @@ -785,7 +788,7 @@ GLUE_C_READ(iie_check_altzp) GLUE_C_READ(iie_80col_off) { if (!(softswitches & SS_80COL)) { - return 0x0; // TODO: no early return? + return floating_bus(cpu65_cycle_count); } softswitches &= ~SS_80COL; @@ -794,13 +797,13 @@ GLUE_C_READ(iie_80col_off) video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_80col_on) { if (softswitches & SS_80COL) { - return 0x0; + return floating_bus(cpu65_cycle_count); } softswitches |= SS_80COL; @@ -809,7 +812,7 @@ GLUE_C_READ(iie_80col_on) video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_80col) @@ -823,7 +826,7 @@ GLUE_C_READ(iie_altchar_off) softswitches &= ~SS_ALTCHAR; c_set_primary_char(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_altchar_on) @@ -832,7 +835,7 @@ GLUE_C_READ(iie_altchar_on) softswitches |= SS_ALTCHAR; c_set_altchar(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_altchar) @@ -860,22 +863,20 @@ GLUE_C_READ(iie_check_ioudis) GLUE_C_READ(iie_dhires_on) { - // FIXME : does this depend on IOUDIS? if (!(softswitches & SS_DHIRES)) { softswitches |= SS_DHIRES; video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_dhires_off) { - // FIXME : does this depend on IOUDIS? if (softswitches & SS_DHIRES) { softswitches &= ~SS_DHIRES; video_redraw(); } - return 0x0; + return floating_bus(cpu65_cycle_count); } GLUE_C_READ(iie_check_dhires) @@ -949,16 +950,12 @@ GLUE_C_READ(iie_read_slot_expansion) { // HACK TODO FIXME : how does the expansion slot get referenced? Need to handle other ROMs that might use this area // ... Also Need moar tests ... + if (ea == 0xCFFF) { + // disable expansion ROM + } return apple_ii_64k[1][ea]; } -GLUE_C_READ(iie_disable_slot_expansion) -{ - // HACK TODO FIXME : how does the expansion slot get referenced? Need to handle other ROMs that might use this area - // ... Also Need moar tests ... - return 0x0; -} - // ---------------------------------------------------------------------------- void debug_print_softwitches(void) { From dcafae83905ca579319c0581038bb8af1c68f874 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 10 Jan 2015 19:29:09 -0800 Subject: [PATCH 020/615] read_rand() is really just floating_bus() return --- src/misc.c | 8 -------- src/misc.h | 1 - src/test/testdisk.c | 15 +++++++-------- src/test/testvm.c | 3 --- src/vm.c | 10 ---------- 5 files changed, 7 insertions(+), 30 deletions(-) diff --git a/src/misc.c b/src/misc.c index ec752edf..691f2114 100644 --- a/src/misc.c +++ b/src/misc.c @@ -274,14 +274,6 @@ void c_initialize_tables() { /* RDVBLBAR switch */ cpu65_vmem_r[0xC019] = iie_check_vbl; - /* random number generator */ - for (i = 0xC020; i < 0xC030; i++) - { - cpu65_vmem_r[i] = - cpu65_vmem_w[i] = - read_rand; - } - /* TEXT switch */ cpu65_vmem_r[0xC050] = cpu65_vmem_w[0xC050] = diff --git a/src/misc.h b/src/misc.h index ce6bf680..f8dac125 100644 --- a/src/misc.h +++ b/src/misc.h @@ -103,7 +103,6 @@ void c_set_primary_char(); void c_set_altchar(); void c_initialize_font(); void c_initialize_vm(); -uint8_t c_read_rand(uint16_t ea); void reinitialize(); /* vm hooks */ diff --git a/src/test/testdisk.c b/src/test/testdisk.c index c2cfa395..6e166ebf 100644 --- a/src/test/testdisk.c +++ b/src/test/testdisk.c @@ -337,6 +337,8 @@ TEST test_cputrace_hello_nib() { PASS(); } +#define EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE EXPECTED_CPUTRACE_HELLO_FILE_SIZE +#define EXPECTED_CPUTRACE_HELLO_PO_SHA EXPECTED_CPUTRACE_HELLO_SHA TEST test_cputrace_hello_po() { test_setup_boot_disk(BLANK_PO, 0); @@ -365,18 +367,18 @@ TEST test_cputrace_hello_po() { FILE *fp = fopen(output, "r"); fseek(fp, 0, SEEK_END); long expectedSize = ftell(fp); - ASSERT(expectedSize == EXPECTED_CPUTRACE_HELLO_FILE_SIZE); + ASSERT(expectedSize == EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE); fseek(fp, 0, SEEK_SET); - unsigned char *buf = malloc(EXPECTED_CPUTRACE_HELLO_FILE_SIZE); - if (fread(buf, 1, EXPECTED_CPUTRACE_HELLO_FILE_SIZE, fp) != EXPECTED_CPUTRACE_HELLO_FILE_SIZE) { + unsigned char *buf = malloc(EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE); + if (fread(buf, 1, EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE, fp) != EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE) { ASSERT(false); } fclose(fp); fp = NULL; - SHA1(buf, EXPECTED_CPUTRACE_HELLO_FILE_SIZE, md); + SHA1(buf, EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE, md); FREE(buf); sha1_to_str(md, mdstr0); - ASSERT(strcmp(mdstr0, EXPECTED_CPUTRACE_HELLO_SHA) == 0); + ASSERT(strcmp(mdstr0, EXPECTED_CPUTRACE_HELLO_PO_SHA) == 0); } while(0); unlink(output); @@ -1050,9 +1052,6 @@ void test_disk(int argc, char **argv) { test_argc = argc; test_argv = argv; - c_read_rand(0x0); - srandom(0); // force a known sequence - pthread_mutex_lock(&interface_mutex); test_common_init(/*cputhread*/true); diff --git a/src/test/testvm.c b/src/test/testvm.c index da33d51b..1f183541 100644 --- a/src/test/testvm.c +++ b/src/test/testvm.c @@ -3471,9 +3471,6 @@ void test_vm(int argc, char **argv) { test_argc = argc; test_argv = argv; - c_read_rand(0x0); - srandom(0); // force a known sequence - pthread_mutex_lock(&interface_mutex); test_common_init(/*cputhread*/true); diff --git a/src/vm.c b/src/vm.c index 7b62ed66..031d4f95 100644 --- a/src/vm.c +++ b/src/vm.c @@ -149,16 +149,6 @@ GLUE_C_READ(read_keyboard_strobe) return apple_ii_64k[0][0xC000]; } -GLUE_C_READ(read_rand) -{ - static time_t seed=0; - if (!seed) { - seed = time(NULL); - srandom(seed); - } - return (random() & 0xFF); -} - GLUE_C_READ(speaker_toggle) { #ifdef AUDIO_ENABLED From bf4e920b1f2b7aaabf801b70dcd0716e8a32c95d Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 10 Jan 2015 20:45:07 -0800 Subject: [PATCH 021/615] should always be NI2 size for converted PO/DO --- src/disk.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/disk.c b/src/disk.c index bfc7d518..dcc58c1f 100644 --- a/src/disk.c +++ b/src/disk.c @@ -412,8 +412,6 @@ static bool load_track_data(void) { return false; } disk6.disk[disk6.drive].nib_count = NIB_TRACK_SIZE; -// } else if ( THIS IS NI2 FORMAT ... ) { -// Do stuff-n-things } else { // .dsk, .do, .po images int track_pos = DSK_TRACK_SIZE * (disk6.disk[disk6.drive].phase >> 1); @@ -426,7 +424,7 @@ static bool load_track_data(void) { } disk6.disk[disk6.drive].nib_count = nibblize_track(buf, disk6.drive); - if ( (disk6.disk[disk6.drive].nib_count != NIB_TRACK_SIZE) && (disk6.disk[disk6.drive].nib_count != NI2_TRACK_SIZE) ) { + if (disk6.disk[disk6.drive].nib_count != NI2_TRACK_SIZE) { ERRLOG("Invalid dsk image creation..."); return false; } From 8d35cf1d2c5a979ab1fb3e7b4e261a019d53941d Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 11 Jan 2015 12:03:07 -0800 Subject: [PATCH 022/615] Rename to motor_off and record motor access times --- src/disk.c | 17 ++++++++++------- src/disk.h | 3 ++- src/meta/debugger.c | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/disk.c b/src/disk.c index dcc58c1f..d20c2317 100644 --- a/src/disk.c +++ b/src/disk.c @@ -502,13 +502,13 @@ GLUE_C_READ(disk_read_write_byte) disk6.disk[disk6.drive].track_dirty = true; } else { - if (disk6.motor) { // ??? - if (disk6.motor > 99) { + if (disk6.motor_off) { // ??? + if (disk6.motor_off > 99) { ERRLOG("OOPS, potential disk motor issue"); value = 0x00; break; } else { - disk6.motor++; + disk6.motor_off++; } } @@ -608,13 +608,15 @@ GLUE_C_READ(disk_read_phase) GLUE_C_READ(disk_read_motor_off) { - disk6.motor = 1; + clock_gettime(CLOCK_MONOTONIC, &disk6.motor_time); + disk6.motor_off = 1; return floating_bus_hibit(1, cpu65_cycle_count); } GLUE_C_READ(disk_read_motor_on) { - disk6.motor = 0; + clock_gettime(CLOCK_MONOTONIC, &disk6.motor_time); + disk6.motor_off = 0; return floating_bus_hibit(1, cpu65_cycle_count); } @@ -705,8 +707,9 @@ void disk_io_initialize(unsigned int slot) { void c_init_6(void) { disk6.disk[0].phase = disk6.disk[1].phase = 0; disk6.disk[0].track_valid = disk6.disk[1].track_valid = 0; - disk6.motor = 1; /* motor on */ - disk6.drive = 0; /* first drive active */ + disk6.motor_time = (struct timespec){ 0 }; + disk6.motor_off = 1; + disk6.drive = 0; disk6.ddrw = 0; } diff --git a/src/disk.h b/src/disk.h index a256a7d6..58f16de8 100644 --- a/src/disk.h +++ b/src/disk.h @@ -51,7 +51,8 @@ typedef struct diskette_t { } diskette_t; typedef struct drive_t { - int motor; + struct timespec motor_time; + int motor_off; int drive; int ddrw; int disk_byte; diff --git a/src/meta/debugger.c b/src/meta/debugger.c index 8e0c0de8..bad6c412 100644 --- a/src/meta/debugger.c +++ b/src/meta/debugger.c @@ -1019,7 +1019,7 @@ void show_disk_info() { /* generic information */ sprintf(second_buf[i++], "drive %s", (disk6.drive) ? "B" : "A"); - sprintf(second_buf[i++], "motor %s", (disk6.motor) ? "off" : "on"); + sprintf(second_buf[i++], "motor %s", (disk6.motor_off) ? "off" : "on"); sprintf(second_buf[i++], "%s", (disk6.ddrw) ? "write" : "read"); sprintf(second_buf[i++], "byte = %02X", disk6.disk_byte); if (!disk6.disk[disk6.drive].nibblized) From cce2bbb0ccb65f1556a87937920cf17c8bb97684 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 11 Jan 2015 12:12:51 -0800 Subject: [PATCH 023/615] timing code cleanup --- src/timing.c | 156 ++++++++++++++++++++++++--------------------------- src/timing.h | 5 +- 2 files changed, 75 insertions(+), 86 deletions(-) diff --git a/src/timing.c b/src/timing.c index b7d5fb3d..bb50f813 100644 --- a/src/timing.c +++ b/src/timing.c @@ -23,10 +23,19 @@ #define EXECUTION_PERIOD_NSECS 1000000 // AppleWin: nExecutionPeriodUsec -const unsigned int uCyclesPerLine = 65; // 25 cycles of HBL & 40 cycles of HBL' -const unsigned int uVisibleLinesPerFrame = 64*3; // 192 -const unsigned int uLinesPerFrame = 262; // 64 in each third of the screen & 70 in VBL -const unsigned int dwClksPerFrame = uCyclesPerLine * uLinesPerFrame; // 17030 +#define DEBUG_TIMING (!defined(NDEBUG) && 0) // enable to print timing stats +#if DEBUG_TIMING +# define TIMING_LOG(...) LOG(__VA_ARGS__) +#else +# define TIMING_LOG(...) +#endif + +#define DISK_MOTOR_QUIET_NSECS 2000000 + +static const unsigned int uCyclesPerLine = 65; // 25 cycles of HBL & 40 cycles of HBL' +static const unsigned int uVisibleLinesPerFrame = 64*3; // 192 +static const unsigned int uLinesPerFrame = 262; // 64 in each third of the screen & 70 in VBL +static const unsigned int dwClksPerFrame = uCyclesPerLine * uLinesPerFrame; // 17030 double g_fCurrentCLK6502 = CLK_6502; bool g_bFullSpeed = false; // HACK TODO FIXME : prolly shouldn't be global anymore -- don't think it's necessary for speaker/soundcore/etc anymore ... @@ -35,13 +44,14 @@ int g_nCpuCyclesFeedback = 0; static unsigned int g_dwCyclesThisFrame = 0; static bool alt_speed_enabled = false; + double cpu_scale_factor = 1.0; double cpu_altscale_factor = 1.0; int gc_cycles_timer_0 = 0; int gc_cycles_timer_1 = 0; -uint8_t emul_reinitialize = 0; +volatile uint8_t emul_reinitialize = 0; pthread_t cpu_thread_id = 0; static unsigned int g_nCyclesExecuted; // # of cycles executed up to last IO access @@ -99,7 +109,7 @@ static void _timing_initialize(double scale) { if (g_bFullSpeed) { - LOG("timing_initialize() emulation at fullspeed ..."); + TIMING_LOG("timing_initialize() emulation at fullspeed ..."); return; } @@ -109,58 +119,36 @@ static void _timing_initialize(double scale) g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE); SpkrReinitialize(); - LOG("timing_initialize() ... ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", g_fCurrentCLK6502, g_fClksPerSpkrSample); + TIMING_LOG("timing_initialize() ... ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", g_fCurrentCLK6502, g_fClksPerSpkrSample); #endif } -static void _switch_to_fullspeed(double scale) +static inline void _switch_to_fullspeed(void) { - if (!g_bFullSpeed) - { - g_bFullSpeed = true; - c_disable_sound_hooks(); - } + g_bFullSpeed = true; } -static void _switch_to_regular_speed(double scale) +static inline void _switch_to_regular_speed(double scale) { - if (g_bFullSpeed) - { - g_bFullSpeed = false; - c_initialize_sound_hooks(); - } + g_bFullSpeed = false; _timing_initialize(scale); } -void timing_toggle_cpu_speed() +void timing_toggle_cpu_speed(void) { alt_speed_enabled = !alt_speed_enabled; - - if (alt_speed_enabled) + double scale_factor = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor; + if (scale_factor >= CPU_SCALE_FASTEST) { - if (cpu_altscale_factor >= CPU_SCALE_FASTEST) - { - _switch_to_fullspeed(cpu_altscale_factor); - } - else - { - _switch_to_regular_speed(cpu_altscale_factor); - } + _switch_to_fullspeed(); } else { - if (cpu_scale_factor >= CPU_SCALE_FASTEST) - { - _switch_to_fullspeed(cpu_scale_factor); - } - else - { - _switch_to_regular_speed(cpu_scale_factor); - } + _switch_to_regular_speed(scale_factor); } } -void timing_initialize() +void timing_initialize(void) { _timing_initialize(cpu_scale_factor); } @@ -178,7 +166,7 @@ void *cpu_thread(void *dummyptr) { int debugging_cycles0 = 0; int debugging_cycles = 0; -#ifndef NDEBUG +#if DEBUG_TIMING unsigned long dbg_ticks = 0; int speaker_neg_feedback = 0; int speaker_pos_feedback = 0; @@ -201,8 +189,7 @@ void *cpu_thread(void *dummyptr) { deltat = timespec_diff(t0, ti, &negative); if (deltat.tv_sec) { - // TODO FIXME : this is innocuous when coming out of interface menus, but are there any other edge cases? - LOG("NOTE : serious divergence from target time ..."); + TIMING_LOG("NOTE : serious divergence from target time ..."); t0 = ti; deltat = timespec_diff(t0, ti, &negative); } @@ -255,7 +242,7 @@ void *cpu_thread(void *dummyptr) { reinitialize(); } } while (is_debugging); -#ifndef NDEBUG +#if DEBUG_TIMING dbg_cycles_executed += cpu65_cycle_count; #endif #ifdef AUDIO_ENABLED @@ -287,53 +274,54 @@ void *cpu_thread(void *dummyptr) { pthread_mutex_unlock(&interface_mutex); // -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj - deltat = timespec_diff(ti, tj, &negative); - assert(!negative); - long sleepfor = 0; - if (!deltat.tv_sec && !g_bFullSpeed) - { - sleepfor = EXECUTION_PERIOD_NSECS - drift_adj_nsecs - deltat.tv_nsec; - } - - if (sleepfor <= 0) - { - // lagging ... - static time_t throttle_warning = 0; - if (t0.tv_sec - throttle_warning > 0) + if (!g_bFullSpeed) { + deltat = timespec_diff(ti, tj, &negative); + assert(!negative); + long sleepfor = 0; + if (!deltat.tv_sec) { - LOG("lagging... %ld . %ld", deltat.tv_sec, deltat.tv_nsec); - throttle_warning = t0.tv_sec; + sleepfor = EXECUTION_PERIOD_NSECS - drift_adj_nsecs - deltat.tv_nsec; } - } - else - { - deltat.tv_sec = 0; - deltat.tv_nsec = sleepfor; - nanosleep(&deltat, NULL); - } -#ifndef NDEBUG - // collect timing statistics + if (sleepfor <= 0) + { + // lagging ... + static time_t throttle_warning = 0; + if (t0.tv_sec - throttle_warning > 0) + { + TIMING_LOG("lagging... %ld . %ld", deltat.tv_sec, deltat.tv_nsec); + throttle_warning = t0.tv_sec; + } + } + else + { + deltat.tv_sec = 0; + deltat.tv_nsec = sleepfor; + nanosleep(&deltat, NULL); + } - if (speaker_neg_feedback > g_nCpuCyclesFeedback) - { - speaker_neg_feedback = g_nCpuCyclesFeedback; - } - if (speaker_pos_feedback < g_nCpuCyclesFeedback) - { - speaker_pos_feedback = g_nCpuCyclesFeedback; - } +#if DEBUG_TIMING + // collect timing statistics + if (speaker_neg_feedback > g_nCpuCyclesFeedback) + { + speaker_neg_feedback = g_nCpuCyclesFeedback; + } + if (speaker_pos_feedback < g_nCpuCyclesFeedback) + { + speaker_pos_feedback = g_nCpuCyclesFeedback; + } - dbg_ticks += EXECUTION_PERIOD_NSECS; - if ((dbg_ticks % NANOSECONDS) == 0) - { - LOG("tick:(%ld.%ld) real:(%ld.%ld) cycles exe: %d ... speaker feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, dbg_cycles_executed, speaker_neg_feedback, speaker_pos_feedback); - dbg_cycles_executed = 0; - dbg_ticks = 0; - speaker_neg_feedback = 0; - speaker_pos_feedback = 0; - } + dbg_ticks += EXECUTION_PERIOD_NSECS; + if ((dbg_ticks % NANOSECONDS) == 0) + { + LOG("tick:(%ld.%ld) real:(%ld.%ld) cycles exe: %d ... speaker feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, dbg_cycles_executed, speaker_neg_feedback, speaker_pos_feedback); + dbg_cycles_executed = 0; + dbg_ticks = 0; + speaker_neg_feedback = 0; + speaker_pos_feedback = 0; + } #endif + } } while (!emul_reinitialize); reinitialize(); diff --git a/src/timing.h b/src/timing.h index ca298cab..1b60c0bc 100644 --- a/src/timing.h +++ b/src/timing.h @@ -42,6 +42,7 @@ extern double g_fCurrentCLK6502; extern bool g_bFullSpeed; extern uint64_t g_nCumulativeCycles; extern int g_nCpuCyclesFeedback; + extern double cpu_scale_factor; extern double cpu_altscale_factor; @@ -52,9 +53,9 @@ extern int gc_cycles_timer_1; struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative); -void timing_toggle_cpu_speed(); +void timing_toggle_cpu_speed(void); -void timing_initialize(); +void timing_initialize(void); void *cpu_thread(void *ignored); From 643a4313868afebb539ac52a8a03345e45092dfe Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 11 Jan 2015 12:27:39 -0800 Subject: [PATCH 024/615] first cut at auto fast-loading of disk images - automatically adjusts cpu timing to fastest if drive motor accessed recently and no audio/video - TODO: audio output clipping issue when (un)pausing audio outpu --- src/display.c | 17 +++++++++++++++++ src/timing.c | 34 ++++++++++++++++++++++++++++++---- src/timing.h | 1 + src/video/glvideo.c | 4 ++++ src/video/video.h | 5 +++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/display.c b/src/display.c index 6347074f..3252d675 100644 --- a/src/display.c +++ b/src/display.c @@ -53,6 +53,8 @@ static uint8_t video__int_font[3][0x4000]; int video__current_page; // current visual page int video__strictcolors = 1;// refactor : should be static +extern volatile bool _vid_dirty; + // Video constants -- sourced from AppleWin static const bool bVideoScannerNTSC = true; static const int kHBurstClock = 53; // clock when Color Burst starts @@ -571,6 +573,7 @@ static inline void _plot_lores(uint8_t **d, const uint32_t val) { #ifdef INTERFACE_CLASSIC void video_plotchar( int x, int y, int scheme, uint8_t c ) { + _vid_dirty = true; uint8_t *d; uint8_t *s; @@ -653,11 +656,13 @@ static inline void _plot_character(const unsigned int font_off, uint8_t *fb_ptr) static inline void _plot_character0(uint16_t ea, uint8_t b) { + _vid_dirty = (video__current_page == 0); _plot_character(b<<7/* *128 */, video__fb1+video__screen_addresses[ea-0x0400]); } static inline void _plot_character1(uint16_t ea, uint8_t b) { + _vid_dirty = (video__current_page == 1); _plot_character(b<<7/* *128 */, video__fb2+video__screen_addresses[ea-0x0800]); } @@ -676,6 +681,7 @@ static inline void _plot_80character(const unsigned int font_off, uint8_t *fb_pt // FIXME TODO NOTE : dup'ing work here? static inline void _plot_80character0(uint16_t ea, uint8_t b) { + _vid_dirty = (video__current_page == 0); b = apple_ii_64k[1][ea]; _plot_80character(b<<6/* *64 */, video__fb1+video__screen_addresses[ea-0x0400]); b = apple_ii_64k[0][ea]; @@ -684,6 +690,7 @@ static inline void _plot_80character0(uint16_t ea, uint8_t b) static inline void _plot_80character1(uint16_t ea, uint8_t b) { + _vid_dirty = (video__current_page == 1); b = apple_ii_64k[1][ea]; _plot_80character(b<<6/* *64 */, video__fb2+video__screen_addresses[ea-0x0800]); b = apple_ii_64k[0][ea]; @@ -718,11 +725,13 @@ static inline void _plot_block(const uint32_t val, uint8_t *fb_ptr) { /* plot lores block first page */ static inline void _plot_block0(uint16_t ea, uint8_t b) { + _vid_dirty = (video__current_page == 0); _plot_block(b, video__fb1+video__screen_addresses[ea-0x0400]); } static inline void _plot_block1(uint16_t ea, uint8_t b) { + _vid_dirty = (video__current_page == 1); _plot_block(b, video__fb2+video__screen_addresses[ea-0x0800]); } @@ -803,6 +812,7 @@ static inline void _plot_dhires_pixels(uint8_t idx, uint8_t *fb_ptr) { // PlotDHires static inline void _plot_dhires(uint16_t base, uint16_t ea, uint8_t *fb_base) { + _vid_dirty = true; ea &= ~0x1; uint16_t memoff = ea - base; @@ -927,6 +937,7 @@ static inline void _plot_hires_pixels(uint8_t *dst, const uint8_t *src) { // PlotByte static inline void _plot_hires(uint16_t ea, uint8_t b, bool is_even, uint8_t *fb_ptr) { + uint8_t _buf[DYNAMIC_SZ] = { 0 }; uint8_t *color_buf = (uint8_t *)_buf; // <--- work around for -Wstrict-aliasing uint8_t *apple2_vmem = (uint8_t *)apple_ii_64k[0]; @@ -1025,8 +1036,10 @@ static inline void _draw_hires_graphics(uint16_t ea, uint8_t b, bool is_even, ui uint8_t *fb_base = NULL; if (page) { off -= 0x2000; + _vid_dirty = (video__current_page == 1); fb_base = video__fb2; } else { + _vid_dirty = (video__current_page == 0); fb_base = video__fb1; } _plot_hires(ea, b, is_even, fb_base+video__screen_addresses[off]); @@ -1080,6 +1093,10 @@ GLUE_C_WRITE(video__write_2e_odd1_mixed) _draw_hires_graphics(ea, b, /*even*/false, 1, (SS_TEXT|SS_MIXED)); } +bool video_dirty(void) { + return _vid_dirty; +} + void video_redraw(void) { // temporarily reset softswitches diff --git a/src/timing.c b/src/timing.c index bb50f813..c3280443 100644 --- a/src/timing.c +++ b/src/timing.c @@ -47,6 +47,7 @@ static bool alt_speed_enabled = false; double cpu_scale_factor = 1.0; double cpu_altscale_factor = 1.0; +bool auto_adjust_speed = true; int gc_cycles_timer_0 = 0; int gc_cycles_timer_1 = 0; @@ -54,7 +55,7 @@ int gc_cycles_timer_1 = 0; volatile uint8_t emul_reinitialize = 0; pthread_t cpu_thread_id = 0; -static unsigned int g_nCyclesExecuted; // # of cycles executed up to last IO access +static unsigned int g_nCyclesExecuted = 0; // # of cycles executed up to last IO access // ----------------------------------------------------------------------------- @@ -245,21 +246,24 @@ void *cpu_thread(void *dummyptr) { #if DEBUG_TIMING dbg_cycles_executed += cpu65_cycle_count; #endif -#ifdef AUDIO_ENABLED unsigned int uActualCyclesExecuted = cpu65_cycle_count; g_dwCyclesThisFrame += uActualCyclesExecuted; +#ifdef AUDIO_ENABLED MB_UpdateCycles(uActualCyclesExecuted); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below) +#endif // N.B.: IO calls that depend on accurate timing will update g_nCyclesExecuted - const unsigned int nRemainingCycles = uActualCyclesExecuted - g_nCyclesExecuted; + const int nRemainingCycles = uActualCyclesExecuted - g_nCyclesExecuted; + assert(nRemainingCycles >= 0); g_nCumulativeCycles += nRemainingCycles; if (!g_bFullSpeed) { +#ifdef AUDIO_ENABLED SpkrUpdate(uActualCyclesExecuted); // play audio - } #endif + } if (g_dwCyclesThisFrame >= dwClksPerFrame) { @@ -274,6 +278,28 @@ void *cpu_thread(void *dummyptr) { pthread_mutex_unlock(&interface_mutex); // -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj + if (auto_adjust_speed) { + deltat = timespec_diff(disk6.motor_time, tj, &negative); + assert(!negative); + if (!g_bFullSpeed && +#ifdef AUDIO_ENABLED + !Spkr_IsActive() && +#endif + !video_dirty() && (!disk6.motor_off && (deltat.tv_sec || deltat.tv_nsec > DISK_MOTOR_QUIET_NSECS)) ) + { + TIMING_LOG("auto switching to full speed"); + _switch_to_fullspeed(); + } else if (g_bFullSpeed && ( +#ifdef AUDIO_ENABLED + Spkr_IsActive() || +#endif + video_dirty() || (disk6.motor_off && (deltat.tv_sec || deltat.tv_nsec > DISK_MOTOR_QUIET_NSECS))) ) + { + TIMING_LOG("auto switching to regular speed"); + _switch_to_regular_speed(alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor); + } + } + if (!g_bFullSpeed) { deltat = timespec_diff(ti, tj, &negative); assert(!negative); diff --git a/src/timing.h b/src/timing.h index 1b60c0bc..c63269ff 100644 --- a/src/timing.h +++ b/src/timing.h @@ -45,6 +45,7 @@ extern int g_nCpuCyclesFeedback; extern double cpu_scale_factor; extern double cpu_altscale_factor; +extern bool auto_adjust_speed; extern pthread_t cpu_thread_id; diff --git a/src/video/glvideo.c b/src/video/glvideo.c index 1036c8c6..04f37aba 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -34,6 +34,8 @@ enum { bool safe_to_do_opengl_logging = false; +volatile bool _vid_dirty = true; + static int windowWidth = SCANWIDTH*1.5; static int windowHeight = SCANHEIGHT*1.5; @@ -649,6 +651,8 @@ static void gldriver_render(void) { // Draw the CRT object glDrawElements(GL_TRIANGLES, crtNumElements, crtElementType, 0); + _vid_dirty = false; + #if USE_GLUT glutSwapBuffers(); #endif diff --git a/src/video/video.h b/src/video/video.h index b728274b..e8797124 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -116,6 +116,11 @@ void video_plotchar(int row, int col, int color, uint8_t code); */ const uint8_t * const video_current_framebuffer(); +/* + * True if anything changed in framebuffer and not yet drawn + */ +bool video_dirty(void); + /* * VBL routines */ From 87a2d081d347bd732328f8a7397aabef76cb276b Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 11 Jan 2015 18:37:57 -0800 Subject: [PATCH 025/615] CPU_TRACING now emits machine cycles --- src/cpu-supp.c | 9 ++++++++- src/cpu.h | 1 + src/test/testcommon.c | 2 +- src/test/testdisk.c | 18 +++++++++--------- src/timing.c | 3 +++ 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/cpu-supp.c b/src/cpu-supp.c index c30c1b83..1b8114c5 100644 --- a/src/cpu-supp.c +++ b/src/cpu-supp.c @@ -788,7 +788,7 @@ GLUE_C_WRITE(cpu65_trace_epilogue) } flags_buf[8] = '\0'; - fprintf(cpu_trace_fp, " %s EA:%04X", flags_buf, cpu65_ea); + fprintf(cpu_trace_fp, " %s CYC:%u EA:%04X", flags_buf, cpu65_opcycles, cpu65_ea); char fmt[64]; sprintf(fmt, " %s %s", opcodes_65c02[cpu65_opcode].mnemonic, disasm_templates[opcodes_65c02[cpu65_opcode].mode]); @@ -829,6 +829,13 @@ GLUE_C_WRITE(cpu65_trace_epilogue) fflush(cpu_trace_fp); } +void cpu65_trace_checkpoint(void) { + if (cpu_trace_fp) { + fprintf(cpu_trace_fp, "---TOTAL CYC:%lu\n",g_nCumulativeCycles); + fflush(cpu_trace_fp); + } +} + # if RESET_VM_TRACING # define VM_TRACING 1 # endif diff --git a/src/cpu.h b/src/cpu.h index 783e507f..caecb240 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -64,6 +64,7 @@ extern int16_t cpu65_cycles_to_execute; void cpu65_trace_begin(const char *trace_file); void cpu65_trace_end(void); void cpu65_trace_toggle(const char *trace_file); +void cpu65_trace_checkpoint(void); #endif #endif /* !__ASSEMBLER__ */ diff --git a/src/test/testcommon.c b/src/test/testcommon.c index 1375cac4..ea91d8b3 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -132,7 +132,7 @@ void test_common_init(bool do_cputhread) { pthread_create(&cpu_thread_id, NULL, (void *) &cpu_thread, (void *)NULL); c_debugger_set_watchpoint(WATCHPOINT_ADDR); if (is_headless) { - c_debugger_set_timeout(10); + c_debugger_set_timeout(15); } else { fprintf(stderr, "NOTE : RUNNING WITH DISPLAY\n"); fprintf(stderr, "Will spinloop on failed tests for debugger intervention\n"); diff --git a/src/test/testdisk.c b/src/test/testdisk.c index 6e166ebf..7dd4106b 100644 --- a/src/test/testdisk.c +++ b/src/test/testdisk.c @@ -237,8 +237,8 @@ TEST test_boot_disk_cputrace() { } #endif -#define EXPECTED_CPUTRACE_HELLO_FILE_SIZE 107500760 -#define EXPECTED_CPUTRACE_HELLO_SHA "CAF2507BAB937488F85377528041705114F06CA4" +#define EXPECTED_CPUTRACE_HELLO_FILE_SIZE 118664420 +#define EXPECTED_CPUTRACE_HELLO_SHA "C01DDA6AE63A2FEA0CE8DA3A3B258F96AE8BA79B" TEST test_cputrace_hello_dsk() { test_setup_boot_disk(BLANK_DSK, 0); @@ -287,8 +287,8 @@ TEST test_cputrace_hello_dsk() { PASS(); } -#define EXPECTED_CPUTRACE_HELLO_NIB_FILE_SIZE 12887880 -#define EXPECTED_CPUTRACE_HELLO_NIB_SHA "CE14642D70BA42B214C22BFB460F00AA54C8BB5C" +#define EXPECTED_CPUTRACE_HELLO_NIB_FILE_SIZE 14612543 +#define EXPECTED_CPUTRACE_HELLO_NIB_SHA "2D494B4302CC6E3753D7AB50B587C03C7E05C93A" TEST test_cputrace_hello_nib() { test_setup_boot_disk(BLANK_NIB, 0); @@ -337,8 +337,8 @@ TEST test_cputrace_hello_nib() { PASS(); } -#define EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE EXPECTED_CPUTRACE_HELLO_FILE_SIZE -#define EXPECTED_CPUTRACE_HELLO_PO_SHA EXPECTED_CPUTRACE_HELLO_SHA +#define EXPECTED_CPUTRACE_HELLO_PO_FILE_SIZE 118680586 +#define EXPECTED_CPUTRACE_HELLO_PO_SHA "716D8D515876C138B7F3D8F078F05684C801D707" TEST test_cputrace_hello_po() { test_setup_boot_disk(BLANK_PO, 0); @@ -388,7 +388,7 @@ TEST test_cputrace_hello_po() { } #define EXPECTED_VM_TRACE_FILE_SIZE 2830792 -#define EXPECTED_VM_TRACE_SHA "0659556B878848A6421D93057F18B3FB518A7D76" +#define EXPECTED_VM_TRACE_SHA "E3AA4EBEACF9053D619E115F6AEB454A8939BFB4" TEST test_boot_disk_vmtrace() { char *homedir = getenv("HOME"); char *disk = NULL; @@ -434,7 +434,7 @@ TEST test_boot_disk_vmtrace() { } #define EXPECTED_VM_TRACE_NIB_FILE_SIZE 2930056 -#define EXPECTED_VM_TRACE_NIB_SHA "9C1B64255B1946011FAAF5DF53C24114401485EE" +#define EXPECTED_VM_TRACE_NIB_SHA "D60DAE2F3AA4002678457F6D16FC8A25FA14C10E" TEST test_boot_disk_vmtrace_nib() { test_setup_boot_disk(BLANK_NIB, 0); @@ -482,7 +482,7 @@ TEST test_boot_disk_vmtrace_nib() { } #define EXPECTED_VM_TRACE_PO_FILE_SIZE EXPECTED_VM_TRACE_FILE_SIZE -#define EXPECTED_VM_TRACE_PO_SHA "23236C80A9CC38E75BB27F2E70359234B6B8D4DA" +#define EXPECTED_VM_TRACE_PO_SHA "DA200BE91FD8D6D09E551A19ED0445F985898C16" TEST test_boot_disk_vmtrace_po() { test_setup_boot_disk(BLANK_PO, 0); diff --git a/src/timing.c b/src/timing.c index c3280443..d616ac2b 100644 --- a/src/timing.c +++ b/src/timing.c @@ -257,6 +257,9 @@ void *cpu_thread(void *dummyptr) { const int nRemainingCycles = uActualCyclesExecuted - g_nCyclesExecuted; assert(nRemainingCycles >= 0); g_nCumulativeCycles += nRemainingCycles; +#if CPU_TRACING + cpu65_trace_checkpoint(); +#endif if (!g_bFullSpeed) { From 882eae4818231d7e9bf20053e756b655fd3a5b32 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Wed, 14 Jan 2015 23:17:50 -0800 Subject: [PATCH 026/615] Ugh, GLUT idle func was chewing up CPU - ugh, why is the millis parameter to glutTimerFunc() an int?! 1000ms/60Hz = 17ms --- src/video/glvideo.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/video/glvideo.c b/src/video/glvideo.c index 04f37aba..4e040433 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -585,9 +585,10 @@ static void gldriver_shutdown(void) { // update, render, reshape // #if USE_GLUT -static void gldriver_update(void) { +static void gldriver_update(int unused) { c_keys_handle_input(-1, 0, 0); glutPostRedisplay(); + glutTimerFunc(17, gldriver_update, 0); } #endif @@ -707,7 +708,7 @@ static void gldriver_init_glut(GLuint fbo) { gldriver_init_common(); - glutIdleFunc(gldriver_update); + glutTimerFunc(16, gldriver_update, 0); glutDisplayFunc(gldriver_render); glutReshapeFunc(gldriver_reshape); From b4f91661d7ea91e7b3e0aeeeec8b33736e7d4483 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Wed, 14 Jan 2015 23:24:51 -0800 Subject: [PATCH 027/615] don't leak a buffer - thank you, valgrind --- src/prefs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/prefs.c b/src/prefs.c index 2a8f0ac5..2be0ca32 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -311,6 +311,8 @@ void load_settings(void) } } + FREE(buffer); + fclose(config_file); } } From 191a1b7395bb9de13fab75dd70912f13a2ce8359 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Fri, 16 Jan 2015 21:10:45 -0800 Subject: [PATCH 028/615] Fix Mac builds --- Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj | 10 ++++++++++ src/common.h | 4 ++++ src/darwin-shim.c | 1 - src/test/testcommon.c | 4 ---- src/timing.c | 3 --- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj b/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj index 700e781f..52c84f5f 100644 --- a/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj +++ b/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 4A2636F919FDEDB700DBFB00 /* Apple2Mac.help in Resources */ = {isa = PBXBuildFile; fileRef = 4A2636F819FDEDB700DBFB00 /* Apple2Mac.help */; }; + 4A61119C1A6A1DE60035F7DE /* blank.po.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4A61119B1A6A1DE60035F7DE /* blank.po.gz */; }; + 4A61119D1A6A1DE60035F7DE /* blank.po.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4A61119B1A6A1DE60035F7DE /* blank.po.gz */; }; + 4A6111A31A6A1DFC0035F7DE /* flapple140.po.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4A6111A21A6A1DFC0035F7DE /* flapple140.po.gz */; }; 4A69C1801A33D6D7001579EF /* images in Resources */ = {isa = PBXBuildFile; fileRef = 4A69C17F1A33D6D7001579EF /* images */; }; 4A69C1921A33DB90001579EF /* DDHidLib.framework in Copy Files (1 item) */ = {isa = PBXBuildFile; fileRef = 77C2796F1A1047AF000FE33F /* DDHidLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4AC7A76D19ECC3FB00BCD457 /* EmulatorWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC7A76C19ECC3FB00BCD457 /* EmulatorWindow.m */; }; @@ -274,6 +277,8 @@ /* Begin PBXFileReference section */ 4A2636F819FDEDB700DBFB00 /* Apple2Mac.help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Apple2Mac.help; sourceTree = ""; }; + 4A61119B1A6A1DE60035F7DE /* blank.po.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = blank.po.gz; sourceTree = ""; }; + 4A6111A21A6A1DFC0035F7DE /* flapple140.po.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = flapple140.po.gz; sourceTree = ""; }; 4A69C17F1A33D6D7001579EF /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; name = images; path = ../../images; sourceTree = ""; }; 4AC7A76B19ECC3FB00BCD457 /* EmulatorWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EmulatorWindow.h; path = Classes/OSX/EmulatorWindow.h; sourceTree = ""; }; 4AC7A76C19ECC3FB00BCD457 /* EmulatorWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EmulatorWindow.m; path = Classes/OSX/EmulatorWindow.m; sourceTree = ""; }; @@ -459,6 +464,8 @@ 4ADC522F19E8D3F600186B36 /* disks */ = { isa = PBXGroup; children = ( + 4A6111A21A6A1DFC0035F7DE /* flapple140.po.gz */, + 4A61119B1A6A1DE60035F7DE /* blank.po.gz */, 4ADC523019E8D3F600186B36 /* blank.dsk.gz */, 4ADC523119E8D3F600186B36 /* blank.nib.gz */, 4ADC523219E8D3F600186B36 /* etc.dsk.gz */, @@ -929,6 +936,7 @@ 4AD4FEB91A52464F00F958EC /* MainMenu-Test.xib in Resources */, 4AD4FEBA1A52464F00F958EC /* InfoPlist.strings in Resources */, 4AD4FEBB1A52464F00F958EC /* Images.xcassets in Resources */, + 4A61119D1A6A1DE60035F7DE /* blank.po.gz in Resources */, 4AD4FEBC1A52464F00F958EC /* speedtest.txt in Resources */, 4AD4FEBD1A52464F00F958EC /* Credits.rtf in Resources */, 4AD4FEBE1A52464F00F958EC /* Basic.vsh in Resources */, @@ -961,7 +969,9 @@ 4A69C1801A33D6D7001579EF /* images in Resources */, 773B3D1A1956885A0085CE5F /* InfoPlist.strings in Resources */, 773B3D281956885A0085CE5F /* Images.xcassets in Resources */, + 4A6111A31A6A1DFC0035F7DE /* flapple140.po.gz in Resources */, 773B3D201956885A0085CE5F /* Credits.rtf in Resources */, + 4A61119C1A6A1DE60035F7DE /* blank.po.gz in Resources */, 4A2636F919FDEDB700DBFB00 /* Apple2Mac.help in Resources */, 779F562919E4FE9E00A6F107 /* Basic.vsh in Resources */, 779F562A19E4FE9E00A6F107 /* Basic.fsh in Resources */, diff --git a/src/common.h b/src/common.h index 11d64a4b..41322c5f 100644 --- a/src/common.h +++ b/src/common.h @@ -54,6 +54,10 @@ #include "uthash.h" #include "zlib-helpers.h" +#ifdef __APPLE__ +#include "darwin-shim.h" +#import +#endif #if VIDEO_OPENGL #include "video_util/glUtil.h" diff --git a/src/darwin-shim.c b/src/darwin-shim.c index bfa2afb6..f37032b6 100644 --- a/src/darwin-shim.c +++ b/src/darwin-shim.c @@ -10,7 +10,6 @@ */ #include "common.h" -#include "darwin-shim.h" #include // Derived from http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x diff --git a/src/test/testcommon.c b/src/test/testcommon.c index ea91d8b3..820c22fa 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -10,10 +10,6 @@ */ #include "testcommon.h" -#ifdef __APPLE__ -#include "darwin-shim.h" -#import -#endif #define TESTBUF_SZ 1024 diff --git a/src/timing.c b/src/timing.c index d616ac2b..9f4956a9 100644 --- a/src/timing.c +++ b/src/timing.c @@ -17,9 +17,6 @@ */ #include "common.h" -#ifdef __APPLE__ -#include "darwin-shim.h" -#endif #define EXECUTION_PERIOD_NSECS 1000000 // AppleWin: nExecutionPeriodUsec From 30b8f308c6e4af28d7f824b42dd8a44cc1e38133 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 18 Jan 2015 11:06:02 -0800 Subject: [PATCH 029/615] Faster keyboard read --- src/vm.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vm.c b/src/vm.c index 031d4f95..c618cf42 100644 --- a/src/vm.c +++ b/src/vm.c @@ -137,10 +137,7 @@ GLUE_C_WRITE(write_unmapped_softswitch) // ... } -GLUE_C_READ(read_keyboard) -{ - return apple_ii_64k[0][0xC000]; -} +GLUE_FIXED_READ(read_keyboard,apple_ii_64k); GLUE_C_READ(read_keyboard_strobe) { @@ -879,7 +876,7 @@ GLUE_C_READ(iie_check_vbl) { bool vbl_bar = false; video_scanner_get_address(&vbl_bar, cpu65_cycle_count); - uint8_t key = c_read_keyboard(0xC000); + uint8_t key = apple_ii_64k[0][0xC000]; return (key & ~0x80) | (vbl_bar ? 0x80 : 0x00); } From b8c44b81abb7d83fe41e07cae558b30042255b9a Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 18 Jan 2015 11:10:16 -0800 Subject: [PATCH 030/615] Simple FPS logging --- src/video/glvideo.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/video/glvideo.c b/src/video/glvideo.c index 4e040433..35cedeef 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -586,6 +586,23 @@ static void gldriver_shutdown(void) { // #if USE_GLUT static void gldriver_update(int unused) { +#if DEBUG_GL + static uint32_t prevCount = 0; + static uint32_t idleCount = 0; + + idleCount++; + + static struct timespec prev = { 0 }; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + if (now.tv_sec != prev.tv_sec) { + LOG("gldriver_update() : %u", idleCount-prevCount); + prevCount = idleCount; + prev = now; + } +#endif + c_keys_handle_input(-1, 0, 0); glutPostRedisplay(); glutTimerFunc(17, gldriver_update, 0); From b4516b49df1d470f59fe207135ef8f6552e394f4 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Fri, 23 Jan 2015 23:34:03 -0800 Subject: [PATCH 031/615] Add general-use branch-prediction macros --- src/common.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common.h b/src/common.h index 41322c5f..fc56c1cb 100644 --- a/src/common.h +++ b/src/common.h @@ -203,4 +203,8 @@ extern FILE *error_log; } while (0) #endif +// branch prediction +#define LIKELY(x) __builtin_expect((x), true) +#define UNLIKELY(x) __builtin_expect((x), false) + #endif // whole file From 780905155abc969a36666480836625c73c2ab686 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Fri, 23 Jan 2015 23:37:43 -0800 Subject: [PATCH 032/615] Only render if necessary --- src/display.c | 13 +++++-------- src/video/glvideo.c | 32 +++++++++++++++++--------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/display.c b/src/display.c index 3252d675..e1ed1481 100644 --- a/src/display.c +++ b/src/display.c @@ -633,6 +633,7 @@ void video_shutdown(void) { ------------------------------------------------------------------------- */ void video_setpage(int p) { + _vid_dirty = true; video__current_page = p; } @@ -643,6 +644,7 @@ const uint8_t * const video_current_framebuffer() { // ---------------------------------------------------------------------------- static inline void _plot_character(const unsigned int font_off, uint8_t *fb_ptr) { + _vid_dirty = true; uint8_t *font_ptr = video__wider_font+font_off; _plot_char40(/*dst*/&fb_ptr, /*src*/&font_ptr); _plot_char40(/*dst*/&fb_ptr, /*src*/&font_ptr); @@ -656,17 +658,16 @@ static inline void _plot_character(const unsigned int font_off, uint8_t *fb_ptr) static inline void _plot_character0(uint16_t ea, uint8_t b) { - _vid_dirty = (video__current_page == 0); _plot_character(b<<7/* *128 */, video__fb1+video__screen_addresses[ea-0x0400]); } static inline void _plot_character1(uint16_t ea, uint8_t b) { - _vid_dirty = (video__current_page == 1); _plot_character(b<<7/* *128 */, video__fb2+video__screen_addresses[ea-0x0800]); } static inline void _plot_80character(const unsigned int font_off, uint8_t *fb_ptr) { + _vid_dirty = true; uint8_t *font_ptr = video__font+font_off; _plot_char80(/*dst*/&fb_ptr, /*src*/&font_ptr); _plot_char80(/*dst*/&fb_ptr, /*src*/&font_ptr); @@ -681,7 +682,6 @@ static inline void _plot_80character(const unsigned int font_off, uint8_t *fb_pt // FIXME TODO NOTE : dup'ing work here? static inline void _plot_80character0(uint16_t ea, uint8_t b) { - _vid_dirty = (video__current_page == 0); b = apple_ii_64k[1][ea]; _plot_80character(b<<6/* *64 */, video__fb1+video__screen_addresses[ea-0x0400]); b = apple_ii_64k[0][ea]; @@ -690,7 +690,6 @@ static inline void _plot_80character0(uint16_t ea, uint8_t b) static inline void _plot_80character1(uint16_t ea, uint8_t b) { - _vid_dirty = (video__current_page == 1); b = apple_ii_64k[1][ea]; _plot_80character(b<<6/* *64 */, video__fb2+video__screen_addresses[ea-0x0800]); b = apple_ii_64k[0][ea]; @@ -698,6 +697,7 @@ static inline void _plot_80character1(uint16_t ea, uint8_t b) } static inline void _plot_block(const uint32_t val, uint8_t *fb_ptr) { + _vid_dirty = true; uint8_t color = (val & 0x0F) << 4; uint32_t val32 = (color << 24) | (color << 16) | (color << 8) | color; @@ -725,13 +725,11 @@ static inline void _plot_block(const uint32_t val, uint8_t *fb_ptr) { /* plot lores block first page */ static inline void _plot_block0(uint16_t ea, uint8_t b) { - _vid_dirty = (video__current_page == 0); _plot_block(b, video__fb1+video__screen_addresses[ea-0x0400]); } static inline void _plot_block1(uint16_t ea, uint8_t b) { - _vid_dirty = (video__current_page == 1); _plot_block(b, video__fb2+video__screen_addresses[ea-0x0800]); } @@ -1032,14 +1030,13 @@ static inline void _draw_hires_graphics(uint16_t ea, uint8_t b, bool is_even, ui return; } + _vid_dirty = true; uint16_t off = ea - 0x2000; uint8_t *fb_base = NULL; if (page) { off -= 0x2000; - _vid_dirty = (video__current_page == 1); fb_base = video__fb2; } else { - _vid_dirty = (video__current_page == 0); fb_base = video__fb1; } _plot_hires(ea, b, is_even, fb_base+video__screen_addresses[off]); diff --git a/src/video/glvideo.c b/src/video/glvideo.c index 35cedeef..2f3b1c94 100644 --- a/src/video/glvideo.c +++ b/src/video/glvideo.c @@ -604,12 +604,19 @@ static void gldriver_update(int unused) { #endif c_keys_handle_input(-1, 0, 0); - glutPostRedisplay(); + if (_vid_dirty) { + glutPostRedisplay(); + } glutTimerFunc(17, gldriver_update, 0); } #endif static void gldriver_render(void) { + const uint8_t * const fb = video_current_framebuffer(); + if (UNLIKELY(!fb)) { + return; + } + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #if PERSPECTIVE @@ -637,22 +644,17 @@ static void gldriver_render(void) { // that we calculated above glUniformMatrix4fv(uniformMVPIdx, 1, GL_FALSE, mvp); - // Update texture from Apple //e internal framebuffer - const uint8_t * const fb = video_current_framebuffer(); - uint8_t index; -#warning FIXME TODO use memcpy ... or don't use indexed color so that we don't need to do this copy? + // Update texture from indexed-color Apple //e internal framebuffer unsigned int count = SCANWIDTH * SCANHEIGHT; char pixels[SCANWIDTH * SCANHEIGHT * 4]; - if (fb != NULL) { - for (unsigned int i=0, j=0; i Date: Sat, 31 Jan 2015 13:57:10 -0800 Subject: [PATCH 033/615] Refactor speaker system to prevent audio glitches and to support CPU automatic speed switching Squashed commit of the following: REFACTOR : fix comments, logging, and rename some variables REFACTOR : fix up some commentary, clean deadc0de REFACTOR : mostly fix all the audio glitches - amplitudes of samples are gradually shifted to zero when speaker has fallen silent - simplifies speaker state machine - fullspeed mode only enqueues quiet samples REFACTOR : fix up a number of other functions and comments REFACTOR : clean up code to submit normal speed wave buffer to OpenAL Move some initializations to the cpu_thread() REFACTOR : properly reset the speaker cycles access counter so we don't underflow and assert REFACTOR : should never get a split buffer from our soundcore implementation Fix warning from gcc ... static array size needs to be computed from integer values REFACTOR : gcc (but not clang) complains about these, so just make them preprocessor defines REFACTOR : use unsigned long long because we don't actually care that this counter is 64bit REFACTOR : remainder_buffer and miscellaneous tweaks - Adds implementation commentary to document remainder_buffer purpose - Also adds sample average for square wave boundary in case where remainder_buffer not used (whole-sample boundary) - Variable renaming and code shuffling REFACTOR : do not dynamically alloc remainder buffer - Never attribute to cache-coherancy bugs what is a simple thread race =P REFACTOR : comments and whitespace REFACTOR : rename public speaker API functions REFACTOR : clean up public speaker API REFACTOR : tabs to spaces REFACTOR : moar deadc0de clean up and renaming REFACTOR : remove deadc0de paths from soundcore REFACTOR : fully excise soundtype stuff now that we only support soundcard output Move a file static to function scope REFACTOR : rename more variables and remove deadc0de REFACTOR : samples_buffer naming and change to explict int16_t REFACTOR : removed deadc0de and shuffled code locations REFACTOR : remainder buffer naming and clarify type REFACTOR : move joystick timing to VM module and remove header visibility REFACTOR : clarify speaker variable name REFACTOR : clarify cycle counting codepaths REFACTOR : VBL/timing interfaces - eliminates passing around a common global REFACTOR : names and comments HACK around volume issue REFACTOR : rename speaker feedback variable REFACTOR : rename global total cycle count REFACTOR : rename a constant Fix test builds REFACTOR: rename to is_fullspeed REFACTOR : local variable naming changes REFACTOR : migrate cycle timing variables to correct location and remove header visibility Allow fullspeed codepath to update speaker REFACTOR : remove deadc0de paths in prep for cleanup REFACTOR : speaker now manages its own VM entry point --- configure.ac | 2 +- src/audio/AY8910.c | 8 +- src/audio/mockingboard.c | 54 +- src/audio/mockingboard.h | 2 +- src/audio/soundcore-openal.c | 7 +- src/audio/soundcore.c | 833 ++++------------- src/audio/soundcore.h | 40 +- src/audio/speaker.c | 1664 ++++++++-------------------------- src/audio/speaker.h | 53 +- src/cpu-supp.c | 4 +- src/cpu.h | 1 - src/disk.c | 14 +- src/display.c | 14 +- src/interface.c | 2 +- src/meta/debugger.c | 6 +- src/misc.c | 21 +- src/test/testcommon.c | 4 +- src/test/testcpu.c | 1 + src/timing.c | 228 ++--- src/timing.h | 64 +- src/video/video.h | 6 +- src/vm.c | 112 ++- 22 files changed, 861 insertions(+), 2279 deletions(-) diff --git a/configure.ac b/configure.ac index 4249485f..1e860223 100644 --- a/configure.ac +++ b/configure.ac @@ -226,7 +226,7 @@ AC_ARG_ENABLE([audio], AS_HELP_STRING([--disable-audio], [Disable emulator audio dnl found OpenAL ... AC_DEFINE(AUDIO_OPENAL, 1, [Enable OpenAL audio output]) AC_DEFINE(AUDIO_ENABLED, 1, [Enable sound module]) - AUDIO_GLUE_C="src/audio/mockingboard.c" + AUDIO_GLUE_C="src/audio/speaker.c src/audio/mockingboard.c" AUDIO_O="src/audio/soundcore.o src/audio/soundcore-openal.o src/audio/speaker.o src/audio/win-shim.o src/audio/alhelpers.o src/audio/mockingboard.o src/audio/AY8910.o" ], [ AC_MSG_WARN([Could not find OpenAL libraries, sound will be disabled]) diff --git a/src/audio/AY8910.c b/src/audio/AY8910.c index 7437ac13..e4bf856c 100644 --- a/src/audio/AY8910.c +++ b/src/audio/AY8910.c @@ -137,7 +137,7 @@ void CAY8910_init(CAY8910 *_this) _this->env_first = 1; _this->env_rev = 0; _this->env_counter = 15; - //m_fCurrentCLK_AY8910 = g_fCurrentCLK6502; -- believe this is handled by an initial call to SetCLK() + //m_fCurrentCLK_AY8910 = cycles_persec_target; -- believe this is handled by an initial call to SetCLK() }; @@ -983,7 +983,7 @@ void SetCLK(double CLK) // AY8910 interface #ifndef APPLE2IX -#include "CPU.h" // For g_nCumulativeCycles +#include "CPU.h" // For cycles_count_total #endif static CAY8910 g_AY8910[MAX_8910]; @@ -996,7 +996,7 @@ static unsigned __int64 g_uLastCumulativeCycles = 0; void _AYWriteReg(int chip, int r, int v) { - libspectrum_dword uOffset = (libspectrum_dword) (g_nCumulativeCycles - g_uLastCumulativeCycles); + libspectrum_dword uOffset = (libspectrum_dword) (cycles_count_total - g_uLastCumulativeCycles); sound_ay_write(&g_AY8910[chip], r, v, uOffset); } @@ -1008,7 +1008,7 @@ void AY8910_reset(int chip) void AY8910UpdateSetCycles() { - g_uLastCumulativeCycles = g_nCumulativeCycles; + g_uLastCumulativeCycles = cycles_count_total; } void AY8910Update(int chip, INT16** buffer, int nNumSamples) diff --git a/src/audio/mockingboard.c b/src/audio/mockingboard.c index c3b2b5d2..5469cbc8 100644 --- a/src/audio/mockingboard.c +++ b/src/audio/mockingboard.c @@ -822,13 +822,13 @@ static void MB_Update() return; #endif - if (g_bFullSpeed) + if (is_fullspeed) { // Keep AY reg writes relative to the current 'frame' // - Required for Ultima3: // . Tune ends - // . g_bFullSpeed:=true (disk-spinning) for ~50 frames - // . U3 sets AY_ENABLE:=0xFF (as a side-effect, this sets g_bFullSpeed:=false) + // . is_fullspeed:=true (disk-spinning) for ~50 frames + // . U3 sets AY_ENABLE:=0xFF (as a side-effect, this sets is_fullspeed:=false) // o Without this, the write to AY_ENABLE gets ignored (since AY8910's /g_uLastCumulativeCycles/ was last set 50 frame ago) AY8910UpdateSetCycles(); @@ -844,12 +844,12 @@ static void MB_Update() { if(!g_nMB_InActiveCycleCount) { - g_nMB_InActiveCycleCount = g_nCumulativeCycles; + g_nMB_InActiveCycleCount = cycles_count_total; } #ifdef APPLE2IX - else if(g_nCumulativeCycles - g_nMB_InActiveCycleCount > (uint64_t)g_fCurrentCLK6502/10) + else if(cycles_count_total - g_nMB_InActiveCycleCount > (uint64_t)cycles_persec_target/10) #else - else if(g_nCumulativeCycles - g_nMB_InActiveCycleCount > (unsigned __int64)g_fCurrentCLK6502/10) + else if(cycles_count_total - g_nMB_InActiveCycleCount > (unsigned __int64)cycles_persec_target/10) #endif { // After 0.1 sec of Apple time, assume MB is not active @@ -872,7 +872,7 @@ static void MB_Update() const double n6522TimerPeriod = MB_GetFramePeriod(); - const double nIrqFreq = g_fCurrentCLK6502 / n6522TimerPeriod + 0.5; // Round-up + const double nIrqFreq = cycles_persec_target / n6522TimerPeriod + 0.5; // Round-up const int nNumSamplesPerPeriod = (int) ((double)SAMPLE_RATE / nIrqFreq); // Eg. For 60Hz this is 735 int nNumSamples = nNumSamplesPerPeriod + nNumSamplesError; // Apply correction if(nNumSamples <= 0) @@ -968,7 +968,7 @@ static void MB_Update() if (dbg_print != now) { dbg_print = now; - LOG("g_nCpuCyclesFeedback:%d nNumSamplesError:%d n6522TimerPeriod:%f nIrqFreq:%f nNumSamplesPerPeriod:%d nNumSamples:%d nBytesRemaining:%d ", g_nCpuCyclesFeedback, nNumSamplesError, n6522TimerPeriod, nIrqFreq, nNumSamplesPerPeriod, nNumSamples, nBytesRemaining); + LOG("cycles_speaker_feedback:%d nNumSamplesError:%d n6522TimerPeriod:%f nIrqFreq:%f nNumSamplesPerPeriod:%d nNumSamples:%d nBytesRemaining:%d ", cycles_speaker_feedback, nNumSamplesError, n6522TimerPeriod, nIrqFreq, nNumSamplesPerPeriod, nNumSamples, nBytesRemaining); } #endif @@ -1611,7 +1611,7 @@ void MB_Initialize() #endif } - AY8910_InitAll((int)g_fCurrentCLK6502, SAMPLE_RATE); + AY8910_InitAll((int)cycles_persec_target, SAMPLE_RATE); LogFileOutput("MB_Initialize: AY8910_InitAll()\n"); for(i=0; i= 0x10000) { printf("OOPS!!! Mockingboard failed assert!\n"); @@ -2137,6 +2136,11 @@ DWORD MB_GetVolume() void MB_SetVolume(DWORD dwVolume, DWORD dwVolumeMax) { +#ifdef APPLE2IX +#warning TODO FIXME ... why is OpenAL on my Linux box so damn loud?! + dwVolume >>= 2; + dwVolumeMax >>= 2; +#endif MockingboardVoice.dwUserVolume = dwVolume; MockingboardVoice.nVolume = NewVolume(dwVolume, dwVolumeMax); diff --git a/src/audio/mockingboard.h b/src/audio/mockingboard.h index d5ff3f2f..0cd1ac3b 100644 --- a/src/audio/mockingboard.h +++ b/src/audio/mockingboard.h @@ -105,7 +105,7 @@ void MB_Mute(); void MB_Demute(); void MB_StartOfCpuExecute(); void MB_EndOfVideoFrame(); -void MB_UpdateCycles(ULONG uExecutedCycles); +void MB_UpdateCycles(void); SS_CARDTYPE MB_GetSoundcardType(); void MB_SetSoundcardType(SS_CARDTYPE NewSoundcardType); double MB_GetFramePeriod(); diff --git a/src/audio/soundcore-openal.c b/src/audio/soundcore-openal.c index 1883465e..da30cb76 100644 --- a/src/audio/soundcore-openal.c +++ b/src/audio/soundcore-openal.c @@ -469,7 +469,10 @@ static long ALGetPosition(void *_this, unsigned long *bytes_queued, unsigned lon { ALVoice *voice = (ALVoice*)_this; *bytes_queued = 0; - *unused_write_cursor = 0; + if (unused_write_cursor) + { + *unused_write_cursor = 0; + } ALuint queued = 0; int err = _ALProcessPlayBuffers(voice, &queued); @@ -537,7 +540,7 @@ static long ALBegin(void *_this, unsigned long unused, unsigned long write_bytes static int _ALSubmitBufferToOpenAL(ALVoice *voice) { - int err =0; + int err = 0; ALPlayBuf *node = PlaylistEnqueue(voice, voice->index); if (!node) diff --git a/src/audio/soundcore.c b/src/audio/soundcore.c index b3946678..b5593310 100644 --- a/src/audio/soundcore.c +++ b/src/audio/soundcore.c @@ -28,42 +28,22 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" - -#ifdef APPLE2IX - #include "audio/win-shim.h" -#else -#include "StdAfx.h" -#endif - //----------------------------------------------------------------------------- #define MAX_SOUND_DEVICES 100 -#ifdef APPLE2IX static char **sound_devices = NULL; -#else -static char *sound_devices[MAX_SOUND_DEVICES]; -static GUID sound_device_guid[MAX_SOUND_DEVICES]; -#endif static long num_sound_devices = 0; -#ifdef APPLE2IX LPDIRECTSOUND g_lpDS = NULL; -#else -static LPDIRECTSOUND g_lpDS = NULL; -#endif //------------------------------------- // Used for muting & fading: -#ifdef APPLE2IX #define uMAX_VOICES 66 -#else -static const UINT uMAX_VOICES = 66; // 64 phonemes + spkr + mockingboard -#endif static UINT g_uNumVoices = 0; static VOICE* g_pVoices[uMAX_VOICES] = {NULL}; @@ -73,7 +53,6 @@ static VOICE* g_pSpeakerVoice = NULL; bool g_bDSAvailable = false; -#ifdef APPLE2IX bool g_bDisableDirectSound = false; FILE *g_fh = NULL; @@ -81,511 +60,209 @@ __attribute__((constructor)) static void _init_soundcore() { g_fh = error_log; } -#endif - -//----------------------------------------------------------------------------- - -#ifndef APPLE2IX -static BOOL CALLBACK DSEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext) -{ - int i = num_sound_devices; - if(i == MAX_SOUND_DEVICES) - return TRUE; - if(lpGUID != NULL) - memcpy(&sound_device_guid[i], lpGUID, sizeof (GUID)); - sound_devices[i] = _strdup(lpszDesc); - - if(g_fh) fprintf(g_fh, "%d: %s - %s\n",i,lpszDesc,lpszDrvName); - - num_sound_devices++; - return TRUE; -} -#endif - -//----------------------------------------------------------------------------- - -#ifdef _DEBUG -static char *DirectSound_ErrorText (HRESULT error) -{ - switch( error ) - { - case DSERR_ALLOCATED: - return "Allocated"; - case DSERR_CONTROLUNAVAIL: - return "Control Unavailable"; - case DSERR_INVALIDPARAM: - return "Invalid Parameter"; - case DSERR_INVALIDCALL: - return "Invalid Call"; - case DSERR_GENERIC: - return "Generic"; - case DSERR_PRIOLEVELNEEDED: - return "Priority Level Needed"; - case DSERR_OUTOFMEMORY: - return "Out of Memory"; - case DSERR_BADFORMAT: - return "Bad Format"; - case DSERR_UNSUPPORTED: - return "Unsupported"; - case DSERR_NODRIVER: - return "No Driver"; - case DSERR_ALREADYINITIALIZED: - return "Already Initialized"; - case DSERR_NOAGGREGATION: - return "No Aggregation"; - case DSERR_BUFFERLOST: - return "Buffer Lost"; - case DSERR_OTHERAPPHASPRIO: - return "Other Application Has Priority"; - case DSERR_UNINITIALIZED: - return "Uninitialized"; - case DSERR_NOINTERFACE: - return "No Interface"; - default: - return "Unknown"; - } -} -#endif //----------------------------------------------------------------------------- bool DSGetLock(LPDIRECTSOUNDBUFFER pVoice, DWORD dwOffset, DWORD dwBytes, - SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, - SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1) + SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, + SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1) { - DWORD nStatus = 0; -#ifdef APPLE2IX - HRESULT hr = pVoice->GetStatus(pVoice->_this, &nStatus); -#else - HRESULT hr = pVoice->GetStatus(&nStatus); -#endif - if(hr != DS_OK) - return false; + DWORD nStatus = 0; + HRESULT hr = pVoice->GetStatus(pVoice->_this, &nStatus); + if(hr != DS_OK) + return false; - if(nStatus & DSBSTATUS_BUFFERLOST) - { - do - { -#ifdef APPLE2IX - hr = pVoice->Restore(pVoice->_this); -#else - hr = pVoice->Restore(); -#endif - if(hr == DSERR_BUFFERLOST) - Sleep(10); - } - while(hr != DS_OK); - } + if(nStatus & DSBSTATUS_BUFFERLOST) + { + do + { + hr = pVoice->Restore(pVoice->_this); + if(hr == DSERR_BUFFERLOST) + Sleep(10); + } + while(hr != DS_OK); + } - // Get write only pointer(s) to sound buffer - if(dwBytes == 0) - { -#ifdef APPLE2IX - if(FAILED(hr = pVoice->Lock(pVoice->_this, 0, 0, -#else - if(FAILED(hr = pVoice->Lock(0, 0, -#endif - (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, - (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, - DSBLOCK_ENTIREBUFFER))) - return false; - } - else - { -#ifdef APPLE2IX - if(FAILED(hr = pVoice->Lock(pVoice->_this, dwOffset, dwBytes, -#else - if(FAILED(hr = pVoice->Lock(dwOffset, dwBytes, -#endif - (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, - (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, - 0))) - return false; - } + // Get write only pointer(s) to sound buffer + if(dwBytes == 0) + { + if(FAILED(hr = pVoice->Lock(pVoice->_this, 0, 0, + (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, + (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, + DSBLOCK_ENTIREBUFFER))) + return false; + } + else + { + if(FAILED(hr = pVoice->Lock(pVoice->_this, dwOffset, dwBytes, + (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, + (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, + 0))) + return false; + } - return true; + return true; } //----------------------------------------------------------------------------- HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels) { - WAVEFORMATEX wavfmt; - DSBUFFERDESC dsbdesc; + WAVEFORMATEX wavfmt; + DSBUFFERDESC dsbdesc; - wavfmt.wFormatTag = WAVE_FORMAT_PCM; - wavfmt.nChannels = nChannels; - wavfmt.nSamplesPerSec = nSampleRate; - wavfmt.wBitsPerSample = 16; - wavfmt.nBlockAlign = wavfmt.nChannels==1 ? 2 : 4; - wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * wavfmt.nSamplesPerSec; + wavfmt.wFormatTag = WAVE_FORMAT_PCM; + wavfmt.nChannels = nChannels; + wavfmt.nSamplesPerSec = nSampleRate; + wavfmt.wBitsPerSample = 16; + wavfmt.nBlockAlign = wavfmt.nChannels==1 ? 2 : 4; + wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * wavfmt.nSamplesPerSec; - memset (&dsbdesc, 0, sizeof (dsbdesc)); - dsbdesc.dwSize = sizeof (dsbdesc); - dsbdesc.dwBufferBytes = dwBufferSize; - dsbdesc.lpwfxFormat = &wavfmt; - dsbdesc.dwFlags = dwFlags | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS; + memset (&dsbdesc, 0, sizeof (dsbdesc)); + dsbdesc.dwSize = sizeof (dsbdesc); + dsbdesc.dwBufferBytes = dwBufferSize; + dsbdesc.lpwfxFormat = &wavfmt; + dsbdesc.dwFlags = dwFlags | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS; - // Are buffers released when g_lpDS OR pVoice->lpDSBvoice is released? - // . From DirectX doc: - // "Buffer objects are owned by the device object that created them. When the - // device object is released, all buffers created by that object are also released..." -#ifdef APPLE2IX + // Are buffers released when g_lpDS OR pVoice->lpDSBvoice is released? + // . From DirectX doc: + // "Buffer objects are owned by the device object that created them. When the + // device object is released, all buffers created by that object are also released..." if (pVoice->lpDSBvoice) { g_lpDS->DestroySoundBuffer(&pVoice->lpDSBvoice); //DSReleaseSoundBuffer(pVoice); } - HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, g_lpDS); -#else - HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, NULL); -#endif - if(FAILED(hr)) - return hr; + HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, g_lpDS); + if(FAILED(hr)) + return hr; - // + // - _ASSERT(g_uNumVoices < uMAX_VOICES); - if(g_uNumVoices < uMAX_VOICES) - g_pVoices[g_uNumVoices++] = pVoice; + _ASSERT(g_uNumVoices < uMAX_VOICES); + if(g_uNumVoices < uMAX_VOICES) + g_pVoices[g_uNumVoices++] = pVoice; - if(pVoice->bIsSpeaker) - g_pSpeakerVoice = pVoice; + if(pVoice->bIsSpeaker) + g_pSpeakerVoice = pVoice; - return hr; + return hr; } void DSReleaseSoundBuffer(VOICE* pVoice) { - if(pVoice->bIsSpeaker) - g_pSpeakerVoice = NULL; + if(pVoice->bIsSpeaker) + g_pSpeakerVoice = NULL; - for(UINT i=0; iDestroySoundBuffer(&pVoice->lpDSBvoice); } -#else - SAFE_RELEASE(pVoice->lpDSBvoice); -#endif } //----------------------------------------------------------------------------- bool DSZeroVoiceBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize) { - DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer - SHORT* pDSLockedBuffer; + DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer + SHORT* pDSLockedBuffer; -#ifdef APPLE2IX DWORD argX = 0; - HRESULT hr = Voice->lpDSBvoice->Stop(Voice->lpDSBvoice->_this); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSStop failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } - hr = !DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, &argX); -#else + HRESULT hr = Voice->lpDSBvoice->Stop(Voice->lpDSBvoice->_this); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSStop failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } + hr = !DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, &argX); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } - hr = DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, 0); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + _ASSERT(dwDSLockedBufferSize == dwBufferSize); + memset(pDSLockedBuffer, 0x00, dwDSLockedBufferSize); - _ASSERT(dwDSLockedBufferSize == dwBufferSize); - memset(pDSLockedBuffer, 0x00, dwDSLockedBufferSize); + hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, argX); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } -#ifdef APPLE2IX - hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, argX); -#else - hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + hr = Voice->lpDSBvoice->Play(Voice->lpDSBvoice->_this,0,0,0); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSPlay failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } -#ifdef APPLE2IX - hr = Voice->lpDSBvoice->Play(Voice->lpDSBvoice->_this,0,0,0); -#else - hr = Voice->lpDSBvoice->Play(0,0,DSBPLAY_LOOPING); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSPlay failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } - - return true; + return true; } //----------------------------------------------------------------------------- bool DSZeroVoiceWritableBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize) { - DWORD dwDSLockedBufferSize0=0, dwDSLockedBufferSize1=0; - SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; + DWORD dwDSLockedBufferSize0=0, dwDSLockedBufferSize1=0; + SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; - HRESULT hr = DSGetLock(Voice->lpDSBvoice, - 0, dwBufferSize, - &pDSLockedBuffer0, &dwDSLockedBufferSize0, - &pDSLockedBuffer1, &dwDSLockedBufferSize1); -#ifdef APPLE2IX + HRESULT hr = DSGetLock(Voice->lpDSBvoice, + 0, dwBufferSize, + &pDSLockedBuffer0, &dwDSLockedBufferSize0, + &pDSLockedBuffer1, &dwDSLockedBufferSize1); hr = !hr; -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } - memset(pDSLockedBuffer0, 0x00, dwDSLockedBufferSize0); - if(pDSLockedBuffer1) - memset(pDSLockedBuffer1, 0x00, dwDSLockedBufferSize1); + memset(pDSLockedBuffer0, 0x00, dwDSLockedBufferSize0); + if(pDSLockedBuffer1) + memset(pDSLockedBuffer1, 0x00, dwDSLockedBufferSize1); -#ifdef APPLE2IX - hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#else - hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#endif - (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, + (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } - return true; + return true; } //----------------------------------------------------------------------------- -static bool g_bTimerActive = false; -#ifndef APPLE2IX -static eFADE g_FadeType = FADE_NONE; -static UINT_PTR g_nTimerID = 0; -#endif - -//------------------------------------- - -#ifndef APPLE2IX -static VOID CALLBACK SoundCore_TimerFunc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); -#endif - -#ifndef APPLE2IX -static bool SoundCore_StartTimer() -{ - if(g_bTimerActive) - return true; - - g_nTimerID = SetTimer(NULL, 0, 1, SoundCore_TimerFunc); // 1ms interval - if(g_nTimerID == 0) - { - fprintf(stderr, "Error creating timer\n"); - _ASSERT(0); - return false; - } - - g_bTimerActive = true; - return true; -} -#endif - -static void SoundCore_StopTimer() -{ -#ifdef APPLE2IX - // using our own timing and nanosleep() ... -#else - if(!g_bTimerActive) - return; - - if(KillTimer(NULL, g_nTimerID) == FALSE) - { - fprintf(stderr, "Error killing timer\n"); - _ASSERT(0); - return; - } - - g_bTimerActive = false; -#endif -} - -bool SoundCore_GetTimerState() -{ - return g_bTimerActive; -} - -//------------------------------------- - -// [OLD: Used to fade volume in/out] -// FADE_OUT : Just keep filling speaker soundbuffer with same value -// FADE_IN : Switch to FADE_NONE & StopTimer() - -#ifndef APPLE2IX -static VOID CALLBACK SoundCore_TimerFunc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) -{ - if((g_pSpeakerVoice == NULL) || (g_pSpeakerVoice->bActive == false)) - g_FadeType = FADE_NONE; - - // Timer expired - if(g_FadeType == FADE_NONE) - { - SoundCore_StopTimer(); - return; - } - - // - -#if 1 - if(g_FadeType == FADE_IN) - g_FadeType = FADE_NONE; - else - SpkrUpdate_Timer(); -#else - const LONG nFadeUnit_Fast = (DSBVOLUME_MAX - DSBVOLUME_MIN) / 10; - const LONG nFadeUnit_Slow = (DSBVOLUME_MAX - DSBVOLUME_MIN) / 1000; // Less noisy for 'silence' - - LONG nFadeUnit = g_pSpeakerVoice->bRecentlyActive ? nFadeUnit_Fast : nFadeUnit_Slow; - LONG nFadeVolume = g_pSpeakerVoice->nFadeVolume; - - if(g_FadeType == FADE_IN) - { - if(nFadeVolume == g_pSpeakerVoice->nVolume) - { - g_FadeType = FADE_NONE; - SoundCore_StopTimer(); - return; - } - - nFadeVolume += nFadeUnit; - - if(nFadeVolume > g_pSpeakerVoice->nVolume) - nFadeVolume = g_pSpeakerVoice->nVolume; - } - else // FADE_OUT - { - if(nFadeVolume == DSBVOLUME_MIN) - { - g_FadeType = FADE_NONE; - SoundCore_StopTimer(); - return; - } - - nFadeVolume -= nFadeUnit; - - if(nFadeVolume < DSBVOLUME_MIN) - nFadeVolume = DSBVOLUME_MIN; - } - - g_pSpeakerVoice->nFadeVolume = nFadeVolume; - g_pSpeakerVoice->lpDSBvoice->SetVolume(nFadeVolume); -#endif -} -#endif - -//----------------------------------------------------------------------------- - -#ifndef APPLE2IX -void SoundCore_SetFade(eFADE FadeType) -{ - static int nLastMode = -1; - - if(g_nAppMode == MODE_DEBUG) - return; - - // Fade in/out just for speaker, the others are demuted/muted - if(FadeType != FADE_NONE) - { - for(UINT i=0; iSetVolume() doesn't work without this! - // . See SoundCore_TweakVolumes() - could be this? - if((g_pVoices[i]->bIsSpeaker) && (g_nAppMode != MODE_LOGO) && (nLastMode != MODE_LOGO)) - { - g_pVoices[i]->lpDSBvoice->GetVolume(&g_pVoices[i]->nFadeVolume); - g_FadeType = FadeType; - SoundCore_StartTimer(); - } - else if(FadeType == FADE_OUT) - { - g_pVoices[i]->lpDSBvoice->SetVolume(DSBVOLUME_MIN); - g_pVoices[i]->bMute = true; - } - else // FADE_IN - { - g_pVoices[i]->lpDSBvoice->SetVolume(g_pVoices[i]->nVolume); - g_pVoices[i]->bMute = false; - } - } - } - else // FadeType == FADE_NONE - { - if( (g_FadeType != FADE_NONE) && // Currently fading-in/out - (g_pSpeakerVoice && g_pSpeakerVoice->bActive) ) - { - g_FadeType = FADE_NONE; // TimerFunc will call StopTimer() - g_pSpeakerVoice->lpDSBvoice->SetVolume(g_pSpeakerVoice->nVolume); - } - } - - nLastMode = g_nAppMode; -} -#endif - -//----------------------------------------------------------------------------- - -// If AppleWin started by double-clicking a .dsk, then our window won't have focus when volumes are set (so gets ignored). -// Subsequent setting (to the same volume) will get ignored, as DirectSound thinks that volume is already set. - -#ifndef APPLE2IX -void SoundCore_TweakVolumes() -{ - for (UINT i=0; ilpDSBvoice->SetVolume(g_pVoices[i]->nVolume-1); - g_pVoices[i]->lpDSBvoice->SetVolume(g_pVoices[i]->nVolume); - } -} -#endif - -//----------------------------------------------------------------------------- - static UINT g_uDSInitRefCount = 0; bool DSInit() { -#ifdef APPLE2IX if (!g_fh) { g_fh = stderr; } -#endif - if(g_bDSAvailable) - { - g_uDSInitRefCount++; - return true; // Already initialised successfully - } + if(g_bDSAvailable) + { + g_uDSInitRefCount++; + return true; // Already initialised successfully + } -#ifdef APPLE2IX if (sound_devices) { LOG("Destroying old device names..."); @@ -598,261 +275,107 @@ bool DSInit() FREE(sound_devices); sound_devices = NULL; } - num_sound_devices = SoundSystemEnumerate(&sound_devices, MAX_SOUND_DEVICES); + num_sound_devices = SoundSystemEnumerate(&sound_devices, MAX_SOUND_DEVICES); HRESULT hr = (num_sound_devices <= 0); -#else - HRESULT hr = DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc, NULL); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "DSEnumerate failed (%08X)\n",(unsigned int)hr); - return false; - } + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "DSEnumerate failed (%08X)\n",(unsigned int)hr); + return false; + } - if(g_fh) - { -#if !defined(APPLE2IX) - fprintf(g_fh, "Number of sound devices = %d\n",num_sound_devices); -#endif - } + if(g_fh) + { + fprintf(g_fh, "Number of sound devices = %ld\n",num_sound_devices); + } - bool bCreatedOK = false; - for(int x=0; xSetCooperativeLevel(g_hFrameWindow, DSSCL_NORMAL); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "SetCooperativeLevel failed (%08X)\n",hr); - return false; - } + g_bDSAvailable = true; - DSCAPS DSCaps; - ZeroMemory(&DSCaps, sizeof(DSCAPS)); - DSCaps.dwSize = sizeof(DSCAPS); - hr = g_lpDS->GetCaps(&DSCaps); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "GetCaps failed (%08X)\n",hr); - // Not fatal: so continue... - } -#endif + g_uDSInitRefCount = 1; - g_bDSAvailable = true; - - g_uDSInitRefCount = 1; - - return true; + return true; } //----------------------------------------------------------------------------- void DSUninit() { - if(!g_bDSAvailable) - return; + if(!g_bDSAvailable) + return; - _ASSERT(g_uDSInitRefCount); + _ASSERT(g_uDSInitRefCount); - if(g_uDSInitRefCount == 0) - return; + if(g_uDSInitRefCount == 0) + return; - g_uDSInitRefCount--; + g_uDSInitRefCount--; - if(g_uDSInitRefCount) - return; + if(g_uDSInitRefCount) + return; - // + // - _ASSERT(g_uNumVoices == 0); + _ASSERT(g_uNumVoices == 0); -#ifdef APPLE2IX SoundSystemDestroy((SoundSystemStruct**)&g_lpDS); -#else - SAFE_RELEASE(g_lpDS); -#endif - g_bDSAvailable = false; - - SoundCore_StopTimer(); + g_bDSAvailable = false; } //----------------------------------------------------------------------------- LONG NewVolume(DWORD dwVolume, DWORD dwVolumeMax) { - float fVol = (float) dwVolume / (float) dwVolumeMax; // 0.0=Max, 1.0=Min + float fVol = (float) dwVolume / (float) dwVolumeMax; // 0.0=Max, 1.0=Min - return (LONG) ((float) DSBVOLUME_MIN * fVol); + return (LONG) ((float) DSBVOLUME_MIN * fVol); } //============================================================================= -static int g_nErrorInc = 20; // Old: 1 -static int g_nErrorMax = 200; // Old: 50 +static int g_nErrorInc = 20; // Old: 1 +static int g_nErrorMax = 200; // Old: 50 int SoundCore_GetErrorInc() { - return g_nErrorInc; + return g_nErrorInc; } void SoundCore_SetErrorInc(const int nErrorInc) { - g_nErrorInc = nErrorInc < g_nErrorMax ? nErrorInc : g_nErrorMax; - if(g_fh) fprintf(g_fh, "Speaker/MB Error Inc = %d\n", g_nErrorInc); + g_nErrorInc = nErrorInc < g_nErrorMax ? nErrorInc : g_nErrorMax; + if(g_fh) fprintf(g_fh, "Speaker/MB Error Inc = %d\n", g_nErrorInc); } int SoundCore_GetErrorMax() { - return g_nErrorMax; + return g_nErrorMax; } void SoundCore_SetErrorMax(const int nErrorMax) { - g_nErrorMax = nErrorMax < MAX_SAMPLES ? nErrorMax : MAX_SAMPLES; - if(g_fh) fprintf(g_fh, "Speaker/MB Error Max = %d\n", g_nErrorMax); + g_nErrorMax = nErrorMax < MAX_SAMPLES ? nErrorMax : MAX_SAMPLES; + if(g_fh) fprintf(g_fh, "Speaker/MB Error Max = %d\n", g_nErrorMax); } -//============================================================================= - -#ifndef APPLE2IX -static DWORD g_dwAdviseToken; -static IReferenceClock *g_pRefClock = NULL; -static HANDLE g_hSemaphore = NULL; -static bool g_bRefClockTimerActive = false; -static DWORD g_dwLastUsecPeriod = 0; -#endif - - -bool SysClk_InitTimer() -{ -#ifdef APPLE2IX - // Not using timers ... - return false; -#else - g_hSemaphore = CreateSemaphore(NULL, 0, 1, NULL); // Max count = 1 - if (g_hSemaphore == NULL) - { - fprintf(stderr, "Error creating semaphore\n"); - return false; - } - - if (CoCreateInstance(CLSID_SystemClock, NULL, CLSCTX_INPROC, - IID_IReferenceClock, (LPVOID*)&g_pRefClock) != S_OK) - { - fprintf(stderr, "Error initialising COM\n"); - return false; // Fails for Win95! - } - - return true; -#endif -} - -void SysClk_UninitTimer() -{ -#ifdef APPLE2IX - // Not using timers ... -#else - SysClk_StopTimer(); - - SAFE_RELEASE(g_pRefClock); - - if (CloseHandle(g_hSemaphore) == 0) - fprintf(stderr, "Error closing semaphore handle\n"); -#endif -} - -// - -void SysClk_WaitTimer() -{ -#ifdef APPLE2IX - // Not using timers ... -#else - if(!g_bRefClockTimerActive) - return; - - WaitForSingleObject(g_hSemaphore, INFINITE); -#endif -} - -// - -void SysClk_StartTimerUsec(DWORD dwUsecPeriod) -{ -#ifdef APPLE2IX - // Not using timers ... -#else - if(g_bRefClockTimerActive && (g_dwLastUsecPeriod == dwUsecPeriod)) - return; - - SysClk_StopTimer(); - - REFERENCE_TIME rtPeriod = (REFERENCE_TIME) (dwUsecPeriod * 10); // In units of 100ns - REFERENCE_TIME rtNow; - - HRESULT hr = g_pRefClock->GetTime(&rtNow); - // S_FALSE : Returned time is the same as the previous value - - if ((hr != S_OK) && (hr != S_FALSE)) - { - _ASSERT(0); - return; - } - - if (g_pRefClock->AdvisePeriodic(rtNow, rtPeriod, g_hSemaphore, &g_dwAdviseToken) != S_OK) - { - fprintf(stderr, "Error creating timer\n"); - _ASSERT(0); - return; - } - - g_dwLastUsecPeriod = dwUsecPeriod; - g_bRefClockTimerActive = true; -#endif -} - -void SysClk_StopTimer() -{ -#ifdef APPLE2IX - // Not using timers ... -#else - if(!g_bRefClockTimerActive) - return; - - if (g_pRefClock->Unadvise(g_dwAdviseToken) != S_OK) - { - fprintf(stderr, "Error deleting timer\n"); - _ASSERT(0); - return; - } - - g_bRefClockTimerActive = false; -#endif -} diff --git a/src/audio/soundcore.h b/src/audio/soundcore.h index e03078b1..508a6ad0 100644 --- a/src/audio/soundcore.h +++ b/src/audio/soundcore.h @@ -21,41 +21,29 @@ #define MAX_SAMPLES (8*1024) -#if defined(APPLE2IX) -#define SAFE_RELEASE(p) FREE(p) -#else -#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } -#endif - -// Define max 1 of these: -//#define RIFF_SPKR -//#define RIFF_MB - -#ifdef APPLE2IX extern bool g_bDisableDirectSound; -#endif typedef struct { - LPDIRECTSOUNDBUFFER lpDSBvoice; + LPDIRECTSOUNDBUFFER lpDSBvoice; #ifdef APPLE2IX // apparently lpDSNotify isn't used... #define LPDIRECTSOUNDNOTIFY void* #endif - LPDIRECTSOUNDNOTIFY lpDSNotify; - bool bActive; // Playback is active - bool bMute; - LONG nVolume; // Current volume (as used by DirectSound) - LONG nFadeVolume; // Current fade volume (as used by DirectSound) - DWORD dwUserVolume; // Volume from slider on Property Sheet (0=Max) - bool bIsSpeaker; - bool bRecentlyActive; // (Speaker only) false after 0.2s of speaker inactivity + LPDIRECTSOUNDNOTIFY lpDSNotify; + bool bActive; // Playback is active + bool bMute; + LONG nVolume; // Current volume (as used by DirectSound) + LONG nFadeVolume; // Current fade volume (as used by DirectSound) + DWORD dwUserVolume; // Volume from slider on Property Sheet (0=Max) + bool bIsSpeaker; + bool bRecentlyActive; // (Speaker only) false after 0.2s of speaker inactivity } VOICE, *PVOICE; bool DSGetLock(LPDIRECTSOUNDBUFFER pVoice, DWORD dwOffset, DWORD dwBytes, - SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, - SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1); + SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, + SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1); HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels); void DSReleaseSoundBuffer(VOICE* pVoice); @@ -63,14 +51,8 @@ void DSReleaseSoundBuffer(VOICE* pVoice); bool DSZeroVoiceBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize); bool DSZeroVoiceWritableBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize); -#if defined(APPLE2IX) typedef enum eFADE {FADE_NONE, FADE_IN, FADE_OUT} eFADE; -#else -enum eFADE {FADE_NONE, FADE_IN, FADE_OUT}; -#endif void SoundCore_SetFade(eFADE FadeType); -bool SoundCore_GetTimerState(); -void SoundCore_TweakVolumes(); int SoundCore_GetErrorInc(); void SoundCore_SetErrorInc(const int nErrorInc); diff --git a/src/audio/speaker.c b/src/audio/speaker.c index 13cfdf7a..dcec9dc4 100644 --- a/src/audio/speaker.c +++ b/src/audio/speaker.c @@ -1,1347 +1,431 @@ /* -AppleWin : An Apple //e emulator for Windows - -Copyright (C) 1994-1996, Michael O'Brien -Copyright (C) 1999-2001, Oliver Schmidt -Copyright (C) 2002-2005, Tom Charlesworth -Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski - -AppleWin 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. - -AppleWin 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 AppleWin; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -/* Description: Speaker emulation + * Apple // emulator for *nix * - * Author: Various - * Linux ALSA/OpenAL Port : Aaron Culliney + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +/* Apple //e speaker support. Source inspired/derived from AppleWin. + * + * - ~46 //e cycles per PC sample (played back at 22.050kHz) -- (CLK_6502/SPKR_SAMPLE_RATE) + * + * The soundcard output drives how much 6502 emulation is done in real-time. If the soundcard buffer is running out of + * sample-data, then more 6502 cycles need to be executed to top-up the buffer, and vice-versa. + * + * This is in contrast to the AY8910 voices (mockingboard/phasor), which can simply generate more data if their buffers + * are running low. */ #include "common.h" -#ifdef APPLE2IX - -#include "audio/win-shim.h" -# ifdef __linux -# include -# endif - +#define DEBUG_SPEAKER (!defined(NDEBUG) && 0) // enable to print timing stats +#if DEBUG_SPEAKER +# define SPEAKER_LOG(...) LOG(__VA_ARGS__) #else -#include "StdAfx.h" -#endif -#include - -// Notes: -// -// [OLD: 23.191 Apple CLKs == 44100Hz (CLK_6502/44100)] -// 23 Apple CLKS per PC sample (played back at 44.1KHz) -// -// -// The speaker's wave output drives how much 6502 emulation is done in real-time, eg: -// If the speaker's wave buffer is running out of sample-data, then more 6502 cycles -// need to be executed to top-up the wave buffer. -// This is in contrast to the AY8910 voices, which can simply generate more data if -// their buffers are running low. -// - -#define SOUND_NONE 0 -#define SOUND_DIRECT 1 -#ifndef APPLE2IX -#define SOUND_SMART 2 -#endif -#define SOUND_WAVE 3 - -#ifdef APPLE2IX -#define g_nSPKR_NumChannels 1 -#else -static const unsigned short g_nSPKR_NumChannels = 1; -#endif -static const DWORD g_dwDSSpkrBufferSize = MAX_SAMPLES * sizeof(short) * g_nSPKR_NumChannels; - -//------------------------------------- - -static short* g_pSpeakerBuffer = NULL; - -// Globals (SOUND_WAVE) -#ifndef APPLE2IX -const short SPKR_DATA_INIT = (short)0x8000; +# define SPEAKER_LOG(...) #endif -static short g_nSpeakerData = SPKR_DATA_INIT; -static UINT g_nBufferIdx = 0; +#define MAX_REMAINDER_BUFFER (((CLK_6502_INT*(unsigned int)CPU_SCALE_FASTEST)/SPKR_SAMPLE_RATE)+1) -static short* g_pRemainderBuffer = NULL; -static UINT g_nRemainderBufferSize; // Setup in SpkrInitialize() -static UINT g_nRemainderBufferIdx; // Setup in SpkrInitialize() +#define SOUNDCORE_BUFFER_SIZE (MAX_SAMPLES*sizeof(int16_t)*1/*mono*/) +#define QUARTER_SIZE (SOUNDCORE_BUFFER_SIZE/4) +#define IDEAL_MIN (SOUNDCORE_BUFFER_SIZE/4) // minimum goldilocks zone for samples-in-play +#define IDEAL_MAX (SOUNDCORE_BUFFER_SIZE/2) // maximum goldilocks-zone for samples-in-play +static int16_t samples_buffer[SPKR_SAMPLE_RATE * sizeof(int16_t)] = { 0 }; // holds max 1 second of samples +static int16_t remainder_buffer[MAX_REMAINDER_BUFFER * sizeof(int16_t)] = { 0 }; // holds enough to create one sample (averaged) +static unsigned int samples_buffer_idx = 0; +static unsigned int remainder_buffer_size = 0; +static unsigned int remainder_buffer_idx = 0; -// Application-wide globals: -DWORD soundtype = SOUND_WAVE; -double g_fClksPerSpkrSample; // Setup in SetClksPerSpkrSample() +static int16_t speaker_amplitude = SPKR_DATA_INIT; +static int16_t speaker_data = 0; -// Globals -#ifndef APPLE2IX -static DWORD lastcyclenum = 0; -static DWORD toggles = 0; -#endif -static uint64_t g_nSpkrQuietCycleCount = 0; -static uint64_t g_nSpkrLastCycle = 0; -static bool g_bSpkrToggleFlag = false; -static VOICE SpeakerVoice = {0}; -static bool g_bSpkrAvailable = false; +static double cycles_per_sample = 0.0; +static unsigned long long cycles_last_update = 0; +static unsigned long long cycles_quiet_time = 0; +static bool speaker_accessed_since_last_flush = false; +static bool speaker_recently_active = false; -#ifndef APPLE2IX -// Globals (SOUND_DIRECT/SOUND_SMART) -static BOOL directio = 0; -static DWORD lastdelta[2] = {0,0}; -static DWORD quietcycles = 0; -static DWORD soundeffect = 0; -static DWORD totaldelta = 0; -#endif +static bool speaker_going_silent = false; +static unsigned int speaker_silent_step = 0; -//----------------------------------------------------------------------------- +static int samples_adjustment_counter = 0; -// Forward refs: -#ifdef APPLE2IX -static ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples); -static ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples); -#else -ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples); -ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples); -#endif -static void Spkr_SetActive(bool bActive); +static VOICE SpeakerVoice = { 0 }; -//============================================================================= +// -------------------------------------------------------------------------------------------------------------------- -#ifndef APPLE2IX -static void DisplayBenchmarkResults () -{ - DWORD totaltime = GetTickCount()-extbench; - VideoRedrawScreen(); - TCHAR buffer[64]; - wsprintf(buffer, - TEXT("This benchmark took %u.%02u seconds."), - (unsigned)(totaltime / 1000), - (unsigned)((totaltime / 10) % 100)); - MessageBox(g_hFrameWindow, - buffer, - TEXT("Benchmark Results"), - MB_ICONINFORMATION | MB_SETFOREGROUND); -} -#endif +/* + * Because disk image loading is slow (AKA close-to-original-//e-speed), we may auto-switch to "fullspeed" for faster + * loading when all the following heuristics hold true: + * - Disk motor is on + * - Speaker has not been toggled in some time (is not "active") + * - The graphics state is not "dirty" + * + * In fullspeed mode we output only quiet samples (zero-amplitude) at such a rate as to prevent the streaming audio from + * either underflowing or overflowing. + * + * We will also auto-switch back to the last configured "scaled" speed when the speaker is toggled. + */ +static void _speaker_init_timing(void) { + // 46.28 //e cycles for 22.05kHz sample rate -//============================================================================= + // AppleWin NOTE : use integer value: Better for MJ Mahon's RT.SYNTH.DSK (integer multiples of 1.023MHz Clk) + cycles_per_sample = (unsigned int)(cycles_persec_target / (double)SPKR_SAMPLE_RATE); -static void InternalBeep (DWORD frequency, DWORD duration) -{ -#ifdef APPLE2IX -# if defined(__i386__) - if (duration) - { - // HACK FIXME TODO : this needs to be properly tested since I don't have a PC with a speaker anymore... - - // 9/8/2013 -- from http://www.johnath.com/beep/beep.c - /* I don't know where this number comes from, I admit that freely. A - * wonderful human named Raine M. Ekman used it in a program that played - * a tune at the console, and apparently, it's how the kernel likes its - * sound requests to be phrased. If you see Raine, thank him for me. - * - * June 28, email from Peter Tirsek (peter at tirsek dot com): - * - * This number represents the fixed frequency of the original PC XT's - * timer chip (the 8254 AFAIR), which is approximately 1.193 MHz. This - * number is divided with the desired frequency to obtain a counter value, - * that is subsequently fed into the timer chip, tied to the PC speaker. - * The chip decreases this counter at every tick (1.193 MHz) and when it - * reaches zero, it toggles the state of the speaker (on/off, or in/out), - * resets the counter to the original value, and starts over. The end - * result of this is a tone at approximately the desired frequency. :) - */ - frequency = 1193180/frequency; - - // http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html - asm ( - "pushl %%eax;" - "movb $0x0B6, %%al;" - "outb %%al, $0x43;" - "movl %0, %%eax;" - "outb %%al, $0x42;" - "movb %%ah, %%al;" - "outb %%al, $0x42;" - "inb $0x61, %%al;" - "orb $0x3, %%al;" - "outb %%al, $0x61;" - "popl %%eax;" - : : "r" (frequency) /*%0*/ : ); + unsigned int last_remainder_buffer_size = remainder_buffer_size; + remainder_buffer_size = (unsigned int)cycles_per_sample; + if ((double)remainder_buffer_size != cycles_per_sample) { + ++remainder_buffer_size; } - else - { - asm ( - "pushl %eax;" - "inb $0x61, %al;" - "andb $0x0FC, %al;" - "outb %al, $0x61;" - "popl %eax;" - ); + assert(remainder_buffer_size <= MAX_REMAINDER_BUFFER); + + if (last_remainder_buffer_size == remainder_buffer_size) { + // no change ... insure seamless remainder_buffer + } else { + SPEAKER_LOG("changing remainder buffer size"); + remainder_buffer_idx = 0; } -# elif defined(__x86_64__) - // X64 Direct Access TODO ... -# endif -#else -#ifdef _X86_ - if (directio) - if (duration) { - frequency = 1193180/frequency; - __asm { - push eax - mov al,0B6h - out 43h,al - mov eax,frequency - out 42h,al - mov al,ah - out 42h,al - in al,61h - or al,3 - out 61h,al - pop eax - } + if (cycles_last_update > cycles_count_total) { + SPEAKER_LOG("resetting speaker cycles counter"); + cycles_last_update = 0; } - else - __asm { - push eax - in al,61h - and al,0FCh - out 61h,al - pop eax - } - else -#endif - Beep(frequency,duration); -#endif -} -//============================================================================= - -static void InternalClick () -{ -#ifdef APPLE2IX - // TODO : I don't have a PC with a physical speaker anymore... can I - // test this with a virtual speaker Linux driver? -# if defined(__i386__) - asm ( - "pushl %eax;" - "inb $0x61, %al;" - "xorb $0x2, %al;" - "outb %al, $0x61;" - "popl %eax;" - ); -# elif defined(__x86_64__) - // X64 Direct Access TODO ... -# endif -#else -#ifdef _X86_ - if (directio) - __asm { - push eax - in al,0x61 - xor al,2 - out 0x61,al - pop eax + if (is_fullspeed) { + remainder_buffer_idx = 0; + samples_buffer_idx = 0; } - else { -#endif - Beep(37,(DWORD)-1); - Beep(0,0); -#ifdef _X86_ - } -#endif -#endif } -//============================================================================= +/* + * Adds to the samples_buffer the number of samples since the last invocation of this function. + * + * Speaker output square wave example: + * _______ ____ _____________________! . +speaker_amplitude + * silence _ . + * threshold _ . + * _ remainder _ . + * average _ . + * _______ ____________ ______ ___ . 0 + * + * - When the speaker is accessed by the emulated program, the output (speaker_data) is toggled between the + * positive amplitude or zero + * - Evenly-divisible samples since last function call (cycles_diff/cycles_per_sample) are put directly into the + * samples_buffer for output to audio system backend + * - (+) Fractional samples are put into a remainder_buffer to be averaged and then transfered to the sample_buffer + * when there is enough data for 1 whole sample (possibly on a subsequent invocation of this function) + * - (+) If the speaker has not been toggled with output at +amplitude for a certain number of machine cycles, we + * gradually step the samples down to the zero bound of true quiet. (This is done to avoid glitching when + * pausing/unpausing emulation for GUI/menus and auto-switching between full and configured speeds) + */ +static void _speaker_update(/*bool toggled*/) { -static void SetClksPerSpkrSample() -{ -// // 23.191 clks for 44.1Khz (when 6502 CLK=1.0Mhz) -// g_fClksPerSpkrSample = g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE; + if (!is_fullspeed) { - // Use integer value: Better for MJ Mahon's RT.SYNTH.DSK (integer multiples of 1.023MHz Clk) - // . 23 clks @ 1.023MHz - g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE); -} + unsigned long long cycles_diff = cycles_count_total - cycles_last_update; -//============================================================================= - -static void InitRemainderBuffer() -{ -#ifdef APPLE2IX - free(g_pRemainderBuffer); -#else - delete [] g_pRemainderBuffer; -#endif - - SetClksPerSpkrSample(); - - g_nRemainderBufferSize = (UINT) g_fClksPerSpkrSample; - if ((double)g_nRemainderBufferSize != g_fClksPerSpkrSample) - g_nRemainderBufferSize++; - -#ifdef APPLE2IX - g_pRemainderBuffer = calloc(g_nRemainderBufferSize, sizeof(short)); -#else - g_pRemainderBuffer = new short [g_nRemainderBufferSize]; - memset(g_pRemainderBuffer, 0, g_nRemainderBufferSize); -#endif - - g_nRemainderBufferIdx = 0; -} - -// -// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE ----- -// - -//============================================================================= - -void SpkrDestroy () -{ - Spkr_DSUninit(); - - // - - if(soundtype == SOUND_WAVE) - { -#ifdef APPLE2IX - free(g_pSpeakerBuffer); - free(g_pRemainderBuffer); -#else - delete [] g_pSpeakerBuffer; - delete [] g_pRemainderBuffer; -#endif - - g_pSpeakerBuffer = NULL; - g_pRemainderBuffer = NULL; - } - else - { - InternalBeep(0,0); - } -} - -//============================================================================= - -void SpkrInitialize () -{ -#ifdef APPLE2IX - if (soundtype == SOUND_DIRECT) - { -# if defined(__i386__) - if (ioperm(0x42, 1, 1) || ioperm(0x61, 1, 1)) - { - ERRLOG("Cannot get direct port access to PC speaker, attempting to use sound card..."); - soundtype = SOUND_WAVE; - } - else - { - LOG("Audio is direct access to PC speaker..."); - } -# elif defined(__x86_64__) - // X64 Direct Access TODO ... - soundtype = SOUND_WAVE; -# else - soundtype = SOUND_WAVE; -# endif - } -#else - if(g_fh) - { - fprintf(g_fh, "Spkr Config: soundtype = %d ",soundtype); - switch(soundtype) - { - case SOUND_NONE: fprintf(g_fh, "(NONE)\n"); break; - case SOUND_DIRECT: fprintf(g_fh, "(DIRECT)\n"); break; - case SOUND_SMART: fprintf(g_fh, "(SMART)\n"); break; - case SOUND_WAVE: fprintf(g_fh, "(WAVE)\n"); break; - default: fprintf(g_fh, "(UNDEFINED!)\n"); break; - } - } -#endif - - if(g_bDisableDirectSound) - { - SpeakerVoice.bMute = true; - } - else - { - g_bSpkrAvailable = Spkr_DSInit(); - } - - // - - if (soundtype == SOUND_WAVE) - { - InitRemainderBuffer(); - -#ifdef APPLE2IX - g_pSpeakerBuffer = calloc(SPKR_SAMPLE_RATE, sizeof(short)); // Buffer can hold a max of 1 seconds worth of samples -#else - g_pSpeakerBuffer = new short [SPKR_SAMPLE_RATE]; // Buffer can hold a max of 1 seconds worth of samples -#endif - } - - // - -#ifdef APPLE2IX -// initialized direct sound above -#else - // IF NONE IS, THEN DETERMINE WHETHER WE HAVE DIRECT ACCESS TO THE PC SPEAKER PORT - if (soundtype != SOUND_WAVE) // *** TO DO: Need way of determining if DirectX init failed *** - { - if (soundtype == SOUND_WAVE) - soundtype = SOUND_SMART; -#ifdef _X86_ - _try - { - __asm { - in al,0x61 - xor al,2 - out 0x61,al - xor al,2 - out 0x61,al - } - directio = 1; - } - _except (EXCEPTION_EXECUTE_HANDLER) - { - directio = 0; - } -#else - directio = 0; -#endif - if ((!directio) && (soundtype == SOUND_DIRECT)) - soundtype = SOUND_SMART; - } - -#endif -} - -//============================================================================= - -// NB. Called when /g_fCurrentCLK6502/ changes -void SpkrReinitialize () -{ - if (soundtype == SOUND_WAVE) - { - InitRemainderBuffer(); - } -} - -//============================================================================= - -void SpkrReset() -{ - g_nBufferIdx = 0; - g_nSpkrQuietCycleCount = 0; - g_bSpkrToggleFlag = false; - - InitRemainderBuffer(); - Spkr_SubmitWaveBuffer(NULL, 0); - Spkr_SetActive(false); - Spkr_Demute(); -} - -//============================================================================= - -BOOL SpkrSetEmulationType (HWND window, DWORD newtype) -{ - if (soundtype != SOUND_NONE) - SpkrDestroy(); - soundtype = newtype; - if (soundtype != SOUND_NONE) - SpkrInitialize(); - if (soundtype != newtype) - switch (newtype) { - - case SOUND_DIRECT: - MessageBox(window, - TEXT("Direct emulation is not available because the ") - TEXT("operating system you are using does not allow ") - TEXT("direct control of the speaker."), - TEXT("Configuration"), - MB_ICONEXCLAMATION | MB_SETFOREGROUND); - return 0; - - case SOUND_WAVE: - MessageBox(window, - TEXT("The emulator is unable to initialize a waveform ") - TEXT("output device. Make sure you have a sound card ") - TEXT("and a driver installed and that windows is ") - TEXT("correctly configured to use the driver. Also ") - TEXT("ensure that no other program is currently using ") - TEXT("the device."), - TEXT("Configuration"), - MB_ICONEXCLAMATION | MB_SETFOREGROUND); - return 0; - - } - return 1; -} - -//============================================================================= - -static void ReinitRemainderBuffer(UINT nCyclesRemaining) -{ - if(nCyclesRemaining == 0) - return; - - for(g_nRemainderBufferIdx=0; (g_nRemainderBufferIdx 2) || (soundtype == SOUND_DIRECT)) - { - if (directio) - { - __asm - { - push eax - in al,0x61 - xor al,2 - out 0x61,al - pop eax + if (samples_buffer_idx < SPKR_SAMPLE_RATE) { + samples_buffer[samples_buffer_idx++] = (int16_t)sample_mean; + } + } } - } - else - { - Beep(37,(DWORD)-1); - Beep(0,0); - } - } - // SAVE INFORMATION ABOUT THE FREQUENCY OF SPEAKER TOGGLING FOR POSSIBLE - // LATER USE BY SOUND AVERAGING - if (lastcyclenum) - { - toggles++; - DWORD delta = cyclenum-lastcyclenum; + const unsigned long long samples_count = (unsigned long long)((double)cycles_diff / cycles_per_sample); + unsigned long long num_samples = samples_count; + const unsigned long long cycles_remainder = (unsigned long long)((double)cycles_diff - (double)num_samples * cycles_per_sample); - // DETERMINE WHETHER WE ARE PLAYING A SOUND EFFECT - if (directio && - ((delta < 250) || - (lastdelta[0] && lastdelta[1] && - (delta-lastdelta[0] > 250) && (lastdelta[0]-delta > 250) && - (delta-lastdelta[1] > 250) && (lastdelta[1]-delta > 250)))) - soundeffect = MIN(35,soundeffect+2); + // populate samples_buffer with whole samples + while (num_samples && (samples_buffer_idx < SPKR_SAMPLE_RATE)) { + samples_buffer[samples_buffer_idx++] = speaker_data; + if (speaker_going_silent && speaker_data) { + speaker_data -= speaker_silent_step; + } + --num_samples; + } - lastdelta[1] = lastdelta[0]; - lastdelta[0] = delta; - totaldelta += delta; - } - lastcyclenum = cyclenum; - -#endif - } - -#ifndef APPLE2IX - return MemReadFloatingBus(nCyclesLeft); -#endif -} - -//============================================================================= - -// Called by ContinueExecution() -void SpkrUpdate(DWORD totalcycles) -{ - if(!g_bSpkrToggleFlag) - { - if(!g_nSpkrQuietCycleCount) - { - g_nSpkrQuietCycleCount = g_nCumulativeCycles; - } - else if(g_nCumulativeCycles - g_nSpkrQuietCycleCount > (unsigned __int64)g_fCurrentCLK6502/5) - { - // After 0.2 sec of Apple time, deactivate spkr voice - // . This allows emulator to auto-switch to full-speed g_nAppMode for fast disk access - Spkr_SetActive(false); - } - } - else - { - g_nSpkrQuietCycleCount = 0; - g_bSpkrToggleFlag = false; - } - - // - - if (soundtype == SOUND_WAVE) - { - UpdateSpkr(); - ULONG nSamplesUsed; - - if(g_bFullSpeed) - nSamplesUsed = Spkr_SubmitWaveBuffer_FullSpeed(g_pSpeakerBuffer, g_nBufferIdx); - else - nSamplesUsed = Spkr_SubmitWaveBuffer(g_pSpeakerBuffer, g_nBufferIdx); - - _ASSERT(nSamplesUsed <= g_nBufferIdx); - memmove(g_pSpeakerBuffer, &g_pSpeakerBuffer[nSamplesUsed], g_nBufferIdx-nSamplesUsed); // FIXME-TC: _Size * 2 - g_nBufferIdx -= nSamplesUsed; - } -#ifndef APPLE2IX - else - { - - // IF WE ARE NOT PLAYING A SOUND EFFECT, PERFORM FREQUENCY AVERAGING - static DWORD currenthertz = 0; - static BOOL lastfull = 0; - static DWORD lasttoggles = 0; - static DWORD lastval = 0; - if ((soundeffect > 2) || (soundtype == SOUND_DIRECT)) { - lastval = 0; - if (currenthertz && (soundeffect > 4)) { - InternalBeep(0,0); - currenthertz = 0; - } - } - else if (toggles && totaldelta) { - DWORD newval = 1000000*toggles/totaldelta; - if (lastval && lastfull && - (newval-currenthertz > 50) && - (currenthertz-newval > 50)) { - InternalBeep(newval,(DWORD)-1); - currenthertz = newval; - lasttoggles = 0; - } - lastfull = (totaldelta+((totaldelta/toggles) << 1) >= totalcycles); - lasttoggles += toggles; - lastval = newval; - } - else if (currenthertz) { - InternalBeep(0,0); - currenthertz = 0; - lastfull = 0; - lasttoggles = 0; - lastval = 0; - } - else if (lastval) { - currenthertz = (lasttoggles > 4) ? lastval : 0; - if (currenthertz) - InternalBeep(lastval,(DWORD)-1); - else - InternalClick(); - lastfull = 0; - lasttoggles = 0; - lastval = 0; - } - - // RESET THE FREQUENCY GATHERING VARIABLES - lastcyclenum = 0; - lastdelta[0] = 0; - lastdelta[1] = 0; - quietcycles = toggles ? 0 : (quietcycles+totalcycles); - toggles = 0; - totaldelta = 0; - if (soundeffect) - soundeffect--; - - } -#endif -} - -#ifndef APPLE2IX -// Called from SoundCore_TimerFunc() for FADE_OUT -void SpkrUpdate_Timer() -{ - if (soundtype == SOUND_WAVE) - { - UpdateSpkr(); - ULONG nSamplesUsed; - - nSamplesUsed = Spkr_SubmitWaveBuffer_FullSpeed(g_pSpeakerBuffer, g_nBufferIdx); - - _ASSERT(nSamplesUsed <= g_nBufferIdx); - memmove(g_pSpeakerBuffer, &g_pSpeakerBuffer[nSamplesUsed], g_nBufferIdx-nSamplesUsed); // FIXME-TC: _Size * 2 - g_nBufferIdx -= nSamplesUsed; - } -} - -//============================================================================= - -static DWORD dwByteOffset = (DWORD)-1; -#endif -static int nNumSamplesError = 0; -static int nDbgSpkrCnt = 0; - -// FullSpeed g_nAppMode, 2 cases: -// i) Short burst of full-speed, so PlayCursor doesn't complete sound from previous fixed-speed session. -// ii) Long burst of full-speed, so PlayCursor completes sound from previous fixed-speed session. - -// Try to: -// 1) Output remaining samples from SpeakerBuffer (from previous fixed-speed session) -// 2) Output pad samples to keep the VoiceBuffer topped-up - -// If nNumSamples>0 then these are from previous fixed-speed session. -// - Output these before outputting zero-pad samples. - -static ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples) -{ - //char szDbg[200]; - nDbgSpkrCnt++; - - if(!SpeakerVoice.bActive) - return nNumSamples; - - // pSpeakerBuffer can't be NULL, as reset clears g_bFullSpeed, so 1st SpkrUpdate() never calls here - _ASSERT(pSpeakerBuffer != NULL); - - // - - DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1; - SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; - //bool bBufferError = false; - - DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; -#ifdef APPLE2IX - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &dwCurrentPlayCursor, &dwCurrentWriteCursor); -#else - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); -#endif - if(FAILED(hr)) - return nNumSamples; + if (cycles_remainder > 0) { + // populate remainder_buffer with fractional samples + assert(remainder_buffer_idx == 0 && "should have already dealt with remainder buffer"); + assert(cycles_remainder < remainder_buffer_size && "otherwise there should have been another whole sample"); + while (remainder_buffer_idx dwCurrentPlayCursor) - { - // |-----PxxxxxW-----| - if((dwByteOffset > dwCurrentPlayCursor) && (dwByteOffset < dwCurrentWriteCursor)) - { - //sprintf(szDbg, "[Submit_FS] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); OutputDebugString(szDbg); - dwByteOffset = dwCurrentWriteCursor; - } - } - else - { - // |xxW----------Pxxx| - if((dwByteOffset > dwCurrentPlayCursor) || (dwByteOffset < dwCurrentWriteCursor)) - { - //sprintf(szDbg, "[Submit_FS] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); OutputDebugString(szDbg); - dwByteOffset = dwCurrentWriteCursor; - } - } - } - - // Calc bytes remaining to be played - int nBytesRemaining = dwByteOffset - dwCurrentPlayCursor; -#else - int nBytesRemaining = (int)dwCurrentPlayCursor; + } else if (toggled && samples_count) { + samples_buffer[samples_buffer_idx-1] = 0; #endif - if(nBytesRemaining < 0) - nBytesRemaining += g_dwDSSpkrBufferSize; - if((nBytesRemaining == 0) && (dwCurrentPlayCursor != dwCurrentWriteCursor)) - nBytesRemaining = g_dwDSSpkrBufferSize; // Case when complete buffer is to be played - - // - - UINT nNumPadSamples = 0; - - if(nBytesRemaining < g_dwDSSpkrBufferSize / 4) - { - // < 1/4 of play-buffer remaining (need *more* data) - nNumPadSamples = ((g_dwDSSpkrBufferSize / 4) - nBytesRemaining) / sizeof(short); - - if(nNumPadSamples > nNumSamples) - nNumPadSamples -= nNumSamples; - else - nNumPadSamples = 0; - - // NB. If nNumPadSamples>0 then all nNumSamples are to be used - } - - // - - UINT nBytesFree = g_dwDSSpkrBufferSize - nBytesRemaining; // Calc free buffer space - ULONG nNumSamplesToUse = nNumSamples + nNumPadSamples; - - if(nNumSamplesToUse * sizeof(short) > nBytesFree) - nNumSamplesToUse = nBytesFree / sizeof(short); - - // - - if(nNumSamplesToUse >= 128) // Limit the buffer unlock/locking to a minimum - { - if(!DSGetLock(SpeakerVoice.lpDSBvoice, -#ifdef APPLE2IX - /*unused*/ 0, (DWORD)nNumSamplesToUse*sizeof(short), -#else - dwByteOffset, (DWORD)nNumSamplesToUse*sizeof(short), -#endif - &pDSLockedBuffer0, &dwDSLockedBufferSize0, - &pDSLockedBuffer1, &dwDSLockedBufferSize1)) - return nNumSamples; - - // - - DWORD dwBufferSize0 = 0; - DWORD dwBufferSize1 = 0; - - if(nNumSamples) - { - //sprintf(szDbg, "[Submit_FS] C=%08X, PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X ***\n", nDbgSpkrCnt, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); OutputDebugString(szDbg); - - if(nNumSamples*sizeof(short) <= dwDSLockedBufferSize0) - { - dwBufferSize0 = nNumSamples*sizeof(short); - dwBufferSize1 = 0; - } - else - { - dwBufferSize0 = dwDSLockedBufferSize0; - dwBufferSize1 = nNumSamples*sizeof(short) - dwDSLockedBufferSize0; - - if(dwBufferSize1 > dwDSLockedBufferSize1) - dwBufferSize1 = dwDSLockedBufferSize1; - } - - memcpy(pDSLockedBuffer0, &pSpeakerBuffer[0], dwBufferSize0); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer0, dwBufferSize0/sizeof(short)); -#endif - nNumSamples = (ULONG)(dwBufferSize0/sizeof(short)); - - if(pDSLockedBuffer1 && dwBufferSize1) - { - memcpy(pDSLockedBuffer1, &pSpeakerBuffer[dwDSLockedBufferSize0/sizeof(short)], dwBufferSize1); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer1, dwBufferSize1/sizeof(short)); -#endif - nNumSamples += dwBufferSize1/sizeof(short); - } - } - - if(nNumPadSamples) - { - //sprintf(szDbg, "[Submit_FS] C=%08X, PC=%08X, WC=%08X, Diff=%08X, Off=%08X, PS=%08X, Data=%04X\n", nDbgSpkrCnt, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumPadSamples, g_nSpeakerData); OutputDebugString(szDbg); - - dwBufferSize0 = dwDSLockedBufferSize0 - dwBufferSize0; - dwBufferSize1 = dwDSLockedBufferSize1 - dwBufferSize1; - - if(dwBufferSize0) - { - wmemset((wchar_t*)pDSLockedBuffer0, (wchar_t)g_nSpeakerData, dwBufferSize0/sizeof(wchar_t)); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer0, dwBufferSize0/sizeof(short)); -#endif - } - - if(pDSLockedBuffer1) - { - wmemset((wchar_t*)pDSLockedBuffer1, (wchar_t)g_nSpeakerData, dwBufferSize1/sizeof(wchar_t)); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer1, dwBufferSize1/sizeof(short)); -#endif - } - } - - // Commit sound buffer -#ifdef APPLE2IX - hr = SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#else - hr = SpeakerVoice.lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#endif - (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); - if(FAILED(hr)) - return nNumSamples; - -#ifndef APPLE2IX - dwByteOffset = (dwByteOffset + (DWORD)nNumSamplesToUse*sizeof(short)*g_nSPKR_NumChannels) % g_dwDSSpkrBufferSize; -#endif - } - - return nNumSamples; -} - -//----------------------------------------------------------------------------- - -static ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples) -{ -#ifndef APPLE2IX - char szDbg[200]; -#endif - nDbgSpkrCnt++; - - if(!SpeakerVoice.bActive) - return nNumSamples; - - if(pSpeakerBuffer == NULL) - { - // Re-init from SpkrReset() -#ifndef APPLE2IX - // HACK FIXME TODO : believe this whole if statement is unnecessary? - dwByteOffset = (DWORD)-1; -#endif - nNumSamplesError = 0; - - // Don't call DSZeroVoiceBuffer() - get noise with "VIA AC'97 Enhanced Audio Controller" - // . I guess SpeakerVoice.Stop() isn't really working and the new zero buffer causes noise corruption when submitted. -#ifndef APPLE2IX - DSZeroVoiceWritableBuffer(&SpeakerVoice, "Spkr", g_dwDSSpkrBufferSize); -#endif - - return 0; - } - - // - - DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1; - SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; - bool bBufferError = false; - - DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; -#ifdef APPLE2IX - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &dwCurrentPlayCursor, &dwCurrentWriteCursor); -#else - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); -#endif - if(FAILED(hr)) - return nNumSamples; - -#if 0 - if(dwByteOffset == (DWORD)-1) - { - // First time in this func (probably after re-init (above)) - - dwByteOffset = dwCurrentPlayCursor + (g_dwDSSpkrBufferSize/8)*3; // Ideal: 0.375 is between 0.25 & 0.50 full - dwByteOffset %= g_dwDSSpkrBufferSize; - //sprintf(szDbg, "[Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X [REINIT]\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset); OutputDebugString(szDbg); - } - else - { - // Check that our offset isn't between Play & Write positions - - if(dwCurrentWriteCursor > dwCurrentPlayCursor) - { - // |-----PxxxxxW-----| - if((dwByteOffset > dwCurrentPlayCursor) && (dwByteOffset < dwCurrentWriteCursor)) - { - double fTicksSecs = (double)GetTickCount() / 1000.0; -#ifdef APPLE2IX - LOG("%010.3f: [Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); -#else - sprintf(szDbg, "%010.3f: [Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); - OutputDebugString(szDbg); - if (g_fh) fprintf(g_fh, szDbg); -#endif - - dwByteOffset = dwCurrentWriteCursor; - nNumSamplesError = 0; - bBufferError = true; - } - } - else - { - // |xxW----------Pxxx| - if((dwByteOffset > dwCurrentPlayCursor) || (dwByteOffset < dwCurrentWriteCursor)) - { - double fTicksSecs = (double)GetTickCount() / 1000.0; -#ifdef APPLE2IX - LOG("%010.3f: [Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); -#else - sprintf(szDbg, "%010.3f: [Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); - OutputDebugString(szDbg); - if (g_fh) fprintf(g_fh, szDbg); -#endif - - dwByteOffset = dwCurrentWriteCursor; - nNumSamplesError = 0; - bBufferError = true; - } - } - } - - // Calc bytes remaining to be played - int nBytesRemaining = dwByteOffset - dwCurrentPlayCursor; -#else - int nBytesRemaining = (int)dwCurrentPlayCursor; -#endif - if(nBytesRemaining < 0) - nBytesRemaining += g_dwDSSpkrBufferSize; - if((nBytesRemaining == 0) && (dwCurrentPlayCursor != dwCurrentWriteCursor)) - nBytesRemaining = g_dwDSSpkrBufferSize; // Case when complete buffer is to be played - - // Calc correction factor so that play-buffer doesn't under/overflow - const int nErrorInc = SoundCore_GetErrorInc(); - if(nBytesRemaining < g_dwDSSpkrBufferSize / 4) - { -#ifdef APPLE2IX - //LOG("execute more cycles due to potential underflow..."); -#endif - nNumSamplesError += nErrorInc; // < 1/4 of play-buffer remaining (need *more* data) - } - else if(nBytesRemaining > g_dwDSSpkrBufferSize / 2) - { -#ifdef APPLE2IX - //LOG("execute less cycles due to potential overflow..."); -#endif - nNumSamplesError -= nErrorInc; // > 1/2 of play-buffer remaining (need *less* data) - } - else - { - nNumSamplesError = 0; // Acceptable amount of data in buffer } - const int nErrorMax = SoundCore_GetErrorMax(); // Cap feedback to +/-nMaxError units - if(nNumSamplesError < -nErrorMax) nNumSamplesError = -nErrorMax; - if(nNumSamplesError > nErrorMax) nNumSamplesError = nErrorMax; - g_nCpuCyclesFeedback = (int) ((double)nNumSamplesError * g_fClksPerSpkrSample); + if (UNLIKELY(samples_buffer_idx >= SPKR_SAMPLE_RATE)) { + assert(samples_buffer_idx == SPKR_SAMPLE_RATE && "should be at exactly the end, no further"); + ERRLOG("OOPS, overflow in speaker samples buffer"); + } + } - // + cycles_last_update = cycles_count_total; +} - UINT nBytesFree = g_dwDSSpkrBufferSize - nBytesRemaining; // Calc free buffer space - ULONG nNumSamplesToUse = nNumSamples; +/* + * Submits "quiet" samples to the audio system backend when CPU thread is running fullspeed, to keep the audio streaming + * topped up. + * + * 20150131 NOTE : it seems that there are still cases where we glitch OpenAL (seemingly on transitioning back to + * regular speed sample submissions). I have not been able to fully isolate the cause, but this has been minimized by + * always trending the samples to the zero value when there has been a sufficient amount of speaker silence. + */ +static void _submit_samples_buffer_fullspeed(void) { + samples_adjustment_counter = 0; - if(nNumSamplesToUse * sizeof(short) > nBytesFree) - nNumSamplesToUse = nBytesFree / sizeof(short); + if (!SpeakerVoice.bActive) { + return; + } - if(bBufferError) - pSpeakerBuffer = &pSpeakerBuffer[nNumSamples - nNumSamplesToUse]; + unsigned long bytes_queued = 0; + long err = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &bytes_queued, NULL); + if (err) { + return; + } + assert(bytes_queued <= SOUNDCORE_BUFFER_SIZE); - // + if (bytes_queued >= IDEAL_MAX) { + return; + } - if(nNumSamplesToUse) - { - //sprintf(szDbg, "[Submit] C=%08X, PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X +++\n", nDbgSpkrCnt, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamplesToUse); OutputDebugString(szDbg); + unsigned int num_samples_pad = (IDEAL_MAX - bytes_queued) / sizeof(int16_t); + if (num_samples_pad == 0) { + return; + } - if(!DSGetLock(SpeakerVoice.lpDSBvoice, -#ifdef APPLE2IX - /*unused*/ 0, (DWORD)nNumSamplesToUse*sizeof(short), + unsigned long system_buffer_size = 0; + int16_t *system_samples_buffer = NULL; + if (!DSGetLock(SpeakerVoice.lpDSBvoice, /*unused*/ 0, num_samples_pad*sizeof(int16_t), &system_samples_buffer, &system_buffer_size, NULL, NULL)) { + return; + } + assert(num_samples_pad*sizeof(int16_t) <= system_buffer_size); + + //SPEAKER_LOG("bytes_queued:%d enqueueing %d quiet samples", bytes_queued, num_samples_pad); + for (unsigned int i=0; iUnlock(SpeakerVoice.lpDSBvoice->_this, (void*)system_samples_buffer, system_buffer_size, NULL, 0); +} + +// Submits samples from the samples_buffer to the audio system backend when running at a normal scaled-speed. This also +// generates cycles feedback to the main CPU timing routine depending on the needs of the streaming audio (more or less +// data). +static unsigned int _submit_samples_buffer(const unsigned int num_samples) { + + assert(num_samples); + + if (!SpeakerVoice.bActive) { + return num_samples; + } + + unsigned long bytes_queued = 0; + long err = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &bytes_queued, NULL); + if (err) { + return num_samples; + } + assert(bytes_queued <= SOUNDCORE_BUFFER_SIZE); + + // + // calculate CPU cycles feedback adjustment to prevent system audio buffer under/overflow + // + + const int error_increment = SoundCore_GetErrorInc(); + + if (bytes_queued < IDEAL_MIN) { + samples_adjustment_counter += error_increment; // need moar data + } else if (bytes_queued > IDEAL_MAX) { + samples_adjustment_counter -= error_increment; // need less data + } else { + samples_adjustment_counter = 0; // Acceptable amount of data in buffer + } + + const int samples_adjustment_max = SoundCore_GetErrorMax(); // capped to a max/min + if (samples_adjustment_counter < -samples_adjustment_max) { + samples_adjustment_counter = -samples_adjustment_max; + } else if (samples_adjustment_counter > samples_adjustment_max) { + samples_adjustment_counter = samples_adjustment_max; + } + + cycles_speaker_feedback = (int)(samples_adjustment_counter * cycles_per_sample); + + //SPEAKER_LOG("feedback:%d samples_adjustment_counter:%d", cycles_speaker_feedback, samples_adjustment_counter); + + // + // copy samples to audio system backend + // + + const unsigned int bytes_free = SOUNDCORE_BUFFER_SIZE - bytes_queued; + unsigned int num_samples_to_use = num_samples; + + if (num_samples_to_use * sizeof(int16_t) > bytes_free) { + num_samples_to_use = bytes_free / sizeof(int16_t); + } + + if (num_samples_to_use) { + unsigned long system_buffer_size = 0; + int16_t *system_samples_buffer = NULL; + + if (!DSGetLock(SpeakerVoice.lpDSBvoice, /*unused*/0, (unsigned long)num_samples_to_use*sizeof(int16_t), &system_samples_buffer, &system_buffer_size, NULL, NULL)) { + return num_samples; + } + + memcpy(system_samples_buffer, &samples_buffer[0], system_buffer_size); + + err = SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)system_samples_buffer, system_buffer_size, NULL, 0); + if (err) { + return num_samples; + } + } + + return num_samples_to_use; +} + +// -------------------------------------------------------------------------------------------------------------------- +// speaker public API functions + +void speaker_destroy(void) { + if (SpeakerVoice.lpDSBvoice && SpeakerVoice.bActive) { + SpeakerVoice.lpDSBvoice->Stop(SpeakerVoice.lpDSBvoice->_this); + SpeakerVoice.bActive = false; + } + DSReleaseSoundBuffer(&SpeakerVoice); +} + +void speaker_init(void) { + SpeakerVoice.bIsSpeaker = true; + SpeakerVoice.bActive = true; + long err = DSGetSoundBuffer(&SpeakerVoice, DSBCAPS_CTRLVOLUME, SOUNDCORE_BUFFER_SIZE, SPKR_SAMPLE_RATE, 1); + assert(!err); + _speaker_init_timing(); +} + +void speaker_reset(void) { + _speaker_init_timing(); +} + +void speaker_flush(void) { + assert(pthread_self() == cpu_thread_id); + + if (is_fullspeed) { + cycles_quiet_time = cycles_count_total; + speaker_going_silent = false; + speaker_accessed_since_last_flush = false; + } else { + if (speaker_accessed_since_last_flush) { + cycles_quiet_time = 0; + speaker_going_silent = false; + speaker_accessed_since_last_flush = false; + } else { + if (!cycles_quiet_time) { + cycles_quiet_time = cycles_count_total; + } + const unsigned int cycles_diff = (unsigned int)(cycles_persec_target/10); // 0.1sec of cycles + if ((cycles_count_total - cycles_quiet_time) > cycles_diff*2) { + // After 0.2sec of //e cycles time set inactive flag (allows auto-switch to full speed for fast disk access) + speaker_recently_active = false; + speaker_going_silent = false; + speaker_data = 0; + } else if ((speaker_data == speaker_amplitude) && (cycles_count_total - cycles_quiet_time > cycles_diff)) { + // After 0.1sec of //e cycles time start reducing samples to zero (if they aren't there already). This + // process attempts to mask the extraneous clicks when freezing/restarting emulation (GUI access) and + // glitching from the audio system backend + speaker_going_silent = true; + speaker_silent_step = 1; + SPEAKER_LOG("speaker going silent"); + } + } + } + _speaker_update(/*toggled:false*/); + + unsigned int samples_used = 0; + if (is_fullspeed) { + assert(!samples_buffer_idx && "should be all quiet samples"); + _submit_samples_buffer_fullspeed(); + } else if (samples_buffer_idx) { + samples_used = _submit_samples_buffer(samples_buffer_idx); + } + assert(samples_used <= samples_buffer_idx); + + if (samples_used) { + memmove(samples_buffer, &samples_buffer[samples_used], samples_buffer_idx-samples_used); + samples_buffer_idx -= samples_used; + } +} + +bool speaker_is_active(void) { + return SpeakerVoice.bActive && speaker_recently_active; +} + +void speaker_set_volume(int16_t amplitude) { + speaker_amplitude = (amplitude/4); +} + +double speaker_cycles_per_sample(void) { + return cycles_per_sample; +} + +// -------------------------------------------------------------------------------------------------------------------- +// VM system entry point + +GLUE_C_READ(speaker_toggle) +{ + assert(pthread_self() == cpu_thread_id); + + timing_checkpoint_cycles(); + +#if DIRECT_SPEAKER_ACCESS +#error this used to be implemented ... + // AFAIK ... this requires an actual speaker device and ability to access it (usually requiring this program to be + // running setuid-operator or (gasp) setuid-root ... maybe #else - dwByteOffset, (DWORD)nNumSamplesToUse*sizeof(short), -#endif - &pDSLockedBuffer0, &dwDSLockedBufferSize0, - &pDSLockedBuffer1, &dwDSLockedBufferSize1)) - return nNumSamples; + speaker_accessed_since_last_flush = true; + speaker_recently_active = true; - memcpy(pDSLockedBuffer0, &pSpeakerBuffer[0], dwDSLockedBufferSize0); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer0, dwDSLockedBufferSize0/sizeof(short)); + if (timing_should_auto_adjust_speed()) { + is_fullspeed = false; + } + + _speaker_update(/*toggled:true*/); + + if (!is_fullspeed) { + if (speaker_data) { + speaker_data = 0; + } else { + speaker_data = speaker_amplitude; + } + } #endif - if(pDSLockedBuffer1) - { - memcpy(pDSLockedBuffer1, &pSpeakerBuffer[dwDSLockedBufferSize0/sizeof(short)], dwDSLockedBufferSize1); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer1, dwDSLockedBufferSize1/sizeof(short)); -#endif - } - - // Commit sound buffer -#ifdef APPLE2IX - hr = SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#else - hr = SpeakerVoice.lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#endif - (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); - if(FAILED(hr)) - return nNumSamples; - -#ifndef APPLE2IX - dwByteOffset = (dwByteOffset + (DWORD)nNumSamplesToUse*sizeof(short)*g_nSPKR_NumChannels) % g_dwDSSpkrBufferSize; -#endif - } - - return bBufferError ? nNumSamples : nNumSamplesToUse; + return floating_bus(); } -//----------------------------------------------------------------------------- - -void Spkr_Mute() -{ - if(SpeakerVoice.bActive && !SpeakerVoice.bMute) - { -#ifdef APPLE2IX - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.lpDSBvoice->_this, DSBVOLUME_MIN); -#else - SpeakerVoice.lpDSBvoice->SetVolume(DSBVOLUME_MIN); -#endif - SpeakerVoice.bMute = true; - } -} - -void Spkr_Demute() -{ - if(SpeakerVoice.bActive && SpeakerVoice.bMute) - { -#ifdef APPLE2IX - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.lpDSBvoice->_this, SpeakerVoice.nVolume); -#else - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume); -#endif - SpeakerVoice.bMute = false; - } -} - -//----------------------------------------------------------------------------- - -static bool g_bSpkrRecentlyActive = false; - -static void Spkr_SetActive(bool bActive) -{ - if(!SpeakerVoice.bActive) - return; - - if(bActive) - { - // Called by SpkrToggle() or SpkrReset() - g_bSpkrRecentlyActive = true; - SpeakerVoice.bRecentlyActive = true; - } - else - { - // Called by SpkrUpdate() after 0.2s of speaker inactivity - g_bSpkrRecentlyActive = false; - SpeakerVoice.bRecentlyActive = false; - } -} - -bool Spkr_IsActive() -{ - return g_bSpkrRecentlyActive; -} - -//----------------------------------------------------------------------------- - -DWORD SpkrGetVolume() -{ - return SpeakerVoice.dwUserVolume; -} - -#ifdef APPLE2IX -// volume is square wave min and max samples -void SpkrSetVolume(short amplitude) -{ - g_nSpeakerData = amplitude; -} -#else -void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax) -{ - SpeakerVoice.dwUserVolume = dwVolume; - - SpeakerVoice.nVolume = NewVolume(dwVolume, dwVolumeMax); - - if(SpeakerVoice.bActive) - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume); -} -#endif - -//============================================================================= - -bool Spkr_DSInit() -{ - // - // Create single Apple speaker voice - // - - if(!g_bDSAvailable) - return false; - - SpeakerVoice.bIsSpeaker = true; - - HRESULT hr = DSGetSoundBuffer(&SpeakerVoice, DSBCAPS_CTRLVOLUME, g_dwDSSpkrBufferSize, SPKR_SAMPLE_RATE, 1); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "Spkr: DSGetSoundBuffer failed (%08X)\n",(unsigned int)hr); - return false; - } - -#ifndef APPLE2IX - if(!DSZeroVoiceBuffer(&SpeakerVoice, "Spkr", g_dwDSSpkrBufferSize)) - return false; -#endif - - SpeakerVoice.bActive = true; - -#ifndef APPLE2IX - // Volume might've been setup from value in Registry - if(!SpeakerVoice.nVolume) - SpeakerVoice.nVolume = DSBVOLUME_MAX; - - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume); - - // - - DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; - hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); - if(SUCCEEDED(hr) && (dwCurrentPlayCursor == dwCurrentWriteCursor)) - { - // KLUDGE: For my WinXP PC with "VIA AC'97 Enhanced Audio Controller" - // . Not required for my Win98SE/WinXP PC with PCI "Soundblaster Live!" - Sleep(200); - - hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); - char szDbg[100]; - sprintf(szDbg, "[DSInit] PC=%08X, WC=%08X, Diff=%08X\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor); OutputDebugString(szDbg); - } -#endif - return true; -} - -void Spkr_DSUninit() -{ - if(SpeakerVoice.lpDSBvoice && SpeakerVoice.bActive) - { -#ifdef APPLE2IX - SpeakerVoice.lpDSBvoice->Stop(SpeakerVoice.lpDSBvoice->_this); -#else - SpeakerVoice.lpDSBvoice->Stop(); -#endif - SpeakerVoice.bActive = false; - } - - DSReleaseSoundBuffer(&SpeakerVoice); -} - -//============================================================================= - -#if !defined(APPLE2IX) -DWORD SpkrGetSnapshot(SS_IO_Speaker* pSS) -{ - pSS->g_nSpkrLastCycle = g_nSpkrLastCycle; - return 0; -} - -DWORD SpkrSetSnapshot(SS_IO_Speaker* pSS) -{ - g_nSpkrLastCycle = pSS->g_nSpkrLastCycle; - return 0; -} -#endif - diff --git a/src/audio/speaker.h b/src/audio/speaker.h index e8587d93..e4369a2c 100644 --- a/src/audio/speaker.h +++ b/src/audio/speaker.h @@ -12,48 +12,21 @@ #ifndef _SPEAKER_H_ #define _SPEAKER_H_ -#ifdef APPLE2IX -#include "audio/win-shim.h" -#endif - -extern DWORD soundtype; -extern double g_fClksPerSpkrSample; - -void SpkrDestroy (); -void SpkrInitialize (); -void SpkrReinitialize (); -void SpkrReset(); -BOOL SpkrSetEmulationType (HWND,DWORD); -void SpkrUpdate (DWORD); -void SpkrUpdate_Timer(); -void Spkr_SetErrorInc(const int nErrorInc); -void Spkr_SetErrorMax(const int nErrorMax); -DWORD SpkrGetVolume(); -#ifdef APPLE2IX +#define SPKR_SAMPLE_RATE 22050 #define SPKR_DATA_INIT 0x4000 -void SpkrSetVolume(short amplitude); -#else -void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax); -#endif -void Spkr_Mute(); -void Spkr_Demute(); -bool Spkr_IsActive(); -bool Spkr_DSInit(); -void Spkr_DSUninit(); -#ifdef APPLE2IX -#define __int64 -typedef struct { - unsigned __int64 g_nSpkrLastCycle; -} SS_IO_Speaker; -#endif -DWORD SpkrGetSnapshot(SS_IO_Speaker* pSS); -DWORD SpkrSetSnapshot(SS_IO_Speaker* pSS); -#ifdef APPLE2IX -void SpkrToggle(); -#else -BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft); -#endif +void speaker_init(void); +void speaker_destroy(void); +void speaker_reset(void); +void speaker_flush(void); +void speaker_set_volume(int16_t amplitude); +bool speaker_is_active(void); + +/* + * returns the machine cycles per sample + * - for emulator running at normal speed: CLK_6502 / SPKR_SAMPLE_RATE == ~46 + */ +double speaker_cycles_per_sample(void); #endif /* whole file */ diff --git a/src/cpu-supp.c b/src/cpu-supp.c index 1b8114c5..f490b37c 100644 --- a/src/cpu-supp.c +++ b/src/cpu-supp.c @@ -36,8 +36,6 @@ uint8_t cpu65_rw; uint8_t cpu65_opcode; uint8_t cpu65_opcycles; -int16_t cpu65_cycle_count = 0; -int16_t cpu65_cycles_to_execute = 0; uint8_t cpu65__signal = 0; static pthread_mutex_t irq_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -831,7 +829,7 @@ GLUE_C_WRITE(cpu65_trace_epilogue) void cpu65_trace_checkpoint(void) { if (cpu_trace_fp) { - fprintf(cpu_trace_fp, "---TOTAL CYC:%lu\n",g_nCumulativeCycles); + fprintf(cpu_trace_fp, "---TOTAL CYC:%lu\n",cycles_count_total); fflush(cpu_trace_fp); } } diff --git a/src/cpu.h b/src/cpu.h index caecb240..61591052 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -58,7 +58,6 @@ extern unsigned char cpu65_flags_encode[256]; extern unsigned char cpu65_flags_decode[256]; extern int16_t cpu65_cycle_count; -extern int16_t cpu65_cycles_to_execute; #if CPU_TRACING void cpu65_trace_begin(const char *trace_file); diff --git a/src/disk.c b/src/disk.c index d20c2317..011dfbc2 100644 --- a/src/disk.c +++ b/src/disk.c @@ -603,33 +603,33 @@ GLUE_C_READ(disk_read_phase) #endif } - return ea == 0xE0 ? 0xFF : floating_bus_hibit(1, cpu65_cycle_count); + return ea == 0xE0 ? 0xFF : floating_bus_hibit(1); } GLUE_C_READ(disk_read_motor_off) { clock_gettime(CLOCK_MONOTONIC, &disk6.motor_time); disk6.motor_off = 1; - return floating_bus_hibit(1, cpu65_cycle_count); + return floating_bus_hibit(1); } GLUE_C_READ(disk_read_motor_on) { clock_gettime(CLOCK_MONOTONIC, &disk6.motor_time); disk6.motor_off = 0; - return floating_bus_hibit(1, cpu65_cycle_count); + return floating_bus_hibit(1); } GLUE_C_READ(disk_read_select_a) { disk6.drive = 0; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(disk_read_select_b) { disk6.drive = 1; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(disk_read_latch) @@ -640,13 +640,13 @@ GLUE_C_READ(disk_read_latch) GLUE_C_READ(disk_read_prepare_in) { disk6.ddrw = 0; - return floating_bus_hibit(disk6.disk[disk6.drive].is_protected, cpu65_cycle_count); + return floating_bus_hibit(disk6.disk[disk6.drive].is_protected); } GLUE_C_READ(disk_read_prepare_out) { disk6.ddrw = 1; - return floating_bus_hibit(1, cpu65_cycle_count); + return floating_bus_hibit(1); } GLUE_C_WRITE(disk_write_latch) diff --git a/src/display.c b/src/display.c index e1ed1481..625fc89a 100644 --- a/src/display.c +++ b/src/display.c @@ -1151,8 +1151,8 @@ void video_redraw(void) { // References to Jim Sather's books are given as eg: // UTAIIe:5-7,P3 (Understanding the Apple IIe, chapter 5, page 7, Paragraph 3) -extern unsigned int CpuGetCyclesThisVideoFrame(const unsigned int nExecutedCycles); -uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedCycles) { +extern unsigned int CpuGetCyclesThisVideoFrame(void); +uint16_t video_scanner_get_address(bool *vblBarOut) { const bool SW_HIRES = (softswitches & SS_HIRES); const bool SW_TEXT = (softswitches & SS_TEXT); const bool SW_PAGE2 = (softswitches & SS_PAGE2); @@ -1160,7 +1160,7 @@ uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedC const bool SW_MIXED = (softswitches & SS_MIXED); // get video scanner position - unsigned int nCycles = CpuGetCyclesThisVideoFrame(executedCycles); + unsigned int nCycles = CpuGetCyclesThisVideoFrame(); // machine state switches int nHires = (SW_HIRES && !SW_TEXT) ? 1 : 0; @@ -1258,13 +1258,13 @@ uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedC return (uint16_t)nAddress; } -uint8_t floating_bus(const unsigned int executedCycles) { - uint16_t scanner_addr = video_scanner_get_address(NULL, executedCycles); +uint8_t floating_bus(void) { + uint16_t scanner_addr = video_scanner_get_address(NULL); return apple_ii_64k[0][scanner_addr]; } -uint8_t floating_bus_hibit(const bool hibit, const unsigned int executedCycles) { - uint16_t scanner_addr = video_scanner_get_address(NULL, executedCycles); +uint8_t floating_bus_hibit(const bool hibit) { + uint16_t scanner_addr = video_scanner_get_address(NULL); uint8_t b = apple_ii_64k[0][scanner_addr]; return (b & ~0x80) | (hibit ? 0x80 : 0); } diff --git a/src/interface.c b/src/interface.c index 0f46cbe5..eb5f3ddb 100644 --- a/src/interface.c +++ b/src/interface.c @@ -1280,7 +1280,7 @@ void c_interface_parameters() #ifdef __linux__ LOG("Back to Linux, w00t!\n"); #endif - SpkrDestroy(); + speaker_destroy(); MB_Destroy(); video_shutdown(); diff --git a/src/meta/debugger.c b/src/meta/debugger.c index bad6c412..e7541e75 100644 --- a/src/meta/debugger.c +++ b/src/meta/debugger.c @@ -1140,10 +1140,10 @@ static int begin_cpu_stepping() { // kludgey set max CPU speed... double saved_scale = cpu_scale_factor; double saved_altscale = cpu_altscale_factor; - bool saved_fullspeed = g_bFullSpeed; + bool saved_fullspeed = is_fullspeed; cpu_scale_factor = CPU_SCALE_FASTEST; cpu_altscale_factor = CPU_SCALE_FASTEST; - g_bFullSpeed = true; + is_fullspeed = true; unsigned int idx = 0; size_t textlen = 0; @@ -1197,7 +1197,7 @@ static int begin_cpu_stepping() { cpu_scale_factor = saved_scale; cpu_altscale_factor = saved_altscale; - g_bFullSpeed = saved_fullspeed; + is_fullspeed = saved_fullspeed; return ch; } diff --git a/src/misc.c b/src/misc.c index 691f2114..44a556b4 100644 --- a/src/misc.c +++ b/src/misc.c @@ -520,7 +520,7 @@ void c_initialize_apple_ii_memory() void c_initialize_sound_hooks() { #ifdef AUDIO_ENABLED - SpkrSetVolume(sound_volume * (SPKR_DATA_INIT/10)); + speaker_set_volume(sound_volume * (SPKR_DATA_INIT/10)); #endif for (int i = 0xC030; i < 0xC040; i++) { @@ -581,8 +581,11 @@ void c_initialize_vm() { void c_initialize_firsttime() ------------------------------------------------------------------------- */ -void reinitialize(void) -{ +void reinitialize(void) { + assert(pthread_self() == cpu_thread_id); + + cycles_count_total = 0; + c_initialize_vm(); softswitches = SS_TEXT | SS_IOUDIS | SS_C3ROM | SS_LCWRT | SS_LCSEC; @@ -600,8 +603,7 @@ void reinitialize(void) #endif } -void c_initialize_firsttime() -{ +void c_initialize_firsttime(void) { #ifdef INTERFACE_CLASSIC /* read in system files and calculate system defaults */ c_load_interface_font(); @@ -610,18 +612,9 @@ void c_initialize_firsttime() /* initialize the video system */ video_init(); - // TODO FIXME : sound system never released ... -#ifdef AUDIO_ENABLED - DSInit(); - SpkrInitialize(); - MB_Initialize(); -#endif - #ifdef DEBUGGER c_debugger_init(); #endif - - reinitialize(); } #if !defined(TESTING) && !defined(__APPLE__) diff --git a/src/test/testcommon.c b/src/test/testcommon.c index 820c22fa..77043c64 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -41,7 +41,7 @@ uint8_t c_PhasorIO(uint16_t addr) { return 0x0; } -void SpkrToggle() { +void c_speaker_toggle(void) { } void c_interface_print(int x, int y, const int cs, const char *s) { @@ -119,7 +119,7 @@ void test_common_init(bool do_cputhread) { // kludgey set max CPU speed... cpu_scale_factor = CPU_SCALE_FASTEST; cpu_altscale_factor = CPU_SCALE_FASTEST; - g_bFullSpeed = true; + is_fullspeed = true; caps_lock = true; diff --git a/src/test/testcpu.c b/src/test/testcpu.c index 71d850c5..323a3081 100644 --- a/src/test/testcpu.c +++ b/src/test/testcpu.c @@ -52,6 +52,7 @@ static void testcpu_setup(void *arg) { //reinitialize(); cpu65_uninterrupt(0xff); + extern int16_t cpu65_cycles_to_execute; cpu65_cycles_to_execute = 1; cpu65_pc = TEST_LOC; diff --git a/src/timing.c b/src/timing.c index 9f4956a9..99ecad7b 100644 --- a/src/timing.c +++ b/src/timing.c @@ -10,9 +10,24 @@ */ /* - * 65c02 CPU Timing Support. Some source derived from AppleWin. + * 65c02 CPU timing support. Source inspired/derived from AppleWin. * - * Copyleft 2013 Aaron Culliney + * Simplified timing loop for each execution period: + * + * ..{...+....[....|..................|.........]....^....|....^....^....}...... + * ti MBB CHK CHK MBE CHX SPK MBX tj ZZZ + * + * - ti : timing sample begin (lock out interface thread) + * - tj : timing sample end (unlock interface thread) + * - [ : cpu65_run() + * - ] : cpu65_run() finished + * - CHK : incoming timing_checkpoint_cycles() call from IO (bumps cycles_count_total) + * - CHX : update remainder of timing_checkpoint_cycles() for execution period + * - MBB : Mockingboard begin + * - MBE : Mockingboard end/flush (output) + * - MBX : Mockingboard end video frame (output) + * - SPK : Speaker output + * - ZZZ : housekeeping+sleep (or not) * */ @@ -29,31 +44,32 @@ #define DISK_MOTOR_QUIET_NSECS 2000000 -static const unsigned int uCyclesPerLine = 65; // 25 cycles of HBL & 40 cycles of HBL' -static const unsigned int uVisibleLinesPerFrame = 64*3; // 192 -static const unsigned int uLinesPerFrame = 262; // 64 in each third of the screen & 70 in VBL -static const unsigned int dwClksPerFrame = uCyclesPerLine * uLinesPerFrame; // 17030 +// VBL constants? +#define uCyclesPerLine 65 // 25 cycles of HBL & 40 cycles of HBL' +#define uVisibleLinesPerFrame (64*3) // 192 +#define uLinesPerFrame (262) // 64 in each third of the screen & 70 in VBL +#define dwClksPerFrame (uCyclesPerLine * uLinesPerFrame) // 17030 -double g_fCurrentCLK6502 = CLK_6502; -bool g_bFullSpeed = false; // HACK TODO FIXME : prolly shouldn't be global anymore -- don't think it's necessary for speaker/soundcore/etc anymore ... -uint64_t g_nCumulativeCycles = 0; // cumulative cycles since emulator (re)start -int g_nCpuCyclesFeedback = 0; +// cycle counting +double cycles_persec_target = CLK_6502; +unsigned long long cycles_count_total = 0; +int cycles_speaker_feedback = 0; +int16_t cpu65_cycles_to_execute = 0; // cycles-to-execute by cpu65_run() +int16_t cpu65_cycle_count = 0; // cycles currently excuted by cpu65_run() +static int16_t cycles_checkpoint_count = 0; static unsigned int g_dwCyclesThisFrame = 0; -static bool alt_speed_enabled = false; - +// scaling and speed adjustments +static bool auto_adjust_speed = true; double cpu_scale_factor = 1.0; double cpu_altscale_factor = 1.0; -bool auto_adjust_speed = true; - -int gc_cycles_timer_0 = 0; -int gc_cycles_timer_1 = 0; +bool is_fullspeed = false; +static bool alt_speed_enabled = false; +// misc volatile uint8_t emul_reinitialize = 0; pthread_t cpu_thread_id = 0; -static unsigned int g_nCyclesExecuted = 0; // # of cycles executed up to last IO access - // ----------------------------------------------------------------------------- struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative) { @@ -94,68 +110,59 @@ struct timespec timespec_diff(struct timespec start, struct timespec end, bool * static inline struct timespec timespec_add(struct timespec start, unsigned long nsecs) { start.tv_nsec += nsecs; - if (start.tv_nsec > NANOSECONDS) + if (start.tv_nsec > NANOSECONDS_PER_SECOND) { - start.tv_sec += (start.tv_nsec / NANOSECONDS); - start.tv_nsec %= NANOSECONDS; + start.tv_sec += (start.tv_nsec / NANOSECONDS_PER_SECOND); + start.tv_nsec %= NANOSECONDS_PER_SECOND; } return start; } -static void _timing_initialize(double scale) -{ - if (g_bFullSpeed) - { - TIMING_LOG("timing_initialize() emulation at fullspeed ..."); - return; +static void _timing_initialize(double scale) { + is_fullspeed = (scale >= CPU_SCALE_FASTEST); + if (!is_fullspeed) { + cycles_persec_target = CLK_6502 * scale; } - - g_fCurrentCLK6502 = CLK_6502 * scale; - // this is extracted out of SetClksPerSpkrSample -- speaker.c #ifdef AUDIO_ENABLED - g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE); - SpkrReinitialize(); - - TIMING_LOG("timing_initialize() ... ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", g_fCurrentCLK6502, g_fClksPerSpkrSample); + speaker_reset(); + //TIMING_LOG("ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", cycles_persec_target, speaker_cycles_per_sample()); #endif } -static inline void _switch_to_fullspeed(void) -{ - g_bFullSpeed = true; +void timing_initialize(void) { + _timing_initialize(alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor); } -static inline void _switch_to_regular_speed(double scale) -{ - g_bFullSpeed = false; - _timing_initialize(scale); -} - -void timing_toggle_cpu_speed(void) -{ +void timing_toggle_cpu_speed(void) { alt_speed_enabled = !alt_speed_enabled; - double scale_factor = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor; - if (scale_factor >= CPU_SCALE_FASTEST) - { - _switch_to_fullspeed(); - } - else - { - _switch_to_regular_speed(scale_factor); - } + timing_initialize(); } -void timing_initialize(void) -{ - _timing_initialize(cpu_scale_factor); +void timing_set_auto_adjust_speed(bool auto_adjust) { + auto_adjust_speed = auto_adjust; + timing_initialize(); +} + +bool timing_should_auto_adjust_speed(void) { + double speed = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor; + return auto_adjust_speed && (speed < CPU_SCALE_FASTEST); } void *cpu_thread(void *dummyptr) { assert(pthread_self() == cpu_thread_id); +#ifdef AUDIO_ENABLED + DSInit(); + speaker_init(); + MB_Initialize(); +#endif + + reinitialize(); + struct timespec deltat; + struct timespec disk_motor_time; struct timespec t0; // the target timer struct timespec ti, tj; // actual time samples bool negative = false; @@ -173,7 +180,6 @@ void *cpu_thread(void *dummyptr) { do { - g_nCumulativeCycles = 0; LOG("cpu_thread : begin main loop ..."); clock_gettime(CLOCK_MONOTONIC, &t0); @@ -187,7 +193,9 @@ void *cpu_thread(void *dummyptr) { deltat = timespec_diff(t0, ti, &negative); if (deltat.tv_sec) { - TIMING_LOG("NOTE : serious divergence from target time ..."); + if (!is_fullspeed) { + TIMING_LOG("NOTE : serious divergence from target time ..."); + } t0 = ti; deltat = timespec_diff(t0, ti, &negative); } @@ -195,15 +203,15 @@ void *cpu_thread(void *dummyptr) { drift_adj_nsecs = negative ? ~deltat.tv_nsec : deltat.tv_nsec; // set up increment & decrement counters - cpu65_cycles_to_execute = (g_fCurrentCLK6502 / 1000); // g_fCurrentCLK6502 * EXECUTION_PERIOD_NSECS / NANOSECONDS - cpu65_cycles_to_execute += g_nCpuCyclesFeedback; + cpu65_cycles_to_execute = (cycles_persec_target / 1000); // cycles_persec_target * EXECUTION_PERIOD_NSECS / NANOSECONDS_PER_SECOND + if (!is_fullspeed) { + cpu65_cycles_to_execute += cycles_speaker_feedback; + } if (cpu65_cycles_to_execute < 0) { cpu65_cycles_to_execute = 0; } - g_nCyclesExecuted = 0; - #ifdef AUDIO_ENABLED MB_StartOfCpuExecute(); #endif @@ -218,6 +226,7 @@ void *cpu_thread(void *dummyptr) { } cpu65_cycle_count = 0; + cycles_checkpoint_count = 0; cpu65_run(); // run emulation for cpu65_cycles_to_execute cycles ... if (is_debugging) { @@ -243,32 +252,23 @@ void *cpu_thread(void *dummyptr) { #if DEBUG_TIMING dbg_cycles_executed += cpu65_cycle_count; #endif - unsigned int uActualCyclesExecuted = cpu65_cycle_count; - g_dwCyclesThisFrame += uActualCyclesExecuted; + g_dwCyclesThisFrame += cpu65_cycle_count; #ifdef AUDIO_ENABLED - MB_UpdateCycles(uActualCyclesExecuted); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below) + MB_UpdateCycles(); // update 6522s (NOTE: do this before updating cycles_count_total) #endif - // N.B.: IO calls that depend on accurate timing will update g_nCyclesExecuted - const int nRemainingCycles = uActualCyclesExecuted - g_nCyclesExecuted; - assert(nRemainingCycles >= 0); - g_nCumulativeCycles += nRemainingCycles; + timing_checkpoint_cycles(); #if CPU_TRACING cpu65_trace_checkpoint(); #endif - if (!g_bFullSpeed) - { #ifdef AUDIO_ENABLED - SpkrUpdate(uActualCyclesExecuted); // play audio + speaker_flush(); // play audio #endif - } - if (g_dwCyclesThisFrame >= dwClksPerFrame) - { + if (g_dwCyclesThisFrame >= dwClksPerFrame) { g_dwCyclesThisFrame -= dwClksPerFrame; - //VideoEndOfVideoFrame(); #ifdef AUDIO_ENABLED MB_EndOfVideoFrame(); #endif @@ -278,29 +278,21 @@ void *cpu_thread(void *dummyptr) { pthread_mutex_unlock(&interface_mutex); // -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj - if (auto_adjust_speed) { - deltat = timespec_diff(disk6.motor_time, tj, &negative); + if (timing_should_auto_adjust_speed()) { + disk_motor_time = timespec_diff(disk6.motor_time, tj, &negative); assert(!negative); - if (!g_bFullSpeed && + if (!is_fullspeed && #ifdef AUDIO_ENABLED - !Spkr_IsActive() && + !speaker_is_active() && #endif - !video_dirty() && (!disk6.motor_off && (deltat.tv_sec || deltat.tv_nsec > DISK_MOTOR_QUIET_NSECS)) ) + !video_dirty() && (!disk6.motor_off && (disk_motor_time.tv_sec || disk_motor_time.tv_nsec > DISK_MOTOR_QUIET_NSECS)) ) { TIMING_LOG("auto switching to full speed"); - _switch_to_fullspeed(); - } else if (g_bFullSpeed && ( -#ifdef AUDIO_ENABLED - Spkr_IsActive() || -#endif - video_dirty() || (disk6.motor_off && (deltat.tv_sec || deltat.tv_nsec > DISK_MOTOR_QUIET_NSECS))) ) - { - TIMING_LOG("auto switching to regular speed"); - _switch_to_regular_speed(alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor); + _timing_initialize(CPU_SCALE_FASTEST); } } - if (!g_bFullSpeed) { + if (!is_fullspeed) { deltat = timespec_diff(ti, tj, &negative); assert(!negative); long sleepfor = 0; @@ -328,19 +320,19 @@ void *cpu_thread(void *dummyptr) { #if DEBUG_TIMING // collect timing statistics - if (speaker_neg_feedback > g_nCpuCyclesFeedback) + if (speaker_neg_feedback > cycles_speaker_feedback) { - speaker_neg_feedback = g_nCpuCyclesFeedback; + speaker_neg_feedback = cycles_speaker_feedback; } - if (speaker_pos_feedback < g_nCpuCyclesFeedback) + if (speaker_pos_feedback < cycles_speaker_feedback) { - speaker_pos_feedback = g_nCpuCyclesFeedback; + speaker_pos_feedback = cycles_speaker_feedback; } dbg_ticks += EXECUTION_PERIOD_NSECS; - if ((dbg_ticks % NANOSECONDS) == 0) + if ((dbg_ticks % NANOSECONDS_PER_SECOND) == 0) { - LOG("tick:(%ld.%ld) real:(%ld.%ld) cycles exe: %d ... speaker feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, dbg_cycles_executed, speaker_neg_feedback, speaker_pos_feedback); + TIMING_LOG("tick:(%ld.%ld) real:(%ld.%ld) cycles exe: %d ... speaker feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, dbg_cycles_executed, speaker_neg_feedback, speaker_pos_feedback); dbg_cycles_executed = 0; dbg_ticks = 0; speaker_neg_feedback = 0; @@ -348,6 +340,21 @@ void *cpu_thread(void *dummyptr) { } #endif } + + if (timing_should_auto_adjust_speed()) { + if (is_fullspeed && ( +#ifdef AUDIO_ENABLED + speaker_is_active() || +#endif + video_dirty() || (disk6.motor_off && (disk_motor_time.tv_sec || disk_motor_time.tv_nsec > DISK_MOTOR_QUIET_NSECS))) ) + { + double speed = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor; + if (speed < CPU_SCALE_FASTEST) { + TIMING_LOG("auto switching to configured speed"); + _timing_initialize(speed); + } + } + } } while (!emul_reinitialize); reinitialize(); @@ -356,23 +363,16 @@ void *cpu_thread(void *dummyptr) { return NULL; } -// From AppleWin... - -unsigned int CpuGetCyclesThisVideoFrame(const unsigned int nExecutedCycles) { - CpuCalcCycles(nExecutedCycles); - return g_dwCyclesThisFrame + g_nCyclesExecuted; +unsigned int CpuGetCyclesThisVideoFrame(void) { + timing_checkpoint_cycles(); + return g_dwCyclesThisFrame + cycles_checkpoint_count; } -// Called when an IO-reg is accessed & accurate cycle info is needed -void CpuCalcCycles(const unsigned long nExecutedCycles) { - // Calc # of cycles executed since this func was last called - const long nCycles = nExecutedCycles - g_nCyclesExecuted; - assert(nCycles >= 0); - g_nCumulativeCycles += nCycles; - // HACK FIXME TODO -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wshorten-64-to-32" - g_nCyclesExecuted = nExecutedCycles; -#pragma clang diagnostic pop +// Called when an IO-reg is accessed & accurate global cycle count info is needed +void timing_checkpoint_cycles(void) { + const int16_t d = cpu65_cycle_count - cycles_checkpoint_count; + assert(d >= 0); + cycles_count_total += d; + cycles_checkpoint_count = cpu65_cycle_count; } diff --git a/src/timing.h b/src/timing.h index c63269ff..74336ce6 100644 --- a/src/timing.h +++ b/src/timing.h @@ -21,45 +21,71 @@ #include "common.h" -#define NANOSECONDS 1000000000 +#if !defined(NANOSECONDS_PER_SECOND) +#define NANOSECONDS_PER_SECOND 1000000000 +#endif -// timing values cribbed from AppleWin ... should double-check _Understanding the Apple IIe_ +// timing values cribbed from AppleWin ... reference: Sather's _Understanding the Apple IIe_ +// TODO: revisit this if/when attempting to actually sync up VBL/VSYNC to actual device vsync // 14318181.81... -#define _M14 (157500000.0 / 11.0) +#define _M14 (157500000.0 / 11.0) +#define _M14_INT (157500000 / 11) // 65 cycles per 912 14M clocks = 1020484.45... -#define CLK_6502 ((_M14 * 65.0) / 912.0) +#define CLK_6502 ((_M14 * 65.0) / 912.0) +#define CLK_6502_INT ((_M14_INT * 65) / 912) #define CPU_SCALE_SLOWEST 0.25 #define CPU_SCALE_FASTEST 4.05 -#define CPU_SCALE_STEP_DIV 0.01 -#define CPU_SCALE_STEP 0.05 +#ifdef INTERFACE_CLASSIC +# define CPU_SCALE_STEP_DIV 0.01 +# define CPU_SCALE_STEP 0.05 +#endif -#define SPKR_SAMPLE_RATE 22050 +extern unsigned long long cycles_count_total;// cumulative cycles count from machine reset +extern double cycles_persec_target; // CLK_6502 * current CPU scale +extern int cycles_speaker_feedback; // current -/+ speaker requested feedback -extern double g_fCurrentCLK6502; -extern bool g_bFullSpeed; -extern uint64_t g_nCumulativeCycles; -extern int g_nCpuCyclesFeedback; - -extern double cpu_scale_factor; -extern double cpu_altscale_factor; -extern bool auto_adjust_speed; +extern double cpu_scale_factor; // scale factor #1 +extern double cpu_altscale_factor; // scale factor #2 +extern bool is_fullspeed; // emulation in full native speed? extern pthread_t cpu_thread_id; -extern int gc_cycles_timer_0; -extern int gc_cycles_timer_1; - +/* + * calculate the difference between two timespec structures + */ struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative); +/* + * toggles CPU speed between configured values + */ void timing_toggle_cpu_speed(void); +/* + * (dis)allow automatic adjusting of CPU speed between presently configured and max (when loading disk images). + */ +void timing_set_auto_adjust_speed(bool auto_adjust); + +/* + * check whether automatic adjusting of CPU speed is configured. + */ +bool timing_should_auto_adjust_speed(void); + +/* + * initialize timing + */ void timing_initialize(void); +/* + * timing/CPU thread entry point + */ void *cpu_thread(void *ignored); -void CpuCalcCycles(const unsigned long nExecutedCycles); +/* + * checkpoints current cycle count and updates total (for timing-dependent I/O) + */ +void timing_checkpoint_cycles(void); #endif // whole file diff --git a/src/video/video.h b/src/video/video.h index e8797124..e6e4bcbe 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -124,9 +124,9 @@ bool video_dirty(void); /* * VBL routines */ -uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedCycles); -uint8_t floating_bus(const unsigned int executedCycles); -uint8_t floating_bus_hibit(const bool hibit, const unsigned int executedCycles); +uint16_t video_scanner_get_address(bool *vblBarOut); +uint8_t floating_bus(void); +uint8_t floating_bus_hibit(const bool hibit); #endif /* !__ASSEMBLER__ */ diff --git a/src/vm.c b/src/vm.c index c618cf42..63ca78ac 100644 --- a/src/vm.c +++ b/src/vm.c @@ -113,6 +113,10 @@ #include "common.h" +// joystick timer values +int gc_cycles_timer_0 = 0; +int gc_cycles_timer_1 = 0; + #if VM_TRACING FILE *test_vm_fp = NULL; @@ -124,7 +128,7 @@ typedef struct vm_trace_range_t { GLUE_C_READ(ram_nop) { - return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(cpu65_cycle_count); + return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(); } GLUE_C_READ(read_unmapped_softswitch) @@ -146,21 +150,13 @@ GLUE_C_READ(read_keyboard_strobe) return apple_ii_64k[0][0xC000]; } -GLUE_C_READ(speaker_toggle) -{ -#ifdef AUDIO_ENABLED - SpkrToggle(); -#endif - return floating_bus(cpu65_cycle_count); -} - // ---------------------------------------------------------------------------- // graphics softswitches GLUE_C_READ(iie_page2_off) { if (!(softswitches & SS_PAGE2)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~(SS_PAGE2|SS_SCREEN); @@ -178,13 +174,13 @@ GLUE_C_READ(iie_page2_off) video_setpage(0); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_page2_on) { if (softswitches & SS_PAGE2) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_PAGE2; @@ -203,7 +199,7 @@ GLUE_C_READ(iie_page2_on) video_setpage(1); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_page2) @@ -217,7 +213,7 @@ GLUE_C_READ(read_switch_graphics) softswitches &= ~SS_TEXT; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(read_switch_text) @@ -226,7 +222,7 @@ GLUE_C_READ(read_switch_text) softswitches |= SS_TEXT; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_text) @@ -240,7 +236,7 @@ GLUE_C_READ(read_switch_no_mixed) softswitches &= ~SS_MIXED; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(read_switch_mixed) @@ -249,7 +245,7 @@ GLUE_C_READ(read_switch_mixed) softswitches |= SS_MIXED; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_mixed) @@ -262,13 +258,13 @@ GLUE_C_READ(iie_annunciator) if ((ea >= 0xC058) && (ea <= 0xC05B)) { // TODO: alternate joystick management? } - return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(cpu65_cycle_count); + return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(); } GLUE_C_READ(iie_hires_off) { if (!(softswitches & SS_HIRES)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~(SS_HIRES|SS_HGRRD|SS_HGRWRT); @@ -286,13 +282,13 @@ GLUE_C_READ(iie_hires_off) } video_redraw(); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_hires_on) { if (softswitches & SS_HIRES) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_HIRES; @@ -310,7 +306,7 @@ GLUE_C_READ(iie_hires_on) } video_redraw(); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_hires) @@ -356,7 +352,7 @@ GLUE_C_READ(read_gc_strobe) } // NOTE (possible TODO FIXME): unimplemented GC2 and GC3 timers since they were not wired on the //e ... - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(read_gc0) @@ -381,12 +377,12 @@ GLUE_C_READ(read_gc1) GLUE_C_READ(iie_read_gc2) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_read_gc3) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } // ---------------------------------------------------------------------------- @@ -418,7 +414,7 @@ GLUE_C_READ(iie_c080) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c081) @@ -438,7 +434,7 @@ GLUE_C_READ(iie_c081) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(lc_c082) @@ -452,7 +448,7 @@ GLUE_C_READ(lc_c082) base_d000_wrt = 0; base_e000_wrt = 0; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c083) @@ -470,7 +466,7 @@ GLUE_C_READ(iie_c083) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c088) @@ -487,7 +483,7 @@ GLUE_C_READ(iie_c088) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c089) @@ -507,7 +503,7 @@ GLUE_C_READ(iie_c089) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(lc_c08a) @@ -520,7 +516,7 @@ GLUE_C_READ(lc_c08a) base_d000_wrt = 0; base_e000_wrt = 0; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c08b) @@ -540,7 +536,7 @@ GLUE_C_READ(iie_c08b) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_bank) @@ -559,7 +555,7 @@ GLUE_C_READ(iie_check_lcram) GLUE_C_READ(iie_80store_off) { if (!(softswitches & SS_80STORE)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~(SS_80STORE|SS_TEXTRD|SS_TEXTWRT|SS_HGRRD|SS_HGRWRT); @@ -586,13 +582,13 @@ GLUE_C_READ(iie_80store_off) video_setpage(1); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_80store_on) { if (softswitches & SS_80STORE) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_80STORE; @@ -619,7 +615,7 @@ GLUE_C_READ(iie_80store_on) softswitches &= ~SS_SCREEN; video_setpage(0); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_80store) @@ -630,7 +626,7 @@ GLUE_C_READ(iie_check_80store) GLUE_C_READ(iie_ramrd_main) { if (!(softswitches & SS_RAMRD)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_RAMRD; @@ -647,13 +643,13 @@ GLUE_C_READ(iie_ramrd_main) base_hgrrd = apple_ii_64k[0]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_ramrd_aux) { if (softswitches & SS_RAMRD) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_RAMRD; @@ -670,7 +666,7 @@ GLUE_C_READ(iie_ramrd_aux) base_hgrrd = apple_ii_64k[1]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_ramrd) @@ -681,7 +677,7 @@ GLUE_C_READ(iie_check_ramrd) GLUE_C_READ(iie_ramwrt_main) { if (!(softswitches & SS_RAMWRT)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_RAMWRT; @@ -698,13 +694,13 @@ GLUE_C_READ(iie_ramwrt_main) base_hgrwrt = apple_ii_64k[0]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_ramwrt_aux) { if (softswitches & SS_RAMWRT) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_RAMWRT; @@ -721,7 +717,7 @@ GLUE_C_READ(iie_ramwrt_aux) base_hgrwrt = apple_ii_64k[1]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_ramwrt) @@ -733,7 +729,7 @@ GLUE_C_READ_ALTZP(iie_altzp_main) { if (!(softswitches & SS_ALTZP)) { /* NOTE : test if ALTZP already off - due to d000-bank issues it is *needed*, not just a shortcut */ - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_ALTZP; @@ -749,14 +745,14 @@ GLUE_C_READ_ALTZP(iie_altzp_main) base_e000_wrt = language_card[0] - 0xE000; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ_ALTZP(iie_altzp_aux) { if (softswitches & SS_ALTZP) { /* NOTE : test if ALTZP already on - due to d000-bank issues it is *needed*, not just a shortcut */ - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_ALTZP; @@ -764,7 +760,7 @@ GLUE_C_READ_ALTZP(iie_altzp_aux) _lc_to_auxmem(); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_altzp) @@ -775,7 +771,7 @@ GLUE_C_READ(iie_check_altzp) GLUE_C_READ(iie_80col_off) { if (!(softswitches & SS_80COL)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_80COL; @@ -784,13 +780,13 @@ GLUE_C_READ(iie_80col_off) video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_80col_on) { if (softswitches & SS_80COL) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_80COL; @@ -799,7 +795,7 @@ GLUE_C_READ(iie_80col_on) video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_80col) @@ -813,7 +809,7 @@ GLUE_C_READ(iie_altchar_off) softswitches &= ~SS_ALTCHAR; c_set_primary_char(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_altchar_on) @@ -822,7 +818,7 @@ GLUE_C_READ(iie_altchar_on) softswitches |= SS_ALTCHAR; c_set_altchar(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_altchar) @@ -854,7 +850,7 @@ GLUE_C_READ(iie_dhires_on) softswitches |= SS_DHIRES; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_dhires_off) @@ -863,7 +859,7 @@ GLUE_C_READ(iie_dhires_off) softswitches &= ~SS_DHIRES; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_dhires) @@ -875,7 +871,7 @@ GLUE_C_READ(iie_check_dhires) GLUE_C_READ(iie_check_vbl) { bool vbl_bar = false; - video_scanner_get_address(&vbl_bar, cpu65_cycle_count); + video_scanner_get_address(&vbl_bar); uint8_t key = apple_ii_64k[0][0xC000]; return (key & ~0x80) | (vbl_bar ? 0x80 : 0x00); } From eb534e17b5382c5bb0d404f5c07f8ac1faad38f0 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 31 Jan 2015 14:23:18 -0800 Subject: [PATCH 034/615] Get CPU tests working again --- src/misc.c | 2 ++ src/test/testcommon.c | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/misc.c b/src/misc.c index 44a556b4..e744d63e 100644 --- a/src/misc.c +++ b/src/misc.c @@ -582,7 +582,9 @@ void c_initialize_vm() { ------------------------------------------------------------------------- */ void reinitialize(void) { +#if !TESTING assert(pthread_self() == cpu_thread_id); +#endif cycles_count_total = 0; diff --git a/src/test/testcommon.c b/src/test/testcommon.c index 77043c64..7206f56a 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -135,6 +135,13 @@ void test_common_init(bool do_cputhread) { fprintf(stderr, "Pass HEADLESS=1 to environment to run nonstop\n"); c_debugger_set_timeout(0); } + } else { +#ifdef AUDIO_ENABLED + DSInit(); + speaker_init(); + MB_Initialize(); +#endif + reinitialize(); } } From c332a89738c4f151add3a88fd9c38d3b347a68fa Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 31 Jan 2015 14:27:13 -0800 Subject: [PATCH 035/615] Don't run finicky or abusive tests by default - But keep them around for such times as when we're not sure why a particular emulated program is not working =) --- src/test/testdisk.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/test/testdisk.c b/src/test/testdisk.c index 7dd4106b..bc7260b9 100644 --- a/src/test/testdisk.c +++ b/src/test/testdisk.c @@ -13,6 +13,9 @@ #define RESET_INPUT() test_common_setup() +#define ABUSIVE_TESTS 0 +#define FINICKY_TESTS 0 + #define TESTING_DISK "testvm1.dsk.gz" #define BLANK_DSK "blank.dsk.gz" #define BLANK_NIB "blank.nib.gz" @@ -998,13 +1001,15 @@ GREATEST_SUITE(test_suite_disk) { #if ABUSIVE_TESTS RUN_TESTp(test_boot_disk_cputrace); #endif + +#if FINICKY_TESTS RUN_TESTp(test_cputrace_hello_dsk); RUN_TESTp(test_cputrace_hello_nib); RUN_TESTp(test_cputrace_hello_po); - RUN_TESTp(test_boot_disk_vmtrace); RUN_TESTp(test_boot_disk_vmtrace_nib); RUN_TESTp(test_boot_disk_vmtrace_po); +#endif RUN_TESTp(test_boot_disk); @@ -1016,6 +1021,8 @@ GREATEST_SUITE(test_suite_disk) { RUN_TESTp(test_disk_bytes_savehello_nib); RUN_TESTp(test_disk_bytes_savehello_po); + c_debugger_set_timeout(60); + RUN_TESTp(test_outofspace_dsk); RUN_TESTp(test_outofspace_nib); RUN_TESTp(test_outofspace_po); From 3d8a6ac23de292a37aa8799ab67d07482c783feb Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 1 Feb 2015 15:25:55 -0800 Subject: [PATCH 036/615] PARTIAL Revert "Faster keyboard read" This reverts commit 30b8f308c6e4af28d7f824b42dd8a44cc1e38133. (Generated assembly for apple_ii_64k ARRAY is wrong on Mac). Conflicts: src/vm.c --- src/vm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vm.c b/src/vm.c index 63ca78ac..556485d7 100644 --- a/src/vm.c +++ b/src/vm.c @@ -141,7 +141,10 @@ GLUE_C_WRITE(write_unmapped_softswitch) // ... } -GLUE_FIXED_READ(read_keyboard,apple_ii_64k); +GLUE_C_READ(read_keyboard) +{ + return apple_ii_64k[0][0xC000]; +} GLUE_C_READ(read_keyboard_strobe) { From 1cbd4265484efd6c564c04ce5a5b64b0b1f7c593 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 1 Feb 2015 15:26:50 -0800 Subject: [PATCH 037/615] removed unused macros --- src/glue.h | 2 -- src/x86/glue-prologue.h | 8 -------- 2 files changed, 10 deletions(-) diff --git a/src/glue.h b/src/glue.h index fe947f6e..4949c60c 100644 --- a/src/glue.h +++ b/src/glue.h @@ -14,8 +14,6 @@ * */ -#define GLUE_FIXED_READ(func,address) -#define GLUE_FIXED_WRITE(func,address) #define GLUE_BANK_READ(func,pointer) #define GLUE_BANK_MAYBEREAD(func,pointer) #define GLUE_BANK_WRITE(func,pointer) diff --git a/src/x86/glue-prologue.h b/src/x86/glue-prologue.h index 8300a60a..f89a5566 100644 --- a/src/x86/glue-prologue.h +++ b/src/x86/glue-prologue.h @@ -19,14 +19,6 @@ #include "misc.h" #include "cpu-regs.h" -#define GLUE_FIXED_READ(func,address) \ -E(func) movb SN(address)(EffectiveAddr_X),%al; \ - ret; - -#define GLUE_FIXED_WRITE(func,address) \ -E(func) movb %al,SN(address)(EffectiveAddr_X); \ - ret; - #define GLUE_BANK_MAYBEREAD(func,pointer) \ E(func) testLQ $SS_CXROM, SN(softswitches); \ jnz 1f; \ From 978011cffa38b2bf2e343ec1836fcb62ed6f3b6c Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 1 Feb 2015 15:27:18 -0800 Subject: [PATCH 038/615] Unstick Mac builds --- Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj b/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj index 52c84f5f..9b58e8d9 100644 --- a/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj +++ b/Apple2Mac/Apple2Mac.xcodeproj/project.pbxproj @@ -1080,7 +1080,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$SRCROOT/../src/x86/genglue\" \"$SRCROOT/../src/disk.c\" \"$SRCROOT/../src/misc.c\" \"$SRCROOT/../src/display.c\" \"$SRCROOT/../src/vm.c\" \"$SRCROOT/../src/audio/mockingboard.c\" > \"$SRCROOT/../src/x86/glue.S\""; + shellScript = "\"$SRCROOT/../src/x86/genglue\" \"$SRCROOT/../src/disk.c\" \"$SRCROOT/../src/misc.c\" \"$SRCROOT/../src/display.c\" \"$SRCROOT/../src/vm.c\" \"$SRCROOT/../src/audio/speaker.c\" \"$SRCROOT/../src/audio/mockingboard.c\" > \"$SRCROOT/../src/x86/glue.S\""; showEnvVarsInLog = 0; }; 773B3DC919568BF20085CE5F /* ShellScript */ = { From 851d22568b8c4747f51a3eac82529641598a48a2 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Tue, 3 Feb 2015 22:10:47 -0800 Subject: [PATCH 039/615] Use 32bit counters to avoid overflow - Previously there were cases where we could overflow cpu65_cycles_to_execute when adding the cycles_speaker_feedback --- src/cpu.h | 2 +- src/test/testcpu.c | 4 +++- src/timing.c | 8 ++++---- src/x86/cpu.S | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/cpu.h b/src/cpu.h index 61591052..b55d96d4 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -57,7 +57,7 @@ extern void *cpu65_vmem_w[65536]; extern unsigned char cpu65_flags_encode[256]; extern unsigned char cpu65_flags_decode[256]; -extern int16_t cpu65_cycle_count; +extern int32_t cpu65_cycle_count; #if CPU_TRACING void cpu65_trace_begin(const char *trace_file); diff --git a/src/test/testcpu.c b/src/test/testcpu.c index 323a3081..fe4db9d6 100644 --- a/src/test/testcpu.c +++ b/src/test/testcpu.c @@ -52,7 +52,9 @@ static void testcpu_setup(void *arg) { //reinitialize(); cpu65_uninterrupt(0xff); - extern int16_t cpu65_cycles_to_execute; + extern int32_t cpu65_cycles_to_execute; + extern int32_t cpu65_cycle_count; + cpu65_cycle_count = 0; cpu65_cycles_to_execute = 1; cpu65_pc = TEST_LOC; diff --git a/src/timing.c b/src/timing.c index 99ecad7b..67672c7d 100644 --- a/src/timing.c +++ b/src/timing.c @@ -54,9 +54,9 @@ double cycles_persec_target = CLK_6502; unsigned long long cycles_count_total = 0; int cycles_speaker_feedback = 0; -int16_t cpu65_cycles_to_execute = 0; // cycles-to-execute by cpu65_run() -int16_t cpu65_cycle_count = 0; // cycles currently excuted by cpu65_run() -static int16_t cycles_checkpoint_count = 0; +int32_t cpu65_cycles_to_execute = 0; // cycles-to-execute by cpu65_run() +int32_t cpu65_cycle_count = 0; // cycles currently excuted by cpu65_run() +static int32_t cycles_checkpoint_count = 0; static unsigned int g_dwCyclesThisFrame = 0; // scaling and speed adjustments @@ -370,7 +370,7 @@ unsigned int CpuGetCyclesThisVideoFrame(void) { // Called when an IO-reg is accessed & accurate global cycle count info is needed void timing_checkpoint_cycles(void) { - const int16_t d = cpu65_cycle_count - cycles_checkpoint_count; + const int32_t d = cpu65_cycle_count - cycles_checkpoint_count; assert(d >= 0); cycles_count_total += d; cycles_checkpoint_count = cpu65_cycle_count; diff --git a/src/x86/cpu.S b/src/x86/cpu.S index 935883f5..86d7f251 100644 --- a/src/x86/cpu.S +++ b/src/x86/cpu.S @@ -2155,10 +2155,10 @@ continue: addb DebugCycleCount, %al movb %al, DebugCycleCount TRACE_EPILOGUE - addw %ax, SN(cpu65_cycle_count) + addl %eax, SN(cpu65_cycle_count) subl %eax, SN(gc_cycles_timer_0) subl %eax, SN(gc_cycles_timer_1) - subw %ax, SN(cpu65_cycles_to_execute) + subl %eax, SN(cpu65_cycles_to_execute) jle exit_cpu65_run continue1: xorLQ _XAX, _XAX orb SN(cpu65__signal), %al From 21f2665da54a6d42ed1343f4f615237334a94a52 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Fri, 6 Feb 2015 20:23:26 -0800 Subject: [PATCH 040/615] Bump Mac version number --- Apple2Mac/Apple2Mac/Apple2Mac-Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Apple2Mac/Apple2Mac/Apple2Mac-Info.plist b/Apple2Mac/Apple2Mac/Apple2Mac-Info.plist index 58e42dd5..30cedae2 100644 --- a/Apple2Mac/Apple2Mac/Apple2Mac-Info.plist +++ b/Apple2Mac/Apple2Mac/Apple2Mac-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.9 + 0.9.1 CFBundleSignature ???? CFBundleVersion @@ -25,7 +25,7 @@ CFBundleHelpBookFolder Apple2Mac.help CFBundleHelpBookName - org.deadc0de.Apple2Mac.help + org.deadc0de.Apple2Mac.help LSApplicationCategoryType public.app-category.education LSMinimumSystemVersion From 50cd4ac6fed980ba2317698c4b5ddf91a8b17c98 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 7 Feb 2015 14:23:16 -0800 Subject: [PATCH 041/615] Synchronize timing functions - Avoids resetting critical speaker variables when CPU thread is churning - Moves pthread_* variable ownership into timing.c --- src/keys.c | 5 ----- src/timing.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/keys.c b/src/keys.c index cbb629f9..f6d24c49 100644 --- a/src/keys.c +++ b/src/keys.c @@ -19,11 +19,6 @@ /* from misc.c */ extern uid_t user, privileged; -/* mutex used to synchronize between cpu and main threads */ -pthread_mutex_t interface_mutex = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t ui_thread_cond = PTHREAD_COND_INITIALIZER; -pthread_cond_t cpu_thread_cond = PTHREAD_COND_INITIALIZER; - static int next_key = -1; static int last_scancode = -1; bool caps_lock = true; // default enabled because so much breaks otherwise diff --git a/src/timing.c b/src/timing.c index 67672c7d..1dc9f3bd 100644 --- a/src/timing.c +++ b/src/timing.c @@ -69,9 +69,20 @@ static bool alt_speed_enabled = false; // misc volatile uint8_t emul_reinitialize = 0; pthread_t cpu_thread_id = 0; +pthread_mutex_t interface_mutex = { 0 }; +pthread_cond_t ui_thread_cond = PTHREAD_COND_INITIALIZER; +pthread_cond_t cpu_thread_cond = PTHREAD_COND_INITIALIZER; // ----------------------------------------------------------------------------- +__attribute__((constructor)) +static void _init_timing() { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&interface_mutex, &attr); +} + struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative) { struct timespec t; @@ -130,18 +141,36 @@ static void _timing_initialize(double scale) { #endif } +static inline void _lock_gui_thread(void) { + if (pthread_self() != cpu_thread_id) { + pthread_mutex_lock(&interface_mutex); + } +} + +static inline void _unlock_gui_thread(void) { + if (pthread_self() != cpu_thread_id) { + pthread_mutex_unlock(&interface_mutex); + } +} + void timing_initialize(void) { + _lock_gui_thread(); _timing_initialize(alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor); + _unlock_gui_thread(); } void timing_toggle_cpu_speed(void) { + _lock_gui_thread(); alt_speed_enabled = !alt_speed_enabled; timing_initialize(); + _unlock_gui_thread(); } void timing_set_auto_adjust_speed(bool auto_adjust) { + _lock_gui_thread(); auto_adjust_speed = auto_adjust; timing_initialize(); + _unlock_gui_thread(); } bool timing_should_auto_adjust_speed(void) { @@ -370,6 +399,8 @@ unsigned int CpuGetCyclesThisVideoFrame(void) { // Called when an IO-reg is accessed & accurate global cycle count info is needed void timing_checkpoint_cycles(void) { + assert(pthread_self() == cpu_thread_id); + const int32_t d = cpu65_cycle_count - cycles_checkpoint_count; assert(d >= 0); cycles_count_total += d; From cec638b9591162eecb660c1ef74ec52ae80fdec3 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Mon, 16 Feb 2015 08:46:29 -0800 Subject: [PATCH 042/615] Get threaded tests unstuck and rename/shuffle some code --- src/keys.h | 3 --- src/meta/debugger.c | 2 +- src/timing.c | 8 +++++--- src/timing.h | 3 +++ 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/keys.h b/src/keys.h index 5ad6c8cf..832e6bc0 100644 --- a/src/keys.h +++ b/src/keys.h @@ -136,9 +136,6 @@ // ---------------------------------------------------------------------------- -extern pthread_mutex_t interface_mutex; -extern pthread_cond_t cpu_thread_cond; -extern pthread_cond_t ui_thread_cond; extern bool caps_lock; int c_mygetch(int block); diff --git a/src/meta/debugger.c b/src/meta/debugger.c index e7541e75..df255efc 100644 --- a/src/meta/debugger.c +++ b/src/meta/debugger.c @@ -1170,7 +1170,7 @@ static int begin_cpu_stepping() { if ((err = pthread_cond_signal(&cpu_thread_cond))) { ERRLOG("pthread_cond_signal : %d", err); } - if ((err = pthread_cond_wait(&ui_thread_cond, &interface_mutex))) { + if ((err = pthread_cond_wait(&dbg_thread_cond, &interface_mutex))) { ERRLOG("pthread_cond_wait : %d", err); } diff --git a/src/timing.c b/src/timing.c index 1dc9f3bd..d2a2d134 100644 --- a/src/timing.c +++ b/src/timing.c @@ -70,16 +70,18 @@ static bool alt_speed_enabled = false; volatile uint8_t emul_reinitialize = 0; pthread_t cpu_thread_id = 0; pthread_mutex_t interface_mutex = { 0 }; -pthread_cond_t ui_thread_cond = PTHREAD_COND_INITIALIZER; +pthread_cond_t dbg_thread_cond = PTHREAD_COND_INITIALIZER; pthread_cond_t cpu_thread_cond = PTHREAD_COND_INITIALIZER; // ----------------------------------------------------------------------------- __attribute__((constructor)) -static void _init_timing() { +static void _init_timing(void) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); +#if !TESTING pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#endif pthread_mutex_init(&interface_mutex, &attr); } @@ -262,7 +264,7 @@ void *cpu_thread(void *dummyptr) { debugging_cycles -= cpu65_cycle_count; if (c_debugger_should_break() || (debugging_cycles <= 0)) { int err = 0; - if ((err = pthread_cond_signal(&ui_thread_cond))) { + if ((err = pthread_cond_signal(&dbg_thread_cond))) { ERRLOG("pthread_cond_signal : %d", err); } if ((err = pthread_cond_wait(&cpu_thread_cond, &interface_mutex))) { diff --git a/src/timing.h b/src/timing.h index 74336ce6..87687977 100644 --- a/src/timing.h +++ b/src/timing.h @@ -52,6 +52,9 @@ extern double cpu_altscale_factor; // scale factor #2 extern bool is_fullspeed; // emulation in full native speed? extern pthread_t cpu_thread_id; +extern pthread_mutex_t interface_mutex; +extern pthread_cond_t cpu_thread_cond; +extern pthread_cond_t dbg_thread_cond; /* * calculate the difference between two timespec structures From cf79056b28adcc5cf5dfad792490712c3e25fb82 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 8 Feb 2015 11:13:42 -0800 Subject: [PATCH 043/615] Merge x86-specific apple2.h stuff into cpu-regs.h --- Makefile.am | 2 +- src/apple2.h | 55 ----------------------------------------- src/x86/cpu-regs.h | 35 ++++++++++++++++++++++++++ src/x86/cpu.S | 1 - src/x86/glue-prologue.h | 1 - 5 files changed, 36 insertions(+), 58 deletions(-) delete mode 100644 src/apple2.h diff --git a/Makefile.am b/Makefile.am index 73edf4ae..0a2c9ae2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,7 +9,7 @@ AM_LFLAGS = -i ############################################################################### # No install -noinst_HEADERS = src/apple2.h src/common.h src/cpu.h src/disk.h src/glue.h \ +noinst_HEADERS = src/common.h src/cpu.h src/disk.h src/glue.h \ src/interface.h src/joystick.h src/keys.h src/misc.h src/prefs.h \ src/timing.h src/uthash.h src/video/video.h src/zlib-helpers.h \ \ diff --git a/src/apple2.h b/src/apple2.h deleted file mode 100644 index 67a1c2ec..00000000 --- a/src/apple2.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Apple // emulator for Linux: Common definitions - * - * Copyright 1994 Alexander Jean-Claude Bottema - * Copyright 1995 Stephen Lee - * Copyright 1997, 1998 Aaron Culliney - * Copyright 1998, 1999, 2000 Michael Deutschmann - * - * This software package is subject to the GNU General Public License - * version 2 or later (your choice) as published by the Free Software - * Foundation. - * - * THERE ARE NO WARRANTIES WHATSOEVER. - * - */ - -#ifndef _A2_H_ -#define _A2_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -// Virtual machine is an Apple ][ (not an NES, etc...) -#define APPLE2_VM 1 - -/* Symbol naming issues */ -#ifdef NO_UNDERSCORES -#define SN(foo) foo -#define SNX(foo, INDEX, SCALE) foo(,INDEX,SCALE) -#define SNX_PROLOGUE(foo) -#define E(foo) .globl foo; .balign 16; foo##: -#define CALL(foo) foo -#else /* !NO_UNDERSCORES */ -#if defined(__APPLE__) -# warning "2014/06/22 -- Apple's clang appears to not like certain manipulations of %_h register values (for example %ah, %ch) that are valid on *nix ... and it creates bizarre bytecode -# define APPLE_ASSEMBLER_IS_BROKEN 1 -# define SN(foo) _##foo(%rip) -# define SNX(foo, INDEX, SCALE) (_X8,INDEX,SCALE) -# ifdef __LP64__ -# define SNX_PROLOGUE(foo) leaLQ _##foo(%rip), _X8; -# else -# error "Building 32bit Darwin/x86 is not supported (unless you're a go-getter and make it supported)" -# endif -# define E(foo) .globl _##foo; .balign 4; _##foo##: -#else -# define SN(foo) _##foo -# define SNX(foo, INDEX, SCALE) _##foo(,INDEX,SCALE) -# define SNX_PROLOGUE(foo) -# define E(foo) .globl _##foo; .balign 16; _##foo##: -#endif -#define CALL(foo) _##foo -#endif /* !NO_UNDERSCORES */ - -#endif /* _A2_H_ */ diff --git a/src/x86/cpu-regs.h b/src/x86/cpu-regs.h index 2f082f13..fdef2af6 100644 --- a/src/x86/cpu-regs.h +++ b/src/x86/cpu-regs.h @@ -12,8 +12,15 @@ #ifndef _CPU_REGS_H_ #define _CPU_REGS_H_ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include "cpu.h" +// Virtual machine is an Apple ][ (not an NES, etc...) +#define APPLE2_VM 1 + #define X_Reg %bl /* 6502 X register in %bl */ #define Y_Reg %bh /* 6502 Y register in %bh */ #define A_Reg %cl /* 6502 A register in %cl */ @@ -109,4 +116,32 @@ # define xorLQ xorl #endif +/* Symbol naming issues */ +#ifdef NO_UNDERSCORES +#define SN(foo) foo +#define SNX(foo, INDEX, SCALE) foo(,INDEX,SCALE) +#define SNX_PROLOGUE(foo) +#define E(foo) .globl foo; .balign 16; foo##: +#define CALL(foo) foo +#else /* !NO_UNDERSCORES */ +#if defined(__APPLE__) +# warning "2014/06/22 -- Apple's clang appears to not like certain manipulations of %_h register values (for example %ah, %ch) that are valid on *nix ... and it creates bizarre bytecode +# define APPLE_ASSEMBLER_IS_BROKEN 1 +# define SN(foo) _##foo(%rip) +# define SNX(foo, INDEX, SCALE) (_X8,INDEX,SCALE) +# ifdef __LP64__ +# define SNX_PROLOGUE(foo) leaLQ _##foo(%rip), _X8; +# else +# error "Building 32bit Darwin/x86 is not supported (unless you're a go-getter and make it supported)" +# endif +# define E(foo) .globl _##foo; .balign 4; _##foo##: +#else +# define SN(foo) _##foo +# define SNX(foo, INDEX, SCALE) _##foo(,INDEX,SCALE) +# define SNX_PROLOGUE(foo) +# define E(foo) .globl _##foo; .balign 16; _##foo##: +#endif +#define CALL(foo) _##foo +#endif /* !NO_UNDERSCORES */ + #endif // whole file diff --git a/src/x86/cpu.S b/src/x86/cpu.S index 86d7f251..0ca9efe1 100644 --- a/src/x86/cpu.S +++ b/src/x86/cpu.S @@ -15,7 +15,6 @@ */ #include "cpu-regs.h" -#include "apple2.h" #include "misc.h" #define DebugCurrEA SN(cpu65_ea) diff --git a/src/x86/glue-prologue.h b/src/x86/glue-prologue.h index f89a5566..cea63a2a 100644 --- a/src/x86/glue-prologue.h +++ b/src/x86/glue-prologue.h @@ -15,7 +15,6 @@ */ #define __ASSEMBLY__ -#include "apple2.h" #include "misc.h" #include "cpu-regs.h" From b246baa90d4aa10c3460e219fafb0e0ddbd01201 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 8 Feb 2015 11:27:29 -0800 Subject: [PATCH 044/615] REFACTOR : macro renaming for clarity/readability --- src/x86/cpu-regs.h | 28 +- src/x86/cpu.S | 614 ++++++++++++++++++++-------------------- src/x86/glue-prologue.h | 26 +- 3 files changed, 334 insertions(+), 334 deletions(-) diff --git a/src/x86/cpu-regs.h b/src/x86/cpu-regs.h index fdef2af6..63e696e1 100644 --- a/src/x86/cpu-regs.h +++ b/src/x86/cpu-regs.h @@ -39,8 +39,8 @@ #define RestoreAltZP \ /* Apple //e set stack point to ALTZP (or not) */ \ - movLQ SN(base_stackzp), _XAX; \ - subLQ SN(base_vmem), _XAX; \ + movLQ SYM(base_stackzp), _XAX; \ + subLQ SYM(base_vmem), _XAX; \ orLQ _XAX, SP_Reg_X; #ifdef __LP64__ @@ -118,28 +118,28 @@ /* Symbol naming issues */ #ifdef NO_UNDERSCORES -#define SN(foo) foo -#define SNX(foo, INDEX, SCALE) foo(,INDEX,SCALE) -#define SNX_PROLOGUE(foo) -#define E(foo) .globl foo; .balign 16; foo##: +#define SYM(foo) foo +#define SYMX(foo, INDEX, SCALE) foo(,INDEX,SCALE) +#define SYMX_PROLOGUE(foo) +#define ENTRY(foo) .globl foo; .balign 16; foo##: #define CALL(foo) foo #else /* !NO_UNDERSCORES */ #if defined(__APPLE__) # warning "2014/06/22 -- Apple's clang appears to not like certain manipulations of %_h register values (for example %ah, %ch) that are valid on *nix ... and it creates bizarre bytecode # define APPLE_ASSEMBLER_IS_BROKEN 1 -# define SN(foo) _##foo(%rip) -# define SNX(foo, INDEX, SCALE) (_X8,INDEX,SCALE) +# define SYM(foo) _##foo(%rip) +# define SYMX(foo, INDEX, SCALE) (_X8,INDEX,SCALE) # ifdef __LP64__ -# define SNX_PROLOGUE(foo) leaLQ _##foo(%rip), _X8; +# define SYMX_PROLOGUE(foo) leaLQ _##foo(%rip), _X8; # else # error "Building 32bit Darwin/x86 is not supported (unless you're a go-getter and make it supported)" # endif -# define E(foo) .globl _##foo; .balign 4; _##foo##: +# define ENTRY(foo) .globl _##foo; .balign 4; _##foo##: #else -# define SN(foo) _##foo -# define SNX(foo, INDEX, SCALE) _##foo(,INDEX,SCALE) -# define SNX_PROLOGUE(foo) -# define E(foo) .globl _##foo; .balign 16; _##foo##: +# define SYM(foo) _##foo +# define SYMX(foo, INDEX, SCALE) _##foo(,INDEX,SCALE) +# define SYMX_PROLOGUE(foo) +# define ENTRY(foo) .globl _##foo; .balign 16; _##foo##: #endif #define CALL(foo) _##foo #endif /* !NO_UNDERSCORES */ diff --git a/src/x86/cpu.S b/src/x86/cpu.S index 0ca9efe1..be4663ae 100644 --- a/src/x86/cpu.S +++ b/src/x86/cpu.S @@ -17,27 +17,27 @@ #include "cpu-regs.h" #include "misc.h" -#define DebugCurrEA SN(cpu65_ea) -#define DebugCurrByte SN(cpu65_d) -#define DebugCurrRW SN(cpu65_rw) -#define DebugCurrOpcode SN(cpu65_opcode) -#define DebugCycleCount SN(cpu65_opcycles) +#define DebugCurrEA SYM(cpu65_ea) +#define DebugCurrByte SYM(cpu65_d) +#define DebugCurrRW SYM(cpu65_rw) +#define DebugCurrOpcode SYM(cpu65_opcode) +#define DebugCycleCount SYM(cpu65_opcycles) #define CommonSaveCPUState() \ movw EffectiveAddr, DebugCurrEA; \ - movb A_Reg, SN(cpu65_a); \ + movb A_Reg, SYM(cpu65_a); \ xorw %ax, %ax; \ movb F_Reg, %al; \ - SNX_PROLOGUE(cpu65_flags_encode); \ - movb SNX(cpu65_flags_encode,_XAX,1), %al; \ - movb %al, SN(cpu65_f); \ - movb X_Reg, SN(cpu65_x); \ - movb Y_Reg, SN(cpu65_y); \ - movb SP_Reg_L, SN(cpu65_sp) + SYMX_PROLOGUE(cpu65_flags_encode); \ + movb SYMX(cpu65_flags_encode,_XAX,1), %al; \ + movb %al, SYM(cpu65_f); \ + movb X_Reg, SYM(cpu65_x); \ + movb Y_Reg, SYM(cpu65_y); \ + movb SP_Reg_L, SYM(cpu65_sp) #if CPU_TRACING # define TRACE_PROLOGUE \ - movw PC_Reg, SN(cpu65_pc); \ + movw PC_Reg, SYM(cpu65_pc); \ callLQ CALL(cpu65_trace_prologue); # define TRACE_ARG \ callLQ CALL(cpu65_trace_arg); @@ -65,21 +65,21 @@ #define GetFromPC_B \ movLQ PC_Reg_X, EffectiveAddr_X; \ incw PC_Reg; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ TRACE_ARG; #define GetFromPC_W \ movLQ PC_Reg_X, EffectiveAddr_X; \ incw EffectiveAddr; \ addw $2, PC_Reg; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ decw EffectiveAddr; \ TRACE_ARG2; \ movb %al, %ah; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ TRACE_ARG1; #define JumpNextInstruction \ @@ -88,43 +88,43 @@ movb %al, DebugCurrOpcode; \ movb $0, DebugCycleCount; \ movb $0, DebugCurrRW; \ - SNX_PROLOGUE(cpu65__opcodes); \ - jmp *SNX(cpu65__opcodes,_XAX,SZ_PTR); + SYMX_PROLOGUE(cpu65__opcodes); \ + jmp *SYMX(cpu65__opcodes,_XAX,SZ_PTR); #define GetFromEA_B \ orb $1, DebugCurrRW; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); #define GetFromEA_W \ incw EffectiveAddr; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ decw EffectiveAddr; \ movb %al, %ah; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); #define PutToEA_B \ orb $2, DebugCurrRW; \ movb %al, DebugCurrByte; \ - SNX_PROLOGUE(cpu65_vmem_w); \ - callLQ *SNX(cpu65_vmem_w,EffectiveAddr_X,SZ_PTR); + SYMX_PROLOGUE(cpu65_vmem_w); \ + callLQ *SYMX(cpu65_vmem_w,EffectiveAddr_X,SZ_PTR); #define GetFromMem_B(x) \ movLQ x, EffectiveAddr_X; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); #define GetFromMem_W(x) \ movLQ x, EffectiveAddr_X; \ incw EffectiveAddr; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ decw EffectiveAddr; \ movb %al, %ah; \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); #define Continue \ jmp continue; @@ -182,13 +182,13 @@ orb %al, F_Reg; #define Push(x) \ - SNX_PROLOGUE(apple_ii_64k); \ - movb x, SNX(apple_ii_64k,SP_Reg_X,1); \ + SYMX_PROLOGUE(apple_ii_64k); \ + movb x, SYMX(apple_ii_64k,SP_Reg_X,1); \ decb SP_Reg_L; #define Pop(x) incb SP_Reg_L; \ - SNX_PROLOGUE(apple_ii_64k); \ - movb SNX(apple_ii_64k,SP_Reg_X,1), x; + SYMX_PROLOGUE(apple_ii_64k); \ + movb SYMX(apple_ii_64k,SP_Reg_X,1), x; /* Immediate Addressing - the operand is contained in the second byte of the instruction. */ @@ -196,8 +196,8 @@ incw PC_Reg; #if CPU_TRACING #define GetImm _GetImm \ - SNX_PROLOGUE(cpu65_vmem_r); \ - callLQ *SNX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ + SYMX_PROLOGUE(cpu65_vmem_r); \ + callLQ *SYMX(cpu65_vmem_r,EffectiveAddr_X,SZ_PTR); \ TRACE_ARG; #else #define GetImm _GetImm @@ -499,7 +499,7 @@ ---------------------------------- */ // Decimal mode -E(op_ADC_dec) +ENTRY(op_ADC_dec) incb DebugCycleCount // +1 cycle GetFromEA_B DebugBCDCheck @@ -551,21 +551,21 @@ _daa_finish: popq _XBX #endif Continue -E(op_ADC_imm) // 0x69 +ENTRY(op_ADC_imm) // 0x69 GetImm testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version DoADC_b Continue -E(op_ADC_zpage) // 0x65 +ENTRY(op_ADC_zpage) // 0x65 GetZPage testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version DoADC_b Continue -E(op_ADC_zpage_x) // 0x75 +ENTRY(op_ADC_zpage_x) // 0x75 GetZPage_X testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version @@ -573,38 +573,38 @@ E(op_ADC_zpage_x) // 0x75 Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_ADC_zpage_y) +ENTRY(op_ADC_zpage_y) jmp CALL(op_NOP) -E(op_ADC_abs) // 0x6d +ENTRY(op_ADC_abs) // 0x6d GetAbs testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version DoADC_b Continue -E(op_ADC_abs_x) // 0x7d +ENTRY(op_ADC_abs_x) // 0x7d GetAbs_X testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version DoADC_b Continue -E(op_ADC_abs_y) // 0x79 +ENTRY(op_ADC_abs_y) // 0x79 GetAbs_Y testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version DoADC_b Continue -E(op_ADC_ind_x) // 0x61 +ENTRY(op_ADC_ind_x) // 0x61 GetIndZPage_X testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version DoADC_b Continue -E(op_ADC_ind_y) // 0x71 +ENTRY(op_ADC_ind_y) // 0x71 GetIndZPage_Y testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version @@ -612,7 +612,7 @@ E(op_ADC_ind_y) // 0x71 Continue // 65c02 : 0x72 -E(op_ADC_ind_zpage) +ENTRY(op_ADC_ind_zpage) GetIndZPage testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_ADC_dec) // Yes, jump to decimal version @@ -624,52 +624,52 @@ E(op_ADC_ind_zpage) logical AND memory with accumulator ---------------------------------- */ -E(op_AND_imm) // 0x29 +ENTRY(op_AND_imm) // 0x29 GetImm DoAND Continue -E(op_AND_zpage) // 0x25 +ENTRY(op_AND_zpage) // 0x25 GetZPage DoAND Continue -E(op_AND_zpage_x) // 0x35 +ENTRY(op_AND_zpage_x) // 0x35 GetZPage_X DoAND Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_AND_zpage_y) +ENTRY(op_AND_zpage_y) jmp CALL(op_NOP) -E(op_AND_abs) // 0x2d +ENTRY(op_AND_abs) // 0x2d GetAbs DoAND Continue -E(op_AND_abs_x) // 0x3d +ENTRY(op_AND_abs_x) // 0x3d GetAbs_X DoAND Continue -E(op_AND_abs_y) // 0x39 +ENTRY(op_AND_abs_y) // 0x39 GetAbs_Y DoAND Continue -E(op_AND_ind_x) // 0x21 +ENTRY(op_AND_ind_x) // 0x21 GetIndZPage_X DoAND Continue -E(op_AND_ind_y) // 0x31 +ENTRY(op_AND_ind_y) // 0x31 GetIndZPage_Y DoAND Continue // 65c02 : 0x32 -E(op_AND_ind_zpage) +ENTRY(op_AND_ind_zpage) GetIndZPage DoAND Continue @@ -679,27 +679,27 @@ E(op_AND_ind_zpage) Arithmetic Shift one bit Left, memory or accumulator ---------------------------------- */ -E(op_ASL_acc) // 0x0a +ENTRY(op_ASL_acc) // 0x0a addb A_Reg, A_Reg FlagNZC Continue -E(op_ASL_zpage) // 0x06 +ENTRY(op_ASL_zpage) // 0x06 GetZPage DoASL Continue -E(op_ASL_zpage_x) // 0x16 +ENTRY(op_ASL_zpage_x) // 0x16 GetZPage_X DoASL Continue -E(op_ASL_abs) // 0x0e +ENTRY(op_ASL_abs) // 0x0e GetAbs DoASL Continue -E(op_ASL_abs_x) // 0x1e +ENTRY(op_ASL_abs_x) // 0x1e GetAbs_X DoASL Continue @@ -711,28 +711,28 @@ E(op_ASL_abs_x) // 0x1e + 2 cycles if branch across page boundary ---------------------------------- */ -E(op_BBR0_65c02) +ENTRY(op_BBR0_65c02) Continue -E(op_BBR1_65c02) +ENTRY(op_BBR1_65c02) Continue -E(op_BBR2_65c02) +ENTRY(op_BBR2_65c02) Continue -E(op_BBR3_65c02) +ENTRY(op_BBR3_65c02) Continue -E(op_BBR4_65c02) +ENTRY(op_BBR4_65c02) Continue -E(op_BBR5_65c02) +ENTRY(op_BBR5_65c02) Continue -E(op_BBR6_65c02) +ENTRY(op_BBR6_65c02) Continue -E(op_BBR7_65c02) +ENTRY(op_BBR7_65c02) Continue /* ---------------------------------- @@ -742,28 +742,28 @@ E(op_BBR7_65c02) + 2 cycles if branch across page boundary ---------------------------------- */ -E(op_BBS0_65c02) +ENTRY(op_BBS0_65c02) Continue -E(op_BBS1_65c02) +ENTRY(op_BBS1_65c02) Continue -E(op_BBS2_65c02) +ENTRY(op_BBS2_65c02) Continue -E(op_BBS3_65c02) +ENTRY(op_BBS3_65c02) Continue -E(op_BBS4_65c02) +ENTRY(op_BBS4_65c02) Continue -E(op_BBS5_65c02) +ENTRY(op_BBS5_65c02) Continue -E(op_BBS6_65c02) +ENTRY(op_BBS6_65c02) Continue -E(op_BBS7_65c02) +ENTRY(op_BBS7_65c02) Continue /* ---------------------------------- @@ -771,7 +771,7 @@ E(op_BBS7_65c02) Branch on Carry Clear ---------------------------------- */ -E(op_BCC) // 0x90 +ENTRY(op_BCC) // 0x90 GetFromPC_B testb $C_Flag, F_Reg jnz op_BCC_not // branch not taken @@ -784,7 +784,7 @@ op_BCC_not: Branch on Carry Set ---------------------------------- */ -E(op_BCS) // 0xB0 +ENTRY(op_BCS) // 0xB0 GetFromPC_B testb $C_Flag, F_Reg jz op_BCS_not // branch not taken @@ -797,7 +797,7 @@ op_BCS_not: Branch if EQual ---------------------------------- */ -E(op_BEQ) // 0xF0 +ENTRY(op_BEQ) // 0xF0 GetFromPC_B testb $Z_Flag, F_Reg jz op_BEQ_not // branch not taken @@ -810,24 +810,24 @@ op_BEQ_not: BIt Test ---------------------------------- */ -E(op_BIT_zpage) // 0x24 +ENTRY(op_BIT_zpage) // 0x24 GetZPage DoBIT Continue -E(op_BIT_abs) // 0x2c +ENTRY(op_BIT_abs) // 0x2c GetAbs DoBIT Continue // 65c02 : 0x34 -E(op_BIT_zpage_x) +ENTRY(op_BIT_zpage_x) GetZPage_X DoBIT Continue // 65c02 : 0x3C -E(op_BIT_abs_x) +ENTRY(op_BIT_abs_x) GetAbs_X DoBIT Continue @@ -836,7 +836,7 @@ E(op_BIT_abs_x) * N and V flags, unlike in other addressing modes. */ // 65c02 : 0x89 -E(op_BIT_imm) +ENTRY(op_BIT_imm) GetImm GetFromEA_B testb %al, A_Reg @@ -848,7 +848,7 @@ E(op_BIT_imm) Branch on result MInus ---------------------------------- */ -E(op_BMI) // 0x30 +ENTRY(op_BMI) // 0x30 GetFromPC_B testb F_Reg, F_Reg /* optimized check of N flag, * which happens to be sign bit */ @@ -862,7 +862,7 @@ op_BMI_not: Branch on result Not Equal ---------------------------------- */ -E(op_BNE) // 0xD0 +ENTRY(op_BNE) // 0xD0 GetFromPC_B testb $Z_Flag, F_Reg jnz op_BNE_not @@ -875,7 +875,7 @@ op_BNE_not: Branch on result PLus ---------------------------------- */ -E(op_BPL) // 0x10 +ENTRY(op_BPL) // 0x10 GetFromPC_B testb F_Reg, F_Reg /* optimized check of N flag, * which happens to be sign bit */ @@ -890,7 +890,7 @@ op_BPL_not: ---------------------------------- */ // 65c02 : 0x80 -E(op_BRA) +ENTRY(op_BRA) GetFromPC_B BranchXCycles Continue @@ -899,8 +899,8 @@ E(op_BRA) BRK instruction ---------------------------------- */ -E(op_UNK) /* make undefined opcodes fault */ -E(op_BRK) +ENTRY(op_UNK) /* make undefined opcodes fault */ +ENTRY(op_BRK) incw PC_Reg movw PC_Reg, %ax #ifdef APPLE_ASSEMBLER_IS_BROKEN @@ -914,8 +914,8 @@ E(op_BRK) orb $(B_Flag|X_Flag), F_Reg xorw %ax, %ax movb F_Reg, %al - SNX_PROLOGUE(cpu65_flags_encode) - movb SNX(cpu65_flags_encode,_XAX,1), %al + SYMX_PROLOGUE(cpu65_flags_encode) + movb SYMX(cpu65_flags_encode,_XAX,1), %al Push(%al) orb $I_Flag, F_Reg movw $0xFFFE, EffectiveAddr // ROM interrupt vector @@ -928,7 +928,7 @@ E(op_BRK) Branch on oVerflow Clear ---------------------------------- */ -E(op_BVC) // 0x50 +ENTRY(op_BVC) // 0x50 GetFromPC_B testb $V_Flag, F_Reg jnz op_BVC_not @@ -941,7 +941,7 @@ op_BVC_not: Branch on oVerflow Set ---------------------------------- */ -E(op_BVS) // 0x70 +ENTRY(op_BVS) // 0x70 GetFromPC_B testb $V_Flag, F_Reg jz op_BVS_not @@ -953,7 +953,7 @@ op_BVS_not: CLC instruction ---------------------------------- */ -E(op_CLC) // 0x18 +ENTRY(op_CLC) // 0x18 andb $~C_Flag, F_Reg Continue @@ -961,7 +961,7 @@ E(op_CLC) // 0x18 CLD instruction ---------------------------------- */ -E(op_CLD) // 0xd8 +ENTRY(op_CLD) // 0xd8 andb $~D_Flag, F_Reg Continue @@ -969,7 +969,7 @@ E(op_CLD) // 0xd8 CLI instruction ---------------------------------- */ -E(op_CLI) // 0x58 +ENTRY(op_CLI) // 0x58 andb $~I_Flag, F_Reg Continue @@ -977,7 +977,7 @@ E(op_CLI) // 0x58 CLV instruction ---------------------------------- */ -E(op_CLV) // 0xB8 +ENTRY(op_CLV) // 0xB8 andb $~V_Flag, F_Reg Continue @@ -986,52 +986,52 @@ E(op_CLV) // 0xB8 CoMPare memory and accumulator ---------------------------------- */ -E(op_CMP_imm) // 0xc9 +ENTRY(op_CMP_imm) // 0xc9 GetImm DoCMP Continue -E(op_CMP_zpage) // 0xc5 +ENTRY(op_CMP_zpage) // 0xc5 GetZPage DoCMP Continue -E(op_CMP_zpage_x) // 0xd5 +ENTRY(op_CMP_zpage_x) // 0xd5 GetZPage_X DoCMP Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_CMP_zpage_y) +ENTRY(op_CMP_zpage_y) jmp CALL(op_NOP) -E(op_CMP_abs) // 0xcd +ENTRY(op_CMP_abs) // 0xcd GetAbs DoCMP Continue -E(op_CMP_abs_x) // 0xdd +ENTRY(op_CMP_abs_x) // 0xdd GetAbs_X DoCMP Continue -E(op_CMP_abs_y) // 0xd9 +ENTRY(op_CMP_abs_y) // 0xd9 GetAbs_Y DoCMP Continue -E(op_CMP_ind_x) // 0xc1 +ENTRY(op_CMP_ind_x) // 0xc1 GetIndZPage_X DoCMP Continue -E(op_CMP_ind_y) // 0xd1 +ENTRY(op_CMP_ind_y) // 0xd1 GetIndZPage_Y DoCMP Continue // 65c02 : 0xD2 -E(op_CMP_ind_zpage) +ENTRY(op_CMP_ind_zpage) GetIndZPage DoCMP Continue @@ -1041,17 +1041,17 @@ E(op_CMP_ind_zpage) ComPare memory and X register ---------------------------------- */ -E(op_CPX_imm) // 0xe0 +ENTRY(op_CPX_imm) // 0xe0 GetImm DoCPX Continue -E(op_CPX_zpage) // 0xe4 +ENTRY(op_CPX_zpage) // 0xe4 GetZPage DoCPX Continue -E(op_CPX_abs) // 0xec +ENTRY(op_CPX_abs) // 0xec GetAbs DoCPX Continue @@ -1061,17 +1061,17 @@ E(op_CPX_abs) // 0xec ComPare memory and Y register ---------------------------------- */ -E(op_CPY_imm) // 0xc0 +ENTRY(op_CPY_imm) // 0xc0 GetImm DoCPY Continue -E(op_CPY_zpage) // 0xc4 +ENTRY(op_CPY_zpage) // 0xc4 GetZPage DoCPY Continue -E(op_CPY_abs) // 0xcc +ENTRY(op_CPY_abs) // 0xcc GetAbs DoCPY Continue @@ -1080,8 +1080,8 @@ E(op_CPY_abs) // 0xcc DEA: DEcrement Accumulator ---------------------------------- */ -E(op_DEC_acc) -E(op_DEA) // 0x3A +ENTRY(op_DEC_acc) +ENTRY(op_DEA) // 0x3A decb A_Reg FlagNZ Continue @@ -1091,22 +1091,22 @@ E(op_DEA) // 0x3A DECrement memory or accumulator by one ---------------------------------- */ -E(op_DEC_zpage) // 0xc6 +ENTRY(op_DEC_zpage) // 0xc6 GetZPage DoDEC Continue -E(op_DEC_zpage_x) // 0xd6 +ENTRY(op_DEC_zpage_x) // 0xd6 GetZPage_X DoDEC Continue -E(op_DEC_abs) // 0xce +ENTRY(op_DEC_abs) // 0xce GetAbs DoDEC Continue -E(op_DEC_abs_x) // 0xde +ENTRY(op_DEC_abs_x) // 0xde GetAbs_X DoDEC Continue @@ -1115,7 +1115,7 @@ E(op_DEC_abs_x) // 0xde DEX instruction ---------------------------------- */ -E(op_DEX) // 0xca +ENTRY(op_DEX) // 0xca decb X_Reg FlagNZ Continue @@ -1124,7 +1124,7 @@ E(op_DEX) // 0xca DEY instruction ---------------------------------- */ -E(op_DEY) // 0x88 +ENTRY(op_DEY) // 0x88 decb Y_Reg FlagNZ Continue @@ -1134,52 +1134,52 @@ E(op_DEY) // 0x88 Exclusive OR memory with accumulator ---------------------------------- */ -E(op_EOR_imm) // 0x49 +ENTRY(op_EOR_imm) // 0x49 GetImm DoEOR Continue -E(op_EOR_zpage) // 0x45 +ENTRY(op_EOR_zpage) // 0x45 GetZPage DoEOR Continue -E(op_EOR_zpage_x) // 0x55 +ENTRY(op_EOR_zpage_x) // 0x55 GetZPage_X DoEOR Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_EOR_zpage_y) +ENTRY(op_EOR_zpage_y) jmp CALL(op_NOP) -E(op_EOR_abs) // 0x4d +ENTRY(op_EOR_abs) // 0x4d GetAbs DoEOR Continue -E(op_EOR_abs_x) // 0x5d +ENTRY(op_EOR_abs_x) // 0x5d GetAbs_X DoEOR Continue -E(op_EOR_abs_y) // 0x59 +ENTRY(op_EOR_abs_y) // 0x59 GetAbs_Y DoEOR Continue -E(op_EOR_ind_x) // 0x41 +ENTRY(op_EOR_ind_x) // 0x41 GetIndZPage_X DoEOR Continue -E(op_EOR_ind_y) // 0x51 +ENTRY(op_EOR_ind_y) // 0x51 GetIndZPage_Y DoEOR Continue // 65c02 : 0x52 -E(op_EOR_ind_zpage) +ENTRY(op_EOR_ind_zpage) GetIndZPage DoEOR Continue @@ -1188,8 +1188,8 @@ E(op_EOR_ind_zpage) INA : INcrement Accumulator ---------------------------------- */ -E(op_INC_acc) -E(op_INA) // 0x1A +ENTRY(op_INC_acc) +ENTRY(op_INA) // 0x1A incb A_Reg FlagNZ Continue @@ -1199,22 +1199,22 @@ E(op_INA) // 0x1A INCrement memory ---------------------------------- */ -E(op_INC_zpage) // 0xe6 +ENTRY(op_INC_zpage) // 0xe6 GetZPage DoINC Continue -E(op_INC_zpage_x) // 0xf6 +ENTRY(op_INC_zpage_x) // 0xf6 GetZPage_X DoINC Continue -E(op_INC_abs) // 0xee +ENTRY(op_INC_abs) // 0xee GetAbs DoINC Continue -E(op_INC_abs_x) // 0xfe +ENTRY(op_INC_abs_x) // 0xfe GetAbs_X DoINC Continue @@ -1223,7 +1223,7 @@ E(op_INC_abs_x) // 0xfe INX instruction ---------------------------------- */ -E(op_INX) // 0xe8 +ENTRY(op_INX) // 0xe8 incb X_Reg FlagNZ Continue @@ -1232,7 +1232,7 @@ E(op_INX) // 0xe8 INY instruction ---------------------------------- */ -E(op_INY) // 0xc8 +ENTRY(op_INY) // 0xc8 incb Y_Reg FlagNZ Continue @@ -1242,12 +1242,12 @@ E(op_INY) // 0xc8 JuMP to new location ---------------------------------- */ -E(op_JMP_abs) +ENTRY(op_JMP_abs) GetAbs movw EffectiveAddr, PC_Reg; Continue -E(op_JMP_ind) // 0x6c +ENTRY(op_JMP_ind) // 0x6c GetFromPC_W cmpb $0xFF, %al je jmp_special @@ -1265,7 +1265,7 @@ jmp_special: // see JMP indirect note in _Understanding the Apple IIe_ 4-25 Continue // 65c02 : 0x7C -E(op_JMP_abs_ind_x) +ENTRY(op_JMP_abs_ind_x) GetFromPC_W movw %ax, EffectiveAddr movzbLQ X_Reg, _XAX @@ -1278,7 +1278,7 @@ E(op_JMP_abs_ind_x) JSR instruction ---------------------------------- */ -E(op_JSR) // 0x20 +ENTRY(op_JSR) // 0x20 GetAbs movw PC_Reg, %ax decw %ax @@ -1298,52 +1298,52 @@ E(op_JSR) // 0x20 LoaD Accumulator with memory ---------------------------------- */ -E(op_LDA_imm) // 0xa9 +ENTRY(op_LDA_imm) // 0xa9 GetImm DoLDA Continue -E(op_LDA_zpage) // 0xa5 +ENTRY(op_LDA_zpage) // 0xa5 GetZPage DoLDA Continue -E(op_LDA_zpage_x) // 0xb5 +ENTRY(op_LDA_zpage_x) // 0xb5 GetZPage_X DoLDA Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_LDA_zpage_y) +ENTRY(op_LDA_zpage_y) jmp CALL(op_NOP) -E(op_LDA_abs) // 0xad +ENTRY(op_LDA_abs) // 0xad GetAbs DoLDA Continue -E(op_LDA_abs_x) // 0xbd +ENTRY(op_LDA_abs_x) // 0xbd GetAbs_X DoLDA Continue -E(op_LDA_abs_y) // 0xb9 +ENTRY(op_LDA_abs_y) // 0xb9 GetAbs_Y DoLDA Continue -E(op_LDA_ind_x) // 0xa1 +ENTRY(op_LDA_ind_x) // 0xa1 GetIndZPage_X DoLDA Continue -E(op_LDA_ind_y) // 0xb1 +ENTRY(op_LDA_ind_y) // 0xb1 GetIndZPage_Y DoLDA Continue // 65c02 : 0xB2 -E(op_LDA_ind_zpage) +ENTRY(op_LDA_ind_zpage) GetIndZPage DoLDA Continue @@ -1352,28 +1352,28 @@ E(op_LDA_ind_zpage) LDX instructions ---------------------------------- */ -E(op_LDX_imm) // 0xa2 +ENTRY(op_LDX_imm) // 0xa2 GetImm DoLDX Continue -E(op_LDX_zpage) // 0xa6 +ENTRY(op_LDX_zpage) // 0xa6 GetZPage DoLDX Continue // HACK : is this used? need to study coverage ... -E(op_LDX_zpage_y) // 0xb6 +ENTRY(op_LDX_zpage_y) // 0xb6 GetZPage_Y DoLDX Continue -E(op_LDX_abs) // 0xae +ENTRY(op_LDX_abs) // 0xae GetAbs DoLDX Continue -E(op_LDX_abs_y) // 0xbe +ENTRY(op_LDX_abs_y) // 0xbe GetAbs_Y DoLDX Continue @@ -1382,27 +1382,27 @@ E(op_LDX_abs_y) // 0xbe LDY instructions ---------------------------------- */ -E(op_LDY_imm) // 0xa0 +ENTRY(op_LDY_imm) // 0xa0 GetImm DoLDY Continue -E(op_LDY_zpage) // 0xa4 +ENTRY(op_LDY_zpage) // 0xa4 GetZPage DoLDY Continue -E(op_LDY_zpage_x) // 0xb4 +ENTRY(op_LDY_zpage_x) // 0xb4 GetZPage_X DoLDY Continue -E(op_LDY_abs) // 0xac +ENTRY(op_LDY_abs) // 0xac GetAbs DoLDY Continue -E(op_LDY_abs_x) // 0xbc +ENTRY(op_LDY_abs_x) // 0xbc GetAbs_X DoLDY Continue @@ -1411,27 +1411,27 @@ E(op_LDY_abs_x) // 0xbc LSR instructions ---------------------------------- */ -E(op_LSR_acc) // 0x4a +ENTRY(op_LSR_acc) // 0x4a shrb $1, A_Reg FlagNZC Continue -E(op_LSR_zpage) // 0x46 +ENTRY(op_LSR_zpage) // 0x46 GetZPage DoLSR Continue -E(op_LSR_zpage_x) // 0x56 +ENTRY(op_LSR_zpage_x) // 0x56 GetZPage_X DoLSR Continue -E(op_LSR_abs) // 0x4e +ENTRY(op_LSR_abs) // 0x4e GetAbs DoLSR Continue -E(op_LSR_abs_x) // 0x5e +ENTRY(op_LSR_abs_x) // 0x5e GetAbs_X DoLSR Continue @@ -1440,59 +1440,59 @@ E(op_LSR_abs_x) // 0x5e NOP instruction ---------------------------------- */ -E(op_NOP) // 0xea +ENTRY(op_NOP) // 0xea Continue /* ---------------------------------- ORA instructions ---------------------------------- */ -E(op_ORA_imm) // 0x09 +ENTRY(op_ORA_imm) // 0x09 GetImm DoORA Continue -E(op_ORA_zpage) // 0x05 +ENTRY(op_ORA_zpage) // 0x05 GetZPage DoORA Continue -E(op_ORA_zpage_x) // 0x15 +ENTRY(op_ORA_zpage_x) // 0x15 GetZPage_X DoORA Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_ORA_zpage_y) +ENTRY(op_ORA_zpage_y) jmp CALL(op_NOP) -E(op_ORA_abs) // 0x0d +ENTRY(op_ORA_abs) // 0x0d GetAbs DoORA Continue -E(op_ORA_abs_x) // 0x1d +ENTRY(op_ORA_abs_x) // 0x1d GetAbs_X DoORA Continue -E(op_ORA_abs_y) // 0x19 +ENTRY(op_ORA_abs_y) // 0x19 GetAbs_Y DoORA Continue -E(op_ORA_ind_x) // 0x01 +ENTRY(op_ORA_ind_x) // 0x01 GetIndZPage_X DoORA Continue -E(op_ORA_ind_y) // 0x11 +ENTRY(op_ORA_ind_y) // 0x11 GetIndZPage_Y DoORA Continue // 65c02 : 0x12 -E(op_ORA_ind_zpage) +ENTRY(op_ORA_ind_zpage) GetIndZPage DoORA Continue @@ -1501,7 +1501,7 @@ E(op_ORA_ind_zpage) PHA instruction ---------------------------------- */ -E(op_PHA) // 0x48 +ENTRY(op_PHA) // 0x48 Push(A_Reg) Continue @@ -1509,10 +1509,10 @@ E(op_PHA) // 0x48 PHP instruction ---------------------------------- */ -E(op_PHP) // 0x08 +ENTRY(op_PHP) // 0x08 movb F_Reg, %al - SNX_PROLOGUE(cpu65_flags_encode) - movb SNX(cpu65_flags_encode,_XAX,1), %al + SYMX_PROLOGUE(cpu65_flags_encode) + movb SYMX(cpu65_flags_encode,_XAX,1), %al Push(%al) Continue @@ -1521,7 +1521,7 @@ E(op_PHP) // 0x08 65c02 : 0xDA ---------------------------------- */ -E(op_PHX) +ENTRY(op_PHX) Push(X_Reg) Continue @@ -1530,7 +1530,7 @@ E(op_PHX) 65c02 : 0x5A ---------------------------------- */ -E(op_PHY) +ENTRY(op_PHY) #ifdef APPLE_ASSEMBLER_IS_BROKEN movb Y_Reg, %al Push(%al) @@ -1543,7 +1543,7 @@ E(op_PHY) PLA instruction ---------------------------------- */ -E(op_PLA) // 0x68 +ENTRY(op_PLA) // 0x68 Pop(A_Reg) orb A_Reg, A_Reg FlagNZ @@ -1553,14 +1553,14 @@ E(op_PLA) // 0x68 PLP instruction ---------------------------------- */ -E(op_PLP) // 0x28 +ENTRY(op_PLP) // 0x28 Pop(%al) - SNX_PROLOGUE(cpu65_flags_decode) + SYMX_PROLOGUE(cpu65_flags_decode) #ifdef APPLE_ASSEMBLER_IS_BROKEN - movb SNX(cpu65_flags_decode,_XAX,1), %al + movb SYMX(cpu65_flags_decode,_XAX,1), %al movb %al, F_Reg #else - movb SNX(cpu65_flags_decode,_XAX,1), F_Reg + movb SYMX(cpu65_flags_decode,_XAX,1), F_Reg #endif orb $(B_Flag|X_Flag), F_Reg Continue @@ -1570,7 +1570,7 @@ E(op_PLP) // 0x28 65c02 : 0xFA ---------------------------------- */ -E(op_PLX) +ENTRY(op_PLX) Pop(X_Reg) orb X_Reg, X_Reg FlagNZ @@ -1581,7 +1581,7 @@ E(op_PLX) 65c02 : 0x7A ---------------------------------- */ -E(op_PLY) +ENTRY(op_PLY) #ifdef APPLE_ASSEMBLER_IS_BROKEN Pop(%al) movb %al, Y_Reg @@ -1596,28 +1596,28 @@ E(op_PLY) ROL instructions ---------------------------------- */ -E(op_ROL_acc) // 0x2a +ENTRY(op_ROL_acc) // 0x2a bt $C_Flag_Bit, AF_Reg_X adcb A_Reg, A_Reg FlagNZC Continue -E(op_ROL_zpage) // 0x26 +ENTRY(op_ROL_zpage) // 0x26 GetZPage DoROL Continue -E(op_ROL_zpage_x) // 0x36 +ENTRY(op_ROL_zpage_x) // 0x36 GetZPage_X DoROL Continue -E(op_ROL_abs) // 0x2e +ENTRY(op_ROL_abs) // 0x2e GetAbs DoROL Continue -E(op_ROL_abs_x) // 0x3e +ENTRY(op_ROL_abs_x) // 0x3e GetAbs_X DoROL Continue @@ -1626,29 +1626,29 @@ E(op_ROL_abs_x) // 0x3e ROR instructions ---------------------------------- */ /* NB: assumes A_Reg = %cl, F_Reg = %ch */ -E(op_ROR_acc) // 0x6a +ENTRY(op_ROR_acc) // 0x6a rorw $1, %cx /* Roll flags into accum */ adcb F_Reg, F_Reg /* Roll carry into flags */ orb A_Reg, A_Reg FlagNZ /* implied C */ Continue -E(op_ROR_zpage) // 0x66 +ENTRY(op_ROR_zpage) // 0x66 GetZPage DoROR Continue -E(op_ROR_zpage_x) // 0x76 +ENTRY(op_ROR_zpage_x) // 0x76 GetZPage_X DoROR Continue -E(op_ROR_abs) // 0x6e +ENTRY(op_ROR_abs) // 0x6e GetAbs DoROR Continue -E(op_ROR_abs_x) // 0x7e +ENTRY(op_ROR_abs_x) // 0x7e GetAbs_X DoROR Continue @@ -1657,14 +1657,14 @@ E(op_ROR_abs_x) // 0x7e RTI instruction ---------------------------------- */ -E(op_RTI) // 0x40 +ENTRY(op_RTI) // 0x40 Pop(%al) - SNX_PROLOGUE(cpu65_flags_decode) + SYMX_PROLOGUE(cpu65_flags_decode) #ifdef APPLE_ASSEMBLER_IS_BROKEN - movb SNX(cpu65_flags_decode,_XAX,1), %al + movb SYMX(cpu65_flags_decode,_XAX,1), %al movb %al, F_Reg #else - movb SNX(cpu65_flags_decode,_XAX,1), F_Reg + movb SYMX(cpu65_flags_decode,_XAX,1), F_Reg #endif orb $(B_Flag|X_Flag), F_Reg Pop(%al) @@ -1682,7 +1682,7 @@ E(op_RTI) // 0x40 RTS instruction ---------------------------------- */ -E(op_RTS) // 0x60 +ENTRY(op_RTS) // 0x60 Pop(%al) #ifdef APPLE_ASSEMBLER_IS_BROKEN @@ -1701,7 +1701,7 @@ E(op_RTS) // 0x60 SuBtract memory from accumulator with Borrow ---------------------------------- */ -E(op_SBC_dec) +ENTRY(op_SBC_dec) incb DebugCycleCount // +1 cycle GetFromEA_B DebugBCDCheck @@ -1758,21 +1758,21 @@ _das_finish: popq _XBX #endif Continue -E(op_SBC_imm) // 0xe9 +ENTRY(op_SBC_imm) // 0xe9 GetImm testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version DoSBC_b Continue -E(op_SBC_zpage) // 0xe5 +ENTRY(op_SBC_zpage) // 0xe5 GetZPage testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version DoSBC_b Continue -E(op_SBC_zpage_x) // 0xf5 +ENTRY(op_SBC_zpage_x) // 0xf5 GetZPage_X testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version @@ -1780,38 +1780,38 @@ E(op_SBC_zpage_x) // 0xf5 Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_SBC_zpage_y) +ENTRY(op_SBC_zpage_y) jmp CALL(op_NOP) -E(op_SBC_abs) // 0xed +ENTRY(op_SBC_abs) // 0xed GetAbs testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version DoSBC_b Continue -E(op_SBC_abs_x) // 0xfd +ENTRY(op_SBC_abs_x) // 0xfd GetAbs_X testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version DoSBC_b Continue -E(op_SBC_abs_y) // 0xf9 +ENTRY(op_SBC_abs_y) // 0xf9 GetAbs_Y testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version DoSBC_b Continue -E(op_SBC_ind_x) // 0xe1 +ENTRY(op_SBC_ind_x) // 0xe1 GetIndZPage_X testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version DoSBC_b Continue -E(op_SBC_ind_y) // 0xf1 +ENTRY(op_SBC_ind_y) // 0xf1 GetIndZPage_Y testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version @@ -1819,7 +1819,7 @@ E(op_SBC_ind_y) // 0xf1 Continue // 65c02 : 0xF2 -E(op_SBC_ind_zpage) +ENTRY(op_SBC_ind_zpage) GetIndZPage testb $D_Flag, F_Reg // Decimal mode? jnz CALL(op_SBC_dec) // Yes, jump to decimal version @@ -1830,7 +1830,7 @@ E(op_SBC_ind_zpage) SEC instruction ---------------------------------- */ -E(op_SEC) // 0x38 +ENTRY(op_SEC) // 0x38 orb $C_Flag, F_Reg Continue @@ -1838,7 +1838,7 @@ E(op_SEC) // 0x38 SED instruction ---------------------------------- */ -E(op_SED) // 0xf8 +ENTRY(op_SED) // 0xf8 orb $D_Flag, F_Reg Continue @@ -1846,7 +1846,7 @@ E(op_SED) // 0xf8 SEI instruction ---------------------------------- */ -E(op_SEI) // 0x78 +ENTRY(op_SEI) // 0x78 orb $I_Flag, F_Reg Continue @@ -1855,75 +1855,75 @@ E(op_SEI) // 0x78 UNIMPLEMENTED : These are documented in the W65C02S datasheet ... ---------------------------------- */ -E(op_SMB0_65c02) +ENTRY(op_SMB0_65c02) Continue -E(op_SMB1_65c02) +ENTRY(op_SMB1_65c02) Continue -E(op_SMB2_65c02) +ENTRY(op_SMB2_65c02) Continue -E(op_SMB3_65c02) +ENTRY(op_SMB3_65c02) Continue -E(op_SMB4_65c02) +ENTRY(op_SMB4_65c02) Continue -E(op_SMB5_65c02) +ENTRY(op_SMB5_65c02) Continue -E(op_SMB6_65c02) +ENTRY(op_SMB6_65c02) Continue -E(op_SMB7_65c02) +ENTRY(op_SMB7_65c02) Continue /* ---------------------------------- STA instructions ---------------------------------- */ -E(op_STA_zpage) // 0x85 +ENTRY(op_STA_zpage) // 0x85 GetZPage DoSTA Continue -E(op_STA_zpage_x) // 0x95 +ENTRY(op_STA_zpage_x) // 0x95 GetZPage_X DoSTA Continue // UNIMPLEMENTED : W65C02S datasheet -E(op_STA_zpage_y) +ENTRY(op_STA_zpage_y) jmp CALL(op_NOP) -E(op_STA_abs) // 0x8d +ENTRY(op_STA_abs) // 0x8d GetAbs DoSTA Continue -E(op_STA_abs_x) // 0x9d +ENTRY(op_STA_abs_x) // 0x9d GetAbs_X_STx DoSTA Continue -E(op_STA_abs_y) // 0x99 +ENTRY(op_STA_abs_y) // 0x99 GetAbs_Y_STA DoSTA Continue -E(op_STA_ind_x) // 0x81 +ENTRY(op_STA_ind_x) // 0x81 GetIndZPage_X DoSTA Continue -E(op_STA_ind_y) // 0x91 +ENTRY(op_STA_ind_y) // 0x91 GetIndZPage_Y_STA DoSTA Continue // 65c02 : 0x92 -E(op_STA_ind_zpage) +ENTRY(op_STA_ind_zpage) GetIndZPage DoSTA Continue @@ -1933,7 +1933,7 @@ E(op_STA_ind_zpage) UNIMPLEMENTED : This is documented in the W65C02S datasheet ... ---------------------------------- */ -E(op_STP_65c02) +ENTRY(op_STP_65c02) Continue /* ---------------------------------- @@ -1941,46 +1941,46 @@ E(op_STP_65c02) UNIMPLEMENTED : These are documented in the W65C02S datasheet ... ---------------------------------- */ -E(op_RMB0_65c02) +ENTRY(op_RMB0_65c02) Continue -E(op_RMB1_65c02) +ENTRY(op_RMB1_65c02) Continue -E(op_RMB2_65c02) +ENTRY(op_RMB2_65c02) Continue -E(op_RMB3_65c02) +ENTRY(op_RMB3_65c02) Continue -E(op_RMB4_65c02) +ENTRY(op_RMB4_65c02) Continue -E(op_RMB5_65c02) +ENTRY(op_RMB5_65c02) Continue -E(op_RMB6_65c02) +ENTRY(op_RMB6_65c02) Continue -E(op_RMB7_65c02) +ENTRY(op_RMB7_65c02) Continue /* ---------------------------------- STX instructions ---------------------------------- */ -E(op_STX_zpage) // 0x86 +ENTRY(op_STX_zpage) // 0x86 GetZPage DoSTX Continue // HACK : is this used? need to study coverage ... -E(op_STX_zpage_y) // 0x96 +ENTRY(op_STX_zpage_y) // 0x96 GetZPage_Y DoSTX Continue -E(op_STX_abs) // 0x8e +ENTRY(op_STX_abs) // 0x8e GetAbs DoSTX Continue @@ -1989,17 +1989,17 @@ E(op_STX_abs) // 0x8e STY instructions ---------------------------------- */ -E(op_STY_zpage) // 0x84 +ENTRY(op_STY_zpage) // 0x84 GetZPage DoSTY Continue -E(op_STY_zpage_x) // 0x94 +ENTRY(op_STY_zpage_x) // 0x94 GetZPage_X DoSTY Continue -E(op_STY_abs) // 0x8c +ENTRY(op_STY_abs) // 0x8c GetAbs DoSTY Continue @@ -2010,25 +2010,25 @@ E(op_STY_abs) // 0x8c ---------------------------------- */ // 65c02 : 0x64 -E(op_STZ_zpage) +ENTRY(op_STZ_zpage) GetZPage DoSTZ Continue // 65c02 : 0x74 -E(op_STZ_zpage_x) +ENTRY(op_STZ_zpage_x) GetZPage_X DoSTZ Continue // 65c02 : 0x9C -E(op_STZ_abs) +ENTRY(op_STZ_abs) GetAbs DoSTZ Continue // 65c02 : 0x9E -E(op_STZ_abs_x) +ENTRY(op_STZ_abs_x) GetAbs_X_STx DoSTZ Continue @@ -2037,7 +2037,7 @@ E(op_STZ_abs_x) TAX instruction ---------------------------------- */ -E(op_TAX) // 0xaa +ENTRY(op_TAX) // 0xaa movb A_Reg, X_Reg orb X_Reg, X_Reg FlagNZ @@ -2047,7 +2047,7 @@ E(op_TAX) // 0xaa TAY instruction ---------------------------------- */ -E(op_TAY) // 0xa8 +ENTRY(op_TAY) // 0xa8 movb A_Reg, Y_Reg orb Y_Reg, Y_Reg FlagNZ @@ -2059,13 +2059,13 @@ E(op_TAY) // 0xa8 ---------------------------------- */ // 65c02 : 0x1C -E(op_TRB_abs) +ENTRY(op_TRB_abs) GetAbs DoTRB Continue // 65c02 : 0x14 -E(op_TRB_zpage) +ENTRY(op_TRB_zpage) GetZPage DoTRB Continue @@ -2076,13 +2076,13 @@ E(op_TRB_zpage) ---------------------------------- */ // 65c02 : 0x0C -E(op_TSB_abs) +ENTRY(op_TSB_abs) GetAbs DoTSB Continue // 65c02 : 0x04 -E(op_TSB_zpage) +ENTRY(op_TSB_zpage) GetZPage DoTSB Continue @@ -2091,7 +2091,7 @@ E(op_TSB_zpage) TSX instruction ---------------------------------- */ -E(op_TSX) // 0xba +ENTRY(op_TSX) // 0xba movb SP_Reg_L, X_Reg orb X_Reg, X_Reg FlagNZ @@ -2101,7 +2101,7 @@ E(op_TSX) // 0xba TXA instruction ---------------------------------- */ -E(op_TXA) // 0x8a +ENTRY(op_TXA) // 0x8a movb X_Reg, A_Reg orb A_Reg, A_Reg FlagNZ @@ -2111,7 +2111,7 @@ E(op_TXA) // 0x8a TXS instruction ---------------------------------- */ -E(op_TXS) // 0x9a +ENTRY(op_TXS) // 0x9a movb X_Reg, SP_Reg_L Continue @@ -2119,7 +2119,7 @@ E(op_TXS) // 0x9a TYA instruction ---------------------------------- */ -E(op_TYA) // 0x98 +ENTRY(op_TYA) // 0x98 movb Y_Reg, A_Reg orb A_Reg, A_Reg FlagNZ @@ -2129,14 +2129,14 @@ E(op_TYA) // 0x98 ??? instruction - 65c02 Defined as NOPs by spec ---------------------------------- */ -E(op_UNK_65c02) +ENTRY(op_UNK_65c02) Continue /* ---------------------------------- WAI instruction - 65c02 UNIMPLEMENTED : This is documented in the W65C02S datasheet ... ---------------------------------- */ -E(op_WAI_65c02) +ENTRY(op_WAI_65c02) Continue #pragma mark - @@ -2149,18 +2149,18 @@ E(op_WAI_65c02) continue: movzbLQ DebugCurrOpcode, _XAX - SNX_PROLOGUE(cpu65__opcycles) - movb SNX(cpu65__opcycles,_XAX,1), %al + SYMX_PROLOGUE(cpu65__opcycles) + movb SYMX(cpu65__opcycles,_XAX,1), %al addb DebugCycleCount, %al movb %al, DebugCycleCount TRACE_EPILOGUE - addl %eax, SN(cpu65_cycle_count) - subl %eax, SN(gc_cycles_timer_0) - subl %eax, SN(gc_cycles_timer_1) - subl %eax, SN(cpu65_cycles_to_execute) + addl %eax, SYM(cpu65_cycle_count) + subl %eax, SYM(gc_cycles_timer_0) + subl %eax, SYM(gc_cycles_timer_1) + subl %eax, SYM(cpu65_cycles_to_execute) jle exit_cpu65_run continue1: xorLQ _XAX, _XAX - orb SN(cpu65__signal), %al + orb SYM(cpu65__signal), %al jnz exception 1: JumpNextInstruction @@ -2172,11 +2172,11 @@ exception: testb $ResetSig, %al jnz ex_reset0 jmp ex_irq -ex_reset0: testb $0xff, SN(joy_button0) // OpenApple +ex_reset0: testb $0xff, SYM(joy_button0) // OpenApple jnz emul_reinit - testb $0xff, SN(joy_button1) // ClosedApple + testb $0xff, SYM(joy_button1) // ClosedApple jnz emul_reinit -ex_reset: movb $0, SN(cpu65__signal) +ex_reset: movb $0, SYM(cpu65__signal) movw $0xFFFC, EffectiveAddr // ROM reset vector GetFromEA_W movw %ax, PC_Reg @@ -2198,8 +2198,8 @@ ex_irq: testb $I_Flag, F_Reg // Already interrupt orb $X_Flag, F_Reg xorw %ax, %ax movb F_Reg, %al - SNX_PROLOGUE(cpu65_flags_encode) - movb SNX(cpu65_flags_encode,_XAX,1), %al + SYMX_PROLOGUE(cpu65_flags_encode) + movb SYMX(cpu65_flags_encode,_XAX,1), %al Push(%al) orb $(B_Flag | I_Flag), F_Reg //andb $~D_Flag, F_Reg // AppleWin clears Decimal bit? @@ -2213,38 +2213,38 @@ ex_irq: testb $I_Flag, F_Reg // Already interrupt CPU thread main entry and exit points ------------------------------------------------------------------------- */ -E(cpu65_run) +ENTRY(cpu65_run) #ifdef __LP64__ pushq %rbx // NOTE: should we be also preserving r12-r15? #endif pushLQ _XBP movLQ _XSP, _XBP - cmpb $0, SN(emul_reinitialize) + cmpb $0, SYM(emul_reinitialize) jnz 1f // Restore CPU state when being called from C. movLQ $0x0100, SP_Reg_X movzwLQ DebugCurrEA, EffectiveAddr_X - movzwLQ SN(cpu65_pc), PC_Reg_X - movzbLQ SN(cpu65_a), AF_Reg_X - movzbLQ SN(cpu65_f), _XAX - SNX_PROLOGUE(cpu65_flags_decode) + movzwLQ SYM(cpu65_pc), PC_Reg_X + movzbLQ SYM(cpu65_a), AF_Reg_X + movzbLQ SYM(cpu65_f), _XAX + SYMX_PROLOGUE(cpu65_flags_decode) #ifdef APPLE_ASSEMBLER_IS_BROKEN - movb SNX(cpu65_flags_decode,_XAX,1), %al + movb SYMX(cpu65_flags_decode,_XAX,1), %al movb %al, F_Reg #else - movb SNX(cpu65_flags_decode,_XAX,1), F_Reg + movb SYMX(cpu65_flags_decode,_XAX,1), F_Reg #endif - movzbLQ SN(cpu65_x), XY_Reg_X - movb SN(cpu65_y), Y_Reg - movb SN(cpu65_sp), SP_Reg_L + movzbLQ SYM(cpu65_x), XY_Reg_X + movb SYM(cpu65_y), Y_Reg + movb SYM(cpu65_sp), SP_Reg_L #ifdef APPLE2_VM RestoreAltZP #endif jmp continue1 -1: movb $0, SN(emul_reinitialize) +1: movb $0, SYM(emul_reinitialize) // Zero all used registers xorLQ _XAX, _XAX xorLQ XY_Reg_X, XY_Reg_X @@ -2260,12 +2260,12 @@ E(cpu65_run) exit_cpu65_run: // Save CPU state when returning from being called from C - movw PC_Reg, SN(cpu65_pc) + movw PC_Reg, SYM(cpu65_pc) CommonSaveCPUState() jmp exit_frame -emul_reinit: movb $0, SN(cpu65__signal) - movb $1, SN(emul_reinitialize) +emul_reinit: movb $0, SYM(cpu65__signal) + movb $1, SYM(emul_reinitialize) exit_frame: popLQ _XBP #ifdef __LP64__ @@ -2277,12 +2277,12 @@ exit_frame: popLQ _XBP Debugger hooks ------------------------------------------------------------------------- */ -E(cpu65_direct_write) +ENTRY(cpu65_direct_write) pushLQ EffectiveAddr_X movLQ 8(_XSP),EffectiveAddr_X movLQ 12(_XSP),_XAX - SNX_PROLOGUE(cpu65_vmem_w) - callLQ *SNX(cpu65_vmem_w,EffectiveAddr_X,SZ_PTR) + SYMX_PROLOGUE(cpu65_vmem_w) + callLQ *SYMX(cpu65_vmem_w,EffectiveAddr_X,SZ_PTR) popLQ EffectiveAddr_X ret diff --git a/src/x86/glue-prologue.h b/src/x86/glue-prologue.h index cea63a2a..366d8035 100644 --- a/src/x86/glue-prologue.h +++ b/src/x86/glue-prologue.h @@ -19,33 +19,33 @@ #include "cpu-regs.h" #define GLUE_BANK_MAYBEREAD(func,pointer) \ -E(func) testLQ $SS_CXROM, SN(softswitches); \ +ENTRY(func) testLQ $SS_CXROM, SYM(softswitches); \ jnz 1f; \ - callLQ *SN(pointer); \ + callLQ *SYM(pointer); \ ret; \ -1: addLQ SN(pointer),EffectiveAddr_X; \ +1: addLQ SYM(pointer),EffectiveAddr_X; \ movb (EffectiveAddr_X),%al; \ - subLQ SN(pointer),EffectiveAddr_X; \ + subLQ SYM(pointer),EffectiveAddr_X; \ ret; #define GLUE_BANK_READ(func,pointer) \ -E(func) addLQ SN(pointer),EffectiveAddr_X; \ +ENTRY(func) addLQ SYM(pointer),EffectiveAddr_X; \ movb (EffectiveAddr_X),%al; \ - subLQ SN(pointer),EffectiveAddr_X; \ + subLQ SYM(pointer),EffectiveAddr_X; \ ret; #define GLUE_BANK_WRITE(func,pointer) \ -E(func) addLQ SN(pointer),EffectiveAddr_X; \ +ENTRY(func) addLQ SYM(pointer),EffectiveAddr_X; \ movb %al,(EffectiveAddr_X); \ - subLQ SN(pointer),EffectiveAddr_X; \ + subLQ SYM(pointer),EffectiveAddr_X; \ ret; #define GLUE_BANK_MAYBEWRITE(func,pointer) \ -E(func) addLQ SN(pointer),EffectiveAddr_X; \ - cmpl $0,SN(pointer); \ +ENTRY(func) addLQ SYM(pointer),EffectiveAddr_X; \ + cmpl $0,SYM(pointer); \ jz 1f; \ movb %al,(EffectiveAddr_X); \ -1: subLQ SN(pointer),EffectiveAddr_X; \ +1: subLQ SYM(pointer),EffectiveAddr_X; \ ret; @@ -61,7 +61,7 @@ E(func) addLQ SN(pointer),EffectiveAddr_X; \ #endif #define GLUE_C_WRITE(func) \ -E(func) pushLQ _XAX; \ +ENTRY(func) pushLQ _XAX; \ pushLQ XY_Reg_X; \ pushLQ AF_Reg_X; \ pushLQ SP_Reg_X; \ @@ -79,7 +79,7 @@ E(func) pushLQ _XAX; \ // TODO FIXME : implement CDECL prologue/epilogues... #define _GLUE_C_READ(func, ...) \ -E(func) pushLQ XY_Reg_X; \ +ENTRY(func) pushLQ XY_Reg_X; \ pushLQ AF_Reg_X; \ pushLQ SP_Reg_X; \ pushLQ PC_Reg_X; \ From 7bd7899325fae6bc360ad6b9a941a05a3097ac40 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Mon, 9 Feb 2015 21:45:20 -0800 Subject: [PATCH 045/615] REFACTOR : brace x86-specific defines, prep for __arm__ --- src/cpu.h | 47 +++++++++++++++++++++-------------------- src/x86/cpu-regs.h | 3 --- src/x86/glue-prologue.h | 1 - 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/cpu.h b/src/cpu.h index b55d96d4..85aebdc6 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -17,6 +17,9 @@ #ifndef __CPU_H_ #define __CPU_H_ +// Virtual machine is an Apple //e (not an NES, etc...) +#define APPLE2_VM 1 + #if !defined(__ASSEMBLER__) #include #include @@ -76,30 +79,28 @@ void cpu65_trace_checkpoint(void); #define IRQGeneric 0x80 /* Note: These are *not* the bit positions used for the flags in the P - * register of a real 6502. Rather, they have been distorted so that C, - * N and Z match the analogous flags in the _80386_ flags register. - * - * Additionally, V matches the position of the overflow flag in the high byte - * of the 80386 register. - * + * register of a real 65c02. Rather, they have been distorted so that C, + * N, Z, etc match the analogous flags in the host flags register. */ -#define C_Flag 0x1 /* 6502 Carry */ -#define X_Flag 0x2 /* 6502 Xtra */ -#define I_Flag 0x4 /* 6502 Interrupt disable */ -#define V_Flag 0x8 /* 6502 Overflow */ -#define B_Flag 0x10 /* 6502 Break */ -#define D_Flag 0x20 /* 6502 Decimal mode */ -#define Z_Flag 0x40 /* 6502 Zero */ -#define N_Flag 0x80 /* 6502 Neg */ - -#define C_Flag_Bit 8 /* 6502 Carry */ -#define X_Flag_Bit 9 /* 6502 Xtra */ -#define I_Flag_Bit 10 /* 6502 Interrupt disable */ -#define V_Flag_Bit 11 /* 6502 Overflow */ -#define B_Flag_Bit 12 /* 6502 Break */ -#define D_Flag_Bit 13 /* 6502 Decimal mode */ -#define Z_Flag_Bit 14 /* 6502 Zero */ -#define N_Flag_Bit 15 /* 6502 Neg */ +#if defined(__i386__) || defined(__x86_64__) +/* + * x86 NOTE: V matches the position of the overflow flag in the high byte + * of the 80386 register. + */ +# define C_Flag_Bit 8 /* 6502 Carry */ +# define C_Flag (C_Flag_Bit>>3) /* 6502 Carry */ +# define X_Flag 0x2 /* 6502 Xtra */ +# define I_Flag 0x4 /* 6502 Interrupt disable */ +# define V_Flag 0x8 /* 6502 Overflow */ +# define B_Flag 0x10 /* 6502 Break */ +# define D_Flag 0x20 /* 6502 Decimal mode */ +# define Z_Flag 0x40 /* 6502 Zero */ +# define N_Flag 0x80 /* 6502 Neg */ +#elif defined(__arm__) +# error in progress ... +#elif defined(__aarch64__) +# error soon ... +#endif /* * 6502 flags bit mask diff --git a/src/x86/cpu-regs.h b/src/x86/cpu-regs.h index 63e696e1..e6428268 100644 --- a/src/x86/cpu-regs.h +++ b/src/x86/cpu-regs.h @@ -18,9 +18,6 @@ #include "cpu.h" -// Virtual machine is an Apple ][ (not an NES, etc...) -#define APPLE2_VM 1 - #define X_Reg %bl /* 6502 X register in %bl */ #define Y_Reg %bh /* 6502 Y register in %bh */ #define A_Reg %cl /* 6502 A register in %cl */ diff --git a/src/x86/glue-prologue.h b/src/x86/glue-prologue.h index 366d8035..3d78b78a 100644 --- a/src/x86/glue-prologue.h +++ b/src/x86/glue-prologue.h @@ -14,7 +14,6 @@ * */ -#define __ASSEMBLY__ #include "misc.h" #include "cpu-regs.h" From 9a87aa8a3d42c4438ad1480c05574e5bcb73bc2b Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 14 Feb 2015 21:28:01 -0800 Subject: [PATCH 046/615] REFACTOR : migrate stack page offset into altzp configuration --- src/x86/cpu-regs.h | 1 + src/x86/cpu.S | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/x86/cpu-regs.h b/src/x86/cpu-regs.h index e6428268..cdc60ace 100644 --- a/src/x86/cpu-regs.h +++ b/src/x86/cpu-regs.h @@ -38,6 +38,7 @@ /* Apple //e set stack point to ALTZP (or not) */ \ movLQ SYM(base_stackzp), _XAX; \ subLQ SYM(base_vmem), _XAX; \ + orLQ $0x0100, SP_Reg_X; \ orLQ _XAX, SP_Reg_X; #ifdef __LP64__ diff --git a/src/x86/cpu.S b/src/x86/cpu.S index be4663ae..954c0203 100644 --- a/src/x86/cpu.S +++ b/src/x86/cpu.S @@ -2224,7 +2224,6 @@ ENTRY(cpu65_run) jnz 1f // Restore CPU state when being called from C. - movLQ $0x0100, SP_Reg_X movzwLQ DebugCurrEA, EffectiveAddr_X movzwLQ SYM(cpu65_pc), PC_Reg_X movzbLQ SYM(cpu65_a), AF_Reg_X @@ -2238,7 +2237,7 @@ ENTRY(cpu65_run) #endif movzbLQ SYM(cpu65_x), XY_Reg_X movb SYM(cpu65_y), Y_Reg - movb SYM(cpu65_sp), SP_Reg_L + movzbLQ SYM(cpu65_sp), SP_Reg_X #ifdef APPLE2_VM RestoreAltZP #endif From 12ba31f1af45df30618c9fe9d9b827d37ee97ee8 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 14 Feb 2015 21:30:06 -0800 Subject: [PATCH 047/615] REFACTOR : whitespace, style, remove deadcode, extract code into macro --- src/cpu.h | 6 ++-- src/x86/cpu.S | 90 +++++++++++++++++++-------------------------------- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/src/cpu.h b/src/cpu.h index 85aebdc6..fb87c3f3 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -91,15 +91,17 @@ void cpu65_trace_checkpoint(void); # define C_Flag (C_Flag_Bit>>3) /* 6502 Carry */ # define X_Flag 0x2 /* 6502 Xtra */ # define I_Flag 0x4 /* 6502 Interrupt disable */ -# define V_Flag 0x8 /* 6502 Overflow */ +# define V_Flag 0x8 /* 6502 oVerflow */ # define B_Flag 0x10 /* 6502 Break */ # define D_Flag 0x20 /* 6502 Decimal mode */ # define Z_Flag 0x40 /* 6502 Zero */ -# define N_Flag 0x80 /* 6502 Neg */ +# define N_Flag 0x80 /* 6502 Negative */ #elif defined(__arm__) # error in progress ... #elif defined(__aarch64__) # error soon ... +#else +# error unknown machine architecture #endif /* diff --git a/src/x86/cpu.S b/src/x86/cpu.S index 954c0203..ea1a1a99 100644 --- a/src/x86/cpu.S +++ b/src/x86/cpu.S @@ -263,17 +263,6 @@ _GetAbs_Y \ 9: movLQ _XAX, EffectiveAddr_X; -/* Absolute Indirect Addressing - The second and third bytes of the - instruction are the low and high bytes of an address, respectively. - The contents of the fully specified memory location is the - low-order byte of the effective address. The next memory location - contains the high order byte of the effective address. */ -/* (unused at the moment. It applies to JMP, but JMP's addressing is done - * without the macro) - */ -#define GetInd GetFromPC_W; \ - GetFromMem_W(_XAX) - /* Zero Page Indirect Addressing (65c02) - The second byte of the instruction points to a memory location on page zero containing the low order byte of the effective address. The next location on page @@ -377,7 +366,7 @@ #define DoASL GetFromEA_B \ addb %al, %al; \ FlagNZC \ - PutToEA_B \ + PutToEA_B /* SAR (and the following AND) effectively moves * bit 6 to Bit 3 while leaving Bit 7 unchanged */ @@ -421,17 +410,17 @@ #define DoLDA GetFromEA_B \ movb %al, A_Reg; \ - orb %al, %al; \ + orb A_Reg, A_Reg; \ FlagNZ #define DoLDX GetFromEA_B \ movb %al, X_Reg; \ - orb %al, %al; \ + orb X_Reg, X_Reg; \ FlagNZ #define DoLDY GetFromEA_B \ movb %al, Y_Reg; \ - orb %al, %al; \ + orb Y_Reg, Y_Reg; \ FlagNZ #define DoLSR GetFromEA_B \ @@ -445,7 +434,7 @@ #define DoROL GetFromEA_B \ bt $C_Flag_Bit, AF_Reg_X; \ - adcb %al,%al; \ + adcb %al, %al; \ FlagNZC \ PutToEA_B @@ -551,24 +540,25 @@ _daa_finish: popq _XBX #endif Continue +#define maybe_DoADC_d \ + testb $D_Flag, F_Reg; /* Decimal mode? */ \ + jnz CALL(op_ADC_dec) /* Yes, jump to decimal version */ + ENTRY(op_ADC_imm) // 0x69 GetImm - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue ENTRY(op_ADC_zpage) // 0x65 GetZPage - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue ENTRY(op_ADC_zpage_x) // 0x75 GetZPage_X - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue @@ -578,44 +568,38 @@ ENTRY(op_ADC_zpage_y) ENTRY(op_ADC_abs) // 0x6d GetAbs - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue ENTRY(op_ADC_abs_x) // 0x7d GetAbs_X - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue ENTRY(op_ADC_abs_y) // 0x79 GetAbs_Y - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue ENTRY(op_ADC_ind_x) // 0x61 GetIndZPage_X - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue ENTRY(op_ADC_ind_y) // 0x71 GetIndZPage_Y - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue // 65c02 : 0x72 ENTRY(op_ADC_ind_zpage) GetIndZPage - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_ADC_dec) // Yes, jump to decimal version + maybe_DoADC_d DoADC_b Continue @@ -1532,7 +1516,7 @@ ENTRY(op_PHX) ENTRY(op_PHY) #ifdef APPLE_ASSEMBLER_IS_BROKEN - movb Y_Reg, %al + movb Y_Reg, %al Push(%al) #else Push(Y_Reg) @@ -1572,7 +1556,7 @@ ENTRY(op_PLP) // 0x28 ENTRY(op_PLX) Pop(X_Reg) - orb X_Reg, X_Reg + orb X_Reg, X_Reg FlagNZ Continue @@ -1588,7 +1572,7 @@ ENTRY(op_PLY) #else Pop(Y_Reg) #endif - orb Y_Reg, Y_Reg + orb Y_Reg, Y_Reg FlagNZ Continue @@ -1683,7 +1667,6 @@ ENTRY(op_RTI) // 0x40 ---------------------------------- */ ENTRY(op_RTS) // 0x60 - Pop(%al) #ifdef APPLE_ASSEMBLER_IS_BROKEN shlw $8, %ax @@ -1758,24 +1741,25 @@ _das_finish: popq _XBX #endif Continue +#define maybe_DoSBC_d \ + testb $D_Flag, F_Reg; /* Decimal mode? */ \ + jnz CALL(op_SBC_dec) /* Yes, jump to decimal version */ + ENTRY(op_SBC_imm) // 0xe9 GetImm - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue ENTRY(op_SBC_zpage) // 0xe5 GetZPage - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue ENTRY(op_SBC_zpage_x) // 0xf5 GetZPage_X - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue @@ -1785,44 +1769,38 @@ ENTRY(op_SBC_zpage_y) ENTRY(op_SBC_abs) // 0xed GetAbs - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue ENTRY(op_SBC_abs_x) // 0xfd GetAbs_X - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue ENTRY(op_SBC_abs_y) // 0xf9 GetAbs_Y - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue ENTRY(op_SBC_ind_x) // 0xe1 GetIndZPage_X - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue ENTRY(op_SBC_ind_y) // 0xf1 GetIndZPage_Y - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue // 65c02 : 0xF2 ENTRY(op_SBC_ind_zpage) GetIndZPage - testb $D_Flag, F_Reg // Decimal mode? - jnz CALL(op_SBC_dec) // Yes, jump to decimal version + maybe_DoSBC_d DoSBC_b Continue From 30c09da259c7a58b73e1fea8a7e88ffb15ea10cf Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 14 Feb 2015 22:22:35 -0800 Subject: [PATCH 048/615] REFACTOR : simplified/streamlined some assembly --- src/x86/cpu.S | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/x86/cpu.S b/src/x86/cpu.S index ea1a1a99..5b9c4559 100644 --- a/src/x86/cpu.S +++ b/src/x86/cpu.S @@ -2137,23 +2137,23 @@ continue: subl %eax, SYM(gc_cycles_timer_1) subl %eax, SYM(cpu65_cycles_to_execute) jle exit_cpu65_run + continue1: xorLQ _XAX, _XAX orb SYM(cpu65__signal), %al jnz exception -1: JumpNextInstruction + JumpNextInstruction /* ------------------------------------------------------------------------- Exception handlers ------------------------------------------------------------------------- */ exception: testb $ResetSig, %al - jnz ex_reset0 - jmp ex_irq + jz ex_irq + testb $0xff, SYM(joy_button0) // OpenApple + jnz exit_reinit + testb $0xff, SYM(joy_button1) // ClosedApple + jnz exit_reinit -ex_reset0: testb $0xff, SYM(joy_button0) // OpenApple - jnz emul_reinit - testb $0xff, SYM(joy_button1) // ClosedApple - jnz emul_reinit ex_reset: movb $0, SYM(cpu65__signal) movw $0xFFFC, EffectiveAddr // ROM reset vector GetFromEA_W @@ -2188,7 +2188,7 @@ ex_irq: testb $I_Flag, F_Reg // Already interrupt JumpNextInstruction /* ------------------------------------------------------------------------- - CPU thread main entry and exit points + 65c02 CPU processing loop entry point ------------------------------------------------------------------------- */ ENTRY(cpu65_run) @@ -2199,7 +2199,7 @@ ENTRY(cpu65_run) pushLQ _XBP movLQ _XSP, _XBP cmpb $0, SYM(emul_reinitialize) - jnz 1f + jnz enter_reinit // Restore CPU state when being called from C. movzwLQ DebugCurrEA, EffectiveAddr_X @@ -2221,7 +2221,7 @@ ENTRY(cpu65_run) #endif jmp continue1 -1: movb $0, SYM(emul_reinitialize) +enter_reinit: movb $0, SYM(emul_reinitialize) // Zero all used registers xorLQ _XAX, _XAX xorLQ XY_Reg_X, XY_Reg_X @@ -2235,16 +2235,23 @@ ENTRY(cpu65_run) movLQ $0x1FC, SP_Reg_X jmp ex_reset +/* ------------------------------------------------------------------------- + 65c02 CPU processing loop exit point + ------------------------------------------------------------------------- */ + exit_cpu65_run: // Save CPU state when returning from being called from C movw PC_Reg, SYM(cpu65_pc) CommonSaveCPUState() - jmp exit_frame + popLQ _XBP +#ifdef __LP64__ + popq %rbx +#endif + ret -emul_reinit: movb $0, SYM(cpu65__signal) +exit_reinit: movb $0, SYM(cpu65__signal) movb $1, SYM(emul_reinitialize) - -exit_frame: popLQ _XBP + popLQ _XBP #ifdef __LP64__ popq %rbx #endif From 080105045512f7cba4505f91d616c7c37a55a948 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 14 Feb 2015 22:58:24 -0800 Subject: [PATCH 049/615] REFACTOR : move special case initialization codepath to C --- src/cpu-supp.c | 9 ++++++++- src/x86/cpu.S | 16 +++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/cpu-supp.c b/src/cpu-supp.c index f490b37c..393be6d4 100644 --- a/src/cpu-supp.c +++ b/src/cpu-supp.c @@ -631,10 +631,17 @@ static void initialize_code_tables() } } -void cpu65_init() +void cpu65_init(void) { initialize_code_tables(); cpu65__signal = 0; + cpu65_pc = 0x0; + cpu65_ea = 0x0; + cpu65_a = 0xFF; + cpu65_x = 0xFF; + cpu65_y = 0xFF; + cpu65_f = (C_Flag_6502|X_Flag_6502|I_Flag_6502|V_Flag_6502|B_Flag_6502|Z_Flag_6502|N_Flag_6502); + cpu65_sp = 0xFC; } void cpu65_interrupt(int reason) diff --git a/src/x86/cpu.S b/src/x86/cpu.S index 5b9c4559..85de8a47 100644 --- a/src/x86/cpu.S +++ b/src/x86/cpu.S @@ -2198,8 +2198,6 @@ ENTRY(cpu65_run) #endif pushLQ _XBP movLQ _XSP, _XBP - cmpb $0, SYM(emul_reinitialize) - jnz enter_reinit // Restore CPU state when being called from C. movzwLQ DebugCurrEA, EffectiveAddr_X @@ -2219,20 +2217,11 @@ ENTRY(cpu65_run) #ifdef APPLE2_VM RestoreAltZP #endif + cmpb $0, SYM(emul_reinitialize) + jnz enter_reinit jmp continue1 enter_reinit: movb $0, SYM(emul_reinitialize) - // Zero all used registers - xorLQ _XAX, _XAX - xorLQ XY_Reg_X, XY_Reg_X - movb $0xFF, X_Reg - movb $0xFF, Y_Reg - xorLQ AF_Reg_X, AF_Reg_X - movb $0xFF, A_Reg - orb $(C_Flag|X_Flag|I_Flag|V_Flag|B_Flag|Z_Flag|N_Flag), F_Reg - xorLQ PC_Reg_X, PC_Reg_X - xorLQ EffectiveAddr_X, EffectiveAddr_X - movLQ $0x1FC, SP_Reg_X jmp ex_reset /* ------------------------------------------------------------------------- @@ -2262,6 +2251,7 @@ exit_reinit: movb $0, SYM(cpu65__signal) ------------------------------------------------------------------------- */ ENTRY(cpu65_direct_write) +#warning FIXME TODO cpu65_direct_write should be implemented in plain C ... pushLQ EffectiveAddr_X movLQ 8(_XSP),EffectiveAddr_X movLQ 12(_XSP),_XAX From 6947240d978bdd552cf798d6e40e4f4e12588b88 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sun, 15 Feb 2015 09:52:27 -0800 Subject: [PATCH 050/615] Skeleton project for Android NDK --- .gitignore | 6 ++ Android/.classpath | 9 ++ Android/AndroidManifest.xml | 25 ++++++ Android/build.xml | 83 ++++++++++++++++++ Android/jni/Android.mk | 15 ++++ Android/jni/jnihooks.c | 42 +++++++++ Android/jni/run.sh | 5 ++ Android/local.properties | 10 +++ Android/project.properties | 14 +++ Android/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 1842 bytes Android/res/drawable-ldpi/ic_launcher.png | Bin 0 -> 840 bytes Android/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1135 bytes Android/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 2578 bytes Android/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 3944 bytes Android/res/drawable-xxxhdpi/ic_launcher.png | Bin 0 -> 5557 bytes Android/res/layout/main.xml | 18 ++++ Android/res/layout/thread_main.xml | 14 +++ Android/res/values/strings.xml | 7 ++ Android/run.sh | 6 ++ .../org/deadc0de/apple2/Apple2Activity.java | 48 ++++++++++ 20 files changed, 302 insertions(+) create mode 100644 Android/.classpath create mode 100644 Android/AndroidManifest.xml create mode 100644 Android/build.xml create mode 100644 Android/jni/Android.mk create mode 100644 Android/jni/jnihooks.c create mode 100755 Android/jni/run.sh create mode 100644 Android/local.properties create mode 100644 Android/project.properties create mode 100644 Android/res/drawable-hdpi/ic_launcher.png create mode 100644 Android/res/drawable-ldpi/ic_launcher.png create mode 100644 Android/res/drawable-mdpi/ic_launcher.png create mode 100644 Android/res/drawable-xhdpi/ic_launcher.png create mode 100644 Android/res/drawable-xxhdpi/ic_launcher.png create mode 100644 Android/res/drawable-xxxhdpi/ic_launcher.png create mode 100644 Android/res/layout/main.xml create mode 100644 Android/res/layout/thread_main.xml create mode 100644 Android/res/values/strings.xml create mode 100755 Android/run.sh create mode 100644 Android/src/org/deadc0de/apple2/Apple2Activity.java diff --git a/.gitignore b/.gitignore index 2e284b82..3bfe2333 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,9 @@ man6 xcuserdata .DS_Store xcshareddata + +# Android builds +Android/bin +Android/gen +Android/libs + diff --git a/Android/.classpath b/Android/.classpath new file mode 100644 index 00000000..7bc01d9a --- /dev/null +++ b/Android/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Android/AndroidManifest.xml b/Android/AndroidManifest.xml new file mode 100644 index 00000000..7c862696 --- /dev/null +++ b/Android/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/Android/build.xml b/Android/build.xml new file mode 100644 index 00000000..4c672b9a --- /dev/null +++ b/Android/build.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Android/jni/Android.mk b/Android/jni/Android.mk new file mode 100644 index 00000000..23a25b7d --- /dev/null +++ b/Android/jni/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := apple2ix +LOCAL_SRC_FILES := jnihooks.c + +LOCAL_LDLIBS := -llog -landroid + +# Build a shared library and let Java/Dalvik drive +include $(BUILD_SHARED_LIBRARY) + +# --OR-- Build an executable so native can drive this show +#include $(BUILD_EXECUTABLE) + diff --git a/Android/jni/jnihooks.c b/Android/jni/jnihooks.c new file mode 100644 index 00000000..4d88d68c --- /dev/null +++ b/Android/jni/jnihooks.c @@ -0,0 +1,42 @@ +/* + * Apple // emulator for *nix + * + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +#include +#include + +#define LOG(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "apple2ix", fmt, __VA_ARGS__) + +#define LAUNCH_WITHOUT_JAVA 0 +#if LAUNCH_WITHOUT_JAVA + +int main(int argc, char **argv) { + for (unsigned int i=0; i<10; i++) { + LOG("counter : %u", i); + sleep(1); + } + LOG("%s", "finished..."); +} + +#else + +void Java_org_deadc0de_apple2_Apple2Activity_nativeOnCreate(JNIEnv *env, jobject obj) { + LOG("%s", "native onCreate..."); +} + +void Java_org_deadc0de_apple2_Apple2Activity_nativeOnResume(JNIEnv *env, jobject obj) { + LOG("%s", "native onResume..."); +} + +void Java_org_deadc0de_apple2_Apple2Activity_nativeOnPause(JNIEnv *env, jobject obj) { + LOG("%s", "native onPause..."); +} + +#endif diff --git a/Android/jni/run.sh b/Android/jni/run.sh new file mode 100755 index 00000000..3dff6b96 --- /dev/null +++ b/Android/jni/run.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +ndk-build NDK_DEBUG=1 && \ + ant -f ../build.xml debug install && \ + adb shell am start -a android.intent.action.MAIN -n org.deadc0de.apple2/.Apple2Activity diff --git a/Android/local.properties b/Android/local.properties new file mode 100644 index 00000000..abeb5205 --- /dev/null +++ b/Android/local.properties @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/home/asc/Android/Sdk diff --git a/Android/project.properties b/Android/project.properties new file mode 100644 index 00000000..ce39f2d0 --- /dev/null +++ b/Android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-18 diff --git a/Android/res/drawable-hdpi/ic_launcher.png b/Android/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e4363d26c2293eef58f670c76e64be96b57750db GIT binary patch literal 1842 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 OtcXEltt&71QvL%(R{0tL literal 0 HcmV?d00001 diff --git a/Android/res/drawable-xxhdpi/ic_launcher.png b/Android/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..5900a23156be2458a3e841ad4579127502ddd4ab GIT binary patch literal 3944 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 literal 0 HcmV?d00001 diff --git a/Android/res/drawable-xxxhdpi/ic_launcher.png b/Android/res/drawable-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e7efff83886271489ac12c8114e3c20b57a63d54 GIT binary patch literal 5557 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 literal 0 HcmV?d00001 diff --git a/Android/res/layout/main.xml b/Android/res/layout/main.xml new file mode 100644 index 00000000..87705d85 --- /dev/null +++ b/Android/res/layout/main.xml @@ -0,0 +1,18 @@ + + + + + +