Priv: [wip] implementing privileged architecture

kernel-mode
Anton Lydike 4 years ago
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…
Cancel
Save