Skip to content

Commit

Permalink
Implement basic CLINT support mtimer device
Browse files Browse the repository at this point in the history
In this PR, replace the `timer` defined in the `emu_state_t` structure,
which originally compared with the instruction counter and triggered
software timer interrupts.

Further, make 64-bit unsigned integer `mtime` with Real time counter (RTC)
to prevent mtime overflow.

Replacing timer with MTIMER perpheral also triggers timer interrupts normally.

Clarify that the MTIMER peripheral can also trigger timer interrupts
within a scheduled period by monitoring `/proc/interrupts` reveals an
increase in timer interrupts triggered by `riscv-timer`:

```
  1:        595  SiFive PLIC   1 Edge      ttyS0
  2:         19  SiFive PLIC   3 Edge      virtio1
  3:          0  SiFive PLIC   2 Edge      virtio0
  5:      58985  RISC-V INTC   5 Edge      riscv-timer
...
  1:        610  SiFive PLIC   1 Edge      ttyS0
  2:         19  SiFive PLIC   3 Edge      virtio1
  3:          0  SiFive PLIC   2 Edge      virtio0
  5:      59196  RISC-V INTC   5 Edge      riscv-timer
...
  1:        625  SiFive PLIC   1 Edge      ttyS0
  2:         19  SiFive PLIC   3 Edge      virtio1
  3:          0  SiFive PLIC   2 Edge      virtio0
  5:      59296  RISC-V INTC   5 Edge      riscv-timer
```

Additionally, the `vm_timer_t` structure is use to retrieve system-wide
clock through `clock_gettime()`. The clock frequency is configured to 65MHz,
which matches the `timebase-frequency` specified in `minimal.dts`.
  • Loading branch information
chiangkd committed Jun 12, 2024
1 parent 6525dc4 commit 4fd0be6
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 22 deletions.
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"

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 _
}

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)

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);


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:
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

0 comments on commit 4fd0be6

Please sign in to comment.