Browse Source

Initial commit

master
mid-kid 4 years ago
commit
631bb30689
  1. 55
      arduino/Makefile
  2. 226
      arduino/source/cart.c
  3. 24
      arduino/source/cart.h
  4. 263
      arduino/source/main.c
  5. 58
      arduino/source/pins.h
  6. 55
      arduino/source/proto.h
  7. 9
      arduino/source/rom.c
  8. 107
      arduino/source/rom.h
  9. 1
      arduino/source/serial.c
  10. 1
      arduino/source/serial.h
  11. 1
      arduino/source/spi.c
  12. 1
      arduino/source/spi.h
  13. 1
      arduino/source/utils.h
  14. 47
      client/Makefile
  15. 144
      client/source/main.c
  16. 87
      toolchain-arduino.sh
  17. 111
      toolchain-musl.sh

55
arduino/Makefile

@ -0,0 +1,55 @@
name := GBCartRead
dir_source := source
dir_build := build
TARGET_ARCH := -mmcu=atmega328p
TARGET_AVRDUDE := -patmega328p -carduino
SERIAL := /dev/ttyACM0
CC := avr-gcc
OBJCOPY := avr-objcopy
OBJDUMP := avr-objdump
AVRDUDE := avrdude
OPTIM := -Os -g -fdata-sections -ffunction-sections -flto -fuse-linker-plugin -fipa-pta #-fgraphite-identity -floop-nest-optimize
CFLAGS := $(OPTIM) -Wall -Wextra -std=c17 -DF_CPU=16000000L
LDFLAGS := $(OPTIM) -Wl,--gc-sections -Wl,--print-gc-sections
rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2))
objects := $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, $(call rwildcard, $(dir_source)/, *.c))
.SECONDEXPANSION:
.PHONY: all
all: $(name).hex $(name).lst
.PHONY: clean
clean:
rm -rf $(dir_build) $(name).hex $(name).lst
.PHONY: upload
upload: $(name).hex $(name).lst
$(AVRDUDE) -v $(TARGET_AVRDUDE) -P$(SERIAL) -Uflash:w:$<:i
.PHONY: screen
screen: upload
minicom -D $(SERIAL) -b 2000000
%.hex: $(dir_build)/%.elf
$(OBJCOPY) -O ihex -R .eeprom $< $@
%.lst: $(dir_build)/%.elf
$(OBJDUMP) -h -S $< > $@
$(dir_build)/$(name).elf: $(objects) | $$(dir $$@)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
$(dir_build)/%.o: $(dir_source)/%.c | $$(dir $$@)
$(COMPILE.c) -MMD -MF $(@:.o=.d) $(OUTPUT_OPTION) $<
.PRECIOUS: %/
%/:
mkdir -p $@
-include $(patsubst %.o, %.d, $(objects))

226
arduino/source/cart.c

@ -0,0 +1,226 @@
#include "cart.h"
#include <stdint.h>
#include <avr/io.h>
#include <util/delay.h>
#include "utils.h"
#include "pins.h"
#include "spi.h"
#include "rom.h"
// Most of the pulses described here are arbitrarily chosen,
// to satisfy my idiotic UX requirements with the leds.
// Here's a pretty cool bus analysis:
// https://dhole.github.io/post/gameboy_cartridge_emu_1/
static void cart_address(const uint16_t address)
{
spi_transfer(address >> 8);
spi_transfer(address & 0xFF);
writepin(PIN_STORE, HIGH);
__asm__ volatile ("nop");
writepin(PIN_STORE, LOW);
}
uint8_t cart_read(const uint16_t address)
{
cart_address(address);
__asm__ volatile ("nop");
return PINS_DAT;
}
void cart_write(const uint16_t address, const uint8_t byte)
{
cart_address(address);
PORTB = (PORTB & ~PINS_DATB) | (byte >> 6);
PORTD = (PORTD & ~PINS_DATD) | (byte << 2);
writepin(PIN_WRITE, LOW);
__asm__ volatile ("nop");
writepin(PIN_WRITE, HIGH);
}
void cart_set_read(void)
{
// Set the data pins as pull-up inputs
DDRB &= ~PINS_DATB;
DDRD &= ~PINS_DATD;
PORTB |= PINS_DATB;
PORTD |= PINS_DATD;
writepin(PIN_READ, LOW);
writepin(PIN_WRITE, HIGH);
}
void cart_set_write(void)
{
// Set the data pins as low outputs
DDRB |= PINS_DATB;
DDRD |= PINS_DATD;
PORTB &= ~PINS_DATB;
PORTD &= ~PINS_DATD;
writepin(PIN_READ, HIGH);
writepin(PIN_WRITE, HIGH);
}
void cart_set_standby(void)
{
// Set the data pins as pull-up inputs
DDRB &= ~PINS_DATB;
DDRD &= ~PINS_DATD;
PORTB |= PINS_DATB;
PORTD |= PINS_DATD;
writepin(PIN_READ, HIGH);
writepin(PIN_WRITE, HIGH);
}
void cart_select_rom(void)
{
writepin(PIN_CS, HIGH);
}
void cart_select_ram(void)
{
writepin(PIN_CS, LOW);
}
void cart_init(void)
{
// Set the appropriate pins as output
DDRB |= pin(PIN_STORE);
DDRC |= pin(PIN_READ) | pin(PIN_WRITE) | pin(PIN_CS);
// Initialize the cart as standby
cart_set_standby();
cart_select_rom();
// Initialize the shift register
writepin(PIN_STORE, LOW);
spi_init(SPI_DIV8);
cart_address(0);
}
void cart_off(void)
{
cart_address(0);
spi_off();
PORTC &= ~pin(PIN_READ) & ~pin(PIN_CS) & ~pin(PIN_WRITE);
writepin(PIN_STORE, LOW);
}
int cart_boot(struct cart_info *info)
{
cart_set_read();
// "simulate" the GB power-up process, by reading the logo once, before comparing.
// This might be compatible with carts that hijack the logo on bootup.
// There is no point in simulating this more accurately (i.e. timing)
// if we don't even simulate the RD/WR pulses properly.
// http://gbdev.gg8.se/wiki/articles/Gameboy_Bootstrap_ROM
// Read the logo once (to display on the screen)
for (unsigned i = rom_offset(logo);
i < rom_offset_end(logo); i++) cart_read(i);
// Read the logo again (to verify it)
for (unsigned i = rom_offset(logo); i < rom_offset_end(logo); i++) {
if (rom_logo[i - rom_offset(logo)] != cart_read(i)) {
cart_set_standby();
return 1;
}
}
// Read and verify the checksum (and save some info from the header)
uint8_t checksum = rom_offset(header_checksum) - rom_offset(title);
for (unsigned i = rom_offset(title);
i < rom_offset_end(header_checksum); i++) {
uint8_t c = cart_read(i);
checksum += c;
if (i == rom_offset(type)) info->type = c;
if (i == rom_offset(rom_size)) info->rom_size = c;
if (i == rom_offset(ram_size)) info->ram_size = c;
}
cart_set_standby();
return checksum;
}
// https://ia801906.us.archive.org/19/items/GameBoyProgManVer1.1/GameBoyProgManVer1.1.pdf (page 215)
// NOTE: These functions only support MBC1, MBC2, MBC3 and MBC5.
// Thankfully, that covers most of the licensed carts.
void cart_rom_bank(__attribute__((unused)) struct cart_info *info, const unsigned bank)
{
// MBC1: 0x2000-0x3FFF (bit 0-4) 0x4000-0x5FFF (bit 5-6, if 0x6000-0x7FFF is set to 0)
// MBC2: 0x2100-0x21FF (bit 0-3)
// MBC3: 0x2000-0x3FFF (bit 0-6)
// MBC5: 0x2000-0x2FFF (bit 0-7) 0x3000-0x3FFF (bit 8)
cart_set_write();
if (info->type >= ROM_TYPE_MBC1 &&
info->type <= ROM_TYPE_MBC1_RAM_BATTERY) {
cart_write(0x2000, bank & 0x1F);
cart_write(0x6000, 0);
cart_write(0x4000, bank >> 5);
return;
}
cart_write(0x2100, bank);
if (info->type >= ROM_TYPE_MBC5 &&
info->type <= ROM_TYPE_MBC5_RUMBLE_RAM_BATTERY) {
cart_write(0x3000, bank >> 8);
}
}
void cart_ram_enable(__attribute__((unused)) struct cart_info *info)
{
// MBC1: 0x0000-0x1FFF = 0x0A
// MBC2: 0x0000-0x0FFF = 0x0A
// MBC3: 0x0000-0x1FFF = 0x0A
// MBC5: 0x0000-0x1FFF = 0x0A
cart_set_write();
cart_write(0x0000, 0x0A);
cart_select_ram();
}
void cart_ram_disable(__attribute__((unused)) struct cart_info *info)
{
// MBC1: 0x0000-0x1FFF = 0x00
// MBC2: 0x0000-0x0FFF = 0x00
// MBC3: 0x0000-0x1FFF = 0x00
// MBC5: 0x0000-0x1FFF = 0x00
cart_set_write();
cart_select_rom();
cart_write(0x0000, 0x00);
}
void cart_ram_bank(__attribute__((unused)) struct cart_info *info, const unsigned bank)
{
// MBC1: 0x4000-0x5FFF (bit 0-1, if 0x6000-0x7FFF is set to 1)
// MBC2: None, it has only one bank. (Writing to 0x4000-0x5FFF should have no effect)
// MBC3: 0x4000-0x5FFF (bit 0-1) NOTE: Writing values 0x08-0x0C opens up possible problems with the RTC!
// MBC5: 0x4000-0x5FFF (bit 0-3)
cart_set_write();
if (info->type >= ROM_TYPE_MBC1 &&
info->type <= ROM_TYPE_MBC1_RAM_BATTERY) {
cart_write(0x6000, 1);
}
// Since writing 0x08-0x0C here opens up the timer,
// it opens up the possibility for corruption.
// As such, it's a good idea to check for it...
if (info->type >= ROM_TYPE_MBC3_TIMER_BATTERY &&
info->type <= ROM_TYPE_MBC3_RAM_BATTERY) {
cart_write(0x4000, bank & 0x07);
return;
}
cart_write(0x4000, bank);
}

24
arduino/source/cart.h

@ -0,0 +1,24 @@
#pragma once
#include <stdint.h>
struct cart_info {
int type;
int rom_size;
int ram_size;
};
uint8_t cart_read(const uint16_t address);
void cart_write(const uint16_t address, const uint8_t byte);
void cart_set_read(void);
void cart_set_write(void);
void cart_set_standby(void);
void cart_select_rom(void);
void cart_select_ram(void);
void cart_init(void);
void cart_off(void);
int cart_boot(struct cart_info *info);
void cart_rom_bank(struct cart_info *info, unsigned bank);
void cart_ram_enable(struct cart_info *info);
void cart_ram_disable(struct cart_info *info);
void cart_ram_bank(struct cart_info *info, unsigned bank);

263
arduino/source/main.c

@ -0,0 +1,263 @@
// 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 <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#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");
//}

58
arduino/source/pins.h

@ -0,0 +1,58 @@
#pragma once
// Pin mappings as on the arduino board
#define PIN_D0 D, 0
#define PIN_D1 D, 1
#define PIN_D2 D, 2
#define PIN_D3 D, 3
#define PIN_D4 D, 4
#define PIN_D5 D, 5
#define PIN_D6 D, 6
#define PIN_D7 D, 7
#define PIN_D8 B, 0
#define PIN_D9 B, 1
#define PIN_D10 B, 2
#define PIN_D11 B, 3
#define PIN_D12 B, 4
#define PIN_D13 B, 5
#define PIN_A0 C, 0
#define PIN_A1 C, 1
#define PIN_A2 C, 2
#define PIN_A3 C, 3
#define PIN_A4 C, 4
#define PIN_A5 C, 5
// Alternative names for certain pins
#define PIN_SPI_SS PIN_D10
#define PIN_SPI_MOSI PIN_D11
#define PIN_SPI_MISO PIN_D12
#define PIN_SPI_SCK PIN_D13
#define PIN_WIRE_SDA PIN_A4
#define PIN_WIRE_SCL PIN_A5
#define LED_BUILTIN PIN_D13
// Button
#define PIN_MOSFET PIN_A0
#define PIN_SWITCH PIN_A1
// Cart
#define PIN_WRITE PIN_A3
#define PIN_CS PIN_A4
#define PIN_READ PIN_A5
// Shift registers
#define PIN_STORE PIN_D10
// Data pins
#define PIN_DAT0 PIN_D2
#define PIN_DAT1 PIN_D3
#define PIN_DAT2 PIN_D4
#define PIN_DAT3 PIN_D5
#define PIN_DAT4 PIN_D6
#define PIN_DAT5 PIN_D7
#define PIN_DAT6 PIN_D8
#define PIN_DAT7 PIN_D9
#define PINS_DATB (pin(PIN_DAT7) | pin(PIN_DAT6))
#define PINS_DATD (pin(PIN_DAT5) | pin(PIN_DAT4) | pin(PIN_DAT3) | pin(PIN_DAT2) | pin(PIN_DAT1) | pin(PIN_DAT0))
#define PINS_DAT (PINB << 6 | PIND >> 2)

55
arduino/source/proto.h

@ -0,0 +1,55 @@
#pragma once
enum proto_reader {
// Sent when the operation was successful
PROTO_READER_OK,
// Sent after a failed operation
PROTO_READER_FAIL
};
enum proto_client {
// Packet:
// u8 - command
// Response:
// u8 - status
PROTO_CLIENT_INIT,
// Packet:
// u8 - command
// u32 - start address
// u32 - size
// Response:
// u8 - status
// u8[] - data (if status == PROTO_READER_OK)
// Read a raw address with whatever current state
// (useful for RTC and other cart addons)
PROTO_CLIENT_READ_RAW,
// Read an absolute ROM address, switching banks as needed
PROTO_CLIENT_READ_ROM,
// Read an absolute SRAM address, switching banks as needed
PROTO_CLIENT_READ_RAM,
// Packet:
// u8 - command
// u32 - start address
// u32 - size
// u8[] - data
// Response:
// u8 - status
// Write a raw address with whatever current state
// (useful for RTC and other cart addons)
PROTO_CLIENT_WRITE_RAW,
// Write an absolute ROM address, switching banks as needed
PROTO_CLIENT_WRITE_ROM,
// Write an absolute SRAM address, switching banks as needed
PROTO_CLIENT_WRITE_RAM
};
struct proto_client_packet {
uint8_t command;
uint32_t address;
uint32_t size;
} __attribute__((packed));

9
arduino/source/rom.c

@ -0,0 +1,9 @@
#include "rom.h"
#include <stdint.h>
uint8_t rom_logo[] = {
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
};

107
arduino/source/rom.h

@ -0,0 +1,107 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#define rom_offset(member) (0x100 + offsetof(struct rom_header, member))
#define rom_offset_end(member) (0x100 + offsetof(struct rom_header, member) + sizeof(((struct rom_header *)0)->member))
enum rom_cgb {
ROM_CGB_SUPPORTED = 0x80,
ROM_CGB_ONLY = 0xC0
};
enum rom_sgb {
ROM_SGB_NO = 0x00,
ROM_SGB_SUPPORTED = 0x03
};
enum rom_type {
ROM_TYPE_ROM_ONLY = 0x00,
ROM_TYPE_MBC1 = 0x01,
ROM_TYPE_MBC1_RAM = 0x2,
ROM_TYPE_MBC1_RAM_BATTERY = 0x03,
// UNUSED = 0x04
ROM_TYPE_MBC2 = 0x05,
ROM_TYPE_MBC2_BATTERY = 0x06,
// UNUSED = 0x07
ROM_TYPE_ROM_RAM = 0x08,
ROM_TYPE_ROM_RAM_BATTERY = 0x09,
// UNUSED = 0x0A
ROM_TYPE_MMM01 = 0x0B,
ROM_TYPE_MMM01_RAM = 0x0C,
ROM_TYPE_MMM01_RAM_BATTERY = 0x0D,
// UNUSED = 0x0E
ROM_TYPE_MBC3_TIMER_BATTERY = 0x0F,
ROM_TYPE_MBC3_TIMER_RAM_BATTERY = 0x10,
ROM_TYPE_MBC3 = 0x11,
ROM_TYPE_MBC3_RAM = 0x12,
ROM_TYPE_MBC3_RAM_BATTERY = 0x13,
// UNUSED = 0x14
// Doesn't exist, but included for "completeness".
ROM_TYPE_MBC4 = 0x15,
ROM_TYPE_MBC4_RAM = 0x16,
ROM_TYPE_MBC4_RAM_BATTERY = 0x17,
// UNUSED = 0x18
ROM_TYPE_MBC5 = 0x19,
ROM_TYPE_MBC5_RAM = 0x1A,
ROM_TYPE_MBC5_RAM_BATTERY = 0x1B,
ROM_TYPE_MBC5_RUMBLE = 0x1C,
ROM_TYPE_MBC5_RUMBLE_RAM = 0x1D,
ROM_TYPE_MBC5_RUMBLE_RAM_BATTERY = 0x1E,
// UNUSED = 0x1F
ROM_TYPE_MBC6_RAM_BATTERY = 0x20,
// UNUSED = 0x21
ROM_TYPE_MBC7_ACCEL_RAM_BATTERY = 0x22,
// UNUSED ...
ROM_TYPE_POCKET_CAMERA = 0xFC,
ROM_TYPE_BANDAI_TAMA5 = 0xFD,
ROM_TYPE_HUC3 = 0xFE,
ROM_TYPE_HUC1_RAM_BATTERY = 0xFF
};
enum rom_region {
ROM_REGION_JAPANESE = 0x00,
ROM_REGION_INTERNATIONAL = 0x01
};
// http://gbdev.gg8.se/files/docs/mirrors/pandocs.html#thecartridgeheader
// https://raw.githubusercontent.com/AntonioND/giibiiadvance/master/docs/TCAGBD.pdf (page 51)
struct rom_header {
uint8_t entry[4];
uint8_t logo[0x30];
uint8_t title[0xB];
uint8_t manufacturer[4];
uint8_t cgb_flag;
uint8_t licensee[2];
uint8_t sgb_flag;
uint8_t type;
uint8_t rom_size; // 0x8000 << x
uint8_t ram_size; // MBC2 uses 0, but has a size of 512 * 4 bytes
uint8_t region;
uint8_t old_licensee; // If 0x33, use the new licensee
uint8_t version;
uint8_t header_checksum;
uint8_t global_checksum[2]; // Big endian
} __attribute__((packed));
extern uint8_t rom_logo[];

1
arduino/source/serial.c

@ -0,0 +1 @@
/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/serial.c

1
arduino/source/serial.h

@ -0,0 +1 @@
/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/serial.h

1
arduino/source/spi.c

@ -0,0 +1 @@
/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/spi.c

1
arduino/source/spi.h

@ -0,0 +1 @@
/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/spi.h

1
arduino/source/utils.h

@ -0,0 +1 @@
/home/mid-kid/Stuff/Workspace/bitsandpieces/atmega/utils.h

47
client/Makefile

@ -0,0 +1,47 @@
name := GBCartRead
dir_source := source
dir_build := build
CFLAGS := -O0 -g -Wall -Wextra -std=gnu17 $(CFLAGS)
CFLAGS += $(shell pkg-config --cflags libserialport)
LDLIBS += $(shell pkg-config --libs libserialport)
SANIT := -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak -fsanitize=undefined
OPTIM := -Os -fdata-sections -ffunction-sections -flto -fuse-linker-plugin -fipa-pta -Wl,--gc-sections -Wl,--print-gc-sections #-fgraphite-identity -floop-nest-optimize
rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2))
objects := $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, $(call rwildcard, $(dir_source)/, *.c))
.SECONDEXPANSION:
.PHONY: all
all: $(name)
.PHONY: clean
clean:
rm -rf $(dir_build) $(name)
.PHONY: sanit
sanit: CFLAGS += $(SANIT)
sanit: LDFLAGS += $(SANIT)
sanit: $(name)
.PHONY: optim
optim: CFLAGS += $(OPTIM)
optim: LDFLAGS += $(OPTIM)
optim: $(name)
strip --strip-all --strip-unneeded $(name)
$(name): $(objects)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
$(dir_build)/%.o: $(dir_source)/%.c | $$(dir $$@)
$(COMPILE.c) -MMD -MF $(@:.o=.d) $(OUTPUT_OPTION) $<
.PRECIOUS: %/
%/:
mkdir -p $@
-include $(patsubst %.o, %.d, $(objects))

144
client/source/main.c

@ -0,0 +1,144 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <locale.h>
#include <libserialport.h>
#include <sys/time.h>
#ifdef __GNUC__
#define A_UNUSED __attribute__((unused))
#else
#define A_UNUSED
#endif
struct sp_port *serial_guess_port(void)
{
struct sp_port **ports;
if (sp_list_ports(&ports) != SP_OK || ports[0] == NULL) {
puts("No serial devices detected");
return NULL;
}
if (ports[1] != NULL) {
puts("Please select a port");
return NULL;
}
return ports[0];
}
char *program_name;
void serial_error(char *message)
{
char *error = sp_last_error_message();
fprintf(stderr, "%s: %s: %s\n", program_name, message, error);
sp_free_error_message(error);
}
int main(int argc, char *argv[])
{
program_name = argv[0];
setlocale(LC_ALL, "");
long save = 0;
if (argc > 1) {
save = strtol(argv[1], NULL, 0);
}
struct sp_port *port;
if (!(port = serial_guess_port())) return 1;
puts(sp_get_port_name(port));
if (sp_open(port, SP_MODE_READ_WRITE) != SP_OK ||
sp_set_baudrate(port, 2000000) != SP_OK ||
sp_set_flowcontrol(port, SP_FLOWCONTROL_NONE) != SP_OK) {
serial_error("Failed to open port");
return 1;
}
usleep(100000LL);
sp_flush(port, SP_BUF_BOTH);
fputs("Waiting for device... ", stdout);
fflush(stdout);
for (;;) {
char c = 0;
sp_blocking_write(port, &c, 1, 0);
if (sp_blocking_read(port, &c, 1, 1000) == 1) {
if (c == 0) {
puts("ROM OK!");
} else {
puts("ROM ERROR");
}
break;
}
}
char command = 1;
uint32_t address = 0x147;
uint32_t size = 3;
sp_blocking_write(port, &command, 1, 0);
sp_blocking_write(port, &address, 4, 0);
sp_blocking_write(port, &size, 4, 0);
sp_blocking_read(port, &command, 1, 0);
if (command != 0) {
puts("An error has occurred");
return 1;
}
char cart_type = 0;
sp_blocking_read(port, &cart_type, 1, 0);
printf("Cart type: %02X\n", cart_type);
char rom_size = 0;
sp_blocking_read(port, &rom_size, 1, 0);
printf("Rom size: %02X\n", rom_size);
char ram_size = 0;
sp_blocking_read(port, &ram_size, 1, 0);
printf("Ram size: %02X\n", ram_size);
command = save ? 3 : 2;
address = 0;
if (!save) {
size = 0x8000 << rom_size;
} else {
size = save;
}
sp_blocking_write(port, &command, 1, 0);
sp_blocking_write(port, &address, 4, 0);
sp_blocking_write(port, &size, 4, 0);
sp_blocking_read(port, &command, 1, 0);
if (command != 0) {
puts("An error has occurred");
return 1;
}
struct timeval begin, end;
gettimeofday(&begin, NULL);
FILE *file = fopen("out.bin", "w");
for (unsigned x = 0; x < size; x += 0x200) {
char c[0x200];
sp_blocking_read(port, &c, 0x200, 0);
if (!save) {
printf("\r%02X:%04X", x / 0x4000, x % 0x4000);
} else {
printf("\r%02X:%04X", x / 0x2000, x % 0x2000);
}
fflush(stdout);
fwrite(&c, 0x200, 1, file);
}
putchar('\n');
fclose(file);
gettimeofday(&end, NULL);
double dt = (double)end.tv_sec - begin.tv_sec + ((double)end.tv_usec - begin.tv_usec) / 1000000;
printf("Average speed: %f KB/s\n", size / 1000 / dt);
}

87
toolchain-arduino.sh

@ -0,0 +1,87 @@
#!/bin/sh
set -e
PREFIX="$PWD/toolchain"
TARGET=avr
export CFLAGS='-Os -pipe'
export CXXFLAGS="$CFLAGS"
export PATH="$PREFIX/bin:$PATH"
export MAKEFLAGS="-j$(nproc)"
mkdir -p "$PREFIX/src"
( cd "$PREFIX/src"
wget -c https://ftp.gnu.org/gnu/binutils/binutils-2.30.tar.xz
wget -c https://ftp.gnu.org/gnu/gcc/gcc-8.1.0/gcc-8.1.0.tar.xz
wget -c https://ftp.gnu.org/gnu/gmp/gmp-6.1.2.tar.xz
wget -c https://ftp.gnu.org/gnu/mpfr/mpfr-4.0.1.tar.xz
wget -c https://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz
wget -c http://isl.gforge.inria.fr/isl-0.19.tar.xz
wget -c https://download.savannah.gnu.org/releases/avr-libc/avr-libc-2.0.0.tar.bz2
wget -c https://download.savannah.gnu.org/releases/avrdude/avrdude-6.3.tar.gz
)
( cd "$PREFIX/src"
rm -rf binutils-*/
tar xf binutils-*.tar.xz
cd binutils-*/
mkdir build
cd build
../configure --prefix="$PREFIX" --target="$TARGET" --disable-nls
make
make install
)
( cd "$PREFIX/src"
rm -rf gcc-*/
tar xf gcc-*.tar.xz
cd gcc-*/
tar xf ../gmp-*.tar.xz
tar xf ../mpfr-*.tar.xz
tar xf ../mpc-*.tar.gz
tar xf ../isl-*.tar.xz
mv gmp-*/ gmp
mv mpfr-*/ mpfr
mv mpc-*/ mpc
mv isl-*/ isl
sed -i -e 's/\.\/fixinc\.sh/-c true/' gcc/Makefile.in
mkdir -p "$PREFIX/$TARGET/include"
mkdir build
cd build
../configure --prefix="$PREFIX" --target="$TARGET" --enable-languages=c,c++ --disable-nls
make
make install
)
( cd "$PREFIX/src"
rm -rf avr-libc-*/
tar xf avr-libc-*.tar.bz2
cd avr-libc-*/
./configure --prefix="$PREFIX" --host="$TARGET"
make
make install
)
( cd "$PREFIX/src"
rm -rf avrdude-*/
tar xf avrdude-*.tar.gz
cd avrdude-*/
mkdir build
cd build
../configure --prefix="$PREFIX"
make
make install
)
( cd "$PREFIX/src"
rm -rf binutils-*/
rm -rf gcc-*/
rm -rf avr-libc-*/
rm -rf avrdude-*/
#find "$PREFIX" -type f -name '*.la' -delete
)
cat > "$PREFIX/env" << EOF
export PATH="$PREFIX/bin:\$PATH"
EOF
echo 'Run "source toolchain/env" before running make!'

111
toolchain-musl.sh

@ -0,0 +1,111 @@
#!/bin/sh
set -e
PREFIX="$PWD/toolchain"
TARGET=x86_64-linux-musl
ARCH=x86_64
export CFLAGS='-Os -pipe'
export CXXFLAGS="$CFLAGS"
export PATH="$PREFIX/bin:$PATH"
export MAKEFLAGS="-j$(nproc)"
mkdir -p "$PREFIX/src"
( cd "$PREFIX/src"
wget -c https://ftp.gnu.org/gnu/binutils/binutils-2.30.tar.xz
wget -c https://ftp.gnu.org/gnu/gcc/gcc-8.1.0/gcc-8.1.0.tar.xz
wget -c https://ftp.gnu.org/gnu/gmp/gmp-6.1.2.tar.xz
wget -c https://ftp.gnu.org/gnu/mpfr/mpfr-4.0.1.tar.xz
wget -c https://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz
wget -c http://isl.gforge.inria.fr/isl-0.19.tar.xz
wget -c https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.16.7.tar.xz
wget -c https://www.musl-libc.org/releases/musl-1.1.19.tar.gz
wget -c http://sigrok.org/download/source/libserialport/libserialport-0.1.1.tar.gz
)
( cd "$PREFIX/src"
rm -rf binutils-*/
tar xf binutils-*.tar.xz
cd binutils-*/
mkdir build
cd build
../configure --prefix="$PREFIX" --target="$TARGET" --disable-nls --disable-multilib
make
make install
)
( cd "$PREFIX/src"
rm -rf gcc-*/
tar xf gcc-*.tar.xz
cd gcc-*/
tar xf ../gmp-*.tar.xz
tar xf ../mpfr-*.tar.xz
tar xf ../mpc-*.tar.gz
tar xf ../isl-*.tar.xz
mv gmp-*/ gmp
mv mpfr-*/ mpfr
mv mpc-*/ mpc
mv isl-*/ isl
mkdir build
cd build
../configure --prefix="$PREFIX" --target="$TARGET" --disable-nls --disable-multilib --disable-libsanitizer --disable-libmpx --enable-languages=c,c++
make all-gcc
make install-gcc
)
( cd "$PREFIX/src"
rm -rf linux-*/
tar xf linux-*.tar.xz
cd linux-*/
make ARCH="$ARCH" INSTALL_HDR_PATH="$PWD/install" headers_install
find install -type f -a ! -name '*.h' -delete
cp -aT install/include "$PREFIX/$TARGET/include"
)
( cd "$PREFIX/src"
rm -rf musl-*/
tar xf musl-*.tar.gz
cd musl-*/
./configure --prefix="$PREFIX/$TARGET" --host="$TARGET" --syslibdir='$(prefix)/lib'
make install-headers
)
( cd "$PREFIX/src"
cd gcc-*/build
make MAKE='make enable_shared=no' all-target-libgcc
make MAKE='make enable_shared=no' install-target-libgcc
)
( cd "$PREFIX/src"
cd musl-*/
make LIBCC="$PREFIX/lib/gcc/$TARGET/*/libgcc.a" install
)
( cd "$PREFIX/src"
cd gcc-*/build
make
make install
)
( cd "$PREFIX/src"
rm -rf libserialport-*/
tar xf libserialport-*.tar.gz
cd libserialport-*/
./configure --prefix="$PREFIX/$TARGET" --host="$TARGET"
make
make install
)
( cd "$PREFIX/src"
rm -rf binutils-*/
rm -rf gcc-*/
rm -rf linux-*/
rm -rf musl-*/
rm -rf libserialport-*/
#find "$PREFIX" -type f -name '*.la' -delete
)
cat > "$PREFIX/env" << EOF
export PATH="$PREFIX/bin:\$PATH"
EOF
echo 'Run "source toolchain/env" before running make!'
Loading…
Cancel
Save