Priv: [wip] implementing privileged architecture
parent
a2e206eaee
commit
483a3f2416
@ -0,0 +1,101 @@
|
||||
from typing import Dict, Union
|
||||
from collections import defaultdict
|
||||
|
||||
MSTATUS_OFFSETS = {
|
||||
'uie': 0,
|
||||
'sie': 1,
|
||||
'mie': 3,
|
||||
'upie': 4,
|
||||
'spie': 5,
|
||||
'mpie': 7,
|
||||
'spp': 8,
|
||||
'mpp': 11,
|
||||
'fs': 13,
|
||||
'xs': 15,
|
||||
'mpriv': 17,
|
||||
'sum': 18,
|
||||
'mxr': 19,
|
||||
'tvm': 20,
|
||||
'tw': 21,
|
||||
'tsr': 22,
|
||||
'sd': 31
|
||||
}
|
||||
"""
|
||||
Offsets for all mstatus bits
|
||||
"""
|
||||
|
||||
MSTATUS_LEN_2 = ('mpp', 'fs', 'xs')
|
||||
"""
|
||||
All mstatus parts that have length 2. All other mstatus parts have length 1
|
||||
"""
|
||||
|
||||
|
||||
class CSR:
|
||||
"""
|
||||
This holds all Control and Status Registers (CSR)
|
||||
"""
|
||||
regs: Dict[int, int]
|
||||
"""
|
||||
All Control and Status Registers are stored here
|
||||
"""
|
||||
|
||||
name_to_addr: Dict[str, int] = {
|
||||
'mstatus': 0x300,
|
||||
'misa': 0x301,
|
||||
'mie': 0x304,
|
||||
'mtvec': 0x305,
|
||||
'mepc': 0x341,
|
||||
'mcause': 0x342,
|
||||
'mtval': 0x343,
|
||||
'mip': 0x344,
|
||||
'mvendorid': 0xF11,
|
||||
'marchid': 0xF12,
|
||||
'mimpid': 0xF13,
|
||||
'mhartid': 0xF14,
|
||||
}
|
||||
"""
|
||||
Translation for named registers
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.regs = defaultdict(lambda: 0)
|
||||
|
||||
def set(self, addr: Union[str, int], val: int):
|
||||
if isinstance(addr, str):
|
||||
if not addr in self.name_to_addr:
|
||||
print("Unknown CSR register {}".format(addr))
|
||||
addr = self.name_to_addr[addr]
|
||||
self.regs[addr] = val
|
||||
|
||||
def get(self, addr: Union[str, int]):
|
||||
if isinstance(addr, str):
|
||||
if not addr in self.name_to_addr:
|
||||
print("Unknown CSR register {}".format(addr))
|
||||
addr = self.name_to_addr[addr]
|
||||
return self.regs[addr]
|
||||
|
||||
# mstatus properties
|
||||
def set_mstatus(self, name: str, val: int):
|
||||
"""
|
||||
Set mstatus bits using this helper. mstatus is a 32 bit register, holding various machine status flags
|
||||
Setting them by hand is super painful, so this helper allows you to set specific bits.
|
||||
|
||||
Please make sure your supplied value has the correct width!
|
||||
|
||||
:param name:
|
||||
:param val:
|
||||
:return:
|
||||
"""
|
||||
size = 2 if name in MSTATUS_LEN_2 else 1
|
||||
off = MSTATUS_OFFSETS[name]
|
||||
mask = (2**size - 1) << off
|
||||
old_val = self.get('mstatus')
|
||||
erased = old_val & (~mask)
|
||||
new_val = erased | (val << off)
|
||||
self.set('mstatus', new_val)
|
||||
|
||||
def get_mstatus(self, name):
|
||||
size = 2 if name in MSTATUS_LEN_2 else 1
|
||||
off = MSTATUS_OFFSETS[name]
|
||||
mask = (2**size - 1) << off
|
||||
return (self.get('mstatus') & mask) >> off
|
@ -0,0 +1,9 @@
|
||||
from ..Executable import Executable, MemorySection, MemoryFlags
|
||||
|
||||
# This requires pyelftools package!
|
||||
|
||||
class ElfExecutable(Executable):
|
||||
def __init__(self, name):
|
||||
from elftools.elf.elffile import ELFFile
|
||||
|
||||
with open(f)
|
@ -0,0 +1,23 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class CpuTrap(BaseException):
|
||||
code: int
|
||||
interrupt: int
|
||||
mtval: int
|
||||
|
||||
def __init__(self, interrupt: int, code: int, mtval=0):
|
||||
assert 0 <= interrupt <= 1
|
||||
|
||||
self.interrupt = interrupt
|
||||
self.code = code
|
||||
self.mtval = mtval
|
||||
|
||||
@property
|
||||
def mcause(self):
|
||||
return (self.code << 31) + self.interrupt
|
||||
|
||||
|
||||
class IllegalInstructionTrap(CpuTrap):
|
||||
def __init__(self):
|
||||
super().__init__(0, 2, 0)
|
@ -0,0 +1,121 @@
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
"""
|
||||
|
||||
from riscemu.CPU import *
|
||||
from enum import IntEnum
|
||||
from riscemu.Executable import LoadedMemorySection
|
||||
from .Exceptions import *
|
||||
from .CSR import CSR
|
||||
from .PrivRV32I import PrivRV32I
|
||||
from ..instructions.RV32M import RV32M
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from typing import Union
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from riscemu import Executable, LoadedExecutable, LoadedInstruction
|
||||
from riscemu.instructions.InstructionSet import InstructionSet
|
||||
|
||||
|
||||
class PrivModes(IntEnum):
|
||||
USER = 0
|
||||
SUPER = 1
|
||||
MACHINE = 3
|
||||
|
||||
|
||||
class PrivCPU(CPU):
|
||||
"""
|
||||
This is a CPU that has different modes, instruction sets and registers.
|
||||
|
||||
It should support M and U Mode, but no U-Mode Traps.
|
||||
|
||||
This allows us to
|
||||
"""
|
||||
|
||||
csr: CSR
|
||||
|
||||
INS_XLEN = 1
|
||||
"""
|
||||
Size of an instruction in memory. Should be 4, but since our loading code is shit, instruction take up
|
||||
the equivalent of "1 byte" (this is actually impossible)
|
||||
"""
|
||||
|
||||
def __init__(self, conf):
|
||||
super().__init__(conf, [PrivRV32I, RV32M])
|
||||
self.mode: PrivModes = PrivModes.MACHINE
|
||||
|
||||
# set up CSR
|
||||
self.csr = CSR()
|
||||
# TODO: Actually populate the CSR with real data (vendorID, heartID, machine implementation etc)
|
||||
self.csr.set('mhartid', 0) # core id
|
||||
self.csr.set('mimpid', 1) # implementation id
|
||||
self.csr.set('misa', ()) # available ISA
|
||||
|
||||
def _run(self, verbose=False):
|
||||
if self.pc <= 0:
|
||||
return False
|
||||
ins = None
|
||||
try:
|
||||
while not self.exit:
|
||||
try:
|
||||
self.cycle += 1
|
||||
ins = self.mmu.read_ins(self.pc)
|
||||
if verbose:
|
||||
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
||||
self.pc += 1
|
||||
self.run_instruction(ins)
|
||||
except CpuTrap as trap:
|
||||
mie = self.csr.get_mstatus('mie')
|
||||
if not mie:
|
||||
print("Caught trap while mie={}!".format(mie))
|
||||
# TODO: handle this a lot better
|
||||
continue
|
||||
# raise trap
|
||||
|
||||
# caught a trap!
|
||||
self.csr.set('mepc', self.pc) # store MEPC
|
||||
self.csr.set_mstatus('mpp', self.mode) # save mpp
|
||||
self.csr.set_mstatus('mpie', mie) # save mie
|
||||
self.csr.set_mstatus('mie', 0) # disable further interrupts
|
||||
self.csr.set('mcause', trap.mcause) # store cause
|
||||
|
||||
# set mtval csr
|
||||
self.csr.set('mtval', trap.mtval)
|
||||
|
||||
# set priv mode to machine
|
||||
self.mode = PrivModes.MACHINE
|
||||
|
||||
# trap vector
|
||||
mtvec = self.csr.get('mtvec')
|
||||
if mtvec & 3 == 1:
|
||||
# vectored mode!
|
||||
self.pc = (mtvec >> 2) + (self.INS_XLEN * trap.code)
|
||||
else:
|
||||
# standard mode
|
||||
self.pc = (mtvec >> 2)
|
||||
|
||||
|
||||
except RiscemuBaseException as ex:
|
||||
if not isinstance(ex, LaunchDebuggerException):
|
||||
print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE)
|
||||
print(ex.message())
|
||||
self.pc -= 1
|
||||
|
||||
if self.active_debug:
|
||||
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
|
||||
return
|
||||
if self.conf.debug_on_exception:
|
||||
launch_debug_session(self, self.mmu, self.regs,
|
||||
"Exception encountered, launching debug:")
|
||||
|
||||
if self.exit:
|
||||
print()
|
||||
print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
||||
sys.exit(self.exit_code)
|
||||
else:
|
||||
print()
|
||||
print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE)
|
@ -0,0 +1,79 @@
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
"""
|
||||
|
||||
from riscemu.instructions.RV32I import *
|
||||
from ..Exceptions import INS_NOT_IMPLEMENTED
|
||||
from riscemu.priv.PrivCPU import PrivModes, PrivCPU
|
||||
from .Exceptions import *
|
||||
|
||||
|
||||
class PrivRV32I(RV32I):
|
||||
cpu: PrivCPU
|
||||
"""
|
||||
This is an extension of RV32I, written for the PrivCPU class
|
||||
"""
|
||||
|
||||
def instruction_csrrw(self, ins: 'LoadedInstruction'):
|
||||
rd, off, rs = self.parse_crs_ins(ins)
|
||||
if rd != 'zero':
|
||||
old_val = int_from_bytes(self.cpu.csr.read(off, 4))
|
||||
self.regs.set(rd, old_val)
|
||||
self.cpu.csr.write(off, 4, int_to_bytes(rs))
|
||||
|
||||
def instruction_csrrs(self, ins: 'LoadedInstruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_csrrc(self, ins: 'LoadedInstruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_csrrsi(self, ins: 'LoadedInstruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_csrrwi(self, ins: 'LoadedInstruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_csrrci(self, ins: 'LoadedInstruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_mret(self, ins: 'LoadedInstruction'):
|
||||
if self.cpu.mode != PrivModes.MACHINE:
|
||||
print("MRET not inside machine level code!")
|
||||
raise IllegalInstructionTrap()
|
||||
# retore mie
|
||||
mpie = self.cpu.csr.get_mstatus('mpie')
|
||||
self.cpu.csr.set_mstatus('mie', mpie)
|
||||
# restore priv
|
||||
mpp = self.cpu.csr.get_mstatus('mpp')
|
||||
self.cpu.mode = PrivModes(mpp)
|
||||
# restore pc
|
||||
mepc = self.cpu.csr.get('mepc')
|
||||
self.cpu.pc = mepc
|
||||
|
||||
def instruction_uret(self, ins: 'LoadedInstruction'):
|
||||
raise IllegalInstructionTrap()
|
||||
|
||||
def instruction_sret(self, ins: 'LoadedInstruction'):
|
||||
raise IllegalInstructionTrap()
|
||||
|
||||
def instruction_scall(self, ins: 'LoadedInstruction'):
|
||||
"""
|
||||
Overwrite the scall from userspace RV32I
|
||||
"""
|
||||
if self.cpu.mode == PrivModes.USER:
|
||||
raise CpuTrap(0, 8) # ecall from U mode
|
||||
elif self.cpu.mode == PrivModes.SUPER:
|
||||
raise CpuTrap(0, 9) # ecall from S mode - should not happen
|
||||
elif self.cpu.mode == PrivModes.MACHINE:
|
||||
raise CpuTrap(0, 11) # ecall from M mode
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def parse_crs_ins(self, ins: 'LoadedInstruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
return ins.get_reg(0), ins.get_imm(1), self.get_reg_content(ins, 2)
|
@ -0,0 +1,15 @@
|
||||
"""
|
||||
RiscEmu (c) 2021 Anton Lydike
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
The priv Module holds everything necessary for emulating privileged risc-v assembly
|
||||
|
||||
|
||||
Running priv is only preferable to the normal userspace emulator, if you actually want to emulate the whole system.
|
||||
|
||||
Syscalls will have to be intercepted by your assembly code.
|
||||
|
||||
|
||||
The PrivCPU Implements the Risc-V M/U Model, meaning there is machine mode and user mode. No PMP or paging is available.
|
||||
"""
|
@ -0,0 +1,27 @@
|
||||
from .PrivCPU import *
|
||||
|
||||
from ..Tokenizer import RiscVInput
|
||||
from ..ExecutableParser import ExecutableParser
|
||||
|
||||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
files = sys.argv
|
||||
|
||||
cpu = PrivCPU(RunConfig())
|
||||
|
||||
try:
|
||||
loaded_exe = None
|
||||
for file in files:
|
||||
tk = cpu.get_tokenizer(RiscVInput.from_file(file))
|
||||
tk.tokenize()
|
||||
loaded_exe = cpu.load(ExecutableParser(tk).parse())
|
||||
# run the last loaded executable
|
||||
cpu.run_loaded(loaded_exe)
|
||||
except RiscemuBaseException as e:
|
||||
print("Error while parsing: {}".format(e.message()))
|
||||
import traceback
|
||||
|
||||
traceback.print_exception(type(e), e, e.__traceback__)
|
||||
sys.exit(1)
|
Loading…
Reference in New Issue