Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic ACLINT support for the MTIMER register #45

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ all: $(BIN) minimal.dtb
OBJS := \
riscv.o \
ram.o \
timer.o \
aclint.o \
plic.o \
uart.o \
main.o \
Expand Down
128 changes: 128 additions & 0 deletions aclint.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "device.h"
#include "riscv.h"
#include "riscv_private.h"

jserv marked this conversation as resolved.
Show resolved Hide resolved
void aclint_timer_interrupts(vm_t *vm, aclint_state_t *aclint)
{
uint64_t time_delta = aclint->mtimecmp - vm_timer_gettime(&vm->timer);
if ((time_delta & 0x8000000000000000) || time_delta == 0)
vm->sip |= RV_INT_STI_BIT;
else
vm->sip &= ~RV_INT_STI_BIT;
}

void aclint_update_interrupts(vm_t *vm, aclint_state_t *aclint)
{
if (aclint->setssip)
vm->sip |= RV_INT_SSI_BIT;
else
vm->sip &= ~RV_INT_SSI_BIT;
}

static bool aclint_reg_read(aclint_state_t *aclint,
uint32_t addr,
uint32_t *value)
{
#define _(reg) ACLINT_##reg
switch (addr) {
case _(SSWI):
/* sswi */
*value = aclint->setssip;
return true;
case _(MTIMECMP_LO):
/* mtimecmp */
*value = aclint->mtimecmp & 0xFFFFFFFF;
return true;
case _(MTIMECMP_HI):
/* mtimecmph */
*value = (aclint->mtimecmp >> 32) & 0xFFFFFFFF;
return true;
case _(MTIME_LO):
/* mtime */
*value = (uint32_t) (vm_timer_gettime(&aclint->mtimer) & 0xFFFFFFFF);
return true;
case _(MTIME_HI):
/* mtimeh */
*value =
(uint32_t) (vm_timer_gettime(&aclint->mtimer) >> 32) & 0xFFFFFFFF;
return true;
default:
return false;
}
#undef _
}

jserv marked this conversation as resolved.
Show resolved Hide resolved
static bool aclint_reg_write(aclint_state_t *aclint,
uint32_t addr,
uint32_t value)
{
#define _(reg) ACLINT_##reg
switch (addr) {
case _(SSWI):
/* sswi */
aclint->setssip = value;
return true;
case _(MTIMECMP_LO):
/* mtimecmp */
aclint->mtimecmp |= value;
return true;
case _(MTIMECMP_HI):
/* mtimecmph */
aclint->mtimecmp |= ((uint64_t) value) << 32;
return true;
case _(MTIME_LO):
/* mtime */
vm_timer_rebase(&aclint->mtimer, value);
return true;
case _(MTIME_HI):
/* mtimeh */
vm_timer_rebase(&aclint->mtimer, ((uint64_t) value) << 32);
return true;
default:
return false;
}
#undef _
}

void aclint_read(vm_t *vm,
aclint_state_t *aclint,
uint32_t addr,
uint8_t width,
uint32_t *value)
{
switch (width) {
case RV_MEM_LW:
if (!aclint_reg_read(aclint, addr >> 2, value))
vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val);
break;
case RV_MEM_LBU:
case RV_MEM_LB:
case RV_MEM_LHU:
case RV_MEM_LH:
vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val);
return;
default:
vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0);
return;
}
}
void aclint_write(vm_t *vm,
aclint_state_t *aclint,
uint32_t addr,
uint8_t width,
uint32_t value)
{
switch (width) {
case RV_MEM_SW:
if (!aclint_reg_write(aclint, addr >> 2, value))
vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val);
break;
case RV_MEM_SB:
case RV_MEM_SH:
vm_set_exception(vm, RV_EXC_STORE_MISALIGN, vm->exc_val);
return;
default:
vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0);
return;
}
}
49 changes: 48 additions & 1 deletion device.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,54 @@ void ram_write(vm_t *core,
const uint8_t width,
const uint32_t value);

/* ACLINT */
#define ACLINT_REG_LIST \
_(SSWI, 0x0000) \
_(MTIMECMP_LO, 0x4000) \
_(MTIMECMP_HI, 0x4004) \
_(MTIME_LO, 0x7FF8) \
_(MTIME_HI, 0x7FFC)
Comment on lines +29 to +30
Copy link
Collaborator

@ranvd ranvd Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After enable the clint driver for Linux kernel, the "load access fault" show up. Maybe this is caused by the wrong address map here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After correcting the error above, the kernel gets stuck after logging out [ 0.010981] clint: registering percpu irq failed [-16]. This error may occur in my implementation as well. I'm still figuring out the reason.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing out my mistake!


enum {
#define _(reg, addr) ACLINT_##reg = addr >> 2,
ACLINT_REG_LIST
#undef _
};

typedef struct {
vm_t *vm;
vm_timer_t mtimer;
uint64_t mtimecmp;
uint32_t setssip;
} aclint_state_t;

void aclint_update_interrupts(vm_t *core, aclint_state_t *aclint);
void aclint_timer_interrupts(vm_t *core, aclint_state_t *aclint);
void aclint_read(vm_t *core,
aclint_state_t *aclint,
uint32_t addr,
uint8_t width,
uint32_t *value);
void aclint_write(vm_t *core,
aclint_state_t *aclint,
uint32_t addr,
uint8_t width,
uint32_t value);
void aclint_send_ipi(vm_t *vm, aclint_state_t *aclint, uint32_t target_hart);

/* PLIC */
#define PLIC_REG_LIST \
_(InterruptPending, 0x1000) \
_(InterruptEnable, 0x2000) \
_(PriorityThresholds, 0x200000) \
_(InterruptClaim, 0x200004) \
_(InterruptCompletion, 0x200004)

enum {
#define _(reg, addr) PLIC_##reg = addr >> 2,
PLIC_REG_LIST
#undef _
};

typedef struct {
uint32_t masked;
Expand Down Expand Up @@ -177,6 +224,7 @@ typedef struct {
bool stopped;
uint32_t *ram;
uint32_t *disk;
aclint_state_t aclint;
plic_state_t plic;
u8250_state_t uart;
#if SEMU_HAS(VIRTIONET)
Expand All @@ -185,5 +233,4 @@ typedef struct {
#if SEMU_HAS(VIRTIOBLK)
virtio_blk_state_t vblk;
#endif
uint64_t timer;
} emu_state_t;
23 changes: 16 additions & 7 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ static void mem_load(vm_t *vm, uint32_t addr, uint8_t width, uint32_t *value)
emu_update_vblk_interrupts(vm);
return;
#endif
case 0x44: /* CLINT */
aclint_read(vm, &data->aclint, addr & 0xFFFF, width, value);
aclint_update_interrupts(vm, &data->aclint);
return;
}
}
vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val);
Expand Down Expand Up @@ -143,6 +147,10 @@ static void mem_store(vm_t *vm, uint32_t addr, uint8_t width, uint32_t value)
emu_update_vblk_interrupts(vm);
return;
#endif
case 0x44: /* CLINT */
aclint_write(vm, &data->aclint, addr & 0xFFFF, width, value);
aclint_update_interrupts(vm, &data->aclint);
return;
}
}
vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val);
Expand All @@ -162,8 +170,8 @@ static inline sbi_ret_t handle_sbi_ecall_TIMER(vm_t *vm, int32_t fid)
emu_state_t *data = PRIV(vm);
switch (fid) {
case SBI_TIMER__SET_TIMER:
data->timer = (((uint64_t) vm->x_regs[RV_R_A1]) << 32) |
(uint64_t) (vm->x_regs[RV_R_A0]);
data->aclint.mtimecmp = (((uint64_t) vm->x_regs[RV_R_A1])) << 32 |
(uint64_t) vm->x_regs[RV_R_A0];
return (sbi_ret_t){SBI_SUCCESS, 0};
default:
return (sbi_ret_t){SBI_ERR_NOT_SUPPORTED, 0};
Expand Down Expand Up @@ -361,6 +369,8 @@ static int semu_start(int argc, char **argv)
/* Initialize the emulator */
emu_state_t emu;
memset(&emu, 0, sizeof(emu));
emu.aclint.mtimer.freq = 65000000;
emu.aclint.mtimecmp = 0xFFFFFFFFFFFFFFFF;

vm_t vm = {
.priv = &emu,
Expand Down Expand Up @@ -406,8 +416,8 @@ static int semu_start(int argc, char **argv)
atexit(unmap_files);

/* Set up RISC-V hart */
emu.timer = 0xFFFFFFFFFFFFFFFF;
vm.s_mode = true;
vm.timer = emu.aclint.mtimer;
vm.x_regs[RV_R_A0] = 0; /* hart ID. i.e., cpuid */
vm.x_regs[RV_R_A1] = dtb_addr;

Expand Down Expand Up @@ -446,10 +456,9 @@ static int semu_start(int argc, char **argv)
#endif
}

if (vm.insn_count > emu.timer)
vm.sip |= RV_INT_STI_BIT;
else
vm.sip &= ~RV_INT_STI_BIT;
aclint_timer_interrupts(&vm, &emu.aclint);
aclint_update_interrupts(&vm, &emu.aclint);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this blank line.


vm_step(&vm);
if (likely(!vm.error))
Expand Down
6 changes: 6 additions & 0 deletions minimal.dts
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,11 @@
interrupts = <3>;
};
#endif
clint0: clint@4400000 {
#interrupt-cells = <1>;
compatible = "sifive,clint0";
reg = <0x4400000 0x000C000>;
interrupts-extended = <&cpu0_intc 1 &cpu0_intc 5>;
};
};
};
20 changes: 11 additions & 9 deletions plic.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ static bool plic_reg_read(plic_state_t *plic, uint32_t addr, uint32_t *value)
/* no priority support: source priority hardwired to 1 */
if (1 <= addr && addr <= 31)
return true;

#define _(reg) PLIC_##reg
switch (addr) {
case 0x400:
case _(InterruptPending):
*value = plic->ip;
return true;
case 0x800:
case _(InterruptEnable):
*value = plic->ie;
return true;
case 0x80000:
case _(PriorityThresholds):
*value = 0;
/* no priority support: target priority threshold hardwired to 0 */
return true;
case 0x80001:
case _(InterruptClaim):
/* claim */
*value = 0;
uint32_t candidates = plic->ip & plic->ie;
Expand All @@ -45,30 +45,32 @@ static bool plic_reg_read(plic_state_t *plic, uint32_t addr, uint32_t *value)
default:
return false;
}
#undef _
}

static bool plic_reg_write(plic_state_t *plic, uint32_t addr, uint32_t value)
{
/* no priority support: source priority hardwired to 1 */
if (1 <= addr && addr <= 31)
return true;

#define _(reg) PLIC_##reg
switch (addr) {
case 0x800:
case _(InterruptEnable):
value &= ~1;
plic->ie = value;
return true;
case 0x80000:
case _(PriorityThresholds):
/* no priority support: target priority threshold hardwired to 0 */
return true;
case 0x80001:
case _(InterruptCompletion):
/* completion */
if (plic->ie & (1 << value))
plic->masked &= ~(1 << value);
return true;
default:
return false;
}
#undef _
}

void plic_read(vm_t *vm,
Expand Down
20 changes: 15 additions & 5 deletions riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -434,12 +434,22 @@ static void csr_read(vm_t *vm, uint16_t addr, uint32_t *value)
if (idx >= 0x20 || !(vm->s_mode || ((vm->scounteren >> idx) & 1)))
vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0);
else {
/* Use the instruction counter for all of the counters.
* Ideally, reads should return the value before the increment,
* and writes should set the value after the increment. However,
* we do not expose any way to write the counters.
/*
* We do not expose any way to write the counters.
*/
*value = vm->insn_count >> ((addr & (1 << 7)) ? 32 : 0);
switch (addr) {
case RV_CSR_INSTRET:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some comments here. E.g., denote the types.

case RV_CSR_INSTRETH:
*value = vm->insn_count >> ((addr & (1 << 7)) ? 32 : 0);
break;
case RV_CSR_TIME:
case RV_CSR_TIMEH:
*value =
vm_timer_gettime(&vm->timer) >> ((addr & 1 << 7) ? 32 : 0);
break;
default:
break;
}
}
return;
}
Expand Down
Loading