-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 2.1 riscv上手 4.5.2 PLIC 4.5.3 AIA * edit 2.1 riscv quick start 4.5.2 PLIC 4.5.3 AIA
- Loading branch information
1 parent
c2776f4
commit 44f0e22
Showing
9 changed files
with
390 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# 安装qemu | ||
安装QEMU 7.2.12: | ||
``` | ||
wget https://download.qemu.org/qemu-7.2.12.tar.xz | ||
# 解压 | ||
tar xvJf qemu-7.2.12.tar.xz | ||
cd qemu-7.2.12 | ||
# 配置Riscv支持 | ||
./configure --target-list=riscv64-softmmu,riscv64-linux-user | ||
make -j$(nproc) | ||
#加入环境变量 | ||
export PATH=$PATH:/path/to/qemu-7.2.12/build | ||
#测试是否安装成功 | ||
qemu-system-riscv64 --version | ||
``` | ||
# 安装交叉编译器 | ||
riscv的交叉编译器需从riscv-gnu-toolchain获取并编译。 | ||
``` | ||
# 安装必要工具 | ||
sudo apt-get install autoconf automake autotools-dev curl python3 python3-pip libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build git cmake libglib2.0-dev libslirp-dev | ||
git clone https://github.com/riscv/riscv-gnu-toolchain | ||
cd riscv-gnu-toolchain | ||
git rm qemu | ||
git submodule update --init --recursive | ||
#上述操作会占用超过5GB以上磁盘空间 | ||
# 如果git报网络错误,可以执行: | ||
git config --global http.postbuffer 524288000 | ||
``` | ||
之后开始编译工具链: | ||
``` | ||
cd riscv-gnu-toolchain | ||
mkdir build | ||
cd build | ||
../configure --prefix=/opt/riscv64 | ||
sudo make linux -j $(nproc) | ||
# 编译完成后,将工具链加入环境变量 | ||
echo 'export PATH=/opt/riscv64/bin:$PATH' >> ~/.bashrc | ||
source ~/.bashrc | ||
``` | ||
这样就得到了 riscv64-unknown-linux-gnu工具链。 | ||
# 编译Linux | ||
``` | ||
git clone https://github.com/torvalds/linux -b v6.2 --depth=1 | ||
cd linux | ||
git checkout v6.2 | ||
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig | ||
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- modules -j$(nproc) | ||
# 开始编译 | ||
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- Image -j$(nproc) | ||
``` | ||
# 制作ubuntu根文件系统 | ||
``` | ||
wget http://cdimage.ubuntu.com/ubuntu-base/releases/20.04/release/ubuntu-base-20.04.2-base-riscv64.tar.gz | ||
mkdir rootfs | ||
dd if=/dev/zero of=riscv_rootfs.img bs=1M count=1024 oflag=direct | ||
mkfs.ext4 riscv_rootfs.img | ||
sudo mount -t ext4 riscv_rootfs.img rootfs/ | ||
sudo tar -xzf ubuntu-base-20.04.2-base-riscv64.tar.gz -C rootfs/ | ||
sudo cp /path-to-qemu/build/qemu-system-riscv64 rootfs/usr/bin/ | ||
sudo cp /etc/resolv.conf rootfs/etc/resolv.conf | ||
sudo mount -t proc /proc rootfs/proc | ||
sudo mount -t sysfs /sys rootfs/sys | ||
sudo mount -o bind /dev rootfs/dev | ||
sudo mount -o bind /dev/pts rootfs/dev/pts | ||
sudo chroot rootfs | ||
# 进入chroot后,安装必要的软件包: | ||
apt-get update | ||
apt-get install git sudo vim bash-completion \ | ||
kmod net-tools iputils-ping resolvconf ntpdate | ||
exit | ||
sudo umount rootfs/proc | ||
sudo umount rootfs/sys | ||
sudo umount rootfs/dev/pts | ||
sudo umount rootfs/dev | ||
sudo umount rootfs | ||
``` | ||
# 运行hvisor | ||
将做好的根文件系统、Linux内核镜像放在hvisor目录下的指定位置,在hvisor根目录下执行`make run ARCH=riscv64`即可 | ||
|
||
默认情况下使用 PLIC,执行`make run ARCH=riscv64 IRQ=aia`开启AIA规范 | ||
|
||
# 可能出现的问题 | ||
linux运行后显示`/bin/sh: 0: can't access tty; job control turned off`,在控制台输入`bash` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# 总体结构 | ||
AIA主要包括两个部分,消息中断控制器 IMSIC 和高级平台级中断控制器 APLIC ,总体结构如图所示 | ||
|
||
<img src="../img/riscv_aia_struct.jpg" style="zoom: 50%;" /> | ||
|
||
外设既可以选择发送消息中断,也可以选择通过线连接的方式发送有线中断。 | ||
|
||
如果外设 A 支持MSI,那么只需要向指定 hart 的中断文件写入指定的数据,之后 IMSIC 就会向目标处理器投送一个中断。 | ||
|
||
对于所有设备,都可以通过中断线与 APLIC 连接, APLIC 将会根据配置,选择中断投送模式为: | ||
* 有线中断 | ||
* MSI | ||
|
||
在hvisor中,中断的投送模式为 MSI | ||
|
||
在hvisor中使用 `IRQ=aia`开启 AIA 规范后,时钟中断的处理仍然一致,软件中断和外部中断的处理有些变化 | ||
# 外部中断 | ||
## IMSIC | ||
|
||
hvisor中一个物理 CPU 对应一个虚拟 CPU ,它们都拥有自己的中断文件 | ||
|
||
<img src="../img/riscv_aia_intfile.png" style="zoom: 80%;" /> | ||
|
||
向某个中断文件写入,即可触发指定 hart 指定特权级别的外部中断 | ||
|
||
为 IMSIC 提供二阶段地址映射表 | ||
```rs | ||
let paddr = 0x2800_0000 as HostPhysAddr; | ||
let size = PAGE_SIZE; | ||
self.gpm.insert(MemoryRegion::new_with_offset_mapper( | ||
paddr as GuestPhysAddr, | ||
paddr + PAGE_SIZE * 1, | ||
size, | ||
MemFlags::READ | MemFlags::WRITE, | ||
))?; | ||
... | ||
``` | ||
|
||
## APLIC | ||
### 结构 | ||
全局只有一个 APLIC | ||
|
||
有线中断到来时,首先到达位于机器模式的根中断域(OpenSBI),之后中断路由到子中断域(hvisor),hvisor将中断信号按照 APLIC 配置好的 target 的寄存器,以 MSI 的方式发送给虚拟机对应的 CPU。 | ||
|
||
<img src="../img/riscv_aia_aplicdomain.png" style="zoom: 70%;" /> | ||
|
||
在 AIA 规范手册中指定了 APLIC 各个字段的字节偏移。定义 APLIC 结构体如下,通过以下方法实现对 APLIC 字段的读写 | ||
```rs | ||
#[repr(C)] | ||
pub struct Aplic { | ||
pub base: usize, | ||
pub size: usize, | ||
} | ||
impl Aplic { | ||
pub fn new(base: usize, size: usize) -> Self { | ||
Self { | ||
base, | ||
size, | ||
} | ||
} | ||
pub fn read_domaincfg(&self) -> u32{ | ||
let addr = self.base + APLIC_DOMAINCFG_BASE; | ||
unsafe { core::ptr::read_volatile(addr as *const u32) } | ||
} | ||
pub fn set_domaincfg(&self, bigendian: bool, msimode: bool, enabled: bool){ | ||
... | ||
let addr = self.base + APLIC_DOMAINCFG_BASE; | ||
let src = (enabled << 8) | (msimode << 2) | bigendian; | ||
unsafe { | ||
core::ptr::write_volatile(addr as *mut u32, src); | ||
} | ||
} | ||
... | ||
} | ||
``` | ||
|
||
### 初始化 | ||
根据设备树中的基地址和大小初始化 APLIC | ||
```rs | ||
pub fn primary_init_early(host_fdt: &Fdt) { | ||
let aplic_info = host_fdt.find_node("/soc/aplic").unwrap(); | ||
init_aplic( | ||
aplic_info.reg().unwrap().next().unwrap().starting_address as usize, | ||
aplic_info.reg().unwrap().next().unwrap().size.unwrap(), | ||
); | ||
} | ||
pub fn init_aplic(aplic_base: usize, aplic_size: usize) { | ||
let aplic = Aplic::new(aplic_base, aplic_size); | ||
APLIC.call_once(|| RwLock::new(aplic)); | ||
} | ||
pub static APLIC: Once<RwLock<Aplic>> = Once::new(); | ||
pub fn host_aplic<'a>() -> &'a RwLock<Aplic> { | ||
APLIC.get().expect("Uninitialized hypervisor aplic!") | ||
} | ||
``` | ||
APLIC全局只有一个,因此加锁避免读写冲突,使用 host_aplic() 方法进行访问 | ||
|
||
虚拟机启动时,将访问 APLIC 的地址空间进行初始化配置,这个地址空间是未被映射的。因此会触发缺页异常,陷入到 hvisor 中来处理 | ||
```rs | ||
pub fn guest_page_fault_handler(current_cpu: &mut ArchCpu) { | ||
... | ||
if addr >= host_aplic_base && addr < host_aplic_base + host_aplic_size { | ||
let mut inst: u32 = read_csr!(CSR_HTINST) as u32; | ||
... | ||
if let Some(inst) = inst { | ||
vaplic_emul_handler(current_cpu, addr, inst); | ||
current_cpu.sepc += ins_size; | ||
} | ||
... | ||
} | ||
} | ||
``` | ||
判断访问的地址空间属于 APLIC 的范围,解析访问指令,进入 vaplic_emul_handler 实现对虚拟机中 APLIC 的模拟 | ||
|
||
```rs | ||
pub fn vaplic_emul_handler( | ||
current_cpu: &mut ArchCpu, | ||
addr: GuestPhysAddr, | ||
inst: Instruction, | ||
) { | ||
let host_aplic = host_aplic(); | ||
let offset = addr.wrapping_sub(host_aplic.read().base); | ||
if offset >= APLIC_DOMAINCFG_BASE && offset < APLIC_SOURCECFG_BASE { | ||
match inst { | ||
Instruction::Sw(i) => { | ||
... | ||
host_aplic.write().set_domaincfg(bigendian, msimode, enabled); | ||
} | ||
Instruction::Lw(i) => { | ||
let value = host_aplic.read().read_domaincfg(); | ||
current_cpu.x[i.rd() as usize] = value as usize; | ||
} | ||
_ => panic!("Unexpected instruction {:?}", inst), | ||
} | ||
} | ||
... | ||
} | ||
``` | ||
## 中断过程 | ||
hvisor 通过缺页异常的方式完成对虚拟机模拟 APLIC 初始化后,进入到虚拟机中,以键盘按下产生的中断为例:中断信号首先来到 OpenSBI ,之后中断路由至 hvisor ,根据target寄存器的配置,向虚拟中断文件写入触发虚拟机的外部中断。 | ||
# 软件中断 | ||
开启 AIA 规范后,虚拟机的 linux 内核会通过 msi 的方式来发送 IPI,不需要再使用 ecall 指令陷入到 hvisor 中 | ||
|
||
<img src="../img/riscv_aia_ipi.jpg" style="zoom:40%;" /> | ||
|
||
如图所示,在hvisor中,向指定hart的中断文件写入,即可触发 IPI。 | ||
|
||
在虚拟机中,只需要向指定的虚拟中断文件写入,即可实现虚拟机中的 IPI,无需hvisor的模拟支持。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
# 中断的来源 | ||
在 hvisor 中有三种中断类型:时钟中断,软件中断和外部中断。 | ||
|
||
时钟中断:当 time 寄存器变得大于 timecmp 寄存器时,产生一个时钟中断 | ||
|
||
软件中断: 在多核系统中,一个 hart 向另一个 hart 发送核间中断,通过SBI调用来实现 | ||
|
||
外部中断: 外部设备通过中断线将中断信号传给处理器 | ||
|
||
# 时钟中断 | ||
虚拟机需要触发时钟中断时,通过 ecall 指令陷入到 hvisor 中 | ||
```rust | ||
ExceptionType::ECALL_VS => { | ||
trace!("ECALL_VS"); | ||
sbi_vs_handler(current_cpu); | ||
current_cpu.sepc += 4; | ||
} | ||
... | ||
pub fn sbi_vs_handler(current_cpu: &mut ArchCpu) { | ||
let eid: usize = current_cpu.x[17]; | ||
let fid: usize = current_cpu.x[16]; | ||
let sbi_ret; | ||
match eid { | ||
... | ||
SBI_EID::SET_TIMER => { | ||
sbi_ret = sbi_time_handler(fid, current_cpu); | ||
} | ||
... | ||
} | ||
} | ||
``` | ||
如果没有开启 sstc 扩展,则需要通过 SBI 调用陷入到机器模式,设置 mtimecmp 寄存器,清零虚拟机的时钟中断挂起位,打开 hvisor 的时钟中断使能位;如果开启了 sstc 扩展,则可以直接设置 stimecmp 。 | ||
```rs | ||
pub fn sbi_time_handler(fid: usize, current_cpu: &mut ArchCpu) -> SbiRet { | ||
... | ||
if current_cpu.sstc { | ||
write_csr!(CSR_VSTIMECMP, stime); | ||
} else { | ||
set_timer(stime); | ||
unsafe { | ||
// clear guest timer interrupt pending | ||
hvip::clear_vstip(); | ||
// enable timer interrupt | ||
sie::set_stimer(); | ||
} | ||
} | ||
return sbi_ret; | ||
} | ||
``` | ||
当 time 寄存器变得大于 timecmp 寄存器时,产生一个时钟中断 | ||
|
||
中断触发后,保存陷入上下文,并分发到相对应的处理函数中 | ||
```rs | ||
InterruptType::STI => { | ||
unsafe { | ||
hvip::set_vstip(); | ||
sie::clear_stimer(); | ||
} | ||
} | ||
``` | ||
将虚拟机的时钟中断挂起位置为1,即向虚拟机注入时钟中断,将hvisor的时钟中断使能位清零,完成中断处理 | ||
|
||
# 软件中断 | ||
虚拟机需要发送 IPI 时,通过 ecall 指令陷入到 hvisor 中 | ||
```rs | ||
SBI_EID::SEND_IPI => { | ||
... | ||
sbi_ret = sbi_call_5( | ||
eid, | ||
fid, | ||
current_cpu.x[10], | ||
current_cpu.x[11], | ||
current_cpu.x[12], | ||
current_cpu.x[13], | ||
current_cpu.x[14], | ||
); | ||
} | ||
``` | ||
再通过 SBI 调用陷入到机器模式中向指定的 hart 发送 IPI ,设置 mip 寄存器的 SSIP 为1即可向hvisor注入核间中断 | ||
|
||
中断触发后,保存陷入上下文,并分发到相对应的处理函数中 | ||
```rs | ||
pub fn handle_ssi(current_cpu: &mut ArchCpu) { | ||
... | ||
clear_csr!(CSR_SIP, 1 << 1); | ||
set_csr!(CSR_HVIP, 1 << 2); | ||
check_events(); | ||
} | ||
``` | ||
将虚拟机的软件中断挂起位置为1,向虚拟机中注入软件中断。之后判断核间中断的类型,唤醒或阻塞cpu,或是处理 VIRTIO 的相关的中断请求 | ||
|
||
# 外部中断 | ||
## PLIC | ||
RISC-V 通过 PLIC 实现对外部中断处理,PLIC 不支持虚拟化,不支持 MSI | ||
|
||
<img src="../img/riscv_plic_struct.png" style="zoom: 50%;" /> | ||
|
||
PLIC 架构框图 | ||
|
||
PLIC的中断流程示意图如下 | ||
|
||
<img src="../img/riscv_plic_flow.png" style="zoom:70%;" /> | ||
|
||
|
||
中断源通过中断线向 PLIC 发送一个中断信号,只有当中断的优先级大于阈值的时候,才可以通过阈值寄存器的筛选。 | ||
|
||
之后读取 claim 寄存器得到 pending 的优先级最高的中断,之后清除对应的 pending 位。传给目标hart进行中断处理 | ||
|
||
处理完成后向 complete 寄存器写入中断号,可以接收下一个中断请求 | ||
## 初始化 | ||
初始化的过程与AIA类似 | ||
## 处理过程 | ||
虚拟机中的外部中断触发时,将访问 vPLIC 的地址空间,然而 PLIC 并不支持虚拟化,这个地址空间是未被映射的。因此会触发缺页异常,陷入到 hvisor 中来处理 | ||
|
||
异常触发后,保存陷入上下文,进入到缺页异常处理函数中 | ||
|
||
```rs | ||
pub fn guest_page_fault_handler(current_cpu: &mut ArchCpu) { | ||
... | ||
if addr >= host_plic_base && addr < host_plic_base + PLIC_TOTAL_SIZE { | ||
let mut inst: u32 = read_csr!(CSR_HTINST) as u32; | ||
... | ||
if let Some(inst) = inst { | ||
if addr >= host_plic_base + PLIC_GLOBAL_SIZE { | ||
vplic_hart_emul_handler(current_cpu, addr, inst); | ||
} else { | ||
vplic_global_emul_handler(current_cpu, addr, inst); | ||
} | ||
current_cpu.sepc += ins_size; | ||
} | ||
... | ||
} | ||
} | ||
``` | ||
判断发生缺页异常的地址是否在 PLIC 的地址空间内,之后解析发生异常的指令,根据访问地址和访问指令,修改 PLIC 的地址空间来实现对于 vPLIC 的模拟配置 | ||
```rs | ||
pub fn vplic_hart_emul_handler(current_cpu: &mut ArchCpu, addr: GuestPhysAddr, inst: Instruction) { | ||
... | ||
if offset >= PLIC_GLOBAL_SIZE && offset < PLIC_TOTAL_SIZE { | ||
... | ||
if index == 0 { | ||
// threshold | ||
match inst { | ||
Instruction::Sw(i) => { | ||
// guest write threshold register to plic core | ||
let value = current_cpu.x[i.rs2() as usize] as u32; | ||
host_plic.write().set_threshold(context, value); | ||
} | ||
_ => panic!("Unexpected instruction threshold {:?}", inst), | ||
} | ||
... | ||
} | ||
} | ||
} | ||
``` |