/* * Copyright (c) 2013, Peter Rutenbar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "shoebill.h" #include "coff.h" #include "core_api.h" void shoebill_start() { pthread_mutex_unlock(&shoe.via_clock_thread_lock); pthread_mutex_unlock(&shoe.cpu_thread_lock); } void *_cpu_thread (void *arg) { pthread_mutex_lock(&shoe.cpu_thread_lock); while (1) { if (shoe.cpu_thread_notifications) { // If there's an interrupt pending if (shoe.cpu_thread_notifications & 0xff) { // process_pending_interrupt() may clear SHOEBILL_STATE_STOPPED process_pending_interrupt(); } if (shoe.cpu_thread_notifications & SHOEBILL_STATE_STOPPED) { continue; // FIXME: yield or block on a condition variable here } } cpu_step(); } } static void _cpu_loop_debug() { pthread_mutex_lock(&shoe.cpu_thread_lock); while (1) { if (shoe.cpu_thread_notifications) { // I think we can safely ignore "stop" instructions for A/UX in debug mode shoe.cpu_thread_notifications &= ~SHOEBILL_STATE_STOPPED; if (shoe.cpu_thread_notifications & 0xff) { process_pending_interrupt(); } if (shoe.cpu_thread_notifications & SHOEBILL_STATE_STOPPED) { continue; // FIXME: yield or block on a condition variable here } } cpu_step(); } } /*void shoebill_cpu_stepi (void) { if (shoe.cpu_mode != CPU_MODE_FREEZE) return ; shoe.cpu_mode = CPU_MODE_STEPI; pthread_mutex_unlock(&shoe.cpu_freeze_lock); // Just spin until the instruction completes - it should be quick while (shoe.cpu_mode != CPU_MODE_STEPI_COMPLETE) pthread_yield_np(); pthread_mutex_lock(&shoe.cpu_freeze_lock); shoe.cpu_mode = CPU_MODE_FREEZE; } void shoebill_cpu_freeze (void) { pthread_mutex_lock(&shoe.cpu_freeze_lock); shoe.cpu_mode = CPU_MODE_FREEZE; shoe.cpu_thread_notifications &= SHOEBILL_STATE_SWITCH_MODE; while (shoe.cpu_thread_notifications & SHOEBILL_STATE_SWITCH_MODE); pthread_yield_np(); }*/ /* * The A/UX bootloader blasts this structure into memory * somewhere before jumping into the kernel to commmunicate * stuff like: which scsi device/partition is root? */ struct __attribute__ ((__packed__)) kernel_info { // Auto data uint32_t auto_magic; uint32_t auto_id[16]; uint32_t auto_version[16]; uint32_t auto_command; uint16_t root_ctrl; uint8_t root_drive; uint8_t root_cluster; struct __attribute__ ((__packed__)) sect_info { uint32_t vstart; uint32_t pstart; uint32_t size; } si[3]; uint16_t machine_type; // Not the same as gestalt IDs! uint32_t drive_queue_offset; uint16_t ki_flags; uint8_t ki_version; // always 1 uint8_t root_partition; uint16_t swap_ctrl; uint8_t swap_drive; uint8_t swap_partition; // A series of DrvQEl (drive queue elements) follow this structure }; /* Inside Macintosh: Files 2-85 throughtfully provides this information * on the secret internal flags: * * The File Manager also maintains four flag bytes preceding each drive queue element. * These bytes contain the following information: * Byte (from low address to high address) * 0 Bit 7=1 if the volume on the drive is locked * 1 0 if no disk in drive; 1 or 2 if disk in drive; 8 if nonejectable disk in drive; $FC–$FF if * disk was ejected within last 1.5 seconds; $48 if disk in drive is nonejectable but driver * wants a call * 2 Used internally during system startup * 3 Bit 7=0 if disk is single-sided * You can read these flags by subtracting 4 bytes from the beginning of a drive queue element, * as illustrated in Listing 2-11. */ struct __attribute__ ((__packed__)) drive_queue_element { /* Secret internal flags */ uint8_t unknown_1 : 7; uint8_t is_locked : 1; // 1 if locked, 0 -> unlocked uint8_t is_in_drive; uint8_t unknown_2; uint8_t unknown_3 : 7; uint8_t double_sided : 1; // 0 if single-sided, presumably 1 if HDD or double-sided /* --- */ uint32_t next_offset; int16_t qType; // 1 -> both dQDrvSz and dQDrvSz2 are used int16_t dQDrive; // drive number (scsi target ID) int16_t dQRefNum; // Driver reference number (I'm thinking -33 is the right value here) int16_t dQFSID; // Filesystem identifier (0 is best, I think) uint16_t dQDrvSz; // low 16 bits of numblocks uint16_t dQDrvSz2; // high 16 bits of numblocks }; /* * Initialize the Macintosh lomem variables, or some of them anyways. * Debugging weird errors revealed that A/UX actually cares about * some of them. */ static void _init_macintosh_lomem_globals (const uint32_t offset) { /* * FIXME: explain what I'm doing here */ uint32_t i; // Fill the entire lomem space with 0xBB to make debugging easier // if somebody tries to read an uninitialized lomem value // for (i=0; i<0x4000; i++) // pset(offset + i, 1, 0xBB); // There are the lomem globals that matter, apparently pset(offset+0x12f, 1, 0x02); // CPUFlag = 0x02 (MC68020) pset(offset+0x31a, 4, 0x00ffffff); // Lo3Bytes (always 0x00ffffff) pset(offset+0x28e, 2, 0x3fff); // ROM85 (always 0x3fff, I think?) // universal info ptr. is allowed to be null on Mac II, (I THINK) pset(offset+0xdd8, 4, 0); pset(offset+0x1d4, 4, 0x50000000); // VIA (via1 ptr) pset(offset+0x1d8, 4, 0x50004000); // SCC pset(offset+0xb22, 2, 0xfc00); // HWCfgFlags } /* * Setup and blast the kernel_info structure into memory * before booting A/UX. * Side-effects: sets CPU registers d0 and a0 */ static void _init_kernel_info(shoebill_control_t *control, scsi_device_t *disks, uint32_t offset) { struct kernel_info ki, *p; uint32_t i, p_addr; p_addr = offset + 0x3c00; p = (struct kernel_info*) (uint64_t)p_addr; /* * On boot, the kernel looks for this magic constant in d0 to see whether * the bootloader setup a kernel_info structure. */ shoe.d[0] = 0x536d7201; // "Smr\1"? Something version 1? shoe.a[0] = (uint32_t)p; // Address of the kernel_info structure /* ----- Setup kernel info structure ------ */ ki.auto_magic = 0x50696773; // 'Pigs' (Pigs in space?) for (i=0; i<16; i++) { ki.auto_id[i] = 0x0000ffff; ki.auto_version[i] = 0; } // FIXME: I need to stick the auto_id for each nubus card in here // ki.auto_id[0xb] = 0x5; // Macintosh II video card has an auto_id of 5 (I guess?) ki.auto_command = control->aux_autoconfig; // AUTO_NONE/AUTO_CONFIG /* * Note: ctrl -> SCSI controller chip * drive -> SCSI Target ID * partition -> partition * cluster -> A set of partitions on a given drive * (Used by escher/eschatology somehow) */ ki.root_ctrl = control->root_ctrl; ki.swap_ctrl = control->swap_ctrl; ki.root_drive = control->root_drive; ki.swap_drive = control->swap_drive; ki.root_partition = control->root_partition; ki.swap_partition = control->swap_partition; ki.root_cluster = control->root_cluster; // Find the text, data, and bss segments in the kernel for (i = 0; i < shoe.coff->num_sections; i++) { coff_section *s = &shoe.coff->sections[i]; uint8_t sect; if (strcmp(s->name, ".text") == 0) sect = 0; else if (strcmp(s->name, ".data") == 0) sect = 1; else if (strcmp(s->name, ".bss") == 0) sect = 2; else continue; ki.si[sect].vstart = s->v_addr; ki.si[sect].pstart = s->p_addr; ki.si[sect].size = s->sz; } ki.machine_type = 4; // Macintosh II? // +4 because the DrvQEl structure has a hidden "flags" field 4 bytes below the pointer ki.drive_queue_offset = sizeof(struct kernel_info) + 4; ki.ki_flags = control->aux_verbose; ki.ki_version = 1; /* ----- Copy ki into memory ----- */ #define ki_pset(_f) pset((uint32_t)&p->_f, sizeof(p->_f), ki._f) ki_pset(auto_magic); for (i=0; i<16; i++) { ki_pset(auto_id[i]); ki_pset(auto_version[i]); } ki_pset(auto_command); ki_pset(root_ctrl); ki_pset(root_drive); ki_pset(root_cluster); for (i=0; i<3; i++) { ki_pset(si[i].vstart); ki_pset(si[i].pstart); ki_pset(si[i].size); } ki_pset(machine_type); ki_pset(drive_queue_offset); ki_pset(ki_flags); ki_pset(ki_version); ki_pset(root_partition); ki_pset(swap_ctrl); ki_pset(swap_drive); ki_pset(swap_partition); /* ---- Copy DrvQEl elements into memory ---- */ // FIXME: btw, this is probably wrong. DrvQEl elements are supposed to be partitions, I think uint32_t addr = p_addr + sizeof(struct kernel_info); uint32_t num_disks = 0; for (i=0; i<7; i++) if (disks[i].f) num_disks++; for (i=0; i<7; i++) { if (disks[i].f == NULL) continue; num_disks--; pset(addr, 4, 0x00080000); addr += 4; // magic internal flags (non-ejectable) pset(addr, 4, num_disks ? 0x14 : 0); addr += 4; // offset to next drvqel pset(addr, 2, 1); addr += 2; // Use both dQDrvSzs pset(addr, 2, i | 8); addr += 2; // SCSI ID (with bit 3 always set?) // pset(addr, 2, 0xffdf); addr += 2; // dQRefNum: not sure if this is right pset(addr, 2, (i | 0x20) ^ 0xffff); addr += 2; // dQRefNum: not sure if this is right pset(addr, 2, 0); addr += 2; // FSID pset(addr, 2, disks[i].num_blocks & 0xffff); addr += 2; // low 16 bits of num_blocks pset(addr, 2, disks[i].num_blocks >> 16); addr += 2; // high bits } } /* * Compute the checksum for a Macintosh ROM file */ static uint32_t _compute_rom_checksum (const uint8_t *rom, const uint32_t len) { uint32_t i, checksum; for (i=4, checksum=0; i < len; i+=2) { const uint16_t word = (rom[i]<<8) + rom[i+1]; checksum += word; } return checksum; } static uint32_t _load_rom (shoebill_control_t *control, uint8_t **_rom_data, uint32_t *_rom_size) { uint32_t i, rom_size; uint8_t *rom_data = (uint8_t*)p_alloc(shoe.pool, 64 * 1024); FILE *f = fopen(control->rom_path, "r"); if (f == NULL) { sprintf(control->error_msg, "Couldn't open rom path [%s]\n", control->rom_path); goto fail; } for (rom_size = 0; rom_size < (2*1024*1024); rom_size += (64*1024)) { rom_data = (uint8_t*)p_realloc(rom_data, rom_size + (64*1024)); if (fread(rom_data + rom_size, 64*1024, 1, f) != 1) break; } // Rom_size had better be a power of two if ((rom_size & (rom_size - 1)) != 0) { sprintf(control->error_msg, "Rom is probably corrupt (size not a power of two %u)\n", rom_size); goto fail; } // Check the checksum const uint32_t computed_checksum = _compute_rom_checksum(rom_data, rom_size); const uint32_t purported_checksum = ntohl(*(uint32_t*)rom_data); if (computed_checksum != purported_checksum) { sprintf(control->error_msg, "Rom checksum doesn't match (computed=0x%08x, expected=0x%08x)\n", computed_checksum, purported_checksum); goto fail; } // rom_data = p_realloc(rom_data, rom_size); assert(rom_data); *_rom_size = rom_size; *_rom_data = rom_data; fclose(f); return 1; fail: if (rom_data) p_free(rom_data); if (f) fclose(f); return 0; } static uint32_t _open_disk_images (shoebill_control_t *control, scsi_device_t *disks) { uint32_t i; for (i=0; i<8; i++) { shoe.scsi_devices[i].scsi_id = i; shoe.scsi_devices[i].block_size = 0; shoe.scsi_devices[i].num_blocks = 0; shoe.scsi_devices[i].image_path = "dummy"; shoe.scsi_devices[i].f = NULL; } for (i=0; i<7; i++) { struct stat stat_buf; const char *path = control->scsi_devices[i].path; if (!path) continue; FILE *f = fopen(path, "r+"); if (f == NULL) { sprintf(control->error_msg, "Couldn't open scsi id #%u disk [%s]\n", i, path); goto fail; } disks[i].scsi_id = i; disks[i].f = f; disks[i].image_path = path; if (fstat(fileno(f), &stat_buf)) { sprintf(control->error_msg, "Couldn't fstat() scsi id #%u disk [%s]\n", i, path); goto fail; } else if (stat_buf.st_size % 512) { sprintf(control->error_msg, "Not aligned to 512 byte blocks: [%s]\n", path); goto fail; } disks[i].block_size = 512; disks[i].num_blocks = stat_buf.st_size / 512; } return 1; fail: for (i=0; i<7; i++) if (disks[i].f) fclose(disks[i].f); memset(disks, 0, 7 * sizeof(scsi_device_t)); return 0; } static uint32_t _load_aux_kernel(shoebill_control_t *control, coff_file *coff, uint32_t *_pc) { uint32_t j, i, pc = 0xffffffff; for (i = 0; i < coff->num_sections; i++) { coff_section *s = &coff->sections[i]; // Don't load a "copy" segment if (s->flags & coff_copy) continue; if ((s->flags & coff_text) || (s->flags & coff_data)) { /* copy text or data section */ for (j = 0; j < s->sz; j++) pset(s->p_addr+j, 1, s->data[j]); if (strcmp(s->name, "pstart") == 0) pc = s->p_addr; } else if (s->flags & coff_bss) { /* Create an empty .bss segment */ for (j = 0; j < s->sz; j++) pset(s->p_addr+j, 1, 0); } } if (pc == 0xffffffff) { sprintf(control->error_msg, "This unix kernel doesn't contain a pstart segment\n"); return 0; } *_pc = pc; // Entry point to the kernel return 1; fail: return 0; } uint32_t shoebill_install_video_card(shoebill_control_t *control, uint8_t slotnum, uint16_t width, uint16_t height, double refresh_rate) { shoebill_card_video_t *ctx = &control->slots[slotnum].card.video; if (control->slots[slotnum].card_type != card_none) { sprintf(control->error_msg, "This slot (%u) already has a card\n", slotnum); return 0; } // Make sure the scanline width is a multiple of 32 pixels, and is at least 32 pixels // beyond the end of the display. If scanline_width==width, A/UX 2.0 will wrap the mouse around // the edge of the screen. uint32_t scanline_width = width + (32 - (width % 32)) + 32; scanline_width = width; // FIXME: undo this shoe.slots[slotnum].connected = 1; shoe.slots[slotnum].read_func = nubus_video_read_func; shoe.slots[slotnum].write_func = nubus_video_write_func; shoe.slots[slotnum].interrupt_rate = refresh_rate; shoe.slots[slotnum].last_fired = 0; shoe.slots[slotnum].interrupts_enabled = 1; nubus_video_init(ctx, slotnum, width, height, scanline_width, refresh_rate); return 1; } /* * Given a config_control_t structure, configure and initialize * the emulator. * This is the first function you should call if you're writing an * interface to Shoebill. */ uint32_t shoebill_initialize(shoebill_control_t *control) { uint32_t i, j, pc = 0xffffffff; coff_file *coff = NULL; scsi_device_t disks[8]; uint8_t *rom_data = NULL, *kernel_data = NULL; uint32_t rom_size = 0, kernel_size; memset(&disks[0], 0, 8 * sizeof(scsi_device_t)); memset(&shoe, 0, sizeof(global_shoebill_context_t)); shoe.pool = p_new_pool(); fpu_setup_jump_table(); // Try to load the ROM if (control->rom_path == NULL) { sprintf(control->error_msg, "No rom file specified\n"); goto fail; } else if (!_load_rom(control, &rom_data, &rom_size)) goto fail; // Try to load the A/UX kernel if (control->aux_kernel_path == NULL) { sprintf(control->error_msg, "No A/UX kernel specified\n"); goto fail; } else if (!control->scsi_devices[0].path || strlen((char*)control->scsi_devices[0].path)==0) { sprintf(control->error_msg, "The root A/UX disk needs to be at scsi ID 0\n"); goto fail; } // Load the kernel from the disk at scsi id #0 kernel_data = shoebill_extract_kernel((char*)control->scsi_devices[0].path, control->aux_kernel_path, control->error_msg, &kernel_size); if (!kernel_data) goto fail; coff = coff_parse(kernel_data, kernel_size); free(kernel_data); // kernel_data was allocated with malloc() if (coff == NULL) { sprintf(control->error_msg, "Can't open that A/UX kernel [%s]\n", control->aux_kernel_path); goto fail; } shoe.coff = coff; // Try to open the disk images if (!_open_disk_images(control, disks)) goto fail; // Allocate and configure the rom and memory space if (control->ram_size < (1024*1024)) { sprintf(control->error_msg, "%u bytes is too little ram\n", control->ram_size); goto fail; } shoe.physical_rom_size = rom_size; shoe.physical_rom_base = p_alloc(shoe.pool, rom_size+8); // +8 because of physical_get hack memcpy(shoe.physical_rom_base, rom_data, rom_size); p_free(rom_data); rom_data = NULL; shoe.physical_mem_size = control->ram_size; shoe.physical_mem_base = p_alloc(shoe.pool, control->ram_size+8); // +8 because of physical_get hack memset(shoe.physical_mem_base, 0, shoe.physical_mem_size); // Initialize Macintosh lomem variables that A/UX actually cares about #define AUX_LOMEM_OFFSET 0x50000 _init_macintosh_lomem_globals(AUX_LOMEM_OFFSET); // Initialize A/UX's kernel_info structure _init_kernel_info(control, disks, AUX_LOMEM_OFFSET); // Load A/UX kernel COFF segments into memory (returns PC, the entry point into the kernel) if (!_load_aux_kernel(control, coff, &pc)) goto fail; /* * Load it all into the internal global shoebill state * (Can't fail after this point) */ /* * FIXME: to implement clean resetting, everything with a global structure needs * an initialization function. Starting here with via/pram... */ init_via_state(); // Put the adb chip in state 3 (idle) // FIXME: put this in a "init_adb_state()"-type function shoe.adb.state = 3; pthread_mutex_init(&shoe.adb.lock, NULL); set_sr(0x2000); shoe.pc = pc; memcpy(shoe.scsi_devices, disks, 8 * sizeof(scsi_device_t)); pthread_mutex_init(&shoe.via_clock_thread_lock, NULL); pthread_mutex_lock(&shoe.via_clock_thread_lock); pthread_create(&control->via_thread_pid, NULL, via_clock_thread, NULL); /* * control->debug_mode is a hack - the debugger implements its own CPU thread */ pthread_mutex_init(&shoe.cpu_thread_lock, NULL); pthread_mutex_lock(&shoe.cpu_thread_lock); if (!control->debug_mode) pthread_create(&control->cpu_thread_pid, NULL, _cpu_thread, NULL); return 1; fail: if (rom_data) p_free(rom_data); for (i=0; i<7; i++) if (disks[i].f) fclose(disks[i].f); if (shoe.physical_rom_base) p_free(shoe.physical_rom_base); if (shoe.physical_mem_base) p_free(shoe.physical_mem_base); p_free_pool(shoe.pool); memset(&shoe, 0, sizeof(global_shoebill_context_t)); return 0; } static void _send_key(uint8_t code) { if ((shoe.key.key_i+1) < KEYBOARD_STATE_MAX_KEYS) { shoe.key.keys[shoe.key.key_i].code_a = code; shoe.key.keys[shoe.key.key_i].code_b = 0xff; shoe.key.key_i++; } } void shoebill_key(uint8_t down, uint8_t key) { const uint8_t down_mask = down ? 0 : 0x80; assert(pthread_mutex_lock(&shoe.adb.lock) == 0); _send_key(key | down_mask); adb_request_service_request(3); pthread_mutex_unlock(&shoe.adb.lock); } void shoebill_key_modifier(uint8_t modifier_mask) { assert(pthread_mutex_lock(&shoe.adb.lock) == 0); const uint8_t changed_mask = shoe.key.last_modifier_mask ^ modifier_mask; shoe.key.last_modifier_mask = modifier_mask; if (changed_mask & modShift) { _send_key(((modifier_mask & modShift) ? 0 : 0x80) | 0x38); } if (changed_mask & modControl) { _send_key(((modifier_mask & modControl) ? 0 : 0x80) | 0x36); } if (changed_mask & modOption) { _send_key(((modifier_mask & modOption) ? 0 : 0x80) | 0x3a); } if (changed_mask & modCommand) { _send_key(((modifier_mask & modCommand) ? 0 : 0x80) | 0x37); } adb_request_service_request(3); pthread_mutex_unlock(&shoe.adb.lock); } void shoebill_mouse_move(int32_t x, int32_t y) { assert(pthread_mutex_lock(&shoe.adb.lock) == 0); int32_t delta_x = x - shoe.mouse.old_x; int32_t delta_y = y - shoe.mouse.old_y; shoe.mouse.old_x = x; shoe.mouse.old_y = y; shoe.mouse.delta_x += delta_x; shoe.mouse.delta_y += delta_y; shoe.mouse.changed = 1; adb_request_service_request(3); pthread_mutex_unlock(&shoe.adb.lock); } void shoebill_mouse_move_delta (int32_t x, int32_t y) { assert(pthread_mutex_lock(&shoe.adb.lock) == 0); shoe.mouse.delta_x += x; shoe.mouse.delta_y += y; shoe.mouse.changed = 1; adb_request_service_request(3); pthread_mutex_unlock(&shoe.adb.lock); } void shoebill_mouse_click(uint8_t down) { assert(pthread_mutex_lock(&shoe.adb.lock) == 0); shoe.mouse.button_down = (down != 0); shoe.mouse.changed = 1; adb_request_service_request(3); pthread_mutex_unlock(&shoe.adb.lock); }