diff --git a/.github/workflows/github-ci.yml b/.github/workflows/github-ci.yml index 00a4d3d8..28eb29e8 100644 --- a/.github/workflows/github-ci.yml +++ b/.github/workflows/github-ci.yml @@ -15,6 +15,7 @@ jobs: project: - qemu-virt-arm32-test - qemu-virt-arm64-test + - qemu-virt-m68k-test - qemu-microblaze-test - qemu-mips-test - qemu-virt-riscv32-test diff --git a/arch/m68k/arch.c b/arch/m68k/arch.c new file mode 100644 index 00000000..468c5d5d --- /dev/null +++ b/arch/m68k/arch.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 LOCAL_TRACE 0 + +void arch_early_init(void) { + LTRACE; + + // set the exception vector base + extern uint32_t exc_vectors[256]; + asm volatile("movec %0, %%vbr" :: "r"(exc_vectors)); +} + +void arch_init(void) { + LTRACE; +} + +void arch_idle(void) { + // set the SR such that we're in supervisor state and no ints are masked + asm("stop #0x2000" ::: "cc"); +} + +void arch_chain_load(void *entry, ulong arg0, ulong arg1, ulong arg2, ulong arg3) { + PANIC_UNIMPLEMENTED; +} + +/* unimplemented cache operations */ +void arch_disable_cache(uint flags) { PANIC_UNIMPLEMENTED; } +void arch_enable_cache(uint flags) { PANIC_UNIMPLEMENTED; } + +void arch_clean_cache_range(addr_t start, size_t len) { PANIC_UNIMPLEMENTED; } +void arch_clean_invalidate_cache_range(addr_t start, size_t len) { PANIC_UNIMPLEMENTED; } +void arch_invalidate_cache_range(addr_t start, size_t len) { PANIC_UNIMPLEMENTED; } +void arch_sync_cache_range(addr_t start, size_t len) { PANIC_UNIMPLEMENTED; } + diff --git a/arch/m68k/asm.S b/arch/m68k/asm.S new file mode 100644 index 00000000..518c31ed --- /dev/null +++ b/arch/m68k/asm.S @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +// void m68k_context_switch(struct m68k_context_switch_frame *oldcs, struct m68k_context_switch_frame *newcs); +FUNCTION(m68k_context_switch) + movel %sp@+,%d0 // pop PC off the stack + movel %sp@,%a0 // oldcs + movel %sp@(4),%a1 // newcs + + // save old state + movel %sp, %a0@(0) // oldcs.sp + movel %d0, %a0@(4) // oldcs.pc + moveml %d2-%d7/%a2-%a6, %a0@(8) // oldcs. + + // load new state + movel %a1@(0), %sp // newcs.sp + movel %a1@(4), %sp@- // newcs.pc -> stack + moveml %a1@(8), %d2-%d7/%a2-%a6 // newcs. + + // return to new PC + rts +END_FUNCTION(m68k_context_switch) + diff --git a/arch/m68k/exceptions.c b/arch/m68k/exceptions.c new file mode 100644 index 00000000..7e853387 --- /dev/null +++ b/arch/m68k/exceptions.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +#define LOCAL_TRACE 0 + +// defined in platform interrupt controller +extern enum handler_return m68k_platform_irq(uint8_t irq); + +void m68k_exception(void *frame, uint8_t code) { + LTRACEF("frame %p, code %hhu\n", frame, code); + + panic("unimplemented exception %hhu\n", code); +} + +void m68k_irq(void *frame, uint8_t code) { + LTRACEF("frame %p, code %hhu\n", frame, code); + + if (unlikely(code == 0)) { + // spurious interrupt + return; + } + + enum handler_return ret = m68k_platform_irq(code); + if (ret == INT_RESCHEDULE) { + thread_preempt(); + } +} diff --git a/arch/m68k/exceptions_asm.S b/arch/m68k/exceptions_asm.S new file mode 100644 index 00000000..46776a60 --- /dev/null +++ b/arch/m68k/exceptions_asm.S @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +.text + +.macro irq_vector n +m68k_irq_vector_\n: + moveml %d0-%d7/%a0-%a6, %sp@- + + pea \n + movel %sp,%sp@- + jsr m68k_irq + addaw #8, %sp + + moveml %sp@+, %d0-%d7/%a0-%a6 + rte +.endm + +irq_vector 0 +irq_vector 1 +irq_vector 2 +irq_vector 3 +irq_vector 4 +irq_vector 5 +irq_vector 6 +irq_vector 7 + +.macro exception_vector name n +\name: + moveml %d0-%d7/%a0-%a6, %sp@- + + pea \n + movel %sp,%sp@- + jsr m68k_exception + addaw #8, %sp + + moveml %sp@+, %d0-%d7/%a0-%a6 + rte +.endm + +exception_vector m68k_access_fault 2 +exception_vector m68k_address_error 3 +exception_vector m68k_illegal_vector 4 +exception_vector m68k_div_by_zero 5 +exception_vector m68k_chk 6 +exception_vector m68k_trap 7 +exception_vector m68k_priv_violation 8 +exception_vector m68k_trace 9 +exception_vector m68k_unimp_aline 10 +exception_vector m68k_unimp_fline 11 + +.section .text.vectab +.align 4 +DATA(exc_vectors) +.org 0x8 + .long m68k_access_fault + .long m68k_address_error + .long m68k_illegal_vector + .long m68k_div_by_zero + .long m68k_chk + .long m68k_trap + .long m68k_priv_violation + .long m68k_trace + .long m68k_unimp_aline + .long m68k_unimp_fline +.org 0x60 + .long m68k_irq_vector_0 + .long m68k_irq_vector_1 + .long m68k_irq_vector_2 + .long m68k_irq_vector_3 + .long m68k_irq_vector_4 + .long m68k_irq_vector_5 + .long m68k_irq_vector_6 + .long m68k_irq_vector_7 +.org 4*256 +END_DATA(exc_vectors) + diff --git a/arch/m68k/include/arch/arch_ops.h b/arch/m68k/include/arch/arch_ops.h new file mode 100644 index 00000000..552f70d7 --- /dev/null +++ b/arch/m68k/include/arch/arch_ops.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +#include +#include + +__BEGIN_CDECLS + +#define M68K_SR_IPM_MASK ((0b111 << 8)) // interrupt priority mask + +static inline void arch_enable_ints(void) { + asm volatile("andi %0, %%sr" :: "i" (~M68K_SR_IPM_MASK) : "memory"); +} + +static inline void arch_disable_ints(void) { + asm volatile("ori %0, %%sr" :: "i" (M68K_SR_IPM_MASK) : "memory"); +} + +static inline bool arch_ints_disabled(void) { + uint16_t sr; + asm volatile("move %%sr, %0" : "=r"(sr) :: "memory"); + + // if the IPM is != 0, consider interrupts disabled + return (sr & M68K_SR_IPM_MASK); +} + +// use a global pointer to store the current_thread +extern struct thread *_current_thread; + +static inline struct thread *arch_get_current_thread(void) { + return _current_thread; +} + +static inline void arch_set_current_thread(struct thread *t) { + _current_thread = t; +} + +static inline ulong arch_cycle_count(void) { return 0; } + +static inline uint arch_curr_cpu_num(void) { + return 0; +} + +// TODO: see if there's a proper (or required) memory barrier on 68k +#define mb() CF +#define wmb() CF +#define rmb() CF +#define smp_mb() CF +#define smp_wmb() CF +#define smp_rmb() CF + +__END_CDECLS diff --git a/arch/m68k/include/arch/arch_thread.h b/arch/m68k/include/arch/arch_thread.h new file mode 100644 index 00000000..bf52b1e7 --- /dev/null +++ b/arch/m68k/include/arch/arch_thread.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +#include + +struct m68k_context_switch_frame { + uint32_t sp; + uint32_t pc; + + /* callee saved */ + uint32_t d2; + uint32_t d3; + uint32_t d4; + uint32_t d5; + uint32_t d6; + uint32_t d7; + uint32_t a2; + uint32_t a3; + uint32_t a4; + uint32_t a5; + uint32_t a6; +}; + +// NOTE: consider using 'state on stack' instead +struct arch_thread { + struct m68k_context_switch_frame cs_frame; +}; + +void m68k_context_switch(struct m68k_context_switch_frame *oldcs, struct m68k_context_switch_frame *newcs); + diff --git a/arch/m68k/include/arch/defines.h b/arch/m68k/include/arch/defines.h new file mode 100644 index 00000000..82321aad --- /dev/null +++ b/arch/m68k/include/arch/defines.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Travis Geiselbrecht + * + * 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 + +#define PAGE_SIZE 4096 +#define PAGE_SIZE_SHIFT 12 + +// XXX is this right? +#define CACHE_LINE 32 + +#define ARCH_DEFAULT_STACK_SIZE 4096 diff --git a/arch/m68k/include/arch/m68k.h b/arch/m68k/include/arch/m68k.h new file mode 100644 index 00000000..daa4558d --- /dev/null +++ b/arch/m68k/include/arch/m68k.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + diff --git a/arch/m68k/include/arch/spinlock.h b/arch/m68k/include/arch/spinlock.h new file mode 100644 index 00000000..da21756c --- /dev/null +++ b/arch/m68k/include/arch/spinlock.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 Travis Geiselbrecht + * + * 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 + +#include +#include + +#if WITH_SMP +#error m68k does not support SMP +#endif + +#define SPIN_LOCK_INITIAL_VALUE (0) + +typedef unsigned int spin_lock_t; + +typedef unsigned int spin_lock_saved_state_t; +typedef unsigned int spin_lock_save_flags_t; + +static inline void arch_spin_lock(spin_lock_t *lock) { + *lock = 1; +} + +static inline int arch_spin_trylock(spin_lock_t *lock) { + return 0; +} + +static inline void arch_spin_unlock(spin_lock_t *lock) { + *lock = 0; +} + +static inline void arch_spin_lock_init(spin_lock_t *lock) { + *lock = SPIN_LOCK_INITIAL_VALUE; +} + +static inline bool arch_spin_lock_held(spin_lock_t *lock) { + return *lock != 0; +} + +/* default arm flag is to just disable plain irqs */ +#define ARCH_DEFAULT_SPIN_LOCK_FLAG_INTERRUPTS 0 + +static inline void +arch_interrupt_save(spin_lock_saved_state_t *statep, spin_lock_save_flags_t flags) { + *statep = arch_ints_disabled(); + arch_disable_ints(); +} + +static inline void +arch_interrupt_restore(spin_lock_saved_state_t old_state, spin_lock_save_flags_t flags) { + if (!old_state) { + arch_enable_ints(); + } +} + + + + diff --git a/arch/m68k/linker.ld b/arch/m68k/linker.ld new file mode 100644 index 00000000..39cea572 --- /dev/null +++ b/arch/m68k/linker.ld @@ -0,0 +1,76 @@ +OUTPUT_FORMAT("elf32-m68k", "elf32-m68k", "elf32-m68k") +/* + * LK linker script for single segment binaries. + */ +PHDRS +{ + code PT_LOAD FLAGS(5); /* PF_R|PF_X */ + rodata PT_LOAD FLAGS(4); /* PF_R */ + data PT_LOAD FLAGS(6); /* PF_R|PF_W */ +} + +ENTRY(_start) +SECTIONS +{ + . = %KERNEL_BASE% + %KERNEL_LOAD_OFFSET%; + + _start = .; + + /* text/read-only data */ + /* set the load address to physical MEMBASE */ + .text : AT(%MEMBASE% + %KERNEL_LOAD_OFFSET%) { + KEEP(*(.text.boot)) + *(.text .text*) + *(.gnu.linkonce.t.*) + } :code + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + __rodata_start = .; + *(.rodata .rodata.* .gnu.linkonce.r.*) + } :rodata + + /* trick to force any extra sections to be emitted here */ + . = .; + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + __data_start = .; + *(.data .data.* .gnu.linkonce.d.*) + __ctor_list = .; + KEEP(*(.ctors .init_array)) + __ctor_end = .; + __dtor_list = .; + KEEP(*(.dtors .fini_array)) + __dtor_end = .; + *(.got*) + *(.dynamic) + } :data + + . = ALIGN(32 / 8); + __data_end = .; + __bss_start = .; + + /* uninitialized data (in same segment as writable data) */ + .bss : { + /* regular bss */ + *(.bss .bss.*) + *(.gnu.linkonce.b.*) + } + + . = ALIGN(32 / 8); + __bss_end = .; + + /* Align the end to ensure anything after the kernel ends up on its own pages */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + _end = .; + + . = %KERNEL_BASE% + %MEMSIZE%; + _end_of_ram = .; + + /* Strip unnecessary stuff */ + /DISCARD/ : { *(.comment .note .eh_frame) } +} + diff --git a/arch/m68k/rules.mk b/arch/m68k/rules.mk new file mode 100644 index 00000000..49911dbf --- /dev/null +++ b/arch/m68k/rules.mk @@ -0,0 +1,64 @@ +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +MODULE_SRCS += \ + $(LOCAL_DIR)/arch.c \ + $(LOCAL_DIR)/asm.S \ + $(LOCAL_DIR)/exceptions.c \ + $(LOCAL_DIR)/exceptions_asm.S \ + $(LOCAL_DIR)/start.S \ + $(LOCAL_DIR)/thread.c \ + +# $(LOCAL_DIR)/asm.S \ + $(LOCAL_DIR)/cache.c \ + $(LOCAL_DIR)/cache-ops.S \ + $(LOCAL_DIR)/ops.S \ + $(LOCAL_DIR)/mmu.c \ + $(LOCAL_DIR)/faults.c \ + $(LOCAL_DIR)/descriptor.c + +GLOBAL_DEFINES += \ + SMP_MAX_CPUS=1 + +# set the default toolchain to microblaze elf and set a #define +ifndef TOOLCHAIN_PREFIX +TOOLCHAIN_PREFIX := m68k-elf- +endif + +WITH_LINKER_GC ?= 0 + +ARCH_COMPILEFLAGS := -mcpu=68040 + +LIBGCC := $(shell $(TOOLCHAIN_PREFIX)gcc $(GLOBAL_COMPILEFLAGS) $(ARCH_COMPILEFLAGS) $(GLOBAL_COMPILEFLAGS) -print-libgcc-file-name) +$(info LIBGCC = $(LIBGCC)) + +cc-option = $(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`"; \ + then echo "$(2)"; else echo "$(3)"; fi ;) + +ARCH_OPTFLAGS := -O2 + +KERNEL_BASE ?= $(MEMBASE) +KERNEL_LOAD_OFFSET ?= 0 + +GLOBAL_DEFINES += \ + MEMBASE=$(MEMBASE) \ + MEMSIZE=$(MEMSIZE) + +# potentially generated files that should be cleaned out with clean make rule +GENERATED += \ + $(BUILDDIR)/linker.ld + +# rules for generating the linker +$(BUILDDIR)/linker.ld: $(LOCAL_DIR)/linker.ld $(wildcard arch/*.ld) linkerscript.phony + @echo generating $@ + @$(MKDIR) + $(NOECHO)sed "s/%MEMBASE%/$(MEMBASE)/;s/%MEMSIZE%/$(MEMSIZE)/;s/%KERNEL_BASE%/$(KERNEL_BASE)/;s/%KERNEL_LOAD_OFFSET%/$(KERNEL_LOAD_OFFSET)/" < $< > $@.tmp + @$(call TESTANDREPLACEFILE,$@.tmp,$@) + +linkerscript.phony: +.PHONY: linkerscript.phony + +LINKER_SCRIPT += $(BUILDDIR)/linker.ld + +include make/module.mk diff --git a/arch/m68k/start.S b/arch/m68k/start.S new file mode 100644 index 00000000..753b3fe0 --- /dev/null +++ b/arch/m68k/start.S @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +.section .text.boot +FUNCTION(_start) + // clear bss + lea __bss_start,%a0 + cmpal #_end,%a0 + beqs 1f +0: + clrb %a0@+ + cmpal #_end,%a0 + bnes 0b +1: + + movel #_default_stack_top,%sp + jsr lk_main + jmp . + +END_FUNCTION(_start) + +.bss +.align 8 +_default_stack_base: + .skip 4096 +_default_stack_top: + diff --git a/arch/m68k/thread.c b/arch/m68k/thread.c new file mode 100644 index 00000000..96ddb9f1 --- /dev/null +++ b/arch/m68k/thread.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +#define LOCAL_TRACE 0 + +struct thread *_current_thread; + +static void initial_thread_func(void) __NO_RETURN; +static void initial_thread_func(void) { + thread_t *ct = get_current_thread(); + +#if LOCAL_TRACE + LTRACEF("thread %p calling %p with arg %p\n", ct, ct->entry, ct->arg); + dump_thread(ct); +#endif + + /* release the thread lock that was implicitly held across the reschedule */ + spin_unlock(&thread_lock); + arch_enable_ints(); + + int ret = ct->entry(ct->arg); + + LTRACEF("thread %p exiting with %d\n", ct, ret); + + thread_exit(ret); +} + +void arch_thread_initialize(thread_t *t) { + LTRACEF("t %p (%s)\n", t, t->name); + + /* zero out the thread context */ + memset(&t->arch.cs_frame, 0, sizeof(t->arch.cs_frame)); + + t->arch.cs_frame.sp = (vaddr_t)t->stack + t->stack_size; + t->arch.cs_frame.pc = (vaddr_t)&initial_thread_func; +} + +void arch_context_switch(thread_t *oldthread, thread_t *newthread) { + LTRACEF("old %p (%s), new %p (%s)\n", oldthread, oldthread->name, newthread, newthread->name); + + m68k_context_switch(&oldthread->arch.cs_frame, &newthread->arch.cs_frame); +} + +void arch_dump_thread(thread_t *t) { +#if 0 + if (t->state != THREAD_RUNNING) { + dprintf(INFO, "\tarch: "); + dprintf(INFO, "sp 0x%x\n", t->arch.cs_frame.r1); + } +#endif +} + diff --git a/platform/qemu-virt-m68k/goldfish_rtc.c b/platform/qemu-virt-m68k/goldfish_rtc.c new file mode 100644 index 00000000..f3c93e47 --- /dev/null +++ b/platform/qemu-virt-m68k/goldfish_rtc.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 +#include +#include +#include + +#define LOCAL_TRACE 0 + +// 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; + +// 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) { + uint64_t t = read_raw_time() - system_boot_offset; + + return t / 1000ULL; // ns -> us +} + +lk_time_t current_time(void) { + uint64_t t = read_raw_time() - system_boot_offset; + + return (lk_time_t)(t / 1000000ULL); // ns -> ms +} + +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; + + uint64_t delta = read_raw_time(); + delta += interval * 1000000ULL; + + 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); +} + + diff --git a/platform/qemu-virt-m68k/goldfish_tty.c b/platform/qemu-virt-m68k/goldfish_tty.c new file mode 100644 index 00000000..5b22eccb --- /dev/null +++ b/platform/qemu-virt-m68k/goldfish_tty.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 "platform_p.h" + +// goldfish tty +// from https://github.com/qemu/qemu/blob/master/hw/char/goldfish_tty.c +volatile unsigned int * const goldfish_tty_base = (void *)VIRT_GF_TTY_MMIO_BASE; + +// registers +enum { + REG_PUT_CHAR = 0x00, + REG_BYTES_READY = 0x04, + REG_CMD = 0x08, + REG_DATA_PTR = 0x10, + REG_DATA_LEN = 0x14, + REG_DATA_PTR_HIGH = 0x18, + REG_VERSION = 0x20, +}; + +// commands + +enum { + CMD_INT_DISABLE = 0x00, + CMD_INT_ENABLE = 0x01, + CMD_WRITE_BUFFER = 0x02, + CMD_READ_BUFFER = 0x03, +}; + +#define RXBUF_SIZE 128 +static char uart_rx_buf_data[RXBUF_SIZE]; +static cbuf_t uart_rx_buf; + +static char transfer_buf[1]; // static pointer used to transfer MMIO data + +static void write_reg(int reg, uint32_t val) { + goldfish_tty_base[reg / 4] = val; +} + +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); + + // set up the transfer buffer for receives + write_reg(REG_DATA_PTR, (uint32_t)transfer_buf); + write_reg(REG_DATA_PTR_HIGH, 0); + write_reg(REG_DATA_LEN, sizeof(transfer_buf)); +} + +void goldfish_tty_init(void) { + /* finish uart init to get rx going */ + cbuf_initialize_etc(&uart_rx_buf, RXBUF_SIZE, uart_rx_buf_data); + + register_int_handler(GOLDFISH_TTY_IRQ, uart_irq_handler, NULL); + + unmask_interrupt(GOLDFISH_TTY_IRQ); + + write_reg(REG_CMD, CMD_INT_ENABLE); +} + +void uart_putc(char c) { + write_reg(REG_PUT_CHAR, c); +} + +int uart_getc(char *c, bool wait) { +#if 1 + return cbuf_read_char(&uart_rx_buf, c, wait); +#else + return platform_pgetc(c, false); +#endif +} + +void platform_dputc(char c) { + if (c == '\n') + platform_dputc('\r'); + uart_putc(c); +} + +int platform_dgetc(char *c, bool wait) { + int ret = uart_getc(c, wait); + + return ret; +} + +/* panic-time getc/putc */ +void platform_pputc(char c) { + return uart_putc(c); +} + +int platform_pgetc(char *c, bool wait) { + // 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); + *c = transfer_buf[0]; + return 0; + } + return -1; +} diff --git a/platform/qemu-virt-m68k/include/platform/virt.h b/platform/qemu-virt-m68k/include/platform/virt.h new file mode 100644 index 00000000..c68b61fc --- /dev/null +++ b/platform/qemu-virt-m68k/include/platform/virt.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 + +// Top level #defines for the 68k-virt machine in qemu 6.0 +// +// From qemu/hw/m68k/virt.c + +/* + * 6 goldfish-pic for CPU IRQ #1 to IRQ #6 + * CPU IRQ #1 -> PIC #1 + * IRQ #1 to IRQ #31 -> unused + * IRQ #32 -> goldfish-tty + * CPU IRQ #2 -> PIC #2 + * IRQ #1 to IRQ #32 -> virtio-mmio from 1 to 32 + * CPU IRQ #3 -> PIC #3 + * IRQ #1 to IRQ #32 -> virtio-mmio from 33 to 64 + * CPU IRQ #4 -> PIC #4 + * IRQ #1 to IRQ #32 -> virtio-mmio from 65 to 96 + * CPU IRQ #5 -> PIC #5 + * IRQ #1 to IRQ #32 -> virtio-mmio from 97 to 128 + * CPU IRQ #6 -> PIC #6 + * IRQ #1 -> goldfish-rtc + * IRQ #2 to IRQ #32 -> unused + * CPU IRQ #7 -> NMI + */ +#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_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 */ +#define VIRT_GF_PIC_IRQ_BASE 1 /* IRQ: #1 -> #6 */ +#define VIRT_GF_PIC_NB 6 + +/* 2 goldfish-rtc (and timer) */ +#define VIRT_GF_RTC_MMIO_BASE 0xff006000 /* MMIO: 0xff006000 - 0xff007fff */ +#define VIRT_GF_RTC_IRQ_BASE PIC_IRQ(6, 1) /* PIC: #6, IRQ: #1 */ +#define VIRT_GF_RTC_NB 2 + +/* 1 goldfish-tty */ +#define VIRT_GF_TTY_MMIO_BASE 0xff008000 /* MMIO: 0xff008000 - 0xff008fff */ +#define VIRT_GF_TTY_IRQ_BASE PIC_IRQ(1, 32) /* PIC: #1, IRQ: #32 */ + +/* 1 virt-ctrl */ +#define VIRT_CTRL_MMIO_BASE 0xff009000 /* MMIO: 0xff009000 - 0xff009fff */ +#define VIRT_CTRL_IRQ_BASE PIC_IRQ(1, 1) /* PIC: #1, IRQ: #1 */ + +/* + * virtio-mmio size is 0x200 bytes + * we use 4 goldfish-pic to attach them, + * we can attach 32 virtio devices / goldfish-pic + * -> we can manage 32 * 4 = 128 virtio devices + */ +#define VIRT_VIRTIO_MMIO_BASE 0xff010000 /* MMIO: 0xff010000 - 0xff01ffff */ +#define VIRT_VIRTIO_IRQ_BASE PIC_IRQ(2, 1) /* PIC: 2, 3, 4, 5, IRQ: ALL */ diff --git a/platform/qemu-virt-m68k/pic.c b/platform/qemu-virt-m68k/pic.c new file mode 100644 index 00000000..4a35990b --- /dev/null +++ b/platform/qemu-virt-m68k/pic.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Travis Geiselbrecht + * + * 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 +#include +#include + +#define LOCAL_TRACE 0 + +// implementation of PIC at +// https://github.com/qemu/qemu/blob/master/hw/intc/goldfish_pic.c + +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) { + 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) { + LTRACEF("vector %u\n", vector); + write_reg(irq_to_pic_num(vector), REG_ENABLE, 1U << irq_to_pic_vec(vector)); + 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 m68k_platform_irq(uint8_t m68k_irq) { + LTRACEF("m68k irq vector %d\n", m68k_irq); + + // 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); + } + + // 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); + + enum handler_return ret = INT_NO_RESCHEDULE; + if (handlers[vector].handler) { + ret = handlers[vector].handler(handlers[vector].arg); + } + + // no need to ack the interrupt controller since all irqs are implicitly level + KEVLOG_IRQ_EXIT(vector); + + return ret; +} + diff --git a/platform/qemu-virt-m68k/platform.c b/platform/qemu-virt-m68k/platform.c new file mode 100644 index 00000000..cee198d2 --- /dev/null +++ b/platform/qemu-virt-m68k/platform.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2018 Travis Geiselbrecht + * + * 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 +#if WITH_LIB_MINIP +#include +#endif +#if WITH_KERNEL_VM +#include +#else +#include +#endif +#if WITH_LIB_CONSOLE +#include +#endif + +#include "platform_p.h" + +#define LOCAL_TRACE 0 + +extern ulong lk_boot_args[4]; + +#if WITH_KERNEL_VM +#define DEFAULT_MEMORY_SIZE (MEMSIZE) /* try to fetch from the emulator via the fdt */ + +static pmm_arena_t arena = { + .name = "ram", + .base = MEMORY_BASE_PHYS, + .size = DEFAULT_MEMORY_SIZE, + .flags = PMM_ARENA_FLAG_KMAP, +}; +#endif + +void platform_early_init(void) { + goldfish_tty_early_init(); + pic_early_init(); + goldfish_rtc_early_init(); +#if 0 + plic_early_init(); + + LTRACEF("starting FDT scan\n"); + + /* look for a flattened device tree in the second arg passed to us */ + bool found_mem = false; + int cpu_count = 0; + const void *fdt = (void *)lk_boot_args[1]; +#if WITH_KERNEL_VM + fdt = (const void *)((uintptr_t)fdt + PERIPHERAL_BASE_VIRT); +#endif + + struct fdt_walk_callbacks cb = { + .mem = memcallback, + .memcookie = &found_mem, + .cpu = cpucallback, + .cpucookie = &cpu_count, + }; + + status_t err = fdt_walk(fdt, &cb); + LTRACEF("fdt_walk returns %d\n", err); + + if (err != 0) { + printf("FDT: error finding FDT at %p, using default memory & cpu count\n", fdt); + } + + if (!found_mem) { +#if WITH_KERNEL_VM + pmm_add_arena(&arena); +#else + novm_add_arena("default", MEMBASE, MEMSIZE); +#endif + } + + if (cpu_count > 0) { + printf("FDT: found %d cpus\n", cpu_count); + riscv_set_secondary_count(cpu_count - 1); + } + +#if WITH_KERNEL_VM + /* reserve the first 256K of ram which is marked protected by the PMP in firmware */ + struct list_node list = LIST_INITIAL_VALUE(list); + pmm_alloc_range(MEMBASE, 0x40000 / PAGE_SIZE, &list); +#endif + + LTRACEF("done scanning FDT\n"); + + /* save a copy of the pointer to the poweroff/reset register */ + /* TODO: read it from the FDT */ +#if WITH_KERNEL_VM + power_reset_reg = paddr_to_kvaddr(0x100000); +#else + power_reset_reg = (void *)0x100000; +#endif +#endif +} + +void platform_init(void) { + pic_init(); + goldfish_tty_init(); + goldfish_rtc_init(); +#if 0 + plic_init(); + uart_init(); + + /* detect any virtio devices */ + uint virtio_irqs[NUM_VIRTIO_TRANSPORTS]; + for (int i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) { + virtio_irqs[i] = IRQ_VIRTIO_BASE + i; + } + + virtio_mmio_detect((void *)VIRTIO_BASE_VIRT, NUM_VIRTIO_TRANSPORTS, virtio_irqs, VIRTIO_STRIDE); + +#if WITH_LIB_MINIP + if (virtio_net_found() > 0) { + uint8_t mac_addr[6]; + + virtio_net_get_mac_addr(mac_addr); + + TRACEF("found virtio networking interface\n"); + + /* start minip */ + minip_set_macaddr(mac_addr); + + __UNUSED uint32_t ip_addr = IPV4(192, 168, 0, 99); + __UNUSED uint32_t ip_mask = IPV4(255, 255, 255, 0); + __UNUSED uint32_t ip_gateway = IPV4_NONE; + + //minip_init(virtio_net_send_minip_pkt, NULL, ip_addr, ip_mask, ip_gateway); + minip_init_dhcp(virtio_net_send_minip_pkt, NULL); + + virtio_net_start(); + } +#endif +#endif +} + +#if 0 +void platform_halt(platform_halt_action suggested_action, + platform_halt_reason reason) { + switch (suggested_action) { + case HALT_ACTION_SHUTDOWN: + dprintf(ALWAYS, "Shutting down... (reason = %d)\n", reason); + *power_reset_reg = 0x5555; + break; + case HALT_ACTION_REBOOT: + dprintf(ALWAYS, "Rebooting... (reason = %d)\n", reason); + *power_reset_reg = 0x7777; + break; + case HALT_ACTION_HALT: +#if ENABLE_PANIC_SHELL + if (reason == HALT_REASON_SW_PANIC) { + dprintf(ALWAYS, "CRASH: starting debug shell... (reason = %d)\n", reason); + arch_disable_ints(); + panic_shell_start(); + } +#endif // ENABLE_PANIC_SHELL + dprintf(ALWAYS, "HALT: spinning forever... (reason = %d)\n", reason); + break; + } + + arch_disable_ints(); + for (;;) + arch_idle(); +} +#endif diff --git a/platform/qemu-virt-m68k/platform_p.h b/platform/qemu-virt-m68k/platform_p.h new file mode 100644 index 00000000..4d3b3612 --- /dev/null +++ b/platform/qemu-virt-m68k/platform_p.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018 Travis Geiselbrecht + * + * 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 + +#include + +void uart_init(void); + +void pic_early_init(void); +void pic_init(void); +void goldfish_rtc_early_init(void); +void goldfish_rtc_init(void); +void goldfish_tty_early_init(void); +void goldfish_tty_init(void); diff --git a/platform/qemu-virt-m68k/rules.mk b/platform/qemu-virt-m68k/rules.mk new file mode 100644 index 00000000..49306959 --- /dev/null +++ b/platform/qemu-virt-m68k/rules.mk @@ -0,0 +1,27 @@ +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +ARCH := m68k +LK_HEAP_IMPLEMENTATION ?= dlmalloc + +MODULE_DEPS += lib/cbuf +MODULE_DEPS += dev/virtio/block +MODULE_DEPS += dev/virtio/gpu +MODULE_DEPS += dev/virtio/net + +MODULE_SRCS += $(LOCAL_DIR)/goldfish_rtc.c +MODULE_SRCS += $(LOCAL_DIR)/goldfish_tty.c +MODULE_SRCS += $(LOCAL_DIR)/pic.c +MODULE_SRCS += $(LOCAL_DIR)/platform.c + +MEMBASE ?= 0x00000000 +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 diff --git a/project/qemu-virt-m68k-test.mk b/project/qemu-virt-m68k-test.mk new file mode 100644 index 00000000..f8a3fe41 --- /dev/null +++ b/project/qemu-virt-m68k-test.mk @@ -0,0 +1,9 @@ +# main project for qemu-riscv64 +MODULES += \ + app/shell + +include project/virtual/test.mk +include project/virtual/fs.mk +include project/virtual/minip.mk +include project/target/qemu-virt-m68k.mk + diff --git a/project/target/qemu-virt-m68k.mk b/project/target/qemu-virt-m68k.mk new file mode 100644 index 00000000..e2012dd7 --- /dev/null +++ b/project/target/qemu-virt-m68k.mk @@ -0,0 +1,2 @@ +TARGET := qemu-virt-m68k + diff --git a/scripts/do-qemum68k b/scripts/do-qemum68k new file mode 100755 index 00000000..47ab2923 --- /dev/null +++ b/scripts/do-qemum68k @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +set -e +set -x + +PROJECT=qemu-virt-m68k-test + +$DIR/make-parallel $PROJECT +qemu-system-m68k -machine virt -cpu m68040 -kernel build-${PROJECT}/lk.elf -nographic $@ diff --git a/target/qemu-virt-m68k/rules.mk b/target/qemu-virt-m68k/rules.mk new file mode 100644 index 00000000..7d159798 --- /dev/null +++ b/target/qemu-virt-m68k/rules.mk @@ -0,0 +1,5 @@ +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +PLATFORM := qemu-virt-m68k diff --git a/top/include/lk/bits.h b/top/include/lk/bits.h index e2ad2a89..a1bd892e 100644 --- a/top/include/lk/bits.h +++ b/top/include/lk/bits.h @@ -15,6 +15,7 @@ __BEGIN_CDECLS #define clz(x) __builtin_clz(x) #define ctz(x) __builtin_ctz(x) +#define ffs(x) __builtin_ffs(x) #define BIT(x, bit) ((x) & (1UL << (bit))) #define BIT_SHIFT(x, bit) (((x) >> (bit)) & 1)