diff --git a/MiSTer.vcxproj b/MiSTer.vcxproj index e4f03a2..72fb3de 100644 --- a/MiSTer.vcxproj +++ b/MiSTer.vcxproj @@ -88,6 +88,7 @@ + @@ -167,6 +168,8 @@ + + diff --git a/MiSTer.vcxproj.filters b/MiSTer.vcxproj.filters index 39e1bab..6fcae57 100644 --- a/MiSTer.vcxproj.filters +++ b/MiSTer.vcxproj.filters @@ -172,6 +172,12 @@ Source Files\support + + Source Files\support + + + Source Files\support + Source Files\support diff --git a/menu.cpp b/menu.cpp index a748665..d0bf2b0 100644 --- a/menu.cpp +++ b/menu.cpp @@ -203,6 +203,10 @@ enum MENU // MT32-pi MENU_MT32PI_MAIN1, MENU_MT32PI_MAIN2, + + // Atari 8bit cartridge type selection + MENU_ATARI8BIT_CART1, + MENU_ATARI8BIT_CART2, }; static uint32_t menustate = MENU_NONE1; @@ -2177,7 +2181,7 @@ void HandleUI(void) if (is_gba() && FileExists(user_io_make_filepath(HomeDir(), "goomba.rom"))) strcat(ext, "GB GBC"); while (strlen(ext) % 3) strcat(ext, " "); - fs_Options = SCANO_DIR | (is_neogeo() ? SCANO_NEOGEO | SCANO_NOENTER : 0) | (store_name ? SCANO_CLEAR : 0); + fs_Options = SCANO_DIR | (is_neogeo() ? SCANO_NEOGEO | SCANO_NOENTER : 0) | (store_name ? SCANO_CLEAR : 0) | (is_atari5200() || (is_atari800() && (ioctl_index == 8 || ioctl_index == 9)) ? SCANO_UMOUNT : 0); fs_MenuSelect = MENU_GENERIC_FILE_SELECTED; fs_MenuCancel = MENU_GENERIC_MAIN1; strcpy(fs_pFileExt, ext); @@ -2350,6 +2354,8 @@ void HandleUI(void) if (is_saturn() && !bit) saturn_reset(); if (is_n64() && !bit) n64_reset(); if (is_psx() && !bit) psx_reset(); + if (is_atari800() && !bit) atari800_reset(); + if (is_atari5200() && !bit) atari5200_reset(); user_io_status_set(opt, 1, ex); user_io_status_set(opt, 0, ex); @@ -2404,7 +2410,7 @@ void HandleUI(void) } } - MenuHide(); + if(!(selPath[0] && (is_atari5200() || (is_atari800() && (ioctl_index == 8 || ioctl_index == 9))))) MenuHide(); printf("File selected: %s\n", selPath); memcpy(Selected_F[ioctl_index & 15], selPath, sizeof(Selected_F[ioctl_index & 15])); @@ -2446,6 +2452,38 @@ void HandleUI(void) { c64_open_file(selPath, idx); } + else if (is_atari800() || is_atari5200()) + { + if(is_atari800() && ioctl_index != 8 && ioctl_index != 9) + { + atari800_open_bios_file(selPath, idx); + } + else + { + int a8bit_cart_matches = is_atari5200() ? atari5200_check_cartridge_file(selPath, idx): atari800_check_cartridge_file(selPath, idx); + if(a8bit_cart_matches <= 1) MenuHide(); + if(a8bit_cart_matches == 1) + { + if(is_atari5200()) + { + atari5200_open_cartridge_file(selPath, 0); + } + else + { + atari800_open_cartridge_file(selPath, 0); + } + } + else if(a8bit_cart_matches > 1 && mgl->done) + { + menustate = MENU_ATARI8BIT_CART1; + menusub = 0; + } + else if(mgl->done) + { + Info("Unsupported cartridge type!", 2000); + } + } + } else { user_io_file_tx(selPath, idx, opensave, 0, 0, load_addr); @@ -2455,6 +2493,14 @@ void HandleUI(void) if (addon[0] == 'f' && addon[1] == '1') process_addon(addon, idx); } + else if(is_atari800() && (ioctl_index == 8 || ioctl_index == 9)) + { + atari800_umount_cartridge(ioctl_index == 9); + } + else if(is_atari5200()) + { + atari5200_umount_cartridge(); + } mgl->state = 3; } @@ -2530,6 +2576,10 @@ void HandleUI(void) neocd_set_en(1); neocd_set_image(selPath); } + else if (is_atari800()) + { + atari800_set_image(user_io_ext_idx(selPath, fs_pFileExt), ioctl_index, selPath); + } else { user_io_set_index(user_io_ext_idx(selPath, fs_pFileExt) << 6 | (menusub + 1)); @@ -6984,6 +7034,61 @@ void HandleUI(void) SelectFile("", 0, SCANO_CORES, MENU_CORE_FILE_SELECTED1, cp_MenuCancel); break; + case MENU_ATARI8BIT_CART1: + helptext_idx = 0; + menumask = 0; + OsdSetSize(16); + OsdSetTitle("Cartridge Type"); + menustate = MENU_ATARI8BIT_CART2; + parentstate = MENU_ATARI8BIT_CART1; + + { + int entry = 0; + uint32_t selentry = 0; + int a8bit_match_count = is_atari5200() ? atari5200_get_match_cart_count() : atari800_get_match_cart_count(); + for (int i = 0; i < a8bit_match_count; i++) + { + s[0] = ' '; + strcpy(s + 1, is_atari5200() ? atari5200_get_cart_match_name(i) : atari800_get_cart_match_name(i)); + OsdWrite(entry, s, menusub == selentry); + menumask = (menumask << 1) | 1; + entry++; + selentry++; + } + + while (entry < OsdGetSize() - 1) OsdWrite(entry++); + + OsdWrite(entry, " Cancel", menusub == selentry); + menusub_last = selentry; + menumask = (menumask << 1) | 1; + } + break; + + case MENU_ATARI8BIT_CART2: + if (menu || left) + { + menustate = MENU_GENERIC_MAIN1; + menusub = ioctl_index - 2; + } + else if (select) + { + menustate = MENU_NONE1; + if (menusub != menusub_last) + { + int match_index = menusub; + HandleUI(); // What MenuHide() would do... + if(is_atari5200()) + { + atari5200_open_cartridge_file(selPath, match_index); + } + else + { + atari800_open_cartridge_file(selPath, match_index); + } + } + } + break; + /******************************************************************/ /* we should never come here */ /******************************************************************/ diff --git a/support.h b/support.h index abfc2c6..e59bd87 100644 --- a/support.h +++ b/support.h @@ -40,6 +40,10 @@ // C64 support #include "support/c64/c64.h" +// Atari 8bit support +#include "support/atari8bit/atari800.h" +#include "support/atari8bit/atari5200.h" + // PCECD support #include "support/pcecd/pcecd.h" diff --git a/support/atari8bit/asm/Makefile b/support/atari8bit/asm/Makefile new file mode 100644 index 0000000..93fb6e9 --- /dev/null +++ b/support/atari8bit/asm/Makefile @@ -0,0 +1,15 @@ +# This produces the supporting binary data for the Atari800 core, +# the internal XEX loader and the PBI rom file boot3.rom. The +# latter should be placed in games/ATARI800 folder of your MiSTer +# SD card. + +all: ../xex_loader.h boot3.rom + +../xex_loader.h: xex_loader.asm + xa xex_loader.asm -o xex_loader.o65 -l xex_loader.lab + python3 process_xex_loader.py + rm xex_loader.o65 xex_loader.lab + mv xex_loader.h .. + +boot3.rom: pbi_bios.asm + xa -M -o boot3.rom pbi_bios.asm \ No newline at end of file diff --git a/support/atari8bit/asm/boot3.rom b/support/atari8bit/asm/boot3.rom new file mode 100644 index 0000000..09857d3 Binary files /dev/null and b/support/atari8bit/asm/boot3.rom differ diff --git a/support/atari8bit/asm/hsio_pbi.xex b/support/atari8bit/asm/hsio_pbi.xex new file mode 100644 index 0000000..6745a4b Binary files /dev/null and b/support/atari8bit/asm/hsio_pbi.xex differ diff --git a/support/atari8bit/asm/pbi_bios.asm b/support/atari8bit/asm/pbi_bios.asm new file mode 100644 index 0000000..d4d2c85 --- /dev/null +++ b/support/atari8bit/asm/pbi_bios.asm @@ -0,0 +1,206 @@ +; Links: +; http://atariki.krap.pl/index.php/ROM_PBI + +#define VER_MAJOR 0 +#define VER_MINOR 9 + +warmst = $008 +pdvmsk = $247 +pdvrs = $248 +ddevic = $300 +dunit = $301 +dstats = $303 +dtimlo = $306 +cdtma1 = $226 +setvbv = $e45c + +; PBI BIOS RAM +pbi_magic = $d100 ; word +pbi_splash_flag = $d102 +pbi_req_init_flag = $d103 +pbi_req_proc_res = $d104 +pbi_req_proc_flag = $d105 +pbi_stack_save = $d106 +pbi_drive_boot_act = $d10a +pbi_drive_boot = $d10b +pbi_drive_conf = $d10c +; HSIO RAM variables sit upwards of $d1f0 incl. + + * = $D800 +bios1_start + .byte 'M', 'S', 'T' + ; Magic 1 + .byte $80 + .byte $31 ; ddevic we are servicing +pdior_vec + jmp pdior +pdint_vec + rts : nop : nop + ; Magic 2 + .byte $91 + .byte $00 ; no CIO + .word pdint_vec + .word pdint_vec + .word pdint_vec + .word pdint_vec + .word pdint_vec + .word pdint_vec + +pdinit + lda pdvmsk : ora pdvrs : sta pdvmsk + lda #0 : ldx #$fe : sta $d100-1,x : dex : bne *-4 + ; Marker for the core firmware + lda #$a5 : sta pbi_magic : sta pbi_magic+1 + ; ask for init + inc pbi_req_init_flag : lda pbi_req_init_flag : bne *-3 + lda warmst : bmi pdinit_1 + lda pbi_drive_boot_act : beq pdinit_1 + sta dunit +pdinit_1 + ; Do we want the splash? + lda pbi_splash_flag : beq pdinit_ret + jsr boot_screen_init + lda #$0c : sta $2c5 ; color 1 + lda #$e0 : sta $d409 ; chbase + lda #display_list : sta $d403 ; display list + lda $14 : cmp $14 : beq *-2 : ldy #$22 : sty $d400 ; dmactl + clc : adc #100 : cmp $14 : bne *-2 + lda #0 : sta $d400 +pdinit_ret + rts + +display_list + .byte $70, $70, $70 + .byte $42 : .word display_text1 + .byte $10 + .byte $42 : .word display_text2 + .byte $70 + .byte $42 : .word display_text3 + .byte $30 + .byte $02 + .byte $41 : .word display_list + +display_text1 + .byte 0,0 + .byte 'A'-$20,'tari','8'-$20,'0'-$20,'0'-$20,0,'M'-$20,'i','S'-$20,'T'-$20,'er',0,'core',0 + .byte 'P'-$20,'B'-$20,'I'-$20,0,'B'-$20,'I'-$20,'O'-$20,'S'-$20,0 + .byte 'v',VER_MAJOR+$10,'.'-$20,VER_MINOR+$10 +display_text1_len = *-display_text1 + .dsb 40-display_text1_len,0 +display_text2 + .byte 0,0 + .byte '('-$20,'C'-$20,')'-$20,0,'2'-$20,'0'-$20,'2'-$20,'6'-$20,0,'woj','@'-$20,'A'-$20,'tari','A'-$20,'ge' +display_text2_len = *-display_text2 + .dsb 40-display_text2_len,0 +display_text3 = $d110 + +drive_labels_1 .byte 'O'-$20, 'P'-$20, 'H'-$20 +drive_labels_2 .byte 'f', 'B'-$20, 'S'-$20 +drive_labels_3 .byte 'f', 'I'-$20, 'I'-$20 +drive_labels_4 .byte 0, 0, 'O'-$20 + +boot_label_1 .byte 'B'-$20,'o','o','t',0,'D'-$20,'r','i','v','e',':'-$20 +boot_label_1_len = *-boot_label_1 +boot_label_2 .byte 'D'-$20,'e','f','a','u','l','t' +boot_label_2_len = *-boot_label_2 +boot_label_3 .byte 'A'-$20,'P'-$20,'T'-$20 +boot_label_3_len = *-boot_label_3 + +boot_screen_init + ldy #1 +boot_screen_init_loop + lda #'D'-$20 : sta display_text3+2,x : inx + tya : pha : ora #$10 : sta display_text3+2,x : inx + lda #':'-$20 : sta display_text3+2,x : inx : inx + lda pbi_drive_conf-1,y : tay + lda drive_labels_1,y : sta display_text3+2,x : inx + lda drive_labels_2,y : sta display_text3+2,x : inx + lda drive_labels_3,y : sta display_text3+2,x : inx + lda drive_labels_4,y : sta display_text3+2,x : inx : inx + pla : tay : iny : cpy #5 : bne boot_screen_init_loop + ldy #boot_label_1_len +boot_screen_init_loop_2 + lda boot_label_1-1,y : sta display_text3+41,y + dey : bne boot_screen_init_loop_2 + ldx pbi_drive_boot : dex + bmi boot_drv_def + beq boot_drv_apt + lda #'D'-$20 : sta display_text3+42+boot_label_1_len+1 + txa : ora #$10 : sta display_text3+42+boot_label_1_len+2 + lda #':'-$20 : sta display_text3+42+boot_label_1_len+3 + bne boot_screen_init_ret +boot_drv_def + ldy #boot_label_2_len +boot_drv_def_loop + lda boot_label_2-1,y : sta display_text3+42+boot_label_1_len,y + dey : bne boot_drv_def_loop + beq boot_screen_init_ret +boot_drv_apt + ldy #boot_label_3_len +boot_drv_apt_loop + lda boot_label_3-1,y : sta display_text3+42+boot_label_1_len,y + dey : bne boot_drv_apt_loop +boot_screen_init_ret + rts + +; The main block I/O routine +pdior + lda ddevic : and #$7F : cmp #$31 : beq pdior_2 + cmp #$20 : bne pdior_bail +pdior_2 + lda dunit : beq pdior_bail + cmp #$10 : bcs pdior_bail + tsx : stx pbi_stack_save + lda dtimlo : ror : ror : tay : and #$3f : tax : tya : ror : and #$c0 : tay : lda #1 + jsr setvbv + lda #pbi_time_out : sta cdtma1+1 + inc pbi_req_proc_flag : lda pbi_req_proc_flag : bne *-3 + ldx #0 : ldy #0 : lda #1 : jsr setvbv + lda pbi_req_proc_res : bmi pdior_bail ; the FW says either no PBI service or ATX (plain SIO) + beq pdior_pbi_ok ; the drive was in PBI mode and got serviced + ; otherwise call HSIO + jsr $dc00 : sec : rts +pdior_pbi_ok + ldy dstats : sec : rts +pdior_bail + ; We are not servicing this block I/O request + clc : rts +pbi_time_out + lda #0 : sta pbi_req_proc_flag : ldx pbi_stack_save : txs + lda #$8a : sta dstats : bne pdior_pbi_ok +bios1_end + +.dsb ($400-bios1_end+bios1_start),$ff + +hsio_start ; This should be $dc00 +.bin 6,0,"hsio_pbi.xex" +hsio_end + +.dsb ($3AD-hsio_end+hsio_start),$ff + +.byte VER_MAJOR*16+VER_MINOR ; Version byte + +.byte (device_name_end-device_name-1) +device_name +.byte "SDHC MiSTer SDEMU v.",VER_MAJOR+$30,'.',VER_MINOR+$30,$9B +device_name_end + +.dsb (40-device_name_end+device_name),$ff + +; .dsb ($3D7-hsio_end+hsio_start),$ff + +.byte (bios_name_end-bios_name-1) +bios_name +.byte "MiSTer core PBI BIOS v.",VER_MAJOR+$30,'.',VER_MINOR+$30,$9B +bios_name_end + +.dsb (40-bios_name_end+bios_name),$ff + + * = $D800 +.dsb $800,$FF + + * = $D800 +.dsb $800,$FF + + * = $D800 +.dsb $800,$FF \ No newline at end of file diff --git a/support/atari8bit/asm/process_xex_loader.py b/support/atari8bit/asm/process_xex_loader.py new file mode 100644 index 0000000..26fea3d --- /dev/null +++ b/support/atari8bit/asm/process_xex_loader.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 + +f = open("xex_loader.o65", "rb") +loader = f.read() +f.close() + +s = "const static uint8_t xex_loader[] =\n{\n\t" + +i = 0 +for b in loader: + s += f"0x{b:02X}," + i += 1 + if i == len(loader): + s = s[:-1] + if i % 16 == 0: + s += "\n\t" + +if i % 16 != 0: + s += "\n" +else: + s = s[:-1] + +s += "};\n\n" + +f = open("xex_loader.lab", "rt") +l = f.read().split("\n") +f.close() + +for ll in l: + if ll[:11] == "read_status": + read_status = ll[17:19] + elif ll[:5] == "init1": + init1 = ll[11:13] + +s += f"#define XEX_READ_STATUS 0x{read_status}\n" +s += f"#define XEX_INIT1 0x{init1}\n\n" + +f = open("xex_loader.h", "wt") +f.write(s) +f.close() diff --git a/support/atari8bit/asm/xex_loader.asm b/support/atari8bit/asm/xex_loader.asm new file mode 100644 index 0000000..cec69ca --- /dev/null +++ b/support/atari8bit/asm/xex_loader.asm @@ -0,0 +1,16 @@ + * = $D100 +magic + .byte $61 +init1 + ldx #0 : stx $09 : dex : txs + dec magic +load_next_block + dec read_status + lda #1 : read_status = *-1 : beq *-2 + bmi init_go + lda #>(load_next_block-1) : pha + lda #<(load_next_block-1) : pha + jmp ($2e2) +init_go + dec magic + jmp ($2e0) diff --git a/support/atari8bit/atari5200.cpp b/support/atari8bit/atari5200.cpp new file mode 100644 index 0000000..e607c4b --- /dev/null +++ b/support/atari8bit/atari5200.cpp @@ -0,0 +1,284 @@ +#include +#include +#include + +#include "../../hardware.h" +#include "../../menu.h" +#include "../../file_io.h" +#include "../../debug.h" +#include "../../user_io.h" +#include "../../fpga_io.h" + +#include "atari5200.h" +#include "atari8bit_defs.h" + +extern uint8_t a8bit_buffer[]; + +void set_a8bit_reg(uint8_t reg, uint8_t val); +uint16_t get_a8bit_reg(uint8_t reg); +void atari8bit_dma_write(const uint8_t *buf, uint32_t addr, uint32_t len); +void atari8bit_dma_zero(uint32_t addr, uint32_t len); + +static void reboot() +{ + set_a8bit_reg(REG_PAUSE, 1); + + // Initialize the first 16K of Atari RAM with a pattern + for(int i = 0; i < BUFFER_SIZE; i += 2) + { + a8bit_buffer[i] = 0xFF; + a8bit_buffer[i+1] = 0x00; + } + user_io_set_index(99); + user_io_set_download(1, ATARI_BASE); + for(int i = 0; i < 0x4000 / BUFFER_SIZE; i++) user_io_file_tx_data(a8bit_buffer, BUFFER_SIZE); + user_io_set_upload(0); + set_a8bit_reg(REG_RESET, 1); + set_a8bit_reg(REG_RESET, 0); + + set_a8bit_reg(REG_PAUSE, 0); +} + +static int cart_matches_total; +static uint8_t cart_matches_type[2]; +static uint8_t cart_match_car; +static unsigned char cart_io_index; + +int atari5200_get_match_cart_count() +{ + return cart_matches_total; +} + +static const char one_chip[16] = "One chip"; +static const char two_chip[16] = "Two chip"; + +const char *atari5200_get_cart_match_name(int match_index) +{ + return match_index == 1 ? two_chip : one_chip; +} + +void atari5200_umount_cartridge() +{ + set_a8bit_reg(REG_CART1_SELECT, 0); + reboot(); +} + +int atari5200_check_cartridge_file(const char* name, unsigned char index) +{ + fileTYPE f = {}; + + int file_size; + cart_match_car = 0; + cart_matches_total = 0; + cart_io_index = index; + + int ext_index = index >> 6; // CAR is index 0 + + if (FileOpen(&f, name)) + { + file_size = f.size; + if (!ext_index) + { + cart_match_car = 1; + FileReadAdv(&f, a8bit_buffer, 16); + } + FileClose(&f); + + if(cart_match_car) + { + // CAR file, presumably, let's check further + if (a8bit_buffer[0] == 'C' && a8bit_buffer[1] == 'A' && a8bit_buffer[2] == 'R' && a8bit_buffer[3] == 'T') + { + switch(a8bit_buffer[7]) + { + case 0x04: + case 0x06: + case 0x07: + case 0x10: + case 0x13: + case 0x14: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + cart_matches_type[0] = a8bit_buffer[7]; + cart_matches_total = 1; + break; + default: + break; + } + } + } + else + { + uint8_t type = 0xFF; + if (file_size == 0x10000) type = 0x47; + else if (file_size == 0x20000) type = 0x48; + else if (file_size == 0x40000) type = 0x49; + else if (file_size == 0x80000) type = 0x4A; + else if (file_size == 40960) type = 0x07; + else if (file_size == 0x8000) type = 0x04; + else if (file_size == 0x2000) type = 0x13; + else if (file_size == 0x1000) type = 0x14; + else if (file_size == 0x4000) + { + cart_matches_type[0] = 0x10; + cart_matches_type[1] = 0x06; + cart_matches_total = 2; + } + + if(cart_matches_total == 0 && type != 0xFF) + { + cart_matches_total = 1; + cart_matches_type[0] = type; + } + } + } + + return cart_matches_total; +} + +void atari5200_open_cartridge_file(const char* name, int match_index) +{ + fileTYPE f = {}; + int offset = cart_match_car ? 16 : 0; + uint8_t cart_type = cart_matches_type[match_index]; + + if (FileOpen(&f, name)) + { + set_a8bit_reg(REG_PAUSE, 1); + set_a8bit_reg(REG_CART1_SELECT, 0); + + ProgressMessage(0, 0, 0, 0); + FileSeek(&f, offset, SEEK_SET); + + user_io_set_index(cart_io_index); + user_io_set_download(1, SDRAM_BASE + 0x4000); + + if(cart_type == 0x06) + { + for(int i = 0; i < 4; i++) + { + ProgressMessage("Loading", f.name, i * 0x2000, 0x8000); + if(i % 2 == 0) FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + } + } + else if(cart_type == 0x10) + { + for(int i = 0; i < 4; i++) + { + ProgressMessage("Loading", f.name, i * 0x2000, 0x8000); + if(i % 2 == 0) FileSeek(&f, offset, SEEK_SET); + FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + } + } + else if(cart_type == 0x13 || cart_type == 0x14) + { + int block_size = f.size - offset; + FileReadAdv(&f, a8bit_buffer, block_size); + for(int i = 0; i < 0x8000 / block_size; i++) + { + ProgressMessage("Loading", f.name, i * block_size, 0x8000); + user_io_file_tx_data(a8bit_buffer, block_size); + } + } + else if(cart_type >= 0x47 && cart_type <= 0x4A) + { + int block_size = f.size - offset; + for(int i = 0; i < 0x80000 / block_size; i++) + { + FileSeek(&f, offset, SEEK_SET); + for(int j = 0; j < block_size / 0x2000; j++) + { + ProgressMessage("Loading", f.name, i * block_size + j * 0x2000, 0x80000); + FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + } + } + } + else if(cart_type == 0x07) + { + FileSeek(&f, 0, SEEK_SET); + FileReadAdv(&f, a8bit_buffer, 512); + uint8_t bb_type1 = (a8bit_buffer[offset] == 0x2F); + + FileSeek(&f, offset + (bb_type1 ? 0 : 0x2000), SEEK_SET); + + ProgressMessage("Loading", f.name, 0, 0xC000); + FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + + ProgressMessage("Loading", f.name, 0x2000, 0xC000); + FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + + FileSeek(&f, offset + (bb_type1 ? 0x8000 : 0), SEEK_SET); + + ProgressMessage("Loading", f.name, 0x4000, 0xC000); + FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + + ProgressMessage("Loading", f.name, 0x6000, 0xC000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + + FileSeek(&f, offset + (bb_type1 ? 0x4000 : 0x6000), SEEK_SET); + + ProgressMessage("Loading", f.name, 0x8000, 0xC000); + FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + + ProgressMessage("Loading", f.name, 0xA000, 0xC000); + FileReadAdv(&f, a8bit_buffer, 0x2000); + user_io_file_tx_data(a8bit_buffer, 0x2000); + } + else if(cart_type == 0x04) + { + while (offset < f.size) + { + int to_read = f.size - offset; + if (to_read > BUFFER_SIZE) to_read = BUFFER_SIZE; + ProgressMessage("Loading", f.name, offset, f.size); + FileReadAdv(&f, a8bit_buffer, to_read); + user_io_file_tx_data(a8bit_buffer, to_read); + offset += to_read; + } + } + FileClose(&f); + user_io_set_download(0); + ProgressMessage(0, 0, 0, 0); + set_a8bit_reg(REG_CART1_SELECT, cart_type); + reboot(); + } +} + +void atari5200_poll() +{ + uint16_t atari_status1 = get_a8bit_reg(REG_ATARI_STATUS1); + + set_a8bit_reg(REG_PAUSE, atari_status1 & STATUS1_MASK_HALT); + if (atari_status1 & STATUS1_MASK_COLDBOOT) reboot(); +} + +void atari5200_init() +{ + set_a8bit_reg(REG_PAUSE, 1); + cart_matches_total = 0; + cart_match_car = 0; + + static char mainpath[512]; + const char *home = HomeDir(); + + sprintf(mainpath, "%s/boot0.rom", home); + user_io_file_tx(mainpath, 0); + atari5200_reset(); +} + +void atari5200_reset() +{ + set_a8bit_reg(REG_PAUSE, 1); + set_a8bit_reg(REG_CART1_SELECT, 0); + atari8bit_dma_zero(SDRAM_BASE + 0x4000, 0x80000); + reboot(); +} diff --git a/support/atari8bit/atari5200.h b/support/atari8bit/atari5200.h new file mode 100644 index 0000000..e8072c4 --- /dev/null +++ b/support/atari8bit/atari5200.h @@ -0,0 +1,14 @@ +#ifndef __ATARI5200_H__ +#define __ATARI5200_H__ + +int atari5200_get_match_cart_count(); +const char *atari5200_get_cart_match_name(int match_index); + +void atari5200_init(); +void atari5200_reset(); +void atari5200_poll(); +void atari5200_umount_cartridge(); +int atari5200_check_cartridge_file(const char* name, unsigned char index); +void atari5200_open_cartridge_file(const char* name, int match_index); + +#endif diff --git a/support/atari8bit/atari800.cpp b/support/atari8bit/atari800.cpp new file mode 100644 index 0000000..3b19901 --- /dev/null +++ b/support/atari8bit/atari800.cpp @@ -0,0 +1,2190 @@ +#include +#include +#include +#include + +#include "../../hardware.h" +#include "../../menu.h" +#include "../../file_io.h" +#include "../../debug.h" +#include "../../user_io.h" +#include "../../fpga_io.h" +#include "../../scheduler.h" +#include "../../input.h" + +#include "atari800.h" +#include "atari8bit_defs.h" + +#define ATARI_COLDST (ATARI_BASE + 0x244) +#define ATARI_BASICF (ATARI_BASE + 0x3F8) +#define ATARI_GINTLK (ATARI_BASE + 0x3FA) +#define ATARI_PUPBT (ATARI_BASE + 0x33D) +#define ATARI_BOOTFLAG (ATARI_BASE + 0x09) +#define ATARI_CASINI (ATARI_BASE + 0x02) +#define ATARI_DOSVEC (ATARI_BASE + 0x0A) +#define ATARI_RUNAD (ATARI_BASE + 0x2E0) +#define ATARI_INITAD (ATARI_BASE + 0x2E2) + +typedef struct { + uint8_t cart_type; // type from CAR header + char name[16]; // name of type + uint8_t cart_mode; // mode used in cartridge emulation + int size; // image size in k +} cart_def_t; + +// Cart modes from the original ZCPU firmware + +// 8k modes (0xA000-$BFFF) +#define TC_MODE_OFF 0x00 // cart disabled +#define TC_MODE_8K 0x01 // 8k banks at $A000 +#define TC_MODE_ATARIMAX1 0x02 // 8k using Atarimax 1MBit compatible banking +#define TC_MODE_ATARIMAX8 0x03 // 8k using Atarimax 8MBit compatible banking +#define TC_MODE_ATARIMAX8_2 0x10 // 8k using Atarimax 8MBit compatible banking (new type) +#define TC_MODE_DCART 0x11 // 512K DCart +#define TC_MODE_OSS_16 0x04 // 16k OSS cart, M091 banking +#define TC_MODE_OSS_8 0x05 // 8k OSS cart, M091 banking +#define TC_MODE_OSS_043M 0x06 // 16k OSS cart, 043M banking + +#define TC_MODE_SDX64 0x08 // SDX 64k cart, $D5Ex banking +#define TC_MODE_SDX128 0x09 // SDX 128k cart, $D5Ex banking +#define TC_MODE_DIAMOND64 0x0A // Diamond GOS 64k cart, $D5Dx banking +#define TC_MODE_EXPRESS64 0x0B // Express 64k cart, $D57x banking + +#define TC_MODE_ATRAX128 0x0C // Atrax 128k cart +#define TC_MODE_WILLIAMS64 0x0D // Williams 64k cart +#define TC_MODE_WILLIAMS32 0x0E // Williams 32k cart +#define TC_MODE_WILLIAMS16 0x0F // Williams 16k cart + +// 16k modes (0x8000-$BFFF) +//#define TC_MODE_FLEXI 0x20 // flexi mode +#define TC_MODE_16K 0x21 // 16k banks at $8000-$BFFF +#define TC_MODE_MEGAMAX16 0x22 // MegaMax 16k mode (up to 2MB) +#define TC_MODE_BLIZZARD 0x23 // Blizzard 16k +#define TC_MODE_SIC_128 0x24 // Sic!Cart 128k +#define TC_MODE_SIC_256 0x25 // Sic!Cart 256k +#define TC_MODE_SIC_512 0x26 // Sic!Cart 512k +#define TC_MODE_SIC_1024 0x27 // Sic!Cart+ 1024k + +#define TC_MODE_BLIZZARD_4 0x12 // Blizzard 4k +#define TC_MODE_BLIZZARD_32 0x13 // Blizzard 32k +#define TC_MODE_RIGHT_8K 0x14 +#define TC_MODE_RIGHT_4K 0x15 +#define TC_MODE_2K 0x16 +#define TC_MODE_4K 0x17 + +// J(atari)Cart versions +#define TC_MODE_JATARI_8 0x18 +#define TC_MODE_JATARI_16 0x19 +#define TC_MODE_JATARI_32 0x1A +#define TC_MODE_JATARI_64 0x1B +#define TC_MODE_JATARI_128 0x1C +#define TC_MODE_JATARI_256 0x1D +#define TC_MODE_JATARI_512 0x1E +#define TC_MODE_JATARI_1024 0x1F + +#define TC_MODE_MEGA_16 0x28 // switchable MegaCarts +#define TC_MODE_MEGA_32 0x29 +#define TC_MODE_MEGA_64 0x2A +#define TC_MODE_MEGA_128 0x2B +#define TC_MODE_MEGA_256 0x2C +#define TC_MODE_MEGA_512 0x2D +#define TC_MODE_MEGA_1024 0x2E +#define TC_MODE_MEGA_2048 0x2F +#define TC_MODE_MEGA_4096 0x20 + +#define TC_MODE_XEGS_32 0x30 // non-switchable XEGS carts +#define TC_MODE_XEGS_64 0x31 +#define TC_MODE_XEGS_128 0x32 +#define TC_MODE_XEGS_256 0x33 +#define TC_MODE_XEGS_512 0x34 +#define TC_MODE_XEGS_1024 0x35 +#define TC_MODE_XEGS_64_2 0x36 + +#define TC_MODE_SXEGS_32 0x38 // switchable XEGS carts +#define TC_MODE_SXEGS_64 0x39 +#define TC_MODE_SXEGS_128 0x3A +#define TC_MODE_SXEGS_256 0x3B +#define TC_MODE_SXEGS_512 0x3C +#define TC_MODE_SXEGS_1024 0x3D + +// XE Multicart versions +#define TC_MODE_XEMULTI_8 0x68 +#define TC_MODE_XEMULTI_16 0x69 +#define TC_MODE_XEMULTI_32 0x6A +#define TC_MODE_XEMULTI_64 0x6B +#define TC_MODE_XEMULTI_128 0x6C +#define TC_MODE_XEMULTI_256 0x6D +#define TC_MODE_XEMULTI_512 0x6E +#define TC_MODE_XEMULTI_1024 0x6F + +#define TC_MODE_PHOENIX 0x40 +#define TC_MODE_AST_32 0x41 +#define TC_MODE_ATRAX_INT128 0x42 +#define TC_MODE_ATRAX_SDX64 0x43 +#define TC_MODE_ATRAX_SDX128 0x44 +#define TC_MODE_TSOFT_64 0x45 +#define TC_MODE_TSOFT_128 0x46 +#define TC_MODE_ULTRA_32 0x47 +#define TC_MODE_DAWLI_32 0x48 +#define TC_MODE_DAWLI_64 0x49 +#define TC_MODE_JRC_LIN_64 0x4A +#define TC_MODE_JRC_INT_64 0x4B +#define TC_MODE_SDX_SIDE2 0x4C +#define TC_MODE_SDX_U1MB 0x4D +#define TC_MODE_DB_32 0x70 +#define TC_MODE_BOUNTY_40 0x73 + +static const cart_def_t cart_def[] = +{ + { 1, "Standard ", TC_MODE_8K, 8 }, + { 2, "Standard ", TC_MODE_16K, 16 }, + // This below is intentional, for 034M carts we fix them + // (we also need to add 2 extra fake AND-ed banks for + // both 043M and 034M) + { 3, "OSS 2 Chip 034M", TC_MODE_OSS_043M, 16 }, + { 5, "DB ", TC_MODE_DB_32, 32 }, + { 8, "Williams ", TC_MODE_WILLIAMS64, 64 }, + { 9, "Express ", TC_MODE_EXPRESS64, 64 }, + { 10, "Diamond ", TC_MODE_DIAMOND64, 64 }, + { 11, "Sparta DOS X ", TC_MODE_SDX64, 64 }, + { 12, "XEGS ", TC_MODE_XEGS_32, 32 }, + { 13, "XEGS Bank 0-7 ", TC_MODE_XEGS_64, 64 }, + { 14, "XEGS ", TC_MODE_XEGS_128, 128 }, + { 15, "OSS 1 Chip ", TC_MODE_OSS_16, 16 }, + { 17, "Atrax Decoded ", TC_MODE_ATRAX128, 128 }, + { 18, "Bounty Bob ", TC_MODE_BOUNTY_40, 40 }, + { 21, "Right ", TC_MODE_RIGHT_8K, 8 }, + { 22, "Williams ", TC_MODE_WILLIAMS32, 32 }, + { 23, "XEGS ", TC_MODE_XEGS_256, 256 }, + { 24, "XEGS ", TC_MODE_XEGS_512, 512 }, + { 25, "XEGS ", TC_MODE_XEGS_1024, 1024 }, + { 26, "MegaCart ", TC_MODE_MEGA_16, 16 }, + { 27, "MegaCart ", TC_MODE_MEGA_32, 32 }, + { 28, "MegaCart ", TC_MODE_MEGA_64, 64 }, + { 29, "MegaCart ", TC_MODE_MEGA_128, 128 }, + { 30, "MegaCart ", TC_MODE_MEGA_256, 256 }, + { 31, "MegaCart ", TC_MODE_MEGA_512, 512 }, + { 32, "MegaCart ", TC_MODE_MEGA_1024, 1024 }, + { 33, "Super XEGS ", TC_MODE_SXEGS_32, 32 }, + { 34, "Super XEGS ", TC_MODE_SXEGS_64, 64 }, + { 35, "Super XEGS ", TC_MODE_SXEGS_128, 128 }, + { 36, "Super XEGS ", TC_MODE_SXEGS_256, 256 }, + { 37, "Super XEGS ", TC_MODE_SXEGS_512, 512 }, + { 38, "Super XEGS ", TC_MODE_SXEGS_1024, 1024 }, + { 39, "Phoenix ", TC_MODE_PHOENIX, 8 }, + { 40, "Blizzard ", TC_MODE_BLIZZARD, 16 }, + { 41, "Atarimax ", TC_MODE_ATARIMAX1, 128 }, + { 42, "Atarimax Old ", TC_MODE_ATARIMAX8, 1024 }, + { 43, "Sparta DOS X ", TC_MODE_SDX128, 128 }, + { 44, "OSS 1 Chip ", TC_MODE_OSS_8, 8 }, + { 45, "OSS 2 Chip 043M", TC_MODE_OSS_043M, 16 }, + { 46, "Blizzard ", TC_MODE_BLIZZARD_4, 4 }, + { 47, "AST ", TC_MODE_AST_32, 32 }, + { 48, "Atrax SDX ", TC_MODE_ATRAX_SDX64, 64 }, + { 49, "Atrax SDX ", TC_MODE_ATRAX_SDX128, 128 }, + { 50, "TurboSoft ", TC_MODE_TSOFT_64, 64 }, + { 51, "TurboSoft ", TC_MODE_TSOFT_128, 128 }, + { 52, "UltraCart ", TC_MODE_ULTRA_32, 32 }, + { 53, "Low Bank XL ", TC_MODE_RIGHT_8K, 8 }, + { 54, "S.I.C. ", TC_MODE_SIC_128, 128 }, + { 55, "S.I.C. ", TC_MODE_SIC_256, 256 }, + { 56, "S.I.C. ", TC_MODE_SIC_512, 512 }, + { 57, "Standard ", TC_MODE_2K, 2 }, + { 58, "Standard ", TC_MODE_4K, 4 }, + { 59, "Right ", TC_MODE_RIGHT_4K, 4 }, + { 60, "Blizzard ", TC_MODE_BLIZZARD_32, 32 }, + { 61, "MegaMax ", TC_MODE_MEGAMAX16, 2048 }, + { 63, "MegaCart ", TC_MODE_MEGA_4096, 4096 }, + { 64, "MegaCart ", TC_MODE_MEGA_2048, 2048 }, + { 67, "XEGS Bank 8-15 ", TC_MODE_XEGS_64_2, 64 }, + { 68, "Atrax Encoded ", TC_MODE_ATRAX_INT128, 128 }, + { 69, "aDawliah ", TC_MODE_DAWLI_32, 32 }, + { 70, "aDawliah ", TC_MODE_DAWLI_64, 64 }, + { 75, "Atarimax New ", TC_MODE_ATARIMAX8_2, 1024 }, + { 76, "Williams ", TC_MODE_WILLIAMS16, 16 }, + { 80, "JRC-Linear ", TC_MODE_JRC_LIN_64, 64 }, + { 83, "S.I.C.+ ", TC_MODE_SIC_1024, 1024 }, + { 86, "XE Multicart ", TC_MODE_XEMULTI_8, 8 }, + { 87, "XE Multicart ", TC_MODE_XEMULTI_16, 16 }, + { 88, "XE Multicart ", TC_MODE_XEMULTI_32, 32 }, + { 89, "XE Multicart ", TC_MODE_XEMULTI_64, 64 }, + { 90, "XE Multicart ", TC_MODE_XEMULTI_128, 128 }, + { 91, "XE Multicart ", TC_MODE_XEMULTI_256, 256 }, + { 92, "XE Multicart ", TC_MODE_XEMULTI_512, 512 }, + { 93, "XE Multicart ", TC_MODE_XEMULTI_1024, 1024 }, + {104, "J(atari)cart ", TC_MODE_JATARI_8, 8 }, + {105, "J(atari)cart ", TC_MODE_JATARI_16, 16 }, + {106, "J(atari)cart ", TC_MODE_JATARI_32, 32 }, + {107, "J(atari)cart ", TC_MODE_JATARI_64, 64 }, + {108, "J(atari)cart ", TC_MODE_JATARI_128, 128 }, + {109, "J(atari)cart ", TC_MODE_JATARI_256, 256 }, + {110, "J(atari)cart ", TC_MODE_JATARI_512, 512 }, + {111, "J(atari)cart ", TC_MODE_JATARI_1024, 1024 }, + {112, "DCART ", TC_MODE_DCART, 512 }, + {160, "JRC-Interleaved", TC_MODE_JRC_INT_64, 64 }, + { 0, "", 0, 0 } +}; + +uint8_t a8bit_buffer[BUFFER_SIZE]; + +#include "xex_loader.h" +#include "boot_xex_loader.h" + +static fileTYPE xex_file = {}; +static uint8_t xex_file_first_block; + +void set_a8bit_reg(uint8_t reg, uint8_t val) +{ + EnableIO(); + spi8(A800_SET_REGISTER); + spi_w((reg << 8) | val); + DisableIO(); +} + +uint16_t get_a8bit_reg(uint8_t reg) +{ + uint16_t r; + EnableIO(); + spi8(A800_GET_REGISTER); + r = spi_w(reg << 8); + DisableIO(); + return r; +} + +static uint16_t get_a800_reg2(uint8_t reg) +{ + uint16_t r; + EnableIO(); + spi8(reg); + r = spi_w(0); + DisableIO(); + return r; +} + +void atari8bit_dma_write(const uint8_t *buf, uint32_t addr, uint32_t len) +{ + user_io_set_index(99); + user_io_set_download(1, addr); + user_io_file_tx_data(buf, len); + user_io_set_download(0); +} + +static void atari800_dma_read(uint8_t *buf, uint32_t addr, uint32_t len) +{ + user_io_set_index(99); + user_io_set_upload(1, addr); + user_io_file_rx_data(buf, len); + user_io_set_upload(0); +} + +void atari8bit_dma_zero(uint32_t addr, uint32_t len) +{ + memset(a8bit_buffer, 0, BUFFER_SIZE); + uint32_t to_write = len > BUFFER_SIZE ? BUFFER_SIZE : len; + + user_io_set_index(99); + user_io_set_download(1, addr); + while(len) + { + user_io_file_tx_data(a8bit_buffer, to_write); + len -= to_write; + to_write = len > BUFFER_SIZE ? BUFFER_SIZE : len; + } + user_io_set_upload(0); +} + +static void reboot(uint8_t cold, uint8_t pause) +{ + int i; + + set_a8bit_reg(REG_PAUSE, 1); + if (cold) + { + set_a8bit_reg(REG_FREEZER, 0); + // Initialize the first 64K of SDRAM with a pattern + for(i = 0; i < BUFFER_SIZE; i += 2) + { + a8bit_buffer[i] = 0xFF; + a8bit_buffer[i+1] = 0x00; + } + user_io_set_index(99); + user_io_set_download(1, SDRAM_BASE); + for(i = 0; i < 0x10000 / BUFFER_SIZE; i++) user_io_file_tx_data(a8bit_buffer, BUFFER_SIZE); + user_io_set_upload(0); + } + else + { + FileClose(&xex_file); + } + + // Both cold==1 and pause==1 is a special case when + // the XEX loader performs a cold/warm boot to push + // in the loader, in this case on the 800 we just want + // the same effect as pressing the RESET (so soft) + // while we actually mean a power cycle with forced + // OS initialization. (In other words, on 800 a power + // cycle does not allow to pre-init the OS to do a warm + // start, it will always be cold). + + set_a8bit_reg(REG_XEX_LOADER, 0); + + if((get_a8bit_reg(REG_ATARI_STATUS1) & STATUS1_MASK_MODE800) && (!cold || pause)) + { + set_a8bit_reg(REG_RESET_RNMI, 1); + set_a8bit_reg(REG_RESET_RNMI, 0); + } + else + { + set_a8bit_reg(REG_RESET, 1); + set_a8bit_reg(REG_RESET, 0); + } + + if(cold) + { + set_a8bit_reg(REG_FREEZER, 1); + } + set_a8bit_reg(REG_PAUSE, pause); +} + +static void uart_init(uint8_t divisor) +{ + set_a8bit_reg(REG_SIO_SETDIV, (divisor << 1) + 1); +} + +static uint8_t uart_full() +{ + return (get_a800_reg2(A800_SIO_TX_STAT) >> 9) & 0x1; +} + +static void uart_send(uint8_t data) +{ +#ifdef USE_SCHEDULER + int counter = 0; +#endif + while(uart_full()) + { + usleep(100); +#ifdef USE_SCHEDULER + counter++; + if(counter > 10) + { + counter = 0; + input_poll(0); + scheduler_yield(); + } +#endif + } + set_a8bit_reg(REG_SIO_TX, data); +} + +static uint8_t uart_available() +{ + return !((get_a800_reg2(A800_SIO_RX_STAT) >> 8) & 0x1); +} + +static uint16_t uart_receive() +{ +#ifdef USE_SCHEDULER + int counter = 0; +#endif + while(!uart_available()) + { + usleep(100); + counter++; +#ifdef USE_SCHEDULER + if(counter > 10) + { + counter = 0; + input_poll(0); + scheduler_yield(); + } +#endif + } + return get_a800_reg2(A800_SIO_RX); +} + +static void uart_switch() +{ + // Working with this for a while now, I still have no clue what it does... :/ + set_a8bit_reg(REG_SIO_SETDIV, (uint8_t)(get_a800_reg2(A800_SIO_GETDIV)-1)); +} + +static uint16_t uart_error() +{ + return get_a800_reg2(A800_SIO_ERROR); +} + +static int cart_matches_total; +static uint8_t cart_matches_mode[16]; // The are 15 supported 64K carts, this is the max ATM +static int cart_matches_idx[16]; +static uint8_t cart_match_car; +static int mounted_cart1_size; +static unsigned char cart_io_index; + +int atari800_get_match_cart_count() +{ + return cart_matches_total; +} + +const char *atari800_get_cart_match_name(int match_index) +{ + return cart_def[cart_matches_idx[match_index]].name; +} + +void atari800_umount_cartridge(uint8_t stacked) +{ + // TODO Clever cart deselect 1 & 2 and reboot? + set_a8bit_reg(stacked ? REG_CART2_SELECT : REG_CART1_SELECT, 0); + if(!stacked) + { + mounted_cart1_size = 0; + } +} + +int atari800_check_cartridge_file(const char* name, unsigned char index) +{ + fileTYPE f = {}; + + int to_read = 0x2000; + int idx = 0; + int file_size; + + cart_match_car = 0; + cart_matches_total = 0; + cart_io_index = index; + + int ext_index = index >> 6; + uint8_t stacked = (index & 0x3F) == 9; + + if (!(stacked && mounted_cart1_size > 0x100000) && FileOpen(&f, name)) + { + file_size = f.size; + if (!ext_index) + { + to_read = 16; + cart_match_car = 1; + } + + if(to_read > f.size) + { + to_read = f.size; + } + FileReadAdv(&f, a8bit_buffer, to_read); + FileClose(&f); + + if(cart_match_car) + { + // CAR file, presumably, let's check further + if (a8bit_buffer[0] == 'C' && a8bit_buffer[1] == 'A' && a8bit_buffer[2] == 'R' && a8bit_buffer[3] == 'T') + { + while (cart_def[idx].cart_type) + { + if(cart_def[idx].cart_type == a8bit_buffer[7]) + { + cart_matches_idx[0] = idx; + cart_matches_mode[0] = cart_def[idx].cart_mode; + cart_matches_total = 1; + break; + } + idx++; + } + } + } + else + { + // First check for Ultimate & SIDE2 SDX cartridges + if(to_read >= 0x2000 && a8bit_buffer[0] == 'S' && a8bit_buffer[1] == 'D' && a8bit_buffer[2] == 'X' && (a8bit_buffer[0x1FF3] == 0xE0 || a8bit_buffer[0x1FF3] == 0xE1)) + { + cart_matches_idx[0] = -1; + cart_matches_mode[0] = (a8bit_buffer[0x1FF3] == 0xE1) ? TC_MODE_SDX_SIDE2 : TC_MODE_SDX_U1MB; + cart_matches_total = 1; + } + else + { + while (cart_def[idx].cart_type) + { + if(cart_def[idx].size == (file_size >> 10)) + { + cart_matches_idx[cart_matches_total] = idx; + cart_matches_mode[cart_matches_total] = cart_def[idx].cart_mode; + cart_matches_total++; + } + idx++; + } + } + } + } + + return cart_matches_total; +} + +void atari800_open_cartridge_file(const char* name, int match_index) +{ + uint8_t stacked = (cart_io_index & 0x3F) == 9; + uint8_t *buf = &a8bit_buffer[0]; + uint8_t *buf2 = &a8bit_buffer[4096]; + fileTYPE f = {}; + int offset = cart_match_car ? 16 : 0; + uint8_t cart_type = cart_def[cart_matches_idx[match_index]].cart_type; + + if (FileOpen(&f, name)) + { + set_a8bit_reg(REG_PAUSE, 1); + set_a8bit_reg(REG_CART2_SELECT, 0); + if(!stacked) set_a8bit_reg(REG_CART1_SELECT, 0); + + ProgressMessage(0, 0, 0, 0); + FileSeek(&f, offset, SEEK_SET); + + user_io_set_index(cart_io_index); + user_io_set_download(1); + + if(cart_type == 3 || cart_type == 45) + { + ProgressMessage("Loading", f.name, 0, 6); + FileReadAdv(&f, buf, 4096); + user_io_file_tx_data(buf, 4096); + + if (cart_type == 3) FileSeek(&f, offset + 8192, SEEK_SET); + ProgressMessage("Loading", f.name, 1, 6); + FileReadAdv(&f, buf, 4096); + user_io_file_tx_data(buf, 4096); + + if (cart_type == 3) FileSeek(&f, offset + 4096, SEEK_SET); + ProgressMessage("Loading", f.name, 2, 6); + FileReadAdv(&f, buf2, 4096); // NOTE different buffer! + user_io_file_tx_data(buf2, 4096); + + if (cart_type == 3) FileSeek(&f, offset + 12288, SEEK_SET); + ProgressMessage("Loading", f.name, 3, 6); + FileReadAdv(&f, buf, 4096); + user_io_file_tx_data(buf, 4096); + + FileSeek(&f, offset, SEEK_SET); + ProgressMessage("Loading", f.name, 4, 6); + FileReadAdv(&f, buf, 4096); + for(int i = 0; i < 4096; i++) buf[i] &= buf2[i]; + user_io_file_tx_data(buf, 4096); + + if (cart_type == 3) FileSeek(&f, offset + 8192, SEEK_SET); + ProgressMessage("Loading", f.name, 5, 6); + FileReadAdv(&f, buf, 4096); + for(int i = 0; i < 4096; i++) buf[i] &= buf2[i]; + user_io_file_tx_data(buf, 4096); + } + else + { + while (offset < f.size) + { + int to_read = f.size - offset; + if (to_read > BUFFER_SIZE) to_read = BUFFER_SIZE; + ProgressMessage("Loading", f.name, offset, f.size); + FileReadAdv(&f, a8bit_buffer, to_read); + user_io_file_tx_data(a8bit_buffer, to_read); + offset += to_read; + } + } + FileClose(&f); + user_io_set_download(0); + ProgressMessage(0, 0, 0, 0); + set_a8bit_reg(stacked ? REG_CART2_SELECT : REG_CART1_SELECT, cart_matches_mode[match_index]); + if(!stacked) + { + mounted_cart1_size = cart_match_car ? f.size - 16 : f.size; + } + + if(!stacked || (get_a8bit_reg(REG_ATARI_STATUS1) & STATUS1_MASK_MODE800)) + { + reboot(1, 0); + } + } +} + +void atari800_open_bios_file(const char* name, unsigned char index) +{ + uint8_t bios_index = (index & 0x3F); + uint16_t mode800 = get_a8bit_reg(REG_ATARI_STATUS1) & STATUS1_MASK_MODE800; + user_io_file_tx(name, index); + if((mode800 && bios_index == 6) || (!mode800 && (bios_index == 4 || bios_index == 5))) reboot(1, 0); +} + +#define MAX_DRIVES 15 + +typedef struct { + fileTYPE file; + uint8_t info; + uint8_t custom_loader; + uint32_t offset; + uint32_t meta_offset; // HDD only + uint16_t partition_id; // HDD only + uint32_t sector_count; + uint16_t sector_size; + uint8_t atari_sector_status; +} drive_info_t; + +static drive_info_t drive_infos[MAX_DRIVES + 1] = {}; + +#define INFO_RO 0x40 +#define INFO_HDD 0x80 +#define INFO_META 0x20 // if the HDD uses the meta information sectors +#define INFO_SS 0x10 // mark that the sector is smaller than the SD card image sector + +typedef struct { + uint16_t wMagic; + uint16_t wPars; + uint16_t wSecSize; + uint8_t btParsHigh; + uint32_t dwCRC; + uint32_t dwUNUSED; + uint8_t btFlags; +} __attribute__((packed)) atr_header_t; + +typedef struct { + uint8_t deviceId; + uint8_t command; + uint8_t aux1; + uint8_t aux2; + uint8_t chksum; + uint16_t auxab; +} __attribute__((packed)) sio_command_t; + +typedef struct { + int bytes; + int success; + int speed; + int respond; + uint8_t *sector_buffer; +} sio_action_t; + +typedef struct { + uint8_t signature[4]; + uint16_t version; + uint16_t minVersion; + uint16_t creator; + uint16_t creatorVersion; + uint32_t flags; + uint16_t imageType; + uint8_t density; + uint8_t reserved0; + uint32_t imageId; + uint16_t imageVersion; + uint16_t reserved1; + uint32_t startData; + uint32_t endData; + uint8_t reserved2[12]; +} __attribute__((packed)) atxFileHeader; + +typedef struct { + uint32_t size; + uint16_t type; + uint16_t reserved0; + uint8_t trackNumber; + uint8_t reserved1; + uint16_t sectorCount; + uint16_t rate; + uint16_t reserved3; + uint32_t flags; + uint32_t headerSize; + uint8_t reserved4[8]; +} __attribute__((packed)) atxTrackHeader; + +typedef struct { + uint32_t next; + uint16_t type; + uint16_t pad0; +} __attribute__((packed)) atxSectorListHeader; + +typedef struct { + uint8_t number; + uint8_t status; + uint16_t timev; + uint32_t data; +} __attribute__((packed)) atxSectorHeader; + +typedef struct { + uint32_t size; + uint8_t type; + uint8_t sectorIndex; + uint16_t data; +} __attribute__((packed)) atxTrackChunk; + +#define ATX_VERSION 0x01 + +// number of angular units in a full disk rotation +#define AU_FULL_ROTATION 26042 + +#define US_CS_CALC_1050 270 // According to Altirra +#define US_CS_CALC_810 5136 // According to Altirra + +#define US_TRACK_STEP_810 5300 // number of microseconds drive takes to step 1 track +#define US_TRACK_STEP_1050 20120 // According to Avery / Altirra +#define US_HEAD_SETTLE_1050 20000 +#define US_HEAD_SETTLE_810 10000 + +#define US_3FAKE_ROT_810 1566000 +#define US_2FAKE_ROT_1050 942000 + +// mask for checking FDC status "data lost" bit +#define MASK_FDC_DLOST 0x04 +// mask for checking FDC status "missing" bit +#define MASK_FDC_MISSING 0x10 +// mask for checking FDC status extended data bit +#define MASK_EXTENDED_DATA 0x40 + +#define MASK_FDC_BUSY 0x01 +#define MASK_FDC_DRQ 0x02 +#define MASK_FDC_CRC 0x08 +#define MASK_FDC_REC 0x20 +#define MASK_FDC_WP 0x40 +#define MASK_RESERVED 0x80 + +#define MAX_RETRIES_1050 2 +#define MAX_RETRIES_810 4 + +#define MAX_TRACK 42 + +enum atx_density { atx_single, atx_medium, atx_double }; + +#define NUM_ATX_DRIVES 4 + +#define XEX_SECTOR_SIZE 128 +#define ATARI_SECTOR_BUFFER_SIZE 512 + +static uint8_t atari_sector_buffer[ATARI_SECTOR_BUFFER_SIZE]; +static uint32_t pre_ce_delay; +static uint32_t pre_an_delay; + +#define DELAY_T2_MIN 100 /* BiboDos needs at least 50us delay before ACK */ +#define DELAY_T5_MIN 600 /* It seems DOS2.0S needs at least 600us delay to function properly */ +#define DELAY_T3_PERIPH 150 /* QMEG OS 3 needs a delay of 150usec between complete and data */ + +struct { + uint16_t bytesPerSector; // number of bytes per sector + uint8_t sectorsPerTrack; // number of sectors in each track + uint32_t trackOffset[MAX_TRACK]; // pre-calculated info for each track and drive + uint8_t currentHeadTrack; + uint8_t density; +} atx_info[NUM_ATX_DRIVES]; + +struct { + uint64_t stamp; + uint16_t angle; +} headPosition; + +static uint64_t get_us(uint64_t offset) +{ + struct timespec tp; + + clock_gettime(CLOCK_BOOTTIME, &tp); + + uint64_t res; + + res = tp.tv_sec; + res *= 1000000; + res += (tp.tv_nsec / 1000); + + return (uint64_t)(res + offset); +} + +static uint8_t check_us(uint64_t time) +{ + long int remaining = time - get_us(0); +#ifdef USE_SCHEDULER + if(remaining >= (long int)10000) + { + input_poll(0); + scheduler_yield(); + } +#endif + return remaining <= 0; +} + +static void wait_us(uint64_t time) +{ + time = get_us(time); + while (!check_us(time)); +} + +static void getCurrentHeadPosition() +{ + uint64_t s = get_us(0); + headPosition.stamp = s; + headPosition.angle = (uint16_t)((s >> 3) % AU_FULL_ROTATION); +} + +static void wait_from_stamp(uint32_t us_delay) +{ + uint32_t t = get_us(0) - headPosition.stamp; + t = us_delay - t; + // If, for whatever reason, we are already too late, just skip + if(t <= us_delay) wait_us(t); +} + +#define ATX_FILE_ACCESS_READ 1 +#define ATX_FILE_ACCESS_WRITE 2 + +int atx_file_access(int drv_num, int type, int offset, int len) +{ + (void)type; // ATM we only support reading, but writing is potentially possible + + FileSeek(&drive_infos[drv_num].file, offset, SEEK_SET); + + return len == FileReadAdv(&drive_infos[drv_num].file, atari_sector_buffer, len); +} + +static uint8_t loadAtxFile(int drv_num) +{ + atxFileHeader *fileHeader; + atxTrackHeader *trackHeader; + uint8_t r = 0; + + if(!atx_file_access(drv_num, ATX_FILE_ACCESS_READ, 0, sizeof(atxFileHeader))) return r; + + // validate the ATX file header + fileHeader = (atxFileHeader *) atari_sector_buffer; + if (fileHeader->signature[0] != 'A' || fileHeader->signature[1] != 'T' || + fileHeader->signature[2] != '8' || fileHeader->signature[3] != 'X' || + fileHeader->version != ATX_VERSION || fileHeader->minVersion != ATX_VERSION) return r; + + r = fileHeader->density; + + // enhanced density is 26 sectors per track, single and double density are 18 + atx_info[drv_num].sectorsPerTrack = (r == atx_medium) ? 26 : 18; + // single and enhanced density are 128 bytes per sector, double density is 256 + atx_info[drv_num].bytesPerSector = (r == atx_double) ? 256 : 128; + atx_info[drv_num].density = r; + atx_info[drv_num].currentHeadTrack = 0; + + // calculate track offsets + uint32_t startOffset = fileHeader->startData; + + for(int track = 0; track < MAX_TRACK ; track++) { + if (!atx_file_access(drv_num, ATX_FILE_ACCESS_READ, startOffset, sizeof(atxTrackHeader))) break; + trackHeader = (atxTrackHeader *) atari_sector_buffer; + atx_info[drv_num].trackOffset[track] = startOffset; + startOffset += trackHeader->size; + } + + return r; +} + +// Return 0 on full success, 1 on "Atari disk problem" (may have data) +// -1 on internal storage problem (corrupt ATX) +static int loadAtxSector(int drv_num, uint16_t num, uint8_t *status) +{ + + atxTrackHeader *trackHeader; + atxSectorListHeader *slHeader; + atxSectorHeader *sectorHeader; + atxTrackChunk *extSectorData; + + int r = 1; + + uint8_t is1050 = get_a8bit_reg(REG_ATARI_STATUS1) & STATUS1_MASK_ATX1050 ? 1 : 0; + + // calculate track and relative sector number from the absolute sector number + uint8_t tgtTrackNumber = (num - 1) / atx_info[drv_num].sectorsPerTrack; + uint8_t tgtSectorNumber = (num - 1) % atx_info[drv_num].sectorsPerTrack + 1; + + // set initial status (in case the target sector is not found) + *status = MASK_FDC_MISSING; + + uint16_t atxSectorSize = atx_info[drv_num].bytesPerSector; + + // delay for track stepping if needed + int diff = tgtTrackNumber - atx_info[drv_num].currentHeadTrack; + if (diff) + { + if (diff > 0) + { + diff += (is1050 ? 1 : 0); + } + else + { + diff = -diff; + } + wait_us(is1050 ? (diff*US_TRACK_STEP_1050 + US_HEAD_SETTLE_1050) : (diff*US_TRACK_STEP_810 + US_HEAD_SETTLE_810)); + } + + getCurrentHeadPosition(); + + // set new head track position + atx_info[drv_num].currentHeadTrack = tgtTrackNumber; + uint16_t sectorCount = 0; + // read the track header + uint32_t currentFileOffset = atx_info[drv_num].trackOffset[tgtTrackNumber]; + + if (currentFileOffset) + { + if(atx_file_access(drv_num, ATX_FILE_ACCESS_READ, currentFileOffset, sizeof(atxTrackHeader))) + { + trackHeader = (atxTrackHeader *) atari_sector_buffer; + sectorCount = trackHeader->sectorCount; + } + else + { + r = -1; + } + } + + if (trackHeader->trackNumber != tgtTrackNumber || atx_info[drv_num].density != ((trackHeader->flags & 0x2) ? atx_medium : atx_single)) + { + sectorCount = 0; + } + + uint32_t trackHeaderSize = trackHeader->headerSize; + + if (sectorCount) + { + currentFileOffset += trackHeaderSize; + if(atx_file_access(drv_num, ATX_FILE_ACCESS_READ, currentFileOffset, sizeof(atxSectorListHeader))) + { + slHeader = (atxSectorListHeader *) atari_sector_buffer; + // sector list header is variable length, so skip any extra header bytes that may be present + currentFileOffset += slHeader->next - sectorCount * sizeof(atxSectorHeader); + } + else + { + sectorCount = 0; + r = -1; + } + } + + uint32_t tgtSectorOffset; // the offset of the target sector data + int16_t weakOffset; + + uint8_t retries = is1050 ? MAX_RETRIES_1050 : MAX_RETRIES_810; + + uint32_t retryOffset = currentFileOffset; + uint16_t extSectorSize; + + while (retries > 0) + { + retries--; + currentFileOffset = retryOffset; + int pTT; + uint16_t tgtSectorIndex = 0; // the index of the target sector within the sector list + tgtSectorOffset = 0; + weakOffset = -1; + // iterate through all sector headers to find the target sector + + if(sectorCount) + { + for (int i = 0; i < sectorCount; i++) + { + if(!atx_file_access(drv_num, ATX_FILE_ACCESS_READ, currentFileOffset, sizeof(atxSectorHeader))) + { + r = -1; + break; + } + sectorHeader = (atxSectorHeader *)atari_sector_buffer; + + // if the sector is not flagged as missing and its number matches the one we're looking for... + if (sectorHeader->number == tgtSectorNumber) + { + if(sectorHeader->status & MASK_FDC_MISSING) + { + currentFileOffset += sizeof(atxSectorHeader); + continue; + } + // check if it's the next sector that the head would encounter angularly... + int tt = sectorHeader->timev - headPosition.angle; + if (!tgtSectorOffset || (tt > 0 && pTT <= 0) || (tt > 0 && pTT > 0 && tt < pTT) || (tt <= 0 && pTT <= 0 && tt < pTT)) + { + pTT = tt; + *status = sectorHeader->status; + tgtSectorIndex = i; + tgtSectorOffset = sectorHeader->data; + } + } + currentFileOffset += sizeof(atxSectorHeader); + } + } + + uint16_t actSectorSize = atxSectorSize; + extSectorSize = 0; + // if an extended data record exists for this track, iterate through all track chunks to search + // for those records (note that we stop looking for chunks when we hit the 8-byte terminator; length == 0) + if (*status & MASK_EXTENDED_DATA) + { + currentFileOffset = atx_info[drv_num].trackOffset[tgtTrackNumber] + trackHeaderSize; + do { + if(!atx_file_access(drv_num, ATX_FILE_ACCESS_READ, currentFileOffset, sizeof(atxTrackChunk))) + { + r = -1; + break; + } + extSectorData = (atxTrackChunk *) atari_sector_buffer; + if (extSectorData->size) + { + // if the target sector has a weak data flag, grab the start weak offset within the sector data + if (extSectorData->sectorIndex == tgtSectorIndex) + { + if(extSectorData->type == 0x10) + { // weak sector + weakOffset = extSectorData->data; + } + else if(extSectorData->type == 0x11) + { // extended sector + extSectorSize = 128 << extSectorData->data; + // 1050 waits for long sectors, 810 does not + if(is1050 ? (extSectorSize > actSectorSize) : (extSectorSize < actSectorSize)) + { + actSectorSize = extSectorSize; + } + } + } + currentFileOffset += extSectorData->size; + } + } while (extSectorData->size); + } + + if (tgtSectorOffset) + { + if(!atx_file_access(drv_num, ATX_FILE_ACCESS_READ, atx_info[drv_num].trackOffset[tgtTrackNumber] + tgtSectorOffset, atxSectorSize)) + { + r = -1; + tgtSectorOffset = 0; + } + + uint16_t au_one_sector_read = (23+actSectorSize)*(atx_info[drv_num].density == atx_single ? 8 : 4)+2; + // We will need to circulate around the disk one more time if we are re-reading the just written sector + wait_from_stamp((au_one_sector_read + pTT + (pTT > 0 ? 0 : AU_FULL_ROTATION))*8); + + if(*status) + { + // This is according to Altirra, but it breaks DjayBee's test J in 1050 mode?! + // wait_us(is1050 ? (US_TRACK_STEP_1050+US_HEAD_SETTLE_1050) : (AU_FULL_ROTATION*8)); + // This is what seems to work: + wait_us(AU_FULL_ROTATION*8); + } + } + else + { + // No matching sector found at all or the track does not match the disk density + wait_from_stamp(is1050 ? US_2FAKE_ROT_1050 : US_3FAKE_ROT_810); + if(is1050 || retries == 2) + { + // Repositioning the head for the target track + if(!is1050) + { + wait_us((43+tgtTrackNumber)*US_TRACK_STEP_810+US_HEAD_SETTLE_810); + } + else if(tgtTrackNumber) + { + wait_us((2*tgtTrackNumber+1)*US_TRACK_STEP_1050+US_HEAD_SETTLE_1050); + } + } + } + + getCurrentHeadPosition(); + + if(!*status || r < 0) break; + } + + *status &= ~(MASK_RESERVED | MASK_EXTENDED_DATA); + + if (*status & MASK_FDC_DLOST) + { + if(is1050) + { + *status |= MASK_FDC_DRQ; + } + else + { + *status &= ~(MASK_FDC_DLOST | MASK_FDC_CRC); + *status |= MASK_FDC_BUSY; + } + } + if(!is1050 && (*status & MASK_FDC_REC)) *status |= MASK_FDC_WP; + + if (tgtSectorOffset && !*status && r >= 0) r = 0; + + // if a weak offset is defined, randomize the appropriate data + if (weakOffset > -1) + { + for (int i = weakOffset; i < atxSectorSize; i++) + { + atari_sector_buffer[i] = rand(); + } + } + + wait_from_stamp(is1050 ? US_CS_CALC_1050 : US_CS_CALC_810); + // There is no file reading since last time stamp, so the alternative + // below is probably equally good + //wait_us(is1050 ? US_CS_CALC_1050 : US_CS_CALC_810); + + // the Atari expects an inverted FDC status byte + *status = ~(*status); + + // return the number of bytes read + return r; +} + +static int speed_index = 0; +static const uint8_t speeds[] = {0x28, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + +#define ui_speed_index ((get_a8bit_reg(REG_ATARI_STATUS1) >> 8) & 0x07) + +static uint8_t get_checksum(uint8_t* buf, int len) +{ + uint8_t sumo = 0; + uint8_t sum = 0; + for(int i = 0; i < len; i++) + { + sum += buf[i]; + if(sum < sumo) sum++; + sumo = sum; + } + return sum; +} + +static void uart_send_buffer(uint8_t *buf, int len) +{ + while(len-- > 0) uart_send(*buf++); +} + +static void uart_send_cmpl_and_atari_sector_buffer_and_check_sum(uint8_t *buf, int len, int success) +{ + wait_us(pre_ce_delay); + uart_send(success ? 'C' : 'E'); + wait_us(DELAY_T3_PERIPH); + uart_send_buffer(buf, len); + uart_send(get_checksum(buf, len)); +} + +static uint8_t hdd_partition_scan(fileTYPE *file, uint8_t info) +{ + int offset = 0; + FileSeek(file, offset, SEEK_SET); + if(FileReadAdv(file, atari_sector_buffer, 256) != 256) return 0; + + if(atari_sector_buffer[1] != 'A' || atari_sector_buffer[2] != 'P' || atari_sector_buffer[3] != 'T') + { + if(FileReadAdv(file, atari_sector_buffer, 256) != 256) return 0; + offset = 0xC2; + while(offset < 0x100) + { + if(atari_sector_buffer[offset] == 0x7F) + { + offset = ((atari_sector_buffer[offset+4]) | + ((atari_sector_buffer[offset+5] << 8)) | + ((atari_sector_buffer[offset+6] << 16)) | + ((atari_sector_buffer[offset+7] << 24))) << 9; + FileSeek(file, offset, SEEK_SET); + if(FileReadAdv(file, atari_sector_buffer, 256) != 256 || atari_sector_buffer[1] != 'A' || atari_sector_buffer[2] != 'P' || atari_sector_buffer[3] != 'T') return 0; + break; + } + offset += 16; + } + } + + if(atari_sector_buffer[0] == 0x10) info |= INFO_META; + else if(atari_sector_buffer[0]) return 0; + + info |= (atari_sector_buffer[4] & 0xF); + for(int pidx = 0; pidx < 15; pidx++) + { + int i = (pidx + 1)*16; + if(!atari_sector_buffer[i]) + { + // empty slot + if(drive_infos[pidx].info & INFO_HDD) drive_infos[pidx].info = 0; + } + else + { + drive_infos[pidx].info = (info & 0xC0) | (atari_sector_buffer[i] & 0x40 ? INFO_META : 0) | + ((atari_sector_buffer[i] & 0x30) || (atari_sector_buffer[i+12] & 0x80) ? INFO_RO : 0); + atari_sector_buffer[i] &= 0x8F; + if(atari_sector_buffer[i] > 3 || (atari_sector_buffer[i+1] != 0x00 && atari_sector_buffer[i+1] != 0x03) || !(atari_sector_buffer[i+12] & 0x40)) + { + drive_infos[pidx].info = 0; // TODO ?! Is this enough to fully disable the drive? + } + else + { + drive_infos[pidx].sector_size = 128 << (atari_sector_buffer[i]-1); + if(drive_infos[pidx].sector_size != 512) + { + drive_infos[pidx].info |= INFO_SS; + } + drive_infos[pidx].offset = + ( atari_sector_buffer[i+2] | + (atari_sector_buffer[i+3] << 8) | + (atari_sector_buffer[i+4] << 16) | + (atari_sector_buffer[i+5] << 24) ) << 9; + drive_infos[pidx].sector_count = + atari_sector_buffer[i+6] | + (atari_sector_buffer[i+7] << 8) | + (atari_sector_buffer[i+8] << 16) | + (atari_sector_buffer[i+9] << 24); + drive_infos[pidx].partition_id = atari_sector_buffer[i+10] | (atari_sector_buffer[i+11] << 8); + if(atari_sector_buffer[i+1] == 0x00) // DOS partition + { + uint16_t p_offset = + (atari_sector_buffer[i+14] | (atari_sector_buffer[i+15] << 8)) << 9; + drive_infos[pidx].meta_offset = drive_infos[pidx].offset - 512; + drive_infos[pidx].offset += p_offset; + } + else // External partition + { + drive_infos[pidx].meta_offset = + (atari_sector_buffer[i+13] | + (atari_sector_buffer[i+14] << 8) | + (atari_sector_buffer[i+15] << 16)) << 9; + } + drive_infos[pidx].custom_loader = 0; + drive_infos[pidx].atari_sector_status = 0xFF; + } + } + } + return info; +} + +static void set_drive_status(int drive_number, const char *name, uint8_t ext_index) +{ + uint8_t info = 0; + atr_header_t atr_header; + + if(drive_number == 4) drive_number = MAX_DRIVES; + + if(drive_number < MAX_DRIVES && (drive_infos[drive_number].info & INFO_HDD)) return; + + if(!name[0]) + { + FileClose(&drive_infos[drive_number].file); + if(drive_number == MAX_DRIVES) + { + for(int i = 0; i < MAX_DRIVES; i++) + { + if(drive_infos[i].info & INFO_HDD) drive_infos[i].info = 0; + } + } + return; + } + + uint8_t read_only = (ext_index == 1) || (ext_index == 3) || + !FileCanWrite(name) || (get_a8bit_reg(REG_ATARI_STATUS1) & STATUS1_MASK_RDONLY); + + + if(!FileOpenEx(&drive_infos[drive_number].file, name, read_only ? O_RDONLY : (O_RDWR | O_SYNC))) return; + + if(read_only) info |= INFO_RO; + + if(drive_number < MAX_DRIVES && ext_index == 0) // ATR only + { + if (FileReadAdv(&drive_infos[drive_number].file, (uint8_t *)&atr_header, 16) != 16) + { + FileClose(&drive_infos[drive_number].file); + return; + } + } + + drive_infos[drive_number].custom_loader = 0; + drive_infos[drive_number].atari_sector_status = 0xff; + + if (ext_index == 2) // XDF + { + drive_infos[drive_number].offset = 0; + drive_infos[drive_number].sector_count = drive_infos[drive_number].file.size / 0x80; + drive_infos[drive_number].sector_size = 0x80; + } + else if (ext_index == 3) // ATX + { + drive_infos[drive_number].custom_loader = 2; + uint8_t atxType = loadAtxFile(drive_number); + drive_infos[drive_number].sector_count = (atxType == atx_medium) ? 1040 : 720; + drive_infos[drive_number].sector_size = (atxType == atx_double) ? 256 : 128; + } + else if (ext_index == 1) // XEX + { + drive_infos[drive_number].custom_loader = 1; + drive_infos[drive_number].sector_count = 0x173+(drive_infos[drive_number].file.size+(XEX_SECTOR_SIZE-4))/(XEX_SECTOR_SIZE-3); + drive_infos[drive_number].sector_size = XEX_SECTOR_SIZE; + } + else // ATR or IMG + { + if(drive_number < MAX_DRIVES) + { + drive_infos[drive_number].offset = 16; + if(atr_header.wSecSize == 512) + { + drive_infos[drive_number].sector_count = (atr_header.wPars | (atr_header.btParsHigh << 16)) / 32; + } + else + { + drive_infos[drive_number].sector_count = 3 + ((atr_header.wPars | (atr_header.btParsHigh << 16))*16 - 128*3) / atr_header.wSecSize; + } + drive_infos[drive_number].sector_size = atr_header.wSecSize; + } + else + { + info |= INFO_HDD; + drive_infos[drive_number].sector_size = 512; + drive_infos[drive_number].sector_count = drive_infos[drive_number].file.size / 0x200; + drive_infos[drive_number].atari_sector_status = 0; + drive_infos[drive_number].offset = 0; + info = hdd_partition_scan(&drive_infos[drive_number].file, info); + if(!info) + { + FileClose(&drive_infos[drive_number].file); + return; + } + } + } + drive_infos[drive_number].info = info; +} + +typedef void (*CommandHandler)(sio_command_t, int, fileTYPE *, sio_action_t *); + +static void handle_speed(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + (void)file; + // We should be guaranteed that this is not called in PBI mode, + // so no need to check the PBI bit + action->bytes = 1; + if(drive_infos[drive_number].custom_loader == 2) + { + speed_index = 0; + action->sector_buffer[0] = speeds[speed_index]; + } + else + { + speed_index = command.aux2 ? 0 : ui_speed_index; + action->sector_buffer[0] = speeds[ui_speed_index]; + } + action->speed = speeds[speed_index] + 6; +} + +static void handle_format(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + (void)command; + memset(action->sector_buffer, 0, drive_infos[drive_number].sector_size); + int i = drive_infos[drive_number].offset; + FileSeek(file, i, SEEK_SET); + for (; i != file->size; i += 128) + { + FileWriteAdv(file, action->sector_buffer, 128); + } + action->sector_buffer[0] = 0xff; + action->sector_buffer[1] = 0xff; + action->bytes = drive_infos[drive_number].sector_size; +} + +static void handle_read_percom(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + (void)command; + (void)file; + uint16_t total_sectors = drive_infos[drive_number].sector_count; + memset(action->sector_buffer, 0, 12); + action->sector_buffer[1] = 0x03; + action->sector_buffer[6] = drive_infos[drive_number].sector_size >> 8; + action->sector_buffer[7] = drive_infos[drive_number].sector_size & 0xff; + action->sector_buffer[8] = 0xff; + + if(!(drive_infos[drive_number].info & INFO_HDD) && (total_sectors == 720 || total_sectors == 1040 || total_sectors == 1440)) + { + total_sectors = total_sectors / 40; + if(total_sectors == 36) + { + total_sectors = total_sectors / 2; + action->sector_buffer[4] = 1; + } + action->sector_buffer[0] = 40; + action->sector_buffer[5] = (drive_infos[drive_number].sector_size == 256 || total_sectors == 26) ? 4 : 0; + } + else + { + action->sector_buffer[0] = 1; + action->sector_buffer[5] = (drive_infos[drive_number].sector_size == 128) ? 0 : 4; + } + action->sector_buffer[2] = total_sectors >> 8; + action->sector_buffer[3] = total_sectors & 0xff; + action->bytes = 12; +} + +static void handle_force_media_change(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + (void)command; + action->respond = 0; + uint8_t info = hdd_partition_scan(file, INFO_HDD | ((file->mode & O_RDONLY) ? INFO_RO : 0)); + if(info) + { + drive_infos[drive_number].info = info; + } + else + { + action->success = 0; + } +} + +static void handle_device_info(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + (void)command; + memset(action->sector_buffer, 0, action->bytes); + action->sector_buffer[0] = 1; + action->sector_buffer[2] = 1; + action->sector_buffer[6] = drive_infos[drive_number].sector_size; + action->sector_buffer[7] = drive_infos[drive_number].sector_size >> 8; + action->sector_buffer[8] = drive_infos[drive_number].sector_count; + action->sector_buffer[9] = drive_infos[drive_number].sector_count >> 8; + action->sector_buffer[10] = drive_infos[drive_number].sector_count >> 16; + action->sector_buffer[11] = drive_infos[drive_number].sector_count >> 24; + if(drive_number == MAX_DRIVES) + { + atari800_dma_read(a8bit_buffer, ATARI_BASE + 0xDFA0, 0x60); + memcpy(&action->sector_buffer[0x10], &a8bit_buffer[0x0F], a8bit_buffer[0x0E]); + memcpy(&action->sector_buffer[0x38], &a8bit_buffer[0x38], a8bit_buffer[0x37]); + } + else + { + action->sector_buffer[3] = drive_infos[drive_number].partition_id; + action->sector_buffer[4] = drive_infos[drive_number].partition_id >> 8; + // TODO This made me realize that we are limited in the SD image size + action->sector_buffer[12] = drive_infos[drive_number].offset >> 9; + action->sector_buffer[13] = drive_infos[drive_number].offset >> 17; + action->sector_buffer[14] = drive_infos[drive_number].offset >> 25; + if(drive_infos[drive_number].info & INFO_META) + { + FileSeek(file, drive_infos[drive_number].meta_offset + 16, SEEK_SET); + if(FileReadAdv(file, &action->sector_buffer[0x10], 40) != 40) action->success = 0; + } + } +} + +#if 0 +void handle_device_status(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + memset(action->sector_buffer, 0, action->bytes); + action->sector_buffer[0x0C] = 0x3F; +} +#endif + +static void handle_get_status(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + (void)command; + (void)file; + uint8_t status = 0x40; + + if(drive_number != MAX_DRIVES) + { + + status = 0x10; // Motor on; + + if (drive_infos[drive_number].info & INFO_RO) + { + status |= 0x08; // write protected; // no write support yet... + } + if(drive_infos[drive_number].sector_count != 720) + { + status |= 0x80; // medium density - or a strange one... + } + if(drive_infos[drive_number].sector_size != 128) + { + status |= 0x20; // 256 byte sectors + } + } + action->sector_buffer[0] = status; + action->sector_buffer[1] = drive_infos[drive_number].atari_sector_status; + action->sector_buffer[2] = drive_number == MAX_DRIVES ? 0x10 : 0xe0; // What should be our ID? + if(drive_number == MAX_DRIVES) + { + atari800_dma_read(&action->sector_buffer[3], ATARI_BASE + 0xDFAD, 1); + } + else + { + action->sector_buffer[3] = 0; + } + action->bytes = 4; +} + +static int set_location_offset(int drive_number, uint32_t sector, uint32_t *location) +{ + *location = drive_infos[drive_number].offset; + int sector_size = drive_infos[drive_number].info & (INFO_HDD | INFO_SS) ? 512 : drive_infos[drive_number].sector_size; + if(drive_infos[drive_number].sector_size == 512 || (drive_infos[drive_number].info & INFO_HDD)) + { + if(drive_number != MAX_DRIVES) + { + sector--; + } + *location += sector * sector_size; + } + else + { + if(sector > 3) + { + *location += 128*3 + (sector-4) * sector_size; + } + else + { + *location = *location + 128 * (sector - 1); + sector_size = 128; + } + } + return sector_size; +} + +static void handle_write(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + uint8_t pbi = command.deviceId & 0x40; + uint32_t sector = (command.auxab << 16) | command.aux1 | (command.aux2 << 8); + int sector_size = 0; + uint32_t location = 0; + + action->respond = 0; + + sector_size = set_location_offset(drive_number, sector, &location); + + uint8_t checksum = 0; + uint8_t expchk = 0; + + if(!pbi) + { + for (int i = 0; i < sector_size; i++) + { + action->sector_buffer[i] = uart_receive(); + } + checksum = uart_receive(); + expchk = get_checksum(action->sector_buffer, sector_size); + } + if (checksum == expchk) + { + if(!pbi) + { + wait_us(850); + uart_send('A'); + } + + FileSeek(file, location, SEEK_SET); + if(drive_infos[drive_number].info & INFO_SS) + { + int step = 512 / drive_infos[drive_number].sector_size; + sector_size = ATARI_SECTOR_BUFFER_SIZE; + memset(atari_sector_buffer, 0, sector_size); + int i = 0; + int written = 0; + while(written < sector_size) + { + atari_sector_buffer[written] = action->sector_buffer[i++]; + written += step; + } + FileWriteAdv(file, atari_sector_buffer, sector_size); + } + else + { + FileWriteAdv(file, atari_sector_buffer, sector_size); + } + + int ok = 1; + + if (command.command == 0x57) + { + FileSeek(file, location, SEEK_SET); + FileReadAdv(file, a8bit_buffer, sector_size); + + for (int i = 0; i < sector_size; i++) + { + if (a8bit_buffer[i] != action->sector_buffer[i]) ok = 0; + } + } + + if(pbi) + { + action->success = ok; + } + else + { + wait_us(DELAY_T5_MIN); + uart_send(ok ? 'C' : 'E'); + } + } + else + { + uart_send('N'); + } +} + +static void handle_read(sio_command_t command, int drive_number, fileTYPE *file, sio_action_t *action) +{ + uint32_t sector = (command.auxab << 16) | command.aux1 | (command.aux2<<8); + + uint32_t location = 0; + + if(drive_infos[drive_number].custom_loader == 1) // XEX file + { + int file_sectors; + + if (sector <= 2) + { + memcpy(action->sector_buffer, &boot_xex_loader[(sector - 1) * XEX_SECTOR_SIZE], XEX_SECTOR_SIZE); + } + else if(sector == 0x168) + { + file_sectors = drive_infos[drive_number].sector_count; + int vtoc_sectors = file_sectors / 1024; + int rem = file_sectors - (vtoc_sectors * 1024); + if(rem > 943) { + vtoc_sectors += 2; + } + else if(rem) + { + vtoc_sectors++; + } + if(!(vtoc_sectors % 2)) + { + vtoc_sectors++; + } + + file_sectors -= (vtoc_sectors + 12); + action->sector_buffer[0] = (uint8_t)((vtoc_sectors + 3)/2); + goto set_number_of_sectors_to_buffer_1_2; + } + else if(sector == 0x169) + { + file_sectors = drive_infos[drive_number].sector_count - 0x173; + { + memset(&action->sector_buffer[5], ' ', 8); + int si = 0; + for(int i = 0; i < 11; i++) + { + char c = drive_infos[drive_number].file.name[si]; + if(c == '.') + { + i = 7; + } + else + { + if(c >= 'a' && c <= 'z') c -= 32; + else if(c < 'A' || c > 'Z') c = '@'; + action->sector_buffer[5+i] = c; + } + si = i == 7 ? strlen(drive_infos[drive_number].file.name) - 3 : si + 1; + } + } + memset(&action->sector_buffer[16], 0, XEX_SECTOR_SIZE-16); + + action->sector_buffer[0]=(file_sectors > 0x28F) ? 0x46 : 0x42; + + action->sector_buffer[3] = 0x71; + action->sector_buffer[4] = 0x01; +set_number_of_sectors_to_buffer_1_2: + action->sector_buffer[1] = file_sectors; + action->sector_buffer[2] = (file_sectors >> 8); + } + else if(sector >= 0x171) + { + FileSeek(file, (sector - 0x171) * (XEX_SECTOR_SIZE - 3), SEEK_SET); + int read = FileReadAdv(file, action->sector_buffer, XEX_SECTOR_SIZE - 3); + sector = (read < (XEX_SECTOR_SIZE - 3)) ? 0 : sector + 1; + action->sector_buffer[XEX_SECTOR_SIZE - 3] = (sector >> 8); + action->sector_buffer[XEX_SECTOR_SIZE - 2] = sector; + action->sector_buffer[XEX_SECTOR_SIZE - 1] = (uint8_t)read; + } + + action->bytes = XEX_SECTOR_SIZE; + } + else if (drive_infos[drive_number].custom_loader == 2) // ATX + { + pre_ce_delay = 0; // Taken care of in loadAtxSector + int res = loadAtxSector(drive_number, sector, &drive_infos[drive_number].atari_sector_status); + action->bytes = drive_infos[drive_number].sector_size; + action->success = (res == 0); + } + else + { + action->bytes = set_location_offset(drive_number, sector, &location); + FileSeek(file, location, SEEK_SET); + if(drive_infos[drive_number].info & INFO_SS) + { + uint8_t step = 512 / drive_infos[drive_number].sector_size; + FileReadAdv(file, atari_sector_buffer, ATARI_SECTOR_BUFFER_SIZE, -1); + int read = 0; + int n = 0; + while(read < ATARI_SECTOR_BUFFER_SIZE) + { + action->sector_buffer[n++] = atari_sector_buffer[read]; + read += step; + } + } + else + { + FileReadAdv(file, action->sector_buffer, action->bytes, -1); + } + } +} + +static CommandHandler get_command_handler(sio_command_t command, uint8_t dstats) +{ + CommandHandler res = NULL; + uint32_t sector = (command.auxab << 16) | command.aux1 | (command.aux2 << 8); + // The HDD SD card counts sectors from 0 + uint8_t min_sector = (command.deviceId & 0x3F) == 0x20 ? 0 : 1; + int drive_number = min_sector ? (command.deviceId & 0xf) - 1 : MAX_DRIVES; + uint8_t pbi = command.deviceId & 0x40; + uint8_t writable = !(drive_infos[drive_number].info & INFO_RO); + + switch (command.command) + { + case 0x3f: + if(!pbi) res = &handle_speed; + break; + case 0x21: // format single + case 0x22: // format enhanced + if(writable && !pbi) res = &handle_format; + break; + case 0x46: + if(pbi) res = &handle_force_media_change; + break; + case 0x4e: // read percom block + if(min_sector) res = &handle_read_percom; + break; + case 0x53: // get status + if(!pbi || dstats == 0x40) res = &handle_get_status; + break; + case 0x50: // write + case 0x57: // write with verify + if (writable && (!pbi || dstats == 0x80) && sector >= min_sector && sector - min_sector < drive_infos[drive_number].sector_count) + res = &handle_write; + break; + case 0x52: // read + if ((!pbi || dstats == 0x40) && sector >= min_sector && sector - min_sector < drive_infos[drive_number].sector_count) + { + if(drive_infos[drive_number].custom_loader == 2) // ATX! + { + pre_an_delay = 3220; + } + res = &handle_read; + } + break; + + case 0x6E: // PBI device info + if(pbi && dstats == 0x40) + res = &handle_device_info; + break; +#if 0 + case 0xEC: // PBI device status + if(pbi && dstats == 0x40) + res = &handle_device_status; + break; +#endif + } + return res; +} + +int get_command(sio_command_t *cmd) +{ + if(!uart_available()) return 0; + + int success = 1; + for (int i = 0; i < 5; i++) + { + uint16_t data = uart_receive(); // Timeout? + if (uart_error() || ((data >> 8) != (i+1))) + { + success = 0; + break; + } + ((uint8_t *)cmd)[i] = (uint8_t)(data & 0xff); + } + + if(!success) return success; + + uart_receive(); + + if (get_checksum((uint8_t *)cmd, 4) == cmd->chksum) + { + uart_switch(); + } + else + { + success = 0; + } + + return success; +} + +static void process_command() +{ + sio_command_t command; + + if(!get_command(&command)) { return; } + command.auxab = 0; + + int drive = (command.deviceId & 0xf) -1; + if (command.deviceId >= 0x31 && command.deviceId <= 0x34 && drive_infos[drive].sector_size != 512) + { + fileTYPE *file = &drive_infos[drive].file; + if (!file->opened()) return; + set_a8bit_reg(REG_DRIVE_LED, 1); + + pre_ce_delay = DELAY_T5_MIN; + pre_an_delay = DELAY_T2_MIN; + + CommandHandler handle_command = get_command_handler(command, 0); + + wait_us(pre_an_delay); + + if (handle_command) + { + sio_action_t action; + action.bytes = 0; + action.success = 1; + action.speed = -1; + action.respond = 1; + action.sector_buffer = atari_sector_buffer; + + uart_send('A'); + memset(atari_sector_buffer, 0, ATARI_SECTOR_BUFFER_SIZE); + + handle_command(command, drive, file, &action); + + if (action.respond) uart_send_cmpl_and_atari_sector_buffer_and_check_sum(action.sector_buffer, action.bytes, action.success); + if (action.speed >= 0) + uart_init(action.speed); + } + else + { + uart_send('N'); + } + set_a8bit_reg(REG_DRIVE_LED, 0); + } +} + +void atari800_set_image(int ext_index, int file_index, const char *name) +{ + if(file_index == 5) // XEX file + { + if(name[0] && FileOpen(&xex_file, name)) + { + xex_file_first_block = 1; + reboot(1, 1); + set_a8bit_reg(REG_XEX_LOADER, 1); + set_a8bit_reg(REG_CART1_SELECT, 0); + set_a8bit_reg(REG_CART2_SELECT, 0); + uint16_t atari_status1 = get_a8bit_reg(REG_ATARI_STATUS1); + + atari8bit_dma_zero(SDRAM_BASE, 0x10000); + atari8bit_dma_write(xex_loader, ATARI_BASE + 0xD100, (uint32_t)sizeof(xex_loader)); + static uint8_t write_bytes[4]; + + write_bytes[0] = 0; + atari8bit_dma_write(write_bytes, ATARI_COLDST, 1); + atari8bit_dma_write(write_bytes, ATARI_GINTLK, 1); + write_bytes[0] = 1; + atari8bit_dma_write(write_bytes, ATARI_BASICF, 1); + write_bytes[0] = 2; + atari8bit_dma_write(write_bytes, ATARI_BOOTFLAG, 1); + write_bytes[0] = XEX_INIT1; + write_bytes[1] = 0xD1; + atari8bit_dma_write(write_bytes, ATARI_CASINI, 2); + write_bytes[0] = 0x71; + write_bytes[1] = 0xE4; + atari8bit_dma_write(write_bytes, ATARI_DOSVEC, 2); + + if(!(atari_status1 & STATUS1_MASK_MODE800)) + { + write_bytes[0] = 0x5C; + write_bytes[1] = 0x93; + write_bytes[2] = 0x25; + atari8bit_dma_write(write_bytes, ATARI_PUPBT, 3); + } + + set_a8bit_reg(REG_PAUSE, 0); + + } + else + { + FileClose(&xex_file); + } + } + else if(file_index == 6) // D1: disk image with automatic boot + { + if(name[0]) + { + set_a8bit_reg(REG_PAUSE, 1); + set_a8bit_reg(REG_CART1_SELECT, 0); + set_a8bit_reg(REG_CART2_SELECT, 0); + } + set_drive_status(0, name, ext_index); + if(name[0]) + { + reboot(1, 0); + set_a8bit_reg(REG_OPTION_FORCE, 1); + set_a8bit_reg(REG_OPTION_FORCE, 0); + } + } + else if(file_index < 5) // 5 is the HDD and requires slightly different handling + { + set_drive_status(file_index, name, ext_index); + } +} + +static uint8_t process_command_pbi(const uint8_t *drives_config) +{ + // We are more or less guranteed to serve the correct device id and + // drive unit number by now, no need to check here + // mark a bit (0x40) in deviceId to indicate this is PBI, this is not the same + // as the XDCB bit (0x80) + + set_a8bit_reg(REG_DRIVE_LED, 1); + + static uint8_t iocb[16]; + atari800_dma_read(iocb, ATARI_BASE + 0x300, 16); + + sio_command_t command; + + uint8_t sd_device = (iocb[0] & 0x7F) == 0x20; + int drive = iocb[1] - 1; + command.deviceId = (iocb[0] + drive) | 0x40; // ddevic + dunit - 1 plus PBI marker + + /* + This piece of admitedely contrived logic takes care of diverting or not further processing + to SIO routines. The procedure is not exactly the same as on, say, Ultimate 1MB PBI BIOS, nor + it is strictly according to the PBI API requirements, here we (safely?) assume there are no + other PBI devices (and hence ROM BIOSes), so this allows us to take some shortcuts (which also + speeds up things on the Atari / SDX side). + */ + + uint8_t mode = (sd_device && !drive) ? 1 : ((!sd_device && drive < 4) ? drives_config[drive] : ((iocb[0] & 0x80) >> 7)); + + if(sd_device && !drive) drive = MAX_DRIVES; + + fileTYPE *file = &drive_infos[drive < MAX_DRIVES && (drive_infos[drive].info & INFO_HDD) ? MAX_DRIVES : drive].file; + + if(file->opened()) + { + if(drive_infos[drive].info & INFO_HDD) // The type should be then ATR + { + if(!sd_device) mode = 1; + } + else + { + if(drive_infos[drive].custom_loader == 2) mode = 0; // ATX -> Off + if(drive_infos[drive].custom_loader == 1) mode = 1; // XEX -> PBI + } + } + + // HSIO does not handle 512-byte sector ATRs + if(!mode || (file->opened() && mode == 2 && drive_infos[drive].sector_size == 512)) + return 0xFF; + + mode--; + if (!file->opened() || mode == 1) + { + if(!mode) iocb[3] = 0x8A; + } + else + { + command.command = iocb[2]; + command.aux1 = iocb[0xA]; + command.aux2 = iocb[0xB]; + command.auxab = (command.deviceId & 0x80) ? iocb[0xC] | (iocb[0xD] << 8) : 0; + + CommandHandler handle_command = get_command_handler(command, iocb[3]); + if (handle_command) + { + sio_action_t action; + action.bytes = iocb[8] | (iocb[9] << 8); + action.success = 1; + action.respond = 1; + // Copy over 512 bytes from Atari to the buffer + if(action.bytes) + { + atari800_dma_read(atari_sector_buffer, ATARI_BASE + (iocb[4] | (iocb[5] << 8)), action.bytes); + action.sector_buffer = atari_sector_buffer; + } + + handle_command(command, drive, file, &action); + + if (action.respond) + { + iocb[8] = action.bytes & 0xFF; + iocb[9] = (action.bytes >> 8) & 0xFF; + atari8bit_dma_write(atari_sector_buffer, ATARI_BASE + (iocb[4] | (iocb[5] << 8)), action.bytes); + atari8bit_dma_write(&iocb[0x08], ATARI_BASE + 0x308, 2); + } + iocb[3] = action.success ? 0x01 : 0x90; + } + else + { + iocb[3] = 0x8B; + } + } + atari8bit_dma_write(&iocb[0x03], ATARI_BASE + 0x303, 1); + set_a8bit_reg(REG_DRIVE_LED, 0); + return mode; +} + +void handle_pbi() +{ + if(!(get_a8bit_reg(REG_ATARI_STATUS1) & STATUS1_MASK_MODEPBI)) return; + + static uint8_t pbi_ram_base[16]; + static uint8_t pbi_drives_config[4]; + + atari800_dma_read(pbi_ram_base, ATARI_BASE + 0xD100, 16); + + if(pbi_ram_base[0] == 0xa5 && pbi_ram_base[1] == 0xa5) + { + uint16_t atari_status2 = get_a8bit_reg(REG_ATARI_STATUS2); + + if(pbi_ram_base[3] == 0x01) + { + pbi_drives_config[0] = (atari_status2 >> 0) & 0x3; + pbi_drives_config[1] = (atari_status2 >> 2) & 0x3; + pbi_drives_config[2] = (atari_status2 >> 4) & 0x3; + pbi_drives_config[3] = (atari_status2 >> 6) & 0x3; + + memcpy(&pbi_ram_base[0x0C], pbi_drives_config, 4); + uint8_t boot_drv = (atari_status2 >> 8) & 0x07; + + pbi_ram_base[0x0B] = boot_drv; + pbi_ram_base[0x0A] = 0x00; + if(boot_drv == 1 && drive_infos[MAX_DRIVES].file.opened()) + { + // APT + pbi_ram_base[0x0A] = drive_infos[MAX_DRIVES].info & 0xF; + } + else if(boot_drv) + { + pbi_ram_base[0x0A] = boot_drv - 1; + } + atari8bit_dma_write(&pbi_ram_base[0x0A], ATARI_BASE + 0xD10A, 6); + pbi_ram_base[2] = atari_status2 & STATUS2_MASK_SPLASH ? 1 : 0; + // Important - this has to be alone and last! + pbi_ram_base[3] = 0; + atari8bit_dma_write(&pbi_ram_base[0x02], ATARI_BASE + 0xD102, 2); + } + else if(pbi_ram_base[5] == 0x01) + { + pbi_ram_base[4] = process_command_pbi(pbi_drives_config); + pbi_ram_base[5] = 0; + // Same here with the order + atari8bit_dma_write(&pbi_ram_base[0x04], ATARI_BASE + 0xD104, 2); + } + } +} + +static void handle_xex() +{ + atari800_dma_read(a8bit_buffer, ATARI_BASE + 0xD100, XEX_READ_STATUS+1); + + if(a8bit_buffer[0] == 0x60) + { + if(!a8bit_buffer[XEX_READ_STATUS]) + { + uint8_t len_buf[2]; + int read_offset, to_read, read_len; + + len_buf[0] = 0xFF; + len_buf[1] = 0xFF; + + // Point to rts + a8bit_buffer[0] = 0x00; + a8bit_buffer[1] = 0xD1; + atari8bit_dma_write(a8bit_buffer, ATARI_INITAD, 2); + + while(len_buf[0] == 0xFF && len_buf[1] == 0xFF) + { + if(FileReadAdv(&xex_file, len_buf, 2) != 2) goto xex_eof; + } + read_offset = len_buf[0] | (len_buf[1] << 8); + if(xex_file_first_block) + { + xex_file_first_block = 0; + atari8bit_dma_write(len_buf, ATARI_RUNAD, 2); + } + + if(FileReadAdv(&xex_file, len_buf, 2) != 2) goto xex_eof; + + read_len = (len_buf[0] | (len_buf[1] << 8)) + 1 - read_offset; + if(read_len < 1) goto xex_eof; + + to_read = read_len > BUFFER_SIZE ? BUFFER_SIZE : read_len; + + while(read_len) + { + if(FileReadAdv(&xex_file, a8bit_buffer, to_read) != to_read) goto xex_eof; + atari8bit_dma_write(a8bit_buffer, ATARI_BASE + read_offset, to_read); + read_len -= to_read; + read_offset += to_read; + to_read = read_len > BUFFER_SIZE ? BUFFER_SIZE : read_len; + } + + a8bit_buffer[0] = 0x01; + atari8bit_dma_write(a8bit_buffer, ATARI_BASE + 0xD100 + XEX_READ_STATUS, 1); + } + } + // Is loader done? + else if(a8bit_buffer[0] == 0x5F) +xex_eof: + { + a8bit_buffer[0] = 0xFF; + atari8bit_dma_write(a8bit_buffer, ATARI_BASE + 0xD100 + XEX_READ_STATUS, 1); + FileClose(&xex_file); + } + +} + +void atari800_poll() +{ + uint16_t atari_status1 = get_a8bit_reg(REG_ATARI_STATUS1); + + set_a8bit_reg(REG_PAUSE, atari_status1 & STATUS1_MASK_HALT); + + if (atari_status1 & STATUS1_MASK_SOFTBOOT) + { + reboot(0, 0); + } + else if (atari_status1 & STATUS1_MASK_COLDBOOT) + { + reboot(1, 0); + } + + if(xex_file.opened()) handle_xex(); + handle_pbi(); + process_command(); +} + +void atari800_init() +{ + set_a8bit_reg(REG_PAUSE, 1); + cart_matches_total = 0; + cart_match_car = 0; + + // Try to load bootX.rom ? TODO - limit only to boot3? or require pbibios.rom name? + // and rely on the OSD menus? + // In any case PBI boot rom should be attempted regardless of the option setting + // (there is no menu entry to load it) + static char mainpath[512]; + const char *home = HomeDir(); + + if(get_a8bit_reg(REG_ATARI_STATUS1) & STATUS1_MASK_BOOTX) + { + for(int i = 0; i < 3; i++) + { + sprintf(mainpath, "%s/boot%d.rom", home, i); + user_io_file_tx(mainpath, i << 6); + } + } + sprintf(mainpath, "%s/boot3.rom", home); + user_io_file_tx(mainpath, 3 << 6); + atari800_reset(); +} + +void atari800_reset() +{ + set_a8bit_reg(REG_PAUSE, 1); + set_a8bit_reg(REG_CART1_SELECT, 0); + mounted_cart1_size = 0; + set_a8bit_reg(REG_CART2_SELECT, 0); + set_a8bit_reg(REG_FREEZER, 0); + for(int i=0; i <= MAX_DRIVES; i++) + { + FileClose(&drive_infos[i].file); + } + FileClose(&xex_file); + speed_index = 0; + uart_init(speeds[speed_index] + 6); + reboot(1, 0); +} diff --git a/support/atari8bit/atari800.h b/support/atari8bit/atari800.h new file mode 100644 index 0000000..d65c7a3 --- /dev/null +++ b/support/atari8bit/atari800.h @@ -0,0 +1,15 @@ +#ifndef __ATARI800_H__ +#define __ATARI800_H__ + +int atari800_get_match_cart_count(); +const char *atari800_get_cart_match_name(int match_index); +void atari800_init(); +void atari800_reset(); +void atari800_poll(); +void atari800_umount_cartridge(uint8_t stacked); +int atari800_check_cartridge_file(const char* name, unsigned char index); +void atari800_open_cartridge_file(const char* name, int match_index); +void atari800_open_bios_file(const char* name, unsigned char index); +void atari800_set_image(int ext_index, int file_index, const char *name); + +#endif diff --git a/support/atari8bit/atari8bit_defs.h b/support/atari8bit/atari8bit_defs.h new file mode 100644 index 0000000..512919d --- /dev/null +++ b/support/atari8bit/atari8bit_defs.h @@ -0,0 +1,43 @@ +#define STATUS1_MASK_SOFTBOOT 0x0001 +#define STATUS1_MASK_COLDBOOT 0x0002 +#define STATUS1_MASK_HALT 0x0004 +#define STATUS1_MASK_MODE800 0x0008 +#define STATUS1_MASK_BOOTX 0x0010 +#define STATUS1_MASK_RDONLY 0x0040 +#define STATUS1_MASK_MODEPBI 0x0080 +#define STATUS1_MASK_ATX1050 0x8000 + +#define STATUS2_MASK_SPLASH 0x0800 + +#define BUFFER_SIZE 8192 + +#if BUFFER_SIZE < 8192 +#error BUFFER_SIZE is too small! +#endif + +#define A800_SIO_TX_STAT 0x03 +#define A800_SIO_RX 0x04 +#define A800_SIO_RX_STAT 0x05 +#define A800_SIO_GETDIV 0x06 +#define A800_SIO_ERROR 0x07 + +#define A800_GET_REGISTER 0x08 +#define A800_SET_REGISTER 0x09 + +#define REG_CART1_SELECT 0x01 +#define REG_CART2_SELECT 0x02 +#define REG_RESET 0x03 +#define REG_PAUSE 0x04 +#define REG_FREEZER 0x05 +#define REG_RESET_RNMI 0x06 +#define REG_OPTION_FORCE 0x07 +#define REG_DRIVE_LED 0x08 +#define REG_XEX_LOADER 0x09 +#define REG_SIO_TX 0x0A +#define REG_SIO_SETDIV 0x0B + +#define REG_ATARI_STATUS1 0x01 +#define REG_ATARI_STATUS2 0x02 + +#define SDRAM_BASE 0x2000000 +#define ATARI_BASE 0x0010000 diff --git a/support/atari8bit/boot_xex_loader.h b/support/atari8bit/boot_xex_loader.h new file mode 100644 index 0000000..34c2548 --- /dev/null +++ b/support/atari8bit/boot_xex_loader.h @@ -0,0 +1,18 @@ +// This loader is most probably originally from SDrive(Max). Should probably be reviewed, +// but then the best way to load XEX files on MiSTer is using the DMA loader anyways... + +const static uint8_t boot_xex_loader[384] = +{ + 0x72,0x02,0x5f,0x07,0xf8,0x07,0xa9,0x00,0x8d,0x04,0x03,0x8d,0x44,0x02,0xa9,0x07, + 0x8d,0x05,0x03,0xa9,0x70,0x8d,0x0a,0x03,0xa9,0x01,0x8d,0x0b,0x03,0x85,0x09,0x60, + 0x7d,0x8a,0x48,0x20,0x53,0xe4,0x88,0xd0,0xfa,0x68,0xaa,0x8c,0x8e,0x07,0xad,0x7d, + 0x07,0xee,0x8e,0x07,0x60,0xa9,0x93,0x8d,0xe2,0x02,0xa9,0x07,0x8d,0xe3,0x02,0xa2, + 0x02,0x20,0xda,0x07,0x95,0x43,0x20,0xda,0x07,0x95,0x44,0x35,0x43,0xc9,0xff,0xf0, + 0xf0,0xca,0xca,0x10,0xec,0x30,0x06,0xe6,0x45,0xd0,0x02,0xe6,0x46,0x20,0xda,0x07, + 0xa2,0x01,0x81,0x44,0xb5,0x45,0xd5,0x43,0xd0,0xed,0xca,0x10,0xf7,0x20,0xd2,0x07, + 0x4c,0x94,0x07,0xa9,0x03,0x8d,0x0f,0xd2,0x6c,0xe2,0x02,0xad,0x8e,0x07,0xcd,0x7f, + 0x07,0xd0,0xab,0xee,0x0a,0x03,0xd0,0x03,0xee,0x0b,0x03,0xad,0x7d,0x07,0x0d,0x7e, + 0x07,0xd0,0x8e,0x20,0xd2,0x07,0x6c,0xe0,0x02,0x20,0xda,0x07,0x8d,0xe0,0x02,0x20, + 0xda,0x07,0x8d,0xe1,0x02,0x2d,0xe0,0x02,0xc9,0xff,0xf0,0xed,0xa9,0x00,0x8d,0x8e, + 0x07,0xf0,0x82 +}; diff --git a/support/atari8bit/xex_loader.h b/support/atari8bit/xex_loader.h new file mode 100644 index 0000000..8a2f9de --- /dev/null +++ b/support/atari8bit/xex_loader.h @@ -0,0 +1,10 @@ +const static uint8_t xex_loader[] = +{ + 0x61,0xA2,0x00,0x86,0x09,0xCA,0x9A,0xCE,0x00,0xD1,0xCE,0x0E,0xD1,0xA9,0x01,0xF0, + 0xFC,0x30,0x09,0xA9,0xD1,0x48,0xA9,0x09,0x48,0x6C,0xE2,0x02,0xCE,0x00,0xD1,0x6C, + 0xE0,0x02 +}; + +#define XEX_READ_STATUS 0x0e +#define XEX_INIT1 0x01 + diff --git a/user_io.cpp b/user_io.cpp index 3ab151f..0934b19 100644 --- a/user_io.cpp +++ b/user_io.cpp @@ -317,6 +317,20 @@ char is_c128() return (is_c128_type == 1); } +static int is_atari800_type = 0; +char is_atari800() +{ + if (!is_atari800_type) is_atari800_type = strcasecmp(orig_name, "Atari800") ? 2 : 1; + return (is_atari800_type == 1); +} + +static int is_atari5200_type = 0; +char is_atari5200() +{ + if (!is_atari5200_type) is_atari5200_type = strcasecmp(orig_name, "Atari5200") ? 2 : 1; + return (is_atari5200_type == 1); +} + static int is_psx_type = 0; char is_psx() { @@ -398,6 +412,8 @@ void user_io_read_core_name() is_gba_type = 0; is_c64_type = 0; is_c128_type = 0; + is_atari800_type = 0; + is_atari5200_type = 0; is_psx_type = 0; is_cdi_type = 0; is_st_type = 0; @@ -1523,6 +1539,14 @@ void user_io_init(const char *path, const char *xml) printf("Identified Archimedes core"); archie_init(); } + else if (is_atari800()) + { + atari800_init(); + } + else if (is_atari5200()) + { + atari5200_init(); + } else { const char *home = HomeDir(); @@ -3667,6 +3691,8 @@ void user_io_poll() uint16_t save_req = spi_uio_cmd(UIO_CHK_UPLOAD); if (save_req) c64_save_cart(save_req >> 8); } + if (is_atari800()) atari800_poll(); + if (is_atari5200()) atari5200_poll(); process_ss(0); } diff --git a/user_io.h b/user_io.h index 5c31026..5b60aaf 100644 --- a/user_io.h +++ b/user_io.h @@ -289,6 +289,8 @@ char is_saturn(); char is_pcxt(); char is_n64(); char is_uneon(); +char is_atari800(); +char is_atari5200(); #define HomeDir(x) user_io_get_core_path(x) #define CoreName user_io_get_core_name()