Skip to content

Commit

Permalink
Migrate to ACLINT
Browse files Browse the repository at this point in the history
To implement timer interrupts and inter-processor interrupts (IPIs) on
RISC-V, ACLINT and CLINT are the commonly used hardware components. The
key difference between ACLINT and CLINT lies in ACLINT’s ability to
support both Supervisor software interrupts (SSWI) and Machine software
interrupts (MSWI).

Additionally, ACLINT modularizes its hardware functionalities, such as
timers and IPI controllers, making the design and implementation more
flexible than CLINT.

According to the Linux kernel documentation:
https://www.kernel.org/doc/html/next/riscv/boot.html#kernel-entry,
there are two methods for entering the Linux kernel on SMP systems:
- RISCV_BOOT_SPINWAIT: Boots all harts simultaneously, mainly used
  for older firmwares without SBI HSM extension and M-mode RISC-V
  kernels.
- Ordered booting: Utilizes the SBI HSM extension to boot only one
  hart during the initial boot phase.

The Linux kernel introduced ordered booting (commit 'cfafe26') to
simplify multi-stage SMP boot management. The commit explains that the
previous method complicated the multi-stage boot process, requiring
management of all harts at each stage. The SBI HSM extension simplifies
this by booting only one hart initially, which can then bring up the
remaining harts sequentially.

To fully support the HSM extension, ACLINT is necessary. particularly
for supervisor-level interrupt management.

This commit transitions from CLINT to ACLINT, aligning with modern
RISC-V specifications and providing support for 'mtimer', 'mswi', and
'sswi'. The existing CLINT implementation has been removed entirely as
ACLINT covers its functionalities.

Testing instructions:
- Run the following command to test the implementation:
  'make check SMP=n', where 'n' is the number of harts to simulate.
- After booting the emulator:
  - Verify multi-core operation and HSM implementation with
    '/proc/cpuinfo'.
  - Check timer interrupts via '/proc/interrupts'.
  - Confirm ACLINT is correctly recognized using '/proc/device-tree'.

Future work:
Currently, due to the lack of implementation, the introduced ACLINT uses
only supervisor-level IPI. Therefore, although the logic for mswi is
implemented, it is not being used at the moment.

Also, SMP support remains incomplete. For example, the current semu
implementation sequentially simulates multi-core execution, causing a
slowdown as the number of cores increases. This leads to a time
desynchronization issue across cores.

To achieve parallel multi-core simulation, RFENCE extension
implementation is required. However, it is currently incomplete. After
completing ACLINT, the next step is to implement the RFENCE extension to
fully support multi-core system simulation.
  • Loading branch information
Mes0903 committed Dec 15, 2024
1 parent 59d39f5 commit 5baf7ee
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 140 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ OBJS := \
plic.o \
uart.o \
main.o \
clint.o \
aclint.o \
$(OBJS_EXTRA)

deps := $(OBJS:%.o=.%.o.d)
Expand Down
218 changes: 218 additions & 0 deletions aclint.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#include <stdint.h>
#include "device.h"
#include "riscv.h"
#include "riscv_private.h"

/* ACLINT MTIMER */
void aclint_mtimer_update_interrupts(hart_t *hart, mtimer_state_t *mtimer)
{
if (semu_timer_get(&mtimer->mtime) >= mtimer->mtimecmp[hart->mhartid])
hart->sip |= RV_INT_STI_BIT; /* Set Supervisor Timer Interrupt */
else
hart->sip &= ~RV_INT_STI_BIT; /* Clear Supervisor Timer Interrupt */
}

static bool aclint_mtimer_reg_read(mtimer_state_t *mtimer,
uint32_t addr,
uint32_t *value)
{
/* 'addr & 0x4' is used to determine the upper or lower 32 bits
* of the mtimecmp register. If 'addr & 0x4' is 0, then the lower 32
* bits are accessed.
*
* 'addr >> 3' is used to get the index of the mtimecmp array. In
* "ACLINT MTIMER Compare Register Map", each mtimecmp register is 8
* bytes long. So, we need to divide the address by 8 to get the index.
*/

/* mtimecmp (0x4300000 ~ 0x4307FF8) */
if (addr < 0x7FF8) {
*value =
(uint32_t) (mtimer->mtimecmp[addr >> 3] >> (addr & 0x4 ? 32 : 0));
return true;
}

/* mtime (0x4307FF8 ~ 0x4308000) */
if (addr < 0x8000) {
*value = (uint32_t) (semu_timer_get(&mtimer->mtime) >>
(addr & 0x4 ? 32 : 0));
return true;
}
return false;
}

static bool aclint_mtimer_reg_write(mtimer_state_t *mtimer,
uint32_t addr,
uint32_t value)
{
/* The 'cmp_val & 0xFFFFFFFF' is used to select the upper 32 bits
* of mtimer->mtimecmp[addr >> 3], then shift the value to the left by
* 32 bits to set the upper 32 bits.
*
* Similarly, 'cmp_val & 0xFFFFFFFF00000000ULL' is used to select the lower
* 32 bits of mtimer->mtimecmp[addr >> 3].
*/

/* mtimecmp (0x4300000 ~ 0x4307FF8) */
if (addr < 0x7FF8) {
uint64_t cmp_val = mtimer->mtimecmp[addr >> 3];

if (addr & 0x4)
cmp_val = (cmp_val & 0xFFFFFFFF) | ((uint64_t) value << 32);
else
cmp_val = (cmp_val & 0xFFFFFFFF00000000ULL) | value;

mtimer->mtimecmp[addr >> 3] = cmp_val;
return true;
}

/* mtime (0x4307FF8 ~ 0x4308000) */
if (addr < 0x8000) {
uint64_t mtime_val = mtimer->mtime.begin;
if (addr & 0x4)
mtime_val = (mtime_val & 0xFFFFFFFF) | ((uint64_t) value << 32);
else
mtime_val = (mtime_val & 0xFFFFFFFF00000000ULL) | value;

semu_timer_rebase(&mtimer->mtime, mtime_val);
return true;
}

return false;
}

void aclint_mtimer_read(hart_t *hart,
mtimer_state_t *mtimer,
uint32_t addr,
uint8_t width,
uint32_t *value)
{
if (!aclint_mtimer_reg_read(mtimer, addr, value))
vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val);

*value >>= (RV_MEM_SW - width);
}

void aclint_mtimer_write(hart_t *hart,
mtimer_state_t *mtimer,
uint32_t addr,
uint8_t width,
uint32_t value)
{
if (!aclint_mtimer_reg_write(mtimer, addr, value << (RV_MEM_SW - width)))
vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val);
}

/* ACLINT MSWI */
void aclint_mswi_update_interrupts(hart_t *hart, mswi_state_t *mswi)
{
if (mswi->msip[hart->mhartid])
hart->sip |= RV_INT_SSI_BIT; /* Set Machine Software Interrupt */
else
hart->sip &= ~RV_INT_SSI_BIT; /* Clear Machine Software Interrupt */
}

static bool aclint_mswi_reg_read(mswi_state_t *mswi,
uint32_t addr,
uint32_t *value)
{
/* 'msip' is an array where each entry corresponds to a Hart,
* each entry is 4 bytes (32 bits). So, we need to divide the address
* by 4 to get the index.
*/

/* Address range for msip: 0x4400000 ~ 0x4404000 */
if (addr < 0x4000) {
*value = mswi->msip[addr >> 2];
return true;
}
return false;
}

static bool aclint_mswi_reg_write(mswi_state_t *mswi,
uint32_t addr,
uint32_t value)
{
if (addr < 0x4000) {
mswi->msip[addr >> 2] = value & 0x1; /* Only the LSB is valid */
return true;
}
return false;
}

void aclint_mswi_read(hart_t *hart,
mswi_state_t *mswi,
uint32_t addr,
uint8_t width,
uint32_t *value)
{
if (!aclint_mswi_reg_read(mswi, addr, value))
vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val);

*value >>= (RV_MEM_SW - width);
}

void aclint_mswi_write(hart_t *hart,
mswi_state_t *mswi,
uint32_t addr,
uint8_t width,
uint32_t value)
{
if (!aclint_mswi_reg_write(mswi, addr, value << (RV_MEM_SW - width)))
vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val);
}

/* ACLINT SSWI */
void aclint_sswi_update_interrupts(hart_t *hart, sswi_state_t *sswi)
{
if (sswi->ssip[hart->mhartid])
hart->sip |= RV_INT_SSI_BIT; /* Set Supervisor Software Interrupt */
else
hart->sip &= ~RV_INT_SSI_BIT; /* Clear Supervisor Software Interrupt */
}

static bool aclint_sswi_reg_read(__attribute__((unused)) sswi_state_t *sswi,
uint32_t addr,
uint32_t *value)
{
/* Address range for ssip: 0x4500000 ~ 0x4504000 */
if (addr < 0x4000) {
*value = 0; /* Upper 31 bits are zero, and LSB reads as 0 */
return true;
}
return false;
}

static bool aclint_sswi_reg_write(sswi_state_t *sswi,
uint32_t addr,
uint32_t value)
{
if (addr < 0x4000) {
sswi->ssip[addr >> 2] = value & 0x1; /* Only the LSB is valid */

return true;
}
return false;
}

void aclint_sswi_read(hart_t *hart,
sswi_state_t *sswi,
uint32_t addr,
uint8_t width,
uint32_t *value)
{
if (!aclint_sswi_reg_read(sswi, addr, value))
vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val);

*value >>= (RV_MEM_SW - width);
}

void aclint_sswi_write(hart_t *hart,
sswi_state_t *sswi,
uint32_t addr,
uint8_t width,
uint32_t value)
{
if (!aclint_sswi_reg_write(sswi, addr, value << (RV_MEM_SW - width)))
vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val);
}
96 changes: 0 additions & 96 deletions clint.c

This file was deleted.

Loading

0 comments on commit 5baf7ee

Please sign in to comment.