diff --git a/platform/amiga/adfgen.c b/platform/amiga/adfgen.c new file mode 100644 index 0000000000..def5562397 --- /dev/null +++ b/platform/amiga/adfgen.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include +#include +#include +#include + +#define BOOTBLOCK_SIZE 1024 +#define ADF_SIZE 901120 + +static uint32_t read_be(const unsigned char *p) { + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | + ((uint32_t)p[3] << 0); +} + +static void write_be(unsigned char *p, uint32_t val) { + p[0] = (val >> 24); + p[1] = (val >> 16); + p[2] = (val >> 8); + p[3] = (val >> 0); +} + +static uint32_t generate_checksum(unsigned char *block) { + uint32_t sum = 0; + + // Clear checksum + write_be(block + 4, 0); + + for (int i = 0; i < BOOTBLOCK_SIZE; i += 4) { + uint32_t word = read_be(block + i); + uint32_t old = sum; + + sum += word; + + if (sum < old) { + sum++; + } + } + + return ~sum; +} + +int main(int argc, char *argv[]) { + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + unsigned char buf[ADF_SIZE]; + memset(buf, 0, sizeof(buf)); + + FILE *bootcode = fopen(argv[1], "rb"); + + if (!bootcode) { + perror("Error opening input file"); + return 1; + } + + size_t bytes_read = fread(buf + 12, 1, BOOTBLOCK_SIZE - 12, bootcode); + + if (ferror(bootcode)) { + perror("Error reading input file"); + fclose(bootcode); + return 1; + } + + fclose(bootcode); + + if (bytes_read == 0) { + fprintf(stderr, "Error: input bootcode binary is empty.\n"); + return 1; + } + + // 'DOS0' type, 'DOS1' also works + buf[0] = 'D'; + buf[1] = 'O'; + buf[2] = 'S'; + buf[3] = 0x00; + + write_be(buf + 4, 0); // checksum + write_be(buf + 8, 880); // root block + + uint32_t checksum = generate_checksum(buf); + write_be(buf + 4, checksum); + + printf("Calculated checksum: 0x%08X\n", checksum); + + FILE *adf_out = fopen(argv[2], "wb"); + + if (!adf_out) { + perror("Error opening output file"); + return 1; + } + + size_t written = fwrite(buf, 1, ADF_SIZE, adf_out); + + if (written != ADF_SIZE) { + perror("Error writing output file"); + fclose(adf_out); + return 1; + } + + fclose(adf_out); + + printf("Successfully generated ADF image: %s\n", argv[2]); + + return 0; +} diff --git a/platform/amiga/display.c b/platform/amiga/display.c new file mode 100644 index 0000000000..00d4400b29 --- /dev/null +++ b/platform/amiga/display.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include "platform_p.h" +#include +#include +#include +#include +#include +#include +#if WITH_KERNEL_VM +#include +#else +#include +#endif + +static volatile uint16_t *const paula_base = (void *)0xDFF000; +static struct display_framebuffer display_fb; + +// 1bpp bitplane +static uint8_t bitplane[BPL_BYTES]; +static uint16_t copper[64]; + +void make_copper_list(uint8_t *bpl) { + // Bitplane ptr register is two 16bit halves. May need to change this if vm/MMU is ever enabled. + uintptr_t bpl_ptr = (uintptr_t)bpl; + uint16_t bplptr_hi = (bpl_ptr >> 16); + uint16_t bplptr_lo = (bpl_ptr & 0xFFFF); + + int i = 0; + + // clang-format off + + // Copper instructions are two words. First word is the destination register offset, second is the value. + + // Set display window + copper[i++] = DIWSTRT; copper[i++] = 0x2C81; // Display window vert + horiz start, PAL low-res mode + copper[i++] = DIWSTOP; copper[i++] = ((((0x2C + H) & 0xFF) << 8) | 0xC1); // vert + horiz end + copper[i++] = DDFSTRT; copper[i++] = 0x0038; // low-res DMA fetch start + copper[i++] = DDFSTOP; copper[i++] = 0x00D0; // DMA fetch stop. Stop and start may need to change if screen width ever does. + + // 1 bitplane, enable "colour" output + copper[i++] = BPLCON0; copper[i++] = (0x200 | (1 << 12)); // Set display mode and bitplane count ("BPU"); one bitplane + copper[i++] = BPLCON1; copper[i++] = 0x0000; // No horizontal playfield scroll + copper[i++] = BPLCON2; copper[i++] = 0x0000; // Set playfield priority + + // Bitplane modulo = 0 for a single linear bitplane + copper[i++] = BPL1MOD; copper[i++] = 0x0000; + copper[i++] = BPL2MOD; copper[i++] = 0x0000; + + // Set bitplane pointer + copper[i++] = BPL1PTH; copper[i++] = bplptr_hi; + copper[i++] = BPL1PTL; copper[i++] = bplptr_lo; + + // Background and foreground colours + copper[i++] = COLOR00; copper[i++] = 0x0000; + copper[i++] = COLOR01; copper[i++] = 0x0FFF; + + // Send end/terminator words, causes Copper to stop processing the list + copper[i++] = 0xFFFF; + copper[i++] = 0xFFFE; + + // clang-format on +} + +static void write_reg(unsigned int reg, uint32_t val) { + paula_base[reg >> 1] = val; +} + +status_t display_get_framebuffer(struct display_framebuffer *fb) { + fb->image.pixels = bitplane; + + fb->image.format = IMAGE_FORMAT_MONO_1; + fb->image.rowbytes = BYTES_PER_ROW; + fb->image.width = W; + fb->image.height = H; + fb->image.stride = BYTES_PER_ROW; + fb->format = DISPLAY_FORMAT_MONO_1; + fb->flush = NULL; + + return NO_ERROR; +} + +status_t display_get_info(struct display_info *info) { + info->format = DISPLAY_FORMAT_MONO_1; + info->width = W; + info->height = H; + + return NO_ERROR; +} + +void platform_init_display(void) { + memset(bitplane, 0, BPL_BYTES); + + make_copper_list(bitplane); + + // Disable DMA + write_reg(DMACON, 0x7FFF); + + // Set up copper list address, high and low. + // Physical address, might need revisiting if/when vm/MMU + uintptr_t clist = (uintptr_t)copper; + write_reg(COP1LCH, (clist >> 16)); + write_reg(COP1LCL, (clist & 0xFFFF)); + write_reg(COPJMP1, 0x0000); + + // Enable DMA. + // 0x200 = master DMA enable, 0x100 = bitplane DMA enable, 0x0080 = Copper DMA enable + write_reg(DMACON, (0x8000 | 0x0200 | 0x0080 | 0x0100)); + + display_get_framebuffer(&display_fb); +} diff --git a/platform/amiga/include/platform/display.h b/platform/amiga/include/platform/display.h new file mode 100644 index 0000000000..1bf30c5be9 --- /dev/null +++ b/platform/amiga/include/platform/display.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#pragma once + +enum { W = 320, + H = 256, + // Round up width to next multiple. bitplane DMA is word-aligned (16 pixels). + BYTES_PER_ROW = (((W + 15) & ~15) / 8), + BPL_BYTES = (BYTES_PER_ROW * H) }; + +void make_copper_list(uint8_t *bpl); +void platform_init_display(void); +void init_display(void); diff --git a/platform/amiga/keyboard.c b/platform/amiga/keyboard.c new file mode 100644 index 0000000000..e4cc79e9c1 --- /dev/null +++ b/platform/amiga/keyboard.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include "platform_p.h" +#include +#include +#include +#include + +#define SCANCODE_LSHIFT 0x60 +#define SCANCODE_RSHIFT 0x61 + +static volatile uint8_t *const cia_base = (volatile uint8_t *)CIA_A_BASE; + +// clang-format off +// Amiga raw keycode -> ASCII (US layout). Unassigned entries are 0. +static const int KeyCodeSingleLower[128] = { + [0x00] = '`', [0x01] = '1', [0x02] = '2', [0x03] = '3', [0x04] = '4', [0x05] = '5', + [0x06] = '6', [0x07] = '7', [0x08] = '8', [0x09] = '9', [0x0a] = '0', [0x0b] = '-', + [0x0c] = '=', [0x0d] = '\\', [0x0e] = 0, [0x41] = '\b', [0x42] = '\t', + + [0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r', [0x14] = 't', [0x15] = 'y', + [0x16] = 'u', [0x17] = 'i', [0x18] = 'o', [0x19] = 'p', [0x1a] = '[', [0x1b] = ']', + [0x44] = '\n', // Enter/Return + + [0x62] = 0, + [0x20] = 'a', [0x21] = 's', [0x22] = 'd', [0x23] = 'f', [0x24] = 'g', [0x25] = 'h', + [0x26] = 'j', [0x27] = 'k', [0x28] = 'l', [0x29] = ';', [0x2a] = '\'', [0x31] = 'z', + [0x32] = 'x', [0x33] = 'c', [0x34] = 'v', [0x35] = 'b', [0x36] = 'n', [0x37] = 'm', + [0x38] = ',', [0x39] = '.', [0x3a] = '/', + [0x40] = ' ', [0x45] = 0x1b, // Space & Esc + + // Keypad + [0x0f] = '0', [0x1d] = '1', [0x1e] = '2', [0x1f] = '3', [0x2d] = '4', [0x2e] = '5', + [0x2f] = '6', [0x3d] = '7', [0x3e] = '8', [0x3f] = '9', [0x3c] = '.', [0x4a] = '-', + [0x5a] = '(', [0x5b] = ')', [0x5c] = '/', [0x5e] = '+', [0x43] = '\n', + + [0x2b] = 0, // (international key on some layouts) + [0x30] = 0, // (international key on some layouts) +}; + +static const int KeyCodeSingleUpper[128] = { + [0x00] = '~', [0x01] = '!', [0x02] = '@', [0x03] = '#', [0x04] = '$', [0x05] = '%', + [0x06] = '^', [0x07] = '&', [0x08] = '*', [0x09] = '(', [0x0a] = ')', [0x0b] = '_', + [0x0c] = '+', [0x0d] = '|', [0x0e] = 0, [0x0f] = '0', // keypad 0 (optional) + + [0x10] = 'Q', [0x11] = 'W', [0x12] = 'E', [0x13] = 'R', [0x14] = 'T', [0x15] = 'Y', + [0x16] = 'U', [0x17] = 'I', [0x18] = 'O', [0x19] = 'P', [0x1a] = '{', [0x1b] = '}', + [0x1c] = '\n', [0x1d] = 0, [0x1e] = 0, [0x1f] = 0, + + [0x20] = 'A', [0x21] = 'S', [0x22] = 'D', [0x23] = 'F', [0x24] = 'G', [0x25] = 'H', + [0x26] = 'J', [0x27] = 'K', [0x28] = 'L', [0x29] = ':', [0x2a] = '"', [0x2b] = 0, + [0x2c] = 0, [0x2d] = 0, [0x2e] = 0, [0x2f] = 0, [0x30] = 0, + + [0x31] = 'Z', [0x32] = 'X', [0x33] = 'C', [0x34] = 'V', [0x35] = 'B', [0x36] = 'N', + [0x37] = 'M', [0x38] = '<', [0x39] = '>', [0x3a] = '?', [0x3b] = 0, [0x3c] = ' ', +}; +// clang-format on + +static bool key_lshift; +static bool key_rshift; + +static cbuf_t *key_buf; + +static inline uint8_t decode_key(uint8_t key) { + return ~((key >> 1) | (key << 7)); +} + +static bool generate_esc_seq(cbuf_t *buf, char a, char b, char c) { + char esc_seq[3] = {a, b, c}; + + if (cbuf_space_avail(buf) < sizeof(esc_seq)) { + return false; + } + + return cbuf_write(buf, esc_seq, sizeof(esc_seq), false) == sizeof(esc_seq); +} + +static bool cia_process_scode(uint8_t scode) { + bool resched = false; + bool keyUpBit; + int keyCode; + + keyUpBit = (scode & 0x80) != 0; + scode &= 0x7f; + + if (scode == SCANCODE_LSHIFT) { + key_lshift = !keyUpBit; + } + + if (scode == SCANCODE_RSHIFT) { + key_rshift = !keyUpBit; + } + + if (!keyUpBit) { + switch (scode) { + case 0x4f: // LEFT + resched = generate_esc_seq(key_buf, 0x1b, '[', 'D'); + break; + case 0x4e: // RIGHT + resched = generate_esc_seq(key_buf, 0x1b, '[', 'C'); + break; + case 0x4c: // UP + resched = generate_esc_seq(key_buf, 0x1b, '[', 'A'); + break; + case 0x4d: // DOWN + resched = generate_esc_seq(key_buf, 0x1b, '[', 'B'); + break; + } + } + + if (key_lshift || key_rshift) { + keyCode = KeyCodeSingleUpper[scode]; + } else { + keyCode = KeyCodeSingleLower[scode]; + } + + if (keyCode != 0 && !keyUpBit) { + resched = cbuf_write_char(key_buf, keyCode, false); + } + + cia_base[CIA_A_CRA] |= (1 << 6); + cia_base[CIA_A_CRA] &= ~(1 << 6); + + return resched; +} + +static enum handler_return cia_kbd_interrupt(void *arg) { + bool resched = false; + uint8_t sdr = cia_base[CIA_A_SDR]; + uint8_t scode = decode_key(sdr); + + resched = cia_process_scode(scode); + + return resched ? INT_RESCHEDULE : INT_NO_RESCHEDULE; +} + +int platform_read_key(char *c) { + return cbuf_read_char(key_buf, c, true); +} + +void platform_keyboard_init(cbuf_t *buffer) { + key_buf = buffer; + + cia_base[CIA_A_CRA] &= ~(1 << 6); // Set SPMODE to input + cia_base[CIA_A_ICR] = (1 << 7 | 1 << 3); // Set SP bit in ICR + + register_int_handler(INTERRUPT_SERP_A, cia_kbd_interrupt, NULL); + unmask_interrupt(INTERRUPT_PORTS); + unmask_interrupt(INTERRUPT_SERP_A); +} diff --git a/platform/amiga/platform.c b/platform/amiga/platform.c new file mode 100644 index 0000000000..dd0765b62c --- /dev/null +++ b/platform/amiga/platform.c @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2025-2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if WITH_KERNEL_VM +#include +#else +#include +#endif + +#include "platform_p.h" + +static volatile uint16_t *const paula_base = (void *)0xDFF000; + +// IRQ handler array +static struct int_handlers { + int_handler handler; + void *arg; +} handlers[NUM_IRQS_TOTAL]; + +static uint8_t cia_a_irqs_enabled, cia_b_irqs_enabled; + +static inline bool is_paula_irq(unsigned irq) { + return irq >= 1 && irq <= NUM_IRQS_PAULA; +} + +static inline bool is_valid_irq(unsigned irq) { + return (irq >= 1 && irq <= NUM_IRQS_TOTAL); +} + +static inline bool is_cia_a_irq(unsigned irq) { + return (irq >= (NUM_IRQS_PAULA + 1) && + irq <= (NUM_IRQS_PAULA + NUM_IRQS_CIA)); +} + +static inline bool is_cia_b_irq(unsigned irq) { + return (irq >= (NUM_IRQS_TOTAL - NUM_IRQS_CIA) && irq <= NUM_IRQS_TOTAL); +} + +static void write_reg(unsigned int reg, uint32_t val) { + paula_base[reg >> 1] = val; +} + +static uint16_t read_reg(unsigned int reg) { + return paula_base[reg >> 1]; +} + +status_t mask_interrupt(unsigned int irq) { + if (!is_valid_irq(irq)) { + return ERR_INVALID_ARGS; + } + + if (is_paula_irq(irq)) { + write_reg(INTENA, (uint16_t)(0x0000 | (1 << (irq - 1)))); + return NO_ERROR; + } + + volatile uint8_t *cia_base; + uint16_t cia_icr; + uint8_t bit; + + if (is_cia_a_irq(irq)) { + cia_base = (void *)CIA_A_BASE; + cia_icr = CIA_A_ICR; + bit = irq - (NUM_IRQS_PAULA + 1); + cia_a_irqs_enabled &= ~(1 << bit); + } else if (is_cia_b_irq(irq)) { + cia_base = (void *)CIA_B_BASE; + cia_icr = CIA_B_ICR; + bit = irq - (NUM_IRQS_PAULA + NUM_IRQS_CIA + 1); + cia_b_irqs_enabled &= ~(1 << bit); + } else { + return ERR_INVALID_ARGS; + } + + cia_base[cia_icr] = (0x00 | (1 << bit)); + + return NO_ERROR; +} + +status_t unmask_interrupt(unsigned int irq) { + if (!is_valid_irq(irq)) { + return ERR_INVALID_ARGS; + } + + if (is_paula_irq(irq)) { + write_reg(INTENA, (uint16_t)(0x8000 | (1 << (irq - 1)))); + return NO_ERROR; + } + + volatile uint8_t *cia_base; + uint16_t cia_icr; + uint8_t bit; + + if (is_cia_a_irq(irq)) { + cia_base = (void *)CIA_A_BASE; + cia_icr = CIA_A_ICR; + bit = (irq - (NUM_IRQS_PAULA + 1)); + cia_a_irqs_enabled |= (1 << bit); + } else if (is_cia_b_irq(irq)) { + cia_base = (void *)CIA_B_BASE; + cia_icr = CIA_B_ICR; + bit = (irq - (NUM_IRQS_PAULA + NUM_IRQS_CIA)) - 1; + cia_b_irqs_enabled |= (1 << bit); + } else { + return ERR_INVALID_ARGS; + } + + cia_base[cia_icr] = (0x80 | (1 << bit)); + + return NO_ERROR; +} + +status_t clear_interrupt(unsigned int irq) { + // TODO: Do we need CIA support here? Reading ICR should clear + + write_reg(INTREQ, (uint16_t)(1 << (irq - 1))); + + return NO_ERROR; +} + +void register_int_handler(unsigned int vector, int_handler handler, void *arg) { + if (vector >= 1 && vector <= NUM_IRQS_TOTAL) { + handlers[vector - 1].handler = handler; + handlers[vector - 1].arg = arg; + } +} + +enum handler_return m68k_platform_irq(uint8_t m68k_irq) { + unsigned int level = m68k_irq - 24; + + // TODO: Possible cleanup here, make naming clearer + uint16_t paula_pending = *(paula_base + (INTREQR >> 1)); + uint16_t paula_enabled = *(paula_base + (INTENAR >> 1)); + + uint16_t paula_all = (paula_pending & paula_enabled); + uint16_t paula_this_level = (paula_all & irq_level_map[level - 1]); + uint32_t combined = (uint32_t)(paula_this_level & ~((1 << CIA_A_MUX_LEVEL) | + (1 << CIA_B_MUX_LEVEL))); + + uint16_t to_clear = 0; + + /* + * Read from CIA ICRs only when corresponding Paula interrupts have fired (IRQ + * 4 & 14). This read also causes CIA interrupt state and ICR to be cleared. + * + * Bit 7 (set/clear bit) of ICR indicates whether one of the IRQ bits are set. + * + * Bits 5 & 6 are unused. + * + */ + if (paula_this_level & (1 << CIA_A_MUX_LEVEL)) { + volatile uint8_t *const cia_a_base = (void *)CIA_A_BASE; + uint8_t icr = cia_a_base[CIA_A_ICR]; + uint8_t pending = icr & 0x1F; + uint32_t enabled = pending & cia_a_irqs_enabled; + + if ((icr & 0x80) && enabled) { + // We're only interested in the lower 5 bits + combined |= (enabled << NUM_IRQS_PAULA); + } + to_clear |= (1 << CIA_A_MUX_LEVEL); + } + + if (paula_this_level & (1 << CIA_B_MUX_LEVEL)) { + volatile uint8_t *const cia_b_base = (void *)CIA_B_BASE; + uint8_t icr = cia_b_base[CIA_B_ICR]; + uint8_t pending = icr & 0x1F; + uint32_t enabled = pending & cia_b_irqs_enabled; + + if ((icr & 0x80) && enabled) { + combined |= (enabled << (NUM_IRQS_PAULA + NUM_IRQS_CIA)); + } + + to_clear |= (1 << CIA_B_MUX_LEVEL); + } + + THREAD_STATS_INC(interrupts); + KEVLOG_IRQ_ENTER(m68k_irq); + + if (combined == 0) { + if (to_clear) { + write_reg(INTREQ, to_clear); + } + return INT_NO_RESCHEDULE; + } + + enum handler_return ret = INT_NO_RESCHEDULE; + + while (combined) { + int irq_bit = ctz(combined); + combined &= ~(1 << irq_bit); // Clear IRQ bit + + if (handlers[irq_bit].handler) { + ret = handlers[irq_bit].handler(handlers[irq_bit].arg); + } + } + + write_reg(INTREQ, to_clear); + + KEVLOG_IRQ_EXIT(m68k_irq); + + return ret; +} + +void platform_early_init(void) { + // Start with a clean interrupt slate, we'll selectively enable/unmask as needed. + // Disable and clear all Paula interrupts initially. + write_reg(INTENA, 0x7FFF); + write_reg(INTREQ, 0x7FFF); + + // Enable Paula master interrupt bit + write_reg(INTENA, 0xC000); + + // Enable Paula 'EXTER' interrupts, needed for CIA-B timers + unmask_interrupt(INTERRUPT_EXTER); + + // Chip mem size is passed in by the bootloader. novm automatically + // moves an arena to avoid overlapping with the kernel itself. + novm_add_arena("mem", MEMBASE, lk_boot_args[0]); + + platform_init_display(); + platform_serial_init(); + cia_timer_init(); +} + +void platform_dputc(char c) { + if (c == '\n') { + platform_dputc('\r'); + } + + uart_putc(c); // TODO: is it possible to have multiple consoles? +} + +int platform_dgetc(char *c, bool wait) { + return cbuf_read_char(&console_input_cbuf, c, wait); +} + +void platform_init(void) { + platform_keyboard_init(&console_input_cbuf); +} diff --git a/platform/amiga/platform_p.h b/platform/amiga/platform_p.h new file mode 100644 index 0000000000..7c0cdd3c51 --- /dev/null +++ b/platform/amiga/platform_p.h @@ -0,0 +1,162 @@ +#pragma once +/* + * Copyright (c) 2025-2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include +#include +#include +#include + +// CIA-A registers +#define CIA_A_BASE 0xBFE000 +#define CIA_A_PRA 0x001 +#define CIA_A_PRB 0x101 +#define CIA_A_DDRA 0x201 +#define CIA_A_DDRB 0x301 +#define CIA_A_TALO 0x401 +#define CIA_A_TAHI 0x501 +#define CIA_A_TBLO 0x601 +#define CIA_A_TBHI 0x701 +#define CIA_A_TODLO 0x801 +#define CIA_A_TODMID 0x901 +#define CIA_A_TODHI 0xA01 +#define CIA_A_SDR 0xC01 +#define CIA_A_ICR 0xD01 +#define CIA_A_CRA 0xE01 +#define CIA_A_CRB 0xF01 + +// CIA-A control flags +#define CIA_A_CRA_START (1 << 0) +#define CIA_A_CRA_LOAD (1 << 4) +#define CIA_A_CRA_SERIAL (1 << 6) + +// CIA-B registers +#define CIA_B_BASE 0xBFD000 +#define CIA_B_PRA 0x000 +#define CIA_B_PRB 0x100 +#define CIA_B_DDRA 0x200 +#define CIA_B_DDRB 0x300 +#define CIA_B_TALO 0x400 +#define CIA_B_TAHI 0x500 +#define CIA_B_TBLO 0x600 +#define CIA_B_TBHI 0x700 +#define CIA_B_TODLO 0x800 +#define CIA_B_TODMID 0x900 +#define CIA_B_TODHI 0xA00 +#define CIA_B_SDR 0xC00 +#define CIA_B_ICR 0xD00 +#define CIA_B_CRA 0xE00 +#define CIA_B_CRB 0xF00 + +// CIA-B control flags +#define CIA_B_CRA_START (1 << 0) +#define CIA_B_CRA_LOAD (1 << 4) +#define CIA_B_CRA_SERIAL (1 << 6) + +#define DMACON 0x096 +#define COP1LCH 0x080 +#define COP1LCL 0x082 +#define COPJMP1 0x088 + +#define BPLCON0 0x100 +#define BPLCON1 0x102 +#define BPLCON2 0x104 + +#define BPL1PTH 0x0E0 +#define BPL1PTL 0x0E2 +#define BPL1MOD 0x108 +#define BPL2MOD 0x10A + +#define DIWSTRT 0x08E +#define DIWSTOP 0x090 +#define DDFSTRT 0x092 +#define DDFSTOP 0x094 + +#define COLOR00 0x180 +#define COLOR01 0x182 + +/* + * Interrupts are defined here with 1-based indexing, for simplicity/readability. + * + * We treat/define CIA interrupt numbers like a continuation of Paula's. + * + */ +enum { + // Paula + INTERRUPT_TBE = 1, + INTERRUPT_DSKBLK, + INTERRUPT_SOFTINT, + INTERRUPT_PORTS, + INTERRUPT_COPER, + INTERRUPT_VERTB, + INTERRUPT_BLIT, + INTERRUPT_AUD2, + INTERRUPT_AUD0, + INTERRUPT_AUD3, + INTERRUPT_AUD1, + INTERRUPT_RBF, + INTERRUPT_DSKSYN, + INTERRUPT_EXTER, + + // CIA A + INTERRUPT_TIMERA_A, + INTERRUPT_TIMERB_A, + INTERRUPT_TOD_A, + INTERRUPT_SERP_A, + INTERRUPT_FLAG_A, + + // CIA B + INTERRUPT_TIMERA_B, + INTERRUPT_TIMERB_B, + INTERRUPT_TOD_B, + INTERRUPT_SERP_B, + INTERRUPT_FLAG_B, +}; + +// Used for dealing with Amiga interrupt multiplexing +static const uint16_t irq_level_map[] = { + 0x0007, // CPU level 1 + 0x300C, // CPU level 2 + 0x0070, // CPU level 3 + 0x0780, // CPU level 4 + 0x1800, // CPU level 5 + 0x2000, // CPU level 6 +}; + +// Paula interrupt register offsets +enum { + INTREQ = 0x9c, + INTENA = 0x9a, + INTREQR = 0x1e, + INTENAR = 0x1c, +}; + +// Fourteen chipset-level interrupts from Paula, and five per CIA +enum { + NUM_IRQS_TOTAL = 24, + NUM_IRQS_PAULA = 14, + NUM_IRQS_CIA = 5, +}; + +// Interrupts originating from each CIA are multiplexed/nested within CPU & +// chipset-level IRQs. These values correspond to Paula's interrupt bits. +enum { + CIA_A_MUX_LEVEL = 3, // 'PORTS' IRQ, CIA-A and INT2 + CIA_B_MUX_LEVEL = 13, // 'EXTER' IRQ, CIA-B and INT6 +}; + +void cia_timer_init(void); +void platform_serial_init(void); +void platform_keyboard_init(cbuf_t *buffer); +int platform_dgetc(char *c, bool wait); + +void uart_putc(char c); +int uart_getc(char *c, bool wait); +void dputc(char c); + +status_t clear_interrupt(unsigned int bit); diff --git a/platform/amiga/power.c b/platform/amiga/power.c new file mode 100644 index 0000000000..5288c230fe --- /dev/null +++ b/platform/amiga/power.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include "platform_p.h" +#include + +static volatile uint8_t *const cia_base = (volatile uint8_t *)CIA_A_BASE; +static volatile uint16_t *const paula_base = (void *)0xDFF000; + +static void amiga_reboot(void) { + cia_base[CIA_A_DDRB >> 1] |= 0x80; // Set port direction + cia_base[CIA_A_PRB >> 1] &= ~0x80; // Assert reset bit +} + +static void amiga_shutdown(void) { + // Might be able to get away with just the 'stop' + paula_base[0x9c >> 1] = 0x7fff; // INTREQ + paula_base[0x9a >> 1] = 0x7fff; // INTENA + paula_base[0x96 >> 1] = 0x7fff; // DMACON + + __asm__ volatile("stop #0x2700"); +} + +void platform_halt(platform_halt_action suggested_action, platform_halt_reason reason) { + dprintf(INFO, "halt action %s, reason %s\n", + platform_halt_action_string(suggested_action), + platform_halt_reason_string(reason)); + platform_halt_default(suggested_action, reason, &amiga_reboot, &amiga_shutdown); +} diff --git a/platform/amiga/rules.mk b/platform/amiga/rules.mk new file mode 100644 index 0000000000..5979c65d7b --- /dev/null +++ b/platform/amiga/rules.mk @@ -0,0 +1,86 @@ +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +ARCH := m68k +# NOTE: All chip models are supported, but 060 needs explicit targeting +M68K_CPU := 68000 + +LK_HEAP_IMPLEMENTATION ?= dlmalloc + +# Optional: Include useful libs +MODULE_DEPS += \ + lib/cbuf \ + lib/console \ + lib/gfxconsole \ + app/tests \ + +# Your platform-specific source files +MODULE_SRCS += $(LOCAL_DIR)/display.c +MODULE_SRCS += $(LOCAL_DIR)/platform.c +MODULE_SRCS += $(LOCAL_DIR)/serial.c +MODULE_SRCS += $(LOCAL_DIR)/keyboard.c +MODULE_SRCS += $(LOCAL_DIR)/timer.c +MODULE_SRCS += $(LOCAL_DIR)/power.c +MODULE_SRCS += $(LOCAL_DIR)/stage1.S +MODULE_SRCS += $(LOCAL_DIR)/stage2.S + +# RAM layout +MEMBASE ?= 0x400 +MEMSIZE ?= 0x7c800 # Target 512KB chip ram for now + +# Optional useful defines +GLOBAL_DEFINES += PLATFORM_SUPPORTS_PANIC_SHELL=1 +GLOBAL_DEFINES += PLATFORM_HAS_DYNAMIC_TIMER=1 # unless you add a real timer +GLOBAL_DEFINES += NOVM_DEFAULT_ARENA=0 +GLOBAL_DEFINES += ARCH_DO_RELOCATION=1 +GLOBAL_DEFINES += CONSOLE_HAS_INPUT_BUFFER=1 + +ADF_GEN := $(BUILDDIR)/platform/amiga/adfgen +ADF_GEN_SRC := $(LOCAL_DIR)/adfgen.c + +STAGE1_ELF := $(BUILDDIR)/platform/amiga/stage1.S.o +STAGE1_RAW := $(BUILDDIR)/platform/amiga/stage1.raw + +STAGE2_ELF := $(BUILDDIR)/platform/amiga/stage2.S.o +STAGE2_RAW := $(BUILDDIR)/platform/amiga/stage2.raw + +BOOTLOADER := $(BUILDDIR)/platform/amiga/bootloader.raw + +KERNEL_IMAGE := $(OUTBIN) +ADF_IMAGE := $(basename $(KERNEL_IMAGE)).adf + +HOST_CC ?= cc + +$(ADF_GEN): $(ADF_GEN_SRC) + mkdir -p $(dir $@) + $(HOST_CC) -o $@ $< + +$(STAGE1_RAW): $(STAGE1_ELF) + m68k-elf-objcopy -O binary $< $@ + truncate -s 512 $@; \ + +# Build and pad stage2 payload +$(STAGE2_RAW): $(STAGE2_ELF) + m68k-elf-objcopy -O binary $< $@ + truncate -s 512 $@ + +$(BOOTLOADER): $(STAGE1_RAW) $(STAGE2_RAW) + dd if=/dev/zero bs=1024 count=1 of=$@; \ + + dd if=$(STAGE1_RAW) of=$@ bs=1 seek=0 conv=notrunc; \ + dd if=$(STAGE2_RAW) of=$@ bs=1 seek=512 conv=notrunc; \ + truncate -s 1012 $@; \ + +$(ADF_IMAGE): $(KERNEL_IMAGE) $(BOOTLOADER) $(ADF_GEN) + rm -f $@ + $(ADF_GEN) $(BOOTLOADER) $@ + @KSIZE=$$(stat -c %s "$(KERNEL_IMAGE)"); \ + echo -n "$$KSIZE"; \ + printf '%08x' "$$KSIZE" | xxd -r -p | dd of=$@ bs=1024 seek=1 conv=notrunc; \ + dd if=$(KERNEL_IMAGE) of=$@ bs=1024 seek=2 conv=notrunc; \ + +EXTRA_BUILDDEPS += $(ADF_IMAGE) +GENERATED += $(ADF_GEN) $(ADF_IMAGE) $(BOOTLOADER) $(STAGE1_RAW) $(STAGE2_RAW) $(STAGE1_ELF) $(STAGE2_ELF) + +include make/module.mk diff --git a/platform/amiga/serial.c b/platform/amiga/serial.c new file mode 100644 index 0000000000..7f311cbd9f --- /dev/null +++ b/platform/amiga/serial.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2025-2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include "platform_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TBE_STATUS (1 << 13) +#define RBF_STATUS (1 << 14) + +#define RXBUF_SIZE 32 + +static event_t tx_ev; +static thread_t *tx_thread = NULL; +static spin_lock_t tx_lock = SPIN_LOCK_INITIAL_VALUE; + +static volatile bool tx_active = false; +static volatile bool tx_kick_pending = false; +static volatile bool tx_writer_started = false; + +static cbuf_t rx_buf; +static cbuf_t tx_buf; +static char rx_buf_data[RXBUF_SIZE]; + +// Serial register offsets, relative to Paula base register +// TODO: Move this somewhere else? (header?) +#define SERDAT 0x030 +#define SERDATR 0x018 +#define SERPER 0x032 + +static volatile uint16_t *serial_reg = (volatile uint16_t *)0xDFF000; + +static void write_reg(unsigned int reg, uint32_t val) { + serial_reg[reg >> 1] = (uint16_t)val; +} + +static uint16_t read_reg(unsigned int reg) { + return serial_reg[reg >> 1]; +} + +// ACK and wake thread/event +static enum handler_return uart_irq_tx_handler(void *arg) { + clear_interrupt(INTERRUPT_TBE); + event_signal(&tx_ev, false); + return INT_NO_RESCHEDULE; +} + +static enum handler_return uart_irq_rx_handler(void *arg) { + bool resched = false; + + uint16_t reg = read_reg(SERDATR); + clear_interrupt(INTERRUPT_RBF); + + if ((reg & RBF_STATUS) > 0) { + char c = reg & 0xFF; + +#if CONSOLE_HAS_INPUT_BUFFER + cbuf_t *target_buf = &console_input_cbuf; +#else + cbuf_t *target_buf = &rx_buf; +#endif + + cbuf_write_char(target_buf, c, false); + resched = true; + } + + return resched ? INT_RESCHEDULE : INT_NO_RESCHEDULE; +} + +void platform_serial_init(void) { + cbuf_initialize(&tx_buf, 256); + cbuf_initialize_etc(&rx_buf, RXBUF_SIZE, rx_buf_data); + + register_int_handler(INTERRUPT_TBE, uart_irq_tx_handler, NULL); + register_int_handler(INTERRUPT_RBF, uart_irq_rx_handler, NULL); + unmask_interrupt(INTERRUPT_RBF); + + event_init(&tx_ev, false, EVENT_FLAG_AUTOUNSIGNAL); +} + +// Drain all queued bytes, mask TX IRQ when empty +static int uart_write_thread(void *arg) { + for (;;) { + event_wait(&tx_ev); + + for (;;) { + // Retrieve bytes from cbuf, mask and idle if none + char c; + spin_lock(&tx_lock); + if (cbuf_read_char(&tx_buf, &c, false) != 1) { + tx_kick_pending = false; + tx_active = false; + mask_interrupt(INTERRUPT_TBE); // Prevent TBE storm + spin_unlock(&tx_lock); + break; + } + + tx_active = true; + spin_unlock(&tx_lock); + + // Wait until transmitter is ready + while ((read_reg(SERDATR) & TBE_STATUS) == 0) { + } + write_reg(SERDAT, c | 0x200); + + // Keep interrupt enabled while there's data + unmask_interrupt(INTERRUPT_TBE); + } + } + + return 0; +} + +void uart_putc(char c) { + bool irqs_disabled = arch_ints_disabled(); + + if (!tx_writer_started && !irqs_disabled) { + tx_thread = thread_create("[uart writer]", uart_write_thread, NULL, + DEFAULT_PRIORITY, DEFAULT_STACK_SIZE); + thread_resume(tx_thread); + tx_writer_started = true; + } + + spin_lock(&tx_lock); + + // Drain queue -> Send current byte -> Drain again -> mask TX + if (irqs_disabled) { + char q; + + // Drain any queued bytes first + while (cbuf_read_char(&tx_buf, &q, false) == 1) { + while ((read_reg(SERDATR) & TBE_STATUS) == 0) { + } + write_reg(SERDAT, q | 0x200); + } + + while ((read_reg(SERDATR) & TBE_STATUS) == 0) { + } + write_reg(SERDAT, c | 0x200); + + // Drain anything that came along in the meantime + while (cbuf_read_char(&tx_buf, &q, false) == 1) { + while ((read_reg(SERDATR) & TBE_STATUS) == 0) { + } + write_reg(SERDAT, q | 0x200); + } + + // Queue should be empty now... + tx_active = false; + mask_interrupt(INTERRUPT_TBE); + + spin_unlock(&tx_lock); + + return; + } + + /* normal path: enqueue and let worker/IRQ drain */ + size_t written = cbuf_write_char(&tx_buf, c, false); + + // Arm TX and wake writer thread + if (!tx_active) { + tx_active = true; + unmask_interrupt(INTERRUPT_TBE); + event_signal(&tx_ev, false); + } + + // Handle buffer being full, write when we can + while (written == 0) { + spin_unlock(&tx_lock); + event_signal(&tx_ev, false); + spin_lock(&tx_lock); + written = cbuf_write_char(&tx_buf, c, false); + } + + spin_unlock(&tx_lock); +} + +int uart_getc(char *c, bool wait) { + return cbuf_read_char(&rx_buf, c, wait); +} + +// TODO: Implement panic stuff properly +int platform_pgetc(char *c, bool wait) { + uint16_t reg = read_reg(SERDATR); + + if ((reg & (1 << 14)) > 0) { + *c = reg & 0xFF; + return 0; + } + + return -1; +} + +void platform_pputc(char c) { + uart_putc(c); +} diff --git a/platform/amiga/stage1.S b/platform/amiga/stage1.S new file mode 100644 index 0000000000..c6bdd60994 --- /dev/null +++ b/platform/amiga/stage1.S @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2025-2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +.text + +lea 0(%pc),%a4 +add.l #0x200,%a4 /* skip bootblock */ + +jmp (%a4) diff --git a/platform/amiga/stage2.S b/platform/amiga/stage2.S new file mode 100644 index 0000000000..9971450e84 --- /dev/null +++ b/platform/amiga/stage2.S @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2025-2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +.text + +kaddr_ptr: + .dc.l 0 +ioreq_ptr: + .dc.l 0 +hdr_ptr: + .dc.l 0 +ksize: + .dc.l 0 + +/* AllocMem stuff */ +.equ MEMF_ANY, 0x00000000 +.equ MEMF_PUBLIC, 0x00000001 +.equ MEMF_CHIP, 0x00000002 +.equ MEMF_FAST, 0x00000004 + +.equ MEMF_TOTAL, 0x00080000 + +.equ MEMF_LOCAL, 0x00000100 +.equ MEMF_24BITDMA, 0x00000200 +.equ MEMF_KICK, 0x00000400 + +.equ MEMF_CLEAR, 0x00010000 +.equ MEMF_LARGEST, 0x00020000 +.equ MEMF_REVERSE, 0x00040000 + +/* Kickstart Exec library routine offsets */ +.equ _LVOAllocAbs, -204 +.equ _LVOAllocMem, -198 +.equ _LVOAvailMem, -216 +.equ _LVOCacheClearU, -636 +.equ _LVOWaitIO, -318 +.equ _LVOCheckIO, -468 +.equ _LVODoIO, -456 + +/* IORequest stuff */ +.equ CMD_READ, 2 +.equ io_Unit, 24 +.equ io_Command, 28 +.equ io_Error, 31 +.equ io_Length, 36 +.equ io_Data, 40 +.equ io_Offset, 44 + +/* Stack stuff */ +.equ STACK_TOP, 0x00070000 +.equ STACK_SIZE, 0x00004000 +.equ STACK_BASE, (STACK_TOP-STACK_SIZE) + +lea STACK_TOP,%sp + +lea loader(%pc),%a5 +move.l %a5,0x00000080 /* Trap handler */ + +/* Enter supervisor mode */ +trap #0 + +loader: + lea STACK_TOP,%sp + movea.l 0x00000004.w,%a6 /* Exec base */ + + /* Stash IORequest address, passed in from Kickstart */ + lea ioreq_ptr,%a5 + move.l %a1,(%a5) + + move.l #STACK_SIZE,%d0 + move.l #STACK_BASE,%a1 + jsr _LVOAllocAbs(%a6) + tst.l %d0 + beq failure + + /* Reserve memory and load header */ + move.l #1024,%d0 + move.l #(MEMF_CLEAR + MEMF_PUBLIC + MEMF_CHIP),%d1 + jsr _LVOAllocMem(%a6) + tst.l %d0 + beq failure + lea hdr_ptr,%a5 + move.l %d0,(%a5) + move.l %d0,%a3 + + /* Restore IOReq ptr */ + lea ioreq_ptr,%a1 + move.l (%a1),%a1 + + bsr wait_ioreq_idle + + /* Retrieve kernel image size from header in ADF */ + move.l #(2*512),io_Length(%a1) + move.l %a3,io_Data(%a1) /* Read header from floppy in to memory provided by AllocMem */ + move.l #0x400,io_Offset(%a1) + move.w #CMD_READ,io_Command(%a1) + jsr _LVODoIO(%a6) + tst.b io_Error(%a1) + bne failure + + /* Determine how much trackdisk should read, based on kernel size. Round to next 512 */ + lea ksize(%pc),%a4 + move.l (%a3),%d2 + add.l #511,%d2 + and.l #0xFFFFFE00,%d2 + move.l %d2,(%a4) + + /* Allocate memory for kernel image */ + move.l (%a4),%d0 + move.l #(MEMF_REVERSE + MEMF_CLEAR + MEMF_CHIP),%d1 + jsr _LVOAllocMem(%a6) + tst.l %d0 + beq failure + lea kaddr_ptr,%a0 + move.l %d0,(%a0) + move.l %d0,%a2 + + /* Restore IOReq pointer before next DoIO call */ + lea ioreq_ptr,%a1 + move.l (%a1),%a1 + + bsr wait_ioreq_idle + + /* Load kernel from floppy in to RAM */ + lea ksize(%pc),%a4 + move.l (%a4),io_Length(%a1) + move.l %a2,io_Data(%a1) /* Read kernel from floppy in to memory provided by AllocMem */ + move.l #0x800,io_Offset(%a1) + move.w #CMD_READ,io_Command(%a1) + jsr _LVODoIO(%a6) + tst.b io_Error(%a1) + bne failure + + bsr wait_ioreq_idle + + move.w #9,io_Command(%a1) /* td_motor */ + clr.l io_Length(%a1) /* Turn off FDD motor */ + jsr _LVODoIO(%a6) + tst.b io_Error(%a1) + bne failure + + lea kaddr_ptr,%a0 + move.l (%a0),%a2 + jsr _LVOCacheClearU(%a6) + + /* Detect available chip RAM, pass to kernel via stack */ + move.l #(MEMF_CHIP + MEMF_TOTAL),%d1 + jsr _LVOAvailMem(%a6) + + clr.l -(%sp) + clr.l -(%sp) + clr.l -(%sp) + move.l %d0,-(%sp) + + /* Jump to kernel. Kickstart will perform cleanup, etc. */ + move.l #0,%d0 + move.l %a2,%a0 + jsr (%a0) + +failure: + | TODO: Throw a useful error, more graceful failure + move.w #0xFF00,0x00DFF180 /* Flash a colour on screen for debugging */ + moveq #0xfffffff0,%d0 + rts + +wait_ioreq_idle: + move.l %a1,-(%sp) + jsr _LVOCheckIO(%a6) + move.l (%sp),%a1 + tst.l %d0 + bne.s .done + + jsr _LVOWaitIO(%a6) +.done: + move.l (%sp)+,%a1 + rts diff --git a/platform/amiga/timer.c b/platform/amiga/timer.c new file mode 100644 index 0000000000..43d0bdfdf2 --- /dev/null +++ b/platform/amiga/timer.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2025-2026 Josh Cummings + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ + +#include "platform_p.h" + +#include +#include +#include +#include +#include +#include +#include + +static volatile uint8_t *const cia_base = (volatile uint8_t *)CIA_B_BASE; + +uint32_t eclockfreq; +uint32_t ciafreq; +uint32_t eclock_hz; +uint32_t cia_clock_interval; + +static uint16_t ta_last; +static uint64_t ta_ticks; + +static void *callback_arg; +static platform_timer_callback t_callback; +static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE; + +static uint8_t read_reg(unsigned int reg) { + return cia_base[reg]; +} + +static void write_reg(unsigned int reg, uint8_t val) { + cia_base[reg] = val; +} + +static uint16_t get_ta_tick(void) { + uint8_t ta_lo = read_reg(CIA_B_TALO); + uint8_t ta_hi1 = read_reg(CIA_B_TAHI); + uint8_t ta_hi2 = read_reg(CIA_B_TAHI); + + if (ta_hi1 != ta_hi2) { + ta_lo = read_reg(CIA_B_TALO); + ta_hi1 = ta_hi2; + } + + return (((uint16_t)ta_hi1 << 8) | ta_lo); +} + +static uint32_t calculate_eclock(void) { + const uint32_t tod_ticks = 128; + uint32_t seen_ticks = 0; + uint64_t tick_accum = 0; + uint64_t hz; + + // Configure and start CIA-B Timer A in free-running, continuous mode + write_reg(CIA_B_CRA, 0x00); // STOP TA + write_reg(CIA_B_TAHI, 0xFF); + write_reg(CIA_B_TALO, 0xFF); + + // Make sure TA interrupt is disabled: write ICR without bit7 to clear enable + // TODO: mask_interrupt? + write_reg(CIA_B_ICR, 0x01); // clear-enable TA (bit0) + write_reg(CIA_B_CRA, (1 << 0)); // START=1, RUNMODE=0 (periodic/continuous) + + uint16_t last_ta_tick = get_ta_tick(); + uint8_t last_tod_tick = read_reg(CIA_B_TODMID); + + while (seen_ticks < tod_ticks) { + uint16_t now_ta = get_ta_tick(); + tick_accum += (last_ta_tick - now_ta); + last_ta_tick = now_ta; + + uint8_t tod = read_reg(CIA_B_TODMID); // & 0x0F; + + if (tod != last_tod_tick) { + seen_ticks++; + last_tod_tick = tod; + } + } + + uint64_t palfreq = (tick_accum * 50) / (uint64_t)tod_ticks; + uint64_t ntscfreq = (tick_accum * 60) / (uint64_t)tod_ticks; + + const uint64_t lo = 650000, hi = 780000; + const uint64_t pal_nom = 709379, ntsc_nom = 715909; + + bool in50 = (palfreq >= lo && palfreq <= hi); + bool in60 = (ntscfreq >= lo && ntscfreq <= hi); + + if (in50 && in60) { + uint64_t d50 = + (palfreq > pal_nom) ? (palfreq - pal_nom) : (pal_nom - palfreq); + uint64_t d60 = + (ntscfreq > ntsc_nom) ? (ntscfreq - ntsc_nom) : (ntsc_nom - ntscfreq); + hz = (d50 <= d60) ? palfreq : ntscfreq; + } else if (in50) { + hz = palfreq; + } else if (in60) { + hz = ntscfreq; + } else { + // Fallback if TOD was paused or we sampled during reset + hz = pal_nom; + } + + return (uint32_t)hz; +} + +lk_bigtime_t current_time_hires(void) { + uint16_t now = get_ta_tick(); + uint16_t delta = (uint16_t)(ta_last - now); + + ta_last = now; + ta_ticks += delta; + + return (lk_bigtime_t)((ta_ticks * 1000000ull) / eclock_hz); +} + +lk_time_t current_time(void) { + return (lk_time_t)(current_time_hires() / 1000); +} + +void platform_stop_timer(void) { + write_reg(CIA_B_CRB, 0x00); + mask_interrupt(INTERRUPT_TIMERB_B); +} + +static inline uint16_t ms_to_ticks(lk_time_t ms) { + uint64_t ticks = (uint64_t)ms * eclock_hz + 500; // round + ticks /= 1000; + if (ticks == 0) { + ticks = 1; + } + if (ticks > 65535) { + ticks = 65535; // single-shot ceiling + } + return (uint16_t)ticks; +} + +static enum handler_return cia_timer_irq(void *arg) { + cia_base[CIA_B_ICR]; // Clear ICR. TODO: Remove when clear_interrupt() has CIA support + + if (t_callback) { + return t_callback(callback_arg, current_time()); + } + + return INT_NO_RESCHEDULE; +} + +void cia_timer_init(void) { + eclock_hz = calculate_eclock(); + + mask_interrupt(INTERRUPT_TIMERA_B); + + // Continuous, free-running mode for Timer A + write_reg(CIA_B_TALO, 0xFF); + write_reg(CIA_B_TAHI, 0xFF); + write_reg(CIA_B_CRA, 0x01); // Start Timer A, RUNMODE = continuous + + ta_last = get_ta_tick(); + ta_ticks = 0; + + platform_stop_timer(); + + register_int_handler(INTERRUPT_TIMERB_B, cia_timer_irq, NULL); +} + +// TODO: Can some of oneshot and periodic's stuff be centralised and called by +// each? Lots of similarities +status_t platform_set_oneshot_timer(platform_timer_callback callback, void *arg, + lk_time_t interval) { + arch_interrupt_saved_state_t state = spin_lock_irqsave(&lock); + + t_callback = callback; + callback_arg = arg; + + uint16_t ticks = ms_to_ticks(interval); + platform_stop_timer(); + + write_reg(CIA_B_TBLO, (uint8_t)(ticks & 0xFF)); + write_reg(CIA_B_TBHI, (uint8_t)(ticks >> 8)); + + // Enable Timer B CIA interrupt + write_reg(CIA_B_ICR, 0x80 | 0x02); + + unmask_interrupt(INTERRUPT_TIMERB_B); + + // Start Timer B in one-shot mode. START | RUNMODE | LOAD + write_reg(CIA_B_CRB, (0 << 5) | (1 << 4) | (1 << 3) | (1 << 0)); + + spin_unlock_irqrestore(&lock, state); + + return NO_ERROR; +} + +int platform_set_periodic_timer(platform_timer_callback callback, void *arg, + lk_time_t interval) { + arch_interrupt_saved_state_t state = spin_lock_irqsave(&lock); + + t_callback = callback; + callback_arg = arg; + + uint16_t ticks = ms_to_ticks(interval); + + write_reg(CIA_B_TBLO, (uint8_t)(ticks & 0xFF)); + write_reg(CIA_B_TBHI, (uint8_t)(ticks >> 8)); + + spin_unlock_irqrestore(&lock, state); + + return NO_ERROR; +} diff --git a/project/amiga.mk b/project/amiga.mk new file mode 100644 index 0000000000..56b04a12c0 --- /dev/null +++ b/project/amiga.mk @@ -0,0 +1,6 @@ +TARGET := amiga + +MODULES += \ + app/shell + +include project/target/amiga.mk diff --git a/project/target/amiga.mk b/project/target/amiga.mk new file mode 100644 index 0000000000..ce1bb4a6c7 --- /dev/null +++ b/project/target/amiga.mk @@ -0,0 +1 @@ +PLATFORM := amiga diff --git a/target/amiga/rules.mk b/target/amiga/rules.mk new file mode 100644 index 0000000000..e69de29bb2