mos-hardware 0.4.0

Hardware register tables and support functions for 8-bit retro computers like the Commodore 64, MEGA65 and others.
Documentation
// copyright 2022 mikael lund aka wombat
// 
// licensed under the apache license, version 2.0 (the "license");
// you may not use this file except in compliance with the license.
// you may obtain a copy of the license at
// 
//     http://www.apache.org/licenses/license-2.0
// 
// unless required by applicable law or agreed to in writing, software
// distributed under the license is distributed on an "as is" basis,
// without warranties or conditions of any kind, either express or implied.
// see the license for the specific language governing permissions and
// limitations under the license.

#include <stdbool.h>
#include <stdint.h>

#define PEEK(address) (*(uint8_t *)(address))
#define POKE(address, value) *(uint8_t *)(address) = (value)

/**
 * This function *must* be defined on the Rust side and is
 * called for every IRQ event.
 */
void called_every_frame();

typedef void (*function_type)(void);

function_type *const KERNAL_IRQ = (function_type *)0x0314;
function_type *const HARDWARE_IRQ = (function_type *)0xfffe;

/**
 * @brief Initialize raster interrupt
 * @param irq_function Function to call on each triggerin event
 * @param triggering_raster_line VIC-II raster line to trigger irq
 * @param irq_address IRQ address to use, e.g. 0x0315 or 0xfffe
 * @param kill_kernal_and_basic Set to true to disable KERNAL and BASIC roms
 */
void init_raster_irq(function_type irq_function, uint8_t triggering_raster_line,
                     function_type *const irq_address,
                     bool kill_kernal_and_basic) {
  asm volatile(
      "sei\n" // disable maskable IRQs
      "lda #$7f\n"
      "sta $dc0d\n" // disable timer interrupts which can be generated by the
                    // two CIA chips
      "sta $dd0d\n" // kernal uses such an interrupt to flash the cursor and
                    // scan the keyboard, so we better stop it.
      "lda $dc0d\n" // by reading this two registers we negate any pending CIA
                    // irqs.
      "lda $dd0d\n" // if we don't do this, a pending CIA irq might occur after
                    // we finish setting up our irq. we don't want that to
                    // happen.
      ::
          : "a");

  POKE(0xd01a, 0x01); // tell VICII to generate a raster interrupt
  POKE(0xd012, triggering_raster_line); // raster line to trigger irq

  // as there are more than 256 rasterlines, the topmost bit of $d011 serves
  // as the 9th bit for the rasterline we want our irq to be triggered. here
  // we simply set up a character screen, leaving the topmost bit 0.
  POKE(0xd011, 0x1b);

  if (kill_kernal_and_basic) {
    POKE(0x01, 0x35);
  }

  *irq_address = irq_function; // set interrupt vectors,

  // clear interrupt flag, allowing the CPU to respond to interrupt requests
  asm volatile("cli");
}

/**
 * Wrapper for the imported rust function `called_every_frame()`.
 */
__attribute__((interrupt)) void irq_wrapper(void) {
  called_every_frame();
  // acknowledge the interrupt by clearing the VIC's interrupt flag
  asm volatile("lsr $d019");
}

/**
 * Trigger hardware raster IRQ to the rust defined `called_every_frame()`
 */
void hardware_raster_irq_c(uint8_t triggering_raster_line) {
  init_raster_irq(&irq_wrapper, triggering_raster_line, HARDWARE_IRQ, true);
}

/*
 * Unused function that illustrates GNU assembler usage. See links below
 * for further information.
 * - https://blog.alex.balgavy.eu/a-practical-guide-to-gcc-inline-assembly/
 * - https://www.lemon64.com/forum/viewtopic.php?t=69663
 */
inline void _indexed_memcopy() {
  const uint16_t _src = 0x0541; // known at compile time..
  const uint16_t _dst = 0x0540; // ... -> "i" operand below
  const uint8_t _size = 39;
  asm volatile(
      "ldx #$00\n"
      "loop: lda %[src],x\n"
      "sta %[dst],x\n"
      "inx\n"
      "cpx #%[size]\n"
      "bne loop\n"
      :: [src] "i" (_src), [dst] "i" (_dst), [size] "i" (_size)
          : "a", "x");
}