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