// AVR-libc documentation: // http://www.nongnu.org/avr-libc/user-manual/modules.html // AVR assembly instructions: // https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_instruction_list.html // ATmega datasheet: // http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega328_P%20AVR%20MCU%20with%20picoPower%20Technology%20Data%20Sheet%2040001984A.pdf /* * FIXME: The current hardware design is questionable. * * The leds. Why does the power (MOSFET) led even have a mosfet to begin * with? It doesn't seem to be doing much. * Furthermore, why aren't the RD and WR leds inverted? Both of these pins * on the cart are active low, not high. */ #include #include #include #include #include "utils.h" #include "pins.h" #include "serial.h" #include "cart.h" #include "proto.h" void proto_send_u32(const uint32_t val) { char *cval = (char *)&val; serial_putchar(cval[0]); serial_putchar(cval[1]); serial_putchar(cval[2]); serial_putchar(cval[3]); } uint32_t proto_read_u32(void) { uint32_t val; char *cval = (char *)&val; cval[0] = serial_getchar(); cval[1] = serial_getchar(); cval[2] = serial_getchar(); cval[3] = serial_getchar(); return val; } struct proto_client_packet proto_read_packet(void) { struct proto_client_packet packet; packet.command = serial_getchar(); packet.address = proto_read_u32(); packet.size = proto_read_u32(); return packet; } void client_read_raw(uint32_t address, uint32_t size) { if (address > (uint32_t)0xFFFF || address + size > (uint32_t)0xFFFF) { serial_putchar(PROTO_READER_FAIL); return; } serial_putchar(PROTO_READER_OK); cart_set_read(); if (address & 0x8000) cart_select_ram(); for (unsigned i = address; i < address + size; i++) { serial_putchar_inline(cart_read(i)); } cart_select_rom(); } void client_write_raw(uint32_t address, uint32_t size) { if (address > (uint32_t)0xFFFF || address + size > (uint32_t)0xFFFF) { serial_putchar(PROTO_READER_FAIL); return; } serial_putchar(PROTO_READER_OK); cart_set_write(); if (address & 0x8000) cart_select_ram(); for (unsigned i = address; i < address + size; i++) { cart_write(i, serial_getchar_inline()); } cart_select_rom(); } void client_read_rom(struct cart_info *cart, uint32_t address, uint32_t size) { // 0x1FF = max bank (MBC5, 9 bits) if (address > (uint32_t)0x4000 * 0x200 || address + size > (uint32_t)0x4000 * 0x200) { serial_putchar(PROTO_READER_FAIL); return; } unsigned bank = address / (uint32_t)0x4000; unsigned bank_end = (address + size - 1) / (uint32_t)0x4000; unsigned offset = 0; // Offset for the first bank we read, which can be bank 0. if (bank) { cart_rom_bank(cart, bank); // Any bank other than 0 starts at 0x4000. offset = 0x4000; } // If we're not reading cross-bank, just do one read. if (bank == bank_end) return client_read_raw(offset + address % 0x4000, size); serial_putchar(PROTO_READER_OK); cart_set_read(); // Read the current bank until the end for (unsigned i = offset + address % 0x4000; i < offset + 0x4000; i++) { serial_putchar_inline(cart_read(i)); } // Read all the entire banks for (unsigned i = bank + 1; i < bank_end; i++) { cart_rom_bank(cart, i); cart_set_read(); for (unsigned i = 0x4000; i < (unsigned)0x4000 + 0x4000; i++) { serial_putchar_inline(cart_read(i)); } } // Read the final bank until the end cart_rom_bank(cart, bank_end); cart_set_read(); for (unsigned i = 0x4000; i <= 0x4000 + (size - 1) % 0x4000; i++) { serial_putchar_inline(cart_read(i)); } } void client_read_ram(struct cart_info *cart, uint32_t address, uint32_t size) { // 0xF = max bank (MBC5, 4 bits) if (address > (uint32_t)0x2000 * 0x10 || address + size > (uint32_t)0x2000 * 0x10) { serial_putchar(PROTO_READER_FAIL); return; } unsigned bank = address / (uint32_t)0x2000; unsigned bank_end = (address + size - 1) / (uint32_t)0x2000; cart_ram_enable(cart); cart_ram_bank(cart, bank); // If we're not reading cross-bank, just do one read. if (bank == bank_end) return client_read_raw(0xA000 + address % 0x2000, size); serial_putchar(PROTO_READER_OK); cart_set_read(); // Read the current bank until the end for (unsigned i = 0xA000 + address % 0x2000; i < 0xA000 + 0x2000; i++) { serial_putchar_inline(cart_read(i)); } // Read all the entire banks for (unsigned i = bank + 1; i < bank_end; i++) { cart_ram_bank(cart, i); cart_set_read(); for (unsigned i = 0xA000; i < 0xA000 + 0x2000; i++) { serial_putchar_inline(cart_read(i)); } } // Read the final bank until the end cart_ram_bank(cart, bank_end); cart_set_read(); for (unsigned i = 0xA000; i <= 0xA000 + (size - 1) % 0x2000; i++) { serial_putchar_inline(cart_read(i)); } cart_ram_disable(cart); } int main(void) { struct cart_info cart; // Configure reset button interrupt //PCICR = 0; //PCMSK1 = _BV(PCINT9); // Initialize the program serial_init(2000000); pinmode(PIN_MOSFET, OUTPUT); writepin(PIN_MOSFET, LOW); cart_init(); sei(); int checksum = cart_boot(&cart); if (checksum) { // Notify the user that the cart couldn't be read properly. writepin(PIN_READ, LOW); writepin(PIN_WRITE, LOW); } sbi(PCICR, PCIE1); while (serial_getchar() != PROTO_CLIENT_INIT); cbi(PCICR, PCIE1); serial_putchar(checksum ? PROTO_READER_FAIL : PROTO_READER_OK); // Interpret commands for (;;) { serial_drain(); cart_set_standby(); sbi(PCICR, PCIE1); struct proto_client_packet packet = proto_read_packet(); cbi(PCICR, PCIE1); switch (packet.command) { case PROTO_CLIENT_READ_RAW: client_read_raw(packet.address, packet.size); break; case PROTO_CLIENT_READ_ROM: client_read_rom(&cart, packet.address, packet.size); break; case PROTO_CLIENT_READ_RAM: client_read_ram(&cart, packet.address, packet.size); break; case PROTO_CLIENT_WRITE_RAW: client_write_raw(packet.address, packet.size); break; case PROTO_CLIENT_WRITE_ROM: case PROTO_CLIENT_WRITE_RAM: default: serial_putchar(PROTO_READER_FAIL); } } } void exit(__attribute__((unused)) int status) { serial_drain(); //sbi(PCICR, PCIE1); cart_off(); for (;;); } //ISR(PCINT1_vect) { //// Debounce it //int pressed = readpin(PIN_SWITCH); //int debounce = 0x2DAB; //while (debounce--) { //if (pressed != readpin(PIN_SWITCH)) return; //} //// Check if it was released //if (pressed) return; //_delay_ms(10); //__asm__ volatile ("jmp 0"); //}