PoC: Add example firmware with embedded that calls syscalls implemented in C

App is embedded in firmware and is loaded into app RAM when firmware
starts.
App continuously calls SET_LED syscalls.

Simulation: `make tb_application_fpga_irqpoc_c_example`
This commit is contained in:
Mikael Ågren 2024-12-13 15:41:59 +01:00
parent e1d7608897
commit 2e1925555d
No known key found for this signature in database
GPG Key ID: E02DA3D397792C46
6 changed files with 448 additions and 1 deletions

View File

@ -157,6 +157,12 @@ IRQPOC_WITH_APP_OBJS = \
$(P)/fw/irqpoc_with_app/main.o \
$(P)/fw/irqpoc_with_app/start.o
IRQPOC_C_EXAMPLE_OBJS = \
$(P)/fw/irqpoc_c_example/main.o \
$(P)/fw/irqpoc_c_example/start.o \
$(P)/fw/tk1/led.o \
$(P)/fw/tk1/assert.o
#-------------------------------------------------------------------
# All: Complete build of HW and FW.
#-------------------------------------------------------------------
@ -196,6 +202,7 @@ $(TESTFW_OBJS): $(FIRMWARE_DEPS)
$(IRQPOC_OBJS): $(FIRMWARE_DEPS)
$(IRQPOC_LED_TOGGLE_OBJS): $(FIRMWARE_DEPS)
$(IRQPOC_WITH_APP_OBJS): $(FIRMWARE_DEPS)
$(IRQPOC_C_EXAMPLE_OBJS): $(FIRMWARE_DEPS)
firmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@
@ -247,6 +254,9 @@ irqpoc_led_toggle.elf: $(IRQPOC_LED_TOGGLE_OBJS) $(P)/fw/tk1/firmware.lds
irqpoc_with_app.elf: $(IRQPOC_WITH_APP_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(IRQPOC_WITH_APP_OBJS) $(LDFLAGS) -o $@
irqpoc_c_example.elf: $(IRQPOC_C_EXAMPLE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(IRQPOC_C_EXAMPLE_OBJS) $(LDFLAGS) -o $@
# Generate a fake BRAM file that will be filled in later after place-n-route
bram_fw.hex:
$(ICESTORM_PATH)icebram -v -g 32 $(BRAM_FW_SIZE) > $@
@ -261,6 +271,8 @@ irqpoc.hex: irqpoc.bin
python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
irqpoc_with_app.hex: irqpoc_with_app.bin
python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
irqpoc_c_example.hex: irqpoc_c_example.bin
python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
.PHONY: check-binary-hashes
@ -485,6 +497,10 @@ tb_application_fpga_irqpoc: irqpoc.hex emptyapp tb_application_fpga
tb_application_fpga_irqpoc_with_app: SIMFIRMWARE=irqpoc_with_app.hex
tb_application_fpga_irqpoc_with_app: irqpoc_with_app.hex emptyapp tb_application_fpga
.PHONY: tb_application_fpga_irqpoc_c_example
tb_application_fpga_irqpoc_c_example: SIMFIRMWARE=irqpoc_c_example.hex
tb_application_fpga_irqpoc_c_example: irqpoc_c_example.hex emptyapp tb_application_fpga
#-------------------------------------------------------------------
# FPGA device programming.
@ -535,6 +551,7 @@ clean_fw:
rm -f $(IRQPOC_OBJS)
rm -f $(IRQPOC_LED_TOGGLE_OBJS)
rm -f $(IRQPOC_WITH_APP_OBJS)
rm -f $(IRQPOC_C_EXAMPLE_OBJS)
rm -f qemu_firmware.elf
.PHONY: clean_fw

View File

@ -0,0 +1,9 @@
# Uses ../.clang-format
FMTFILES=main.c
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(FMTFILES)
clang-format --verbose -i $(FMTFILES)
.PHONY: checkfmt
checkfmt:
clang-format --dry-run --ferror-limit=0 --Werror $(FMTFILES)

View File

@ -0,0 +1,102 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
#define regnum_q0 0
#define regnum_q1 1
#define regnum_q2 2
#define regnum_q3 3
#define regnum_x0 0
#define regnum_x1 1
#define regnum_x2 2
#define regnum_x3 3
#define regnum_x4 4
#define regnum_x5 5
#define regnum_x6 6
#define regnum_x7 7
#define regnum_x8 8
#define regnum_x9 9
#define regnum_x10 10
#define regnum_x11 11
#define regnum_x12 12
#define regnum_x13 13
#define regnum_x14 14
#define regnum_x15 15
#define regnum_x16 16
#define regnum_x17 17
#define regnum_x18 18
#define regnum_x19 19
#define regnum_x20 20
#define regnum_x21 21
#define regnum_x22 22
#define regnum_x23 23
#define regnum_x24 24
#define regnum_x25 25
#define regnum_x26 26
#define regnum_x27 27
#define regnum_x28 28
#define regnum_x29 29
#define regnum_x30 30
#define regnum_x31 31
#define regnum_zero 0
#define regnum_ra 1
#define regnum_sp 2
#define regnum_gp 3
#define regnum_tp 4
#define regnum_t0 5
#define regnum_t1 6
#define regnum_t2 7
#define regnum_s0 8
#define regnum_s1 9
#define regnum_a0 10
#define regnum_a1 11
#define regnum_a2 12
#define regnum_a3 13
#define regnum_a4 14
#define regnum_a5 15
#define regnum_a6 16
#define regnum_a7 17
#define regnum_s2 18
#define regnum_s3 19
#define regnum_s4 20
#define regnum_s5 21
#define regnum_s6 22
#define regnum_s7 23
#define regnum_s8 24
#define regnum_s9 25
#define regnum_s10 26
#define regnum_s11 27
#define regnum_t3 28
#define regnum_t4 29
#define regnum_t5 30
#define regnum_t6 31
// x8 is s0 and also fp
#define regnum_fp 8
#define r_type_insn(_f7, _rs2, _rs1, _f3, _rd, _opc) \
.word (((_f7) << 25) | ((_rs2) << 20) | ((_rs1) << 15) | ((_f3) << 12) | ((_rd) << 7) | ((_opc) << 0))
#define picorv32_getq_insn(_rd, _qs) \
r_type_insn(0b0000000, 0, regnum_ ## _qs, 0b100, regnum_ ## _rd, 0b0001011)
#define picorv32_setq_insn(_qd, _rs) \
r_type_insn(0b0000001, 0, regnum_ ## _rs, 0b010, regnum_ ## _qd, 0b0001011)
#define picorv32_retirq_insn() \
r_type_insn(0b0000010, 0, 0, 0b000, 0, 0b0001011)
#define picorv32_maskirq_insn(_rd, _rs) \
r_type_insn(0b0000011, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)
#define picorv32_waitirq_insn(_rd) \
r_type_insn(0b0000100, 0, 0, 0b100, regnum_ ## _rd, 0b0001011)
#define picorv32_timer_insn(_rd, _rs) \
r_type_insn(0b0000101, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
#include "../tk1/types.h"
#include "../tk1/led.h"
#include "../tk1/assert.h"
// Proof-of-concept firmware for handling syscalls.
// This is NOT a best-practice example of secure syscall implementation.
#define SYSCALL_HI (1 << 31)
#define SYSCALL_LO 0
#define SYSCALL_HI_SET_LED (SYSCALL_HI | 10)
#define SYSCALL_LO_SET_LED (SYSCALL_LO | 10)
static void delay(int32_t count) {
volatile int32_t c = count;
while (c > 0) {
c--;
}
}
int32_t syscall_lo_handler(uint32_t syscall_nr, uint32_t arg1) {
switch (syscall_nr) {
case SYSCALL_LO_SET_LED:
set_led(arg1);
//delay(1000000);
return 0;
default:
assert(1 == 2);
}
assert(1 == 2);
return -1; // This should never run
}
int32_t syscall_hi_handler(uint32_t syscall_nr, uint32_t arg1) {
switch (syscall_nr) {
case SYSCALL_HI_SET_LED:
set_led(arg1);
//delay(500000);
return 0;
default:
assert(1 == 2);
}
assert(1 == 2);
return -1; // This should never run
}
int main(void)
{
while (1) {
}
}

View File

@ -0,0 +1,261 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// This firmware copies an app from ROM to app RAM.
// The app continuosly calls the SET_LED syscalls in firmware (main.c).
//
#include "../tk1_mem.h"
#include "custom_ops.S" // PicoRV32 custom instructions
#define illegal_insn() .word 0
#define FW_SP_STORAGE (TK1_MMIO_FW_RAM_BASE + TK1_MMIO_FW_RAM_SIZE - 4)
#define FW_STACK_TOP (TK1_MMIO_FW_RAM_BASE + TK1_MMIO_FW_RAM_SIZE - 2*4)
#define LED_RED (1 << TK1_MMIO_TK1_LED_R_BIT)
#define LED_GREEN (1 << TK1_MMIO_TK1_LED_G_BIT)
#define LED_BLUE (1 << TK1_MMIO_TK1_LED_B_BIT)
.section ".text.init"
.globl _start
_start:
j init
//
// IRQ handler
//
.=0x10 // IRQ handler at fixed address 0x10
irq_handler:
// PicoRV32 stores the IRQ bitmask in x4.
// If bit 31 is 1: IRQ31 was triggered.
// If bit 30 is 1: IRQ30 was triggered.
irq_syscall_lo_check:
li t4, (1 << 30)
bne x4, t4, irq_syscall_hi_check
// Run low privilege syscall handler
call syscall_lo_handler
j irq_source_check_done
irq_syscall_hi_check:
li t4, (1 << 31)
bne x4, t4, unexpected_irq
// Save app stack pointer. App is responsible for saving the rest of
// the registers.
la t0, FW_SP_STORAGE
sw sp, 0(t0)
// Setup firmware stack pointer
la sp, FW_STACK_TOP
// Run high privilege syscall handler
call syscall_hi_handler
// Restore app stack pointer
la t0, FW_SP_STORAGE
lw sp, 0(t0)
j irq_source_check_done
unexpected_irq:
illegal_insn()
irq_source_check_done:
// Verify that interrupt return address is in app RAM
li t0, TK1_RAM_BASE // 0x40000000
blt x3, t0, x3_invalid
li t0, TK1_RAM_BASE + TK1_RAM_SIZE // 0x40020000
bge x3, t0, x3_invalid
j x3_valid
x3_invalid:
illegal_insn()
j x3_invalid
x3_valid:
// Remove data left over from the syscall handling
mv x0, zero
mv x1, zero
// x2 (sp) is assumed to be preserved by the interrupt handler
// x3 (interrupt return address) assumed preserved
mv x4, zero
mv x5, zero
mv x6, zero
mv x7, zero
mv x8, zero
mv x9, zero
// x10 (a0) contains syscall return value. And should not be destroyed.
mv x11, zero
mv x12, zero
mv x13, zero
mv x14, zero
mv x15, zero
mv x16, zero
mv x17, zero
mv x18, zero
mv x19, zero
mv x20, zero
mv x21, zero
mv x22, zero
mv x23, zero
mv x24, zero
mv x25, zero
mv x26, zero
mv x27, zero
mv x28, zero
mv x29, zero
mv x30, zero
mv x31, zero
picorv32_retirq_insn() // Return from interrupt
//
// Init
//
.=0x100
init:
li t0, TK1_MMIO_TK1_LED
li t1, LED_RED
sw t1, 0(t0)
// Enable IRQs
li t0, 0x3fffffff // IRQ31 & IRQ30 mask
picorv32_maskirq_insn(zero, t0) // Enable IRQs
// Copy app to App RAM
la t0, app_start
la t1, app_end
li t2, 0x40000000 // 0x40000000: App RAM
copy_app:
lw t3, 0(t0)
sw t3, 0(t2)
addi t0, t0, 4
addi t2, t2, 4
bleu t0, t1, copy_app
// Jump to app
li t2, 0x40000000 // 0x40000000: App RAM
jalr zero, 0(t2)
//
// App
//
#define APP_SYSCALL_HI (1 << 31)
#define APP_SYSCALL_LO 0
#define APP_SYSCALL_HI_SET_LED (APP_SYSCALL_HI | 10)
#define APP_SYSCALL_LO_SET_LED (APP_SYSCALL_LO | 10)
.=0x1000
app_start:
// Set stack pointer to end of app RAM
li sp, 0x4001fffc
app_loop:
li a0, APP_SYSCALL_LO_SET_LED
li a1, LED_GREEN
call app_syscall
li a0, APP_SYSCALL_LO_SET_LED
li a1, LED_BLUE
call app_syscall
li a0, APP_SYSCALL_HI_SET_LED
li a1, LED_RED | LED_GREEN
call app_syscall
li a0, APP_SYSCALL_HI_SET_LED
li a1, LED_RED | LED_BLUE
call app_syscall
j app_loop
app_syscall:
// Save registers to stack
addi sp, sp, -32*4
sw x0, 0*4(sp)
sw x1, 1*4(sp)
// x2 (sp) is assumed to be preserved by the interrupt handler.
sw x3, 3*4(sp)
sw x4, 4*4(sp)
sw x5, 5*4(sp)
sw x6, 6*4(sp)
sw x7, 7*4(sp)
sw x8, 8*4(sp)
sw x9, 9*4(sp)
// x10 (a0) will contain syscall return value. And should not be saved.
sw x11, 11*4(sp)
sw x12, 12*4(sp)
sw x13, 13*4(sp)
sw x14, 14*4(sp)
sw x15, 15*4(sp)
sw x16, 16*4(sp)
sw x17, 17*4(sp)
sw x18, 18*4(sp)
sw x19, 19*4(sp)
sw x20, 20*4(sp)
sw x21, 21*4(sp)
sw x22, 22*4(sp)
sw x23, 23*4(sp)
sw x24, 24*4(sp)
sw x25, 25*4(sp)
sw x26, 26*4(sp)
sw x27, 27*4(sp)
sw x28, 28*4(sp)
sw x29, 29*4(sp)
sw x30, 30*4(sp)
sw x31, 31*4(sp)
// Raise interrupt depending on bit 31 in a0
// 0: Low privilege syscall
// 1: High privilege syscall
li t0, (1 << 31)
and t0, a0, t0
beqz t0, app_syscall_low_priv
li t1, 0xe1000000 // High privilege interrupt
sw zero, 0(t1) // Trigger interrupt
j app_syscall_restore_registers
app_syscall_low_priv:
li t1, 0xe0000000 // Low privelege interrupt
sw zero, 0(t1) // Trigger interrupt
app_syscall_restore_registers:
// Restore registers from stack
lw x0, 0*4(sp)
lw x1, 1*4(sp)
// x2 (sp) is assumed to be preserved by the interrupt handler.
lw x3, 3*4(sp)
lw x4, 4*4(sp)
lw x5, 5*4(sp)
lw x6, 6*4(sp)
lw x7, 7*4(sp)
lw x8, 8*4(sp)
lw x9, 9*4(sp)
// x10 (a0) contains syscall return value. And should not be destroyed.
lw x11, 11*4(sp)
lw x12, 12*4(sp)
lw x13, 13*4(sp)
lw x14, 14*4(sp)
lw x15, 15*4(sp)
lw x16, 16*4(sp)
lw x17, 17*4(sp)
lw x18, 18*4(sp)
lw x19, 19*4(sp)
lw x20, 20*4(sp)
lw x21, 21*4(sp)
lw x22, 22*4(sp)
lw x23, 23*4(sp)
lw x24, 24*4(sp)
lw x25, 25*4(sp)
lw x26, 26*4(sp)
lw x27, 27*4(sp)
lw x28, 28*4(sp)
lw x29, 29*4(sp)
lw x30, 30*4(sp)
lw x31, 31*4(sp)
addi sp, sp, 32*4
ret
.align 4
app_end:

View File

@ -84,7 +84,7 @@ module tb_application_fpga_sim ();
//----------------------------------------------------------------
initial begin
// End simulation after XXX time units (set by timescale)
#3000;
#14000;
$display("TIMEOUT");
$finish;
end