#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 "st_tos.h" #define CONFIG_FILENAME "ATARIST0.CFG" const char* tos_mem[] = { "512kb", "1mb", "2mb", "4mb", "8mb", "14mb", "--", "--" }; const char* tos_scanlines[] = { "Off","25%","50%","75%" }; const char* tos_stereo[] = { "Mono","Stereo" }; const char* tos_chipset[] = { "ST","STE","MegaSTE","STEroids" }; const char* tos_chipset_short[] = { "ST","STe","MST","ST+" }; typedef struct { unsigned long system_ctrl; // system control word char tos_img[1024]; char cart_img[1024]; char acsi_img[2][1024]; char video_adjust[2]; char cdc_control_redirect; } tos_config_t; static tos_config_t config; fileTYPE hdd_image[2] = {}; static unsigned char dma_buffer[512]; static const char *acsi_cmd_name(int cmd) { static const char *cmdname[] = { "Test Drive Ready", "Restore to Zero", "Cmd $2", "Request Sense", "Format Drive", "Read Block limits", "Reassign Blocks", "Cmd $7", "Read Sector", "Cmd $9", "Write Sector", "Seek Block", "Cmd $C", "Cmd $D", "Cmd $E", "Cmd $F", "Cmd $10", "Cmd $11", "Inquiry", "Verify", "Cmd $14", "Mode Select", "Cmd $16", "Cmd $17", "Cmd $18", "Cmd $19", "Mode Sense", "Start/Stop Unit", "Cmd $1C", "Cmd $1D", "Cmd $1E", "Cmd $1F", // extended commands supported by ICD feature: "Cmd $20", "Cmd $21", "Cmd $22", "Read Format Capacities", "Cmd $24", "Read Capacity (10)", "Cmd $26", "Cmd $27", "Read (10)", "Read Generation", "Write (10)", "Seek (10)" }; if (cmd > 0x2b) return NULL; return cmdname[cmd]; } static int uart_mode = 0; static void set_control(uint32_t ctrl) { ctrl = uart_mode ? (ctrl | TOS_CONTROL_REDIR0) : (ctrl & ~TOS_CONTROL_REDIR0); spi_uio_cmd_cont(UIO_SET_STATUS2); spi32w(ctrl); DisableIO(); } void tos_update_sysctrl(uint32_t ctrl) { config.system_ctrl = ctrl; set_control(config.system_ctrl); } unsigned long tos_system_ctrl() { return config.system_ctrl; } void tos_uart_mode(int enable) { uart_mode = enable; set_control(config.system_ctrl); } static void memory_read(uint8_t *data, uint32_t words) { EnableIO(); spi8(ST_READ_MEMORY); // transmitted bytes must be multiple of 2 (-> words) uint16_t *buf = (uint16_t*)data; while (words--) *buf++ = spi_w(0); DisableIO(); } static void memory_write(uint8_t *data, uint32_t words) { EnableIO(); spi8(ST_WRITE_MEMORY); uint16_t *buf = (uint16_t*)data; while (words--) spi_w(*buf++); DisableIO(); } static void dma_ack(uint8_t status) { EnableIO(); spi8(ST_ACK_DMA); spi8(status); DisableIO(); } static void dma_nak() { EnableIO(); spi8(ST_NAK_DMA); DisableIO(); } static void handle_acsi(unsigned char *buffer) { static uint8_t buf[65536]; static uint8_t asc[2] = { 0,0 }; uint8_t target = buffer[10] >> 5; uint8_t device = buffer[1] >> 5; uint8_t cmd = buffer[0]; uint32_t lba = 256 * 256 * (buffer[1] & 0x1f) + 256 * buffer[2] + buffer[3]; uint32_t length = buffer[4]; if (length == 0) length = 256; if (0) { tos_debugf("ACSI: target %d.%d, \"%s\" (%02x)", target, device, acsi_cmd_name(cmd), cmd); tos_debugf("ACSI: lba %u (%x), length %u", lba, lba, length); } // only a harddisk on ACSI 0/1 is supported // ACSI 0/1 is only supported if a image is loaded if (((target < 2) && (hdd_image[target].size != 0))) { uint32_t blocks = hdd_image[target].size / 512; // only lun0 is fully supported switch (cmd) { case 0x25: if (device == 0) { bzero(dma_buffer, 512); dma_buffer[0] = (blocks - 1) >> 24; dma_buffer[1] = (blocks - 1) >> 16; dma_buffer[2] = (blocks - 1) >> 8; dma_buffer[3] = (blocks - 1) >> 0; dma_buffer[6] = 2; // 512 bytes per block memory_write(dma_buffer, 4); dma_ack(0x00); asc[target] = 0x00; } else { dma_ack(0x02); asc[target] = 0x25; } break; case 0x00: // test drive ready case 0x04: // format if (device == 0) { asc[target] = 0x00; dma_ack(0x00); } else { asc[target] = 0x25; dma_ack(0x02); } break; case 0x03: // request sense if (device != 0) asc[target] = 0x25; bzero(dma_buffer, 512); dma_buffer[7] = 0x0b; if (asc[target] != 0) { dma_buffer[2] = 0x05; dma_buffer[12] = asc[target]; } memory_write(dma_buffer, 9); // 18 bytes dma_ack(0x00); asc[target] = 0x00; break; case 0x08: // read sector case 0x28: // read (10) if (device == 0) { if (cmd == 0x28) { lba = 256 * 256 * 256 * buffer[2] + 256 * 256 * buffer[3] + 256 * buffer[4] + buffer[5]; length = 256 * buffer[7] + buffer[8]; // iprintf("READ(10) %d, %d\n", lba, length); } if (lba + length <= blocks) { DISKLED_ON; FileSeekLBA(&hdd_image[target], lba); while (length) { uint32_t len = length; if (len > 128) len = 128; length -= len; len *= 512; FileReadAdv(&hdd_image[target], buf, len); memory_write(buf, len / 2); } DISKLED_OFF; dma_ack(0x00); asc[target] = 0x00; } else { tos_debugf("ACSI: read (%u+%d) exceeds device limits (%u)", lba, length, blocks); dma_ack(0x02); asc[target] = 0x21; } } else { dma_ack(0x02); asc[target] = 0x25; } break; case 0x0a: // write sector case 0x2a: // write (10) if (device == 0) { if (cmd == 0x2a) { lba = 256 * 256 * 256 * buffer[2] + 256 * 256 * buffer[3] + 256 * buffer[4] + buffer[5]; length = 256 * buffer[7] + buffer[8]; // iprintf("WRITE(10) %d, %d\n", lba, length); } if (lba + length <= blocks) { DISKLED_ON; FileSeekLBA(&hdd_image[target], lba); while (length) { uint32_t len = length; if (len > 128) len = 128; length -= len; len *= 512; memory_read(buf, len / 2); FileWriteAdv(&hdd_image[target], buf, len); } DISKLED_OFF; dma_ack(0x00); asc[target] = 0x00; } else { tos_debugf("ACSI: write (%u+%d) exceeds device limits (%u)", lba, length, blocks); dma_ack(0x02); asc[target] = 0x21; } } else { dma_ack(0x02); asc[target] = 0x25; } break; case 0x12: // inquiry tos_debugf("ACSI: Inquiry %.11s", hdd_image[target].name); bzero(dma_buffer, 512); dma_buffer[2] = 2; // SCSI-2 dma_buffer[4] = length - 5; // len memcpy(dma_buffer + 8, "MISTer ", 8); // Vendor memcpy(dma_buffer + 16, " ", 16); // Clear device entry memcpy(dma_buffer + 16, hdd_image[target].name, 11); // Device memcpy(dma_buffer + 32, "ATH ", 4); // Product revision memcpy(dma_buffer + 36, VDATE " ", 8); // Serial number if (device != 0) dma_buffer[0] = 0x7f; memory_write(dma_buffer, length / 2); dma_ack(0x00); asc[target] = 0x00; break; case 0x1a: // mode sense if (device == 0) { tos_debugf("ACSI: mode sense, blocks = %u", blocks); bzero(dma_buffer, 512); dma_buffer[3] = 8; // size of extent descriptor list dma_buffer[5] = blocks >> 16; dma_buffer[6] = blocks >> 8; dma_buffer[7] = blocks; dma_buffer[10] = 2; // byte 1 of block size in bytes (512) memory_write(dma_buffer, length / 2); dma_ack(0x00); asc[target] = 0x00; } else { asc[target] = 0x25; dma_ack(0x02); } break; #if 0 case 0x1f: // ICD command? tos_debugf("ACSI: ICD command %s ($%02x)", acsi_cmd_name(buffer[1] & 0x1f), buffer[1] & 0x1f); asc[target] = 0x05; dma_ack(0x02); break; #endif default: tos_debugf("ACSI: >>>>>>>>>>>> Unsupported command <<<<<<<<<<<<<<<<"); asc[target] = 0x20; dma_ack(0x02); break; } } else { tos_debugf("ACSI: Request for unsupported target"); // tell acsi state machine that io controller is done // but don't generate a acsi irq dma_nak(); } } static void get_dmastate() { uint8_t buffer[16]; EnableIO(); spi8(ST_GET_DMASTATE); spi_read(buffer, 16, 0); DisableIO(); if (buffer[10] & 0x01) handle_acsi(buffer); } static void fill_tx(uint16_t fill, uint32_t len, int index) { user_io_set_index(index); user_io_set_download(1); len /= 2; EnableFpga(); spi8(UIO_FILE_TX_DAT); while(len--) spi_w(fill); DisableFpga(); user_io_set_download(0); } void tos_load_cartridge(const char *name) { if (name) strncpy(config.cart_img, name, 11); // erase that ram area to remove any previously uploaded // image tos_debugf("Erasing cart memory"); fill_tx(0xff, 128 * 1024, 2); // upload cartridge if (config.cart_img[0] && FileExists(config.cart_img)) { user_io_file_tx(config.cart_img, 2); tos_debugf("%s uploaded", config.cart_img); return; } } char tos_cartridge_is_inserted() { return config.cart_img[0]; } void tos_poll() { static unsigned long timer = 0; get_dmastate(); // check the user button if (!user_io_osd_is_visible() && user_io_user_button()) { if (!timer) timer = GetTimer(1000); else if (timer != 1) { if (CheckTimer(timer)) { tos_reset(1); timer = 1; } } } else { timer = 0; } } const char *tos_get_disk_name(int index) { const char *name = 0; if(index <= 1) name = get_image_name(index); else { if (!hdd_image[index & 1].size) name = 0; else { name = strrchr(hdd_image[index & 1].name, '/'); if (!name) name = hdd_image[index & 1].name; else name++; } } return name ? name : "* no disk *"; } const char *tos_get_image_name() { char *p = strrchr(config.tos_img, '/'); return p ? p+1 : config.tos_img; } const char *tos_get_cartridge_name() { if (!config.cart_img[0]) return "* no cartridge *"; char *p = strrchr(config.cart_img, '/'); return p ? p + 1 : config.cart_img; } char tos_disk_is_inserted(int index) { if (index <= 1) return (get_image_name(index) != NULL); return hdd_image[index & 1].size != 0; } static void tos_select_hdd_image(int i, const char *name) { tos_debugf("Select ACSI%c image %s", '0' + i, name); strcpy(config.acsi_img[i], name); if (!strlen(name)) { FileClose(&hdd_image[i]); hdd_image[i].size = 0; config.system_ctrl &= ~(TOS_ACSI0_ENABLE << i); } else { if (FileOpenEx(&hdd_image[i], name, (O_RDWR | O_SYNC))) { config.system_ctrl |= (TOS_ACSI0_ENABLE << i); } } // update system control set_control(config.system_ctrl); } void tos_insert_disk(int index, const char *name) { static int wpins = 0; if (index <= 1) { user_io_file_mount(name, index); if (tos_disk_is_inserted(index)) { if (!index) wpins &= ~TOS_CONTROL_FDC_WR_PROT_A; else wpins &= ~TOS_CONTROL_FDC_WR_PROT_B; } else { if (!index) wpins |= TOS_CONTROL_FDC_WR_PROT_A; else wpins |= TOS_CONTROL_FDC_WR_PROT_B; } set_control(config.system_ctrl ^ (wpins & (TOS_CONTROL_FDC_WR_PROT_A | TOS_CONTROL_FDC_WR_PROT_B))); } else tos_select_hdd_image(index & 1, name); } // force ejection of all disks (SD card has been removed) void tos_eject_all() { for (int i = 0; i < 4; i++) tos_insert_disk(i, ""); } void tos_reset(char cold) { tos_update_sysctrl(config.system_ctrl | TOS_CONTROL_CPU_RESET); // set reset if (cold) { // clear first 16k tos_debugf("Clear first 16k"); fill_tx(0, 16 * 1024, 3); // upload and verify tos image int len = FileLoad(config.tos_img, 0, 0); if (len) { tos_debugf("TOS.IMG:\n size = %d", len); if (len >= 256 * 1024) user_io_file_tx(config.tos_img, 0); else if (len == 192 * 1024) user_io_file_tx(config.tos_img, 1); else tos_debugf("WARNING: Unexpected TOS size!"); } else { tos_debugf("Unable to find tos.img"); return; } tos_load_cartridge(NULL); for (int i = 0; i < 2; i++) { if (FileExists(config.acsi_img[i])) { tos_select_hdd_image(i, config.acsi_img[i]); } } } tos_update_sysctrl(config.system_ctrl & ~TOS_CONTROL_CPU_RESET); // release reset } void tos_upload(const char *name) { if(name) strcpy(config.tos_img, name); tos_reset(1); } // load/init configuration void tos_config_load(int slot) { char name[64] = { CONFIG_FILENAME }; static char last_slot = 0; char new_slot; tos_eject_all(); new_slot = (slot == -1) ? last_slot : slot; memset(&config, 0, sizeof(config)); // set default values config.system_ctrl = TOS_MEMCONFIG_1M | TOS_CONTROL_VIDEO_COLOR | TOS_CONTROL_BORDER; strcpy(config.tos_img, user_io_get_core_path()); strcat(config.tos_img, "/TOS.IMG"); // try to load config name[7] = '0' + new_slot; int len = FileLoadConfig(name, 0, 0); tos_debugf("Configuration file size: %d (should be %d)", len, sizeof(tos_config_t)); if (len == sizeof(tos_config_t)) FileLoadConfig(name, &config, sizeof(tos_config_t)); // ethernet is auto detected later config.system_ctrl &= ~TOS_CONTROL_ETHERNET; } // save configuration void tos_config_save(int slot) { char name[64] = { CONFIG_FILENAME }; name[7] = '0' + slot; FileSaveConfig(name, &config, sizeof(config)); } // configuration file check int tos_config_exists(int slot) { char name[64] = { CONFIG_FILENAME }; name[7] = '0' + slot; return FileLoadConfig(name, 0, 0); } const char* tos_get_cfg_string(int num) { static char str[256]; char name[64] = { CONFIG_FILENAME }; name[7] = '0' + num; static tos_config_t tmp; memset(&tmp, 0, sizeof(tmp)); int len = FileLoadConfig(name, 0, 0); if (len == sizeof(tos_config_t)) { FileLoadConfig(name, &tmp, sizeof(tos_config_t)); memset(str, 0, sizeof(str)); strcat(str, tos_mem[(tmp.system_ctrl >> 1) & 7]); strcat(str, " "); if (tmp.acsi_img[0][0]) strcat(str, "H0 "); if (tmp.acsi_img[1][0]) strcat(str, "H1 "); if (tmp.cart_img[0]) strcat(str, "CR "); strcat(str, tos_chipset_short[(tmp.system_ctrl >> 23) & 3]); strcat(str, " "); if (!((tmp.system_ctrl >> 23) & 3) && (tmp.system_ctrl & TOS_CONTROL_BLITTER)) strcat(str, "B "); if (tmp.system_ctrl & TOS_CONTROL_VIKING) strcat(str, "V "); if (tmp.system_ctrl & TOS_CONTROL_VIDEO_AR) strcat(str, "W "); strcat(str, (tmp.system_ctrl & TOS_CONTROL_VIDEO_COLOR) ? "C " : (tmp.system_ctrl & TOS_CONTROL_MDE60) ? "M6 " : "M "); if (!(tmp.system_ctrl & TOS_CONTROL_BORDER)) strcat(str, "F "); int sl = (tmp.system_ctrl >> 20) & 3; if (sl) sprintf(str + strlen(str), "S%d ", sl); strcat(str, (tmp.system_ctrl & TOS_CONTROL_STEREO) ? "AS " : "AM "); if (tmp.tos_img[0]) { char *p = strrchr(tmp.tos_img, '/'); if (!p) p = tmp.tos_img; else p++; int len = strlen(p); if (len >= 4) len -= 4; memcpy(str + strlen(str), p, len); } return str; } return "< empty slot >"; }