[arch][m68k] add exception and irq processing

-Add interrupt controller and timer support for qemu virt machine
-Switch tty read to irq driven as well
This commit is contained in:
Travis Geiselbrecht
2021-06-06 20:44:21 -07:00
parent 12fee4b59a
commit d6fa4d5b80
13 changed files with 303 additions and 142 deletions

View File

@@ -17,49 +17,113 @@
#include <platform/interrupts.h>
#include <platform/virt.h>
#include <platform/timer.h>
#include <platform.h>
#define LOCAL_TRACE 0
static platform_timer_callback t_callback;
// implementation of RTC at
// https://github.com/qemu/qemu/blob/master/hw/rtc/goldfish_rtc.c
volatile unsigned int * const goldfish_rtc_base = (void *)VIRT_GF_RTC_MMIO_BASE;
static volatile uint ticks = 0;
static lk_time_t periodic_interval;
// registers
enum {
RTC_TIME_LOW = 0x00,
RTC_TIME_HIGH = 0x04,
RTC_ALARM_LOW = 0x08,
RTC_ALARM_HIGH = 0x0c,
RTC_IRQ_ENABLED = 0x10,
RTC_CLEAR_ALARM = 0x14,
RTC_ALARM_STATUS = 0x18,
RTC_CLEAR_INTERRUPT = 0x1c,
};
static uint64_t system_boot_offset;
static platform_timer_callback t_callback;
static void *t_arg;
static void write_reg(int reg, uint32_t val) {
goldfish_rtc_base[reg / 4] = val;
}
static uint32_t read_reg(int reg) {
return goldfish_rtc_base[reg / 4];
}
// raw time from the RTC is ns wall time
static uint64_t read_raw_time(void) {
uint32_t low, high;
// read both registers and assemble a 64bit counter
// reading low first latches a shadow high register which will prevent wraparound
low = read_reg(RTC_TIME_LOW);
high = read_reg(RTC_TIME_HIGH);
return ((uint64_t)high << 32) | low;
}
enum handler_return rtc_irq(void *unused) {
enum handler_return ret = INT_NO_RESCHEDULE;
write_reg(RTC_CLEAR_ALARM, 1);
write_reg(RTC_CLEAR_INTERRUPT, 1);
if (t_callback) {
ret = t_callback(t_arg, current_time());
}
return ret;
}
void goldfish_rtc_early_init(void) {
// sample the timer and use it as a offset for system start
system_boot_offset = read_raw_time();
// clear and stop any pending irqs on the timer
platform_stop_timer();
register_int_handler(GOLDFISH_RTC_IRQ, &rtc_irq, NULL);
unmask_interrupt(GOLDFISH_RTC_IRQ);
// its okay to enable the irq since we've cleared the alarm and any pending interrupts
write_reg(RTC_IRQ_ENABLED, 1);
}
void goldfish_rtc_init(void) {
}
lk_bigtime_t current_time_hires(void) {
static lk_bigtime_t bt = 0;
return ++bt;
uint64_t t = read_raw_time() - system_boot_offset;
return t / 1000ULL; // ns -> us
}
lk_time_t current_time(void) {
static lk_time_t time = 0;
return ++time;
uint64_t t = read_raw_time() - system_boot_offset;
return (lk_time_t)(t / 1000000ULL); // ns -> ms
}
status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
status_t platform_set_oneshot_timer (platform_timer_callback callback, void *arg, lk_time_t interval) {
LTRACEF("callback %p, arg %p, interval %u\n", callback, arg, interval);
t_callback = callback;
t_arg = arg;
periodic_interval = interval;
uint64_t delta = read_raw_time();
delta += interval * 1000000ULL;
#if 0
uint32_t ticks = periodic_interval * 1000; /* timer is running close to 1Mhz */
ASSERT(ticks <= 0xffff);
TIMREG(IEN(0)) = (1<<0); // interval interrupt
TIMREG(INTERVAL_VAL(0)) = ticks;
TIMREG(CNT_CTRL(0)) = (1<<5) | (1<<4) | (1<<1); // no wave, reset, interval mode
unmask_interrupt(TTC0_A_INT);
#endif
write_reg(RTC_ALARM_HIGH, delta >> 32);
write_reg(RTC_ALARM_LOW, delta & 0xffffffff);
return NO_ERROR;
}
void platform_stop_timer(void) {
LTRACE;
write_reg(RTC_CLEAR_ALARM, 1);
write_reg(RTC_CLEAR_INTERRUPT, 1);
}

View File

@@ -47,36 +47,6 @@ static cbuf_t uart_rx_buf;
static char transfer_buf[1]; // static pointer used to transfer MMIO data
#if 0
// simple 16550 driver for the emulated serial port on qemu riscv virt machine
static volatile uint8_t *const uart_base = (uint8_t *)UART0_BASE_VIRT;
static inline uint8_t uart_read_8(size_t offset) {
return uart_base[offset];
}
static inline void uart_write_8(size_t offset, uint8_t val) {
uart_base[offset] = val;
}
static enum handler_return uart_irq_handler(void *arg) {
unsigned char c;
bool resched = false;
while (uart_read_8(5) & (1<<0)) {
c = uart_read_8(0);
cbuf_write_char(&uart_rx_buf, c, false);
resched = true;
}
return resched ? INT_RESCHEDULE : INT_NO_RESCHEDULE;
}
#endif
static void write_reg(int reg, uint32_t val) {
goldfish_tty_base[reg / 4] = val;
}
@@ -85,6 +55,20 @@ static uint32_t read_reg(int reg) {
return goldfish_tty_base[reg / 4];
}
static enum handler_return uart_irq_handler(void *arg) {
bool resched = false;
// use a DMA read of one byte if a byte is ready
if (read_reg(REG_BYTES_READY) > 0) {
write_reg(REG_CMD, CMD_READ_BUFFER);
char c = transfer_buf[0];
cbuf_write_char(&uart_rx_buf, c, false);
resched = true;
}
return resched ? INT_RESCHEDULE : INT_NO_RESCHEDULE;
}
void goldfish_tty_early_init(void) {
// make sure irqs are disabled
write_reg(REG_CMD, CMD_INT_DISABLE);
@@ -99,13 +83,11 @@ void goldfish_tty_init(void) {
/* finish uart init to get rx going */
cbuf_initialize_etc(&uart_rx_buf, RXBUF_SIZE, uart_rx_buf_data);
#if 0
register_int_handler(IRQ_UART0, uart_irq_handler, NULL);
register_int_handler(GOLDFISH_TTY_IRQ, uart_irq_handler, NULL);
uart_write_8(1, 0x1); // enable receive data available interrupt
unmask_interrupt(GOLDFISH_TTY_IRQ);
unmask_interrupt(IRQ_UART0);
#endif
write_reg(REG_CMD, CMD_INT_ENABLE);
}
void uart_putc(char c) {
@@ -113,7 +95,7 @@ void uart_putc(char c) {
}
int uart_getc(char *c, bool wait) {
#if 0
#if 1
return cbuf_read_char(&uart_rx_buf, c, wait);
#else
return platform_pgetc(c, false);

View File

@@ -29,14 +29,15 @@
* IRQ #2 to IRQ #32 -> unused
* CPU IRQ #7 -> NMI
*/
#define NUM_IRQS (6 * 32) // PIC 1 - 6
#define NUM_PICS 6
#define NUM_IRQS (NUM_PICS * 32) // PIC 1 - 6
#define PIC_IRQ_TO_LINEAR(pic, irq) (((pic) - 1) * 32 + ((irq) - 1))
#define GOLDFISH_TTY_IRQ PIC_IRQ_TO_LINEAR(1, 32) // PIC 1, irq 32
#define GOLDFISH_RTC_IRQ PIC_IRQ_TO_LINEAR(6, 1) // PIC 6, irq 1
#define PIC_IRQ_BASE(num) (8 + (num - 1) * 32)
#define PIC_IRQ(num, irq) (PIC_IRQ_BASE(num) + irq - 1)
//#define PIC_IRQ_BASE(num) (8 + (num - 1) * 32)
//#define PIC_IRQ(num, irq) (PIC_IRQ_BASE(num) + irq - 1)
//#define PIC_GPIO(pic_irq) (qdev_get_gpio_in(pic_dev[(pic_irq - 8) / 32], (pic_irq - 8) % 32))
#define VIRT_GF_PIC_MMIO_BASE 0xff000000 /* MMIO: 0xff000000 - 0xff005fff */

View File

@@ -8,6 +8,7 @@
#include "platform_p.h"
#include <assert.h>
#include <lk/bits.h>
#include <lk/err.h>
#include <lk/debug.h>
#include <lk/reg.h>
@@ -19,24 +20,66 @@
#define LOCAL_TRACE 0
void pic_early_init(void) {
}
// implementation of PIC at
// https://github.com/qemu/qemu/blob/master/hw/intc/goldfish_pic.c
void pic_init(void) {
}
enum {
REG_STATUS = 0x00,
REG_IRQ_PENDING = 0x04,
REG_IRQ_DISABLE_ALL = 0x08,
REG_DISABLE = 0x0c,
REG_ENABLE = 0x10,
};
volatile unsigned int * const goldfish_pic_base = (void *)VIRT_GF_PIC_MMIO_BASE;
static struct int_handlers {
int_handler handler;
void *arg;
} handlers[NUM_IRQS];
static void write_reg(int pic, int reg, uint32_t val) {
goldfish_pic_base[0x1000 * pic / 4 + reg / 4] = val;
}
static uint32_t read_reg(int pic, int reg) {
return goldfish_pic_base[0x1000 * pic / 4 + reg / 4];
}
static void dump_pic(int i) {
dprintf(INFO, "PIC %d: status %u pending %#x\n", i, read_reg(i, REG_STATUS), read_reg(i, REG_IRQ_PENDING));
}
static void dump_all_pics(void) {
for (int i = 0; i < NUM_PICS; i++) {
dump_pic(i);
}
}
static int irq_to_pic_num(unsigned int vector) {
return vector / 32;
}
static int irq_to_pic_vec(unsigned int vector) {
return vector % 32;
}
void pic_early_init(void) {
}
void pic_init(void) {
dump_all_pics();
}
status_t mask_interrupt(unsigned int vector) {
//*REG32(PLIC_ENABLE(vector, riscv_current_hart())) &= ~(1 << (vector % 32));
LTRACEF("vector %u\n", vector);
write_reg(irq_to_pic_num(vector), REG_DISABLE, 1U << irq_to_pic_vec(vector));
return NO_ERROR;
}
status_t unmask_interrupt(unsigned int vector) {
//*REG32(PLIC_ENABLE(vector, riscv_current_hart())) |= (1 << (vector % 32));
LTRACEF("vector %u\n", vector);
write_reg(irq_to_pic_num(vector), REG_ENABLE, 1U << irq_to_pic_vec(vector));
return NO_ERROR;
}
@@ -49,60 +92,28 @@ void register_int_handler(unsigned int vector, int_handler handler, void *arg) {
handlers[vector].arg = arg;
}
#if 0
enum handler_return m68k_platform_irq(uint8_t m68k_irq) {
LTRACEF("m68k irq vector %d\n", m68k_irq);
// Driver for PLIC implementation for qemu riscv virt machine
#define PLIC_PRIORITY(irq) (PLIC_BASE_VIRT + 4 + 4 * (irq))
#define PLIC_PENDING(irq) (PLIC_BASE_VIRT + 0x1000 + (4 * ((irq) / 32)))
#define PLIC_ENABLE(irq, hart) (PLIC_BASE_VIRT + 0x2000 + (0x80 * PLIC_HART_IDX(hart)) + (4 * ((irq) / 32)))
#define PLIC_THRESHOLD(hart) (PLIC_BASE_VIRT + 0x200000 + (0x1000 * PLIC_HART_IDX(hart)))
#define PLIC_COMPLETE(hart) (PLIC_BASE_VIRT + 0x200004 + (0x1000 * PLIC_HART_IDX(hart)))
#define PLIC_CLAIM(hart) PLIC_COMPLETE(hart)
void plic_early_init(void) {
// mask all irqs and set their priority to 1
// TODO: mask on all the other cpus too
for (int i = 1; i < NUM_IRQS; i++) {
*REG32(PLIC_ENABLE(i, riscv_current_hart())) &= ~(1 << (i % 32));
*REG32(PLIC_PRIORITY(i)) = 1;
// translate m68k irqs to pic numbers
int pic_num;
if (likely(m68k_irq >= 1 && m68k_irq <= 6)) {
pic_num = m68k_irq - 1;
} else {
panic("unhandled irq %d from cpu\n", m68k_irq);
}
// set global priority threshold to 0
*REG32(PLIC_THRESHOLD(riscv_current_hart())) = 0;
}
void plic_init(void) {
}
status_t mask_interrupt(unsigned int vector) {
*REG32(PLIC_ENABLE(vector, riscv_current_hart())) &= ~(1 << (vector % 32));
return NO_ERROR;
}
status_t unmask_interrupt(unsigned int vector) {
*REG32(PLIC_ENABLE(vector, riscv_current_hart())) |= (1 << (vector % 32));
return NO_ERROR;
}
void register_int_handler(unsigned int vector, int_handler handler, void *arg) {
LTRACEF("vector %u handler %p arg %p\n", vector, handler, arg);
DEBUG_ASSERT(vector < NUM_IRQS);
handlers[vector].handler = handler;
handlers[vector].arg = arg;
}
enum handler_return riscv_platform_irq(void) {
// see what irq triggered it
uint32_t vector = *REG32(PLIC_CLAIM(riscv_current_hart()));
LTRACEF("vector %u\n", vector);
if (unlikely(vector == 0)) {
// nothing pending
// see what is pending
uint32_t pending = read_reg(pic_num, REG_IRQ_PENDING);
if (pending == 0) {
// spurious
return INT_NO_RESCHEDULE;
}
// find the lowest numbered bit set
uint vector = ctz(pending) + pic_num * 32;
LTRACEF("pic %d pending %#x vector %u\n", pic_num, pending, vector);
THREAD_STATS_INC(interrupts);
KEVLOG_IRQ_ENTER(vector);
@@ -111,12 +122,9 @@ enum handler_return riscv_platform_irq(void) {
ret = handlers[vector].handler(handlers[vector].arg);
}
// ack the interrupt
*REG32(PLIC_COMPLETE(riscv_current_hart())) = vector;
// no need to ack the interrupt controller since all irqs are implicitly level
KEVLOG_IRQ_EXIT(vector);
return ret;
}
#endif

View File

@@ -21,4 +21,7 @@ MEMSIZE ?= 0x08000000 # default to 128MB
# we can revert to a poll based uart spin routine
GLOBAL_DEFINES += PLATFORM_SUPPORTS_PANIC_SHELL=1
# our timer supports one shot mode
GLOBAL_DEFINES += PLATFORM_HAS_DYNAMIC_TIMER=1
include make/module.mk