forked from BSB-WS23/mpstubs
Handout for Assignment 1: In- & Output (text mode, keyboard)
This commit is contained in:
parent
5bfe56aafd
commit
3e8f31ac80
|
@ -0,0 +1,60 @@
|
|||
# Kernel Makefile
|
||||
# try `make help` for more information
|
||||
|
||||
# Default target
|
||||
.DEFAULT_GOAL = all
|
||||
|
||||
# Path to the files for the initial ramdisk (for Assignment 7)
|
||||
INITRD_DIR ?= initrd/
|
||||
INITRD_TOOL ?= fs/tool/fstool
|
||||
INITRD_DEP =
|
||||
# 1MB free space
|
||||
INITRD_FREE ?= 1048576
|
||||
|
||||
# Kernel source files
|
||||
LINKER_SCRIPT = compiler/sections.ld
|
||||
CRTI_SOURCE = compiler/crti.asm
|
||||
CRTN_SOURCE = compiler/crtn.asm
|
||||
CC_SOURCES = $(shell find * -name "*.cc" -a ! -name '.*' -a ! -path 'test*' -a ! -path 'fs/tool/*')
|
||||
ASM_SOURCES = $(shell find * -name "*.asm" -a ! -name '.*')
|
||||
|
||||
# Target files
|
||||
KERNEL = $(BUILDDIR)/system
|
||||
KERNEL64 = $(KERNEL)64
|
||||
ISOFILE = $(BUILDDIR)/stubs.iso
|
||||
|
||||
# Include global variables and standard recipes
|
||||
include tools/common.mk
|
||||
|
||||
# Initial Ramdisk
|
||||
ifneq ($(wildcard $(INITRD_DIR)*),)
|
||||
INITRD = $(BUILDDIR)/initrd.img
|
||||
INITRD_DEP += $(shell find $(INITRD_DIR) -type f )
|
||||
# Additional dependency for kernel
|
||||
$(KERNEL): $(INITRD)
|
||||
endif
|
||||
|
||||
all: $(KERNEL)
|
||||
|
||||
# Linking the system image
|
||||
# We use the C++ compiler (which calls the actual linker)
|
||||
$(KERNEL64): $(CRTI_OBJECT) $(CRTN_OBJECT) $(ASM_OBJECTS) $(CC_OBJECTS) $(LINKER_SCRIPT) $(MAKEFILE_LIST)
|
||||
@echo "LD $@"
|
||||
@mkdir -p $(@D)
|
||||
$(VERBOSE) $(CXX) $(CXXFLAGS) -Wl,-T $(LINKER_SCRIPT) -o $@ $(CRTI_OBJECT) $(CRTBEGIN_OBJECT) $(ASM_OBJECTS) $(CC_OBJECTS) $(LDFLAGS) $(LIBGCC) $(CRTEND_OBJECT) $(CRTN_OBJECT)
|
||||
|
||||
# The kernel must be a 32bit elf for multiboot compliance
|
||||
$(KERNEL): $(KERNEL64)
|
||||
$(VERBOSE) $(OBJCOPY) -I elf64-x86-64 -O elf32-i386 $< $@
|
||||
|
||||
# Tool for editing a Minix v3 file system image (Assignment 7)
|
||||
$(INITRD_TOOL): $(shell test -d $(dir $(INITRD_TOOL)) && find $(dir $(INITRD_TOOL)) -name "*.cc" -or -name '*.h')
|
||||
@echo "Make $@"
|
||||
$(VERBOSE) MAKEFLAGS="" $(MAKE) -C $(dir $(INITRD_TOOL))
|
||||
|
||||
# Initial Ramdisk with Minix v3 file system
|
||||
$(INITRD): $(INITRD_TOOL) $(INITRD_DEP)
|
||||
@echo "INITRD $@"
|
||||
$(VERBOSE) dd if=/dev/zero of=$@ bs=$(shell du -s $(INITRD_DIR) | cut -f1 | xargs expr $(INITRD_FREE) + ) count=1
|
||||
$(VERBOSE) /sbin/mkfs.minix -3 $@ # optional --inodes <number>
|
||||
$(VERBOSE) ./$(INITRD_TOOL) put "$(INITRD_DIR)" $@
|
|
@ -0,0 +1,245 @@
|
|||
; The stony path to Long Mode (64-bit)...
|
||||
; ... begins in 32-bit Protected Mode
|
||||
[BITS 32]
|
||||
|
||||
; Pointer to Long Mode Global Descriptor Table (GDT, machine/gdt.cc)
|
||||
[EXTERN gdt_long_mode_pointer]
|
||||
|
||||
[GLOBAL long_mode]
|
||||
long_mode:
|
||||
|
||||
; You can check if the CPU supports Long Mode by using the `cpuid` command.
|
||||
; Problem: You first have to figure out if the `cpuid` command itself is
|
||||
; supported. Therefore, you have to try to reverse the 21st bit in the EFLAGS
|
||||
; register -- if it works, then there is the 'cpuid' instruction.
|
||||
CPUID_BIT_MASK equ 1 << 21
|
||||
|
||||
check_cpuid:
|
||||
; Save EFLAGS on stack
|
||||
pushfd
|
||||
|
||||
; Copy stored EFLAGS from stack to EAX register
|
||||
mov eax, [esp]
|
||||
|
||||
; Flip the 21st bit (ID) in EAX
|
||||
xor eax, CPUID_BIT_MASK
|
||||
|
||||
; Copy EAX to EFLAGS (using the stack)
|
||||
push eax
|
||||
popfd
|
||||
|
||||
; And reverse: copy EFLAGS to EAX (using the stack)
|
||||
; (but the 21st bit should now still be flipped, if `cpuid` is supported)
|
||||
pushfd
|
||||
pop eax
|
||||
|
||||
; Compare the new EFLAGS copy (residing in EAX) with the EFLAGS stored at
|
||||
; the beginning of this function by using an exclusive OR -- all different
|
||||
; (flipped) bits will be stored in EAX.
|
||||
xor eax, [esp]
|
||||
|
||||
; Restore original EFLAGS
|
||||
popfd
|
||||
|
||||
; If 21st Bit in EAX is set, `cpuid` is supported -- continue at check_long_mode
|
||||
and eax, CPUID_BIT_MASK
|
||||
jnz check_long_mode
|
||||
|
||||
; Show error message "No CPUID" and stop CPU
|
||||
mov dword [0xb8000], 0xcf6fcf4e
|
||||
mov dword [0xb8004], 0xcf43cf20
|
||||
mov dword [0xb8008], 0xcf55cf50
|
||||
mov dword [0xb800c], 0xcf44cf49
|
||||
hlt
|
||||
|
||||
; Now you are able to use the `cpuid` instruction to check if Long Mode is
|
||||
; available -- after you've checked if the `cpuid` is able to perform the
|
||||
; check itself (since it is an extended `cpuid` function)...
|
||||
|
||||
CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER equ 0x80000000
|
||||
CPUID_GET_EXTENDED_PROCESSOR_FEATURES equ 0x80000001
|
||||
CPUID_HAS_LONGMODE equ 1 << 29
|
||||
|
||||
check_long_mode:
|
||||
; Set argument for `cpuid` to check the availability of extended functions
|
||||
; and call cpuid
|
||||
mov eax, CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER
|
||||
cpuid
|
||||
; The return value contains the maximum function number supported by `cpuid`,
|
||||
; You'll need the function number for extended processor features
|
||||
cmp eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
||||
; If not present, the CPU is definitely too old to support long mode
|
||||
jb no_long_mode
|
||||
|
||||
; Finally, you are able to check the Long Mode support itself
|
||||
mov eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
||||
cpuid
|
||||
; If the return value in the EDX register has set the 29th bit,
|
||||
; then long mode is supported -- continue with setup_paging
|
||||
test edx, CPUID_HAS_LONGMODE
|
||||
jnz setup_paging
|
||||
|
||||
no_long_mode:
|
||||
; Show error message "No 64bit" and stop CPU
|
||||
mov dword [0xb8000], 0xcf6fcf4e
|
||||
mov dword [0xb8004], 0xcf36cf20
|
||||
mov dword [0xb8008], 0xcf62cf34
|
||||
mov dword [0xb800c], 0xcf74cf69
|
||||
hlt
|
||||
|
||||
; Paging is required for Long Mode.
|
||||
; Since an extensive page manager might be a bit of an overkill to start with,
|
||||
; the following code creates an identity mapping for the first four gigabytes
|
||||
; (using huge pages): each virtual address will point to the same physical one.
|
||||
; This area (up to 4 GiB) is important for some memory mapped devices (APIC)
|
||||
; and you don't want to remap them yet for simplicity reasons.
|
||||
; In the advanced operating systems lecture, this topic is covered in detail,
|
||||
; however, if you want a quick overview, have a look at
|
||||
; https://wiki.osdev.org/Page_Tables#2_MiB_pages_2
|
||||
|
||||
PAGE_SIZE equ 4096
|
||||
PAGE_FLAGS_PRESENT equ 1 << 0
|
||||
PAGE_FLAGS_WRITEABLE equ 1 << 1
|
||||
PAGE_FLAGS_USER equ 1 << 2
|
||||
PAGE_FLAGS_HUGE equ 1 << 7
|
||||
|
||||
setup_paging:
|
||||
; Unlike in Protected Mode, an entry in the page table has a size of 8 bytes
|
||||
; (vs 4 bytes), so there are only 512 (and not 1024) entries per table.
|
||||
; Structure of the 3-level PAE paging: One entry in the
|
||||
; - lv2: Page-Directory-Table (PDT) covers 2 MiB (1 Huge Page)
|
||||
; - lv3: Page-Directory-Pointer-Table (PDPT) covers 1 GiB (512 * 2 MiB)
|
||||
; - lv4: Page-Map-Level-4-Table (PML4) covers 512 GiB (512 * 1 GiB)
|
||||
|
||||
; To address 4 GiB only four level-2 tables are required.
|
||||
; All entries of the level-2 tables should be marked as writeable (attributes)
|
||||
; and map (point to) the corresponding physical memory.
|
||||
|
||||
; This is done in a loop using ECX as counter
|
||||
mov ecx, 0
|
||||
|
||||
.identitymap_level2:
|
||||
; Calculate physical address in EAX (2 MiB multiplied by the counter)
|
||||
mov eax, 0x200000
|
||||
mul ecx
|
||||
; Configure page attributes
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_HUGE | PAGE_FLAGS_USER
|
||||
; Write (8 byte) entry in the level-2 table
|
||||
mov [paging_level2_tables + ecx * 8], eax
|
||||
|
||||
; Increment counter...
|
||||
inc ecx
|
||||
; ... until all four level-2 tables are filled
|
||||
cmp ecx, 512 * 4
|
||||
jne .identitymap_level2
|
||||
|
||||
; The first four entries of the level-3 table should point to the
|
||||
; four level-2 tables (and be writeable as well).
|
||||
; Again, ECX acts as counter for the loop
|
||||
mov ecx, 0
|
||||
|
||||
.identitymap_level3:
|
||||
; Calculate the address: ECX * PAGE_SIZE + paging_level2_tables
|
||||
mov eax, ecx
|
||||
; The size of a page is stored in the EDX register
|
||||
mov edx, PAGE_SIZE
|
||||
mul edx
|
||||
add eax, paging_level2_tables
|
||||
; Configure attributes
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
; Write (8 byte) entry in the level-3 table
|
||||
mov [paging_level3_table + ecx * 8], eax
|
||||
|
||||
; Increment counter...
|
||||
inc ecx
|
||||
; ... until all four entries of the table are written
|
||||
cmp ecx, 4
|
||||
jne .identitymap_level3
|
||||
|
||||
mov eax, paging_level2_tables
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
mov [paging_level3_table], eax
|
||||
|
||||
; The first entry of the level-4 table should point to to the level-3 table
|
||||
mov eax, paging_level3_table
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
mov [paging_level4_table], eax
|
||||
|
||||
; Time to activate paging
|
||||
paging_enable:
|
||||
; First setup the control registers
|
||||
|
||||
; Write the address of the level-4 table into the CR3 register
|
||||
mov eax, paging_level4_table
|
||||
mov cr3, eax
|
||||
|
||||
; Activate Physical Address Extension (PAE)
|
||||
; by setting the 5th bits in the CR4 register
|
||||
mov eax, cr4
|
||||
or eax, 1 << 5
|
||||
mov cr4, eax
|
||||
|
||||
; Set the Long Mode Enable Bit in the EFER MSR
|
||||
; (Extended Feature Enable Register Model Specific Register)
|
||||
mov ecx, 0xC0000080
|
||||
rdmsr
|
||||
or eax, 1 << 8
|
||||
wrmsr
|
||||
|
||||
; Finally, the 31st bit in CR0 is set to enable Paging
|
||||
mov eax, cr0
|
||||
or eax, 1 << 31
|
||||
mov cr0, eax
|
||||
|
||||
; Load Long Mode Global Descriptor Table
|
||||
lgdt [gdt_long_mode_pointer]
|
||||
|
||||
; Far jump to the 64-bit start code
|
||||
jmp 0x8:long_mode_start
|
||||
|
||||
; print `KO` to screen
|
||||
mov dword [0xb8000], 0x3f4f3f4b
|
||||
hlt
|
||||
|
||||
; Memory reserved for page tables
|
||||
[SECTION .bss]
|
||||
|
||||
ALIGN 4096
|
||||
|
||||
[GLOBAL paging_level4_table]
|
||||
[GLOBAL paging_level3_table]
|
||||
[GLOBAL paging_level2_tables]
|
||||
; 1x Level-4 Table (Page Map Level 4)
|
||||
paging_level4_table:
|
||||
resb PAGE_SIZE
|
||||
|
||||
; 1x Level-3 Table (Page Directory Pointer Table)
|
||||
paging_level3_table:
|
||||
resb PAGE_SIZE
|
||||
|
||||
; 4x Level-2 Table (Page Directory)
|
||||
paging_level2_tables:
|
||||
resb PAGE_SIZE * 4
|
||||
|
||||
[SECTION .text]
|
||||
[EXTERN kernel_init] ; C++ entry function
|
||||
|
||||
; Continue with 64 bit code
|
||||
[BITS 64]
|
||||
|
||||
long_mode_start:
|
||||
; Set data segment registers to SEGMENT_KERNEL_DATA (machine/gdt.h)
|
||||
mov ax, 0x10
|
||||
mov ss, ax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
; Call high-level (C++) kernel initialization function
|
||||
call kernel_init
|
||||
|
||||
; Print `STOP` to screen and stop
|
||||
mov rax, 0x2f502f4f2f544f53
|
||||
mov qword [0xb8000], rax
|
||||
hlt
|
|
@ -0,0 +1,22 @@
|
|||
; Magic Header, has to be present in Kernel to indicate Multiboot compliance
|
||||
MULTIBOOT_HEADER_MAGIC_OS equ 0x1badb002
|
||||
|
||||
; Answer by the boot loader for Multiboot compliance, written in eax register
|
||||
MULTIBOOT_HEADER_MAGIC_LOADER equ 0x2badb002
|
||||
|
||||
; Flags instructing the Multiboot compliant boot loader to setup the system
|
||||
; according to your needs
|
||||
MULTIBOOT_PAGE_ALIGN equ 1<<0 ; Align boot modules (initrds) at 4 KiB border
|
||||
MULTIBOOT_MEMORY_INFO equ 1<<1 ; Request Memory Map information
|
||||
MULTIBOOT_VIDEO_MODE equ 1<<2 ; Configure video mode
|
||||
|
||||
MULTIBOOT_HEADER_FLAGS equ 0
|
||||
|
||||
; Desired video mode (only considered if MULTIBOOT_VIDEO_MODE set)
|
||||
; (boot loader will choose the best fitting mode, which might differ from the settings below)
|
||||
MULTIBOOT_VIDEO_WIDTH equ 1280 ; Desired width
|
||||
MULTIBOOT_VIDEO_HEIGHT equ 1024 ; Desired height
|
||||
MULTIBOOT_VIDEO_BITDEPTH equ 32 ; Desired bit depth
|
||||
|
||||
; Checksum
|
||||
MULTIBOOT_HEADER_CHKSUM equ -(MULTIBOOT_HEADER_MAGIC_OS + MULTIBOOT_HEADER_FLAGS)
|
|
@ -0,0 +1,165 @@
|
|||
#include "boot/multiboot/data.h"
|
||||
|
||||
/*! \brief Multiboot Information Structure according to Specification
|
||||
* \see [Multiboot Specification]{#multiboot}
|
||||
*/
|
||||
struct multiboot_info {
|
||||
/*! \brief Helper Structure
|
||||
*/
|
||||
struct Array {
|
||||
uint32_t size; ///< Length
|
||||
uint32_t addr; ///< Begin (physical address)
|
||||
} __attribute__((packed));
|
||||
|
||||
enum Flag : uint32_t {
|
||||
Memory = 1U << 0, ///< is there basic lower/upper memory information?
|
||||
BootDev = 1U << 1, ///< is there a boot device set?
|
||||
CmdLine = 1U << 2, ///< is the command-line defined?
|
||||
Modules = 1U << 3, ///< are there modules to do something with?
|
||||
/* These next two are mutually exclusive */
|
||||
SymbolTable = 1U << 4, ///< is there an a.out symbol table loaded?
|
||||
SectionHeader = 1U << 5, ///< is there an ELF section header table?
|
||||
|
||||
MemoryMap = 1U << 6, ///< is there a full memory map?
|
||||
DriveInfo = 1U << 7, ///< Is there drive info?
|
||||
ConfigTable = 1U << 8, ///< Is there a config table?
|
||||
BootLoaderName = 1U << 9, ///< Is there a boot loader name?
|
||||
ApmTable = 1U << 10, ///< Is there a APM table?
|
||||
|
||||
// Is there video information?
|
||||
VbeInfo = 1U << 11, ///< Vesa bios extension
|
||||
FramebufferInfo = 1U << 12 ///< Framebuffer
|
||||
} flags;
|
||||
|
||||
/*! \brief Available memory retrieved from BIOS
|
||||
*/
|
||||
struct {
|
||||
uint32_t lower; ///< Amount of memory below 1 MiB in kilobytes
|
||||
uint32_t upper; ///< Amount of memory above 1 MiB in kilobytes
|
||||
} mem __attribute__((packed));
|
||||
uint32_t boot_device; ///< "root" partition
|
||||
uint32_t cmdline; ///< Kernel command line
|
||||
Array mods; ///< List of boot modules
|
||||
union {
|
||||
/*! \brief Symbol table for kernel in a.out format
|
||||
*/
|
||||
struct {
|
||||
uint32_t tabsize;
|
||||
uint32_t strsize;
|
||||
uint32_t addr;
|
||||
uint32_t reserved;
|
||||
} aout_symbol_table __attribute__((packed));
|
||||
|
||||
/*! \brief Section header table for kernel in ELF
|
||||
*/
|
||||
struct {
|
||||
uint32_t num; ///< Number of entries
|
||||
uint32_t size; ///< Size per entry
|
||||
uint32_t addr; ///< Start of the header table
|
||||
uint32_t shndx; ///< String table index
|
||||
} elf_section_header_table __attribute__((packed));
|
||||
};
|
||||
|
||||
struct Array mmap; ///< Memory Map
|
||||
struct Array drives; ///< Drive Information
|
||||
uint32_t config_table; ///< ROM configuration table
|
||||
uint32_t boot_loader_name; ///< Boot Loader Name
|
||||
uint32_t apm_table; ///< APM table
|
||||
|
||||
struct Multiboot::VBE vbe; ///< VBE Information
|
||||
struct Multiboot::Framebuffer framebuffer; ///< Framebuffer information
|
||||
|
||||
/*! \brief Check if setting is available
|
||||
* \param flag Flag to check
|
||||
* \return `true` if available
|
||||
*/
|
||||
bool has(enum Flag flag) const {
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
assert_size(multiboot_info, 116);
|
||||
|
||||
/*! \brief The pointer to the multiboot structures will be assigned in the assembler startup code (boot/startup.asm)
|
||||
*/
|
||||
struct multiboot_info *multiboot_addr = 0;
|
||||
|
||||
namespace Multiboot {
|
||||
Module * getModule(unsigned i) {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::Modules) &&
|
||||
i < multiboot_addr->mods.size) {
|
||||
return i + reinterpret_cast<Module*>(static_cast<uintptr_t>(multiboot_addr->mods.addr));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned getModuleCount() {
|
||||
return multiboot_addr->mods.size;
|
||||
}
|
||||
|
||||
void * Memory::getStartAddress() const {
|
||||
if (sizeof(void*) == 4 && (addr >> 32) != 0) {
|
||||
return reinterpret_cast<void*>(addr & 0xffffffff);
|
||||
} else {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(addr));
|
||||
}
|
||||
}
|
||||
|
||||
void * Memory::getEndAddress() const {
|
||||
uint64_t end = addr + len;
|
||||
if (sizeof(void*) == 4 && (end >> 32) != 0) {
|
||||
return reinterpret_cast<void*>(end & 0xffffffff);
|
||||
} else {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(end));
|
||||
}
|
||||
}
|
||||
|
||||
bool Memory::isAvailable() const {
|
||||
return type == AVAILABLE;
|
||||
}
|
||||
|
||||
Memory * Memory::getNext() const {
|
||||
if (multiboot_addr != nullptr && multiboot_addr->has(multiboot_info::Flag::MemoryMap)) {
|
||||
uintptr_t next = reinterpret_cast<uintptr_t>(this) + size + sizeof(size);
|
||||
if (next < multiboot_addr->mmap.addr + multiboot_addr->mmap.size) {
|
||||
return reinterpret_cast<Memory *>(next);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory * getMemoryMap() {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::MemoryMap) &&
|
||||
multiboot_addr->mmap.size > 0) {
|
||||
return reinterpret_cast<Memory *>(static_cast<uintptr_t>(multiboot_addr->mmap.addr));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
char * getCommandLine() {
|
||||
return reinterpret_cast<char*>(static_cast<uintptr_t>(multiboot_addr->cmdline));
|
||||
}
|
||||
|
||||
char * getBootLoader() {
|
||||
return reinterpret_cast<char*>(static_cast<uintptr_t>(multiboot_addr->boot_loader_name));
|
||||
}
|
||||
|
||||
VBE * getVesaBiosExtensionInfo() {
|
||||
if (multiboot_addr != nullptr && multiboot_addr->has(multiboot_info::Flag::VbeInfo)) {
|
||||
return &(multiboot_addr->vbe);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Framebuffer * getFramebufferInfo() {
|
||||
if (multiboot_addr != nullptr && multiboot_addr->has(multiboot_info::Flag::FramebufferInfo)) {
|
||||
return &(multiboot_addr->framebuffer);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace Multiboot
|
|
@ -0,0 +1,232 @@
|
|||
/*! \file
|
||||
* \brief \ref Multiboot Interface
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "compiler/fix.h"
|
||||
#include "debug/assert.h"
|
||||
|
||||
/*! \brief Interface for Multiboot
|
||||
*
|
||||
* Due to historical reasons, a normal BIOS allows you to do quite an egg dance
|
||||
* until you finally reach the actual kernel (especially with only 512 bytes
|
||||
* available in the master boot record...).
|
||||
* Fortunately, there are [boot loaders](https://wiki.osdev.org/Bootloader) that
|
||||
* (partly) do this ungrateful job for you:
|
||||
* They load your kernel into memory, switch (the bootstrap processor) to
|
||||
* protected mode (32 bit) and jump to the entry point of our kernel -- saving
|
||||
* you a lot of boring (or enlightening?) work: reading ancient systems documentation.
|
||||
* One of the most famous representatives is the
|
||||
* [Grand Unified Bootloader (GRUB)](https://www.gnu.org/software/grub/), which
|
||||
* is also the reference implementation of the \ref multiboot "Multiboot Specification".
|
||||
*
|
||||
* A Multiboot compliant boot loader will prepare the system according to your
|
||||
* needs and can hand you a lot of useful information (e.g. references to
|
||||
* initial ramdisks).
|
||||
*
|
||||
* However, you have to inform the loader that you are also compliant to the
|
||||
* specification, and (if required) instruct the loader to adjust specific
|
||||
* settings (e.g. the graphics mode).
|
||||
*
|
||||
* For this purpose you have to configure the beginning of the kernel (the first
|
||||
* 8192 bytes of the kernel binary) accordingly (see `compiler/section.ld`) --
|
||||
* this is were the boot loader will search for a magic header and parse the
|
||||
* subsequent entries containing the desired system configuration.
|
||||
* In StuBS these flags are set in `boot/multiboot/config.inc` and the header
|
||||
* structure is generated in `boot/multiboot/header.asm`.
|
||||
*
|
||||
* The first step in your \ref startup_bsp() "kernel entry function" is saving
|
||||
* the pointer to the struct with the information from the boot loader
|
||||
* (transferred via register `ebx`) -- and \ref Multiboot provides you the
|
||||
* interface to comfortably access its contents!
|
||||
*/
|
||||
namespace Multiboot {
|
||||
/*! \brief Boot Module
|
||||
* (also known as `initrd` = initial Ramdisk)
|
||||
*
|
||||
* \see \ref multiboot-boot-modules "1.7 Boot modules"
|
||||
* \see \ref multiboot-boot-info "3.3 Boot information format"
|
||||
*/
|
||||
class Module {
|
||||
uint32_t start; ///< Start address
|
||||
uint32_t end; ///< End address (excluded)
|
||||
uint32_t cmdline; ///< commandline parameter
|
||||
uint32_t pad UNUSED_STRUCT_FIELD; ///< alignment; must be 0
|
||||
|
||||
public:
|
||||
/*! \brief Get start of this boot module
|
||||
* \return Pointer to begin of modules physical address
|
||||
*/
|
||||
void * getStartAddress() const {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(start));
|
||||
}
|
||||
|
||||
/*! \brief Get end of this boot module
|
||||
* \return Pointer beyond the modules physical address
|
||||
*/
|
||||
void * getEndAddress() const {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(end));
|
||||
}
|
||||
|
||||
/*! \brief Get the size of this boot module
|
||||
* \return Module size in bytes (difference of end and start address)
|
||||
*/
|
||||
size_t getSize() const {
|
||||
return static_cast<size_t>(end-start);
|
||||
}
|
||||
|
||||
/*! \brief Get the command line for this module
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char * getCommandLine() const {
|
||||
return reinterpret_cast<char*>(static_cast<uintptr_t>(cmdline));
|
||||
}
|
||||
} __attribute__((packed));
|
||||
assert_size(Module, 16);
|
||||
|
||||
/*! \brief Retrieve a certain boot module
|
||||
* \param i boot module number
|
||||
* \return Pointer to structure with boot module information
|
||||
*/
|
||||
Module * getModule(unsigned i);
|
||||
|
||||
/*! \brief Get the number of modules
|
||||
* \return Pointer to structure with boot module information
|
||||
*/
|
||||
unsigned getModuleCount();
|
||||
|
||||
/*! \brief Get the kernel command line
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char * getCommandLine();
|
||||
|
||||
/*! \brief Get the name of the boot loader
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char * getBootLoader();
|
||||
|
||||
/*! \brief Memory Map
|
||||
*
|
||||
* The boot loader queries the BIOS for a memory map and stores its result in
|
||||
* (something like) a linked list. However, this list may not be complete,
|
||||
* can have contradictory entries and does not take the location of your kernel
|
||||
* or any boot modules into account.
|
||||
* (Anyways, it is still the best memory map you will have in StuBS...)
|
||||
*
|
||||
* \note Needs to be enabled explicitly by setting the `MULTIBOOT_MEMORY_INFO` flag
|
||||
* in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*
|
||||
* \see [Detecting Memory](https://wiki.osdev.org/Detecting_Memory_(x86))
|
||||
*/
|
||||
class Memory {
|
||||
uint32_t size; ///< Size of this entry (can exceed size of the class, rest will be padding bits)
|
||||
uint64_t addr; ///< Begin of memory area
|
||||
uint64_t len; ///< length of the memory area
|
||||
|
||||
/*! \brief Usage Type
|
||||
*/
|
||||
enum Type : uint32_t {
|
||||
AVAILABLE = 1, ///< Memory is available and usable in kernel
|
||||
RESERVED = 2, ///< Memory is reserved (without further explanation)
|
||||
ACPI = 3, ///< Memory may be reclaimed by ACPI
|
||||
NVS = 4, ///< Memory is non volatile storage for ACPI
|
||||
BADRAM = 5 ///< Area contains bad memory
|
||||
} type;
|
||||
|
||||
public:
|
||||
/*! \brief Get start of this memory area
|
||||
* \return Pointer to begin of the physical address of the memory area
|
||||
*/
|
||||
void * getStartAddress() const;
|
||||
|
||||
/*! \brief Get end of this memory area
|
||||
* \return Pointer beyond the physical address of this memory area
|
||||
*/
|
||||
void * getEndAddress() const;
|
||||
|
||||
/*! \brief Is the memory marked as usable
|
||||
* \return `true` if available, `false` if not usable.
|
||||
*/
|
||||
bool isAvailable() const;
|
||||
|
||||
/*! \brief Get the next memory area
|
||||
* \return pointer to the next memory area entry or `nullptr` if last area
|
||||
*/
|
||||
Memory * getNext() const;
|
||||
} __attribute__((packed));
|
||||
assert_size(Memory, 24);
|
||||
|
||||
/*! \brief Retrieve the first entry of the memory map
|
||||
*/
|
||||
Memory * getMemoryMap();
|
||||
|
||||
/*! \brief Video mode: Vesa BIOS Extension
|
||||
*
|
||||
* \see [VESA BIOS Extension (VBE) Core Functions (Version 3)](vbe3.pdf)
|
||||
*/
|
||||
struct VBE {
|
||||
uint32_t control_info; ///< Pointer to VBE control information
|
||||
uint32_t mode_info; ///< Pointer to VBE mode information
|
||||
uint16_t mode; ///< Selected video mode (as defined in the standard)
|
||||
uint16_t interface_seg; ///< Protected mode interface (unused)
|
||||
uint16_t interface_off; ///< Protected mode interface (unused)
|
||||
uint16_t interface_len; ///< Protected mode interface (unused)
|
||||
} __attribute__((packed));
|
||||
assert_size(VBE, 16);
|
||||
|
||||
/*! \brief Get pointer to Vesa BIOS Extension information
|
||||
*
|
||||
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
|
||||
* in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*/
|
||||
VBE * getVesaBiosExtensionInfo();
|
||||
|
||||
/*! \brief Video mode: Framebuffer
|
||||
*
|
||||
* This beautiful structure contains everything required for using the graphic
|
||||
* framebuffer in a very handy manner -- however, it may not be well supported
|
||||
* by current boot loaders...
|
||||
* These information can be retrieved from \ref VBE as well, though you then
|
||||
* have to parse these huge structures containing a lot of useless stuff.
|
||||
*/
|
||||
struct Framebuffer {
|
||||
uint64_t address; ///< Physical address of the framebuffer
|
||||
uint32_t pitch; ///< Number of bytes per row
|
||||
uint32_t width; ///< Width of framebuffer
|
||||
uint32_t height; ///< Height of framebuffer
|
||||
uint8_t bpp; ///< Bits per pixel
|
||||
enum Type : uint8_t {
|
||||
INDEXED = 0, ///< Using a custom color palette
|
||||
RGB = 1, ///< Standard red-green-blue
|
||||
EGA_TEXT = 2 ///< Enhanced Graphics Adapter color palette
|
||||
} type;
|
||||
union {
|
||||
/*! \brief For INDEXED type
|
||||
*/
|
||||
struct {
|
||||
uint32_t palette_addr; ///< Address of an array with RGB values
|
||||
uint16_t palette_num_colors; ///< Number of colors (in array above)
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief For RGB type
|
||||
*/
|
||||
struct {
|
||||
uint8_t offset_red; ///< Offset of red value
|
||||
uint8_t bits_red; ///< Bits used in red value
|
||||
uint8_t offset_green; ///< Offset of green value
|
||||
uint8_t bits_green; ///< Bits used in green value
|
||||
uint8_t offset_blue; ///< Offset of blue value
|
||||
uint8_t bits_blue; ///< Bits used in blue value
|
||||
} __attribute__((packed));
|
||||
} __attribute__((packed));
|
||||
} __attribute__((packed));
|
||||
assert_size(Framebuffer, 28);
|
||||
|
||||
/*! \brief Get pointer to framebuffer information
|
||||
*
|
||||
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
|
||||
* in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*/
|
||||
Framebuffer * getFramebufferInfo();
|
||||
} // namespace Multiboot
|
|
@ -0,0 +1,33 @@
|
|||
; The first 8192 bytes of the kernel binary must contain a header with
|
||||
; predefined (and sometimes "magic") values according to the Multiboot standard.
|
||||
; Based on these values, the boot loader decides whether and how to load the
|
||||
; kernel -- which is compiled and linked into an ELF file.
|
||||
; To make this possible with your StuBS kernel, the linker places the following
|
||||
; entry `multiboot_header` at the very beginning of the file thanks to the
|
||||
; linker script (located in compiler/sections.ld).
|
||||
|
||||
[SECTION .multiboot_header]
|
||||
|
||||
; Include configuration
|
||||
%include 'boot/multiboot/config.inc'
|
||||
|
||||
; Multiboot Header
|
||||
ALIGN 4
|
||||
multiboot_header:
|
||||
dd MULTIBOOT_HEADER_MAGIC_OS ; Magic Header Value
|
||||
dd MULTIBOOT_HEADER_FLAGS ; Flags (affects following entries)
|
||||
dd MULTIBOOT_HEADER_CHKSUM ; Header Checksum
|
||||
|
||||
; Following fields would have been required to be defined
|
||||
; if flag A_OUT KLUDGE was set (but we don't need this)
|
||||
dd 0 ; Header address
|
||||
dd 0 ; Begin of load address
|
||||
dd 0 ; end of load address
|
||||
dd 0 ; end of bss segment
|
||||
dd 0 ; address of entry function
|
||||
|
||||
; Following fields are required for video mode (flag MULTIBOOT_VIDEO_MODE)
|
||||
dd 0 ; Mode: 0 = Graphic / 1 = Text
|
||||
dd MULTIBOOT_VIDEO_WIDTH ; Width (pixels / columns)
|
||||
dd MULTIBOOT_VIDEO_HEIGHT ; Height (pixels / rows)
|
||||
dd MULTIBOOT_VIDEO_BITDEPTH ; color depth / number of colors
|
|
@ -0,0 +1,77 @@
|
|||
; This is the actual entry point of the kernel.
|
||||
; The switch into the 32-bit 'Protected Mode' has already been performed
|
||||
; (by the boot loader).
|
||||
; The assembly code just performs the absolute necessary steps (like setting up
|
||||
; the stack) to be able to jump into the C++ code -- and continue further
|
||||
; initialization in a (more) high-level language.
|
||||
|
||||
[BITS 32]
|
||||
|
||||
; External functions and variables
|
||||
[EXTERN CPU_CORE_STACK_SIZE] ; Constant containing the initial stack size (per CPU core), see `machine/core.cc`
|
||||
[EXTERN cpu_core_stack_pointer] ; Pointer to reserved memory for CPU core stacks, see `machine/core.cc`
|
||||
[EXTERN gdt_protected_mode_pointer] ; Pointer to 32 Bit Global Descriptor Table (located in `machine/gdt.cc`)
|
||||
[EXTERN long_mode] ; Low level function to jump into the 64-bit mode ('Long Mode', see `boot/longmode.asm`)
|
||||
[EXTERN multiboot_addr] ; Variable, in which the Pointer to Multiboot information
|
||||
; structure should be stored (`boot/multiboot/data.cc`)
|
||||
|
||||
; Load Multiboot settings
|
||||
%include "boot/multiboot/config.inc"
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
; Entry point for the bootstrap processor (CPU0)
|
||||
[GLOBAL startup_bsp]
|
||||
startup_bsp:
|
||||
; Check if kernel was booted by a Multiboot compliant boot loader
|
||||
cmp eax, MULTIBOOT_HEADER_MAGIC_LOADER
|
||||
jne skip_multiboot
|
||||
; Pointer to Multiboot information structure has been stored in ebx by the
|
||||
; boot loader -- copy to a variable for later usage.
|
||||
mov [multiboot_addr], ebx
|
||||
|
||||
skip_multiboot:
|
||||
; Disable interrupts
|
||||
cli
|
||||
; Disable non maskable interrupts (NMI)
|
||||
; (we are going to ignore them)
|
||||
mov al, 0x80
|
||||
out 0x70, al
|
||||
|
||||
jmp load_cs
|
||||
|
||||
; Segment initialization
|
||||
; (code used by bootstrap and application processors as well)
|
||||
[GLOBAL segment_init]
|
||||
segment_init:
|
||||
; Load temporary protected mode Global Descriptor Table (GDT)
|
||||
lgdt [gdt_protected_mode_pointer]
|
||||
|
||||
; Initialize segment register
|
||||
mov ax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
; Load code segment register
|
||||
jmp 0x8:load_cs
|
||||
|
||||
load_cs:
|
||||
; Initialize stack pointer:
|
||||
; Atomic increment of `cpu_core_stack_pointer` by `CPU_CORE_STACK_SIZE`
|
||||
; (to avoid race conditions at application processor boot)
|
||||
mov eax, [CPU_CORE_STACK_SIZE]
|
||||
lock xadd [cpu_core_stack_pointer], eax
|
||||
; Since the stack grows into the opposite direction,
|
||||
; Add `CPU_CORE_STACK_SIZE` again
|
||||
add eax, [CPU_CORE_STACK_SIZE]
|
||||
; Assign stack pointer
|
||||
mov esp, eax
|
||||
|
||||
; Clear direction flag for string operations
|
||||
cld
|
||||
|
||||
; Switch to long mode (64 bit)
|
||||
jmp long_mode
|
|
@ -0,0 +1,86 @@
|
|||
#include "startup.h"
|
||||
|
||||
#include "types.h"
|
||||
#include "compiler/libc.h"
|
||||
#include "debug/output.h"
|
||||
#include "debug/kernelpanic.h"
|
||||
#include "interrupt/handler.h"
|
||||
#include "machine/acpi.h"
|
||||
#include "machine/apic.h"
|
||||
#include "machine/core.h"
|
||||
#include "machine/idt.h"
|
||||
#include "machine/pic.h"
|
||||
|
||||
/*! \brief The first processor is the Bootstrap Processor (BSP)
|
||||
*/
|
||||
static bool is_bootstrap_processor = true;
|
||||
|
||||
extern "C" [[noreturn]] void kernel_init() {
|
||||
if (is_bootstrap_processor) {
|
||||
is_bootstrap_processor = false;
|
||||
/* Setup and load Interrupt Descriptor Table (IDT)
|
||||
*
|
||||
* On the first call to \ref kernel_init(), we have to assign the
|
||||
* addresses of the entry functions for each interrupt
|
||||
* (called 'interrupt_entry_VECTOR', defined in `interrupt/handler.asm`)
|
||||
* to the IDT. These entry functions save the context and call the C++
|
||||
* \ref interrupt_handler(). As the \ref IDT is used by all CPUs,
|
||||
* it is sufficient to do this initialization on only the BSP (first core).
|
||||
*/
|
||||
for (int i = 0; i < Core::Interrupt::VECTORS ; i++) {
|
||||
IDT::handle(i, interrupt_entry[i]);
|
||||
}
|
||||
IDT::load();
|
||||
|
||||
// Initialize PICs
|
||||
PIC::initialize();
|
||||
|
||||
// Call global constructors
|
||||
CSU::initializer();
|
||||
|
||||
// Initialize ACPI
|
||||
if (!ACPI::init()) {
|
||||
DBG_VERBOSE << "No ACPI!";
|
||||
Core::die();
|
||||
}
|
||||
// Initialize APIC (using ACPI)
|
||||
if (!APIC::init()) {
|
||||
DBG_VERBOSE << "APIC Initialization failed";
|
||||
Core::die();
|
||||
}
|
||||
|
||||
// Initialize the Bootstrap Processor
|
||||
Core::init();
|
||||
|
||||
// Go to main function
|
||||
main();
|
||||
|
||||
// Exit CPU
|
||||
DBG_VERBOSE << "CPU core " << Core::getID() << " (BSP) shutdown." << endl;
|
||||
Core::exit();
|
||||
} else {
|
||||
// Load Interrupt Descriptor Table (IDT)
|
||||
IDT::load();
|
||||
|
||||
// Initialize this application processor
|
||||
Core::init();
|
||||
|
||||
// And call the AP main
|
||||
main_ap();
|
||||
|
||||
// Exit CPU
|
||||
DBG_VERBOSE << "CPU core " << Core::getID() << " (AP) shutdown." << endl;
|
||||
Core::exit();
|
||||
}
|
||||
|
||||
// Only on last core
|
||||
if (Core::countOnline() == 1) {
|
||||
// Call global destructors
|
||||
CSU::finalizer();
|
||||
}
|
||||
|
||||
// wait forever
|
||||
while (true) {
|
||||
Core::die();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*! \file
|
||||
* \brief Startup of the first core, also known as bootstrap processor (BSP)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "compiler/fix.h"
|
||||
|
||||
/*! \brief Entry point of your kernel
|
||||
*
|
||||
* \ingroup Startup
|
||||
*
|
||||
* Executed by boot loader.
|
||||
* Stores Pointer to \ref Multiboot information structure,
|
||||
* initializes stack pointer,
|
||||
* switches to long mode
|
||||
* and finally calls the C++ \ref kernel_init function
|
||||
*/
|
||||
extern "C" void startup_bsp()
|
||||
ERROR_ON_CALL("The kernel entry point shall never be called from your code!");
|
||||
|
||||
/*! \brief Initializes the C++ environment and detects system components
|
||||
*
|
||||
* \ingroup Startup
|
||||
*
|
||||
* The startup code(both for \ref startup_bsp "bootstrap" and \ref startup_ap "application processor")
|
||||
* jumps to this high level function. After initialization it will call \ref main()
|
||||
*or \ref main_ap() respectively
|
||||
*/
|
||||
extern "C" [[noreturn]] void kernel_init()
|
||||
ERROR_ON_CALL("The kernel init function shall never be called from your code!");
|
||||
|
||||
/*! \brief Kernels main function
|
||||
*
|
||||
* Called after initialization of the system by \ref kernel_init()
|
||||
*
|
||||
* \note This code will only be executed on the booting CPU (i.e., the one with ID 0).
|
||||
*/
|
||||
extern "C" int main();
|
||||
|
||||
/*! \brief Entry point for application processors
|
||||
*
|
||||
* Called after initialization of the system by \ref kernel_init()
|
||||
*
|
||||
* \note Code in this function will be executed on all APs (i.e., all CPUs except ID 0)
|
||||
*/
|
||||
extern "C" int main_ap();
|
|
@ -0,0 +1,66 @@
|
|||
; Startup of the remaining application processors (in real mode)
|
||||
; and switching to 'Protected Mode' with a temporary GDT.
|
||||
; This code is relocated by ApplicationProcessor::relocateSetupCode()
|
||||
|
||||
[SECTION .setup_ap_seg]
|
||||
[GLOBAL setup_ap_gdt]
|
||||
[GLOBAL setup_ap_gdtd]
|
||||
|
||||
; Unlike the bootstrap processor, the application processors have not been
|
||||
; setup by the boot loader -- they start in real mode (16 bit) and have to be
|
||||
; switched manually to protected mode (32 bit)
|
||||
[BITS 16]
|
||||
|
||||
setup_ap:
|
||||
; Initialize segment register
|
||||
mov ax, cs ; Code segment and...
|
||||
mov ds, ax ; .. data segment should point to the same segment
|
||||
; (we don't use stack / stack segment)
|
||||
|
||||
; Disable interrupts
|
||||
cli
|
||||
; Disable non maskable interrupts (NMI)
|
||||
mov al, 0x80
|
||||
out 0x70, al
|
||||
|
||||
; load temporary real mode Global Descriptor Table (GDT)
|
||||
lgdt [setup_ap_gdtd - setup_ap]
|
||||
|
||||
; Switch to protected mode:
|
||||
; enable protected mode bit (1 << 0) in control register 0
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
; Far jump to 32 bit `startup_ap` function
|
||||
jmp dword 0x08:startup_ap
|
||||
|
||||
; memory reserved for temporary real mode GDT
|
||||
; initialized by ApplicationProcessor::relocateSetupCode()
|
||||
ALIGN 4
|
||||
setup_ap_gdt:
|
||||
dq 0,0,0,0,0 ; reserve memory for at least 5 GDT entries
|
||||
|
||||
; memory reserved for temporary real mode GDT descriptor
|
||||
; initialized by ApplicationProcessor::relocateSetupCode()
|
||||
setup_ap_gdtd:
|
||||
dw 0,0,0,0,0 ; reserve memory for GDT descriptor
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
[BITS 32]
|
||||
|
||||
; Segment initialization defined in `boot/startup.asm`
|
||||
[EXTERN segment_init]
|
||||
|
||||
; protected mode (32 bit) startup code for application processor
|
||||
startup_ap:
|
||||
; reload all segment selectors (since they still point to the real mode GDT)
|
||||
mov ax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
; Use same segment initialization function as bootstrap processor
|
||||
jmp segment_init
|
|
@ -0,0 +1,83 @@
|
|||
#include "startup_ap.h"
|
||||
#include "utils/string.h"
|
||||
#include "utils/size.h"
|
||||
#include "debug/output.h"
|
||||
#include "debug/assert.h"
|
||||
#include "machine/lapic.h"
|
||||
#include "machine/core_interrupt.h"
|
||||
#include "machine/gdt.h"
|
||||
#include "machine/pit.h"
|
||||
|
||||
namespace ApplicationProcessor {
|
||||
|
||||
// Make sure that the RELOCATED_SETUP is in low memory (< 1 MiB)
|
||||
static_assert((RELOCATED_SETUP & ~0x000ff000) == 0, "Not a valid 1 MB address for RELOCATED_SETUP!");
|
||||
|
||||
/*! \brief Temporary Global Descriptor Table
|
||||
*
|
||||
* Blue print, to be copied into real mode code
|
||||
*/
|
||||
constexpr GDT::SegmentDescriptor ap_gdt[] = {
|
||||
// nullptr-Deskriptor
|
||||
{},
|
||||
|
||||
// code segment
|
||||
{ /* base = */ 0x0,
|
||||
/* limit = */ 0xFFFFFFFF,
|
||||
/* code = */ true,
|
||||
/* ring = */ 0,
|
||||
/* size = */ GDT::SIZE_32BIT },
|
||||
|
||||
// data segment
|
||||
{ /* base = */ 0x0,
|
||||
/* limit = */ 0xFFFFFFFF,
|
||||
/* code = */ false,
|
||||
/* ring = */ 0,
|
||||
/* size = */ GDT::SIZE_32BIT },
|
||||
};
|
||||
|
||||
void relocateSetupCode() {
|
||||
// Relocated setup code
|
||||
memcpy(reinterpret_cast<void*>(RELOCATED_SETUP), &___SETUP_AP_START__, &___SETUP_AP_END__ - &___SETUP_AP_START__);
|
||||
|
||||
// Adjust GDT:
|
||||
// Calculate offset for real mode GDT and GDT descriptor
|
||||
uintptr_t ap_gdt_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdt) -
|
||||
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
|
||||
uintptr_t ap_gdtd_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdtd) -
|
||||
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
|
||||
|
||||
// Copy blue print of real mode GDT to the relocated memory
|
||||
void * relocated_ap_gdt = reinterpret_cast<void*>(RELOCATED_SETUP + ap_gdt_offset);
|
||||
memcpy(relocated_ap_gdt, &ap_gdt, sizeof(ap_gdt));
|
||||
|
||||
// Calculate GDT descriptor for relocated address
|
||||
GDT::Pointer * relocated_ap_gdtd = reinterpret_cast<GDT::Pointer*>(RELOCATED_SETUP + ap_gdtd_offset);
|
||||
relocated_ap_gdtd->set(relocated_ap_gdt, size(ap_gdt));
|
||||
}
|
||||
|
||||
void boot(void) {
|
||||
assert(!Core::Interrupt::isEnabled() && "Interrupts should not be enabled before APs have booted!");
|
||||
|
||||
// Relocate setup code
|
||||
relocateSetupCode();
|
||||
|
||||
// Calculate Init-IPI vector based on address of relocated setup_ap()
|
||||
uint8_t vector = RELOCATED_SETUP >> 12;
|
||||
|
||||
// Send Init-IPI to all APs
|
||||
LAPIC::IPI::sendInit();
|
||||
|
||||
// wait at least 10ms
|
||||
PIT::delay(10000);
|
||||
|
||||
// Send Startup-IPI twice
|
||||
DBG_VERBOSE << "Sending STARTUP IPI #1" << endl;
|
||||
LAPIC::IPI::sendStartup(vector);
|
||||
// wait at least 200us
|
||||
PIT::delay(200);
|
||||
|
||||
DBG_VERBOSE << "Sending STARTUP IPI #2" << endl;
|
||||
LAPIC::IPI::sendStartup(vector);
|
||||
}
|
||||
} // namespace ApplicationProcessor
|
|
@ -0,0 +1,109 @@
|
|||
/*! \file
|
||||
* \brief Startup of additional cores, the application processors (APs)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "compiler/fix.h"
|
||||
|
||||
/*! \brief Application Processor Boot
|
||||
*
|
||||
* Interface to boot the APs
|
||||
*/
|
||||
namespace ApplicationProcessor {
|
||||
/*! \brief Address (below 1 MiB) to which the setup code gets relocated
|
||||
*/
|
||||
const uintptr_t RELOCATED_SETUP = 0x40000;
|
||||
|
||||
/*! \brief Relocate the real mode setup code
|
||||
*
|
||||
* The application processors (APs) start in real mode, which means that your setup
|
||||
* code must be placed within the first megabyte -- your operating system resides
|
||||
* currently at a much higher address (16 MiB), so the code has to be copied
|
||||
* down there first.
|
||||
*
|
||||
* Luckily, the code in `setup_ap()` can be relocated by copying -- because it
|
||||
* does not use any absolute addressing (except when jumping to the protected
|
||||
* mode function `startup_ap()`).
|
||||
* The function must be copied to the address of \ref RELOCATED_SETUP (0x40000),
|
||||
* so that the APs can start there.
|
||||
*
|
||||
* The memory section contains a reserved area for the \ref GDT and its descriptor,
|
||||
* which has to be assigned first with the contents of \ref ap_gdt.
|
||||
*
|
||||
* \note You could also tell the linker script to put the code directly
|
||||
* at the appropriate place, but unfortunately the Qemu multiboot
|
||||
* implementation (via `-kernel` parameter) can't handle it properly.
|
||||
*/
|
||||
void relocateSetupCode();
|
||||
|
||||
/*! \brief Boot all application processors
|
||||
*
|
||||
* Performs relocation by calling \ref relocateSetupCode()
|
||||
*
|
||||
* \see [ISDMv3, 8.4.4.2 Typical AP Initialization Sequence](intel_manual_vol3.pdf#page=276)
|
||||
*/
|
||||
void boot();
|
||||
} // namespace ApplicationProcessor
|
||||
|
||||
/*! \brief Begin of setup code for application processors
|
||||
*
|
||||
* The setup code has to switch from real mode (16 bit) to protected mode (32 bit),
|
||||
* hence it is written in assembly and must be executed in low memory (< 1 MiB).
|
||||
*
|
||||
* After kernel start the code is somewhere above 16 MiB (the bootstrap
|
||||
* processor was already launched in protected mode by the boot loader).
|
||||
* Therefore this symbol is required for relocate the code to the position
|
||||
* specified by \ref ApplicationProcessor::RELOCATED_SETUP.
|
||||
*
|
||||
* Luckily, the `setup_ap` code in `boot/startup_ap.asm` is rather simple and
|
||||
* doesn't depend on absolute addressing -- and is therefore relocatable.
|
||||
*
|
||||
* Relocation is done by the function \ref ApplicationProcessor::relocateSetupCode()
|
||||
*
|
||||
* The `___SETUP_AP_START__` symbol is defined in the linker script (`compiler/section.ld`)
|
||||
*/
|
||||
extern char ___SETUP_AP_START__;
|
||||
|
||||
/*! \brief End of startup code for application processors
|
||||
*
|
||||
* This Symbol is defined in the linker script (`compiler/section.ld`)
|
||||
*/
|
||||
extern char ___SETUP_AP_END__;
|
||||
|
||||
/*! \brief Memory reserved for a temporary real mode GDT
|
||||
* within the relocatable memory area of the setup code
|
||||
*/
|
||||
extern char setup_ap_gdt;
|
||||
|
||||
/*! \brief Memory reserved for a temporary real mode GDT descriptor
|
||||
* within the relocatable memory area of the setup code
|
||||
*/
|
||||
extern char setup_ap_gdtd;
|
||||
|
||||
/*! \brief Entry point for application processors
|
||||
*
|
||||
* Unlike the bootstrap processor, the application processors have not been
|
||||
* setup by the boot loader -- they start in `Real Mode` (16 bit) and have to be
|
||||
* switched manually to `Protected Mode` (32 bit).
|
||||
* This is exactly what this real mode function does, handing over control
|
||||
* to the (32 bit) function \ref startup_ap()
|
||||
*
|
||||
* This code is written is assembly (`boot/startup_ap.asm`) and relocated by
|
||||
* \ref ApplicationProcessor::relocateSetupCode() during
|
||||
* \ref ApplicationProcessor::boot()
|
||||
*/
|
||||
extern "C" void setup_ap()
|
||||
ERROR_ON_CALL("The setup function for application processors shall never be called from your code!");
|
||||
|
||||
/*! \brief Startup for application processors
|
||||
* \ingroup Startup
|
||||
*
|
||||
* This function behaves similar to \ref startup_bsp():
|
||||
* Initializes stack pointer,
|
||||
* switches to long mode
|
||||
* and calls the C++ \ref kernel_init function
|
||||
*/
|
||||
extern "C" void startup_ap()
|
||||
ERROR_ON_CALL("The startup function for application processors shall never be called from your code!");
|
|
@ -0,0 +1,18 @@
|
|||
; C Runtime Objects - Function prologues for the initialization (.init) and termination routines (.fini) required by the runtime libs.
|
||||
; When developing on Linux, these files are automatically added by the compiler/linker; we, however, are writing an operating system
|
||||
; and therefore need to add them ourselves.
|
||||
|
||||
; C Runtime - beginning (needs to be passed as first element to the linker)
|
||||
[SECTION .init]
|
||||
[GLOBAL _init]
|
||||
_init:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
; The linker will inject the contents of the .init from crtbegin.o here
|
||||
|
||||
[SECTION .fini]
|
||||
[GLOBAL _fini]
|
||||
_fini:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
; The linker will inject the contents of the .fini from crtbegin.o here
|
|
@ -0,0 +1,12 @@
|
|||
; C Runtime Objects - Function prologues for the initialization (.init) and termination routines (.fini) required by the runtime libs.
|
||||
|
||||
; C Runtime - end (needs to be passed as last element to the linker)
|
||||
[SECTION .init]
|
||||
; The linker will inject the contents of the .init from crtend.o here
|
||||
pop rbp
|
||||
ret
|
||||
|
||||
[SECTION .fini]
|
||||
; The linker will inject the contents of the .fini from crtend.o here
|
||||
pop rbp
|
||||
ret
|
|
@ -0,0 +1,21 @@
|
|||
/*! \file
|
||||
* \brief Compiler-dependent fixes & idiosyncrasies
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __clang__
|
||||
# define UNUSED_STRUCT_FIELD __attribute__((unused))
|
||||
#else
|
||||
// GCC does not understand this attribute correctly for structures
|
||||
# define UNUSED_STRUCT_FIELD
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
// Only GCC understands the error attribute
|
||||
# define ERROR_ON_CALL(MSG) __attribute__((error(MSG)));
|
||||
#else
|
||||
# define ERROR_ON_CALL(MSG)
|
||||
#endif
|
||||
|
||||
#define MAYBE_UNUSED __attribute__((unused))
|
|
@ -0,0 +1,44 @@
|
|||
#include "compiler/libc.h"
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Function pointer for initialization/finalization functions for global objects
|
||||
* required since GCC 4.7 and later.
|
||||
*
|
||||
* These symbols appear kind of magically due to the compiler
|
||||
*/
|
||||
extern void(*__preinit_array_start[]) ();
|
||||
extern void(*__preinit_array_end[]) ();
|
||||
extern void(*__init_array_start[]) ();
|
||||
extern void(*__init_array_end[]) ();
|
||||
extern void(*__fini_array_start[]) ();
|
||||
extern void(*__fini_array_end[]) ();
|
||||
|
||||
extern "C" void _init();
|
||||
extern "C" void _fini();
|
||||
|
||||
namespace CSU {
|
||||
|
||||
void initializer() {
|
||||
const unsigned int preinit_size = __preinit_array_end - __preinit_array_start;
|
||||
for (unsigned int i = 0; i != preinit_size; ++i) {
|
||||
(*__preinit_array_start[i])();
|
||||
}
|
||||
|
||||
_init();
|
||||
|
||||
const size_t size = __init_array_end - __init_array_start;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
(*__init_array_start[i])();
|
||||
}
|
||||
}
|
||||
|
||||
void finalizer() {
|
||||
const unsigned int fini_size = __fini_array_end - __fini_array_start;
|
||||
for (unsigned int i = 0; i != fini_size; ++i) {
|
||||
(*__fini_array_start[i])();
|
||||
}
|
||||
|
||||
_fini();
|
||||
}
|
||||
|
||||
} // namespace CSU
|
|
@ -0,0 +1,22 @@
|
|||
/*! \file
|
||||
* \brief Initialization functions for global objects required by the compiler
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \brief C StartUp (CSU)
|
||||
* required by the compiler and provided by the c standard library
|
||||
*/
|
||||
namespace CSU {
|
||||
|
||||
/*! \brief Call global constructors and initialization functions
|
||||
* (this is usually done by __libc_csu_init)
|
||||
*/
|
||||
void initializer();
|
||||
|
||||
/*! \brief Call global destructors and finalizer functions
|
||||
* (this is usually done by __libc_csu_fini)
|
||||
*/
|
||||
void finalizer();
|
||||
|
||||
} // namespace CSU
|
|
@ -0,0 +1,31 @@
|
|||
/*! \file
|
||||
* \brief C++ runtime support functions
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void operator delete(void *ptr) {
|
||||
(void) ptr;
|
||||
}
|
||||
|
||||
void operator delete(void *ptr, size_t size) {
|
||||
(void) ptr;
|
||||
(void) size;
|
||||
}
|
||||
|
||||
extern "C" int __cxa_atexit(void (*func)(void *), void * arg, void * dso_handle) {
|
||||
// Registers a function that will be executed on exit.
|
||||
// We simply ignore those functions, as we don't need them for our operating systems.
|
||||
|
||||
(void) func;
|
||||
(void) arg;
|
||||
(void) dso_handle;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" [[noreturn]] void __cxa_pure_virtual() {
|
||||
// Pure virtual function was called -- this if obviously not valid,
|
||||
// therefore we wait infinitely.
|
||||
while (true) {}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/* Entry in our OS -- label 'startup_bsp' in file boot/startup.asm */
|
||||
ENTRY(startup_bsp)
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* start address of our kernel */
|
||||
. = 16M;
|
||||
|
||||
/* This is a linker script defined "variable" (without value -- therefore
|
||||
* better to be considered as a symbol), which can be referenced in the C++
|
||||
* source code using `&___KERNEL_START___` (mind the reference operator!)
|
||||
* to get a pointer to the current (virtual) memory position.
|
||||
* However, a previous declaration with C linkage is required, e.g.
|
||||
* extern "C" void * ___KERNEL_START___;
|
||||
* For more information have a look at
|
||||
* https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html
|
||||
*/
|
||||
___KERNEL_START___ = .;
|
||||
|
||||
.boot :
|
||||
{
|
||||
/* Multiboot Header should be at the very beginning of the binary */
|
||||
*(.multiboot_header)
|
||||
}
|
||||
|
||||
/* The (executable) machine code */
|
||||
.text :
|
||||
{
|
||||
*(.text .text.* .gnu.linkonce.t.*)
|
||||
*(.init)
|
||||
*(.fini)
|
||||
}
|
||||
|
||||
/* Align to the next page border
|
||||
* allowing the linker to mark the section above as [read and] executable
|
||||
* while the following sections is just read-only. */
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
/* Start for application processors, relocated by APIC::init()
|
||||
* to a below 1 MB address to boot from real mode.
|
||||
*
|
||||
* Please note:
|
||||
* It is possible to let the linker place it at a below 1 MB address,
|
||||
* while all the rest starts at 16 MB. This will work for multiboot
|
||||
* compliant boot loader like GRUB and PXELINUX, however,
|
||||
* the qemu boot loader cannot handle such ELF files (yet)...
|
||||
* That's why we have to copy it ourself in the kernel */
|
||||
.setup_ap_seg ALIGN(0x10) :
|
||||
{
|
||||
___SETUP_AP_START__ = .;
|
||||
*(.setup_ap_seg)
|
||||
}
|
||||
___SETUP_AP_END__ = .;
|
||||
|
||||
/* Read-only data */
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata .rodata.* .gnu.linkonce.r.*)
|
||||
KEEP(*(.note.gnu.build-id))
|
||||
}
|
||||
|
||||
/* Align to the next page border
|
||||
* allowing the linker to mark the section below as [read and] writeable */
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
/* lists containing the start address of global constructors and destructors (generated by the compiler) */
|
||||
.preinit_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||
KEEP (*(.preinit_array))
|
||||
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||
}
|
||||
.init_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__init_array_start = .);
|
||||
KEEP (*(SORT(.init_array.*)))
|
||||
KEEP (*(.init_array))
|
||||
PROVIDE_HIDDEN (__init_array_end = .);
|
||||
}
|
||||
.fini_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||
KEEP (*(SORT(.fini_array.*)))
|
||||
KEEP (*(.fini_array))
|
||||
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||
}
|
||||
|
||||
/* the data section containing initialized static variables
|
||||
* (writeable with a value not zero) */
|
||||
.data :
|
||||
{
|
||||
*(.data .data.* .gnu.linkonce.d.*)
|
||||
/* Global offset table */
|
||||
*(.got*)
|
||||
/* Exception Handling */
|
||||
*(.eh_frame*)
|
||||
}
|
||||
|
||||
/* the bss (block starting symbol) section containing uninitialized
|
||||
* static variables (writeable with an initial value of zero);
|
||||
* this section does not consume any memory in the binary */
|
||||
.bss :
|
||||
{
|
||||
*(.bss .bss.* .gnu.linkonce.b.*)
|
||||
*(COMMON)
|
||||
}
|
||||
|
||||
___KERNEL_END___ = .;
|
||||
|
||||
/* Sections which should not be included in the binary */
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.note)
|
||||
*(.comment)
|
||||
/* Debug information (commented out to keep it)
|
||||
*(.debug*)
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#include "assert.h"
|
||||
|
||||
[[noreturn]] void assertion_failed(const char * exp, const char * func, const char * file, int line) {
|
||||
(void) exp;
|
||||
(void) func;
|
||||
(void) file;
|
||||
(void) line;
|
||||
// TODO: Print error message (in debug window)
|
||||
// TODO: Then stop the current core permanently
|
||||
// Use appropriate method from class Core to do so.
|
||||
while(true) {} // wait forever
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*! \file
|
||||
* \brief Contains several macros usable for making assertions
|
||||
*
|
||||
* Depending on the type of assertion (either static or at runtime), a failing assertion will trigger an error.
|
||||
* For static assertion, this error will be shown at compile time and abort compilation.
|
||||
* Runtime assertions will trigger a message containing details about the error occurred and will make the CPU die.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef STRINGIFY
|
||||
/*! \def STRINGIFY(S)
|
||||
* \brief Converts a macro parameter into a string
|
||||
* \ingroup debug
|
||||
* \param S Expression to be converted
|
||||
* \return stringified version of S
|
||||
*/
|
||||
#define STRINGIFY(S) #S
|
||||
#endif
|
||||
|
||||
/*! \def assert_size(TYPE, SIZE)
|
||||
* \brief Statically ensure (at compile time) that a data type (or variable) has the expected size.
|
||||
* \ingroup debug
|
||||
* \param TYPE The type to be checked
|
||||
* \param SIZE Expected size in bytes
|
||||
*/
|
||||
#define assert_size(TYPE, SIZE) \
|
||||
static_assert(sizeof(TYPE) == (SIZE), "Wrong size for " STRINGIFY(TYPE))
|
||||
|
||||
/*! \def assert(EXP)
|
||||
* \brief Ensure (at execution time) an expression evaluates to `true`, print an error message and stop the CPU otherwise.
|
||||
* \ingroup debug
|
||||
* \param EXP The expression to be checked
|
||||
*/
|
||||
#ifdef NDEBUG
|
||||
#define assert(EXP) \
|
||||
do { \
|
||||
(void)sizeof(EXP); \
|
||||
} while (false)
|
||||
#else
|
||||
#define assert(EXP) \
|
||||
do { \
|
||||
if (__builtin_expect(!(EXP), 0)) { \
|
||||
assertion_failed(STRINGIFY(EXP), __func__, __FILE__, __LINE__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
/*! \brief Handles a failed assertion
|
||||
*
|
||||
* This function will print a message containing further information about the
|
||||
* failed assertion and stops the current CPU permanently.
|
||||
*
|
||||
* \note This function should never be called directly, but only via the macro `assert`.
|
||||
*
|
||||
* \todo Implement Remainder of Method (output & CPU stopping)
|
||||
*
|
||||
* \param exp Expression that did not hold
|
||||
* \param func Name of the function in which the assertion failed
|
||||
* \param file Name of the file in which the assertion failed
|
||||
* \param line Line in which the assertion failed
|
||||
*/
|
||||
[[noreturn]] void assertion_failed(const char * exp, const char * func, const char * file, int line);
|
||||
#endif
|
|
@ -0,0 +1,22 @@
|
|||
/*! \file
|
||||
* \brief Macro to print an error message and stop the current core.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \def kernelpanic
|
||||
* \brief Print an error message in the debug window and \ref Core::die "stop the current core"
|
||||
* \ingroup debug
|
||||
* \param MSG error message
|
||||
*/
|
||||
#define kernelpanic(MSG) \
|
||||
do { \
|
||||
DBG << "PANIC: '" << (MSG) << "' in " << __func__ \
|
||||
<< " @ " << __FILE__ << ":" << __LINE__ \
|
||||
<< flush; \
|
||||
Core::die(); \
|
||||
} while (0)
|
||||
|
||||
// The includes are intentionally placed at the end, so the macro can be used inside those included files as well.
|
||||
#include "debug/output.h"
|
||||
#include "machine/core.h"
|
|
@ -0,0 +1,4 @@
|
|||
#include "debug/nullstream.h"
|
||||
|
||||
// Instance
|
||||
NullStream nullstream;
|
|
@ -0,0 +1,39 @@
|
|||
/*! \file
|
||||
* \brief \ref NullStream is a stream discarding everything
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "object/outputstream.h"
|
||||
|
||||
/*! \brief Ignore all data passed by the stream operator
|
||||
* \ingroup io
|
||||
*
|
||||
* Can be used instead of the \ref OutputStream if (for debugging reasons) all
|
||||
* output should be ignored, e.g. for \ref DBG_VERBOSE
|
||||
*
|
||||
* By using template programming, a single generic methods is sufficient
|
||||
* (which simply discard everything).
|
||||
*/
|
||||
class NullStream {
|
||||
public:
|
||||
/*! \brief Empty default constructor
|
||||
*/
|
||||
NullStream() {}
|
||||
|
||||
/*! \brief Generic stream operator for any data type
|
||||
*
|
||||
* Uses template meta programming for a generic & short solution
|
||||
*
|
||||
* \tparam T Type of data to ignore
|
||||
* \param value data to be ignore
|
||||
* \return Reference to the \ref NullStream object allowing concatenation of operators
|
||||
*/
|
||||
template <typename T>
|
||||
NullStream& operator << (T value) {
|
||||
(void) value;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
extern NullStream nullstream;
|
|
@ -0,0 +1,65 @@
|
|||
/*! \file
|
||||
* \brief Debug macros enabling debug output on a separate window for each core.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \def DBG_VERBOSE
|
||||
* \brief An output stream, which is only displayed in the debug window in verbose mode
|
||||
*
|
||||
* \note If a serial console has been implemented, the output can be redirected
|
||||
* to the serial stream instead (by changing the macro) -- this makes the
|
||||
* (usually) very large output more readable (since it allows scrolling back)
|
||||
*/
|
||||
#ifdef VERBOSE
|
||||
// If VERBOSE is defined, forward everything to \ref DBG
|
||||
#define DBG_VERBOSE DBG
|
||||
#else
|
||||
// Otherwise sent everything to the NullStream (which will simply discard everything)
|
||||
#define DBG_VERBOSE nullstream
|
||||
// in this case we have to include the null stream
|
||||
#include "debug/nullstream.h"
|
||||
#endif
|
||||
|
||||
/*! \def DBG
|
||||
* \brief An output stream, which is displayed in the debug window of the core it was executed on
|
||||
*
|
||||
* In single core (\OOStuBS) this is just an alias to the debug window object
|
||||
* `dout`.
|
||||
* However, on a multi core system a debug window for each core is
|
||||
* required, therefore `dout` has to be an \ref TextStream object array with the
|
||||
* core ID as array index -- the selection is done via Core::getID()
|
||||
*
|
||||
*/
|
||||
#define DBG dout[Core::getID()]
|
||||
|
||||
#include "device/textstream.h"
|
||||
#include "machine/core.h"
|
||||
|
||||
/*! \brief Debug window
|
||||
*
|
||||
* Debug output using \ref DBG like
|
||||
* `DBG << "var = " << var << endl`
|
||||
* should be displayed in window dedicated to the core it is executed on.
|
||||
*
|
||||
* While this is quite easy on single core systems like \OOStuBS -- they only
|
||||
* require a single \ref TextStream object called `dout` -- multi core systems
|
||||
* like \MPStuBS need an object array with one window per core.
|
||||
* In the latter case direct list initialization can be used:
|
||||
*
|
||||
* \code{.cpp}
|
||||
* TextStream dout[Core::MAX]{
|
||||
* {0, 40, 17, 21}, // Debug window for core 0, like TextStream(0, 40, 17, 21)
|
||||
* {40, 80, 17, 21}, // Debug window for core 1, like TextStream(40, 80, 17, 21)
|
||||
* //...
|
||||
* };
|
||||
* \endcode
|
||||
*
|
||||
* The debug windows in should be located right below the normal output window
|
||||
* without any overlap and should be able to display at least 3 lines.
|
||||
* In \MPStuBS, two windows can be placed side-by-side, having 40 columns each.
|
||||
*
|
||||
* \todo Define `dout`
|
||||
*/
|
||||
extern TextStream dout[Core::MAX];
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#include "device/serialstream.h"
|
||||
#include "debug/output.h"
|
||||
|
||||
SerialStream::SerialStream(ComPort port, BaudRate baud_rate, DataBits data_bits, StopBits stop_bits, Parity parity)
|
||||
: Serial(port, baud_rate, data_bits, stop_bits, parity) {}
|
||||
|
||||
void SerialStream::flush() {
|
||||
}
|
||||
|
||||
void SerialStream::setForeground(Color c) {
|
||||
(void) c;
|
||||
}
|
||||
|
||||
void SerialStream::setBackground(Color c) {
|
||||
(void) c;
|
||||
}
|
||||
|
||||
void SerialStream::setAttribute(Attrib a) {
|
||||
(void) a;
|
||||
}
|
||||
|
||||
void SerialStream::reset() {
|
||||
}
|
||||
|
||||
void SerialStream::setPos(int x, int y) {
|
||||
(void) x;
|
||||
(void) y;
|
||||
}
|
||||
|
||||
bool SerialStream::getPos(int &x, int &y) {
|
||||
(void) x;
|
||||
(void) y;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SerialStream::print(char* str, int length) {
|
||||
(void) str;
|
||||
(void) length;
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*! \file
|
||||
* \brief \ref Serial \ref SerialStream "output stream"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "object/outputstream.h"
|
||||
#include "machine/serial.h"
|
||||
|
||||
/*! \brief Console (VT100 compatible) via \ref Serial interface.
|
||||
* \ingroup io
|
||||
*
|
||||
* This class allows to connect a VT100-compatible display terminal via
|
||||
* the serial interface.
|
||||
*
|
||||
* The utility 'screen' can be used to attach a terminal to an interface
|
||||
* at a specified connection speed: `screen /dev/ttyS0 115200`
|
||||
*
|
||||
* Color and position can be adjusted with the help of
|
||||
* [escape codes](https://web.archive.org/web/20210126100003/https://ascii-table.com/ansi-escape-sequences-vt-100.php).
|
||||
*/
|
||||
|
||||
class SerialStream : public OutputStream, public Serial {
|
||||
/*! \brief Helper to send a multi-digit number as human readable ASCII characters
|
||||
* \param num Number to send
|
||||
*/
|
||||
void write_number(int num);
|
||||
|
||||
public:
|
||||
/*! \brief Attributes
|
||||
* can be used to influence the display of the output.
|
||||
*
|
||||
* \note The attributes might not be supported or have a different effect
|
||||
* depending on the terminal emulator!
|
||||
*/
|
||||
enum Attrib {
|
||||
RESET = 0, ///< Turn off character attributes
|
||||
BRIGHT = 1, ///< Bold
|
||||
DIM = 2, ///< Low intensity (dimmed)
|
||||
UNDERSCORE = 4, ///< Underline
|
||||
BLINK = 5, ///< Blink (slow)
|
||||
REVERSE = 7, ///< Swap fore & background
|
||||
HIDDEN = 8, ///< Concealed
|
||||
};
|
||||
|
||||
/*! \brief Color codes
|
||||
*
|
||||
* Default VT100 supports eight colors for both foreground and background
|
||||
* (later versions 256 [8 bit] and even true color [32 bit]).
|
||||
* The actual color is affected by the attributes and can look significantly
|
||||
* different depending on the terminal emulator.
|
||||
*/
|
||||
enum Color {
|
||||
BLACK = 0,
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
YELLOW = 3,
|
||||
BLUE = 4,
|
||||
MAGENTA = 5,
|
||||
CYAN = 6,
|
||||
WHITE = 7
|
||||
};
|
||||
|
||||
/*! \brief Constructor for the VT100-compatible console
|
||||
*
|
||||
* Sets up the serial connection as well
|
||||
*
|
||||
* \todo Implement Method
|
||||
*/
|
||||
explicit SerialStream(ComPort port = COM1, BaudRate baud_rate = BAUD_115200, DataBits data_bits = DATA_8BIT,
|
||||
StopBits stop_bits = STOP_1BIT, Parity parity = PARITY_NONE);
|
||||
|
||||
/*! \brief Method to output the buffer contents of the base class \ref Stringbuffer
|
||||
*
|
||||
* The method is automatically called when the buffer is full,
|
||||
* but can also be called explicitly to force output of the current buffer.
|
||||
*
|
||||
* \todo Implement Method
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/*! \brief Change foreground color (for subsequent output)
|
||||
*
|
||||
* \todo Implement Method
|
||||
*
|
||||
* \param c Color
|
||||
*/
|
||||
void setForeground(Color c);
|
||||
|
||||
/*! \brief Change background color (for subsequent output)
|
||||
*
|
||||
* \todo Implement Method
|
||||
*
|
||||
* \param c Color
|
||||
*/
|
||||
void setBackground(Color c);
|
||||
|
||||
/*! \brief Change text attribute (for subsequent output)
|
||||
*
|
||||
* \todo Implement Method
|
||||
*
|
||||
* \param a Attribute
|
||||
*/
|
||||
void setAttribute(Attrib a);
|
||||
|
||||
/*! \brief Reset terminal
|
||||
*
|
||||
* Clear screen, place cursor at the beginning and reset colors
|
||||
* and attributes to the default value.
|
||||
*
|
||||
* \opt Implement Method
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/*! \brief Set the cursor position
|
||||
*
|
||||
* \param x Column in window
|
||||
* \param y Row in window
|
||||
*
|
||||
* \opt Implement Method
|
||||
*/
|
||||
void setPos(int x, int y);
|
||||
|
||||
/*! \brief Read the current cursor position
|
||||
*
|
||||
* It is possible to receive the current cursor position via a special
|
||||
* escape code: Request by sending `\e[6n`, answer will be `\e[y;xR` with
|
||||
* `y` (row) and `x` (column) as human readable ASCII character number.
|
||||
*
|
||||
* However, depending on the implementation, it may be possible that the
|
||||
* system waits endlessly due to an disconnected terminal or data
|
||||
* transmission error.
|
||||
*
|
||||
* \param x Column in window
|
||||
* \param y Row in window
|
||||
* \return `true` if position was successfully received
|
||||
*
|
||||
* \opt Implement Method
|
||||
*/
|
||||
bool getPos(int &x, int &y);
|
||||
|
||||
/*! \brief Display multiple characters in the window starting at the current cursor position
|
||||
*
|
||||
* This method can be used to output a string, starting at the current cursor
|
||||
* position. Since the string does not need to contain a '\0' termination
|
||||
* (as it is usually the case in C), the parameter `length` is required to
|
||||
* specify the number of characters in the string.
|
||||
*
|
||||
* The text is displayed using the previously configured
|
||||
* \ref setAttribute() "attributes", \ref setForeground() "fore-"
|
||||
* and \ref setBackground "background" color.
|
||||
*
|
||||
* A line break will occur wherever the character `\n` is inserted
|
||||
* in the text to be output (for compatibility reasons a `\r` is
|
||||
* automatically appended).
|
||||
*
|
||||
* \todo Implement Method
|
||||
* \param str String to output
|
||||
* \param length length of string
|
||||
*/
|
||||
void print(char* str, int length);
|
||||
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
#include "device/textstream.h"
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*! \file
|
||||
* \brief \ref TextStream outputs text onto the screen in \ref TextMode
|
||||
*/
|
||||
|
||||
/*! \defgroup io I/O subsystem
|
||||
* \brief The input/output subsystem
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "object/outputstream.h"
|
||||
#include "machine/textwindow.h"
|
||||
|
||||
/*! \brief Output text (form different data type sources) on screen in text mode
|
||||
* \ingroup io
|
||||
*
|
||||
* Allows the output of different data types as strings on the \ref TextMode
|
||||
* screen of a PC.
|
||||
* To achieve this, \ref TextStream is derived from both \ref OutputStream and
|
||||
* \ref TextWindow and only implements the method \ref TextStream::flush().
|
||||
* Further formatting or special effects are implemented in \ref TextWindow.
|
||||
*/
|
||||
class TextStream {
|
||||
// Prevent copies and assignments
|
||||
TextStream(const TextStream&) = delete;
|
||||
TextStream& operator=(const TextStream&) = delete;
|
||||
public:
|
||||
/// \copydoc TextWindow::TextWindow(unsigned,unsigned,unsigned,unsigned,bool)
|
||||
TextStream(unsigned from_col, unsigned to_col, unsigned from_row, unsigned to_row, bool use_cursor = false) {
|
||||
(void) from_col;
|
||||
(void) to_col;
|
||||
(void) from_row;
|
||||
(void) to_row;
|
||||
(void) use_cursor;
|
||||
}
|
||||
|
||||
/*! \brief Output the buffer contents of the base class \ref Stringbuffer
|
||||
*
|
||||
* The method is automatically called when the buffer is full,
|
||||
* but can also be called explicitly to force output of the current buffer.
|
||||
*
|
||||
*
|
||||
* \todo Implement method
|
||||
*/
|
||||
void flush();
|
||||
};
|
|
@ -0,0 +1,120 @@
|
|||
; Low-level stuff required for interrupt handling
|
||||
; The "actual" code to be executed is implemented in the C function "interrupt_handler"
|
||||
|
||||
[EXTERN interrupt_handler]
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
; Entry function for interrupt handling (of any vector)
|
||||
|
||||
; The interrupt handling of each vector has to be started in assembler to store
|
||||
; the scratch registers (see SystemV calling conventions) before the actual
|
||||
; high-level (C++) interrupt_handler function can be executed.
|
||||
;
|
||||
; For this purpose we use a macro to generate a customized entry function for
|
||||
; each interrupt vector (0-255), wheres the vector itself is the first parameter.
|
||||
; The second parameter is a boolean flag indicating whether an error code is
|
||||
; placed on the stack for the corresponding trap (by the CPU).
|
||||
;
|
||||
; Usage: IRQ <vector> <error-code?>
|
||||
%macro IRQ 2
|
||||
ALIGN 16
|
||||
interrupt_entry_%1:
|
||||
%if %2 == 0
|
||||
; If the vector is not a trap with an error code automatically pushed
|
||||
; on the stack, the entry function pushes a zero instead
|
||||
; to retain an identical stack layout in each case.
|
||||
push 0
|
||||
%endif
|
||||
|
||||
; The interrupt may be triggered asynchronously, therefore the whole context
|
||||
; has to be saved and restored, or the interrupted code might not be able to
|
||||
; continue. The C++ compiler will only generates code to preserve
|
||||
; non-scratch registers in the high-level interrupt handler -- the scratch
|
||||
; registers have to be saved (and restored later) manually!
|
||||
push rax
|
||||
push rcx
|
||||
push rdx
|
||||
push rsi
|
||||
push rdi
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
|
||||
; Clear direction flag for string operations
|
||||
cld
|
||||
|
||||
; Assign vector as first parameter for the high-level interrupt handler
|
||||
mov rdi, %1
|
||||
|
||||
; Assign pointer to the context (= interrupt stack) as second parameter
|
||||
mov rsi, rsp
|
||||
|
||||
; Call the high-level interrupt handler routine
|
||||
call interrupt_handler
|
||||
|
||||
; Restore scratch registers
|
||||
pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rax
|
||||
|
||||
; Drop error code (or the fake zero value)
|
||||
add rsp, 8
|
||||
|
||||
; Return from interrupt
|
||||
iretq
|
||||
%endmacro
|
||||
|
||||
; For traps the CPU sometimes pushes an error code onto the stack.
|
||||
; These vectors are documented in the manual (or instead use the
|
||||
; [osdev wiki](https://wiki.osdev.org/Exceptions) for a handy list).
|
||||
; Therefore we manually call the macro for the corresponding traps.
|
||||
IRQ 0, 0
|
||||
IRQ 1, 0
|
||||
IRQ 2, 0
|
||||
IRQ 3, 0
|
||||
IRQ 4, 0
|
||||
IRQ 5, 0
|
||||
IRQ 6, 0
|
||||
IRQ 7, 0
|
||||
IRQ 8, 1
|
||||
IRQ 9, 0
|
||||
IRQ 10, 1
|
||||
IRQ 11, 1
|
||||
IRQ 12, 1
|
||||
IRQ 13, 1
|
||||
IRQ 14, 1
|
||||
IRQ 15, 0
|
||||
IRQ 16, 0
|
||||
IRQ 17, 1
|
||||
|
||||
; All subsequent interrupts (18 - 255) have no error code,
|
||||
; therefore we use a loop calling the macro.
|
||||
%assign i 18
|
||||
%rep 238
|
||||
IRQ i, 0
|
||||
%assign i i+1
|
||||
%endrep
|
||||
|
||||
[SECTION .data]
|
||||
|
||||
; Create a function pointer array for each interrupt entry
|
||||
; (to be used in C++ for IDT::handle)
|
||||
[GLOBAL interrupt_entry]
|
||||
|
||||
interrupt_entry:
|
||||
%macro interrupt_vector 1
|
||||
dq interrupt_entry_%1
|
||||
%endmacro
|
||||
%assign i 0
|
||||
%rep 256
|
||||
interrupt_vector i
|
||||
%assign i i+1
|
||||
%endrep
|
|
@ -0,0 +1,7 @@
|
|||
#include "interrupt/handler.h"
|
||||
|
||||
extern "C" void interrupt_handler(Core::Interrupt::Vector vector, InterruptContext *context) {
|
||||
(void) vector;
|
||||
(void) context;
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*! \file
|
||||
* \brief \ref interrupt_handler() Interrupt handler
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "machine/core_interrupt.h"
|
||||
|
||||
/*! \defgroup interrupts Interrupt Handling
|
||||
* \brief The interrupt subsystem
|
||||
*
|
||||
* The interrupt subsystem of StubBS contains all functionality to accept
|
||||
* interrupts from the hardware and process them.
|
||||
* In later exercises the interrupts will enable applications to
|
||||
* execute core functionality (system calls).
|
||||
* The entry point for the interrupt subsystem is the function
|
||||
* 'interrupt_entry_VECTOR' (in `interrupt/handler.asm`).
|
||||
*/
|
||||
|
||||
/*! \brief Preserved interrupt context
|
||||
*
|
||||
* After an interrupt was triggered, the core first saves the basic context
|
||||
* (current code- & stack segment, instruction & stack pointer and the status
|
||||
* flags register) and looks up the handling function for the vector using the
|
||||
* \ref IDT. In StuBS for each vector an own `interrupt_entry_VECTOR` function
|
||||
* (written in assembly in `interrupt/handler.asm`) was registered during boot
|
||||
* by \ref kernel_init(), which all save the scratch registers on the stack
|
||||
* before calling the C++ function \ref interrupt_handler().
|
||||
* The high-level handler gets a pointer to the part of the stack which
|
||||
* corresponds to the \ref InterruptContext structure as second parameter.
|
||||
* After returning from the high-level handler, the previous state is restored
|
||||
* from this context (scratch register in assembly and basic context while
|
||||
* executing `iret`) so it can continue transparently at the previous position.
|
||||
*/
|
||||
struct InterruptContext {
|
||||
// Scratch register (stored by `interrupt/handler.asm`)
|
||||
uintptr_t r11; ///< scratch register R11
|
||||
uintptr_t r10; ///< scratch register R10
|
||||
uintptr_t r9; ///< scratch register R9
|
||||
uintptr_t r8; ///< scratch register R8
|
||||
uintptr_t rdi; ///< scratch register RDI
|
||||
uintptr_t rsi; ///< scratch register RSI
|
||||
uintptr_t rdx; ///< scratch register RDX
|
||||
uintptr_t rcx; ///< scratch register RCX
|
||||
uintptr_t rax; ///< scratch register RAX
|
||||
|
||||
// Context saved by CPU
|
||||
uintptr_t error_code; ///< Error Code
|
||||
uintptr_t ip; ///< Instruction Pointer (at interrupt)
|
||||
uintptr_t cs : 16; ///< Code segment (in case of a ring switch it is the segment of the user mode)
|
||||
uintptr_t : 0; ///< Alignment (due to 16 bit code segment)
|
||||
uintptr_t flags; ///< Status flags register
|
||||
uintptr_t sp; ///< Stack pointer (at interrupt)
|
||||
uintptr_t ss : 16; ///< Stack segment (in case of a ring switch it is the segment of the user mode)
|
||||
uintptr_t : 0; ///< Alignment (due to 16 bit stack segment)
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief High-Level Interrupt Handling.
|
||||
* \ingroup interrupts
|
||||
*
|
||||
* Main interrupt handling routine of the system.
|
||||
* This function is called by the corresponding `interrupt_entry_VECTOR`
|
||||
* function (located in `interrupt/handler.asm`) with disabled interrupts.
|
||||
*
|
||||
* \param vector number of the interrupt
|
||||
* \param context Pointer to interrupt context (on stack).
|
||||
*
|
||||
*/
|
||||
extern "C" void interrupt_handler(Core::Interrupt::Vector vector, InterruptContext *context);
|
||||
|
||||
/*! \brief Array of function pointer to the default low-level interrupt handlers
|
||||
*
|
||||
* The index corresponds to the vectors entry function, e.g. `interrupt_entry[6]`
|
||||
* points to `interrupt_entry_6`, handling the trap for
|
||||
* \ref Core::Interrupt::INVALID_OPCODE "invalid opcode".
|
||||
*
|
||||
* The entry functions and this array are defined in assembly in
|
||||
* `interrupt/handler.asm` and used in \ref kernel_init() to
|
||||
* initialize the \ref IDT "Interrupt Descriptor Table (IDT)".
|
||||
*/
|
||||
extern "C" void * const interrupt_entry[];
|
|
@ -0,0 +1,139 @@
|
|||
#include "machine/acpi.h"
|
||||
#include "debug/output.h"
|
||||
|
||||
namespace ACPI {
|
||||
|
||||
static RSDP* rsdp = 0;
|
||||
static RSDT* rsdt = 0;
|
||||
static XSDT* xsdt = 0;
|
||||
|
||||
const char * RSDP_SIGNATURE = "RSD PTR ";
|
||||
|
||||
static int checksum(const void *pos, unsigned len) {
|
||||
const uint8_t *mem = reinterpret_cast<const uint8_t*>(pos);
|
||||
uint8_t sum = 0;
|
||||
for (unsigned i = 0; i < len; i++) {
|
||||
sum += mem[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static const RSDP* findRSDP(const void *pos, unsigned len) {
|
||||
/* since the RSDP is 16-Byte aligned, we only need to check
|
||||
every second 64bit memory block */
|
||||
for (unsigned block = 0; block < len / 8; block += 2) {
|
||||
const uint64_t *mem = reinterpret_cast<const uint64_t*>(pos) + block;
|
||||
if (*mem == *reinterpret_cast<const uint64_t *>(RSDP_SIGNATURE)) {
|
||||
const RSDP *rsdp = reinterpret_cast<const RSDP*>(mem);
|
||||
/* ACPI Specification Revision 4.0a: 5.2.5.3*/
|
||||
if ((rsdp->revision == 0 && checksum(mem, 20) == 0) ||
|
||||
(rsdp->length > 20 && checksum(mem, rsdp->length) == 0)) {
|
||||
return rsdp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool init() {
|
||||
/* ACPI Specification Revision 4.0a:
|
||||
* 5.2.5.1 Finding the RSDP on IA-PC Systems
|
||||
* OSPM finds the Root System Description Pointer (RSDP) structure by
|
||||
* searching physical memory ranges on 16-byte boundaries for a valid
|
||||
* Root System Description Pointer structure signature and checksum
|
||||
* match as follows:
|
||||
* * The first 1 KB of the Extended BIOS Data Area (EBDA). For EISA or
|
||||
* MCA systems, the EBDA can be found in the two-byte location 40:0Eh
|
||||
* on the BIOS data area.
|
||||
* * The BIOS read-only memory space between 0E0000h and 0FFFFFh.
|
||||
*/
|
||||
volatile uintptr_t ebda_base_address = 0x40e;
|
||||
const uintptr_t ebda = static_cast<uintptr_t>(*reinterpret_cast<uint32_t *>(ebda_base_address));
|
||||
const RSDP *rsdp = findRSDP(reinterpret_cast<void*>(ebda), 1024);
|
||||
if (rsdp == nullptr) {
|
||||
rsdp = findRSDP(reinterpret_cast<void*>(0xe0000), 0xfffff-0xe0000);
|
||||
}
|
||||
|
||||
if (rsdp == nullptr) {
|
||||
DBG_VERBOSE << "No ACPI!" << endl;
|
||||
return false;
|
||||
}
|
||||
rsdt = reinterpret_cast<RSDT*>(static_cast<uintptr_t>(rsdp->rsdtaddress));
|
||||
|
||||
/* If the XSDT is present we must use it; see:
|
||||
* ACPI Specification Revision 4.0a:
|
||||
* "An ACPI-compatible OS must use the XSDT if present."
|
||||
*/
|
||||
if (rsdp->revision != 0 && rsdp->length >= 36) {
|
||||
xsdt = reinterpret_cast<XSDT*>(rsdp->xsdtaddress);
|
||||
}
|
||||
DBG_VERBOSE << "ACPI revision " << rsdp->revision << endl;
|
||||
for (unsigned i = 0; i != count(); ++i) {
|
||||
SDTH *sdt = get(i);
|
||||
if (sdt != nullptr) {
|
||||
char *c = reinterpret_cast<char*>(&sdt->signature);
|
||||
DBG_VERBOSE << i << ". " << c[0] << c[1] << c[2] << c[3] << " @ " << reinterpret_cast<void*>(sdt) << endl;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned count() {
|
||||
if (xsdt != nullptr) {
|
||||
return (xsdt->length-36)/8;
|
||||
} else if (rsdt != nullptr) {
|
||||
return (rsdt->length-36)/4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SDTH *get(unsigned num) {
|
||||
if (xsdt != nullptr) {
|
||||
SDTH *entry = reinterpret_cast<SDTH*>(xsdt->entries[num]);
|
||||
if (checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
} else if (rsdt != nullptr) {
|
||||
SDTH *entry = reinterpret_cast<SDTH*>(static_cast<uintptr_t>(rsdt->entries[num]));
|
||||
if (checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDTH *get(char a, char b, char c, char d) {
|
||||
union {
|
||||
char signature[4];
|
||||
uint32_t value;
|
||||
};
|
||||
signature[0] = a;
|
||||
signature[1] = b;
|
||||
signature[2] = c;
|
||||
signature[3] = d;
|
||||
|
||||
if (xsdt != nullptr) {
|
||||
for (unsigned i = 0; i < count(); i++) {
|
||||
SDTH *entry = reinterpret_cast<SDTH*>(xsdt->entries[i]);
|
||||
if (entry->signature == value && checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
} else if (rsdt != nullptr) {
|
||||
for (unsigned i = 0; i < count(); i++) {
|
||||
SDTH *entry = reinterpret_cast<SDTH*>(static_cast<uintptr_t>(rsdt->entries[i]));
|
||||
if (entry->signature == value && checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int revision() {
|
||||
return rsdp != nullptr ? rsdp->revision : -1;
|
||||
}
|
||||
|
||||
} // namespace ACPI
|
|
@ -0,0 +1,257 @@
|
|||
/*! \file
|
||||
* \brief Structs and methods related to the \ref ACPI "Advanced Configuration and Power Interface (ACPI)"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Abstracts the ACPI standard that provides interfaces for hardware detection, device configuration,
|
||||
* and energy management.
|
||||
* \ingroup io
|
||||
*
|
||||
* ACPI is the successor to APM (Advanced Power Management), aiming to give the operating system more control
|
||||
* over the hardware. This extended control, for instance, enables the operating system to assign a particular amount
|
||||
* of energy to every device (e.g., by disabling a device or changing to standby mode).
|
||||
* For this purpose, BIOS and chipset provide a set of tables that describe the system and its components and provide
|
||||
* routines the OS can call.
|
||||
* These tables contain details about the system, such as the number of CPU cores and the LAPIC/IOAPIC, which are
|
||||
* determined during system boot.
|
||||
*/
|
||||
|
||||
namespace ACPI {
|
||||
|
||||
/*! \brief Root System Description Pointer (RSDP)
|
||||
*
|
||||
* The first step to using ACPI is finding the RSDP that is used to find the RSDT / XSDT, which themselves
|
||||
* contain pointers to even more tables.
|
||||
*
|
||||
* On UEFI systems, the RSDP can be found in the EFI_SYSTEM_TABLE; for non-UEFI systems we have to search for the
|
||||
* signature 'RSD PTR ' in the EBDA (Extended Bios Data Area) or in the memory area up to `FFFFFh`.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.5.3; Root System Description Pointer (RSDP) Structure](acpi.pdf#page=161)
|
||||
*/
|
||||
|
||||
struct RSDP {
|
||||
char signature[8]; /* must exactly be equal to 'RSD PTR ' */
|
||||
uint8_t checksum;
|
||||
char oemid[6];
|
||||
uint8_t revision; /* specifies the ACPI version */
|
||||
uint32_t rsdtaddress; /* physical address of the RSDT */
|
||||
uint32_t length;
|
||||
uint64_t xsdtaddress; /* physical address of the XSDT */
|
||||
uint8_t extended_checksum;
|
||||
uint8_t reserved[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief System Description Table Header (SDTH)
|
||||
*
|
||||
* All System Description Tables (e.g., the RSDT) contain the same entries at the very beginning of
|
||||
* the structure, which are abstracted in the SDTH.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.6; System Description Table Header](acpi.pdf#page=162)
|
||||
*/
|
||||
struct SDTH {
|
||||
uint32_t signature; /* table id */
|
||||
uint32_t length;
|
||||
uint8_t revision;
|
||||
uint8_t checksum;
|
||||
char oemid[6];
|
||||
char oem_table_id[8];
|
||||
uint32_t oem_revision;
|
||||
uint32_t creator_id;
|
||||
uint32_t creator_revision;
|
||||
|
||||
/* \brief Helper method
|
||||
* \return Pointer to the end of the table
|
||||
*/
|
||||
void *end() {
|
||||
return reinterpret_cast<uint8_t*>(this)+length;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Root System Description Table (RSDT)
|
||||
*
|
||||
* The RSDT can be found in the RSDP. The RSDT contains physical addresses of all other System Description Tables,
|
||||
* for example the MADT.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.7; Root System Description Table (RSDT)](acpi.pdf#page=167)
|
||||
*/
|
||||
|
||||
struct RSDT : SDTH {
|
||||
uint32_t entries[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Extended System Description Table (XSDT)
|
||||
*
|
||||
* Like RSDT, but contains 64-bit instead of 32-bit addresses.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.8; Extended System Description Table (XSDT)](acpi.pdf#page=168)
|
||||
*/
|
||||
|
||||
struct XSDT : SDTH {
|
||||
uint64_t entries[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Helper structure
|
||||
*
|
||||
* Is used for accessing the substructures present in SRAT / MADT.
|
||||
*
|
||||
*/
|
||||
struct SubHeader {
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
|
||||
/* Method to traverse multiple substructures */
|
||||
SubHeader *next() {
|
||||
return reinterpret_cast<SubHeader*>(reinterpret_cast<uint8_t*>(this)+length);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Multiple APIC Description Table (MADT)
|
||||
*
|
||||
* Describes all interrupt controllers present within the system. Is used to obtain the IDs of the APICs, along with
|
||||
* the number of available processor cores.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12; Multiple APIC Description Table (MADT)](acpi.pdf#page=193)
|
||||
*/
|
||||
struct MADT : SDTH {
|
||||
uint32_t local_apic_address;
|
||||
uint32_t flags_pcat_compat:1,
|
||||
flags_reserved:31;
|
||||
|
||||
/* method to access the first subheader */
|
||||
SubHeader *first() {
|
||||
return reinterpret_cast<SubHeader*>(reinterpret_cast<uint8_t*>(this)+sizeof(MADT));
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
enum class AddressSpace : uint8_t {
|
||||
MEMORY = 0x0,
|
||||
IO = 0x1,
|
||||
};
|
||||
|
||||
/*! \brief ACPI address format
|
||||
*
|
||||
* The ACPI standard defines its own address format that is able to handle addresses both in memory address space,
|
||||
* as well as IO-port address space.
|
||||
*/
|
||||
struct Address {
|
||||
AddressSpace address_space;
|
||||
uint8_t register_bit_width;
|
||||
uint8_t register_bit_offset;
|
||||
uint8_t reserved;
|
||||
uint64_t address;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Multiple APIC Definition Structure
|
||||
namespace MADS {
|
||||
enum Type {
|
||||
Type_LAPIC = 0,
|
||||
Type_IOAPIC = 1,
|
||||
Type_Interrupt_Source_Override = 2,
|
||||
Type_LAPIC_Address_Override = 5,
|
||||
};
|
||||
|
||||
/*! \brief Processor Local APIC (LAPIC) Structure
|
||||
*
|
||||
* Represents a physical processor along with its local interrupt controller.
|
||||
* The MADT contains a LAPIC structure for every processor available in the system.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.2; Processor Local APIC Structure](acpi.pdf#page=195)
|
||||
*/
|
||||
struct LAPIC : SubHeader {
|
||||
uint8_t acpi_processor_id;
|
||||
uint8_t apic_id;
|
||||
uint32_t flags_enabled : 1,
|
||||
flags_reserved : 31; /* must be 0 */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief I/O APIC Structure
|
||||
*
|
||||
* Represents an I/O-APIC.
|
||||
* The MADT contains an IOAPIC structure for every I/O APIC present in the system.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.3; I/O APIC Structure](acpi.pdf#page=196)
|
||||
*/
|
||||
|
||||
struct IOAPIC : SubHeader {
|
||||
uint8_t ioapic_id;
|
||||
uint8_t reserved;
|
||||
uint32_t ioapic_address;
|
||||
uint32_t global_system_interrupt_base;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Interrupt Source Override Structure
|
||||
*
|
||||
* Is required to describe differences between the IA-PC standard interrupt definition and the actual
|
||||
* hardware implementation.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.5; Interrupt Source Override Structure](acpi.pdf#page=197)
|
||||
*/
|
||||
struct Interrupt_Source_Override : SubHeader {
|
||||
uint8_t bus;
|
||||
uint8_t source;
|
||||
uint32_t global_system_interrupt;
|
||||
uint16_t flags_polarity : 2,
|
||||
flags_trigger_mode : 2,
|
||||
flags_reserved : 12; /* must be 0 */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Local APIC Address Override Structure
|
||||
*
|
||||
* Support for 64-bit systems is achieved by replacing the 32-bit physical LAPIC address stored in the MADT
|
||||
* with the corresponding 64-bit address.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.8; Local APIC Address Override Structure](acpi.pdf#page=199)
|
||||
*/
|
||||
|
||||
struct LAPIC_Address_Override : SubHeader {
|
||||
uint16_t reserved;
|
||||
union {
|
||||
uint64_t lapic_address;
|
||||
struct {
|
||||
uint32_t lapic_address_low;
|
||||
uint32_t lapic_address_high;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
} __attribute__((packed));
|
||||
|
||||
} // namespace MADS
|
||||
|
||||
/*! \brief Initialize the ACPI description table
|
||||
*
|
||||
* Searches physical memory ranges o 16-byte boundaries for a valid Root System Description Pointer (RSDP)
|
||||
* structure signature and checksum.
|
||||
* If present, the superseding Extended System Description Table (XSDT) is used.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.5 Root System Description Pointer (RSDP)](acpi.pdf#page=160)
|
||||
* \see [ACPI-Specification 5.2.8 Extended System Description Table (XSDT)](acpi.pdf#page=168)
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/*! \brief Number of entries in the description table
|
||||
*/
|
||||
unsigned count();
|
||||
|
||||
/*! \brief Get entry of description table by index
|
||||
*
|
||||
* \param num index in description table
|
||||
* \return Pointer to corresponding entry or `nullptr` if not available
|
||||
*/
|
||||
SDTH *get(unsigned num);
|
||||
|
||||
/*! \brief Get entry of description table by four character identifier
|
||||
*
|
||||
* \param a first character of identifier
|
||||
* \param b second character of identifier
|
||||
* \param c third character of identifier
|
||||
* \param d forth and last character of identifier
|
||||
* \return Pointer to corresponding entry or `nullptr` if not available
|
||||
*/
|
||||
SDTH *get(char a, char b, char c, char d);
|
||||
|
||||
/*! \brief Retrieve the revision from the Root System Description Pointer (RSDP)
|
||||
*/
|
||||
int revision();
|
||||
|
||||
} // namespace ACPI
|
|
@ -0,0 +1,143 @@
|
|||
#include "machine/apic.h"
|
||||
#include "machine/acpi.h"
|
||||
#include "machine/core.h"
|
||||
#include "machine/cmos.h"
|
||||
#include "machine/ioport.h"
|
||||
#include "machine/lapic.h"
|
||||
#include "machine/lapic_registers.h"
|
||||
#include "utils/string.h"
|
||||
#include "debug/assert.h"
|
||||
#include "debug/output.h"
|
||||
|
||||
namespace APIC {
|
||||
|
||||
static struct {
|
||||
uint32_t id;
|
||||
uintptr_t address;
|
||||
uint32_t interrupt_base;
|
||||
} ioapic;
|
||||
|
||||
static uint8_t slot_map[16];
|
||||
|
||||
static uint8_t lapic_id[Core::MAX];
|
||||
static unsigned lapics = 0;
|
||||
|
||||
bool init() {
|
||||
// get Multiple APIC Definition Table (MADT) from ACPI
|
||||
ACPI::MADT *madt = static_cast<ACPI::MADT*>(ACPI::get('A', 'P', 'I', 'C'));
|
||||
if(madt == 0) {
|
||||
DBG_VERBOSE << "ERROR: no MADT found in ACPI" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// read the local APIC address
|
||||
LAPIC::base_address = static_cast<uintptr_t>(madt->local_apic_address);
|
||||
DBG_VERBOSE << "LAPIC Address "
|
||||
<< reinterpret_cast<void*>(static_cast<uintptr_t>(madt->local_apic_address)) << endl;
|
||||
|
||||
// PC/AT compatibility mode
|
||||
if (madt->flags_pcat_compat != 0) {
|
||||
// The APIC operating mode is set to compatible PIC mode - we have to change it.
|
||||
DBG_VERBOSE << "PIC comp mode, disabling PICs." << endl;
|
||||
|
||||
// Select Interrupt Mode Control Register (IMCR)
|
||||
// (this register will only exist if hardware supports the PIC mode)
|
||||
IOPort reg(0x22);
|
||||
reg.outb(0x70);
|
||||
// disable PIC mode, use APIC
|
||||
IOPort imcr(0x23);
|
||||
imcr.outb(0x01);
|
||||
}
|
||||
|
||||
// Set default mapping of external interrupt slots (might be overwritten below)
|
||||
for (unsigned i = 0; i < sizeof(slot_map)/sizeof(slot_map[0]); i++) {
|
||||
slot_map[i] = i;
|
||||
}
|
||||
|
||||
// Initialize invalid lapic_ids
|
||||
for (unsigned i = 0; i < Core::MAX; i++) {
|
||||
lapic_id[i] = INVALID_ID;
|
||||
}
|
||||
|
||||
// reset numbers, store apic data into arrays
|
||||
for (ACPI::SubHeader *mads = madt->first(); mads < madt->end(); mads = mads->next()) {
|
||||
switch(mads->type) {
|
||||
case ACPI::MADS::Type_LAPIC:
|
||||
{
|
||||
ACPI::MADS::LAPIC* mads_lapic = static_cast<ACPI::MADS::LAPIC*>(mads);
|
||||
if (mads_lapic->flags_enabled == 0) {
|
||||
DBG_VERBOSE << "Detected disabled LAPIC with ID " << static_cast<unsigned>(mads_lapic->apic_id) << endl;
|
||||
} else if (lapics >= Core::MAX) {
|
||||
DBG_VERBOSE << "Got more LAPICs than Core::MAX" << endl;
|
||||
} else if (mads_lapic->apic_id == INVALID_ID) {
|
||||
DBG_VERBOSE << "Got invalid APIC ID" << endl;
|
||||
} else {
|
||||
DBG_VERBOSE << "Detected LAPIC with ID " << static_cast<unsigned>(mads_lapic->apic_id) << endl;
|
||||
lapic_id[lapics++] = mads_lapic->apic_id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACPI::MADS::Type_IOAPIC:
|
||||
{
|
||||
ACPI::MADS::IOAPIC* mads_ioapic = static_cast<ACPI::MADS::IOAPIC*>(mads);
|
||||
DBG_VERBOSE << "Detected IO APIC with ID " << static_cast<unsigned>(mads_ioapic->ioapic_id) << " / Base "
|
||||
<< reinterpret_cast<void*>(static_cast<uintptr_t>(mads_ioapic->global_system_interrupt_base))
|
||||
<< endl;
|
||||
if (mads_ioapic->global_system_interrupt_base > 23) {
|
||||
DBG_VERBOSE << "Ignoring IOAPIC since we currently only support one." << endl;
|
||||
} else {
|
||||
ioapic.id = mads_ioapic->ioapic_id;
|
||||
ioapic.address = static_cast<uintptr_t>(mads_ioapic->ioapic_address);
|
||||
ioapic.interrupt_base = mads_ioapic->global_system_interrupt_base;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACPI::MADS::Type_Interrupt_Source_Override:
|
||||
{
|
||||
ACPI::MADS::Interrupt_Source_Override* mads_iso = static_cast<ACPI::MADS::Interrupt_Source_Override*>(mads);
|
||||
if (mads_iso->bus == 0) {
|
||||
DBG_VERBOSE << "Overriding Interrupt Source " << static_cast<unsigned>(mads_iso->source)
|
||||
<< " with " << mads_iso->global_system_interrupt << endl;
|
||||
if (mads_iso->source < sizeof(slot_map)/sizeof(slot_map[0])) {
|
||||
slot_map[mads_iso->source] = mads_iso->global_system_interrupt;
|
||||
}
|
||||
} else {
|
||||
DBG_VERBOSE << "Override for bus " << mads_iso->bus << " != ISA. Does not conform to ACPI." << endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACPI::MADS::Type_LAPIC_Address_Override:
|
||||
{
|
||||
ACPI::MADS::LAPIC_Address_Override* mads_lao = static_cast<ACPI::MADS::LAPIC_Address_Override*>(mads);
|
||||
LAPIC::base_address = static_cast<uintptr_t>(mads_lao->lapic_address_low);
|
||||
DBG_VERBOSE << "Overriding LAPIC address with "
|
||||
<< reinterpret_cast<void*>(static_cast<uintptr_t>(mads_lao->lapic_address)) << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t getIOAPICSlot(APIC::Device device) {
|
||||
return slot_map[device];
|
||||
}
|
||||
|
||||
uintptr_t getIOAPICAddress() {
|
||||
return ioapic.address;
|
||||
}
|
||||
|
||||
uint8_t getIOAPICID() {
|
||||
return ioapic.id;
|
||||
}
|
||||
|
||||
uint8_t getLogicalAPICID(uint8_t core) {
|
||||
return core < Core::MAX ? (1 << core) : 0;
|
||||
}
|
||||
|
||||
uint8_t getLAPICID(uint8_t core) {
|
||||
assert(core < Core::MAX);
|
||||
return lapic_id[core];
|
||||
}
|
||||
|
||||
} // namespace APIC
|
|
@ -0,0 +1,80 @@
|
|||
/*! \file
|
||||
* \brief Gather system information from the \ref ACPI about the \ref APIC "Advanced Programmable Interrupt Controller (APIC)"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Information about the (extended) Advanced Programmable Interrupt Controller
|
||||
*/
|
||||
namespace APIC {
|
||||
/*! \brief Historic order of interrupt lines (PIC)
|
||||
*/
|
||||
enum Device {
|
||||
TIMER = 0, ///< Programmable Interrupt Timer (\ref PIT)
|
||||
KEYBOARD = 1, ///< Keyboard
|
||||
COM1 = 4, ///< First serial interface
|
||||
COM2 = 3, ///< Second serial interface
|
||||
COM3 = 4, ///< Third serial interface (shared with COM1)
|
||||
COM4 = 3, ///< Forth serial interface (shared with COM2)
|
||||
FLOPPY = 6, ///< Floppy device
|
||||
LPT1 = 7, ///< Printer
|
||||
REALTIMECLOCK = 8, ///< Real time clock
|
||||
PS2MOUSE = 12, ///< Mouse
|
||||
IDE1 = 14, ///< First hard disk
|
||||
IDE2 = 15 ///< Second hard disk
|
||||
};
|
||||
|
||||
/*! \brief Invalid APIC ID
|
||||
*
|
||||
* The highest address is reserved according to xAPIC specification
|
||||
*/
|
||||
const uint8_t INVALID_ID = 0xff;
|
||||
|
||||
/*! \brief Executes system detection
|
||||
*
|
||||
* Searches and evaluates the APIC entries in the \ref ACPI table.
|
||||
* This function recognizes a possibly existing multicore system.
|
||||
* After successful detection, the number of available CPUs (which is equal
|
||||
* to the number of \ref LAPIC "local APICs") ) can be queried
|
||||
* using the method \ref Core::count().
|
||||
*
|
||||
* \note Called by \ref kernel_init() on BSP
|
||||
*
|
||||
* \return `true` if detection of the APIC entries was successful
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/*! \brief Queries the physical I/O-APIC address determined during system boot
|
||||
*
|
||||
* \return Base address of the (first & only supported) I/O APIC
|
||||
*/
|
||||
uintptr_t getIOAPICAddress();
|
||||
|
||||
/*! \brief Queries of ID of the I/O-APIC determined during system boot
|
||||
*
|
||||
* \return Identification of the (first & only supported) I/O APIC
|
||||
*/
|
||||
uint8_t getIOAPICID();
|
||||
|
||||
/*! \brief Returns the pin number the \p device is connected to.
|
||||
*/
|
||||
uint8_t getIOAPICSlot(APIC::Device device);
|
||||
|
||||
/*! \brief Returns the logical ID of the Local APIC passed for \a core.
|
||||
*
|
||||
* The LAPIC's logical ID is set (by StuBS) during boot such that exactly one bit is set per CPU core.
|
||||
* For core 0, bit 0 is set in its ID, while core 1 has bit 1 set, etc.
|
||||
*
|
||||
* \param core The queried CPU core
|
||||
*/
|
||||
uint8_t getLogicalAPICID(uint8_t core);
|
||||
|
||||
/*! \brief Get the Local APIC ID of a CPU
|
||||
* \param core Query CPU core number
|
||||
* \return LAPIC ID of CPU or INVALID_ID if invalid CPU ID
|
||||
*/
|
||||
uint8_t getLAPICID(uint8_t core);
|
||||
|
||||
} // namespace APIC
|
|
@ -0,0 +1,61 @@
|
|||
#include "machine/cmos.h"
|
||||
|
||||
#include "machine/core.h"
|
||||
#include "machine/ioport.h"
|
||||
|
||||
namespace CMOS {
|
||||
static IOPort address(0x70);
|
||||
static IOPort data(0x71);
|
||||
|
||||
namespace NMI {
|
||||
static const uint8_t mask = 0x80;
|
||||
// Cache NMI to speed things up
|
||||
static bool disabled = false;
|
||||
|
||||
void enable() {
|
||||
bool status = Core::Interrupt::disable();
|
||||
uint8_t value = address.inb();
|
||||
value &= ~mask;
|
||||
address.outb(value);
|
||||
Core::Interrupt::restore(status);
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
bool status = Core::Interrupt::disable();
|
||||
uint8_t value = address.inb();
|
||||
value |= mask;
|
||||
address.outb(value);
|
||||
Core::Interrupt::restore(status);
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
disabled = (address.inb() & mask) != 0;
|
||||
return !disabled;
|
||||
}
|
||||
} // namespace NMI
|
||||
|
||||
static void setAddress(enum Register reg) {
|
||||
uint8_t value = reg;
|
||||
// The highest bit controls the Non Maskable Interrupt
|
||||
// so we don't want to accidentally change it.
|
||||
if (NMI::disabled) {
|
||||
value |= NMI::mask;
|
||||
} else {
|
||||
value &= ~NMI::mask;
|
||||
}
|
||||
address.outb(value);
|
||||
}
|
||||
|
||||
uint8_t read(enum Register reg) {
|
||||
setAddress(reg);
|
||||
return data.inb();
|
||||
}
|
||||
|
||||
void write(enum Register reg, uint8_t value) {
|
||||
setAddress(reg);
|
||||
data.outb(value);
|
||||
}
|
||||
|
||||
} // namespace CMOS
|
|
@ -0,0 +1,41 @@
|
|||
/*! \file
|
||||
* \brief Controlling the \ref CMOS "complementary metal oxide semiconductor (CMOS)"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief CMOS
|
||||
* \ingroup CMOS
|
||||
*/
|
||||
namespace CMOS {
|
||||
|
||||
enum Register {
|
||||
REG_SECOND = 0x0, ///< RTC
|
||||
REG_ALARM_SECOND = 0x1, ///< RTC
|
||||
REG_MINUTE = 0x2, ///< RTC
|
||||
REG_ALARM_MINUTE = 0x3, ///< RTC
|
||||
REG_HOUR = 0x4, ///< RTC
|
||||
REG_ALARM_HOUR = 0x5, ///< RTC
|
||||
REG_WEEKDAY = 0x6, ///< RTC
|
||||
REG_DAYOFMONTH = 0x7, ///< RTC
|
||||
REG_MONTH = 0x8, ///< RTC
|
||||
REG_YEAR = 0x9, ///< RTC
|
||||
REG_STATUS_A = 0xa, ///< RTC
|
||||
REG_STATUS_B = 0xb, ///< RTC
|
||||
REG_STATUS_C = 0xc, ///< RTC
|
||||
REG_STATUS_D = 0xd, ///< RTC
|
||||
REG_STATUS_DIAGNOSE = 0xe,
|
||||
REG_STATUS_SHUTDOWN = 0xf
|
||||
};
|
||||
|
||||
uint8_t read(enum Register reg);
|
||||
void write(enum Register reg, uint8_t value);
|
||||
|
||||
namespace NMI {
|
||||
void enable();
|
||||
void disable();
|
||||
bool isEnabled();
|
||||
} // namespace NMI
|
||||
} // namespace CMOS
|
|
@ -0,0 +1,75 @@
|
|||
#include "machine/core.h"
|
||||
#include "machine/apic.h"
|
||||
#include "machine/lapic.h"
|
||||
|
||||
/*! \brief Initial size of CPU core stacks
|
||||
*
|
||||
* Used during startup in `boot/startup.asm`
|
||||
*/
|
||||
extern "C" const unsigned long CPU_CORE_STACK_SIZE = 4096;
|
||||
|
||||
/*! \brief Reserved memory for CPU core stacks
|
||||
*/
|
||||
alignas(16) static unsigned char cpu_core_stack[Core::MAX * CPU_CORE_STACK_SIZE];
|
||||
|
||||
/*! \brief Pointer to stack memory
|
||||
*
|
||||
* Incremented during startup of each core (bootstrap and application processors) in `boot/startup.asm`
|
||||
*/
|
||||
unsigned char * cpu_core_stack_pointer = cpu_core_stack;
|
||||
|
||||
namespace Core {
|
||||
|
||||
static unsigned cores = 0; ///< Number of available CPU cores
|
||||
static unsigned core_id[255]; ///< Lookup table for CPU core IDs with LAPIC ID as index
|
||||
|
||||
static unsigned online_cores = 0; ///< Number of currently online CPU cores
|
||||
static bool online_core[Core::MAX]; ///< Lookup table for online CPU cores with CPU core ID as index
|
||||
|
||||
void init() {
|
||||
// Increment number of online CPU cores
|
||||
if (__atomic_fetch_add(&online_cores, 1, __ATOMIC_RELAXED) == 0) {
|
||||
// Fill Lookup table
|
||||
for (unsigned i = 0; i < Core::MAX; i++) {
|
||||
uint8_t lapic_id = APIC::getLAPICID(i);
|
||||
if (lapic_id < APIC::INVALID_ID) { // ignore invalid LAPICs
|
||||
core_id[lapic_id] = i;
|
||||
cores++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get CPU ID
|
||||
uint8_t cpu = getID();
|
||||
|
||||
// initialize local APIC with logical APIC ID
|
||||
LAPIC::init(APIC::getLogicalAPICID(cpu));
|
||||
|
||||
// set current CPU online
|
||||
online_core[cpu] = true;
|
||||
|
||||
}
|
||||
|
||||
void exit() {
|
||||
// CPU core offline
|
||||
online_core[getID()] = false;
|
||||
__atomic_fetch_sub(&online_cores, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
unsigned getID() {
|
||||
return core_id[LAPIC::getID()];
|
||||
}
|
||||
|
||||
unsigned count() {
|
||||
return cores;
|
||||
}
|
||||
|
||||
unsigned countOnline() {
|
||||
return online_cores;
|
||||
}
|
||||
|
||||
bool isOnline(uint8_t core_id) {
|
||||
return core_id > Core::MAX ? false : online_core[core_id];
|
||||
}
|
||||
|
||||
} // namespace Core
|
|
@ -0,0 +1,112 @@
|
|||
/*! \file
|
||||
* \brief Access to internals of a CPU \ref Core
|
||||
*/
|
||||
|
||||
/*! \defgroup sync CPU Synchronization
|
||||
*
|
||||
* The synchronization module houses functions useful for orchestrating multiple processors and their activities.
|
||||
* Synchronisation, in this case, means handling the resource contention between multiple participants, running on
|
||||
* either the same or different cores.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "machine/core_cr.h"
|
||||
#include "machine/core_interrupt.h"
|
||||
#include "machine/core_msr.h"
|
||||
|
||||
/*! \brief Implements an abstraction for CPU internals.
|
||||
*
|
||||
* These internals include functions to \ref Core::Interrupt "allow or deny interrupts",
|
||||
* access \ref Core::CR "control registers".
|
||||
*/
|
||||
namespace Core {
|
||||
|
||||
/*! \brief Maximum number of supported CPUs
|
||||
*/
|
||||
const unsigned MAX = 8;
|
||||
|
||||
/*! \brief Get the ID of the current CPU core
|
||||
* using \ref LAPIC::getID() with an internal lookup table.
|
||||
*
|
||||
* \return ID of current Core (a number between 0 and \ref Core::MAX)
|
||||
*/
|
||||
unsigned getID();
|
||||
|
||||
/*! \brief Initialize this CPU core
|
||||
*
|
||||
* Mark this core as *online* and setup the cores \ref LAPIC by assigning it a
|
||||
* unique \ref APIC::getLogicalAPICID() "logical APIC ID"
|
||||
*
|
||||
* \note Should only be called from \ref kernel_init() during startup.
|
||||
*/
|
||||
void init();
|
||||
|
||||
/*! \brief Deinitialize this CPU core
|
||||
*
|
||||
* Mark this Core as *offline*
|
||||
*
|
||||
* \note Should only be called from \ref kernel_init() after returning from `main()` or `main_ap()`.
|
||||
*/
|
||||
void exit();
|
||||
|
||||
/*! \brief Get number of available CPU cores
|
||||
*
|
||||
* \return total number of cores
|
||||
*/
|
||||
unsigned count();
|
||||
|
||||
/*! \brief Get number of successfully started (and currently active) CPU cores
|
||||
*
|
||||
* \return total number of online cores
|
||||
*/
|
||||
unsigned countOnline();
|
||||
|
||||
/*! \brief Check if CPU core is currently active
|
||||
* \param core_id ID of the CPU core
|
||||
* \return `true` if successfully started and is currently active
|
||||
*/
|
||||
bool isOnline(uint8_t core_id);
|
||||
|
||||
/*! \brief Gives the core a hint that it is executing a spinloop and should sleep "shortly"
|
||||
*
|
||||
* Improves the over-all performance when executing a spinloop by waiting a short moment reduce
|
||||
* the load on the memory.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. PAUSE - Spin Loop Hint](intel_manual_vol2.pdf#page=887)
|
||||
*/
|
||||
inline void pause() {
|
||||
asm volatile("pause\n\t" : : : "memory");
|
||||
}
|
||||
|
||||
/*! \brief Halt the CPU core until the next interrupt.
|
||||
*
|
||||
* Halts the current CPU core such that it will wake up on the next interrupt. Internally, this function first enables
|
||||
* the interrupts via `sti` and then halts the core using `hlt`. Halted cores can only be woken by interrupts.
|
||||
* The effect of `sti` is delayed by one instruction, making the sequence `sti hlt` atomic (if interrupts were
|
||||
* disabled previously).
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. STI - Set Interrupt Flag](intel_manual_vol2.pdf#page=1297)
|
||||
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
|
||||
*/
|
||||
inline void idle() {
|
||||
asm volatile("sti\n\t hlt\n\t" : : : "memory");
|
||||
}
|
||||
|
||||
/*! \brief Permanently halts the core.
|
||||
*
|
||||
* Permanently halts the current CPU core. Internally, this function first disables the interrupts via `cli` and
|
||||
* then halts the CPU core using `hlt`. As halted CPU cores can only be woken by interrupts, it is guaranteed that
|
||||
* this core will be halted until the next reboot. The execution of die never returns.
|
||||
* On multicore systems, only the executing CPU core will be halted permanently, other cores will continue execution.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 3. CLI - Clear Interrupt Flag](intel_manual_vol2.pdf#page=245)
|
||||
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
|
||||
*/
|
||||
[[noreturn]] inline void die() {
|
||||
while (true) {
|
||||
asm volatile("cli\n\t hlt\n\t" : : : "memory");
|
||||
}
|
||||
}
|
||||
} // namespace Core
|
|
@ -0,0 +1,81 @@
|
|||
/*! \file
|
||||
* \brief Access to \ref Core::CR "Control Register" of a \ref Core "CPU core"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace Core {
|
||||
/*! \brief Control Register 0
|
||||
*
|
||||
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=74)
|
||||
*/
|
||||
enum CR0 : uintptr_t {
|
||||
CR0_PE = 1U << 0, ///< Protected Mode enabled
|
||||
CR0_MP = 1U << 1, ///< Monitor co-processor
|
||||
CR0_EM = 1U << 2, ///< Emulation (no x87 floating-point unit present)
|
||||
CR0_TS = 1U << 3, ///< Task switched
|
||||
CR0_ET = 1U << 4, ///< Extension type
|
||||
CR0_NE = 1U << 5, ///< Numeric error
|
||||
CR0_WP = 1U << 16, ///< Write protect
|
||||
CR0_AM = 1U << 18, ///< Alignment mask
|
||||
CR0_NW = 1U << 29, ///< Not-write through caching
|
||||
CR0_CD = 1U << 30, ///< Cache disable
|
||||
CR0_PG = 1U << 31, ///< Paging
|
||||
};
|
||||
|
||||
/*! \brief Control Register 4
|
||||
*
|
||||
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=77)
|
||||
*/
|
||||
enum CR4 : uintptr_t {
|
||||
CR4_VME = 1U << 0, ///< Virtual 8086 Mode Extensions
|
||||
CR4_PVI = 1U << 1, ///< Protected-mode Virtual Interrupts
|
||||
CR4_TSD = 1U << 2, ///< Time Stamp Disable
|
||||
CR4_DE = 1U << 3, ///< Debugging Extensions
|
||||
CR4_PSE = 1U << 4, ///< Page Size Extension
|
||||
CR4_PAE = 1U << 5, ///< Physical Address Extension
|
||||
CR4_MCE = 1U << 6, ///< Machine Check Exception
|
||||
CR4_PGE = 1U << 7, ///< Page Global Enabled
|
||||
CR4_PCE = 1U << 8, ///< Performance-Monitoring Counter enable
|
||||
CR4_OSFXSR = 1U << 9, ///< Operating system support for FXSAVE and FXRSTOR instructions
|
||||
CR4_OSXMMEXCPT = 1U << 10, ///< Operating System Support for Unmasked SIMD Floating-Point Exceptions
|
||||
CR4_UMIP = 1U << 11, ///< User-Mode Instruction Prevention
|
||||
CR4_VMXE = 1U << 13, ///< Virtual Machine Extensions Enable
|
||||
CR4_SMXE = 1U << 14, ///< Safer Mode Extensions Enable
|
||||
CR4_FSGSBASE = 1U << 16, ///< Enables the instructions RDFSBASE, RDGSBASE, WRFSBASE, and WRGSBASE.
|
||||
CR4_PCIDE = 1U << 17, ///< PCID Enable
|
||||
CR4_OSXSAVE = 1U << 18, ///< XSAVE and Processor Extended States Enable
|
||||
CR4_SMEP = 1U << 20, ///< Supervisor Mode Execution Protection Enable
|
||||
CR4_SMAP = 1U << 21, ///< Supervisor Mode Access Prevention Enable
|
||||
CR4_PKE = 1U << 22, ///< Protection Key Enable
|
||||
};
|
||||
|
||||
/*! \brief Access to the Control Register
|
||||
*
|
||||
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=73)
|
||||
* \tparam id Control Register to access
|
||||
*/
|
||||
template<uint8_t id>
|
||||
class CR {
|
||||
public:
|
||||
/*! \brief Read the value of the current Control Register
|
||||
*
|
||||
* \return Value stored in the CR
|
||||
*/
|
||||
inline static uintptr_t read(void) {
|
||||
uintptr_t val;
|
||||
asm volatile("mov %%cr%c1, %0" : "=r"(val) : "n"(id) : "memory");
|
||||
return val;
|
||||
}
|
||||
|
||||
/*! \brief Write a value into the current Control Register
|
||||
*
|
||||
* \param value Value to write into the CR
|
||||
*/
|
||||
inline static void write(uintptr_t value) {
|
||||
asm volatile("mov %0, %%cr%c1" : : "r"(value), "n"(id) : "memory");
|
||||
}
|
||||
};
|
||||
} // namespace Core
|
|
@ -0,0 +1,130 @@
|
|||
/*! \file
|
||||
* \brief \ref Core::Interrupt "Interrupt control" and \ref Core::Interrupt::Vector "interrupt vector list"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace Core {
|
||||
/*! \brief Exception and Interrupt control
|
||||
*
|
||||
* \see [ISDMv3, Chapter 6 Interrupt and Exception Handling](intel_manual_vol3.pdf#page=185)
|
||||
*/
|
||||
namespace Interrupt {
|
||||
|
||||
/*! \brief Bit in `FLAGS` register corresponding to the current interrupt state
|
||||
*/
|
||||
const uintptr_t FLAG_ENABLE = 1 << 9;
|
||||
|
||||
/*! \brief List of used interrupt vectors.
|
||||
*
|
||||
* The exception vectors from `0` to `31` are reserved for traps, faults and aborts.
|
||||
* Their behavior is different for each exception, some push an *error code*,
|
||||
* some are not recoverable.
|
||||
*
|
||||
* The vectors from `32` to `255` are user defined interrupts.
|
||||
*
|
||||
* \see [ISDMv3, 6.15 Exception and Interrupt Reference](intel_manual_vol3.pdf#page=203)
|
||||
*/
|
||||
enum Vector {
|
||||
// Predefined Exceptions
|
||||
DIVISON_BY_ZERO = 0, ///< Divide-by-zero Error (at a `DIV`/`IDIV` instruction)
|
||||
DEBUG = 1, ///< Debug exception
|
||||
NON_MASKABLE_INTERRUPT = 2, ///< Non Maskable Interrupt
|
||||
BREAKPOINT = 3, ///< Breakpoint exception (used for debugging)
|
||||
OVERFLOW = 4, ///< Overflow exception (at `INTO` instruction)
|
||||
BOUND_RANGE_EXCEEDED = 5, ///< Bound Range Exceeded (at `BOUND` instruction)
|
||||
INVALID_OPCODE = 6, ///< Opcode at Instruction Pointer is invalid (you probably shouldn't be here)
|
||||
DEVICE_NOT_AVAILABLE = 7, ///< FPU "FPU/MMX/SSE" instruction but corresponding extension not activate
|
||||
DOUBLE_FAULT = 8, ///< Exception occurred while trying to call exception/interrupt handler
|
||||
// Coprocessor Segment Overrun (Legacy)
|
||||
INVALID_TSS = 10, ///< Invalid Task State Segment selector (see error code for index)
|
||||
SEGMENT_NOT_PRESENT = 11, ///< Segment not available (see error code for selector index)
|
||||
STACK_SEGMENT_FAULT = 12, ///< Stack segment not available or invalid (see error code for selector index)
|
||||
GENERAL_PROTECTION_FAULT = 13, ///< Operation not allowed (see error code for selector index)
|
||||
PAGE_FAULT = 14, ///< Operation on Page (r/w/x) not allowed for current privilege (error code + `cr2`)
|
||||
// reserved (15)
|
||||
FLOATING_POINT_EXCEPTION = 16, ///< x87 FPU error (at `WAIT`/`FWAIT`), accidentally \ref Core::CR0_NE set?
|
||||
ALIGNMENT_CHECK = 17, ///< Unaligned memory access in userspace (Exception activated by \ref Core::CR0_AM)
|
||||
MACHINE_CHECK = 18, ///< Model specific exception
|
||||
SIMD_FP_EXCEPTION = 19, ///< SSE/MMX error (if \ref Core::CR4_OSXMMEXCPT activated)
|
||||
// reserved (20 - 31)
|
||||
EXCEPTIONS = 32, ///< Number of exceptions
|
||||
|
||||
// Interrupts
|
||||
TIMER = 32, ///< Periodic CPU local \ref LAPIC::Timer interrupt
|
||||
KEYBOARD = 33, ///< Keyboard interrupt (key press / release)
|
||||
GDB = 35, ///< Inter-processor interrupt to stop other CPUs for debugging in \ref GDB
|
||||
ASSASSIN = 100, ///< Inter-processor interrupt to immediately stop threads running on other CPUs
|
||||
WAKEUP = 101, ///< Inter-processor interrupt to WakeUp sleeping CPUs
|
||||
|
||||
VECTORS = 256 ///< Number of interrupt vectors
|
||||
};
|
||||
|
||||
/*! \brief Check if interrupts are enabled on this CPU
|
||||
*
|
||||
* This is done by pushing the `FLAGS` register onto stack,
|
||||
* reading it into a register and checking the corresponding bit.
|
||||
*
|
||||
* \return `true` if enabled, `false` if disabled
|
||||
*/
|
||||
inline bool isEnabled() {
|
||||
uintptr_t out;
|
||||
asm volatile (
|
||||
"pushf\n\t"
|
||||
"pop %0\n\t"
|
||||
: "=r"(out)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
return (out & FLAG_ENABLE) != 0;
|
||||
}
|
||||
|
||||
/*! \brief Allow interrupts
|
||||
*
|
||||
* Enables interrupt handling by executing the instruction `sti`.
|
||||
* Since this instruction is delayed by one cycle, an subsequent `nop` is executed
|
||||
* (to ensure deterministic behavior, independent from the compiler generated code)
|
||||
*
|
||||
* A pending interrupt (i.e., those arriving while interrupts were disabled) will
|
||||
* be delivered after re-enabling interrupts.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. STI - Set Interrupt Flag](intel_manual_vol2.pdf#page=1297)
|
||||
*/
|
||||
inline void enable() {
|
||||
asm volatile("sti\n\t nop\n\t" : : : "memory");
|
||||
}
|
||||
|
||||
/*! \brief Forbid interrupts
|
||||
*
|
||||
* Prevents interrupt handling by executing the instruction `cli`.
|
||||
* Will return the previous interrupt state.
|
||||
* \return `true` if interrupts were enabled at the time of executing this function,
|
||||
* `false` if they were already disabled.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 3. CLI - Clear Interrupt Flag](intel_manual_vol2.pdf#page=245)
|
||||
*/
|
||||
inline bool disable() {
|
||||
bool enabled = isEnabled();
|
||||
asm volatile ("cli\n\t" : : : "memory");
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/*! \brief Restore interrupt
|
||||
*
|
||||
* Restore the interrupt state to the state prior to calling \ref disable() by using its return value.
|
||||
*
|
||||
* \note This function will never disable interrupts, even if val is false!
|
||||
* This function is designed to allow nested disabling and restoring of the interrupt state.
|
||||
*
|
||||
* \param val if set to `true`, interrupts will be enabled; nothing will happen on false.
|
||||
*/
|
||||
inline void restore(bool val) {
|
||||
if (val) {
|
||||
enable();
|
||||
}
|
||||
}
|
||||
} // namespace Interrupt
|
||||
} // namespace Core
|
|
@ -0,0 +1,91 @@
|
|||
/*! \file
|
||||
* \brief \ref Core::MSRs "Identifiers" for \ref Core::MSR "Model-Specific Register"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
/*! \brief Model-Specific Register Identifiers
|
||||
*
|
||||
* Selection of useful identifiers.
|
||||
*
|
||||
* \see [ISDMv4](intel_manual_vol4.pdf)
|
||||
*/
|
||||
enum MSRs : uint32_t {
|
||||
MSR_PLATFORM_INFO = 0xceU, ///< Platform information including bus frequency (Intel)
|
||||
MSR_TSC_DEADLINE = 0x6e0U, ///< Register for \ref LAPIC::Timer Deadline mode
|
||||
// Fast system calls
|
||||
MSR_EFER = 0xC0000080U, ///< Extended Feature Enable Register, \see Core::MSR_EFER
|
||||
MSR_STAR = 0xC0000081U, ///< eip (protected mode), ring 0 and 3 segment bases
|
||||
MSR_LSTAR = 0xC0000082U, ///< rip (long mode)
|
||||
MSR_SFMASK = 0xC0000084U, ///< lower 32 bit: flag mask, if bit is set corresponding rflag is cleared through syscall
|
||||
|
||||
// Core local variables
|
||||
MSR_FS_BASE = 0xC0000100U,
|
||||
MSR_GS_BASE = 0xC0000101U, ///< Current GS base pointer
|
||||
MSR_SHADOW_GS_BASE = 0xC0000102U, ///< Usually called `MSR_KERNEL_GS_BASE` but this is misleading
|
||||
};
|
||||
|
||||
/* \brief Important bits in Extended Feature Enable Register (EFER)
|
||||
*
|
||||
* \see [ISDMv3, 2.2.1 Extended Feature Enable Register](intel_manual_vol3.pdf#page=69)
|
||||
* \see [AAPMv2, 3.1.7 Extended Feature Enable Register](amd64_manual_vol2.pdf#page=107)
|
||||
*/
|
||||
enum MSR_EFER : uintptr_t {
|
||||
MSR_EFER_SCE = 1U << 0, ///< System Call Extensions
|
||||
MSR_EFER_LME = 1U << 8, ///< Long mode enable
|
||||
MSR_EFER_LMA = 1U << 10, ///< Long mode active
|
||||
MSR_EFER_NXE = 1U << 11, ///< No-Execute Enable
|
||||
MSR_EFER_SVME = 1U << 12, ///< Secure Virtual Machine Enable
|
||||
MSR_EFER_LMSLE = 1U << 13, ///< Long Mode Segment Limit Enable
|
||||
MSR_EFER_FFXSR = 1U << 14, ///< Fast `FXSAVE`/`FXRSTOR` instruction
|
||||
MSR_EFER_TCE = 1U << 15, ///< Translation Cache Extension
|
||||
};
|
||||
|
||||
/*! \brief Access to the Model-Specific Register (MSR)
|
||||
*
|
||||
* \see [ISDMv3, 9.4 Model-Specific Registers (MSRs)](intel_manual_vol3.pdf#page=319)
|
||||
* \see [ISDMv4](intel_manual_vol4.pdf)
|
||||
* \tparam id ID of the Model-Specific Register to access
|
||||
*/
|
||||
template<enum MSRs id>
|
||||
class MSR {
|
||||
/*! \brief Helper to access low and high bits of a 64 bit value
|
||||
* \internal
|
||||
*/
|
||||
union uint64_parts {
|
||||
struct {
|
||||
uint32_t low;
|
||||
uint32_t high;
|
||||
} __attribute__((packed));
|
||||
uint64_t value;
|
||||
|
||||
explicit uint64_parts(uint64_t value = 0) : value(value) {}
|
||||
};
|
||||
|
||||
public:
|
||||
/*! \brief Read the value of the current MSR
|
||||
*
|
||||
* \return Value stored in the MSR
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. RDMSR - Read from Model Specific Register](intel_manual_vol2.pdf#page=1186)
|
||||
*/
|
||||
static inline uint64_t read() {
|
||||
uint64_parts p;
|
||||
asm volatile ("rdmsr \n\t" : "=a"(p.low), "=d"(p.high) : "c"(id));
|
||||
return p.value;
|
||||
}
|
||||
|
||||
/*! \brief Write a value into the current MSR
|
||||
*
|
||||
* \param value Value to write into the MSR
|
||||
*
|
||||
* \see [ISDMv2, Chapter 5. WRMSR - Write to Model Specific Register](intel_manual_vol2.pdf#page=1912)
|
||||
*/
|
||||
static inline void write(uint64_t value) {
|
||||
uint64_parts p(value);
|
||||
asm volatile ("wrmsr \n\t" : : "c"(id), "a"(p.low), "d"(p.high));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Core
|
|
@ -0,0 +1,53 @@
|
|||
#include "machine/gdt.h"
|
||||
#include "machine/core.h"
|
||||
#include "debug/assert.h"
|
||||
#include "debug/output.h"
|
||||
|
||||
namespace GDT {
|
||||
|
||||
// The static 32-bit Global Descriptor Table (GDT)
|
||||
alignas(16) static SegmentDescriptor protected_mode[] = {
|
||||
// NULL descriptor
|
||||
{},
|
||||
|
||||
// Global code segment von 0-4GB
|
||||
{ /* base = */ 0x0,
|
||||
/* limit = */ 0xFFFFFFFF,
|
||||
/* code = */ true,
|
||||
/* ring = */ 0,
|
||||
/* size = */ SIZE_32BIT },
|
||||
|
||||
// Global data segment von 0-4GB
|
||||
{ /* base = */ 0x0,
|
||||
/* limit = */ 0xFFFFFFFF,
|
||||
/* code = */ false,
|
||||
/* ring = */ 0,
|
||||
/* size = */ SIZE_32BIT },
|
||||
|
||||
};
|
||||
extern "C" constexpr Pointer gdt_protected_mode_pointer(protected_mode);
|
||||
|
||||
// The static 64-bit Global Descriptor Table (GDT)
|
||||
// \see [ISDMv3 3.2.4 Segmentation in IA-32e Mode](intel_manual_vol3.pdf#page=91)
|
||||
alignas(16) static SegmentDescriptor long_mode[] = {
|
||||
// Null segment
|
||||
{},
|
||||
|
||||
// Global code segment
|
||||
{ /* base = */ 0x0,
|
||||
/* limit = */ 0x0, // ignored
|
||||
/* code = */ true,
|
||||
/* ring = */ 0,
|
||||
/* size = */ SIZE_64BIT_CODE },
|
||||
|
||||
// Global data segment
|
||||
{ /* base = */ 0x0,
|
||||
/* limit = */ 0x0, // ignored
|
||||
/* code = */ false,
|
||||
/* ring = */ 0,
|
||||
/* size = */ SIZE_64BIT_DATA },
|
||||
|
||||
};
|
||||
extern "C" constexpr Pointer gdt_long_mode_pointer(long_mode);
|
||||
|
||||
} // namespace GDT
|
|
@ -0,0 +1,203 @@
|
|||
/*! \file
|
||||
* \brief The \ref GDT "Global Descriptor Table (GDT)".
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Abstracts the GDT that, primarily, contains descriptors to memory segments.
|
||||
* \ingroup memory
|
||||
*
|
||||
* The GDT is a table that primarily contains segment descriptors. Segment descriptors has a size of 8 Bytes and
|
||||
* contains the size, position, access rights, and purpose of such a segment.
|
||||
* Unlike the LDT, the GDT is shared between all processes and may contain TSS and LDT descriptors.
|
||||
* For the kernel, the first entry is required to be a null descriptor and the code and data segments.
|
||||
* To support user-mode processes, additional TSS, code, and data segments for ring 3 must be added.
|
||||
*
|
||||
* The base address and size of the GDT are written to the GDTR register during boot (via. `lgdt`).
|
||||
*
|
||||
* \see [ISDMv3, 2.4.1; Global Descriptor Table Register (GDTR)](intel_manual_vol3.pdf#page=72)
|
||||
* \see [ISDMv3, 3.5.1; Segment Descriptor Tables](intel_manual_vol3.pdf#page=99)
|
||||
*/
|
||||
namespace GDT {
|
||||
|
||||
enum Segments {
|
||||
SEGMENT_NULL = 0,
|
||||
SEGMENT_KERNEL_CODE,
|
||||
SEGMENT_KERNEL_DATA,
|
||||
};
|
||||
|
||||
/*! \brief Unit of the segment limit
|
||||
*/
|
||||
enum Granularity {
|
||||
GRANULARITY_BYTES = 0, ///< Segment limit in Bytes
|
||||
GRANULARITY_4KBLOCK = 1 ///< Segment limit in blocks of 4 Kilobytes
|
||||
};
|
||||
|
||||
/*! \brief Descriptor type */
|
||||
enum DescriptorType {
|
||||
DESCRIPTOR_SYSTEM = 0, ///< entry is a system segment
|
||||
DESCRIPTOR_CODEDATA = 1, ///< entry is a code/data segment
|
||||
};
|
||||
|
||||
/*! \brief Address width
|
||||
*/
|
||||
enum Size {
|
||||
SIZE_16BIT = 0, ///< 16-bit (D/B = 0, L = 0)
|
||||
SIZE_32BIT = 2, ///< 32-bit (D/B = 1, L = 0)
|
||||
SIZE_64BIT_CODE = 1, ///< 64-bit (D/B = 0, L = 1)
|
||||
SIZE_64BIT_DATA = 0, ///< 64-bit (D/B = 0, L = 0)
|
||||
};
|
||||
|
||||
/*! \brief Describes the structure of segment descriptors
|
||||
*
|
||||
* A data structure that contains size, position, access rights, and purpose of any segment.
|
||||
* Segment descriptors are used in both the GDT, as well as in LDTs.
|
||||
*
|
||||
* \see [ISDMv3, 3.4.5; Segment Descriptors](intel_manual_vol3.pdf#page=95)
|
||||
* \see [AAPMv2, 4.7 Legacy Segment Descriptors](amd64_manual_vol2.pdf#page=132)
|
||||
* \see [AAPMv2, 4.8 Long-Mode Segment Descriptors](amd64_manual_vol2.pdf#page=140)
|
||||
*/
|
||||
union SegmentDescriptor {
|
||||
// Universally valid values (shared across all segment types)
|
||||
struct {
|
||||
uint64_t limit_low : 16; ///< Least-significant bits of segment size (influenced by granularity!)
|
||||
uint64_t base_low : 24; ///< Least-significant bits of base address
|
||||
uint64_t type : 4; ///< Meaning of those 4 bits depends on descriptor_type below
|
||||
DescriptorType descriptor_type : 1; ///< Descriptor type (influences the meaning of the 3 bits above)
|
||||
uint64_t privilege_level : 2; ///< Ring for this segment
|
||||
bool present : 1; ///< Entry is valid iff set to `true`
|
||||
uint64_t limit_high : 4; ///< Most-significant bits of segment size
|
||||
bool available : 1; ///< Bit which can be used for other purposes (in software)
|
||||
uint64_t custom : 2; ///< Meaning of those 2 bits relate to descriptor_type and type
|
||||
Granularity granularity : 1; ///< Unit used as granularity for the segment limit
|
||||
uint64_t base_high : 8; ///< most-significant bits of base address
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Fields specific for Code Segment
|
||||
* (for debugging purposes)
|
||||
* \see [ISDMv3, 3.4.5.1; Code- and Data-Segment Descriptor Types](intel_manual_vol3.pdf#page=98)
|
||||
*/
|
||||
struct {
|
||||
uint64_t : 40; ///< Ignored (set via `limit_low` and `base_low` )
|
||||
|
||||
/* `type` field bits */
|
||||
bool code_accessed : 1; ///< If set, the code segment was used since the last reset of this value
|
||||
bool readable : 1; ///< If set, the code is readable (otherwise only executable)
|
||||
|
||||
/*! \brief If set, the execution of code from this segment is only allowed when running at a privilege of
|
||||
* numerically less than or equal to privilege_level (i.e. the executor has the same or higher
|
||||
* privileges). However, the executor's privileges remain unchanged.
|
||||
* For nonconforming code segments (i.e., conforming is set to `0`), execution is allowed only if
|
||||
* the privileges are equal.
|
||||
* Execution will cause a GPF in case of privilege violation.
|
||||
*/
|
||||
bool conforming : 1;
|
||||
bool code : 1; ///< Has to be set to `true`
|
||||
|
||||
uint64_t : 9; ///< Ignored (set via `privilege_level` ... `available`)
|
||||
|
||||
Size operation_size : 2; ///< Default address width (`custom` field bit)
|
||||
|
||||
uint64_t : 0; ///< Remainder ignored (set via `base_high`)
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Fields specific for Data Segment
|
||||
* (for debugging purposes)
|
||||
* \see [ISDMv3, 3.4.5.1; Code- and Data-Segment Descriptor Types](intel_manual_vol3.pdf#page=98)
|
||||
*/
|
||||
struct {
|
||||
uint64_t : 40; ///< Ignored (set via `limit_low` and `base_low`)
|
||||
bool data_accessed : 1; ///< If set, the data segment was used since the last reset of this value
|
||||
bool writeable : 1; ///< If set, data is writable (otherwise read only)
|
||||
bool expand_down : 1; ///< Growing direction for dynamically growing segments
|
||||
bool notData : 1; ///< Has to be cleared (`false`)
|
||||
uint64_t : 9; ///< Ignored (set via `privilege_level` ... `available`)
|
||||
uint64_t reserved : 1; ///< Reserved, always set to `0`!
|
||||
|
||||
/*! \brief Size of the stack pointer (`false` = 16 bit, `true` = 32 bit)
|
||||
* \warning Has a different meaning in case expand_down is set to `1`.
|
||||
*/
|
||||
bool big : 1;
|
||||
|
||||
uint64_t : 0; ///< Remainder ignored
|
||||
} __attribute__((packed));
|
||||
|
||||
uint64_t value; ///!< Merged value; useful for debugging
|
||||
|
||||
/*! \brief Constructor for a specific value */
|
||||
constexpr SegmentDescriptor(uint64_t val = 0) : value(val) {} //NOLINT due to copy-initialization
|
||||
|
||||
/*! \brief Constructor for a code/data GDT entry.
|
||||
* \param base Base Address of segment
|
||||
* \param limit Size of segment
|
||||
* \param code Code or data segment
|
||||
* \param ring Privilege level
|
||||
* \param size Address width
|
||||
*/
|
||||
constexpr SegmentDescriptor(uintptr_t base, uint32_t limit, bool code, int ring, Size size) :
|
||||
limit_low(limit >> (limit > 0xFFFFF ? 12 : 0) & 0xFFFF),
|
||||
base_low(base & 0xFFFFFF),
|
||||
type(code ? 0xA : 0x2), // code readable / non-conforming, data writeable and not expanding down
|
||||
descriptor_type(DESCRIPTOR_CODEDATA),
|
||||
privilege_level(ring),
|
||||
present(true),
|
||||
limit_high((limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF),
|
||||
available(false),
|
||||
custom(size),
|
||||
granularity(limit > 0xFFFFF ? GRANULARITY_4KBLOCK : GRANULARITY_BYTES),
|
||||
base_high((base >> 24) & 0xFF) {}
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
static_assert(sizeof(SegmentDescriptor) == 8, "GDT::SegmentDescriptor has wrong size");
|
||||
|
||||
/*! \brief Structure that describes a GDT Pointer (aka GDT Descriptor)
|
||||
*
|
||||
* It contains both the length (in bytes) of the GDT (minus 1 byte) and the pointer to the GDT.
|
||||
* The pointer to the GDT can be loaded using the instruction `lgdt`.
|
||||
*
|
||||
* \note As Intel uses little endian for representing multi-byte values, the GDT::Pointer structure can be used for
|
||||
* 16, 32, and 64 bit descriptor tables:
|
||||
* \verbatim
|
||||
* | 16 bit | 16 bit | 16 bit | 16 bit | 16 bit |
|
||||
* +--------+---------------------------------------+
|
||||
* Pointer | limit | base (up to 64 bit) |
|
||||
* +--------+---------+---------+---------+---------+
|
||||
* | used for 16 bit | ignored... |
|
||||
* | used for 32 bit | ignored... |
|
||||
* | used for 64 bit |
|
||||
* \endverbatim
|
||||
*
|
||||
* \see [ISDMv3, Figure 2-6; Memory Management Registers](intel_manual_vol3.pdf#page=72)
|
||||
*/
|
||||
struct Pointer {
|
||||
uint16_t limit; //!< GDT size in bytes (minus 1 byte)
|
||||
void * base; //!< GDT base address
|
||||
|
||||
/*! \brief Constructor (automatic length)
|
||||
* \param desc Array of GDT segment descriptors -- must be defined in the same module!
|
||||
*/
|
||||
template<typename T, size_t LEN>
|
||||
explicit constexpr Pointer(const T (&desc)[LEN]) : limit(LEN * sizeof(T) - 1), base(const_cast<T*>(desc)) {}
|
||||
|
||||
/*! \brief Constructor
|
||||
* \param desc Address of the GDT segment descriptors
|
||||
* \param len Number of entries
|
||||
*/
|
||||
constexpr Pointer(void * desc, size_t len) : limit(len * sizeof(SegmentDescriptor) - 1), base(desc) {}
|
||||
|
||||
/*! \brief Set an address
|
||||
* \note On change, `lgdt` must be executed again
|
||||
* \param desc Address of the GDT segment descriptors
|
||||
* \param len Number of entries
|
||||
*/
|
||||
void set(void * desc, size_t len) {
|
||||
limit = len * sizeof(SegmentDescriptor) - 1;
|
||||
base = desc;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(Pointer) == 10, "GDT::Pointer has wrong size");
|
||||
|
||||
} // namespace GDT
|
|
@ -0,0 +1,63 @@
|
|||
#include "machine/idt.h"
|
||||
#include "machine/gdt.h"
|
||||
#include "machine/core_interrupt.h"
|
||||
|
||||
namespace IDT {
|
||||
|
||||
// Interrupt Descriptor stored in the Interrupt-Descriptor Table (IDT)
|
||||
struct alignas(8) InterruptDescriptor {
|
||||
uint16_t address_low; ///< lower interrupt function offset
|
||||
uint16_t selector; ///< code segment selector in GDT or LDT
|
||||
union {
|
||||
struct {
|
||||
uint8_t ist : 3; ///< IST Index (64 bit)
|
||||
uint8_t : 5; ///< unused, has to be 0
|
||||
Gate type : 3; ///< gate type
|
||||
GateSize size : 1; ///< gate size
|
||||
uint8_t : 1; ///< unused, has to be 0
|
||||
DPL dpl : 2; ///< descriptor privilege level
|
||||
uint8_t present : 1; ///< present: 1 for interrupts
|
||||
} __attribute__((packed));
|
||||
uint16_t flags;
|
||||
};
|
||||
uint64_t address_high : 48; ///< higher interrupt function offset
|
||||
uint64_t : 0; ///< fill until aligned with 64 bit
|
||||
} __attribute__((packed));
|
||||
|
||||
// Interrupt Descriptor Table, 8 Byte aligned
|
||||
static struct InterruptDescriptor idt[256];
|
||||
|
||||
// Struct used for loading (the address of) the Interrupt Descriptor Table into the IDT-Register
|
||||
struct Register {
|
||||
uint16_t limit; // Address of the last valid byte (relative to base)
|
||||
struct InterruptDescriptor * base;
|
||||
explicit Register(uint8_t max = 255) {
|
||||
limit = (max + static_cast<uint16_t>(1)) * sizeof(InterruptDescriptor) - 1;
|
||||
base = idt;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
static_assert(sizeof(InterruptDescriptor) == 16, "IDT::InterruptDescriptor has wrong size");
|
||||
static_assert(sizeof(Register) == 10, "IDT::Register has wrong size");
|
||||
static_assert(alignof(decltype(idt)) % 8 == 0, "IDT must be 8 byte aligned!");
|
||||
|
||||
void load() {
|
||||
// Create structure required for writing to idtr and load via lidt
|
||||
Register idtr(Core::Interrupt::VECTORS - 1);
|
||||
asm volatile("lidt %0\n\t" :: "m"(idtr) );
|
||||
}
|
||||
|
||||
void handle(uint8_t vector, void * handler, enum Gate type, enum GateSize size, enum DPL dpl, bool present) {
|
||||
struct InterruptDescriptor &item = idt[vector];
|
||||
item.selector = GDT::SEGMENT_KERNEL_CODE * sizeof(GDT::SegmentDescriptor);
|
||||
item.type = type;
|
||||
item.size = size;
|
||||
item.dpl = dpl;
|
||||
item.present = present ? 1 : 0;
|
||||
|
||||
uintptr_t address = reinterpret_cast<uintptr_t>(handler);
|
||||
item.address_low = address & 0xffff;
|
||||
item.address_high = (address >> 16) & 0xffffffffffff;
|
||||
}
|
||||
|
||||
} // namespace IDT
|
|
@ -0,0 +1,60 @@
|
|||
/*! \file
|
||||
* \brief \ref IDT "Interrupt Descriptor Table (IDT)" containing the entry points for interrupt handling.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief "Interrupt Descriptor Table (IDT)
|
||||
* \ingroup interrupt
|
||||
*
|
||||
* \see [ISDMv3 6.14 Exception and Interrupt Handling in 64-bit Mode](intel_manual_vol3.pdf#page=200)
|
||||
*/
|
||||
|
||||
namespace IDT {
|
||||
/*! \brief Gate types
|
||||
*
|
||||
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
|
||||
*/
|
||||
enum Gate {
|
||||
GATE_TASK = 0x5, ///< Task Gate
|
||||
GATE_INT = 0x6, ///< Interrupt Gate
|
||||
GATE_TRAP = 0x7, ///< Trap Gate
|
||||
};
|
||||
|
||||
/*! \brief Segment type
|
||||
*
|
||||
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
|
||||
*/
|
||||
enum GateSize {
|
||||
GATE_SIZE_16 = 0, ///< 16 bit
|
||||
GATE_SIZE_32 = 1, ///< 32 / 64 bit
|
||||
};
|
||||
|
||||
/*! \brief Descriptor Privilege Level
|
||||
*/
|
||||
enum DPL {
|
||||
DPL_KERNEL = 0, ///< Ring 0 / Kernel mode
|
||||
/* DPLs 1 and 2 are unused */
|
||||
DPL_USER = 3, ///< Ring 3 / User mode
|
||||
};
|
||||
|
||||
/*! \brief Load the IDT's address and size into the IDT-Register via `idtr`.
|
||||
*/
|
||||
void load();
|
||||
|
||||
/*! \brief Configure entry point for interrupt handling
|
||||
*
|
||||
* The provided entry function ("handler") is required to, as first step, save the registers.
|
||||
*
|
||||
* \param vector Interrupt vector number for which the handler is to be set/changed
|
||||
* \param handler Low-level entry point for interrupt handling
|
||||
* \param type Gate type (Interrupt, Trap, or Task)
|
||||
* \param size 16- or 32-bit
|
||||
* \param dpl Permissions required for enter this interrupt handler (kernel- or user space)
|
||||
* \param present Denotes whether the IDT descriptor is marked as available
|
||||
*/
|
||||
void handle(uint8_t vector, void * handler, enum Gate type = Gate::GATE_INT,
|
||||
enum GateSize size = GateSize::GATE_SIZE_32, enum DPL dpl = DPL::DPL_KERNEL, bool present = true);
|
||||
} // namespace IDT
|
|
@ -0,0 +1,83 @@
|
|||
/*! \file
|
||||
* \brief \ref IOPort provides access to the x86 IO address space
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Abstracts access to the I/O address space
|
||||
*
|
||||
* x86 PCs have a separated I/O address space that is accessible only via the machine instructions `in` and `out`.
|
||||
* An IOPort object encapsulates the corresponding address in the I/O address space and can be used for byte or
|
||||
* word-wise reading or writing.
|
||||
*/
|
||||
|
||||
class IOPort {
|
||||
/*! \brief Address in I/O address space
|
||||
*
|
||||
*/
|
||||
uint16_t address;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
* \param addr Address from the I/O address space
|
||||
*/
|
||||
explicit IOPort(uint16_t addr) : address(addr) {}
|
||||
|
||||
/*! \brief Write one byte to the I/O port
|
||||
* \param val The value to be written
|
||||
*/
|
||||
void outb(uint8_t val) const {
|
||||
asm volatile(
|
||||
"out %%al, %%dx\n\t"
|
||||
:
|
||||
: "a"(val), "d"(address)
|
||||
:
|
||||
);
|
||||
}
|
||||
|
||||
/*! \brief Write one word (2 bytes) to the I/O port
|
||||
* \param val The value to be written
|
||||
*/
|
||||
void outw(uint16_t val) const {
|
||||
asm volatile(
|
||||
"out %%ax, %%dx\n\t"
|
||||
:
|
||||
:"a"(val), "d"(address)
|
||||
:
|
||||
);
|
||||
}
|
||||
|
||||
/*! \brief Read one byte from the I/O port
|
||||
* \return Read byte
|
||||
*/
|
||||
uint8_t inb() const {
|
||||
uint8_t out = 0;
|
||||
|
||||
asm volatile(
|
||||
"in %%dx, %%al\n\t"
|
||||
:"=a"(out)
|
||||
:"d"(address)
|
||||
:
|
||||
);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/*! \brief Read one word (2 bytes) from the I/O port
|
||||
* \return Read word (2 bytes)
|
||||
*/
|
||||
uint16_t inw() const {
|
||||
uint16_t out = 0;
|
||||
|
||||
asm volatile(
|
||||
"inw %%dx, %%ax\n\t"
|
||||
:"=a"(out)
|
||||
:"d"(address)
|
||||
:
|
||||
);
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,118 @@
|
|||
#include "machine/keydecoder.h"
|
||||
#include "machine/ps2controller.h"
|
||||
|
||||
// Constants used for key decoding
|
||||
const unsigned char BREAK_BIT = 0x80;
|
||||
const unsigned char PREFIX_1 = 0xe0;
|
||||
const unsigned char PREFIX_2 = 0xe1;
|
||||
|
||||
Key KeyDecoder::decode(unsigned char code) {
|
||||
Key key = modifier;
|
||||
|
||||
// All keys that are introduced by the MF II keyboard (compared to the older AT keyboard)
|
||||
// always send a prefix value as first byte.
|
||||
if (code == PREFIX_1 || code == PREFIX_2) {
|
||||
prefix = code;
|
||||
} else {
|
||||
// Releasing a key is, for us, only important for the modifier keys such as SHIFT, CTRL and ALT,
|
||||
// For other, non-modifier keys, we ignore the break code.
|
||||
bool pressed = (code & BREAK_BIT) == 0;
|
||||
|
||||
// A key's break code is identical to its make code with an additionally set BREAK_BIT
|
||||
Key::Scancode scancode = static_cast<Key::Scancode>(code & (~BREAK_BIT));
|
||||
|
||||
// We ignore "new" special keys, such as the Windows key
|
||||
if (scancode < Key::Scancode::KEYS) {
|
||||
// save state
|
||||
status[scancode] = pressed;
|
||||
|
||||
// Take a closer look at modifier make and break events
|
||||
bool isModifier = true;
|
||||
switch (scancode) {
|
||||
// both shifts are handled equally
|
||||
case Key::Scancode::KEY_LEFT_SHIFT:
|
||||
case Key::Scancode::KEY_RIGHT_SHIFT:
|
||||
modifier.shift = pressed;
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_LEFT_ALT:
|
||||
if (prefix == PREFIX_1) {
|
||||
modifier.alt_right = pressed;
|
||||
} else {
|
||||
modifier.alt_left = pressed;
|
||||
}
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_LEFT_CTRL:
|
||||
if (prefix == PREFIX_1) {
|
||||
modifier.ctrl_right = pressed;
|
||||
} else {
|
||||
modifier.ctrl_left = pressed;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
isModifier = false;
|
||||
}
|
||||
|
||||
// For keys other than modifiers, we only care about the make code
|
||||
if (pressed && !isModifier) {
|
||||
switch (scancode) {
|
||||
case Key::Scancode::KEY_CAPS_LOCK:
|
||||
modifier.caps_lock ^= 1;
|
||||
setLed(PS2Controller::LED_CAPS_LOCK, modifier.caps_lock);
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_SCROLL_LOCK:
|
||||
modifier.scroll_lock ^= 1;
|
||||
setLed(PS2Controller::LED_SCROLL_LOCK, modifier.scroll_lock);
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_NUM_LOCK: // Can be both NumLock and pause
|
||||
// On old keyboards, the pause functionality was only accessible by
|
||||
// pressing Ctrl+NumLock. Modern MF-II keyboards therefore send exactly
|
||||
// this code combination when the pause key was pressed.
|
||||
// Normally, the pause key does not provide an ASCII code, but we check
|
||||
// that anyway. In either case, we're now done decoding.
|
||||
if (modifier.ctrl_left) { // pause key
|
||||
key.scancode = scancode;
|
||||
} else { // NumLock
|
||||
modifier.num_lock ^= 1;
|
||||
setLed(PS2Controller::LED_NUM_LOCK, modifier.num_lock);
|
||||
}
|
||||
break;
|
||||
|
||||
// Special case scan code 53: This code is used by both the minus key on the main
|
||||
// keyboard and the division key on the number block.
|
||||
// When the division key was pressed, we adjust the scancode accordingly.
|
||||
case Key::Scancode::KEY_SLASH:
|
||||
if (prefix == PREFIX_1) {
|
||||
key.scancode = Key::Scancode::KEY_DIV;
|
||||
key.shift = true;
|
||||
} else {
|
||||
key.scancode = scancode;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
key.scancode = scancode;
|
||||
|
||||
// When NumLock is enabled and a key on the keypad was pressed, we
|
||||
// want return the ASCII and scan codes of the corresponding numerical
|
||||
// key instead of the arrow keys.
|
||||
// The keys on the cursor block (prefix == PREFIX_1), however, should
|
||||
// remain usable. Therefore, as a little hack, we deactivate the NumLock
|
||||
// for these keys.
|
||||
if (modifier.num_lock && prefix == PREFIX_1) {
|
||||
key.num_lock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The prefix is only valid for the immediately following code, which was just handled.
|
||||
prefix = 0;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*! \file
|
||||
* \brief \ref KeyDecoder decodes a keystroke to the corresponding \ref Key object
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "object/key.h"
|
||||
|
||||
/*! \brief Decoder for \ref ps2keyboardset1 "keyboard codes" received from the \ref PS2Controller
|
||||
* \ingroup io
|
||||
*
|
||||
* Extracts the make and break codes, modifier and scan codes from the pressed key.
|
||||
*/
|
||||
class KeyDecoder {
|
||||
unsigned char prefix; ///< Prefix byte for keys
|
||||
Key modifier; ///< activated modifier keys (e.g., caps lock)
|
||||
|
||||
public:
|
||||
/*! \brief Current state (pressed or released) of all keys.
|
||||
*/
|
||||
bool status[Key::Scancode::KEYS];
|
||||
|
||||
/*! \brief Default constructor
|
||||
*/
|
||||
KeyDecoder() {}
|
||||
|
||||
/*! \brief Interprets the \ref ps2keyboardset1 "make and break codes" received from the
|
||||
* keyboard and derives the corresponding scan code and further information about
|
||||
* other pressed keys, such as \key{shift} and \key{ctrl}.
|
||||
*
|
||||
* \param code Byte from Keyboard to decode
|
||||
* \return Pressed key (\ref Key::valid returns `false` if the key is not yet complete)
|
||||
*/
|
||||
Key decode(unsigned char code);
|
||||
};
|
|
@ -0,0 +1,191 @@
|
|||
#include "machine/lapic.h"
|
||||
#include "machine/lapic_registers.h"
|
||||
|
||||
namespace LAPIC {
|
||||
|
||||
/*! \brief Base Address
|
||||
* used with offset to access memory mapped registers
|
||||
*/
|
||||
volatile uintptr_t base_address = 0xfee00000;
|
||||
|
||||
Register read(Index idx) {
|
||||
return *reinterpret_cast<volatile Register *>(base_address + idx);
|
||||
}
|
||||
|
||||
void write(Index idx, Register value) {
|
||||
*reinterpret_cast<volatile Register *>(base_address + idx) = value;
|
||||
}
|
||||
|
||||
/*! \brief Local APIC ID (for Pentium 4 and newer)
|
||||
*
|
||||
* Is assigned automatically during boot and should not be changed.
|
||||
*
|
||||
* \see [ISDMv3, 10.4.6 Local APIC ID](intel_manual_vol3.pdf#page=371)
|
||||
*/
|
||||
union IdentificationRegister {
|
||||
struct {
|
||||
uint32_t : 24, ///< (reserved)
|
||||
apic_id : 8; ///< APIC ID
|
||||
};
|
||||
Register value;
|
||||
|
||||
IdentificationRegister() : value(read(Index::IDENTIFICATION)) {}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Local APIC Version
|
||||
*
|
||||
* \see [ISDMv3 10.4.8 Local APIC Version Register](intel_manual_vol3.pdf#page=373)
|
||||
*/
|
||||
union VersionRegister {
|
||||
struct {
|
||||
uint32_t version : 8, ///< 0x14 for P4 and Xeon, 0x15 for more recent hardware
|
||||
: 8, ///< (reserved)
|
||||
max_lvt_entry : 8, ///< Maximum number of local vector entries
|
||||
suppress_eoi_broadcast : 1, ///< Support for suppressing EOI broadcasts
|
||||
: 7; ///< (reserved)
|
||||
};
|
||||
Register value;
|
||||
|
||||
VersionRegister() : value(read(Index::VERSION)) {}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Logical Destination Register
|
||||
* \see [ISDMv3 10.6.2.2 Logical Destination Mode](intel_manual_vol3.pdf#page=385)
|
||||
*/
|
||||
union LogicalDestinationRegister {
|
||||
struct {
|
||||
uint32_t : 24, ///< (reserved)
|
||||
lapic_id : 8; ///< Logical APIC ID
|
||||
};
|
||||
Register value;
|
||||
|
||||
LogicalDestinationRegister() : value(read(Index::LOGICAL_DESTINATION)) {}
|
||||
~LogicalDestinationRegister() {
|
||||
write(Index::LOGICAL_DESTINATION, value);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
enum Model {
|
||||
CLUSTER = 0x0,
|
||||
FLAT = 0xf
|
||||
};
|
||||
|
||||
/*! \brief Destination Format Register
|
||||
*
|
||||
* \see [ISDMv3 10.6.2.2 Logical Destination Mode](intel_manual_vol3.pdf#page=385)
|
||||
*/
|
||||
union DestinationFormatRegister {
|
||||
struct {
|
||||
uint32_t : 28; ///< (reserved)
|
||||
Model model : 4; ///< Model (Flat vs. Cluster)
|
||||
};
|
||||
Register value;
|
||||
DestinationFormatRegister() : value(read(Index::DESTINATION_FORMAT)) {}
|
||||
~DestinationFormatRegister() {
|
||||
write(Index::DESTINATION_FORMAT, value);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Task Priority Register
|
||||
*
|
||||
* \see [ISDMv3 10.8.3.1 Task and Processor Priorities](intel_manual_vol3.pdf#page=391)
|
||||
*/
|
||||
union TaskPriorityRegister {
|
||||
struct {
|
||||
uint32_t task_prio_sub : 4, ///< Task Priority Sub-Class
|
||||
task_prio : 4, ///< Task Priority
|
||||
: 24; ///< (reserved)
|
||||
};
|
||||
Register value;
|
||||
TaskPriorityRegister() : value(read(Index::TASK_PRIORITY)) {}
|
||||
~TaskPriorityRegister() {
|
||||
write(Index::TASK_PRIORITY, value);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief APIC Software Status for Spurious Interrupt Vector */
|
||||
enum APICSoftware {
|
||||
APIC_DISABLED = 0,
|
||||
APIC_ENABLED = 1,
|
||||
};
|
||||
|
||||
/*! \brief Focus Processor Checking for Spurious Interrupt Vector */
|
||||
enum FocusProcessorChecking {
|
||||
CHECKING_ENABLED = 0,
|
||||
CHECKING_DISABLED = 1,
|
||||
};
|
||||
|
||||
/*! \brief Suppress End-Of-Interrupt-Broadcast for Spurious Interrupt Vector */
|
||||
enum SuppressEOIBroadcast {
|
||||
BROADCAST = 0,
|
||||
SUPPRESS_BROADCAST = 1,
|
||||
};
|
||||
|
||||
/*! \brief Spurious Interrupt Vector Register
|
||||
*
|
||||
* \see [ISDMv3 10.9 Spurious Interrupt](intel_manual_vol3.pdf#page=394)
|
||||
*/
|
||||
union SpuriousInterruptVectorRegister {
|
||||
struct {
|
||||
uint32_t spurious_vector : 8; ///< Spurious Vector
|
||||
APICSoftware apic_software : 1; ///< APIC Software Enable/Disable
|
||||
FocusProcessorChecking focus_processor_checking : 1; ///< Focus Processor Checking
|
||||
uint32_t reserved_1 : 2;
|
||||
SuppressEOIBroadcast eoi_broadcast_suppression : 1;
|
||||
uint32_t reserved:19;
|
||||
};
|
||||
Register value;
|
||||
|
||||
SpuriousInterruptVectorRegister() : value(read(Index::SPURIOUS_INTERRUPT_VECTOR)) {}
|
||||
~SpuriousInterruptVectorRegister() {
|
||||
write(Index::SPURIOUS_INTERRUPT_VECTOR, value);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(SpuriousInterruptVectorRegister) == 4, "LAPIC Spurious Interrupt Vector has wrong size");
|
||||
|
||||
uint8_t getID() {
|
||||
IdentificationRegister ir;
|
||||
return ir.apic_id;
|
||||
}
|
||||
|
||||
uint8_t getLogicalID() {
|
||||
LogicalDestinationRegister ldr;
|
||||
return ldr.lapic_id;
|
||||
}
|
||||
|
||||
uint8_t getVersion() {
|
||||
VersionRegister vr;
|
||||
return vr.version;
|
||||
}
|
||||
|
||||
void init(uint8_t logical_id) {
|
||||
// reset logical destination ID
|
||||
// can be set using setLogicalLAPICID()
|
||||
LogicalDestinationRegister ldr;
|
||||
ldr.lapic_id = logical_id;
|
||||
|
||||
// set task priority to 0 -> accept all interrupts
|
||||
TaskPriorityRegister tpr;
|
||||
tpr.task_prio = 0;
|
||||
tpr.task_prio_sub = 0;
|
||||
|
||||
// set flat delivery mode
|
||||
DestinationFormatRegister dfr;
|
||||
dfr.model = Model::FLAT;
|
||||
|
||||
// use 255 as spurious vector, enable APIC and disable focus processor
|
||||
SpuriousInterruptVectorRegister sivr;
|
||||
sivr.spurious_vector = 0xff;
|
||||
sivr.apic_software = APICSoftware::APIC_ENABLED;
|
||||
sivr.focus_processor_checking = FocusProcessorChecking::CHECKING_DISABLED;
|
||||
}
|
||||
|
||||
void endOfInterrupt() {
|
||||
// dummy read
|
||||
read(SPURIOUS_INTERRUPT_VECTOR);
|
||||
|
||||
// signal end of interrupt
|
||||
write(EOI, 0);
|
||||
}
|
||||
|
||||
} // namespace LAPIC
|
|
@ -0,0 +1,142 @@
|
|||
/*! \file
|
||||
* \brief \ref LAPIC abstracts access to the Local \ref APIC
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Abstracts the local APIC (which is integrated into every CPU core)
|
||||
* \ingroup interrupts
|
||||
*
|
||||
* In modern (x86) PCs, every CPU core has its own Local APIC (LAPIC). The LAPIC is the link between the
|
||||
* local CPU core and the I/O APIC (that takes care about external interrupt sources.
|
||||
* Interrupt messages received by the LAPIC will be passed to the corresponding CPU core and trigger the
|
||||
* interrupt handler on this core.
|
||||
*
|
||||
* \see [ISDMv3 10.4 Local APIC](intel_manual_vol3.pdf#page=366)
|
||||
*/
|
||||
namespace LAPIC {
|
||||
/*! \brief Initialized the local APIC of the calling CPU core and sets the logical LAPIC ID in the LDR register
|
||||
* \param logical_id APIC ID to be set
|
||||
*/
|
||||
void init(uint8_t logical_id);
|
||||
|
||||
/*! \brief Signalize EOI (End of interrupt)
|
||||
*
|
||||
* Signalizes the LAPIC that the handling of the current interrupt finished. This function must be called at
|
||||
* the end of interrupt handling before ireting.
|
||||
*/
|
||||
void endOfInterrupt();
|
||||
|
||||
/*! \brief Get the ID of the current core's LAPIC
|
||||
* \return LAPIC ID
|
||||
*/
|
||||
uint8_t getID();
|
||||
|
||||
/*! \brief Get the Logical ID of the current core's LAPIC
|
||||
* \return Logical ID
|
||||
*/
|
||||
uint8_t getLogicalID();
|
||||
|
||||
/*! \brief Set the Logical ID of the current core's LAPIC
|
||||
* \param id new Logical ID
|
||||
*/
|
||||
void setLogicalID(uint8_t id);
|
||||
|
||||
/*! \brief Get version number of local APIC
|
||||
* \return version number
|
||||
*/
|
||||
uint8_t getVersion();
|
||||
|
||||
/*! \brief Inter-Processor Interrupts
|
||||
*
|
||||
* For multi-core systems, the LAPIC enables sending messages (Inter-Processor Interrupts, IPIs) to
|
||||
* other CPU cores and receiving those sent from other cores.
|
||||
*
|
||||
* \see [ISDMv3 10.6 Issuing Interprocessor Interrupts](intel_manual_vol3.pdf#page=380)
|
||||
*/
|
||||
namespace IPI {
|
||||
|
||||
/*! \brief Check if the previously sent IPI has reached its destination.
|
||||
*
|
||||
* \return `true` if the previous IPI was accepted from its target processor, otherwise `false`
|
||||
*/
|
||||
bool isDelivered();
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI)
|
||||
* \param destination ID of the target processor (use APIC::getLAPICID(core) )
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void send(uint8_t destination, uint8_t vector);
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI) to a group of processors
|
||||
* \param logical_destination Mask containing the logical APIC IDs of the target processors (use APIC::getLogicalLAPICID())
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void sendGroup(uint8_t logical_destination, uint8_t vector);
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI) to all processors (including self)
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void sendAll(uint8_t vector);
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI) to all other processors (all but self)
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void sendOthers(uint8_t vector);
|
||||
|
||||
/*! \brief Send an INIT request IPI to all other processors
|
||||
*
|
||||
* \note Only required for startup
|
||||
*
|
||||
* \param assert if `true` send an INIT,
|
||||
* on `false` send an INIT Level De-assert
|
||||
*/
|
||||
void sendInit(bool assert = true);
|
||||
|
||||
/*! \brief Send an Startup IPI to all other processors
|
||||
*
|
||||
* \note Only required for startup
|
||||
*
|
||||
* \param vector Pointer to a startup routine
|
||||
*/
|
||||
void sendStartup(uint8_t vector);
|
||||
|
||||
} // namespace IPI
|
||||
|
||||
/*! \brief Local Timer (for each LAPIC / CPU)
|
||||
*
|
||||
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
|
||||
*/
|
||||
namespace Timer {
|
||||
|
||||
/*! \brief Determines the LAPIC timer frequency.
|
||||
*
|
||||
* This function will calculate the number of LAPIC-timer ticks passing in the course of one millisecond.
|
||||
* To do so, this function will rely on PIT timer functionality and measure the tick delta between start
|
||||
* and end of waiting for a predefined period.
|
||||
*
|
||||
* For measurement, the LAPIC-timer single-shot mode (without interrupts) is used; after measurement, the
|
||||
* timer is disabled again.
|
||||
*
|
||||
* \note The timer is counting towards zero.
|
||||
*
|
||||
* \return Number of LAPIC-timer ticks per millisecond
|
||||
*/
|
||||
uint32_t ticks(void);
|
||||
|
||||
/*! \brief Set the LAPIC timer.
|
||||
* \param counter Initial counter value; decremented on every LAPIC timer tick
|
||||
* \param divide Divider (power of 2, i.e., 1 2 4 8 16 32...) used as prescaler between bus frequency
|
||||
* and LAPIC timer frequency: `LAPIC timer frequency = divide * bus frequency`.
|
||||
* `divide` is a numerical parameter, the conversion to the corresponding bit mask is
|
||||
* done internally by calling getClockDiv().
|
||||
* \param vector Interrupt vector number to be triggered on counter expiry
|
||||
* \param periodic If set, the interrupt will be issued periodically
|
||||
* \param masked If set, interrupts on counter expiry are suppressed
|
||||
*/
|
||||
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic, bool masked = false);
|
||||
|
||||
} // namespace Timer
|
||||
} // namespace LAPIC
|
|
@ -0,0 +1,228 @@
|
|||
#include "machine/lapic_registers.h"
|
||||
|
||||
namespace LAPIC {
|
||||
namespace IPI {
|
||||
|
||||
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
|
||||
enum DeliveryMode {
|
||||
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the destination bit mask
|
||||
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority core from destination mask
|
||||
SMI = 2, ///< System Management Interrupt; vector number required to be 0
|
||||
// Reserved
|
||||
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge triggered
|
||||
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
|
||||
INIT_LEVEL_DEASSERT = 5, ///< Synchronization interrupt
|
||||
STARTUP = 6, ///< Dedicated Startup-Interrupt (SIPI)
|
||||
// Reserved
|
||||
};
|
||||
|
||||
/*! \brief Way of interpreting the value written to the destination field. */
|
||||
enum DestinationMode {
|
||||
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
|
||||
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
|
||||
};
|
||||
|
||||
/*! \brief Interrupt state */
|
||||
enum DeliveryStatus {
|
||||
IDLE = 0, ///< No activity for this interrupt
|
||||
SEND_PENDING = 1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
|
||||
};
|
||||
|
||||
/*! \brief Interrupt level */
|
||||
enum Level {
|
||||
DEASSERT = 0, ///< Must be zero when DeliveryMode::INIT_LEVEL_DEASSERT
|
||||
ASSERT = 1 ///< Must be one for all other delivery modes
|
||||
};
|
||||
|
||||
/*! \brief Trigger mode for DeliveryMode::INIT_LEVEL_DEASSERT */
|
||||
enum TriggerMode {
|
||||
EDGE_TRIGGERED = 0, ///< edge triggered
|
||||
LEVEL_TRIGGERED = 1 ///< level triggered
|
||||
};
|
||||
|
||||
/*! \brief Shorthand for commonly used destinations */
|
||||
enum DestinationShorthand {
|
||||
NO_SHORTHAND = 0, ///< Use destination field instead of shorthand
|
||||
SELF = 1, ///< Send IPI to self
|
||||
ALL_INCLUDING_SELF = 2, ///< Send IPI to all including self
|
||||
ALL_EXCLUDING_SELF = 3 ///< Send IPI to all except self
|
||||
};
|
||||
|
||||
/*! \brief Interrupt mask */
|
||||
enum InterruptMask {
|
||||
UNMASKED = 0, ///< Interrupt entry is active (non-masked)
|
||||
MASKED = 1 ///< Interrupt entry is deactivated (masked)
|
||||
};
|
||||
|
||||
/*! \brief Interrupt Command
|
||||
*
|
||||
* \see [ISDMv3 10.6.1 Interrupt Command Register (ICR)](intel_manual_vol3.pdf#page=381)
|
||||
*/
|
||||
union InterruptCommand {
|
||||
struct {
|
||||
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table (IDT)" will be
|
||||
* activated when the corresponding external interrupt triggers.
|
||||
*//*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table (IDT)" will be
|
||||
* activated when the corresponding external interrupt triggers.
|
||||
*/
|
||||
uint64_t vector : 8;
|
||||
|
||||
/*! \brief The delivery mode denotes the way the interrupts will be delivered to the local CPU
|
||||
* cores, respectively to their local APICs.
|
||||
*
|
||||
* For StuBS, we use `DeliveryMode::LowestPriority`, as all CPU cores have the same
|
||||
* priority and we want to distribute interrupts evenly among them.
|
||||
* It, however, is not guaranteed that this method of load balancing will work on every system.
|
||||
*/
|
||||
enum DeliveryMode delivery_mode : 3;
|
||||
|
||||
/*! \brief The destination mode defines how the value stored in `destination` will be
|
||||
* interpreted.
|
||||
*
|
||||
* For StuBS, we use `DestinationMode::Logical`.
|
||||
*/
|
||||
enum DestinationMode destination_mode : 1;
|
||||
|
||||
/*! \brief Delivery status holds the current status of interrupt delivery.
|
||||
*
|
||||
* \note This field is read only; write accesses to this field will be ignored.
|
||||
*/
|
||||
enum DeliveryStatus delivery_status : 1;
|
||||
|
||||
uint64_t : 1; ///< reserved
|
||||
|
||||
/*! \brief The polarity denotes when an interrupt should be issued.
|
||||
*
|
||||
* For StuBS, we use `Polarity::High` (i.e., when the interrupt line is, logically, 1).
|
||||
*/
|
||||
enum Level level : 1;
|
||||
|
||||
/*! \brief The trigger mode states whether the interrupt signaling is level or edge triggered.
|
||||
*
|
||||
* StuBS uses `TriggerMode::Edge` for Keyboard and Timer, the (optional) serial interface,
|
||||
* however, needs `TriggerMode::Level`.
|
||||
*/
|
||||
enum TriggerMode trigger_mode : 1;
|
||||
|
||||
uint64_t : 2; ///< reserved
|
||||
|
||||
enum DestinationShorthand destination_shorthand : 2;
|
||||
|
||||
uint64_t : 36; ///< Reserved, do not modify
|
||||
|
||||
/*! \brief Interrupt destination.
|
||||
*
|
||||
* The meaning of destination depends on the destination mode:
|
||||
* For the logical destination mode, destination holds a bit mask made up of the cores that
|
||||
* are candidates for receiving the interrupt.
|
||||
* In the single-core case, this value is `1`, in the multi-core case, the `n` low-order bits
|
||||
* needs to be set (with `n` being the number of CPU cores, see \ref Core::count() ).
|
||||
* Setting the `n` low-order bits marks all available cores as candidates for receiving
|
||||
* interrupts and thereby balancing the number of interrupts between the cores.
|
||||
*
|
||||
* \note This form of load balancing depends on the hardware's behavior and may not work on all
|
||||
* systems in the same fashion. Most notably, in QEMU all interrupts are sent to the BSP
|
||||
* (core 0).
|
||||
*/
|
||||
uint64_t destination : 8;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief I/O redirection-table entry
|
||||
*
|
||||
* Every entry in the redirection table represents an external source of interrupts and has a size
|
||||
* of 64 bits. Due to the I/O APIC registers being only 32 bits wide, the 64-bit value is split in two
|
||||
* 32 bit values.
|
||||
*/
|
||||
struct {
|
||||
Register value_low; ///< First, low-order register
|
||||
Register value_high; ///< Second, high-order register
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Default constructor */
|
||||
InterruptCommand() = default;
|
||||
|
||||
explicit InterruptCommand(uint8_t destination, uint8_t vector = 0,
|
||||
DestinationMode destination_mode = DestinationMode::PHYSICAL,
|
||||
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||
Level level = Level::ASSERT) {
|
||||
readRegister();
|
||||
this->vector = vector;
|
||||
this->delivery_mode = delivery_mode;
|
||||
this->destination_mode = destination_mode;
|
||||
this->level = level;
|
||||
this->trigger_mode = trigger_mode;
|
||||
this->destination_shorthand = DestinationShorthand::NO_SHORTHAND;
|
||||
this->destination = destination;
|
||||
}
|
||||
|
||||
InterruptCommand(DestinationShorthand destination_shorthand, uint8_t vector,
|
||||
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||
Level level = Level::ASSERT) {
|
||||
readRegister();
|
||||
this->vector = vector;
|
||||
this->delivery_mode = delivery_mode;
|
||||
this->level = level;
|
||||
this->trigger_mode = trigger_mode;
|
||||
this->destination_shorthand = destination_shorthand;
|
||||
this->destination = destination;
|
||||
}
|
||||
|
||||
void send() const {
|
||||
write(INTERRUPT_COMMAND_REGISTER_HIGH, value_high);
|
||||
write(INTERRUPT_COMMAND_REGISTER_LOW, value_low);
|
||||
}
|
||||
|
||||
bool isSendPending() {
|
||||
value_low = read(INTERRUPT_COMMAND_REGISTER_LOW);
|
||||
return delivery_status == DeliveryStatus::SEND_PENDING;
|
||||
}
|
||||
|
||||
private:
|
||||
void readRegister() {
|
||||
while (isSendPending()) {}
|
||||
value_high = read(INTERRUPT_COMMAND_REGISTER_HIGH);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(InterruptCommand) == 8, "LAPIC Interrupt Command has wrong size");
|
||||
|
||||
bool isDelivered() {
|
||||
InterruptCommand ic;
|
||||
return !ic.isSendPending();
|
||||
}
|
||||
|
||||
void send(uint8_t destination, uint8_t vector) {
|
||||
InterruptCommand ic(destination, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendGroup(uint8_t logical_destination, uint8_t vector) {
|
||||
InterruptCommand ic(logical_destination, vector, DestinationMode::LOGICAL);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendAll(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_INCLUDING_SELF, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendOthers(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendInit(bool assert) {
|
||||
LAPIC::IPI::InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, 0, DeliveryMode::INIT,
|
||||
assert ? TriggerMode::EDGE_TRIGGERED : TriggerMode::LEVEL_TRIGGERED,
|
||||
assert ? Level::ASSERT : Level::DEASSERT);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendStartup(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector, DeliveryMode::STARTUP);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
} // namespace IPI
|
||||
} // namespace LAPIC
|
|
@ -0,0 +1,48 @@
|
|||
/*! \file
|
||||
* \brief Structures and macros for accessing \ref LAPIC "the local APIC".
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace LAPIC {
|
||||
// Memory Mapped Base Address
|
||||
extern volatile uintptr_t base_address;
|
||||
|
||||
typedef uint32_t Register;
|
||||
|
||||
/*! \brief Register Offset Index
|
||||
*
|
||||
* \see [ISDMv3 10.4.1 The Local APIC Block Diagram](intel_manual_vol3.pdf#page=368)
|
||||
*/
|
||||
enum Index : uint16_t {
|
||||
IDENTIFICATION = 0x020, ///< Local APIC ID Register, RO (sometimes R/W). Do not change!
|
||||
VERSION = 0x030, ///< Local APIC Version Register, RO
|
||||
TASK_PRIORITY = 0x080, ///< Task Priority Register, R/W
|
||||
EOI = 0x0b0, ///< EOI Register, WO
|
||||
LOGICAL_DESTINATION = 0x0d0, ///< Logical Destination Register, R/W
|
||||
DESTINATION_FORMAT = 0x0e0, ///< Destination Format Register, bits 0-27 RO, bits 28-31 R/W
|
||||
SPURIOUS_INTERRUPT_VECTOR = 0x0f0, ///< Spurious Interrupt Vector Register, bits 0-8 R/W, bits 9-1 R/W
|
||||
INTERRUPT_COMMAND_REGISTER_LOW = 0x300, ///< Interrupt Command Register 1, R/W
|
||||
INTERRUPT_COMMAND_REGISTER_HIGH = 0x310, ///< Interrupt Command Register 2, R/W
|
||||
TIMER_CONTROL = 0x320, ///< LAPIC timer control register, R/W
|
||||
TIMER_INITIAL_COUNTER = 0x380, ///< LAPIC timer initial counter register, R/W
|
||||
TIMER_CURRENT_COUNTER = 0x390, ///< LAPIC timer current counter register, RO
|
||||
TIMER_DIVIDE_CONFIGURATION = 0x3e0 ///< LAPIC timer divide configuration register, RW
|
||||
};
|
||||
|
||||
/*! \brief Get value from APIC register
|
||||
*
|
||||
* \param idx Register Offset Index
|
||||
* \return current value of register
|
||||
*/
|
||||
Register read(Index idx);
|
||||
|
||||
/*! \brief Write value to APIC register
|
||||
*
|
||||
* \param idx Register Offset Index
|
||||
* \param value value to be written into register
|
||||
*/
|
||||
void write(Index idx, Register value);
|
||||
} // namespace LAPIC
|
|
@ -0,0 +1,97 @@
|
|||
#include "types.h"
|
||||
#include "machine/lapic.h"
|
||||
#include "machine/lapic_registers.h"
|
||||
#include "machine/core.h"
|
||||
|
||||
namespace LAPIC {
|
||||
namespace Timer {
|
||||
|
||||
/*! \brief Timer Delivery Status */
|
||||
enum DeliveryStatus {
|
||||
IDLE = 0,
|
||||
SEND_PENDING = 1
|
||||
};
|
||||
|
||||
/*! \brief Timer Mode */
|
||||
enum TimerMode {
|
||||
ONE_SHOT = 0,
|
||||
PERIODIC = 1,
|
||||
DEADLINE = 2
|
||||
// reserved
|
||||
};
|
||||
|
||||
/*! \brief Timer Mask */
|
||||
enum Mask {
|
||||
NOT_MASKED = 0,
|
||||
MASKED = 1
|
||||
};
|
||||
|
||||
static const Register INVALID_DIV = 0xff;
|
||||
|
||||
/*! \brief LAPIC-Timer Control Register
|
||||
*
|
||||
* \see [ISDMv3 10.5.1 Local Vector Table](intel_manual_vol3.pdf#page=375)
|
||||
*/
|
||||
union ControlRegister {
|
||||
struct {
|
||||
uint32_t vector : 8; ///< Vector
|
||||
uint32_t : 4;
|
||||
DeliveryStatus delivery_status : 1; ///< Delivery Status
|
||||
uint32_t : 3;
|
||||
Mask masked : 1; ///< Interrupt Mask (if set, interrupt will not trigger)
|
||||
TimerMode timer_mode : 2; ///< Timer Mode
|
||||
uint32_t : 13;
|
||||
};
|
||||
Register value;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief LAPIC timer divider table
|
||||
*
|
||||
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
|
||||
*/
|
||||
static const Register div_masks[] = {
|
||||
0xb, ///< divides by 1
|
||||
0x0, ///< divides by 2
|
||||
0x1, ///< divides by 4
|
||||
0x2, ///< divides by 8
|
||||
0x3, ///< divides by 16
|
||||
0x8, ///< divides by 32
|
||||
0x9, ///< divides by 64
|
||||
0xa ///< divides by 128
|
||||
};
|
||||
|
||||
/*! \brief Calculate the bit mask for the LAPIC-timer divider.
|
||||
* \param div Divider, must be power of two: 1, 2, 4, 8, 16, 32, 64, 128
|
||||
* \return Bit mask for LAPIC::Timer::set() or `0xff` if `div` is invalid.
|
||||
*/
|
||||
Register getClockDiv(uint8_t div) {
|
||||
// div is zero or not a power of two?
|
||||
if (div == 0 || (div & (div - 1)) != 0) {
|
||||
return INVALID_DIV;
|
||||
}
|
||||
|
||||
int trail = __builtin_ctz(div); // count trailing 0-bits
|
||||
if (trail > 7) {
|
||||
return INVALID_DIV;
|
||||
}
|
||||
|
||||
return div_masks[trail];
|
||||
}
|
||||
|
||||
uint32_t ticks(void) {
|
||||
uint32_t ticks = 0; // ticks per millisecond
|
||||
// Calculation (Assignment 5)
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic, bool masked) {
|
||||
(void) counter;
|
||||
(void) divide;
|
||||
(void) vector;
|
||||
(void) periodic;
|
||||
(void) masked;
|
||||
}
|
||||
|
||||
} // namespace Timer
|
||||
} // namespace LAPIC
|
|
@ -0,0 +1,61 @@
|
|||
#include "machine/pic.h"
|
||||
#include "machine/ioport.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace PIC {
|
||||
|
||||
void initialize() {
|
||||
// Access primary & secondary PIC via two ports each
|
||||
IOPort primary_port_a(0x20);
|
||||
IOPort primary_port_b(0x21);
|
||||
IOPort secondary_port_a(0xa0);
|
||||
IOPort secondary_port_b(0xa1);
|
||||
|
||||
// Initialization Command Word 1 (ICW1)
|
||||
// Basic PIC configuration, starting initialization
|
||||
enum InitializationCommandWord1 {
|
||||
ICW4_NEEDED = 1 << 0, // use Initialization Command Word 4
|
||||
SINGLE_MODE = 1 << 1, // Single or multiple (cascade mode) 8259A
|
||||
ADDRESS_INTERVAL_HALF = 1 << 2, // 4 or 8 bit interval between the interrupt vector locations
|
||||
LEVEL_TRIGGERED = 1 << 3, // Level or edge triggered
|
||||
ALWAYS_1 = 1 << 4,
|
||||
};
|
||||
const uint8_t icw1 = InitializationCommandWord1::ICW4_NEEDED
|
||||
| InitializationCommandWord1::ALWAYS_1;
|
||||
// ICW1 in port A (each)
|
||||
primary_port_a.outb(icw1);
|
||||
secondary_port_a.outb(icw1);
|
||||
|
||||
// Initialization Command Word 2 (ICW2):
|
||||
// Configure interrupt vector base offset in port B
|
||||
primary_port_b.outb(0x20); // Primary: IRQ Offset 32
|
||||
secondary_port_b.outb(0x28); // Secondary: IRQ Offset 40
|
||||
|
||||
// Initialization Command Word 3 (ICW3):
|
||||
// Configure pin on primary PIC connected to secondary PIC
|
||||
const uint8_t pin = 2; // Secondary connected on primary pin 2
|
||||
primary_port_b.outb(1 << pin); // Pin as bit mask for primary
|
||||
secondary_port_b.outb(pin); // Pin as value (ID) for secondary
|
||||
|
||||
// Initialization Command Word 4 (ICW4)
|
||||
// Basic PIC configuration, starting initialization
|
||||
enum InitializationCommandWord4 {
|
||||
MODE_8086 = 1 << 0, // 8086/8088 or 8085 mode
|
||||
AUTO_EOI = 1 << 1, // Single or multiple (cascade mode) 8259A
|
||||
BUFFER_PRIMARY = 1 << 2, // Primary or secondary buffering
|
||||
BUFFERED_MODE = 1 << 3, // Enable or disable buffering (for primary or secondary above)
|
||||
SPECIAL_FULLY_NESTED = 1 << 4 // Special or non special fully nested
|
||||
};
|
||||
const uint8_t icw4 = InitializationCommandWord4::MODE_8086
|
||||
| InitializationCommandWord4::AUTO_EOI;
|
||||
// ICW3 in port B (each)
|
||||
primary_port_b.outb(icw4);
|
||||
secondary_port_b.outb(icw4);
|
||||
|
||||
// Operation Control Word 1 (OCW1):
|
||||
// Disable (mask) all hardware interrupts on both legacy PICs (we'll use APIC)
|
||||
secondary_port_b.outb(0xff);
|
||||
primary_port_b.outb(0xff);
|
||||
}
|
||||
|
||||
} // namespace PIC
|
|
@ -0,0 +1,17 @@
|
|||
/*! \file
|
||||
* \brief Handle (disable) the old Programmable Interrupt Controller (PIC)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \brief The Programmable Interrupt Controller (PIC aka 8259A)
|
||||
*/
|
||||
namespace PIC {
|
||||
|
||||
/*! \brief Initialize the PICs (Programmable Interrupt Controller, 8259A),
|
||||
* such that all 15 hardware interrupts are stored sequentially in the \ref IDT
|
||||
* and the hardware interrupts are disabled (in favor of \ref APIC).
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
} // namespace PIC
|
|
@ -0,0 +1,216 @@
|
|||
#include "machine/pit.h"
|
||||
#include "machine/ioport.h"
|
||||
#include "machine/core.h"
|
||||
|
||||
namespace PIT {
|
||||
|
||||
// we only use PIT channel 2
|
||||
const uint8_t CHANNEL = 2;
|
||||
static IOPort data(0x40 + CHANNEL);
|
||||
|
||||
/*! \brief Access mode
|
||||
*/
|
||||
enum AccessMode {
|
||||
LATCH_COUNT_VALUE = 0,
|
||||
LOW_BYTE_ONLY = 1,
|
||||
HIGH_BYTE_ONLY = 2,
|
||||
LOW_AND_HIGH_BYTE = 3
|
||||
};
|
||||
|
||||
/*! \brief Operating Mode
|
||||
*
|
||||
* \warning Channel 2 is not able to send interrupts, however, the status bit will be set
|
||||
*/
|
||||
enum OperatingMode {
|
||||
INTERRUPT_ON_TERMINAL_COUNT = 0,
|
||||
PROGRAMMABLE_ONE_SHOT = 1,
|
||||
RATE_GENERATOR = 2,
|
||||
SQUARE_WAVE_GENERATOR = 3, ///< useful for the PC speaker
|
||||
SOFTWARE_TRIGGERED_STROBE = 4,
|
||||
HARDWARE_TRIGGERED_STROBE = 5
|
||||
};
|
||||
|
||||
/*! \brief data format
|
||||
*/
|
||||
enum Format {
|
||||
BINARY = 0,
|
||||
BCD = 1 ///< Binary Coded Decimals
|
||||
};
|
||||
|
||||
// Mode register (only writable)
|
||||
static IOPort mode_register(0x43);
|
||||
union Mode {
|
||||
struct {
|
||||
Format format : 1;
|
||||
OperatingMode operating : 3;
|
||||
AccessMode access : 2;
|
||||
uint8_t channel : 2;
|
||||
};
|
||||
uint8_t value;
|
||||
|
||||
/*! \brief Constructor for mode, takes the numeric value */
|
||||
explicit Mode(uint8_t value) : value(value) {}
|
||||
|
||||
/*! \brief Constructor for counting mode
|
||||
* \param access Access mode to the 16-bit counter value
|
||||
* \param operating Operating mode for the counter
|
||||
* \param format Number format for the 16-bit counter values (binary or BCD)
|
||||
*/
|
||||
Mode(AccessMode access, OperatingMode operating, Format format) :
|
||||
format(format), operating(operating), access(access), channel(PIT::CHANNEL) {}
|
||||
|
||||
/*! \brief (Default) constructor for reading the counter value
|
||||
*/
|
||||
Mode() : value(0) {
|
||||
this->channel = PIT::CHANNEL;
|
||||
}
|
||||
|
||||
/*! \brief Write the value to the mode register
|
||||
*/
|
||||
void write() const {
|
||||
mode_register.outb(value);
|
||||
}
|
||||
};
|
||||
|
||||
// The NMI Status and Control Register contains details about PIT counter 2
|
||||
static IOPort controlRegister(0x61);
|
||||
union Control {
|
||||
/*! \brief I/O-port bitmap for the NMI Status and Control Register
|
||||
* \note Over time, the meaning of the bits stored at I/O port 0x61 changed; don't get the structure confused
|
||||
* with old documentation on the IBM PC XT platform.
|
||||
* \see [Intel® I/O Controller Hub 7 (ICH7) Family](i-o-controller-hub-7-datasheet.pdf#page=415), page 415
|
||||
*/
|
||||
struct {
|
||||
//! If enabled, the interrupt state will be visible at status_timer_counter2
|
||||
uint8_t enable_timer_counter2 : 1;
|
||||
uint8_t enable_speaker_data : 1; ///< If set, speaker output is equal to status_timer_counter2
|
||||
uint8_t enable_pci_serr : 1; ///< not important, do not modify
|
||||
uint8_t enable_nmi_iochk : 1; ///< not important, do not modify
|
||||
const uint8_t refresh_cycle_toggle : 1; ///< not important, must be 0 on write
|
||||
const uint8_t status_timer_counter2 : 1; ///< will be set on timer expiration; must be 0 on write
|
||||
const uint8_t status_iochk_nmi_source : 1; ///< not important, must be 0 on write
|
||||
const uint8_t status_serr_nmi_source : 1; ///< not important, must be 0 on write
|
||||
};
|
||||
uint8_t value;
|
||||
|
||||
/*! \brief Constructor
|
||||
* \param value Numeric value for the control register
|
||||
*/
|
||||
explicit Control(uint8_t value) : value(value) {}
|
||||
|
||||
/*! \brief Default constructor
|
||||
* Automatically reads the current contents from the control register.
|
||||
*/
|
||||
Control() : value(controlRegister.inb()) {}
|
||||
|
||||
/*! \brief Write the current state to the control register.
|
||||
*/
|
||||
void write() const {
|
||||
controlRegister.outb(value);
|
||||
}
|
||||
};
|
||||
|
||||
// The base frequency is, due to historic reasons, 1.193182 MHz.
|
||||
const uint64_t BASE_FREQUENCY = 1193182ULL;
|
||||
|
||||
bool set(uint16_t us) {
|
||||
// Counter ticks for us
|
||||
uint64_t counter = BASE_FREQUENCY * us / 1000000ULL;
|
||||
|
||||
// As the hardware counter has a size of 16 bit, we want to check whether the
|
||||
// calculated counter value is too large ( > 54.9ms )
|
||||
if (counter > 0xffff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Interrupt state should be readable in status register, but PC speaker should remain off
|
||||
Control c;
|
||||
c.enable_speaker_data = 0;
|
||||
c.enable_timer_counter2 = 1;
|
||||
c.write();
|
||||
|
||||
// Channel 2, 16-bit divisor, with mode 0 (interrupt) in binary format
|
||||
Mode m(AccessMode::LOW_AND_HIGH_BYTE, OperatingMode::INTERRUPT_ON_TERMINAL_COUNT, Format::BINARY);
|
||||
m.write();
|
||||
|
||||
// Set the counter's start value
|
||||
data.outb(counter & 0xff); // low
|
||||
data.outb((counter >> 8) & 0xff); // high
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t get(void) {
|
||||
// Set mode to reading
|
||||
Mode m;
|
||||
m.write();
|
||||
|
||||
// Read low and high
|
||||
uint16_t value = data.inb();
|
||||
value |= data.inb() << 8;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool isActive(void) {
|
||||
Control c; // reads the current value from the control register
|
||||
return c.enable_timer_counter2 == 1 && c.status_timer_counter2 == 0;
|
||||
}
|
||||
|
||||
bool waitForTimeout(void) {
|
||||
while(true) {
|
||||
Control c; // reads the current value from the control register
|
||||
if (c.enable_timer_counter2 == 0) {
|
||||
return false;
|
||||
} else if (c.status_timer_counter2 == 1) {
|
||||
return true;
|
||||
} else {
|
||||
Core::pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool delay(uint16_t us) {
|
||||
return set(us) && waitForTimeout();
|
||||
}
|
||||
|
||||
void pcspeaker(uint32_t freq) {
|
||||
Control c;
|
||||
if (freq == 0) {
|
||||
disable();
|
||||
} else {
|
||||
// calculate frequency divider
|
||||
uint64_t div = BASE_FREQUENCY / freq;
|
||||
if (div > 0xffff) {
|
||||
div = 0xffff;
|
||||
}
|
||||
|
||||
// check if already configured
|
||||
if (c.enable_speaker_data == 0) {
|
||||
// if not, set mode
|
||||
Mode m(AccessMode::LOW_AND_HIGH_BYTE, OperatingMode::SQUARE_WAVE_GENERATOR, Format::BINARY);
|
||||
m.write();
|
||||
}
|
||||
|
||||
// write frequency divider
|
||||
data.outb(div & 0xff);
|
||||
data.outb((div >> 8) & 0xff);
|
||||
|
||||
// already configured? (second part to prevent playing a wrong sound)
|
||||
if (c.enable_speaker_data == 0) {
|
||||
// activate PC speaker
|
||||
c.enable_speaker_data = 1;
|
||||
c.enable_timer_counter2 = 1;
|
||||
c.write();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void disable(void) {
|
||||
Control c;
|
||||
c.enable_speaker_data = 0;
|
||||
c.enable_timer_counter2 = 0;
|
||||
c.write();
|
||||
}
|
||||
|
||||
} // namespace PIT
|
|
@ -0,0 +1,74 @@
|
|||
/*! \file
|
||||
* \brief The old/historical \ref PIT "Programmable Interval Timer (PIT)"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Abstraction of the historical Programmable Interval Timer (PIT).
|
||||
*
|
||||
* Historically, PCs had a Timer component of type 8253 or 8254, modern systems come with a compatible chip.
|
||||
* Each of these chips provides three 16-bit wide counters ("channel"), each running at a frequency of 1.19318 MHz.
|
||||
* The timer's counting speed is thereby independent from the CPU frequency.
|
||||
*
|
||||
* Traditionally, the first counter (channel 0) was used for triggering interrupts, the second one (channel 1) controlled
|
||||
* the memory refresh, and the third counter (channel 2) was assigned to the PC speaker.
|
||||
*
|
||||
* As the PIT's frequency is fixed to a constant value of 1.19318 MHz, the PIT can be used for calibration.
|
||||
* For this purpose, we use channel 2 only.
|
||||
*
|
||||
* \note Interrupts should be disabled while configuring the timer.
|
||||
*/
|
||||
namespace PIT {
|
||||
|
||||
/*! \brief Start timer
|
||||
*
|
||||
* Sets the channel 2 timer to the provided value and starts counting.
|
||||
*
|
||||
* \note The maximum waiting time is approx. 55 000 us due to the timers being limited to 16 bit.
|
||||
* \param us Waiting time in us
|
||||
* \return `true` if the counter is running; `false` if the waiting time exceeds the limits.
|
||||
*/
|
||||
bool set(uint16_t us);
|
||||
|
||||
/*! \brief Reads the current timer value
|
||||
* \return Current timer value
|
||||
*/
|
||||
uint16_t get(void);
|
||||
|
||||
/*! \brief Check if the timer is running
|
||||
* \return `true` if running, `false` otherwise
|
||||
*/
|
||||
bool isActive(void);
|
||||
|
||||
/*! \brief (Active) waiting for timeout
|
||||
* \return `true` when timeout was successfully hit, `false` if the timer was not active prior to calling.
|
||||
*/
|
||||
bool waitForTimeout(void);
|
||||
|
||||
/*! \brief Set the timer and wait for timeout
|
||||
* \note The maximum waiting time is approx. 55 000 us due to the timers being limited to 16 bit.
|
||||
* \param us Waiting time in us
|
||||
* \return `true` when waiting successfully terminated; `false` on error (e.g., waiting time exceeds its limits)
|
||||
*/
|
||||
bool delay(uint16_t us);
|
||||
|
||||
/*! \brief Play a given frequency on the PC speaker.
|
||||
*
|
||||
* As the PC speaker is connected to PIT channel 2, the PIT can be used to play an acoustic signal.
|
||||
* Playing sounds occupies the PIT, so it cannot be used for other purposes while playback.
|
||||
*
|
||||
* \note Not every PC has an activated PC speaker
|
||||
* \note Qemu & KVM have to be launched with `-audiodev`
|
||||
* If you still cannot hear anything, try to set `QEMU_AUDIO_DRV` to `alsa`
|
||||
* (by launching \StuBS with `QEMU_AUDIO_DRV=alsa make kvm`)
|
||||
* \param freq Frequency (in Hz) of the sound to be played, or 0 to deactivate playback.
|
||||
*/
|
||||
void pcspeaker(uint32_t freq);
|
||||
|
||||
/*! \brief Deactivate the timer
|
||||
*/
|
||||
void disable(void);
|
||||
|
||||
} // namespace PIT
|
|
@ -0,0 +1,118 @@
|
|||
#include "machine/ps2controller.h"
|
||||
#include "machine/keydecoder.h"
|
||||
#include "machine/ioport.h"
|
||||
#include "debug/output.h"
|
||||
#include "compiler/fix.h"
|
||||
|
||||
namespace PS2Controller {
|
||||
|
||||
// I/O Ports of the PS2 Controller
|
||||
static const IOPort ctrl_port(0x64); ///< Access status- (read) and command (write) register
|
||||
static const IOPort data_port(0x60); ///< Access PS/2 device [keyboard] output- (read) and input (write) buffer
|
||||
/* The buffers are used to communicate with the controller or the connected
|
||||
* PS/2 devices alike:
|
||||
* - For the output buffer, the controller decides to which PS/2 device the
|
||||
* data gets forwarded to -- by default it is the primary PS/2 device (keyboard).
|
||||
* - The source device from which the data was gathered can be determined using
|
||||
* the status flag (\ref IS_MOUSE).
|
||||
*
|
||||
* Please also note, that the naming of the buffer may be a bit contra-intuitive
|
||||
* since it is the perspective of the PS/2 controller due to historical reasons.
|
||||
*/
|
||||
|
||||
// Key decoder (stores the state of the modifier keys)
|
||||
static KeyDecoder key_decoder;
|
||||
|
||||
// To store the current state of the Keyboard LEDs
|
||||
static uint8_t MAYBE_UNUSED leds = 0;
|
||||
|
||||
/*! \brief Flags in the PS/2 controller status register
|
||||
*/
|
||||
enum Status {
|
||||
HAS_OUTPUT = 1 << 0, ///< Output buffer non-empty?
|
||||
INPUT_PENDING = 1 << 1, ///< Is input buffer full?
|
||||
SYSTEM_FLAG = 1 << 2, ///< set on soft reset, cleared on power up
|
||||
IS_COMMAND = 1 << 3, ///< Is command Byte? (otherwise data)
|
||||
IS_MOUSE = 1 << 5, ///< Mouse output has data
|
||||
TIMEOUT_ERROR = 1 << 6, ///< Timeout error
|
||||
PARITY_ERROR = 1 << 7 ///< Parity error
|
||||
};
|
||||
|
||||
/*! \brief Commands to be send to the Keyboard
|
||||
*/
|
||||
enum KeyboardCommand : uint8_t {
|
||||
KEYBOARD_SET_LED = 0xed, ///< Set the LED (according to the following parameter byte)
|
||||
KEYBOARD_SEND_ECHO = 0xee, ///< Send an echo packet
|
||||
KEYBOARD_SET_SPEED = 0xf3, ///< Set the repeat rate (according to the following parameter byte)
|
||||
KEYBOARD_ENABLE = 0xf4, ///< Enable Keyboard
|
||||
KEYBOARD_DISABLE = 0xf5, ///< Disable Keyboard
|
||||
KEYBOARD_SET_DEFAULT = 0xf6, ///< Load defaults
|
||||
};
|
||||
|
||||
/*! \brief Replies
|
||||
*/
|
||||
enum Reply {
|
||||
ACK = 0xfa, ///< Acknowledgement
|
||||
RESEND = 0xfe, ///< Request to resend (not required to implement)
|
||||
ECHO = 0xee ///< Echo answer
|
||||
};
|
||||
|
||||
/*! \brief Commands for the PS/2 Controller
|
||||
*
|
||||
* These commands are processed by the controller and *not* send to keyboard/mouse.
|
||||
* They have to be written into the command register.
|
||||
*/
|
||||
enum ControllerCommand {
|
||||
CONTROLLER_GET_COMMAND_BYTE = 0x20, ///< Read Command Byte of PS/2 Controller
|
||||
CONTROLLER_SET_COMMAND_BYTE = 0x60, ///< Write Command Byte of PS/2 Controller
|
||||
CONTROLLER_MOUSE_DISABLE = 0xa7, ///< Disable mouse interface
|
||||
CONTROLLER_MOUSE_ENABLE = 0xa8, ///< Enable mouse interface
|
||||
CONTROLLER_KEYBOARD_DISABLE = 0xad, ///< Disable keyboard interface
|
||||
CONTROLLER_KEYBOARD_ENABLE = 0xae, ///< Enable keyboard interface
|
||||
CONTROLLER_SEND_TO_MOUSE = 0xd4, ///< Send parameter to mouse device
|
||||
};
|
||||
|
||||
/*! \brief Send a command or data to a connected PS/2 device
|
||||
*
|
||||
* The value must only be written into the input buffer after the previously
|
||||
* written values have been fetched (\ref INPUT_PENDING in the status register).
|
||||
*
|
||||
* \todo Implement method
|
||||
*
|
||||
* \param value data to be sent
|
||||
*/
|
||||
static void MAYBE_UNUSED sendData(uint8_t value) {
|
||||
// TODO: You have to implement this method
|
||||
(void) value;
|
||||
}
|
||||
|
||||
void init() {
|
||||
|
||||
// Switch all LEDs off (on many PCs NumLock is turned on after power up)
|
||||
setLed(LED_CAPS_LOCK, false);
|
||||
setLed(LED_SCROLL_LOCK, false);
|
||||
setLed(LED_NUM_LOCK, false);
|
||||
|
||||
// Set to maximum speed & minimum delay
|
||||
setRepeatRate(SPEED_30_0CPS, DELAY_250MS);
|
||||
}
|
||||
|
||||
bool fetch(Key &pressed) {
|
||||
// TODO: You have to implement this method
|
||||
(void) pressed;
|
||||
return false;
|
||||
}
|
||||
|
||||
void setRepeatRate(Speed speed, Delay delay) {
|
||||
// TODO: You have to implement this method. Use sendData()
|
||||
(void) speed;
|
||||
(void) delay;
|
||||
}
|
||||
|
||||
void setLed(enum LED led, bool on) {
|
||||
// TODO: You have to implement this method. Use sendData()
|
||||
(void) led;
|
||||
(void) on;
|
||||
}
|
||||
|
||||
} // namespace PS2Controller
|
|
@ -0,0 +1,127 @@
|
|||
/*! \file
|
||||
* \brief \ref PS2Controller "PS/2 Controller" (Intel 8042, also known as Keyboard Controller)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "object/key.h"
|
||||
|
||||
/*! \brief PS/2 Controller
|
||||
* \ingroup io
|
||||
*
|
||||
* Initializes the PS/2 devices (Keyboard and optional Mouse), and
|
||||
* determines both the scan code and ASCII character of a pressed key from the
|
||||
* transmitted make and break codes using the \ref KeyDecoder.
|
||||
*
|
||||
* \note This controller is also known as Intel 8042 (nowadays integrated in
|
||||
* the mainboard) or *Keyboard Controller*.
|
||||
* But to avoid confusion with the actual Keyboard and since we use the
|
||||
* PS/2-compatible mode to support the Mouse as well, the name
|
||||
* PS/2 Controller was chosen for the sake of simplicity.
|
||||
*
|
||||
* \note Since modern PCs sometimes don't have an PS/2 connector, USB keyboards
|
||||
* and mice are emulated as PS/2 device with USB Legacy Support.
|
||||
*/
|
||||
namespace PS2Controller {
|
||||
/*! \brief Initialization of connected devices
|
||||
*
|
||||
* All status LEDs of the keyboard are switched off and the repetition rate is
|
||||
* set to maximum speed.
|
||||
*/
|
||||
void init();
|
||||
|
||||
/*! \brief Retrieve the keyboard event
|
||||
*
|
||||
* Retrieves make and brake events from the keyboard.
|
||||
* If a valid (non special) key was pressed, the scan code is determined
|
||||
* using \ref KeyDecoder::decode into a \ref Key object.
|
||||
* Events on special keys like \key{Shift}, \key{Alt}, \key{CapsLock} etc. are stored
|
||||
* (in \ref KeyDecoder) and applied on subsequent keystrokes,
|
||||
* while no valid key is retrieved.
|
||||
*
|
||||
* Mouse events are ignored.
|
||||
*
|
||||
* \todo Implement Method
|
||||
* \param pressed Reference to an object which will contain the pressed \ref Key on success
|
||||
* \return `true` if a valid key was decoded
|
||||
*/
|
||||
bool fetch(Key &pressed);
|
||||
|
||||
/*! \brief Delay before the keyboard starts repeating sending a pressed key
|
||||
*/
|
||||
enum Delay {
|
||||
DELAY_250MS = 0, ///< Delay of 0.25s
|
||||
DELAY_500MS = 1, ///< Delay of 0.5s
|
||||
DELAY_750MS = 2, ///< Delay of 0.75s
|
||||
DELAY_1000MS = 3 ///< Delay of 1s
|
||||
};
|
||||
|
||||
/*! \brief Repeat Rate of Characters
|
||||
*
|
||||
* \see \ref ps2keyboard
|
||||
*/
|
||||
enum Speed {
|
||||
SPEED_30_0CPS = 0x00, ///< 30 characters per second
|
||||
SPEED_26_7CPS = 0x01, ///< 26.7 characters per second
|
||||
SPEED_24_0CPS = 0x02, ///< 24 characters per second
|
||||
SPEED_21_8CPS = 0x03, ///< 12.8 characters per second
|
||||
SPEED_20_7CPS = 0x04, ///< 20.7 characters per second
|
||||
SPEED_18_5CPS = 0x05, ///< 18.5 characters per second
|
||||
SPEED_17_1CPS = 0x06, ///< 17.1 characters per second
|
||||
SPEED_16_0CPS = 0x07, ///< 16 characters per second
|
||||
SPEED_15_0CPS = 0x08, ///< 15 characters per second
|
||||
SPEED_13_3CPS = 0x09, ///< 13.3 characters per second
|
||||
SPEED_12_0CPS = 0x0a, ///< 12 characters per second
|
||||
SPEED_10_9CPS = 0x0b, ///< 10.9 characters per second
|
||||
SPEED_10_0CPS = 0x0c, ///< 10 characters per second
|
||||
SPEED_09_2CPS = 0x0d, ///< 9.2 characters per second
|
||||
SPEED_08_6CPS = 0x0e, ///< 8.6 characters per second
|
||||
SPEED_08_0CPS = 0x0f, ///< 8 characters per second
|
||||
SPEED_07_5CPS = 0x10, ///< 7.5 characters per second
|
||||
SPEED_06_7CPS = 0x11, ///< 6.7 characters per second
|
||||
SPEED_06_0CPS = 0x12, ///< 6 characters per second
|
||||
SPEED_05_5CPS = 0x13, ///< 5.5 characters per second
|
||||
SPEED_05_0CPS = 0x14, ///< 5 characters per second
|
||||
SPEED_04_6CPS = 0x15, ///< 4.6 characters per second
|
||||
SPEED_04_3CPS = 0x16, ///< 4.3 characters per second
|
||||
SPEED_04_0CPS = 0x17, ///< 4 characters per second
|
||||
SPEED_03_7CPS = 0x18, ///< 3.7 characters per second
|
||||
SPEED_03_3CPS = 0x19, ///< 3.3 characters per second
|
||||
SPEED_03_0CPS = 0x1a, ///< 3 characters per second
|
||||
SPEED_02_7CPS = 0x1b, ///< 2.7 characters per second
|
||||
SPEED_02_5CPS = 0x1c, ///< 2.5 characters per second
|
||||
SPEED_02_3CPS = 0x1d, ///< 2.3 characters per second
|
||||
SPEED_02_1CPS = 0x1e, ///< 2.1 characters per second
|
||||
SPEED_02_0CPS = 0x1f, ///< 2 characters per second
|
||||
};
|
||||
|
||||
/*! \brief Configure the repeat rate of the keyboard
|
||||
*
|
||||
* \param delay configures how long a key must be pressed before the repetition begins.
|
||||
* \param speed determines how fast the key codes should follow each other.
|
||||
* Valid values are between `0` (30 characters per second) and
|
||||
* `31` (2 characters per second).
|
||||
*
|
||||
* \todo Implement method
|
||||
*/
|
||||
void setRepeatRate(Speed speed, Delay delay);
|
||||
|
||||
/*! \brief Keyboard LEDs
|
||||
*/
|
||||
enum LED {
|
||||
LED_SCROLL_LOCK = 1 << 0, ///< Scroll Lock
|
||||
LED_NUM_LOCK = 1 << 1, ///< Num Lock
|
||||
LED_CAPS_LOCK = 1 << 2, ///< Caps Lock
|
||||
};
|
||||
|
||||
/*! \brief Enable or disable a keyboard LED
|
||||
*
|
||||
* \param led LED to enable or disable
|
||||
* \param on `true` will enable the specified LED, `false` disable
|
||||
*
|
||||
* \todo Implement method
|
||||
*/
|
||||
void setLed(enum LED led, bool on);
|
||||
|
||||
} // namespace PS2Controller
|
|
@ -0,0 +1,35 @@
|
|||
#include "machine/serial.h"
|
||||
|
||||
Serial::Serial(ComPort port, BaudRate baud_rate, DataBits data_bits, StopBits stop_bits, Parity parity) : port(port) {
|
||||
// TODO: Implement
|
||||
(void) baud_rate;
|
||||
(void) data_bits;
|
||||
(void) stop_bits;
|
||||
(void) parity;
|
||||
}
|
||||
|
||||
void Serial::writeReg(RegisterIndex reg, char out) {
|
||||
// TODO: Implement (if you want, optional exercise)
|
||||
(void) reg;
|
||||
(void) out;
|
||||
}
|
||||
|
||||
char Serial::readReg(RegisterIndex reg) {
|
||||
// TODO: Implement (if you want, optional exercise)
|
||||
(void) reg;
|
||||
return '\0';
|
||||
}
|
||||
|
||||
int Serial::write(char out, bool blocking) {
|
||||
// TODO: Implement (if you want, optional exercise)
|
||||
(void) out;
|
||||
(void) blocking;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Serial::read(bool blocking) {
|
||||
// TODO: Implement (if you want, optional exercise)
|
||||
(void) blocking;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/*! \file
|
||||
* \brief Communication via the \ref Serial interface (RS-232)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Serial interface.
|
||||
* \ingroup io
|
||||
*
|
||||
* This class provides a serial interface (COM1 - COM4) for communication with the outside world.
|
||||
*
|
||||
* The first IBM PC used the external chip [8250 UART](https://de.wikipedia.org/wiki/NSC_8250), whereas, in today's
|
||||
* systems, this functionality is commonly integrated into the southbridge, but remained compatible.
|
||||
*
|
||||
* \see [PC8250A Data Sheet](uart-8250a.pdf#page=11) (Registers on page 11)
|
||||
* \see [PC16550D Data Sheet](uart-16550d.pdf#page=16) (Successor, for optional FIFO buffer, page 16)
|
||||
*/
|
||||
|
||||
class Serial {
|
||||
public:
|
||||
/*! \brief COM-Port
|
||||
*
|
||||
* The serial interface and its hardware addresses. Modern desktop PCs have, at most,
|
||||
* a single, physical COM-port (`COM1`)
|
||||
*/
|
||||
enum ComPort{
|
||||
COM1 = 0x3f8,
|
||||
COM2 = 0x2f8,
|
||||
COM3 = 0x3e8,
|
||||
COM4 = 0x2e8,
|
||||
};
|
||||
|
||||
/*! \brief Transmission speed
|
||||
*
|
||||
* The unit Baud describes the transmission speed in number of symbols per seconds.
|
||||
* 1 Baud therefore equals the transmission of 1 symbol per second.
|
||||
* The possible Baud rates are whole-number dividers of the clock frequency of 115200 Hz..
|
||||
*/
|
||||
enum BaudRate {
|
||||
BAUD_300 = 384,
|
||||
BAUD_600 = 192,
|
||||
BAUD_1200 = 96,
|
||||
BAUD_2400 = 48,
|
||||
BAUD_4800 = 24,
|
||||
BAUD_9600 = 12,
|
||||
BAUD_19200 = 6,
|
||||
BAUD_38400 = 3,
|
||||
BAUD_57600 = 2,
|
||||
BAUD_115200 = 1,
|
||||
};
|
||||
|
||||
/*! \brief Number of data bits per character */
|
||||
enum DataBits {
|
||||
DATA_5BIT = 0,
|
||||
DATA_6BIT = 1,
|
||||
DATA_7BIT = 2,
|
||||
DATA_8BIT = 3,
|
||||
};
|
||||
|
||||
/*! \brief Number of stop bits per character */
|
||||
enum StopBits {
|
||||
STOP_1BIT = 0,
|
||||
STOP_1_5BIT = 4,
|
||||
STOP_2BIT = 4,
|
||||
};
|
||||
|
||||
/*! \brief parity bit */
|
||||
enum Parity {
|
||||
PARITY_NONE = 0,
|
||||
PARITY_ODD = 8,
|
||||
PARITY_EVEN = 24,
|
||||
PARITY_MARK = 40,
|
||||
PARITY_SPACE = 56,
|
||||
};
|
||||
|
||||
private:
|
||||
/*! \brief register index */
|
||||
enum RegisterIndex {
|
||||
// if Divisor Latch Access Bit [DLAB] = 0
|
||||
RECEIVE_BUFFER_REGISTER = 0, ///< read only
|
||||
TRANSMIT_BUFFER_REGISTER = 0, ///< write only
|
||||
INTERRUPT_ENABLE_REGISTER = 1,
|
||||
|
||||
// if Divisor Latch Access Bit [DLAB] = 1
|
||||
DIVISOR_LOW_REGISTER = 0,
|
||||
DIVISOR_HIGH_REGISTER = 1,
|
||||
|
||||
// (irrespective from DLAB)
|
||||
INTERRUPT_IDENT_REGISTER = 2, ///< read only
|
||||
FIFO_CONTROL_REGISTER = 2, ///< write only -- 16550 and newer (esp. not 8250a)
|
||||
LINE_CONTROL_REGISTER = 3, ///< highest-order bit is DLAB (see above)
|
||||
MODEM_CONTROL_REGISTER = 4,
|
||||
LINE_STATUS_REGISTER = 5,
|
||||
MODEM_STATUS_REGISTER = 6
|
||||
};
|
||||
|
||||
/*! \brief Mask for the respective register */
|
||||
enum RegisterMask : uint8_t {
|
||||
// Interrupt Enable Register
|
||||
RECEIVED_DATA_AVAILABLE = 1 << 0,
|
||||
TRANSMITTER_HOLDING_REGISTER_EMPTY = 1 << 1,
|
||||
RECEIVER_LINE_STATUS = 1 << 2,
|
||||
MODEM_STATUS = 1 << 3,
|
||||
|
||||
// Interrupt Ident Register
|
||||
INTERRUPT_PENDING = 1 << 0, ///< 0 means interrupt pending
|
||||
INTERRUPT_ID_0 = 1 << 1,
|
||||
INTERRUPT_ID_1 = 1 << 2,
|
||||
|
||||
// FIFO Control Register
|
||||
ENABLE_FIFO = 1 << 0, ///< 0 means disabled ^= conforming to 8250a
|
||||
CLEAR_RECEIVE_FIFO = 1 << 1,
|
||||
CLEAR_TRANSMIT_FIFO = 1 << 2,
|
||||
DMA_MODE_SELECT = 1 << 3,
|
||||
TRIGGER_RECEIVE = 1 << 6,
|
||||
|
||||
// Line Control Register
|
||||
// bits per character: 5 6 7 8
|
||||
WORD_LENGTH_SELECT_0 = 1 << 0, // Setting Select0: 0 1 0 1
|
||||
WORD_LENGTH_SELECT_1 = 1 << 1, // Setting Select1: 0 0 1 1
|
||||
NUMBER_OF_STOP_BITS = 1 << 2, // 0 ≙ one stop bit, 1 ≙ 1.5/2 stop bits
|
||||
PARITY_ENABLE = 1 << 3,
|
||||
EVEN_PARITY_SELECT = 1 << 4,
|
||||
STICK_PARITY = 1 << 5,
|
||||
SET_BREAK = 1 << 6,
|
||||
DIVISOR_LATCH_ACCESS_BIT = 1 << 7, // DLAB
|
||||
|
||||
// Modem Control Register
|
||||
DATA_TERMINAL_READY = 1 << 0,
|
||||
REQUEST_TO_SEND = 1 << 1,
|
||||
OUT_1 = 1 << 2,
|
||||
OUT_2 = 1 << 3, // must be set for interrupts!
|
||||
LOOP = 1 << 4,
|
||||
|
||||
// Line Status Register
|
||||
DATA_READY = 1 << 0, // Set when there is a value in the receive buffer
|
||||
OVERRUN_ERROR = 1 << 1,
|
||||
PARITY_ERROR = 1 << 2,
|
||||
FRAMING_ERROR = 1 << 3,
|
||||
BREAK_INTERRUPT = 1 << 4,
|
||||
TRANSMITTER_HOLDING_REGISTER = 1 << 5,
|
||||
TRANSMITTER_EMPTY = 1 << 6, // Send buffer empty (ready to send)
|
||||
|
||||
// Modem Status Register
|
||||
DELTA_CLEAR_TO_SEND = 1 << 0,
|
||||
DELTA_DATA_SET_READY = 1 << 1,
|
||||
TRAILING_EDGE_RING_INDICATOR = 1 << 2,
|
||||
DELTA_DATA_CARRIER_DETECT = 1 << 3,
|
||||
CLEAR_TO_SEND = 1 << 4,
|
||||
DATA_SET_READY = 1 << 5,
|
||||
RING_INDICATOR = 1 << 6,
|
||||
DATA_CARRIER_DETECT = 1 << 7
|
||||
};
|
||||
|
||||
/*! \brief Read value from register
|
||||
*
|
||||
* \todo Implement Method
|
||||
*
|
||||
* \param reg Register index
|
||||
* \return The value read from register
|
||||
*/
|
||||
char readReg(RegisterIndex reg);
|
||||
|
||||
/*! \brief Write value to register
|
||||
*
|
||||
* \todo Implement Method
|
||||
*
|
||||
* \param reg Register index
|
||||
* \param out value to be written
|
||||
*/
|
||||
void writeReg(RegisterIndex reg, char out);
|
||||
|
||||
protected:
|
||||
/*! \brief Selected COM port */
|
||||
const ComPort port;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
*
|
||||
* Creates a Serial object that encapsulates the used COM port, as well as the parameters used for the
|
||||
* serial connection. Default values are `8N1` (8 bit, no parity bit, one stop bit) with 115200 Baud using COM1.
|
||||
*
|
||||
* \todo Implement Constructor
|
||||
*/
|
||||
explicit Serial(ComPort port = COM1, BaudRate baud_rate = BAUD_115200, DataBits data_bits = DATA_8BIT,
|
||||
StopBits stop_bits = STOP_1BIT, Parity parity = PARITY_NONE);
|
||||
|
||||
/*! \brief Read one byte from the serial interface
|
||||
*
|
||||
* \todo Implement Method
|
||||
*
|
||||
* \param blocking If set, \ref read() blocks until one byte was read
|
||||
* \return Value read from serial interface (or `-1 ` if non-blocking and no data ready)
|
||||
*/
|
||||
int read(bool blocking = true);
|
||||
|
||||
/*! \brief Write one byte to the serial interface
|
||||
*
|
||||
* \todo Implement Method
|
||||
*
|
||||
* \param out Byte to be written
|
||||
* \param blocking If set, \ref write() blocks until the byte was written
|
||||
* \return Byte written (or `-1` if writing byte failed)
|
||||
*/
|
||||
int write(char out, bool blocking = true);
|
||||
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
#include "machine/system.h"
|
||||
#include "machine/cmos.h"
|
||||
#include "machine/ioport.h"
|
||||
#include "debug/output.h"
|
||||
|
||||
namespace System {
|
||||
|
||||
void reboot() {
|
||||
const IOPort system_control_port_a(0x92);
|
||||
DBG_VERBOSE << "rebooting smp" << endl;
|
||||
CMOS::write(CMOS::REG_STATUS_SHUTDOWN, 0);
|
||||
system_control_port_a.outb(0x3);
|
||||
}
|
||||
|
||||
} // namespace System
|
|
@ -0,0 +1,14 @@
|
|||
/*! \file
|
||||
* \brief General \ref System functionality (\ref System::reboot "reboot")
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \brief General System functions
|
||||
*/
|
||||
namespace System {
|
||||
|
||||
/*! \brief Perform a reboot
|
||||
*/
|
||||
void reboot();
|
||||
} // namespace System
|
|
@ -0,0 +1,119 @@
|
|||
/*! \file
|
||||
* \brief \ref TextMode provides a basic interface to display a character in VGA-compatible text mode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Basic operations in the VGA-compatible text mode
|
||||
* \ingroup io
|
||||
*
|
||||
* This class provides an interface to access the screen in text mode, with
|
||||
* access directly on the hardware level, i.e. the video memory and the
|
||||
* I/O ports of the graphics card.
|
||||
*/
|
||||
class TextMode {
|
||||
public:
|
||||
static const unsigned ROWS = 25; ///< Visible rows in text mode
|
||||
static const unsigned COLUMNS = 80; ///< Visible columns in text mode
|
||||
|
||||
/*! \brief CGA color palette
|
||||
*
|
||||
* Colors for the attribute byte.
|
||||
* All 16 colors can be used for the foreground while the background colors
|
||||
* are limited to the first eight (from`BLACK` to `LIGHT_GREY`)
|
||||
*/
|
||||
enum Color {
|
||||
BLACK, ///< Black (fore- and background)
|
||||
BLUE, ///< Blue (fore- and background)
|
||||
GREEN, ///< Green (fore- and background)
|
||||
CYAN, ///< Cyan (fore- and background)
|
||||
RED, ///< Red (fore- and background)
|
||||
MAGENTA, ///< Magenta (fore- and background)
|
||||
BROWN, ///< Brown (fore- and background)
|
||||
LIGHT_GREY, ///< Light grey (fore- and background)
|
||||
DARK_GREY, ///< Dark grey (foreground only)
|
||||
LIGHT_BLUE, ///< Light blue (foreground only)
|
||||
LIGHT_GREEN, ///< Light green (foreground only)
|
||||
LIGHT_CYAN, ///< Light cyan (foreground only)
|
||||
LIGHT_RED, ///< Light red (foreground only)
|
||||
LIGHT_MAGENTA, ///< Light magenta (foreground only)
|
||||
YELLOW, ///< Yellow (foreground only)
|
||||
WHITE ///< White (foreground only)
|
||||
};
|
||||
|
||||
/*! \brief Structure of a character attribute
|
||||
* consists of 4 bit fore- and 3 bit background color, and a single blink bit.
|
||||
*
|
||||
* [Bit fields](https://en.cppreference.com/w/cpp/language/bit_field) can
|
||||
* notably simplify the access and code readability.
|
||||
*
|
||||
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
|
||||
* is indeed undefined behavior in C++. However, *gcc* explicitly allows this construct as a
|
||||
* [language extension](https://gcc.gnu.org/bugs/#nonbugs).
|
||||
* Some compilers ([other than gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
|
||||
* might allow this feature only by disabling strict aliasing (`-fno-strict-aliasing`).
|
||||
* In \StuBS we use this feature extensively due to the improved code readability.
|
||||
*/
|
||||
union Attribute {
|
||||
struct {
|
||||
uint8_t foreground : 4; ///< `.... XXXX` Foreground color
|
||||
uint8_t background : 3; ///< `.XXX ....` Background color
|
||||
uint8_t blink : 1; ///< `X... ....` Blink
|
||||
} __attribute__((packed));
|
||||
uint8_t value; ///< combined value
|
||||
|
||||
/*! \brief Attribute constructor (with default values)
|
||||
*
|
||||
* \todo Complete constructor
|
||||
*
|
||||
* \param foreground Foreground color (Default: \ref LIGHT_GREY)
|
||||
* \param background Background color (Default: \ref BLACK)
|
||||
* \param blink Blink if `true` (default: no blinking)
|
||||
*/
|
||||
explicit Attribute(Color foreground = LIGHT_GREY, Color background = BLACK, bool blink = false)
|
||||
{ //NOLINT
|
||||
(void) foreground;
|
||||
(void) background;
|
||||
(void) blink;
|
||||
}
|
||||
|
||||
} __attribute__((packed)); // prevent padding by the compiler
|
||||
|
||||
/*! \brief Set the keyboard hardware cursor to absolute screen position
|
||||
*
|
||||
* \todo Implement the method using \ref IOPort
|
||||
*
|
||||
* \param abs_x absolute column of the keyboard hardware cursor
|
||||
* \param abs_y absolute row of the keyboard hardware cursor
|
||||
*/
|
||||
static void setCursor(unsigned abs_x, unsigned abs_y);
|
||||
|
||||
/*! \brief Retrieve the keyboard hardware cursor position on screen
|
||||
*
|
||||
* \todo Implement the method using the \ref IOPort
|
||||
*
|
||||
* \param abs_x absolute column of the keyboard hardware cursor
|
||||
* \param abs_y absolute row of the keyboard hardware cursor
|
||||
*/
|
||||
static void getCursor(unsigned& abs_x, unsigned& abs_y);
|
||||
|
||||
/*! \brief Basic output of a character at a specific position on the screen.
|
||||
*
|
||||
* This method outputs the given character at the absolute screen position
|
||||
* (`x`, `y`) with the specified color attribute.
|
||||
*
|
||||
* The position (`0`,`0`) indicates the upper left corner of the screen.
|
||||
* The attribute defines characteristics such as background color,
|
||||
* foreground color and blinking.
|
||||
*
|
||||
* \param abs_x Column (`abs_x` < \ref COLUMNS) in which the character should be displayed
|
||||
* \param abs_y Row (`abs_y` < \ref ROWS) in which the character should be displayed
|
||||
* \param character Character to be displayed
|
||||
* \param attrib Attribute with color settings
|
||||
* \todo Implement the method
|
||||
*/
|
||||
static void show(unsigned abs_x, unsigned abs_y, char character, Attribute attrib = Attribute());
|
||||
|
||||
};
|
|
@ -0,0 +1,112 @@
|
|||
/*! \file
|
||||
* \brief \ref TextWindow provides virtual output windows in text mode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "machine/textmode.h"
|
||||
|
||||
/*! \brief Virtual windows in text mode
|
||||
* \ingroup io
|
||||
*
|
||||
* Outputs text on a part of the screen in \ref TextMode "text mode",
|
||||
* a window defined in its position and size (with its own cursor).
|
||||
*
|
||||
* This allows to separate the output of the application from the debug output
|
||||
* on the screen without having to synchronize.
|
||||
*/
|
||||
class TextWindow : public TextMode {
|
||||
// Prevent copies and assignments
|
||||
TextWindow(const TextWindow&) = delete;
|
||||
TextWindow& operator=(const TextWindow&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor of a text window
|
||||
*
|
||||
* Creates a virtual, rectangular text window on the screen.
|
||||
* The coordinates to construct the window are absolute positions in the
|
||||
* \ref TextMode screen.
|
||||
*
|
||||
* \note Overlapping windows are neither supported nor prevented -- better
|
||||
* just try to avoid construction windows with overlapping coordinates!
|
||||
*
|
||||
* \warning Don't use the hardware cursor in more than one window!
|
||||
*
|
||||
* \param from_col Text Window starts in column `from_col`,
|
||||
* the first (leftmost) possible column is `0`
|
||||
* \param to_col Text Window extends to the right to column `to_col` (exclusive).
|
||||
* This column has to be strictly greater than `from_col`,
|
||||
* the maximum allowed value is \ref TextMode::COLUMNS (rightmost)
|
||||
* \param from_row Text Window starts in row `from_row`,
|
||||
* the first possible (uppermost) row is `0`
|
||||
* \param to_row Text Window extends down to row `to_row` (exclusive).
|
||||
* This row has to be strictly greater than `from_row`,
|
||||
* the maximum allowed value is \ref TextMode::ROWS (bottom-most)
|
||||
* \param use_cursor Specifies whether the hardware cursor (`true`) or a
|
||||
* software cursor/variable (`false`) should be used to
|
||||
* store the current position
|
||||
*
|
||||
* \todo Implement constructor
|
||||
*/
|
||||
TextWindow(unsigned from_col, unsigned to_col, unsigned from_row, unsigned to_row, bool use_cursor = false);
|
||||
|
||||
/*! \brief Set the cursor position in the window
|
||||
*
|
||||
* Depending on the constructor parameter `use_cursor` either the
|
||||
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||
* is stored internally in the object.
|
||||
*
|
||||
* The coordinates are relative to the upper left starting position of
|
||||
* the window.
|
||||
*
|
||||
* \param rel_x Column in window
|
||||
* \param rel_y Row in window
|
||||
* \todo Implement method, use \ref TextMode::setCursor() for the hardware cursor
|
||||
*/
|
||||
void setPos(unsigned rel_x, unsigned rel_y);
|
||||
|
||||
/*! \brief Get the current cursor position in the window
|
||||
*
|
||||
* Depending on the constructor parameter `use_cursor` either the
|
||||
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||
* is retrieved from the internally stored object.
|
||||
*
|
||||
* \param rel_x Column in window
|
||||
* \param rel_y Row in window
|
||||
* \todo Implement Method, use \ref TextMode::getCursor() for the hardware cursor
|
||||
*/
|
||||
void getPos(unsigned& rel_x, unsigned& rel_y) const;
|
||||
|
||||
/*! \brief Display multiple characters in the window
|
||||
*
|
||||
* Output a character string, starting at the current cursor position.
|
||||
* Since the string does not need to contain a `\0` termination (unlike the
|
||||
* common C string), a length parameter is required to specify the number
|
||||
* of characters in the string.
|
||||
* When the output is complete, the cursor is positioned after the last
|
||||
* printed character.
|
||||
* The same attributes (colors) are used for the entire text.
|
||||
*
|
||||
* If there is not enough space left at the end of the line,
|
||||
* the output continues on the following line.
|
||||
* As soon as the last window line is filled, the entire window area is
|
||||
* moved up one line: The first line disappears, the bottom line is cleared.
|
||||
*
|
||||
* A line break also occurs whenever the character `\n` appears in the text.
|
||||
*
|
||||
* \param string Text to be printed
|
||||
* \param length Length of text
|
||||
* \param attrib Attribute for text
|
||||
* \todo Implement Method
|
||||
*/
|
||||
void print(const char* string, size_t length, Attribute attrib = TextMode::Attribute()); //NOLINT
|
||||
|
||||
/*! \brief Delete all contents in the window and reset the cursor.
|
||||
*
|
||||
* \param character Fill character
|
||||
* \param attrib Attribute for fill character
|
||||
* \todo Implement Method
|
||||
*/
|
||||
void reset(char character = ' ', Attribute attrib = TextMode::Attribute());
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
#include "boot/startup_ap.h"
|
||||
#include "machine/lapic.h"
|
||||
#include "debug/output.h"
|
||||
|
||||
const char * os_name = "MP" "StuBS";
|
||||
|
||||
// Main function (the bootstrap processor starts here)
|
||||
extern "C" int main() {
|
||||
|
||||
unsigned int num_cpus = Core::count();
|
||||
DBG_VERBOSE << "Number of CPUs: " << num_cpus << endl;
|
||||
|
||||
// Start application processors
|
||||
ApplicationProcessor::boot();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Main function for application processors
|
||||
extern "C" int main_ap() {
|
||||
DBG_VERBOSE << "CPU core " << static_cast<int>(Core::getID())
|
||||
<< " / LAPIC " << static_cast<int>(LAPIC::getID()) << " in main_ap()" << endl;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
#include "object/key.h"
|
||||
|
||||
// Character table for scan codes for US keyboards
|
||||
static struct {
|
||||
const unsigned char normal, // Character without modifiers
|
||||
shift, // Character with pressed Shift, Capslock, or in Numpad
|
||||
alt; // Character with pressed Alt key
|
||||
} ascii_tab[Key::Scancode::KEYS] = {
|
||||
{ 0, 0, 0 }, // KEY_INVALID
|
||||
{ 0, 0, 0 }, // KEY_ESCAPE
|
||||
{ '1', '!', 0 }, // KEY_1
|
||||
{ '2', '"', 253 }, // KEY_2
|
||||
{ '3', 21, 0 }, // KEY_3
|
||||
{ '4', '$', 0 }, // KEY_4
|
||||
{ '5', '%', 0 }, // KEY_5
|
||||
{ '6', '&', 0 }, // KEY_6
|
||||
{ '7', '/', '{' }, // KEY_7
|
||||
{ '8', '(', '[' }, // KEY_8
|
||||
{ '9', ')', ']' }, // KEY_9
|
||||
{ '0', '=', '}' }, // KEY_0
|
||||
{ 225, '?', '\\'}, // KEY_DASH
|
||||
{ 39, 96, 0 }, // KEY_EQUAL
|
||||
{'\b', 0, 0 }, // KEY_BACKSPACE
|
||||
{ 0, 0, 0 }, // KEY_TAB
|
||||
{ 'q', 'Q', '@' }, // KEY_Q
|
||||
{ 'w', 'W', 0 }, // KEY_W
|
||||
{ 'e', 'E', 0 }, // KEY_E
|
||||
{ 'r', 'R', 0 }, // KEY_R
|
||||
{ 't', 'T', 0 }, // KEY_T
|
||||
{ 'z', 'Z', 0 }, // KEY_Y
|
||||
{ 'u', 'U', 0 }, // KEY_U
|
||||
{ 'i', 'I', 0 }, // KEY_I
|
||||
{ 'o', 'O', 0 }, // KEY_O
|
||||
{ 'p', 'P', 0 }, // KEY_P
|
||||
{ 129, 154, 0 }, // KEY_OPEN_BRACKET
|
||||
{ '+', '*', '~' }, // KEY_CLOSE_BRACKET
|
||||
{'\n', 0, 0 }, // KEY_ENTER
|
||||
{ 0, 0, 0 }, // KEY_LEFT_CTRL
|
||||
{ 'a', 'A', 0 }, // KEY_A
|
||||
{ 's', 'S', 0 }, // KEY_S
|
||||
{ 'd', 'D', 0 }, // KEY_D
|
||||
{ 'f', 'F', 0 }, // KEY_F
|
||||
{ 'g', 'G', 0 }, // KEY_G
|
||||
{ 'h', 'H', 0 }, // KEY_H
|
||||
{ 'j', 'J', 0 }, // KEY_J
|
||||
{ 'k', 'K', 0 }, // KEY_K
|
||||
{ 'l', 'L', 0 }, // KEY_L
|
||||
{ 148, 153, 0 }, // KEY_SEMICOLON
|
||||
{ 132, 142, 0 }, // KEY_APOSTROPH
|
||||
{ '^', 248, 0 }, // KEY_GRAVE_ACCENT
|
||||
{ 0, 0, 0 }, // KEY_LEFT_SHIFT
|
||||
{ '#', 39, 0 }, // KEY_BACKSLASH
|
||||
{ 'y', 'Y', 0 }, // KEY_Z
|
||||
{ 'x', 'X', 0 }, // KEY_X
|
||||
{ 'c', 'C', 0 }, // KEY_C
|
||||
{ 'v', 'V', 0 }, // KEY_V
|
||||
{ 'b', 'B', 0 }, // KEY_B
|
||||
{ 'n', 'N', 0 }, // KEY_N
|
||||
{ 'm', 'M', 230 }, // KEY_M
|
||||
{ ',', ';', 0 }, // KEY_COMMA
|
||||
{ '.', ':', 0 }, // KEY_PERIOD
|
||||
{ '-', '_', 0 }, // KEY_SLASH
|
||||
{ 0, 0, 0 }, // KEY_RIGHT_SHIFT
|
||||
{ '*', '*', 0 }, // KEY_KP_STAR
|
||||
{ 0, 0, 0 }, // KEY_LEFT_ALT
|
||||
{ ' ', ' ', 0 }, // KEY_SPACEBAR
|
||||
{ 0, 0, 0 }, // KEY_CAPS_LOCK
|
||||
{ 0, 0, 0 }, // KEY_F1
|
||||
{ 0, 0, 0 }, // KEY_F2
|
||||
{ 0, 0, 0 }, // KEY_F3
|
||||
{ 0, 0, 0 }, // KEY_F4
|
||||
{ 0, 0, 0 }, // KEY_F5
|
||||
{ 0, 0, 0 }, // KEY_F6
|
||||
{ 0, 0, 0 }, // KEY_F7
|
||||
{ 0, 0, 0 }, // KEY_F8
|
||||
{ 0, 0, 0 }, // KEY_F9
|
||||
{ 0, 0, 0 }, // KEY_F10
|
||||
{ 0, 0, 0 }, // KEY_NUM_LOCK
|
||||
{ 0, 0, 0 }, // KEY_SCROLL_LOCK
|
||||
{ 0, '7', 0 }, // KEY_KP_7
|
||||
{ 0, '8', 0 }, // KEY_KP_8
|
||||
{ 0, '9', 0 }, // KEY_KP_9
|
||||
{ '-', '-', 0 }, // KEY_KP_DASH
|
||||
{ 0, '4', 0 }, // KEY_KP_4
|
||||
{ 0, '5', 0 }, // KEY_KP_5
|
||||
{ 0, '6', 0 }, // KEY_KP_6
|
||||
{ '+', '+', 0 }, // KEY_KP_PLUS
|
||||
{ 0, '1', 0 }, // KEY_KP_1
|
||||
{ 0, '2', 0 }, // KEY_KP_2
|
||||
{ 0, '3', 0 }, // KEY_KP_3
|
||||
{ 0, '0', 0 }, // KEY_KP_0
|
||||
{ 127, ',', 0 }, // KEY_KP_PERIOD
|
||||
{ 0, 0, 0 }, // KEY_SYSREQ
|
||||
{ 0, 0, 0 }, // KEY_EUROPE_2
|
||||
{ '<', '>', '|' }, // KEY_F11
|
||||
{ 0, 0, 0 }, // KEY_F12
|
||||
{ 0, 0, 0 }, // KEY_KP_EQUAL
|
||||
};
|
||||
|
||||
unsigned char Key::ascii() const {
|
||||
// Select the correct table depending on the modifier bits.
|
||||
// For the sake of simplicity, Shift and NumLock have precedence over Alt.
|
||||
// The Ctrl modifier does not have a distinct table.
|
||||
|
||||
if (!valid()) {
|
||||
return '\0';
|
||||
} else if (shift
|
||||
|| (caps_lock
|
||||
&& (
|
||||
(scancode >= KEY_Q && scancode <= KEY_P)
|
||||
|| (scancode >= KEY_A && scancode <= KEY_L)
|
||||
|| (scancode >= KEY_Z && scancode <= KEY_M)
|
||||
)
|
||||
)
|
||||
|| (num_lock && scancode >= KEY_KP_7 && scancode <= KEY_KP_PERIOD)
|
||||
) {
|
||||
return ascii_tab[scancode].shift;
|
||||
} else if (alt()) {
|
||||
return ascii_tab[scancode].alt;
|
||||
} else {
|
||||
return ascii_tab[scancode].normal;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
/*! \file
|
||||
* \brief \ref Key, an abstraction for handling pressed keys and their modifiers
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Class that abstracts a key, made up of the scan code and the modifier bits.
|
||||
*/
|
||||
struct Key {
|
||||
/*! \brief The keys' scan codes (code 1)
|
||||
*/
|
||||
enum Scancode : uint8_t {
|
||||
// Invalid scan code
|
||||
KEY_INVALID = 0,
|
||||
|
||||
// "real" valid scan codes
|
||||
KEY_ESCAPE,
|
||||
KEY_1,
|
||||
KEY_2,
|
||||
KEY_3,
|
||||
KEY_4,
|
||||
KEY_5,
|
||||
KEY_6,
|
||||
KEY_7,
|
||||
KEY_8,
|
||||
KEY_9,
|
||||
KEY_0,
|
||||
KEY_DASH,
|
||||
KEY_EQUAL,
|
||||
KEY_BACKSPACE,
|
||||
KEY_TAB,
|
||||
KEY_Q,
|
||||
KEY_W,
|
||||
KEY_E,
|
||||
KEY_R,
|
||||
KEY_T,
|
||||
KEY_Y,
|
||||
KEY_U,
|
||||
KEY_I,
|
||||
KEY_O,
|
||||
KEY_P,
|
||||
KEY_OPEN_BRACKET,
|
||||
KEY_CLOSE_BRACKET,
|
||||
KEY_ENTER,
|
||||
KEY_LEFT_CTRL,
|
||||
KEY_A,
|
||||
KEY_S,
|
||||
KEY_D,
|
||||
KEY_F,
|
||||
KEY_G,
|
||||
KEY_H,
|
||||
KEY_J,
|
||||
KEY_K,
|
||||
KEY_L,
|
||||
KEY_SEMICOLON,
|
||||
KEY_APOSTROPH,
|
||||
KEY_GRAVE_ACCENT,
|
||||
KEY_LEFT_SHIFT,
|
||||
KEY_BACKSLASH,
|
||||
KEY_Z,
|
||||
KEY_X,
|
||||
KEY_C,
|
||||
KEY_V,
|
||||
KEY_B,
|
||||
KEY_N,
|
||||
KEY_M,
|
||||
KEY_COMMA,
|
||||
KEY_PERIOD,
|
||||
KEY_SLASH,
|
||||
KEY_RIGHT_SHIFT,
|
||||
KEY_KP_STAR,
|
||||
KEY_LEFT_ALT,
|
||||
KEY_SPACEBAR,
|
||||
KEY_CAPS_LOCK,
|
||||
KEY_F1,
|
||||
KEY_F2,
|
||||
KEY_F3,
|
||||
KEY_F4,
|
||||
KEY_F5,
|
||||
KEY_F6,
|
||||
KEY_F7,
|
||||
KEY_F8,
|
||||
KEY_F9,
|
||||
KEY_F10,
|
||||
KEY_NUM_LOCK,
|
||||
KEY_SCROLL_LOCK,
|
||||
KEY_KP_7,
|
||||
KEY_KP_8,
|
||||
KEY_KP_9,
|
||||
KEY_KP_DASH,
|
||||
KEY_KP_4,
|
||||
KEY_KP_5,
|
||||
KEY_KP_6,
|
||||
KEY_KP_PLUS,
|
||||
KEY_KP_1,
|
||||
KEY_KP_2,
|
||||
KEY_KP_3,
|
||||
KEY_KP_0,
|
||||
KEY_KP_PERIOD,
|
||||
KEY_SYSREQ,
|
||||
KEY_EUROPE_2,
|
||||
KEY_F11,
|
||||
KEY_F12,
|
||||
KEY_KP_EQUAL,
|
||||
|
||||
// Number of keys (excluding aliases below)
|
||||
KEYS,
|
||||
|
||||
// aliases
|
||||
KEY_DIV = KEY_7,
|
||||
KEY_DEL = KEY_KP_PERIOD,
|
||||
KEY_UP = KEY_KP_8,
|
||||
KEY_DOWN = KEY_KP_2,
|
||||
KEY_LEFT = KEY_KP_4,
|
||||
KEY_RIGHT = KEY_KP_6,
|
||||
};
|
||||
|
||||
Scancode scancode;
|
||||
|
||||
// bit masks for the modifier keys
|
||||
bool shift : 1,
|
||||
alt_left : 1,
|
||||
alt_right : 1,
|
||||
ctrl_left : 1,
|
||||
ctrl_right : 1,
|
||||
caps_lock : 1,
|
||||
num_lock : 1,
|
||||
scroll_lock : 1;
|
||||
|
||||
/*! \brief Default constructor: Instantiates an invalid key by setting ASCII, scan code, and modifier bits to 0
|
||||
*/
|
||||
Key() : scancode(KEY_INVALID), shift(false), alt_left(false), alt_right(false),
|
||||
ctrl_left(false), ctrl_right(false),
|
||||
caps_lock(false), num_lock(false), scroll_lock(false) {}
|
||||
|
||||
/*! \brief Invalid keys have a scancode = 0
|
||||
* \return Checks whether a key is valid.
|
||||
*/
|
||||
bool valid() const {
|
||||
return scancode != KEY_INVALID && scancode < KEYS;
|
||||
}
|
||||
|
||||
/*! \brief Marks the key as invalid by setting the scan code to 0.
|
||||
*
|
||||
*/
|
||||
void invalidate() {
|
||||
scancode = KEY_INVALID;
|
||||
}
|
||||
|
||||
/*! \brief Get the key's ASCII value
|
||||
* \return the key's ASCII value
|
||||
*/
|
||||
unsigned char ascii() const;
|
||||
|
||||
/*! \brief Indicates whether the ALT modifier is set
|
||||
* \return `true` if ALT key was pressed during key press
|
||||
*/
|
||||
bool alt() const {
|
||||
return alt_left || alt_right;
|
||||
}
|
||||
|
||||
/*! \brief Indicates whether the CTRL modifier is set
|
||||
* \return `true` if CTRL key was pressed during key press
|
||||
*/
|
||||
bool ctrl() const {
|
||||
return ctrl_left || ctrl_right;
|
||||
}
|
||||
|
||||
/*! \brief Conversion to char (ASCII code)
|
||||
*
|
||||
*/
|
||||
operator char() const { //NOLINT since we want implicit conversions
|
||||
return static_cast<char>(ascii());
|
||||
}
|
||||
};
|
|
@ -0,0 +1,143 @@
|
|||
# Build the kernel
|
||||
|
||||
# Folder the generated files will be placed in.
|
||||
BUILDDIR ?= .build
|
||||
# Build folder suffixes
|
||||
OPTTAG = -opt
|
||||
NOOPTTAG = -noopt
|
||||
DBGTAG = -dbg
|
||||
VERBOSETAG = -verbose
|
||||
|
||||
# C++
|
||||
CXX = $(PREFIX)g++
|
||||
|
||||
CXXFLAGS_ARCH = -m64
|
||||
|
||||
CXXFLAGS_DEFAULT = -std=c++14 -ffreestanding -fno-pic -nodefaultlibs -nostdlib -nostdinc -I. -fno-rtti -fno-exceptions -Wno-write-strings -fno-stack-protector -mno-red-zone -g -gdwarf-2
|
||||
CXXFLAGS_OPT = -O3 -fomit-frame-pointer
|
||||
CXXFLAGS_WARNING = -Wall -Wextra -Werror -Wno-error=unused-parameter -Wno-non-virtual-dtor
|
||||
CXXFLAGS_CLANG = -no-pie -Wno-error=unused-private-field -Wno-implicit-exception-spec-mismatch -Wno-error=unused-const-variable -Wno-unused-command-line-argument -Wno-unused-const-variable -fno-strict-aliasing
|
||||
CXXFLAGS_GCC = -fno-tree-loop-distribute-patterns -no-pie -nostartfiles -Wstack-usage=1024 -Wno-error=stack-usage= -fno-threadsafe-statics
|
||||
CXXFLAGS_NOFPU = -mno-mmx -mno-sse -mgeneral-regs-only
|
||||
CXXFLAGS = $(CXXFLAGS_ARCH) $(CXXFLAGS_DEFAULT) $(CXXFLAGS_OPT) $(CXXFLAGS_NOFPU) $(CXXFLAGS_WARNING)
|
||||
# Compiler specific flags
|
||||
ifneq (,$(findstring clang,$(CXX)))
|
||||
COMPILER := CLANG
|
||||
CXXFLAGS += $(CXXFLAGS_CLANG)
|
||||
else ifneq (,$(findstring g++,$(CXX)))
|
||||
COMPILER := GCC
|
||||
# g++ 6 does not support general-regs-only flag
|
||||
ifeq "$(shell expr `$(CXX) -dumpversion | cut -f1 -d.` \<= 6)" "1"
|
||||
CXXFLAGS := $(filter-out -mgeneral-regs-only,$(CXXFLAGS))
|
||||
endif
|
||||
CXXFLAGS += $(CXXFLAGS_GCC)
|
||||
else
|
||||
COMPILER :=
|
||||
endif
|
||||
|
||||
# Assembly
|
||||
ASM = nasm
|
||||
ASMFLAGS = -f elf64
|
||||
|
||||
# Additional build utilities
|
||||
OBJCOPY = $(PREFIX)objcopy
|
||||
STRIP = $(PREFIX)strip
|
||||
AR = $(PREFIX)ar
|
||||
|
||||
# C Runtime objects
|
||||
CRTBEGIN_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crtbegin.o)
|
||||
CRTEND_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crtend.o)
|
||||
# GCC library
|
||||
# Attention: libgcc.a must not use red-zone!
|
||||
LIBGCC = $(shell $(CXX) $(CXXFLAGS) -print-libgcc-file-name )
|
||||
|
||||
# Subdirectories with sources
|
||||
VPATH = $(sort $(dir $(CC_SOURCES) $(ASM_SOURCES)))
|
||||
|
||||
# Lists of object files that are generated by compilation:
|
||||
# Note that the variables containing the input files are to be defined by
|
||||
# the Makefiles prior to including this common.mk.
|
||||
ifdef CRTI_SOURCE
|
||||
CRTI_OBJECT = $(addprefix $(BUILDDIR)/,$(addsuffix .o,$(CRTI_SOURCE)))
|
||||
else
|
||||
CRTI_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crti.o)
|
||||
endif
|
||||
ifdef CRTN_SOURCE
|
||||
CRTN_OBJECT = $(addprefix $(BUILDDIR)/,$(addsuffix .o,$(CRTN_SOURCE)))
|
||||
else
|
||||
CRTN_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crtn.o)
|
||||
endif
|
||||
CC_OBJECTS = $(addprefix $(BUILDDIR)/,$(CC_SOURCES:.cc=.o))
|
||||
DEP_FILES = $(addprefix $(BUILDDIR)/,$(CC_SOURCES:.cc=.d) $(addsuffix .d,$(ASM_SOURCES)))
|
||||
ASM_OBJECTS = $(addprefix $(BUILDDIR)/,$(addsuffix .o,$(filter-out $(CRTI_SOURCE) $(CRTN_SOURCE),$(ASM_SOURCES))))
|
||||
|
||||
# Dependency files
|
||||
$(BUILDDIR)/%.d : %.cc $(MAKEFILE_LIST)
|
||||
@echo "DEP $<"
|
||||
@mkdir -p $(@D)
|
||||
$(VERBOSE) $(CXX) $(CXXFLAGS) -MM -MT $(BUILDDIR)/$*.o -MF $@ $<
|
||||
|
||||
$(BUILDDIR)/%.asm.d : %.asm $(MAKEFILE_LIST)
|
||||
@echo "DEP $<"
|
||||
@mkdir -p $(@D)
|
||||
$(VERBOSE) $(ASM) $(ASMFLAGS) -M -MT $(BUILDDIR)/$*.asm.o -MF $@ $<
|
||||
|
||||
# Object files
|
||||
$(BUILDDIR)/%.o : %.cc $(MAKEFILE_LIST)
|
||||
@echo "CXX $<"
|
||||
@mkdir -p $(@D)
|
||||
$(VERBOSE) $(CXX) -c $(CXXFLAGS) -o $@ $<
|
||||
|
||||
$(BUILDDIR)/%.asm.o : %.asm $(MAKEFILE_LIST)
|
||||
@echo "ASM $<"
|
||||
@mkdir -p $(@D)
|
||||
$(VERBOSE) $(ASM) $(ASMFLAGS) -o $@ $<
|
||||
|
||||
# The standard target 'clean' removes the whole generated system, the object files, and the dependency files.
|
||||
clean::
|
||||
@echo "RM $(BUILDDIR)"
|
||||
$(VERBOSE) rm -rf "$(BUILDDIR)" "$(BUILDDIR)$(OPTTAG)" "$(BUILDDIR)$(NOOPTTAG)" "$(BUILDDIR)$(DBGTAG)" "$(BUILDDIR)$(VERBOSETAG)"
|
||||
|
||||
# Target issuing a nested call to make generating a fully optimized systems without assertions.
|
||||
%$(OPTTAG):
|
||||
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(OPTTAG)" ISODIR="$(ISODIR)$(OPTTAG)" CXXFLAGS_OPT="-Ofast -fomit-frame-pointer -flto -march=westmere -DNDEBUG" $*
|
||||
|
||||
# Target issuing a nested call to make generating a non-optimized system.
|
||||
%$(NOOPTTAG):
|
||||
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(NOOPTTAG)" ISODIR="$(ISODIR)$(NOOPTTAG)" CXXFLAGS_OPT="-O0" $*
|
||||
|
||||
# Target issuing a nested call to make generating a system optimized for debugging.
|
||||
%$(DBGTAG):
|
||||
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(DBGTAG)" ISODIR="$(ISODIR)$(DBGTAG)" CXXFLAGS_OPT="-Og -fno-omit-frame-pointer" $*
|
||||
|
||||
# Target issuing a nested call to make generating a system with verbose output.
|
||||
%$(VERBOSETAG):
|
||||
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(VERBOSETAG)" ISODIR="$(ISODIR)$(VERBOSETAG)" CXXFLAGS_OPT="-DVERBOSE" $*
|
||||
|
||||
# Documentation
|
||||
help::
|
||||
@/bin/echo -e "" \
|
||||
"All targets exist in different flavours in addition to \e[2;3m<name>\e[0m:\n" \
|
||||
"\e[2;3m<name>\e[0;3m-noopt\e[0m, \e[2;3m<name>\e[0;3m-opt\e[0m, \e[2;3m<name>\e[0;3m-dbg\e[0m, and \e[2;3m<name>\e[0;3m-verbose\e[0m.\n" \
|
||||
"Targets suffixed with \e[3m-noopt\e[0m are compiled without optimizations,\n" \
|
||||
"\e[3m-opt\e[0m targets produce a highly optimized binary, while\n" \
|
||||
"\e[3m-dbg\e[0m targets only use optimizations not hindering debugging.\n" \
|
||||
"Targets suffixed with \e[3m-verbose\e[0m generate binaries including\n" \
|
||||
"verbose output (via \e[3mDBG_VERBOSE\e[0m), making such targets useful for debugging.\n" \
|
||||
"To get a verbose make output, clear VERBOSE, e.g. \e[3mmake VERBOSE=\e[0m.\n" \
|
||||
"The following targets are available (each target can be suffixed by \e[3m-noopt\e[0m\n" \
|
||||
"and \e[3m-verbose\e[0m):\n\n" \
|
||||
" \e[3mall\e[0m Builds $(PROJECT), generating an ELF binary\n\n"
|
||||
|
||||
# Print warnings, if appropriate
|
||||
ifeq (,$(COMPILER))
|
||||
$(warning Unknown (and potentially unsupported) compiler "$(CXX)"!)
|
||||
endif
|
||||
|
||||
# Include dependency files (generated via gcc flag -MM)
|
||||
ifneq ($(MAKECMDGOALS),clean)
|
||||
-include $(DEP_FILES)
|
||||
endif
|
||||
|
||||
# Phony targets
|
||||
.PHONY: clean help
|
|
@ -0,0 +1,31 @@
|
|||
# Common include Makefile
|
||||
|
||||
# Hide commands
|
||||
VERBOSE = @
|
||||
# Prefix for toolchain binaries
|
||||
PREFIX ?=
|
||||
# Project name
|
||||
PROJECT ?= "MPStuBS"
|
||||
|
||||
help::
|
||||
@/bin/echo -e "\n" \
|
||||
"\e[1mMAKEFILE for the teaching operating system $(PROJECT)\e[0m\n" \
|
||||
"--------------------------------------------------\n\n" \
|
||||
"Executing '\e[4mmake\e[0m' will compile the operating system from source.\n"
|
||||
|
||||
# Get current directory path
|
||||
CURRENT_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
|
||||
|
||||
# Include Makefile scripts
|
||||
include $(CURRENT_DIR)/build.mk
|
||||
include $(CURRENT_DIR)/qemu.mk
|
||||
include $(CURRENT_DIR)/image.mk
|
||||
include $(CURRENT_DIR)/linter.mk
|
||||
include $(CURRENT_DIR)/remote.mk
|
||||
|
||||
# Disable buitlin rules
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
MAKEFLAGS += --no-builtin-variables
|
||||
|
||||
# Disable buitlin suffixes
|
||||
.SUFFIXES:
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
|
||||
import sys
|
||||
# path to local stubs package
|
||||
sys.path.insert(1, './tools/gdb/')
|
||||
# path to checkout of qmp package 'https://pypi.org/project/qemu.qmp/'
|
||||
sys.path.insert(1, '/proj/i4stubs/tools/python/')
|
||||
try:
|
||||
import stubs
|
||||
except Exception as e:
|
||||
print(f"could not load gdb stubs plugin: {str(e)}", file=sys.stderr)
|
||||
pass
|
|
@ -0,0 +1,176 @@
|
|||
|
||||
from . import monitor
|
||||
from . import idt
|
||||
from . import gdt
|
||||
from . import paging
|
||||
|
||||
import gdb
|
||||
import traceback
|
||||
|
||||
qemu = monitor.Monitor('qmp.sock')
|
||||
|
||||
def _active_cr3():
|
||||
i = gdb.selected_inferior()
|
||||
cr3_desc = i.architecture().registers().find('cr3')
|
||||
cr3 = gdb.selected_frame().read_register(cr3_desc)
|
||||
val = cr3.cast(gdb.lookup_type('unsigned long long'))
|
||||
return val
|
||||
|
||||
class PageVisualizer(gdb.Command):
|
||||
"""resolves a virtual adress: vaview [<cr3>] <virtual address>"""
|
||||
|
||||
def __init__(self, monitor):
|
||||
super(PageVisualizer, self).__init__("vaview", gdb.COMMAND_SUPPORT)
|
||||
self.monitor = monitor
|
||||
pass
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
args = gdb.string_to_argv(arg)
|
||||
base = None
|
||||
va = None
|
||||
if len(args) == 1:
|
||||
base = _active_cr3()
|
||||
va = gdb.parse_and_eval(args[0])
|
||||
pass
|
||||
elif len(args) == 2:
|
||||
base = gdb.parse_and_eval(args[0])
|
||||
va = gdb.parse_and_eval(args[1])
|
||||
else:
|
||||
raise gdb.GdbError("vaview [<cr3>] <virtual address>")
|
||||
|
||||
try:
|
||||
base = int(base)
|
||||
if va.type.code == gdb.TYPE_CODE_FUNC:
|
||||
va = int(va.address)
|
||||
else:
|
||||
va = int(va)
|
||||
pass
|
||||
|
||||
mmu = paging.MMU(self.monitor, paging.Arch.X86_64)
|
||||
page, size, offset, entries = mmu.resolve(base, va)
|
||||
|
||||
parts = mmu.split_addr(va)
|
||||
print(
|
||||
f"cr3: 0x{base:x}; vaddr: 0x{va:x} = "
|
||||
f"({ '|'.join([hex(int(p)) for p in parts[0]]) })"
|
||||
)
|
||||
|
||||
for e in entries:
|
||||
print(e)
|
||||
if page is not None and offset is not None:
|
||||
print(f"0x{va:x} -> 0x{page:x}:{offset:x}")
|
||||
else:
|
||||
print(f"0x{va:x} -> <unmapped>")
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise e
|
||||
pass
|
||||
|
||||
def _gdtidtargs(arg, kind):
|
||||
args = gdb.string_to_argv(arg)
|
||||
if len(args) == 0:
|
||||
mapping = _active_cr3()
|
||||
cpuid = current_cpuid()
|
||||
regs = qemu.registers()[cpuid]
|
||||
base, limit = regs[kind]
|
||||
pass
|
||||
elif len(args) == 2:
|
||||
# base, limit
|
||||
mapping = _active_cr3()
|
||||
base = gdb.parse_and_eval(args[0])
|
||||
limit = gdb.parse_and_eval(args[1])
|
||||
try:
|
||||
limit = int(limit)
|
||||
if base.type.code == gdb.TYPE_CODE_FUNC:
|
||||
base = int(base.address)
|
||||
else:
|
||||
base = int(base)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise e
|
||||
pass
|
||||
elif len(args) == 3:
|
||||
# mapping, cr3, limit
|
||||
mapping = gdb.parse_and_eval(args[0])
|
||||
base = gdb.parse_and_eval(args[1])
|
||||
limit = gdb.parse_and_eval(args[2])
|
||||
try:
|
||||
limit = int(limit)
|
||||
if base.type.code == gdb.TYPE_CODE_FUNC:
|
||||
base = int(base.address)
|
||||
else:
|
||||
base = int(base)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise e
|
||||
pass
|
||||
else:
|
||||
raise gdb.GdbError("invalid args")
|
||||
mapping = int(mapping)
|
||||
return mapping, base, limit
|
||||
|
||||
class GDTVisualizer(gdb.Command):
|
||||
"""print the GDT: gdtview [[<mapping>] <base> <limit>]"""
|
||||
|
||||
def __init__(self, monitor):
|
||||
super(GDTVisualizer, self).__init__("gdtview", gdb.COMMAND_SUPPORT)
|
||||
self.monitor = monitor
|
||||
pass
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
mapping, base, limit = _gdtidtargs(arg, 'gdt')
|
||||
mmu = paging.MMU(self.monitor, paging.Arch.X86_64)
|
||||
gdt.GDT(mmu, mapping, base, limit).print()
|
||||
|
||||
class InterruptGateVisualizer(gdb.Command):
|
||||
"""print the IDT: idtview [[<mapping>] <base> <limit>]"""
|
||||
|
||||
def __init__(self, monitor):
|
||||
super(InterruptGateVisualizer, self).__init__(
|
||||
"idtview", gdb.COMMAND_USER)
|
||||
self.monitor = monitor
|
||||
pass
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
mapping, base, limit = _gdtidtargs(args, 'idt')
|
||||
mmu = paging.MMU(self.monitor, paging.Arch.X86_64)
|
||||
try:
|
||||
idt.IDT(mmu, mapping, base, limit).print()
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise e
|
||||
|
||||
class CurrentThread(gdb.Function):
|
||||
"""Fetch the current thread `Dispatcher::life` """
|
||||
def __init__(self):
|
||||
super(CurrentThread, self).__init__("current")
|
||||
pass
|
||||
|
||||
def invoke(self):
|
||||
if not gdb.selected_thread():
|
||||
return None
|
||||
|
||||
cpuid = current_cpuid()
|
||||
|
||||
sym, field = gdb.lookup_symbol('Dispatcher::life')
|
||||
instances = sym.value()['instances']
|
||||
thread_ptr = instances[cpuid]['value']
|
||||
|
||||
return thread_ptr
|
||||
|
||||
def current_cpuid():
|
||||
inferior = gdb.selected_inferior()
|
||||
|
||||
max_threadid = len(inferior.threads())
|
||||
assert (max_threadid > 0)
|
||||
_, threadid, _ = gdb.selected_thread().ptid
|
||||
assert (threadid > 0 and threadid <= max_threadid)
|
||||
|
||||
cpuid = threadid - 1
|
||||
return cpuid
|
||||
pass
|
||||
|
||||
InterruptGateVisualizer(qemu)
|
||||
PageVisualizer(qemu)
|
||||
GDTVisualizer(qemu)
|
||||
CurrentThread()
|
|
@ -0,0 +1,209 @@
|
|||
|
||||
from . import helper
|
||||
|
||||
class SegmentDescriptor(object):
|
||||
pass
|
||||
|
||||
def __init__(self, memview):
|
||||
assert (len(memview) > 7)
|
||||
self.kind = '<generic descriptor>'
|
||||
|
||||
raw = int.from_bytes(memview[0:8], byteorder='little')
|
||||
|
||||
self.base = helper.bits(raw, 16, 24) \
|
||||
| helper.bits(raw, 32 + 24, 8) << 24
|
||||
|
||||
self.limit = helper.bits(raw, 0, 16) \
|
||||
| helper.bits(raw, 32 + 16, 4) << 16
|
||||
|
||||
self.type = helper.bits(raw, 32 + 8, 4)
|
||||
|
||||
self.g = helper.bits(raw, 32 + 23, 1)
|
||||
self.p = helper.bits(raw, 32 + 15, 1)
|
||||
self.l = helper.bits(raw, 32 + 21, 1)
|
||||
self.dpl = helper.bits(raw, 32 + 13, 2)
|
||||
pass
|
||||
|
||||
def create(memview):
|
||||
raw = int.from_bytes(memview[0:8], byteorder='little')
|
||||
feature = helper.bits(raw, 32 + 11, 2)
|
||||
if feature == 2:
|
||||
return DataSegmentDescriptor(memview)
|
||||
elif feature == 3:
|
||||
return CodeSegmentDescriptor(memview)
|
||||
else:
|
||||
feature = helper.bits(raw, 32 + 8, 5)
|
||||
if feature == 0x00:
|
||||
return NullDescriptor(memview)
|
||||
elif feature == 0x0f:
|
||||
return CallGateDescriptor(memview)
|
||||
else:
|
||||
return SystemSegmentDescriptor.create(memview)
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
verbose = self.str_verbose()
|
||||
return f"<{self.kind}> 0x{self.base:x}:{self.limit:x} [{verbose}]"
|
||||
if verbose:
|
||||
return f"<{self.kind}> 0x{self.base:x}:{self.limit:x} [{verbose}]"
|
||||
else:
|
||||
return f"<{self.kind}> 0x{self.base:x}:{self.limit:x}"
|
||||
|
||||
def size(self):
|
||||
return None
|
||||
|
||||
class DataSegmentDescriptor(SegmentDescriptor):
|
||||
def __init__(self, memview):
|
||||
super(DataSegmentDescriptor, self).__init__(memview)
|
||||
self.kind = 'data'
|
||||
|
||||
self.raw = int.from_bytes(memview[0:8], byteorder='little')
|
||||
|
||||
self.b = helper.bits(self.raw, 32 + 22, 1)
|
||||
self.avl = helper.bits(self.raw, 32 + 20, 1)
|
||||
|
||||
self.e = helper.bits(self.raw, 32 + 10, 1)
|
||||
self.w = helper.bits(self.raw, 32 + 9, 1)
|
||||
self.a = helper.bits(self.raw, 32 + 8, 1)
|
||||
|
||||
def str_verbose(self):
|
||||
msg = "|".join([
|
||||
f"g{self.g}", f"b{self.b}", f"l{self.l}",
|
||||
f"avl{self.avl}", f"p{self.p}", f"dpl{self.dpl}",
|
||||
f"e{self.e}", f"w{self.w}", f"a{self.a} {self.raw:x}"
|
||||
])
|
||||
if self.l:
|
||||
msg = msg + '"invalid l"'
|
||||
pass
|
||||
return msg
|
||||
|
||||
def size(self):
|
||||
return 8
|
||||
|
||||
class CodeSegmentDescriptor(SegmentDescriptor):
|
||||
def __init__(self, memview):
|
||||
super(CodeSegmentDescriptor, self).__init__(memview)
|
||||
self.kind = 'code'
|
||||
|
||||
self.raw = int.from_bytes(memview[0:8], byteorder='little')
|
||||
|
||||
self.d = helper.bits(self.raw, 32 + 22, 1)
|
||||
self.avl = helper.bits(self.raw, 32 + 20, 1)
|
||||
|
||||
self.c = helper.bits(self.raw, 32 + 10, 1)
|
||||
self.r = helper.bits(self.raw, 32 + 9, 1)
|
||||
self.a = helper.bits(self.raw, 32 + 8, 1)
|
||||
|
||||
def size(self):
|
||||
return 8
|
||||
|
||||
def str_verbose(self):
|
||||
return "|".join([
|
||||
f"g{self.g}", f"d{self.d}", f"l{self.l}",
|
||||
f"avl{self.avl}", f"p{self.p}", f"dpl{self.dpl}",
|
||||
f"c{self.c}", f"r{self.r}", f"a{self.a}", f"{self.raw:x}"
|
||||
])
|
||||
|
||||
class SystemSegmentDescriptor(SegmentDescriptor):
|
||||
|
||||
def __init__(self, memview):
|
||||
super(SystemSegmentDescriptor, self).__init__(memview)
|
||||
self.kind = 'system'
|
||||
self.raw = int.from_bytes(memview[0:16], byteorder='little')
|
||||
pass
|
||||
|
||||
def create(memview):
|
||||
raw = int.from_bytes(memview[0:8], byteorder='little')
|
||||
type = helper.bits(raw, 32 + 8, 4)
|
||||
masks = [(TSSDescriptor, 0x9)]
|
||||
for ctor, mask in masks:
|
||||
if type & mask == mask:
|
||||
return ctor(memview)
|
||||
pass
|
||||
else:
|
||||
raise Exception("no matching Descriptor")
|
||||
pass
|
||||
|
||||
def size(self):
|
||||
return 16
|
||||
|
||||
def str_verbose(self):
|
||||
return f"raw: {self.raw}"
|
||||
|
||||
class TSSDescriptor(SystemSegmentDescriptor):
|
||||
|
||||
def __init__(self, memview):
|
||||
super(SystemSegmentDescriptor, self).__init__(memview)
|
||||
self.kind = 'tss'
|
||||
self.raw = int.from_bytes(memview[0:16], byteorder='little')
|
||||
|
||||
self.type = helper.bits(self.raw, 32 + 8, 4)
|
||||
|
||||
self.base = helper.bits(self.raw, 16, 24) \
|
||||
| helper.bits(self.raw, 32 + 24, 8) << 24 \
|
||||
| helper.bits(self.raw, 64, 32) << 32
|
||||
|
||||
self.avl = helper.bits(self.raw, 32 + 20, 1)
|
||||
|
||||
self.null = helper.bits(self.raw, 3 * 32 + 8, 5)
|
||||
self.reserved_a = helper.bits(self.raw, 3 * 32 + 13, 32 - 13)
|
||||
self.reserved_b = helper.bits(self.raw, 3 * 32, 8)
|
||||
assert (self.null == 0)
|
||||
|
||||
def str_verbose(self):
|
||||
b = helper.bits(self.raw, 32 + 22, 1)
|
||||
e = helper.bits(self.raw, 32 + 12, 1)
|
||||
msg = "|".join([
|
||||
f"rsvd: {self.reserved_a}", f"0: {self.null}",
|
||||
f"rsvd: {self.reserved_b}" f"g: {self.g}",
|
||||
f"0{b}", f"0{self.l}", f"avl{self.avl}",
|
||||
f"p{self.p}", f"dpl{self.dpl}", f"0{e}",
|
||||
f"type: {self.type:x}", f"{self.raw:x}"
|
||||
])
|
||||
return msg
|
||||
|
||||
class NullDescriptor(SystemSegmentDescriptor):
|
||||
def __init__(self, memview):
|
||||
super(NullDescriptor, self).__init__(memview)
|
||||
self.kind = 'call gate'
|
||||
self.kind = 'null'
|
||||
self.raw = int.from_bytes(memview[0:8], byteorder='little')
|
||||
pass
|
||||
|
||||
def size(self):
|
||||
return 8
|
||||
|
||||
def str_verbose(self):
|
||||
return f"{self.raw:x}"
|
||||
|
||||
class CallGateDescriptor(SystemSegmentDescriptor):
|
||||
def __init__(self, mems):
|
||||
super(CallGateDescriptor, self).__init__(mems)
|
||||
self.kind = 'call gate'
|
||||
pass
|
||||
pass
|
||||
|
||||
class GDT:
|
||||
def __init__(self, mmu, mapping, base, limit):
|
||||
self.mmu = mmu
|
||||
self.mapping = mapping
|
||||
self.limit = limit
|
||||
self.base = base
|
||||
pass
|
||||
|
||||
def print(self):
|
||||
offset = 0
|
||||
print(f"base: 0x{self.base:x}, limit: 0x{self.limit:x}")
|
||||
while offset < self.limit + 1:
|
||||
|
||||
desc_bytes = self.mmu.linear_bytes(
|
||||
self.mapping,
|
||||
self.base + offset,
|
||||
16
|
||||
)
|
||||
|
||||
segment = SegmentDescriptor.create(desc_bytes)
|
||||
print(f"[0x{offset:x}]: {str(segment)}")
|
||||
offset += segment.size()
|
||||
pass
|
||||
pass
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
def compact_entries(entries, cmp_eq):
|
||||
l = []
|
||||
for idx, entry in entries:
|
||||
# prepare first item in list
|
||||
if len(l) == 0:
|
||||
l.append((idx, idx, entry))
|
||||
continue
|
||||
|
||||
# check for duplicate entry
|
||||
if cmp_eq(l[-1][2], entry):
|
||||
old = l.pop()
|
||||
l.append((old[0], idx, old[2]))
|
||||
continue
|
||||
|
||||
l.append((idx, idx, entry))
|
||||
return l
|
||||
|
||||
def bits(value, start, nbits):
|
||||
# drop any bits positioned higher than (start + nbits)
|
||||
mask = 1 << (start + nbits) # mind the overflow in any other language without bigints!
|
||||
mask = mask - 1
|
||||
value = value & mask
|
||||
|
||||
# select only nbits
|
||||
mask = (1 << nbits) - 1
|
||||
mask = mask << start
|
||||
value = value & mask
|
||||
|
||||
# shift result accordingly
|
||||
value = value >> start
|
||||
|
||||
return value
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
import gdb
|
||||
from . import helper
|
||||
|
||||
class IDT:
|
||||
class InterruptTrapGate:
|
||||
def __init__(self, raw):
|
||||
self.raw = raw # 128-bit type
|
||||
self.offset = helper.bits(self.raw, 0, 16) \
|
||||
| helper.bits(self.raw, 32 + 16, 16) << 16\
|
||||
| helper.bits(self.raw, 64, 32) << 32
|
||||
self.segment = helper.bits(self.raw, 16, 16)
|
||||
self.ist = helper.bits(self.raw, 32, 4)
|
||||
self.type = helper.bits(self.raw, 32 + 8, 4)
|
||||
self.dpl = helper.bits(self.raw, 32 + 13, 2)
|
||||
self.p = helper.bits(self.raw, 32 + 15, 1)
|
||||
|
||||
def _get_symbol(self):
|
||||
block = gdb.block_for_pc(self.offset)
|
||||
if block is None:
|
||||
return "?"
|
||||
if block.function is None:
|
||||
return "?"
|
||||
return block.function
|
||||
|
||||
def __str__(self):
|
||||
symbol = self._get_symbol()
|
||||
addr = f"0x{self.segment:x}:0x{self.offset:x}"
|
||||
bits = "|".join([
|
||||
f"off:{self.offset:x}", f"p:{self.p}",
|
||||
f"dpl:{self.dpl}", f"type:{self.type:x}",
|
||||
f"ist:{self.ist:x}", f"ss:{self.segment:x}"
|
||||
])
|
||||
return f"{addr} <{symbol}> [{bits}] raw={self.raw:032x}"
|
||||
|
||||
def __init__(self, mmu, mapping, base, limit):
|
||||
self.mmu = mmu
|
||||
self.mapping = mapping
|
||||
self.base = base
|
||||
self.limit = limit
|
||||
self.desc_size = 16
|
||||
self.nentry = (limit + 1) // self.desc_size
|
||||
pass
|
||||
|
||||
def print(self):
|
||||
entries = []
|
||||
for i in range(0, self.nentry):
|
||||
offset = i * self.desc_size
|
||||
desc_bytes = self.mmu.linear_bytes(
|
||||
self.mapping,
|
||||
self.base + offset,
|
||||
self.desc_size
|
||||
)
|
||||
desc = int.from_bytes(desc_bytes, 'little')
|
||||
gate = self.InterruptTrapGate(desc)
|
||||
entries.append((i, gate))
|
||||
|
||||
def cmp_eq(a, b):
|
||||
return a.offset == b.offset and a.segment == b.segment
|
||||
|
||||
compact = helper.compact_entries(entries, cmp_eq)
|
||||
for start, stop, gate in compact:
|
||||
if start == stop:
|
||||
print(f"[{start}]:\t{str(gate)}")
|
||||
else:
|
||||
print(f"[{start}-{stop}]:\t{str(gate)}")
|
||||
pass
|
||||
pass
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
import asyncio
|
||||
import re
|
||||
from qemu.qmp import QMPClient
|
||||
|
||||
class Monitor:
|
||||
def __init__(self, socket):
|
||||
self.socket = socket
|
||||
self.qmp = QMPClient(f'Monitor: {socket}')
|
||||
pass
|
||||
|
||||
async def _connect(self):
|
||||
await self.qmp.connect(self.socket)
|
||||
|
||||
async def _disconnect(self):
|
||||
await self.qmp.disconnect()
|
||||
|
||||
async def _hmc(self, cmd):
|
||||
raw = await self.qmp.execute(
|
||||
'human-monitor-command', {'command-line': cmd})
|
||||
return raw
|
||||
|
||||
def registers(self):
|
||||
registers = dict()
|
||||
|
||||
async def query_registers():
|
||||
await self._connect()
|
||||
raw = await self._hmc('info registers -a')
|
||||
await self._disconnect()
|
||||
return raw
|
||||
raw = asyncio.run(query_registers())
|
||||
|
||||
# each paragraph of `raw` contains the registers for a logical CPU
|
||||
cpu_split = raw.split('\r\n\r\n')
|
||||
cpu_split_stripped = (x.strip() for x in cpu_split)
|
||||
cpu_split = [s for s in cpu_split_stripped if s]
|
||||
|
||||
# general purpose registers
|
||||
def fetch_gpr(input):
|
||||
registers = dict()
|
||||
gprs = ['RAX', 'RBX', 'RCX', 'RDX', 'RSI', 'RDI',
|
||||
'RBP', 'RSP', 'R8', 'R9', 'R10', 'R11',
|
||||
'R12', 'R13', 'R14', 'R15', 'RIP']
|
||||
for gpr in gprs:
|
||||
pattern = rf"{gpr}\s?=(?P<{gpr}>\w{{16}})"
|
||||
match = re.search(pattern, input)
|
||||
value_raw = match.group(gpr)
|
||||
value = int(value_raw, 16)
|
||||
registers[gpr.lower()] = value
|
||||
return registers
|
||||
|
||||
# control registers
|
||||
def fetch_cr(input):
|
||||
registers = dict()
|
||||
for cr in ['CR0', 'CR2', 'CR3', 'CR4']:
|
||||
pattern = rf"{cr}=(?P<{cr}>\w{{8,16}})"
|
||||
match = re.search(pattern, input)
|
||||
value_raw = match.group(cr)
|
||||
value = int(value_raw, 16)
|
||||
registers[cr.lower()] = value
|
||||
return registers
|
||||
|
||||
# desriptor tables
|
||||
def fetch_dt(input):
|
||||
registers = dict()
|
||||
for tbl in ['GDT', 'IDT']:
|
||||
pattern = rf"{tbl}\s*=\s*(?P<{tbl}_base>\w{{16}})" \
|
||||
rf"\s+(?P<{tbl}_limit>\w{{8}})"
|
||||
match = re.search(pattern, input)
|
||||
base_raw = match.group(f"{tbl}_base")
|
||||
limit_raw = match.group(f"{tbl}_limit")
|
||||
base = int(base_raw, 16)
|
||||
limit = int(limit_raw, 16)
|
||||
registers[tbl.lower()] = (base, limit)
|
||||
return registers
|
||||
|
||||
registers = dict()
|
||||
for cpuid, regstr in enumerate(cpu_split):
|
||||
assert (regstr is not None and len(regstr) > 0)
|
||||
registers[cpuid] = dict()
|
||||
registers[cpuid].update(fetch_gpr(regstr))
|
||||
registers[cpuid].update(fetch_cr(regstr))
|
||||
registers[cpuid].update(fetch_dt(regstr))
|
||||
|
||||
return registers
|
||||
|
||||
def virtual_memory(self, addr, size):
|
||||
# byte, word, double word, giant
|
||||
types = {1: 'b', 2: 'w', 4: 'd', 8: 'g'}
|
||||
assert (size in types)
|
||||
|
||||
async def query_virtual_memory():
|
||||
await self._connect()
|
||||
res = await self._hmc(f"x/x{types[size]} {addr}")
|
||||
await self._disconnect()
|
||||
return res
|
||||
|
||||
res = asyncio.run(query_virtual_memory())
|
||||
match = re.match(r"[a-f\d]+:\s*(0x[a-f\d]+)", res)
|
||||
assert (match)
|
||||
return int(match.group(1), 16)
|
||||
|
||||
def physical_memory(self, addr, size):
|
||||
# byte, word, double word, giant
|
||||
types = {1: 'b', 2: 'w', 4: 'd', 8: 'g'}
|
||||
assert (size in types)
|
||||
|
||||
async def query_physical_memory():
|
||||
await self._connect()
|
||||
res = await self._hmc(f"xp/x{types[size]} {addr}")
|
||||
await self._disconnect()
|
||||
return res
|
||||
|
||||
res = asyncio.run(query_physical_memory())
|
||||
match = re.match(r"[a-f\d]+:\s*(0x[a-f\d]+)", res)
|
||||
assert (match)
|
||||
return int(match.group(1), 16)
|
||||
|
||||
def gva2gpa(self, addr):
|
||||
async def query_gva2gpa():
|
||||
await self._connect()
|
||||
res = await self._hmc(f"gva2gpa {addr}")
|
||||
await self._disconnect()
|
||||
return res
|
||||
|
||||
res = asyncio.run(query_gva2gpa())
|
||||
if res == 'Unmapped\r\n':
|
||||
return None
|
||||
match = re.match(r"gpa:\s*0x([\da-f]+)", res)
|
||||
assert (match)
|
||||
return int(match.group(1), 16)
|
|
@ -0,0 +1,333 @@
|
|||
from enum import Enum
|
||||
from . import helper
|
||||
|
||||
class Arch(Enum):
|
||||
X86 = 0
|
||||
X86_64 = 1
|
||||
|
||||
class PageLevel(Enum):
|
||||
PML4 = 0
|
||||
DirectoryPtr = 1
|
||||
Directory = 2
|
||||
Table = 3
|
||||
|
||||
class PageTableEntry:
|
||||
def __init__(self, base, idx, mmu, value):
|
||||
self.base = base
|
||||
self.idx = idx
|
||||
self.raw = value
|
||||
self.faulty = False
|
||||
|
||||
self.present = helper.bits(value, 0, 1)
|
||||
self.rw = helper.bits(value, 1, 1)
|
||||
self.us = helper.bits(value, 2, 1)
|
||||
self.pwt = helper.bits(value, 3, 1)
|
||||
self.pcd = helper.bits(value, 4, 1)
|
||||
self.a = helper.bits(value, 5, 1)
|
||||
self.ps = helper.bits(value, 7, 1)
|
||||
|
||||
self.r = helper.bits(value, 11, 1)
|
||||
self.xd = helper.bits(value, 63, 1)
|
||||
self.reference = helper.bits(value, 12, mmu.M)
|
||||
self.rsvd = helper.bits(value, mmu.M, 64 - mmu.M)
|
||||
pass
|
||||
|
||||
def addr(self):
|
||||
return self.reference << 12
|
||||
|
||||
def name(self):
|
||||
raise Exception("must be implemented by inheriting class")
|
||||
|
||||
def description(self):
|
||||
raise Exception("must be implemented by inheriting class")
|
||||
|
||||
def __str__(self):
|
||||
return f"<{self.name()}> {self.description()}"
|
||||
|
||||
class PML4Entry(PageTableEntry):
|
||||
def __init__(self, base, idx, mmu, value):
|
||||
super().__init__(base, idx, mmu, value)
|
||||
if (self.ps != 0):
|
||||
self.faulty = True
|
||||
pass
|
||||
|
||||
def name(self):
|
||||
return f"PML4 (0x{self.base:x}[0x{self.idx:x}])"
|
||||
|
||||
def description(self):
|
||||
if not self.present:
|
||||
return f"[p:0|raw: {self.raw:x}]"
|
||||
return "[" + str.join('|', [
|
||||
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
|
||||
f"pcd:{self.pcd}", f"a:{self.a}", f"rsvd:{self.ps}",
|
||||
f"r:{self.r}", f"addr:0x{self.reference:x}",
|
||||
f"rsvd:{self.rsvd}"
|
||||
]) + f"] = {self.raw:x}"
|
||||
pass
|
||||
|
||||
class PDPEntry(PageTableEntry):
|
||||
|
||||
def __init__(self, base, idx, mmu, value):
|
||||
super().__init__(base, idx, mmu, value)
|
||||
self.d = helper.bits(value, 6, 1)
|
||||
self.g = helper.bits(value, 8, 1)
|
||||
self.pat = helper.bits(value, 12, 1)
|
||||
self.xd = helper.bits(value, 63, 1)
|
||||
if self.ps == 1:
|
||||
self.reference = helper.bits(value, 30, mmu.M - (30 - 12))
|
||||
|
||||
def name(self):
|
||||
return f"PDP (0x{self.base:x}[0x{self.idx:x}])"
|
||||
|
||||
def addr(self):
|
||||
if self.ps == 1:
|
||||
return self.reference << (12 + 18)
|
||||
return self.reference << 12
|
||||
|
||||
def description(self):
|
||||
if not self.present:
|
||||
return f"[p:0|raw: {self.raw:x}]"
|
||||
return "[" + str.join('|', [
|
||||
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
|
||||
f"pcd:{self.pcd}", f"a:{self.a}", f"d:{self.d}", f"ps:{self.ps}",
|
||||
f"g:{self.g}", f"r:{self.r}", f"addr:0x{self.reference:x}",
|
||||
f"rsvd:{self.rsvd}"
|
||||
]) + f"] = {self.raw:x}"
|
||||
pass
|
||||
|
||||
class PDEntry(PageTableEntry):
|
||||
|
||||
def __init__(self, base, idx, mmu, value):
|
||||
super().__init__(base, idx, mmu, value)
|
||||
self.d = helper.bits(value, 6, 1)
|
||||
self.g = helper.bits(value, 8, 1)
|
||||
self.pat = helper.bits(value, 12, 1)
|
||||
self.xd = helper.bits(value, 63, 1)
|
||||
if self.ps == 1:
|
||||
self.reference = helper.bits(value, 21, mmu.M - (21 - 12))
|
||||
|
||||
def addr(self):
|
||||
if self.ps == 1:
|
||||
return self.reference << (12 + 9)
|
||||
return self.reference << 12
|
||||
|
||||
def name(self):
|
||||
return f"PD (0x{self.base:x}[0x{self.idx:x}])"
|
||||
|
||||
def description(self):
|
||||
if not self.present:
|
||||
return f"[p:0|raw: {self.raw:x}]"
|
||||
desc = [
|
||||
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
|
||||
f"pcd:{self.pcd}", f"a:{self.a}", f"d:{self.d}", f"ps:{self.ps}",
|
||||
f"g:{self.g}", f"r:{self.r}", f"r:{self.r}"
|
||||
]
|
||||
if self.ps == 1:
|
||||
desc.append(f"pat:{self.pat}")
|
||||
desc = desc + [
|
||||
f"addr:0x{self.reference:x}", f"rsvd:{self.rsvd}", f"xd:{self.xd}"
|
||||
]
|
||||
return "[" + str.join('|', desc) + f"] = {self.raw:x}"
|
||||
pass
|
||||
|
||||
class PTEntry(PageTableEntry):
|
||||
|
||||
def __init__(self, base, idx, mmu, value):
|
||||
super().__init__(base, idx, mmu, value)
|
||||
self.d = helper.bits(value, 6, 1)
|
||||
self.g = helper.bits(value, 8, 1)
|
||||
self.xd = helper.bits(value, 63, 1)
|
||||
|
||||
def name(self):
|
||||
return f"PT (0x{self.base:x}[0x{self.idx:x}])"
|
||||
|
||||
def description(self):
|
||||
if not self.present:
|
||||
return f"[p:0|raw: {self.raw:x}]"
|
||||
desc = [
|
||||
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
|
||||
f"pcd:{self.pcd}", f"a:{self.a}", f"d:{self.d}", f"ps:{self.ps}",
|
||||
f"g:{self.g}", f"r:{self.r}", f"r:{self.r}",
|
||||
f"addr:0x{self.reference:x}", f"rsvd:{self.rsvd}", f"xd:{self.xd}"
|
||||
]
|
||||
return "[" + str.join('|', desc) + f"] = {self.raw:x}"
|
||||
|
||||
class FourLevelPagingTable:
|
||||
entries = 512
|
||||
pass
|
||||
|
||||
def __init__(self, mmu, base):
|
||||
self.mmu = mmu
|
||||
self.base = base
|
||||
self.descriptor_size = 8
|
||||
|
||||
def _create(self, base, idx, mmu, val):
|
||||
pass
|
||||
|
||||
def __iter__(self):
|
||||
table = self
|
||||
|
||||
class TableIterator:
|
||||
def __init__(self):
|
||||
self.idx = 0
|
||||
|
||||
def __next__(self):
|
||||
if self.idx < FourLevelPagingTable.entries:
|
||||
idx = self.idx
|
||||
e = table.entry(self.idx)
|
||||
self.idx = self.idx + 1
|
||||
return idx, e
|
||||
raise StopIteration
|
||||
return TableIterator()
|
||||
|
||||
def name(self):
|
||||
return f"table_{hex(self.base)}"
|
||||
|
||||
def entry(self, idx):
|
||||
offset = idx * self.descriptor_size
|
||||
val = self.mmu.monitor.physical_memory(
|
||||
self.base + offset, self.descriptor_size)
|
||||
return self._create(self.base, idx, self.mmu, val)
|
||||
pass
|
||||
|
||||
class PML4Table(FourLevelPagingTable):
|
||||
|
||||
def _create(self, base, idx, mmu, val):
|
||||
return PML4Entry(base, idx, self.mmu, val)
|
||||
pass
|
||||
|
||||
class PageDirectoryPointerTable(FourLevelPagingTable):
|
||||
|
||||
def _create(self, base, idx, mmu, val):
|
||||
return PDPEntry(base, idx, self.mmu, val)
|
||||
pass
|
||||
|
||||
class PageDirectory(FourLevelPagingTable):
|
||||
|
||||
def _create(self, base, idx, mmu, val):
|
||||
return PDEntry(base, idx, self.mmu, val)
|
||||
pass
|
||||
pass
|
||||
|
||||
class PageTable(FourLevelPagingTable):
|
||||
|
||||
def _create(self, base, idx, mmu, val):
|
||||
return PTEntry(base, idx, self.mmu, val)
|
||||
pass
|
||||
pass
|
||||
|
||||
class MMU:
|
||||
def __init__(self, monitor, arch):
|
||||
self.monitor = monitor
|
||||
if arch == Arch.X86:
|
||||
self.bits = 32
|
||||
self.M = 20
|
||||
pass
|
||||
else:
|
||||
self.bits = 64
|
||||
self.M = 48 # todo check
|
||||
pass
|
||||
assert (self.bits == 64) # TODO x86
|
||||
pass
|
||||
|
||||
def assert_width(self, addr):
|
||||
assert ((1 << self.bits) > addr)
|
||||
pass
|
||||
|
||||
def split_addr(self, addr: int) -> list[int]:
|
||||
parts = []
|
||||
# 4Ki pages
|
||||
parts.append([
|
||||
(addr >> 39) & 0x1ff,
|
||||
(addr >> 30) & 0x1ff,
|
||||
(addr >> 21) & 0x1ff,
|
||||
(addr >> 12) & 0x1ff,
|
||||
addr & 0xfff
|
||||
])
|
||||
# 2Mi pages
|
||||
parts.append([
|
||||
(addr >> 39) & 0x1ff,
|
||||
(addr >> 30) & 0x1ff,
|
||||
(addr >> 21) & 0x1ff,
|
||||
addr & 0x1fffff
|
||||
])
|
||||
|
||||
# 1Gi pages
|
||||
parts.append([
|
||||
(addr >> 39) & 0x1ff,
|
||||
(addr >> 30) & 0x1ff,
|
||||
addr & 0x3fffffff
|
||||
])
|
||||
return parts
|
||||
|
||||
def resolve(self, base: int, addr: int):
|
||||
|
||||
self.assert_width(addr)
|
||||
|
||||
entries = list()
|
||||
parts = self.split_addr(addr)
|
||||
|
||||
pml4_tbl = PML4Table(self, base)
|
||||
pml4_idx = parts[0][0]
|
||||
pml4_entry = pml4_tbl.entry(pml4_idx)
|
||||
entries.append(pml4_entry)
|
||||
|
||||
if not pml4_entry.present:
|
||||
return (None, None, None, entries)
|
||||
|
||||
pdp_tbl = PageDirectoryPointerTable(self, pml4_entry.addr())
|
||||
pdp_idx = parts[0][1]
|
||||
pdp_entry = pdp_tbl.entry(pdp_idx)
|
||||
entries.append(pdp_entry)
|
||||
|
||||
if not pdp_entry.present:
|
||||
return (None, None, None, entries)
|
||||
|
||||
if (pdp_entry.ps):
|
||||
return pdp_entry.addr(), (4096 << 18), parts[2][2], entries
|
||||
|
||||
pd_tbl = PageDirectory(self, pdp_entry.addr())
|
||||
pd_idx = parts[0][2]
|
||||
pd_entry = pd_tbl.entry(pd_idx)
|
||||
entries.append(pd_entry)
|
||||
|
||||
if not pd_entry.present:
|
||||
return (None, None, None, entries)
|
||||
|
||||
if (pd_entry.ps):
|
||||
return pd_entry.addr(), (4096 << 9), parts[1][3], entries
|
||||
|
||||
pt_tbl = PageTable(self, pd_entry.addr())
|
||||
pt_idx = parts[0][3]
|
||||
pt_entry = pt_tbl.entry(pt_idx)
|
||||
entries.append(pt_entry)
|
||||
|
||||
if not pt_entry.present:
|
||||
return (None, None, None, entries)
|
||||
|
||||
physical = pt_entry.addr()
|
||||
return physical, 4096, parts[0][4], entries
|
||||
|
||||
def linear_bytes(self, mapping, addr, len):
|
||||
chunks = []
|
||||
while len > 0:
|
||||
page, size, offset, _ = self.resolve(mapping, addr)
|
||||
# read larger 8 byte blocks
|
||||
if offset % 8 == 0:
|
||||
while offset + 8 < size and len >= 8:
|
||||
b = self.monitor.physical_memory(page + offset, 8)
|
||||
chunks.append(int.to_bytes(b, 8, 'little'))
|
||||
len = len - 8
|
||||
offset = offset + 8
|
||||
pass
|
||||
|
||||
# read single bytes
|
||||
while offset < size and len > 0:
|
||||
b = self.monitor.physical_memory(page + offset, 1)
|
||||
chunks.append(int.to_bytes(b, 1, 'little'))
|
||||
len = len - 1
|
||||
offset = offset + 1
|
||||
pass
|
||||
addr = page + size
|
||||
pass
|
||||
return bytes().join(chunks)
|
|
@ -0,0 +1,101 @@
|
|||
# Generates a bootable ISO image that can be transferred to external media, such as CDs or USB sticks.
|
||||
# This will install, in addition to your kernel, the bootloader GRUB (https://www.gnu.org/software/grub/).
|
||||
#
|
||||
# The target 'gemu-iso' is used to test the image generated with 'iso'.
|
||||
#
|
||||
# Assuming that a USB mass-storage devices is connected as, for instance /dev/sdc, the target 'usb-sdc'
|
||||
# can be used to make your device bootable (requires root access, substitute sdc with the matching device).
|
||||
# Alternatively, you can burn the .iso file directly to CD.
|
||||
|
||||
DD = dd
|
||||
XORRISO = xorriso
|
||||
MKISO = grub-mkrescue
|
||||
|
||||
ISODIR = $(BUILDDIR)-iso
|
||||
ISOGRUBCFG = boot/grub/grub.cfg
|
||||
ISOKERNEL = boot/kernel
|
||||
ISOINITRD = initrd
|
||||
GRUBTITLE = $(shell id -un)s $(PROJECT)
|
||||
GRUBTIMEOUT = 2
|
||||
GRUBBIN = /usr/lib/grub/i386-pc
|
||||
|
||||
# Default ISO target
|
||||
iso: $(ISOFILE)
|
||||
|
||||
# Create Grub config
|
||||
$(ISODIR)/$(ISOGRUBCFG):
|
||||
@echo "GEN $@"
|
||||
@mkdir -p $(dir $@)
|
||||
@/bin/echo -e "set timeout=$(GRUBTIMEOUT)\nset default=0\n\nmenuentry \"$(GRUBTITLE)\" {\n\tmultiboot /$(ISOKERNEL)\n\tmodule /$(ISOINITRD)\n\tboot\n}" > $@
|
||||
|
||||
# Strip debug symbols from kernel binary
|
||||
$(ISODIR)/$(ISOKERNEL): all
|
||||
@echo "STRIP $@"
|
||||
@mkdir -p $(dir $@)
|
||||
$(VERBOSE) $(STRIP) --strip-debug --strip-unneeded -p -o $@ $(KERNEL)
|
||||
|
||||
# copy inital ramdisk
|
||||
$(ISODIR)/$(ISOINITRD): all
|
||||
@echo "CPY $@"
|
||||
@mkdir -p $(dir $@)
|
||||
@if [ -s $(INITRD) ] ; then cp -a $(INITRD) $@ ; else touch $@ ; fi
|
||||
|
||||
# Pack to ISO
|
||||
$(ISOFILE): $(ISODIR)/$(ISOKERNEL) $(ISODIR)/$(ISOINITRD) $(ISODIR)/$(ISOGRUBCFG)
|
||||
@echo "ISO $@"
|
||||
@which $(XORRISO) >/dev/null || echo "Xorriso cannot be found - if building the ISO fails, this may be the reason!" >&2
|
||||
$(VERBOSE) $(MKISO) -d $(GRUBBIN) -o $@ $(ISODIR)
|
||||
|
||||
# Run ISO in Qemu
|
||||
qemu-iso: $(ISOFILE)
|
||||
$(QEMU) -cdrom $< -smp $(QEMUCPUS) $(QEMUFLAGS)
|
||||
|
||||
# Run ISO in KVM
|
||||
kvm-iso: $(ISOFILE)
|
||||
$(QEMU) -cdrom $< -smp $(QEMUCPUS) $(KVMFLAGS)
|
||||
|
||||
# Copy ISO to USB device
|
||||
usb: $(ISOFILE)
|
||||
ifeq (,$(USBDEV))
|
||||
@echo "The environment variable USBDEV must contain the path to the USB mass-storage device:" >&2
|
||||
@lsblk -o TYPE,KNAME,SIZE,MODEL -a -p | grep "^disk" | cut -b 6-
|
||||
@exit 1
|
||||
else
|
||||
$(VERBOSE) $(DD) if=$< of=$(USBDEV) bs=4M status=progress && sync
|
||||
endif
|
||||
|
||||
# Shorthand to copy ISO to a specific USB device
|
||||
usb-%:
|
||||
@$(MAKE) USBDEV=/dev/$* usb
|
||||
|
||||
# Burn ISO to CD
|
||||
cd: $(ISOFILE)
|
||||
ifeq (,$(CDRWDEV))
|
||||
@echo "The environment variable CDRWDEV must contain the path to the CD/DVD writer" >&2
|
||||
@exit 1
|
||||
else
|
||||
$(VERBOSE) $(XORRISO) -as cdrecord -v dev=$(CDRWDEV) -dao $<
|
||||
endif
|
||||
|
||||
# Shorthand to nurn ISO to specific CD device
|
||||
cd-%:
|
||||
@$(MAKE) CDRWDEV=/dev/$* cd
|
||||
|
||||
# The standard target 'clean' removes the whole generated system, the object files, and the dependency files.
|
||||
clean::
|
||||
@echo "RM $(ISODIR)"
|
||||
$(VERBOSE) rm -rf "$(ISODIR)" "$(ISODIR)$(OPTTAG)" "$(ISODIR)$(NOOPTTAG)" "$(ISODIR)$(DBGTAG)" "$(ISODIR)$(VERBOSETAG)"
|
||||
|
||||
# Documentation
|
||||
help::
|
||||
@/bin/echo -e "" \
|
||||
" \e[3miso\e[0m Generates a bootable system image (File: $(ISOFILE))\n\n" \
|
||||
" \e[3mqemu-iso\e[0m Starts the system in QEMU by booting from the virtual CD drive\n\n" \
|
||||
" \e[3mkvm-iso\e[0m Same as \e[3mqemu-iso\e[0m, but with hardware acceleration\n\n" \
|
||||
" \e[3musb\e[0m Generates a bootable USB mass-storage device; the environment\n" \
|
||||
" variable \e[4mUSBDEV\e[0m should point to the USB device\n\n" \
|
||||
" \e[3mcd\e[0m Generates a bootable CD; the environment variable \e[4mCDRWDEV\e[0m\n" \
|
||||
" should point to the CD writer\n\n"
|
||||
|
||||
# Phony targets
|
||||
.PHONY: iso qemu-iso kvm-iso cd usb help
|
|
@ -0,0 +1,30 @@
|
|||
# Perform static code checks
|
||||
|
||||
TIDY ?= clang-tidy
|
||||
CPPLINT ?= /usr/bin/env python3 "$(CURRENT_DIR)/cpplint.py"
|
||||
|
||||
# Check sources with Clang Tidy
|
||||
tidy::
|
||||
ifeq (,$(CC_SOURCES))
|
||||
@echo "(nothing to tidy)"
|
||||
else
|
||||
$(VERBOSE) $(TIDY) --format-style=google -header-filter=.* -warnings-as-errors="readability*" -checks="readability*,google-readability-casting,google-explicit-constructor,bugprone*,-bugprone-easily-swappable-parameters,-bugprone-implicit-widening-of-multiplication-result,-bugprone-narrowing-conversions,-bugprone-reserved-identifier,-readability-else-after-return,-readability-identifier-length,-readability-magic-numbers,-readability-use-anyofallof,-readability-function-cognitive-complexity" $(filter-out utils/png.cc,$(CC_SOURCES)) -- $(CXXFLAGS_ARCH) $(CXXFLAGS_DEFAULT) $(CXXFLAGS_OPT)
|
||||
endif
|
||||
|
||||
# Check sources with cpplint
|
||||
lint::
|
||||
@if $(CPPLINT) --quiet --recursive . ; then \
|
||||
echo "Congratulations, coding style obeyed!" ; \
|
||||
else \
|
||||
echo "Coding style violated -- see CPPLINT.cfg for details" ; \
|
||||
exit 1 ; \
|
||||
fi
|
||||
|
||||
# Documentation
|
||||
help::
|
||||
@/bin/echo -e "" \
|
||||
" \e[3mlint\e[0m Checks the coding style using \e[4mCPPLINT\e[0m\n\n" \
|
||||
" \e[3mtidy\e[0m Uses \e[4mClang Tidy\e[0m for a static code analysis\n\n"
|
||||
|
||||
# Phony targets
|
||||
.PHONY: tidy lint help
|
|
@ -0,0 +1,65 @@
|
|||
# Targets for running and debugging in Qemu/KVM
|
||||
|
||||
QEMUCPUS ?= 4
|
||||
INITRD ?= /dev/null
|
||||
QEMUSERIAL ?= pty
|
||||
QEMUFLAGS = -k en-us -serial $(QEMUSERIAL) -d guest_errors -m 2048
|
||||
# According to qemu(1): "Creates a backend using PulseAudio. This backend is
|
||||
# available on most systems." So we use pa as audiodev.
|
||||
QEMUFLAGS += -audiodev pa,id=stubsad -machine pcspk-audiodev=stubsad
|
||||
# Switch to curses if no graphical output is available
|
||||
ifeq ($(DISPLAY),)
|
||||
QEMUFLAGS += -display curses
|
||||
endif
|
||||
KVMFLAGS = -enable-kvm -cpu host $(QEMUFLAGS)
|
||||
DBGFLAGS = -no-shutdown -no-reboot -qmp unix:qmp.sock,server=on,wait=off -monitor vc
|
||||
DBGKERNEL ?= $(KERNEL64)
|
||||
DBGARCH ?= i386:x86-64
|
||||
QEMU ?= qemu-system-x86_64
|
||||
QEMUKERNEL := -kernel $(KERNEL) -initrd $(INITRD)
|
||||
GDB = $(PREFIX)gdb
|
||||
GDBFLAG = --eval-command="source tools/gdb/stubs.py"
|
||||
|
||||
# Run the kernel in Qemu
|
||||
qemu: all
|
||||
$(QEMU) $(QEMUKERNEL) -smp $(QEMUCPUS) $(QEMUFLAGS)
|
||||
|
||||
# Execute Qemu with activated GDB stub and directly connect GDB to the spawned Qemu.
|
||||
qemu-gdb: all
|
||||
$(GDB) $(GDBFLAG) $(DBGKERNEL) \
|
||||
-ex "set arch $(DBGARCH)" \
|
||||
-ex "target remote | exec $(QEMU) -gdb stdio $(QEMUKERNEL) -smp $(QEMUCPUS) -S $(QEMUFLAGS) $(DBGFLAGS)"
|
||||
|
||||
qemu-gdb-tmux: all
|
||||
tmux new-session -s StuBS -n QEMU bash -c "tmux new-window -n gdb $(GDB) $(DBGKERNEL) -ex 'set arch $(DBGARCH)' -ex 'target remote localhost:1234'; $(QEMU) -s -S $(QEMUKERNEL) -initrd /dev/null -smp $(QEMUCPUS) $(QEMUFLAGS) -display curses $(DBGFLAGS)"
|
||||
|
||||
# Runs StuBS in Qemu with with hardware accelerations (KVM support) enabled
|
||||
# The started emulator provides several virtual CPUs that execute in parallel.
|
||||
kvm: all
|
||||
$(QEMU) $(QEMUKERNEL) -smp $(QEMUCPUS) $(KVMFLAGS)
|
||||
|
||||
# Executes Qemu with KVM suppot with activated GDB stub
|
||||
# and directly connect GDB to the spawned Qemu.
|
||||
# Please note: Software breakpoints may not work before the stubs kernel
|
||||
# has switched to long mode -- so we use a hardware breakpoint to stop
|
||||
# at `kernel_init` (the C++ entry point)
|
||||
kvm-gdb: all
|
||||
$(GDB) $(GDBFLAG) $(DBGKERNEL) \
|
||||
-ex "set arch $(DBGARCH)" \
|
||||
-ex "target remote | exec $(QEMU) -gdb stdio $(QEMUKERNEL) -smp $(QEMUCPUS) -S $(KVMFLAGS) $(DBGFLAGS)" \
|
||||
-ex "hbreak kernel_init" \
|
||||
-ex "continue"
|
||||
|
||||
# Help for Qemu targets
|
||||
help::
|
||||
@/bin/echo -e "" \
|
||||
" \e[3mqemu\e[0m Starts $(PROJECT) in QEMU\n" \
|
||||
" Due to the internal design of QEMU, some things (especially\n" \
|
||||
" race conditions) might behave different compared to hardware!\n\n" \
|
||||
" \e[3mqemu-gdb\e[0m Starts $(PROJECT) in QEMU with internal GDB stub and attaches\n" \
|
||||
" it to a GDB session allowing step-by-step debugging\n\n" \
|
||||
" \e[3mkvm\e[0m Starts $(PROJECT) in KVM, a hardware-accelerated virtual machine\n\n" \
|
||||
" \e[3mkvm-gdb\e[0m Same as \e[3mqemu-gdb\e[0m, but with hardware acceleration\n\n"
|
||||
|
||||
# Phony targets
|
||||
.PHONY: qemu kvm qemu-gdb kvm-gdb help
|
|
@ -0,0 +1,91 @@
|
|||
# use the sys Infrastructure
|
||||
|
||||
SOLUTIONDIR = /fs/stubs/solutions
|
||||
|
||||
SOLUTIONPREFIX = musterloesung-m
|
||||
|
||||
SOLUTIONTMPDIR = .solution
|
||||
|
||||
NETBOOTDIR = /fs/stubs/students
|
||||
# Will use either the username from the ssh configuration or, by default, `whoami`@faui06.cs.fau.de
|
||||
NETBOOTSSH ?= mars.cs.tu-dortmund.de
|
||||
USERNAME ?= $(USER)
|
||||
|
||||
##HALLOFFAMESRC = /proj/i4stubs/halloffame/halloffame.iso
|
||||
#HALLOFFAMEISO = $(SOLUTIONTMPDIR)/halloffame.iso
|
||||
|
||||
# Fetch an ISO including all hall of fame images
|
||||
#$(HALLOFFAMEISO):
|
||||
# $(VERBOSE) echo "Get Hall-Of-Fame ISO" ; \
|
||||
# mkdir -p $(SOLUTIONTMPDIR) ; \
|
||||
# if [ -f "$(HALLOFFAMESRC)" ] ; then \
|
||||
# cp $(HALLOFFAMESRC) $@ ; \
|
||||
# else \
|
||||
# echo "via SSH $(USERNAME)@$(NETBOOTSSH)" ; \
|
||||
# scp $(USERNAME)@$(NETBOOTSSH):$(HALLOFFAMESRC) $@ ; \
|
||||
# fi
|
||||
|
||||
# 'halloffame' starts an ISO including all hall of fame images
|
||||
# with 4 cores and KVM virtualization
|
||||
#halloffame: $(HALLOFFAMEISO)
|
||||
# $(QEMU) -cdrom $< -smp $(QEMUCPUS) $(KVMFLAGS) ; \
|
||||
|
||||
# 'halloffame-old' starts an ISO including all hall of fame images
|
||||
# in compatibility mode (single core, software emulation)
|
||||
#halloffame-old: $(HALLOFFAMEISO)
|
||||
# $(QEMU) -cdrom $< ; \
|
||||
#
|
||||
|
||||
# The target 'netboot' copies the resulting StuBS kernel to the base directory
|
||||
# of our tftp server. The tftp server enables the text systems to boot the image
|
||||
# via pxelinux.
|
||||
netboot: all
|
||||
$(VERBOSE) initrd="$(INITRD)" ; \
|
||||
if [ ! -s "$$initrd" ] ; then \
|
||||
initrd="$(BUILDDIR)/fake-initrd" ; \
|
||||
echo "(none)" > "$$initrd" ; \
|
||||
fi ; \
|
||||
if [ -d "$(NETBOOTDIR)" ] ; then \
|
||||
echo "CPY $(NETBOOTDIR)/$(USERNAME)/kernel" ; \
|
||||
install -m 644 $(KERNEL) $(NETBOOTDIR)/$(USERNAME)/kernel ; \
|
||||
echo "CPY $(NETBOOTDIR)/$(USERNAME)/initrd.img" ; \
|
||||
install -m 644 "$$initrd" $(NETBOOTDIR)/$(USERNAME)/initrd.img ; \
|
||||
else \
|
||||
echo "via SSH $(USERNAME)@$(NETBOOTSSH)" ; \
|
||||
echo "SSH $(USERNAME)@$(NETBOOTSSH)@$(NETBOOTDIR)/$(USERNAME)/kernel" ; \
|
||||
echo "SSH $(USERNAME)@$(NETBOOTSSH)@$(NETBOOTDIR)/$(USERNAME)/initrd.img" ; \
|
||||
tar --mode="u=rw,og=r" --transform='flags=r;s|$(KERNEL)|kernel|' --transform="flags=r;s|$$initrd|initrd.img|" -cz $(KERNEL) "$$initrd" | \
|
||||
ssh "$(USERNAME)@$(NETBOOTSSH)" "cat - | tar -xvzp -C $(NETBOOTDIR)/\`id -run\`/" ; \
|
||||
fi
|
||||
|
||||
# 'solution-1' starts our solution for exercise 1 using KVM.
|
||||
# Of course, other exercise numbers can be supplied, too.
|
||||
solution-%:
|
||||
$(VERBOSE) echo "Solution for $(PROJECT) assignment $*" ; \
|
||||
if [ -d "$(SOLUTIONDIR)" ] ; then \
|
||||
$(QEMU) -kernel $(SOLUTIONDIR)/$(SOLUTIONPREFIX)$*.elf -initrd $(SOLUTIONDIR)/$(SOLUTIONPREFIX)$*.rd -smp $(QEMUCPUS) $(KVMFLAGS) ; \
|
||||
else \
|
||||
echo "via SSH $(USERNAME)@$(NETBOOTSSH)" ; \
|
||||
mkdir -p $(SOLUTIONTMPDIR) ; \
|
||||
bash -c "scp $(USERNAME)@$(NETBOOTSSH):$(SOLUTIONDIR)/$(SOLUTIONPREFIX)$*.{elf,rd} $(SOLUTIONTMPDIR)" && \
|
||||
$(QEMU) -kernel $(SOLUTIONTMPDIR)/$(SOLUTIONPREFIX)$*.elf -initrd $(SOLUTIONTMPDIR)/$(SOLUTIONPREFIX)$*.rd -smp $(QEMUCPUS) $(KVMFLAGS) ; \
|
||||
fi
|
||||
|
||||
# The standard target 'clean' removes the temporary solution directory
|
||||
clean::
|
||||
@echo "RM $(SOLUTIONTMPDIR)"
|
||||
$(VERBOSE) rm -rf "$(SOLUTIONTMPDIR)"
|
||||
|
||||
# Documentation
|
||||
help::
|
||||
@/bin/echo -e "" \
|
||||
" \e[3mnetboot\e[0m Copies $(PROJECT) to the network share, allowing the test systems\n" \
|
||||
" to boot your system. Define the shell variable USERNAME to use a specific user.\n" \
|
||||
" This is useful if your local username is different from the one on mars.\n\n\n" \
|
||||
"Apart from the above targets that run your implementation, our solution can\n" \
|
||||
"be run in KVM (at least when called in the SysLab or IRB pool) using the target\n\n" \
|
||||
" \e[3msolution-\e[2;3mexercise\e[0m\n\n" \
|
||||
"where \e[2;3mexercise\e[0m is the number of the exercise whose solution should be executed.\n\n"
|
||||
|
||||
# Phony targets
|
||||
.PHONY: netboot help
|
|
@ -0,0 +1,65 @@
|
|||
/*! \file
|
||||
* \brief Definition of standard integer types with specified widths and their limits
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Standard Integer Types
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned long long uint64_t;
|
||||
typedef unsigned long long uintptr_t;
|
||||
|
||||
typedef __SIZE_TYPE__ size_t;
|
||||
|
||||
typedef char int8_t;
|
||||
typedef short int16_t;
|
||||
typedef int int32_t;
|
||||
typedef long long int64_t;
|
||||
typedef long long intptr_t;
|
||||
|
||||
typedef long int ssize_t;
|
||||
|
||||
typedef __PTRDIFF_TYPE__ ptrdiff_t;
|
||||
|
||||
// validate typedef size
|
||||
static_assert(sizeof(int8_t) == (1), "Wrong size for 'int8_t'");
|
||||
static_assert(sizeof(int16_t) == (2), "Wrong size for 'int16_t'");
|
||||
static_assert(sizeof(int32_t) == (4), "Wrong size for 'int32_t'");
|
||||
static_assert(sizeof(int64_t) == (8), "Wrong size for 'int64_t'");
|
||||
static_assert(sizeof(intptr_t) == sizeof(void*), "Wrong size for 'intptr_t'");
|
||||
static_assert(sizeof(uint8_t) == (1), "Wrong size for 'uint8_t'");
|
||||
static_assert(sizeof(uint16_t) == (2), "Wrong size for 'uint16_t'");
|
||||
static_assert(sizeof(uint32_t) == (4), "Wrong size for 'uint32_t'");
|
||||
static_assert(sizeof(uint64_t) == (8), "Wrong size for 'uint64_t'");
|
||||
static_assert(sizeof(uintptr_t) == sizeof(void*), "Wrong size for 'uintptr_t'");
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL ((uintptr_t)0)
|
||||
#endif
|
||||
|
||||
// Limits
|
||||
#define INT8_MIN (-__INT8_MAX__-1)
|
||||
#define INT8_MAX (__INT8_MAX__)
|
||||
#define INT16_MIN (-__INT16_MAX__-1)
|
||||
#define INT16_MAX (__INT16_MAX__)
|
||||
#define INT32_MIN (-__INT32_MAX__-1)
|
||||
#define INT32_MAX (__INT32_MAX__)
|
||||
#define INT64_MIN (-__INT64_MAX__-1)
|
||||
#define INT64_MAX (__INT64_MAX__)
|
||||
#define INTPTR_MIN (-__INTPTR_MAX__-1)
|
||||
#define INTPTR_MAX (__INTPTR_MAX__)
|
||||
|
||||
#define UINT8_MAX (__UINT8_MAX__)
|
||||
#define UINT16_MAX (__UINT16_MAX__)
|
||||
#define UINT32_MAX (__UINT32_MAX__)
|
||||
#define UINT64_MAX (__UINT64_MAX__)
|
||||
#define UINTPTR_MAX (__UINTPTR_MAX__)
|
||||
|
||||
#define PTRDIFF_MIN (-__PTRDIFF_MAX__-1)
|
||||
#define PTRDIFF_MAX (__PTRDIFF_MAX__)
|
||||
|
||||
#define SIZE_MAX (__SIZE_MAX__)
|
||||
#define SSIZE_MIN (-__INT64_MAX__-1)
|
||||
#define SSIZE_MAX (__INT64_MAX__)
|
|
@ -0,0 +1,5 @@
|
|||
#include "user/app1/appl.h"
|
||||
|
||||
void Application::action() { //NOLINT
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
/*! \brief Test application
|
||||
*
|
||||
*
|
||||
*/
|
||||
class Application {
|
||||
// Prevent copies and assignments
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
*/
|
||||
|
||||
/*! \brief Contains the application code.
|
||||
*
|
||||
*/
|
||||
void action();
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
#include "user/app2/kappl.h"
|
||||
|
||||
void KeyboardApplication::action() { //NOLINT
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*! \file
|
||||
* \brief \ref KeyboardApplication to test the input
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \brief Keyboard Application
|
||||
*/
|
||||
class KeyboardApplication {
|
||||
// Prevent copies and assignments
|
||||
KeyboardApplication(const KeyboardApplication&) = delete;
|
||||
KeyboardApplication& operator=(const KeyboardApplication&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
*/
|
||||
|
||||
/*! \brief Contains the application code.
|
||||
*
|
||||
*/
|
||||
void action();
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*! \file
|
||||
* \brief General purpose \ref Math "math functions"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \brief Basic math helper functions
|
||||
*/
|
||||
namespace Math {
|
||||
template <typename T>
|
||||
T abs(T a) {
|
||||
return (a >= 0 ? a : -a);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T min(T a, T b) {
|
||||
return a > b ? b : a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T max(T a, T b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
} // namespace Math
|
|
@ -0,0 +1,17 @@
|
|||
/*! \file
|
||||
* \brief Template function to determine the length of an array
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* \brief Helper to retrieve the number of elements in an array
|
||||
* (Warning: template magic)
|
||||
* \param Array
|
||||
* \return Number of elements
|
||||
*/
|
||||
template<class T, size_t N>
|
||||
constexpr size_t size(T (&/*unused*/)[N]) {
|
||||
return N;
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
#include "string.h"
|
||||
#include "types.h"
|
||||
|
||||
extern "C" char *strchrnul(const char *s, int c) {
|
||||
if (s != nullptr) {
|
||||
while(*s != '\0') {
|
||||
if (*s == c) {
|
||||
break;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
return const_cast<char *>(s);
|
||||
}
|
||||
|
||||
extern "C" char *strchr(const char *s, int c) {
|
||||
if (s != nullptr) {
|
||||
s = strchrnul(s, c);
|
||||
if (*s == c) {
|
||||
return const_cast<char *>(s);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
extern "C" int strcmp(const char *s1, const char *s2) {
|
||||
if (s1 == nullptr || s2 == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while(*s1 == *s2++) {
|
||||
if (*s1++ == '\0') {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return static_cast<int>(*s1) - static_cast<int>(*(s2-1));
|
||||
}
|
||||
|
||||
extern "C" int strncmp(const char *s1, const char *s2, size_t n) {
|
||||
if (s1 != nullptr && s2 != nullptr) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (s1[i] != s2[i]) {
|
||||
return static_cast<int>(s1[i]) - static_cast<int>(s2[i]);
|
||||
} else if (s1[i] == '\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" size_t strlen(const char *s) {
|
||||
size_t len = 0;
|
||||
if (s != nullptr) {
|
||||
while (*s++ != '\0') {
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
extern "C" size_t strnlen(const char *s, size_t maxlen) {
|
||||
size_t len = 0;
|
||||
if (s != nullptr) {
|
||||
while (maxlen-- > 0 && *s++ != '\0') {
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
extern "C" char * strcpy(char *dest, const char *src) { //NOLINT
|
||||
char *r = dest;
|
||||
if (dest != nullptr && src != nullptr) {
|
||||
while ((*dest++ = *src++) != '\0') {}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
extern "C" char * strncpy(char *dest, const char *src, size_t n) {
|
||||
char *r = dest;
|
||||
if (dest != nullptr && src != nullptr) {
|
||||
while (*src != '\0' && n-- != 0) {
|
||||
*dest++ = *src++;
|
||||
}
|
||||
while (n-- != 0) {
|
||||
*dest++ = '\0';
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
extern "C" void* memcpy(void * __restrict__ dest, void const * __restrict__ src, size_t size) {
|
||||
uint8_t *destination = reinterpret_cast<uint8_t*>(dest);
|
||||
uint8_t const *source = (uint8_t const*)src;
|
||||
|
||||
for(size_t i = 0; i != size; ++i) {
|
||||
destination[i] = source[i];
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
extern "C" void* memmove(void * dest, void const * src, size_t size) {
|
||||
uint8_t *destination = reinterpret_cast<uint8_t*>(dest);
|
||||
uint8_t const *source = reinterpret_cast<uint8_t const*>(src);
|
||||
|
||||
if(source > destination) {
|
||||
for(size_t i = 0; i != size; ++i) {
|
||||
destination[i] = source[i];
|
||||
}
|
||||
} else {
|
||||
for(size_t i = size; i != 0; --i) {
|
||||
destination[i-1] = source[i-1];
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
extern "C" void* memset(void *dest, int pattern, size_t size) {
|
||||
uint8_t *destination = reinterpret_cast<uint8_t*>(dest);
|
||||
|
||||
for(size_t i = 0; i != size; ++i) {
|
||||
destination[i] = static_cast<uint8_t>(pattern);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
extern "C" int memcmp(const void * s1, const void * s2, size_t n) {
|
||||
const unsigned char * c1 = reinterpret_cast<const unsigned char*>(s1);
|
||||
const unsigned char * c2 = reinterpret_cast<const unsigned char*>(s2);
|
||||
|
||||
for(size_t i = 0; i != n; ++i) {
|
||||
if (c1[i] != c2[i]) {
|
||||
return static_cast<int>(c1[i]) - static_cast<int>(c2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*! \file
|
||||
* \brief General purpose \ref string "String functions"
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*! \defgroup string String function
|
||||
* \brief String functions as provided by `%string.h` in the C standard library
|
||||
*/
|
||||
|
||||
/*! \brief Find the first occurrence of a character in a string
|
||||
* \ingroup string
|
||||
* \param s string to
|
||||
* \param c character to find
|
||||
* \return Pointer to first occurrence of the character
|
||||
* or to null byte at the end of the string if not found
|
||||
*/
|
||||
extern "C" char *strchrnul(const char *s, int c);
|
||||
|
||||
/*! \brief Find the first occurrence of a character in a string
|
||||
* \ingroup string
|
||||
* \param s string to
|
||||
* \param c character to find
|
||||
* \return Pointer to first occurrence of the character
|
||||
* or to nullptr if not found
|
||||
*/
|
||||
extern "C" char *strchr(const char *s, int c);
|
||||
|
||||
/*! \brief Compare two strings
|
||||
* \ingroup string
|
||||
* \param s1 first string
|
||||
* \param s2 second string
|
||||
* \return an integer less than, equal to, or greater than zero if first string is found, respectively,
|
||||
* to be less than, to match, or be greater than second string
|
||||
*/
|
||||
extern "C" int strcmp(const char *s1, const char *s2);
|
||||
|
||||
/*! \brief Compare two strings
|
||||
* \ingroup string
|
||||
* \param s1 first string
|
||||
* \param s2 second string
|
||||
* \param n number of bytes to compare
|
||||
* \return an integer less than, equal to, or greater than zero if the given number of bytes of the first string are
|
||||
* found, respectively, to be less than, to match, or be greater than second string
|
||||
*/
|
||||
extern "C" int strncmp(const char *s1, const char *s2, size_t n);
|
||||
|
||||
/*! \brief Calculate the length of a string
|
||||
* \ingroup string
|
||||
* \param s pointer to a string
|
||||
* \return number of bytes in the string
|
||||
*/
|
||||
extern "C" size_t strlen(const char *s);
|
||||
|
||||
/*! \brief Calculate the length of a string, limited by maxlen
|
||||
* \ingroup string
|
||||
* \param s pointer to a string
|
||||
* \param maxlen upper limit of length to be returned
|
||||
* \return number of bytes in the string, or maxlen -- whichever is smaller
|
||||
*/
|
||||
extern "C" size_t strnlen(const char *s, size_t maxlen);
|
||||
|
||||
/*! \brief Copy the contents of a string
|
||||
* including the terminating null byte (`\0`)
|
||||
* \ingroup string
|
||||
* \param dest destination string buffer
|
||||
* \param src source string buffer
|
||||
* \return a pointer to the destination string buffer
|
||||
* \note Beware of buffer overruns!
|
||||
*/
|
||||
extern "C" char * strcpy(char * dest, const char * src); //NOLINT
|
||||
|
||||
/*! \brief Copy the contents of a string up to a maximum length
|
||||
* or the terminating null byte (`\0`), whatever comes first.
|
||||
* \ingroup string
|
||||
* \param dest destination string buffer
|
||||
* \param src source string buffer
|
||||
* \param n maximum number of bytes to copy
|
||||
* \return a pointer to the destination string buffer
|
||||
* \note If there is no null byte (`\0`) among the first `n` bytes, the destination will not be null-terminated!
|
||||
*/
|
||||
extern "C" char * strncpy(char * dest, const char * src, size_t n);
|
||||
|
||||
/*! \brief Copy a memory area
|
||||
* \ingroup string
|
||||
* \param dest destination buffer
|
||||
* \param src source buffer
|
||||
* \param size number of bytes to copy
|
||||
* \return pointer to destination
|
||||
* \note The memory must not overlap!
|
||||
*/
|
||||
extern "C" void* memcpy(void * __restrict__ dest, void const * __restrict__ src, size_t size);
|
||||
|
||||
/*! \brief Copy a memory area
|
||||
* while the source may overlap with the destination
|
||||
* \ingroup string
|
||||
* \param dest destination buffer
|
||||
* \param src source buffer
|
||||
* \param size number of bytes to copy
|
||||
* \return pointer to destination
|
||||
*/
|
||||
extern "C" void* memmove(void * dest, void const * src, size_t size);
|
||||
|
||||
/*! \brief Fill a memory area with a pattern
|
||||
* \ingroup string
|
||||
* \param dest destination buffer
|
||||
* \param pattern single byte pattern
|
||||
* \param size number of bytes to fill with pattern
|
||||
* \return pointer to destination
|
||||
*/
|
||||
extern "C" void* memset(void *dest, int pattern, size_t size);
|
||||
|
||||
/*! \brief Compare a memory area
|
||||
* \ingroup string
|
||||
* \param s1 first memory buffer
|
||||
* \param s2 second memory buffer
|
||||
* \param n number of bytes to compare
|
||||
* \return an integer less than, equal to, or greater than zero if the first n bytes of s1 is found, respectively,
|
||||
* to be less than, to match, or be greater than the first n bytes of s2.
|
||||
*/
|
||||
extern "C" int memcmp(const void * s1, const void * s2, size_t n);
|
Loading…
Reference in New Issue