Compare commits
39 Commits
a1d788fd5f
...
cf60d6734a
Author | SHA1 | Date | |
---|---|---|---|
cf60d6734a | |||
32070e9af4 | |||
1bd27128ae | |||
1160c5d143 | |||
f24af6f800 | |||
f7aa9c6e35 | |||
88ed908faa | |||
9fc8654e7b | |||
903b2d7ead | |||
bf202a42ba | |||
1db789889c | |||
e3bdce6065 | |||
18113a3f4b | |||
8078438d3c | |||
921f68a081 | |||
57a634d4ee | |||
23bb3e1153 | |||
428fca40ae | |||
1309ee7489 | |||
5022c840de | |||
ab5f2b200c | |||
8fb04f9ad0 | |||
a8e8c1fcac | |||
4131baa1a5 | |||
c19af78ae2 | |||
9c515cfab0 | |||
db859ddeb8 | |||
eda305e402 | |||
c03c996a48 | |||
![]() |
b8cd346b0a | ||
622f6d1791 | |||
59df532105 | |||
fa2ff36610 | |||
d6e8bb8859 | |||
8f71fa0771 | |||
fc93078d3f | |||
9cebf49862 | |||
db99e90198 | |||
8378376385 |
@ -9,4 +9,8 @@ trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
|
||||
[*.asm]
|
||||
indent_style = space
|
||||
indent_style = space
|
||||
|
||||
[*.yml]
|
||||
indent_size = space
|
||||
indent_size = 2
|
82
.gitea/workflows/ci.yml
Normal file
82
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,82 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
typos:
|
||||
name: "Typos"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check typos
|
||||
uses: crate-ci/typos@master
|
||||
with:
|
||||
config: ./tools/typos.toml
|
||||
|
||||
clippy:
|
||||
name: "Clippy"
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: "Install Rust"
|
||||
run: |
|
||||
sudo apt update
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
sudo apt install --yes \
|
||||
rustup
|
||||
|
||||
- name: Run clippy
|
||||
working-directory: ./tetros
|
||||
run: cargo clippy --all-targets --all-features
|
||||
|
||||
build:
|
||||
name: "Build"
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: "Install Rust"
|
||||
run: |
|
||||
sudo apt update
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
sudo apt install --yes \
|
||||
rustup nasm python3-requests
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
|
||||
# Upload build output
|
||||
- name: "Save output"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Build output"
|
||||
path: "build/*"
|
||||
retention-days: 7
|
||||
|
||||
#- name: "Publish package (hash)"
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
# run: |
|
||||
# PUBLISH_USER="${{ secrets.PUBLISH_USER }}" \
|
||||
# PUBLISH_KEY="${{ secrets.PUBLISH_KEY }}" \
|
||||
# VERSION="${{ github.sha }}" \
|
||||
# PACKAGE="${{ vars.PACKAGE }}" \
|
||||
# python tools/scripts/publish.py
|
||||
|
||||
- name: "Publish package (latest)"
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
run: |
|
||||
PUBLISH_USER="${{ secrets.PUBLISH_USER }}" \
|
||||
PUBLISH_KEY="${{ secrets.PUBLISH_KEY }}" \
|
||||
VERSION="latest" \
|
||||
PACKAGE="${{ vars.PACKAGE }}" \
|
||||
python tools/scripts/publish.py
|
105
Makefile
105
Makefile
@ -2,30 +2,73 @@ BUILD=./build
|
||||
|
||||
# Default rule
|
||||
.PHONY: default
|
||||
default: all
|
||||
default: $(BUILD)/disk.img
|
||||
|
||||
# Remove all build files
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -drf $(BUILD)
|
||||
cd bootloader; make clean
|
||||
cd tetros; cargo clean
|
||||
|
||||
# Make everything
|
||||
.PHONY: all
|
||||
all: img
|
||||
#
|
||||
# MARK: disk
|
||||
#
|
||||
|
||||
# Make bios bootloader
|
||||
# dd if=./bootloader/build/stage2.bin of=$@ conv=notrunc bs=512 seek=40
|
||||
# Compile tetros as a library
|
||||
# (so that we can link it with a custom linker script)
|
||||
LIB_SRC = ./tetros/Cargo.toml ./tetros/Cargo.lock $(shell find ./tetros/src -type f)
|
||||
$(BUILD)/tetros.lib: $(LIB_SRC)
|
||||
@mkdir -p $(BUILD)
|
||||
cd tetros && \
|
||||
env RUSTFLAGS="-C soft-float" \
|
||||
cargo rustc \
|
||||
-Z build-std=core \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target "./targets/x86-unknown-none.json" \
|
||||
--lib \
|
||||
--release \
|
||||
-- \
|
||||
--emit link="$(CURDIR)/$@"
|
||||
|
||||
.PHONY: img
|
||||
img: $(BUILD)/disk.img
|
||||
$(BUILD)/disk.img:
|
||||
mkdir -p $(BUILD)
|
||||
cd bootloader; make
|
||||
dd if=/dev/zero of=$@ bs=512 count=32
|
||||
dd if=./bootloader/build/512.bin of=$@ conv=notrunc bs=512
|
||||
dd if=./bootloader/build/stage2.bin of=$@ conv=notrunc seek=5 bs=512
|
||||
# Link tetros using custom linker script
|
||||
BIOS_LD = ./tetros/linkers/x86-unknown-none.ld
|
||||
$(BUILD)/tetros.elf: $(BUILD)/tetros.lib $(BIOS_LD)
|
||||
ld \
|
||||
-m elf_i386 \
|
||||
--gc-sections \
|
||||
-z max-page-size=0x1000 \
|
||||
-T "$(BIOS_LD)" \
|
||||
-o "$@" \
|
||||
"$<"
|
||||
|
||||
objcopy --only-keep-debug "$@" "$@.sym"
|
||||
objcopy --strip-debug "$@"
|
||||
|
||||
# Wrap tetros in BIOS loader
|
||||
# Parameters:
|
||||
# - BIOS_SRC: source directory of bios assembly
|
||||
# - STAGE2_SECTOR: the index of the first sector of the stage 2 binary on the disk
|
||||
BIOS_SRC = ./bios
|
||||
STAGE2_SECTOR = 1
|
||||
$(BUILD)/disk.img: $(wildcard $(BIOS_SRC)/*.asm) $(BUILD)/tetros.elf
|
||||
@mkdir -p "$(BUILD)"
|
||||
nasm \
|
||||
-f bin \
|
||||
-D STAGE3=$(BUILD)/tetros.elf \
|
||||
-D STAGE2_SECTOR=$(STAGE2_SECTOR) \
|
||||
-o "$@" \
|
||||
-l "$@.lst" \
|
||||
-i "$(BIOS_SRC)" \
|
||||
"$(BIOS_SRC)/main.asm"
|
||||
|
||||
#
|
||||
# MARK: qemu
|
||||
#
|
||||
# Do not use `-enable-kvm` or `-cpu host`,
|
||||
# this confuses gdb.
|
||||
#
|
||||
|
||||
.PHONY: qemu
|
||||
qemu: $(BUILD)/disk.img
|
||||
qemu-system-i386 \
|
||||
-d cpu_reset \
|
||||
@ -33,8 +76,32 @@ qemu: $(BUILD)/disk.img
|
||||
-smp 1 -m 2048 \
|
||||
-machine q35 \
|
||||
-net none \
|
||||
-serial stdio \
|
||||
-fda "$<"
|
||||
# -gdb tcp::26000 \
|
||||
# -S
|
||||
# -enable-kvm \
|
||||
# -cpu host \
|
||||
|
||||
# Same as qemu, but with no dependency.
|
||||
# Used for remote dev, where build box != run box.
|
||||
.PHONY: qemu-remote
|
||||
qemu-remote:
|
||||
qemu-system-i386 \
|
||||
-d cpu_reset \
|
||||
-no-reboot \
|
||||
-smp 1 -m 2048 \
|
||||
-machine q35 \
|
||||
-net none \
|
||||
-serial stdio \
|
||||
-fda "$(BUILD)/disk.img"
|
||||
|
||||
# Same as qemu, but with gdb options
|
||||
.PHONY: qemu-gdb
|
||||
qemu-gdb: $(BUILD)/disk.img
|
||||
qemu-system-i386 \
|
||||
-d cpu_reset \
|
||||
-no-reboot \
|
||||
-smp 1 -m 2048 \
|
||||
-machine q35 \
|
||||
-net none \
|
||||
-serial stdio \
|
||||
-fda "$<" \
|
||||
-gdb tcp::26000 \
|
||||
-S
|
||||
|
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# TetrOS: bare-metal tetris
|
||||
|
||||
## Features
|
||||
- Compiles to a standalone disk image
|
||||
- Written from scratch using only Nasm and Rust
|
||||
- Custom BIOS bootloader
|
||||
- 32-bit x86 OS
|
||||
- Detailed comments. Read the [makefile](./Makefile), then start in [`./bios/main.asm`](./bios/main.asm).
|
||||
|
||||
|
||||
## Non-Features
|
||||
- Never tested on real hardware
|
||||
- Minimal gameplay and graphics. These features aren't hard to implement, but also don't present any interesting challenges. I have other things to do.
|
||||
|
||||
|
||||
## 🚀 Building and Running
|
||||
- All scripts are in the [makefile](./Makefile).
|
||||
- To build and run, use `make qemu`.
|
||||
- Dependencies: `nasm`, `cargo`, GNU `binutils`, `qemu`
|
||||
- This will NOT work on MacOS. BSD `ld` does not work like GNU `ld`.
|
||||
- Alternatively, a compiled disk image for the latest commit is [here](https://git.betalupi.com/api/packages/Mark/generic/tetros/latest/disk.img). This is the same file produced by `make`.
|
||||
- Download it and run `qemu-system-i386 -d cpu_reset -no-reboot -smp 1 -m 2048 -machine q35 -net none -serial stdio -fda "disk.img"`
|
||||
|
||||
|
||||
## 📜 Resources
|
||||
**Used directly:**
|
||||
- [jdh's video](https://www.youtube.com/watch?v=FaILnmUYS_U)
|
||||
- [The OSDev wiki](https://wiki.osdev.org/Main_Page)
|
||||
- [RedoxOS bootloader](https://gitlab.redox-os.org/redox-os/bootloader)
|
||||
|
||||
**Useful background knowledge:**
|
||||
- [Writing an OS in Rust](https://os.phil-opp.com)
|
||||
- [Operating Systems: From 0 to 1](https://github.com/tuhdo/os01)
|
@ -1,6 +1,6 @@
|
||||
SECTION .text ; cannot use .data
|
||||
|
||||
struc GDTEntry
|
||||
struc GDTEntry ; spell:disable-line
|
||||
.limitl resw 1
|
||||
.basel resw 1
|
||||
.basem resb 1
|
||||
@ -26,21 +26,21 @@ gdt_attr:
|
||||
.accessed equ 1 << 0
|
||||
;system
|
||||
; legacy
|
||||
.tssAvailabe16 equ 0x1
|
||||
.tssAvailabe16 equ 0x1 ; spell:disable-line
|
||||
.ldt equ 0x2
|
||||
.tssBusy16 equ 0x3
|
||||
.call16 equ 0x4
|
||||
.task equ 0x5
|
||||
.interrupt16 equ 0x6
|
||||
.trap16 equ 0x7
|
||||
.tssAvailabe32 equ 0x9
|
||||
.tssAvailabe32 equ 0x9 ; spell:disable-line
|
||||
.tssBusy32 equ 0xB
|
||||
.call32 equ 0xC
|
||||
.interrupt32 equ 0xE
|
||||
.trap32 equ 0xF
|
||||
; long mode
|
||||
.ldt32 equ 0x2
|
||||
.tssAvailabe64 equ 0x9
|
||||
.tssAvailabe64 equ 0x9 ; spell:disable-line
|
||||
.tssBusy64 equ 0xB
|
||||
.call64 equ 0xC
|
||||
.interrupt64 equ 0xE
|
||||
@ -64,27 +64,6 @@ gdt:
|
||||
.null equ $ - gdt
|
||||
dq 0
|
||||
|
||||
.lm64_code equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code
|
||||
at GDTEntry.flags__limith, db gdt_flag.long_mode
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
.lm64_data equ $ - gdt
|
||||
istruc GDTEntry
|
||||
at GDTEntry.limitl, dw 0
|
||||
at GDTEntry.basel, dw 0
|
||||
at GDTEntry.basem, db 0
|
||||
; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it
|
||||
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
|
||||
at GDTEntry.flags__limith, db 0
|
||||
at GDTEntry.baseh, db 0
|
||||
iend
|
||||
|
||||
; All GTD addresses are multiples of 8,
|
||||
; and thus end in three zero bits.
|
||||
;
|
64
bios/main.asm
Normal file
64
bios/main.asm
Normal file
@ -0,0 +1,64 @@
|
||||
sectalign off
|
||||
|
||||
; The following code expects two external macros:
|
||||
; STAGE3, a path to the stage3 binary
|
||||
; STAGE2_SECTOR, the location of stage 2
|
||||
; on the disk, in 512-byte sectors.
|
||||
;
|
||||
; Both of these are set in the makefile.
|
||||
|
||||
; BIOS loads stage 1 at 0x7C00
|
||||
ORG 0x7C00
|
||||
SECTION .text
|
||||
|
||||
; Stage 1 is MBR code, and should fit in LBA 0
|
||||
; (i.e, in the first 512 bytes).
|
||||
%include "stage1.asm"
|
||||
|
||||
; Stage 1 is at most 440 bytes
|
||||
; This limit is set by the GPT spec.
|
||||
; See https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html
|
||||
;
|
||||
; This `times` will throw an error if the subtraction is negative.
|
||||
times 440-($-$$) db 0
|
||||
db 0xee
|
||||
|
||||
; Pad until 512
|
||||
times 510-($-$$) db 0
|
||||
|
||||
; MBR signature.
|
||||
; This tells the BIOS that this disk is bootable.
|
||||
db 0x55
|
||||
db 0xaa
|
||||
|
||||
|
||||
; Include stage 2. This is loaded into memory by stage 1.
|
||||
; (stage 1 loads both stage 2 and stage 3)
|
||||
;
|
||||
; Stage 2 sets up protected mode, sets up the GDT,
|
||||
; and initializes a minimal environment for stage 3.
|
||||
;
|
||||
; On a "real" boot disk, this data will not immediately follow stage 1.
|
||||
; It would be stored in a special disk partition.
|
||||
;
|
||||
; We don't need this kind of complexity here, though, so we store
|
||||
; stage 2 right after stage 1. (This is why STAGE2_SECTOR is 1.)
|
||||
;
|
||||
; This is nice, because the layout of the code on our boot disk
|
||||
; matches the layout of the code in memory. THIS IS NOT USUALLY THE CASE.
|
||||
stage2:
|
||||
%include "stage2.asm"
|
||||
align 512, db 0
|
||||
stage2.end:
|
||||
|
||||
; Pad to 0x3000.
|
||||
; This makes sure that state3 is loaded at the address
|
||||
; the linker expects. Must match the value in `tetros/linkers/x86-unknown-none.ld`.
|
||||
times (0x8000 - 0x7c00)-($-$$) db 0
|
||||
|
||||
; Include stage 3, the binary compiled from Rust sources.
|
||||
stage3:
|
||||
%defstr STAGE3_STR %[STAGE3]
|
||||
incbin STAGE3_STR
|
||||
align 512, db 0
|
||||
.end:
|
@ -1,22 +1,21 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
; provide function for printing in x86 real mode
|
||||
|
||||
; print a string and a newline
|
||||
; CLOBBER
|
||||
; ax
|
||||
; Print a string and a newline
|
||||
;
|
||||
; Clobbers ax
|
||||
print_line:
|
||||
mov al, 13
|
||||
call print_char
|
||||
mov al, 10
|
||||
jmp print_char
|
||||
|
||||
; print a string
|
||||
; IN
|
||||
; Print a string
|
||||
;
|
||||
; Input:
|
||||
; si: points at zero-terminated String
|
||||
; CLOBBER
|
||||
; si, ax
|
||||
;
|
||||
; Clobbers si, ax
|
||||
print:
|
||||
pushf
|
||||
cld
|
||||
@ -30,8 +29,9 @@ print:
|
||||
popf
|
||||
ret
|
||||
|
||||
; print a character
|
||||
; IN
|
||||
; Print a character
|
||||
;
|
||||
; Input:
|
||||
; al: character to print
|
||||
print_char:
|
||||
pusha
|
||||
@ -42,10 +42,11 @@ print_char:
|
||||
ret
|
||||
|
||||
; print a number in hex
|
||||
; IN
|
||||
;
|
||||
; Input:
|
||||
; bx: the number
|
||||
; CLOBBER
|
||||
; al, cx
|
||||
;
|
||||
; Clobbers al, cx
|
||||
print_hex:
|
||||
mov cx, 4
|
||||
.lp:
|
@ -1,37 +1,39 @@
|
||||
USE16
|
||||
|
||||
stage1: ; dl comes with disk
|
||||
; initialize segment registers
|
||||
xor ax, ax
|
||||
stage1:
|
||||
; Initialize segment registers
|
||||
xor ax, ax ; Set ax to 0
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov ss, ax
|
||||
|
||||
; initialize stack
|
||||
; Initialize stack pointer
|
||||
; (stack grows up)
|
||||
mov sp, 0x7C00
|
||||
|
||||
; initialize CS
|
||||
; far jump sets both CS and IP to a known-good state,
|
||||
; we don't know where the BIOS put us at startup.
|
||||
; (could be 0x00:0x7C00, could be 0x7C00:0x00.
|
||||
; Not everybody follows spec.)
|
||||
push ax
|
||||
; Initialize CS
|
||||
;
|
||||
; `retf` sets both CS and IP to a known-good state.
|
||||
; This is necessary because we don't know where the BIOS put us at startup.
|
||||
; (could be 0x00:0x7C00, could be 0x7C00:0x00. Not everybody follows spec.)
|
||||
push ax ; `ax` is still 0
|
||||
push word .set_cs
|
||||
retf
|
||||
|
||||
.set_cs:
|
||||
; save disk number
|
||||
; Save disk number.
|
||||
; BIOS sets `dl` to the number of
|
||||
; the disk we're booting from.
|
||||
mov [disk], dl
|
||||
|
||||
; Print "Stage 1"
|
||||
mov si, stage_msg
|
||||
call print
|
||||
mov al, '1'
|
||||
call print_char
|
||||
call print_line
|
||||
|
||||
|
||||
|
||||
; read CHS gemotry
|
||||
; read CHS gemotry, save into [chs]
|
||||
; CL (bits 0-5) = maximum sector number
|
||||
; CL (bits 6-7) = high bits of max cylinder number
|
||||
; CH = low bits of maximum cylinder number
|
||||
@ -51,11 +53,10 @@ stage1: ; dl comes with disk
|
||||
and cl, 0x3f
|
||||
mov [chs.s], cl
|
||||
|
||||
; disk address of stage 2
|
||||
; (start sector)
|
||||
; First sector of stage 2
|
||||
mov eax, STAGE2_SECTOR
|
||||
|
||||
; where to load stage 2
|
||||
; Where to load stage 2
|
||||
mov bx, stage2
|
||||
|
||||
; length of stage2 + stage3
|
||||
@ -63,36 +64,40 @@ stage1: ; dl comes with disk
|
||||
mov cx, (stage3.end - stage2) / 512
|
||||
mov dx, 0
|
||||
|
||||
; Consume eax, bx, cx, dx
|
||||
; and load code from disk.
|
||||
call load
|
||||
|
||||
jmp stage2.entry
|
||||
|
||||
; load some sectors from disk to a buffer in memory
|
||||
; buffer has to be below 1MiB
|
||||
; IN
|
||||
; Load sectors from disk to memory.
|
||||
; Cannot load more than 1MiB.
|
||||
;
|
||||
; Input:
|
||||
; ax: start sector
|
||||
; bx: offset of buffer
|
||||
; cx: number of sectors (512 Bytes each)
|
||||
; dx: segment of buffer
|
||||
; CLOBBER
|
||||
; ax, bx, cx, dx, si
|
||||
; TODO rewrite to (eventually) move larger parts at once
|
||||
; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end)
|
||||
;
|
||||
; Clobbers ax, bx, cx, dx, si
|
||||
load:
|
||||
; replaced 127 with 1.
|
||||
; see https://stackoverflow.com/questions/58564895/problem-with-bios-int-13h-read-sectors-from-drive
|
||||
; TODO: fix later
|
||||
; Every "replace 1" comment means that the `1`
|
||||
; on that line could be bigger.
|
||||
;
|
||||
; See https://stackoverflow.com/questions/58564895/problem-with-bios-int-13h-read-sectors-from-drive
|
||||
; We have to load one sector at a time to avoid the 1K boundary error.
|
||||
; Would be nice to read more sectors at a time, though, that's faster.
|
||||
|
||||
cmp cx, 1 ;127
|
||||
cmp cx, 1 ; replace 1
|
||||
jbe .good_size
|
||||
|
||||
pusha
|
||||
mov cx, 1; 127
|
||||
mov cx, 1 ; replace 1
|
||||
call load
|
||||
popa
|
||||
add eax, 1; 127
|
||||
add dx, 1 * 512 / 16 ; 127
|
||||
sub cx, 1;127
|
||||
add eax, 1 ; replace 1
|
||||
add dx, 1 * 512 / 16 ; replace 1
|
||||
sub cx, 1 ; replace 1
|
||||
|
||||
jmp load
|
||||
.good_size:
|
||||
@ -101,44 +106,37 @@ load:
|
||||
mov [DAPACK.count], cx
|
||||
mov [DAPACK.seg], dx
|
||||
|
||||
; This should be a subroutine,
|
||||
; but we don't call/ret to save a few bytes.
|
||||
; (we only use this once)
|
||||
;
|
||||
;call print_dapack
|
||||
;print_dapack:
|
||||
mov bx, [DAPACK.addr + 2]
|
||||
; Print the data we're reading
|
||||
; Prints AAAAAAAA#BBBB CCCC:DDDD, where:
|
||||
; - A..A is the lba we're reading (printed in two parts)
|
||||
; - BBBB is the number of sectors we're reading
|
||||
; - CCCC is the index we're writing to
|
||||
; - DDDD is the buffer we're writing to
|
||||
mov bx, [DAPACK.addr + 2] ; last two bytes
|
||||
call print_hex
|
||||
|
||||
mov bx, [DAPACK.addr]
|
||||
mov bx, [DAPACK.addr] ; first two bytes
|
||||
call print_hex
|
||||
|
||||
mov al, '#'
|
||||
call print_char
|
||||
|
||||
mov bx, [DAPACK.count]
|
||||
call print_hex
|
||||
|
||||
mov al, ' '
|
||||
call print_char
|
||||
|
||||
mov bx, [DAPACK.seg]
|
||||
call print_hex
|
||||
|
||||
mov al, ':'
|
||||
call print_char
|
||||
|
||||
mov bx, [DAPACK.buf]
|
||||
call print_hex
|
||||
|
||||
call print_line
|
||||
;ret
|
||||
; End of print_dapack
|
||||
|
||||
|
||||
; Read from disk.
|
||||
; int13h, ah=0x42 does not work on some disks.
|
||||
; use int13h, ah=0x02 in this case.
|
||||
cmp byte [chs.s], 0
|
||||
jne .chs
|
||||
;INT 0x13 extended read does not work on CDROM!
|
||||
|
||||
mov dl, [disk]
|
||||
mov si, DAPACK
|
||||
mov ah, 0x42
|
||||
@ -188,6 +186,10 @@ load:
|
||||
jc error ; carry flag set on error
|
||||
ret
|
||||
|
||||
;
|
||||
; MARK: errors
|
||||
;
|
||||
|
||||
error_chs:
|
||||
mov ah, 0
|
||||
|
||||
@ -200,13 +202,18 @@ error:
|
||||
|
||||
mov si, stage1_error_msg
|
||||
call print
|
||||
call print_line
|
||||
call print_line
|
||||
|
||||
; halt after printing error details
|
||||
.halt:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt
|
||||
|
||||
;
|
||||
; MARK: data
|
||||
;
|
||||
|
||||
%include "print.asm"
|
||||
|
||||
stage_msg: db "Stage ",0
|
||||
@ -215,9 +222,9 @@ stage1_error_msg: db " ERROR",0
|
||||
disk: db 0
|
||||
|
||||
chs:
|
||||
.c: dd 0
|
||||
.h: dd 0
|
||||
.s: dd 0
|
||||
.c: dd 0
|
||||
.h: dd 0
|
||||
.s: dd 0
|
||||
|
||||
DAPACK:
|
||||
db 0x10
|
||||
@ -225,6 +232,4 @@ DAPACK:
|
||||
.count: dw 0 ; int 13 resets this to # of blocks actually read/written
|
||||
.buf: dw 0 ; memory buffer destination address (0:7c00)
|
||||
.seg: dw 0 ; in memory page zero
|
||||
.addr: dq 0 ; put the lba to read in this spot
|
||||
|
||||
db 0xff
|
||||
.addr: dq 0 ; put the lba to read in this spot
|
72
bios/stage2.asm
Normal file
72
bios/stage2.asm
Normal file
@ -0,0 +1,72 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
%include "gdt.asm"
|
||||
%include "thunk.asm"
|
||||
|
||||
stage2.entry:
|
||||
mov si, stage_msg
|
||||
call print
|
||||
mov al, '3'
|
||||
call print_char
|
||||
call print_line
|
||||
|
||||
; enable A20-Line via IO-Port 92, might not work on all motherboards
|
||||
in al, 0x92
|
||||
or al, 2
|
||||
out 0x92, al
|
||||
|
||||
protected_mode:
|
||||
; disable interrupts
|
||||
cli
|
||||
|
||||
; load protected mode GDT
|
||||
lgdt [gdtr]
|
||||
|
||||
; set protected mode bit of cr0
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
|
||||
; far jump to load CS with 32 bit segment
|
||||
; We need to do this because we are entering 32-bit mode,
|
||||
; but the instruction pipeline still has 16-bit instructions.
|
||||
;
|
||||
; gdt.pm32_code is a multiple of 8, so it always ends with three zero bits.
|
||||
; The GDT spec abuses this fact, and uses these last three bits to store other
|
||||
; data (table type and privilege). In this case, 000 is what we need anyway.
|
||||
;
|
||||
; Also note that CS isn't an address in protected mode---it's a GDT descriptor.
|
||||
jmp gdt.pm32_code:protected_mode_inner
|
||||
|
||||
; We can now use 32-bit instructions!
|
||||
USE32
|
||||
|
||||
protected_mode_inner:
|
||||
; load all the other segments with 32 bit data segments
|
||||
mov eax, gdt.pm32_data
|
||||
mov ds, eax
|
||||
mov es, eax
|
||||
mov fs, eax
|
||||
mov gs, eax
|
||||
mov ss, eax
|
||||
|
||||
; Place stage 3 stack at 448 KiB
|
||||
; (512KiB minus 64KiB disk buffer)
|
||||
mov esp, 0x70000
|
||||
|
||||
; push arguments to `start()`
|
||||
mov eax, thunk.int10
|
||||
push eax
|
||||
|
||||
; Call `start()`.
|
||||
; 0x18 skips ELF headers.
|
||||
mov eax, [stage3 + 0x18]
|
||||
call eax
|
||||
|
||||
.halt:
|
||||
; Halt if `start()` ever returns (it shouldn't, but just in case)
|
||||
; Without this, we'll try to execute whatever comes next in memory.
|
||||
cli
|
||||
hlt
|
||||
jmp .halt
|
@ -1,3 +1,10 @@
|
||||
; Thunk allows stage 3 (rust code)
|
||||
; to use interrupts that are not
|
||||
; usually available in protected mode.
|
||||
;
|
||||
; "thunk": a subroutine used to inject
|
||||
; a calculation into another subroutine.
|
||||
|
||||
SECTION .text
|
||||
USE32
|
||||
|
||||
@ -6,18 +13,6 @@ thunk:
|
||||
mov dword [.func], .int10_real
|
||||
jmp .enter
|
||||
|
||||
.int13:
|
||||
mov dword [.func], .int13_real
|
||||
jmp .enter
|
||||
|
||||
.int15:
|
||||
mov dword [.func], .int15_real
|
||||
jmp .enter
|
||||
|
||||
.int16:
|
||||
mov dword [.func], .int16_real
|
||||
jmp .enter
|
||||
|
||||
.func: dd 0
|
||||
.esp: dd 0
|
||||
.cr0: dd 0
|
||||
@ -65,18 +60,6 @@ USE16
|
||||
int 0x10
|
||||
ret
|
||||
|
||||
.int13_real:
|
||||
int 0x13
|
||||
ret
|
||||
|
||||
.int15_real:
|
||||
int 0x15
|
||||
ret
|
||||
|
||||
.int16_real:
|
||||
int 0x16
|
||||
ret
|
||||
|
||||
.pm16:
|
||||
; set segment selectors to protected mode 16-bit
|
||||
mov eax, gdt.pm16_data
|
1
bootloader/.gitignore
vendored
1
bootloader/.gitignore
vendored
@ -1 +0,0 @@
|
||||
build
|
@ -1,83 +0,0 @@
|
||||
# This compiles our bootloader as a static library,
|
||||
# and wraps it in a multistage loader.
|
||||
|
||||
|
||||
BUILD = ./build
|
||||
|
||||
|
||||
.PHONY: all
|
||||
all: $(BUILD)/mbr.bin $(BUILD)/512.bin $(BUILD)/stage2.bin
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -drf $(BUILD)
|
||||
cd bootloader; cargo clean
|
||||
|
||||
# Compile bootloader as library
|
||||
LIB_SRC = ./bootloader/Cargo.toml ./bootloader/Cargo.lock $(shell find ./bootloader/src -type f)
|
||||
$(BUILD)/bootloader.lib: $(LIB_SRC)
|
||||
@mkdir -p $(BUILD)
|
||||
cd bootloader && \
|
||||
env RUSTFLAGS="-C soft-float" \
|
||||
cargo rustc \
|
||||
--manifest-path="./Cargo.toml" \
|
||||
-Z build-std=core,alloc \
|
||||
-Z build-std-features=compiler-builtins-mem \
|
||||
--target "./targets/x86-unknown-none.json" \
|
||||
--lib \
|
||||
--release \
|
||||
-- \
|
||||
--emit link="$(CURDIR)/$@"
|
||||
|
||||
# Link bootloader
|
||||
BIOS_LD = ./bootloader/linkers/x86-unknown-none.ld
|
||||
$(BUILD)/bootloader.elf: $(BUILD)/bootloader.lib $(BIOS_LD)
|
||||
ld \
|
||||
-m elf_i386 \
|
||||
--gc-sections \
|
||||
-z max-page-size=0x1000 \
|
||||
-T "$(BIOS_LD)" \
|
||||
-o "$@" \
|
||||
"$<"
|
||||
|
||||
objcopy --only-keep-debug "$@" "$@.sym"
|
||||
objcopy --strip-debug "$@"
|
||||
|
||||
# Wrap bootloader in three-stage BIOS loader
|
||||
# Parameters:
|
||||
# - BIOS_SRC: source directory of bios assembly
|
||||
# - STAGE3: path to linked stage 3 binary
|
||||
# - STAGE2_SECTOR: the index of the first sector of the stage 2 binary on the disk
|
||||
BIOS_SRC = ./bios
|
||||
STAGE2_SECTOR = 5
|
||||
STAGE3 = $(BUILD)/bootloader.elf
|
||||
$(BUILD)/bios.bin: $(wildcard $(BIOS_SRC)/*.asm) $(STAGE3)
|
||||
@mkdir -p "$(BUILD)"
|
||||
nasm \
|
||||
-f bin \
|
||||
-D STAGE3=$(STAGE3) \
|
||||
-D STAGE2_SECTOR=$(STAGE2_SECTOR) \
|
||||
-o "$@" \
|
||||
-l "$@.lst" \
|
||||
-i "$(BIOS_SRC)" \
|
||||
"$(BIOS_SRC)/main.asm"
|
||||
|
||||
# Extract MBR code (first 440 bytes)
|
||||
# This can be used to embed this mbr in gpt-partitioned disks
|
||||
$(BUILD)/mbr.bin: $(BUILD)/bios.bin
|
||||
@mkdir -p "$(BUILD)"
|
||||
@echo ""
|
||||
dd if="$<" bs=440 count=1 of="$@"
|
||||
|
||||
# Extract full mbr (first 512 bytes)
|
||||
# This can be used to make raw boot disks
|
||||
$(BUILD)/512.bin: $(BUILD)/bios.bin
|
||||
@mkdir -p "$(BUILD)"
|
||||
@echo ""
|
||||
dd if="$<" bs=512 count=1 of="$@"
|
||||
|
||||
# Extract stage 2 (rest of file)
|
||||
$(BUILD)/stage2.bin: $(BUILD)/bios.bin
|
||||
@mkdir -p "$(BUILD)"
|
||||
@echo ""
|
||||
dd if="$<" bs=512 skip=1 of="$@"
|
@ -1,176 +0,0 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
cpuid_required_features:
|
||||
.edx equ cpuid_edx.fpu | cpuid_edx.pse | cpuid_edx.pge | cpuid_edx.fxsr
|
||||
.ecx equ 0
|
||||
|
||||
cpuid_check:
|
||||
; If bit 21 of EFLAGS can be changed, then CPUID is supported
|
||||
pushfd ;Save EFLAGS
|
||||
pushfd ;Store EFLAGS
|
||||
xor dword [esp],0x00200000 ;Invert the ID bit in stored EFLAGS
|
||||
popfd ;Load stored EFLAGS (with ID bit inverted)
|
||||
pushfd ;Store EFLAGS again (ID bit may or may not be inverted)
|
||||
pop eax ;eax = modified EFLAGS (ID bit may or may not be inverted)
|
||||
xor eax,[esp] ;eax = whichever bits were changed
|
||||
popfd ;Restore original EFLAGS
|
||||
test eax,0x00200000 ;eax = zero if ID bit can't be changed, else non-zero
|
||||
jz .no_cpuid
|
||||
|
||||
mov eax, 1
|
||||
cpuid
|
||||
|
||||
and edx, cpuid_required_features.edx
|
||||
cmp edx, cpuid_required_features.edx
|
||||
jne .error
|
||||
|
||||
and ecx, cpuid_required_features.ecx
|
||||
cmp ecx, cpuid_required_features.ecx
|
||||
jne .error
|
||||
|
||||
ret
|
||||
|
||||
.no_cpuid:
|
||||
mov si, .msg_cpuid
|
||||
call print
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
jmp .halt
|
||||
|
||||
.error:
|
||||
push ecx
|
||||
push edx
|
||||
|
||||
mov si, .msg_features
|
||||
call print
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
mov si, .msg_edx
|
||||
call print
|
||||
|
||||
pop ebx
|
||||
push ebx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
pop ebx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_must_contain
|
||||
call print
|
||||
|
||||
mov ebx, cpuid_required_features.edx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
mov ebx, cpuid_required_features.edx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
mov si, .msg_ecx
|
||||
call print
|
||||
|
||||
pop ebx
|
||||
push ebx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
pop ebx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_must_contain
|
||||
call print
|
||||
|
||||
mov ebx, cpuid_required_features.ecx
|
||||
shr ebx, 16
|
||||
call print_hex
|
||||
|
||||
mov ebx, cpuid_required_features.ecx
|
||||
call print_hex
|
||||
|
||||
mov si, .msg_line
|
||||
call print
|
||||
|
||||
.halt:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt
|
||||
|
||||
.msg_cpuid: db "CPUID not supported",0
|
||||
.msg_features: db "Required CPU features are not present",0
|
||||
.msg_line: db 13,10,0
|
||||
.msg_edx: db "EDX ",0
|
||||
.msg_ecx: db "ECX ",0
|
||||
.msg_must_contain: db " must contain ",0
|
||||
|
||||
cpuid_edx:
|
||||
.fpu equ 1 << 0
|
||||
.vme equ 1 << 1
|
||||
.de equ 1 << 2
|
||||
.pse equ 1 << 3
|
||||
.tsc equ 1 << 4
|
||||
.msr equ 1 << 5
|
||||
.pae equ 1 << 6
|
||||
.mce equ 1 << 7
|
||||
.cx8 equ 1 << 8
|
||||
.apic equ 1 << 9
|
||||
.sep equ 1 << 11
|
||||
.mtrr equ 1 << 12
|
||||
.pge equ 1 << 13
|
||||
.mca equ 1 << 14
|
||||
.cmov equ 1 << 15
|
||||
.pat equ 1 << 16
|
||||
.pse_36 equ 1 << 17
|
||||
.psn equ 1 << 18
|
||||
.clfsh equ 1 << 19
|
||||
.ds equ 1 << 21
|
||||
.acpi equ 1 << 22
|
||||
.mmx equ 1 << 23
|
||||
.fxsr equ 1 << 24
|
||||
.sse equ 1 << 25
|
||||
.sse2 equ 1 << 26
|
||||
.ss equ 1 << 27
|
||||
.htt equ 1 << 28
|
||||
.tm equ 1 << 29
|
||||
.ia64 equ 1 << 30
|
||||
.pbe equ 1 << 31
|
||||
|
||||
cpuid_ecx:
|
||||
.sse3 equ 1 << 0
|
||||
.pclmulqdq equ 1 << 1
|
||||
.dtes64 equ 1 << 2
|
||||
.monitor equ 1 << 3
|
||||
.ds_cpl equ 1 << 4
|
||||
.vmx equ 1 << 5
|
||||
.smx equ 1 << 6
|
||||
.est equ 1 << 7
|
||||
.tm2 equ 1 << 8
|
||||
.ssse3 equ 1 << 9
|
||||
.cnxt_id equ 1 << 10
|
||||
.sdbg equ 1 << 11
|
||||
.fma equ 1 << 12
|
||||
.cmpxchg16b equ 1 << 13
|
||||
.xtpr equ 1 << 14
|
||||
.pdcm equ 1 << 15
|
||||
.pcid equ 1 << 17
|
||||
.dca equ 1 << 18
|
||||
.sse4_1 equ 1 << 19
|
||||
.sse4_2 equ 1 << 20
|
||||
.x2apic equ 1 << 21
|
||||
.movbe equ 1 << 22
|
||||
.popcnt equ 1 << 23
|
||||
.tsc_deadline equ 1 << 24
|
||||
.aes equ 1 << 25
|
||||
.xsave equ 1 << 26
|
||||
.osxsave equ 1 << 27
|
||||
.avx equ 1 << 28
|
||||
.f16c equ 1 << 29
|
||||
.rdrand equ 1 << 30
|
||||
.hypervisor equ 1 << 31
|
@ -1,4 +0,0 @@
|
||||
; sector = 512 bytes
|
||||
|
||||
; first sector of stage 2, on disk.
|
||||
%assign PARAM_STAGE2_SECTOR 34
|
@ -1,56 +0,0 @@
|
||||
SECTION .text
|
||||
USE32
|
||||
|
||||
long_mode:
|
||||
.func: dq 0
|
||||
.page_table: dd 0
|
||||
|
||||
.entry:
|
||||
; disable interrupts
|
||||
cli
|
||||
|
||||
; disable paging
|
||||
mov eax, cr0
|
||||
and eax, 0x7FFFFFFF
|
||||
mov cr0, eax
|
||||
|
||||
; enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension
|
||||
mov eax, cr4
|
||||
or eax, 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4
|
||||
mov cr4, eax
|
||||
|
||||
; load long mode GDT
|
||||
lgdt [gdtr]
|
||||
|
||||
; enable long mode
|
||||
mov ecx, 0xC0000080 ; Read from the EFER MSR.
|
||||
rdmsr
|
||||
or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit.
|
||||
wrmsr
|
||||
|
||||
; set page table
|
||||
mov eax, [.page_table]
|
||||
mov cr3, eax
|
||||
|
||||
; enabling paging and protection simultaneously
|
||||
mov eax, cr0
|
||||
or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
|
||||
mov cr0, eax
|
||||
|
||||
; far jump to enable Long Mode and load CS with 64 bit segment
|
||||
jmp gdt.lm64_code:.inner
|
||||
|
||||
USE64
|
||||
|
||||
.inner:
|
||||
; load all the other segments with 64 bit data segments
|
||||
mov rax, gdt.lm64_data
|
||||
mov ds, rax
|
||||
mov es, rax
|
||||
mov fs, rax
|
||||
mov gs, rax
|
||||
mov ss, rax
|
||||
|
||||
; jump to specified function
|
||||
mov rax, [.func]
|
||||
jmp rax
|
@ -1,66 +0,0 @@
|
||||
sectalign off
|
||||
|
||||
; This program expects two external macros:
|
||||
; STAGE3, a path to the stage3 binary
|
||||
; STAGE2_SECTOR, the location of stage 2
|
||||
; on the disk, in 512-byte sectors.
|
||||
; On a gpt disk, this is probably 34.
|
||||
|
||||
; Stage 1 is MBR code, and should fit in LBA 0
|
||||
; (512 bytes). Layout is as follows:
|
||||
; (Format is `offset, length: purpose`)
|
||||
; 0, 424: x86 boot code
|
||||
; 440, 4: Unique disk signature
|
||||
; 444, 2: unknown
|
||||
; 446, 16*4: Array of four legacy MBR records
|
||||
; 510, 2: signature 0x55 0xAA
|
||||
; 512 to end of logical block: reserved
|
||||
;
|
||||
; See https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html
|
||||
|
||||
ORG 0x7C00
|
||||
SECTION .text
|
||||
|
||||
; stage 1 is sector 0, loaded into memory at 0x7C00
|
||||
%include "stage1.asm"
|
||||
|
||||
; Stage 1 is at most 440 bytes
|
||||
times 440-($-$$) db 0
|
||||
db 0xee
|
||||
|
||||
; Pad until 512
|
||||
times 510-($-$$) db 0
|
||||
|
||||
; MBR signature.
|
||||
; This isn't loaded into memory, it's
|
||||
; only here for debugging.
|
||||
db 0x55
|
||||
db 0xaa
|
||||
|
||||
stage2:
|
||||
%include "stage2.asm"
|
||||
align 512, db 0
|
||||
stage2.end:
|
||||
|
||||
; TODO: why? Stage 1 read limit?
|
||||
; Can we make this smaller?
|
||||
; The maximum size of stage2 is 4 KiB,
|
||||
; This fill will throw an error if the subtraction
|
||||
; is negative.
|
||||
times (4*1024)-($-stage2) db 0
|
||||
|
||||
; LEGACY
|
||||
; Pad to 0x13000
|
||||
; This needs to match the value configured
|
||||
; in the stage3 linker script
|
||||
times (0x13000 - 0x7c00)-($-$$) db 0
|
||||
|
||||
stage3:
|
||||
%defstr STAGE3_STR %[STAGE3]
|
||||
incbin STAGE3_STR
|
||||
align 512, db 0
|
||||
.end:
|
||||
|
||||
; TODO: why? Of the disk, or of memory?
|
||||
; the maximum size of the boot loader portion is 384 KiB
|
||||
times (384*1024)-($-$$) db 0
|
@ -1,46 +0,0 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
protected_mode:
|
||||
|
||||
.func: dd 0
|
||||
|
||||
.entry:
|
||||
; disable interrupts
|
||||
cli
|
||||
|
||||
; load protected mode GDT
|
||||
lgdt [gdtr]
|
||||
|
||||
; set protected mode bit of cr0
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
|
||||
; far jump to load CS with 32 bit segment
|
||||
; (we are in 32-bit mode, but instruction pipeline
|
||||
; has 16-bit instructions.
|
||||
jmp gdt.pm32_code:.inner
|
||||
|
||||
; gdt.pm32_code is a multiple of 8, so it always ends with three zero bits.
|
||||
; The GDT spec abuses this fact, and uses these last three bits to store other
|
||||
; data (table type and privilege). In this case, 000 is what we need anyway.
|
||||
;
|
||||
; Also note that CS isn't an address in protected mode---it's a GDT descriptor.
|
||||
|
||||
|
||||
|
||||
USE32
|
||||
|
||||
.inner:
|
||||
; load all the other segments with 32 bit data segments
|
||||
mov eax, gdt.pm32_data
|
||||
mov ds, eax
|
||||
mov es, eax
|
||||
mov fs, eax
|
||||
mov gs, eax
|
||||
mov ss, eax
|
||||
|
||||
; jump to specified function
|
||||
mov eax, [.func]
|
||||
jmp eax
|
@ -1,141 +0,0 @@
|
||||
SECTION .text
|
||||
USE16
|
||||
|
||||
stage2.entry:
|
||||
mov si, stage_msg
|
||||
call print
|
||||
mov al, '3'
|
||||
call print_char
|
||||
call print_line
|
||||
|
||||
|
||||
; check for required features
|
||||
call cpuid_check
|
||||
|
||||
; enable A20-Line via IO-Port 92, might not work on all motherboards
|
||||
in al, 0x92
|
||||
or al, 2
|
||||
out 0x92, al
|
||||
|
||||
mov dword [protected_mode.func], stage3.entry
|
||||
jmp protected_mode.entry
|
||||
|
||||
%include "cpuid.asm"
|
||||
%include "gdt.asm"
|
||||
%include "long_mode.asm"
|
||||
%include "protected_mode.asm"
|
||||
%include "thunk.asm"
|
||||
|
||||
USE32
|
||||
|
||||
stage3.entry:
|
||||
; stage3 stack at 448 KiB (512KiB minus 64KiB disk buffer)
|
||||
mov esp, 0x70000
|
||||
|
||||
; push arguments
|
||||
mov eax, thunk.int16
|
||||
push eax
|
||||
mov eax, thunk.int15
|
||||
push eax
|
||||
mov eax, thunk.int13
|
||||
push eax
|
||||
mov eax, thunk.int10
|
||||
push eax
|
||||
xor eax, eax
|
||||
mov al, [disk]
|
||||
push eax
|
||||
mov eax, kernel.entry
|
||||
push eax
|
||||
mov eax, [stage3 + 0x18]
|
||||
call eax
|
||||
.halt:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt
|
||||
|
||||
kernel:
|
||||
.stack: dq 0
|
||||
.func: dq 0
|
||||
.args: dq 0
|
||||
|
||||
.entry:
|
||||
; page_table: usize
|
||||
mov eax, [esp + 4]
|
||||
mov [long_mode.page_table], eax
|
||||
|
||||
; stack: u64
|
||||
mov eax, [esp + 8]
|
||||
mov [.stack], eax
|
||||
mov eax, [esp + 12]
|
||||
mov [.stack + 4], eax
|
||||
|
||||
; func: u64
|
||||
mov eax, [esp + 16]
|
||||
mov [.func], eax
|
||||
mov eax, [esp + 20]
|
||||
mov [.func + 4], eax
|
||||
|
||||
; args: *const KernelArgs
|
||||
mov eax, [esp + 24]
|
||||
mov [.args], eax
|
||||
|
||||
; long_mode: usize
|
||||
mov eax, [esp + 28]
|
||||
test eax, eax
|
||||
jz .inner32
|
||||
|
||||
mov eax, .inner64
|
||||
mov [long_mode.func], eax
|
||||
jmp long_mode.entry
|
||||
|
||||
.inner32:
|
||||
; disable paging
|
||||
mov eax, cr0
|
||||
and eax, 0x7FFFFFFF
|
||||
mov cr0, eax
|
||||
|
||||
;TODO: PAE (1 << 5)
|
||||
; enable FXSAVE/FXRSTOR, Page Global, and Page Size Extension
|
||||
mov eax, cr4
|
||||
or eax, 1 << 9 | 1 << 7 | 1 << 4
|
||||
mov cr4, eax
|
||||
|
||||
; set page table
|
||||
mov eax, [long_mode.page_table]
|
||||
mov cr3, eax
|
||||
|
||||
; enabling paging and protection simultaneously
|
||||
mov eax, cr0
|
||||
; Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
|
||||
or eax, 1 << 31 | 1 << 16 | 1
|
||||
mov cr0, eax
|
||||
|
||||
; enable FPU
|
||||
;TODO: move to Rust
|
||||
mov eax, cr0
|
||||
and al, 11110011b ; Clear task switched (3) and emulation (2)
|
||||
or al, 00100010b ; Set numeric error (5) monitor co-processor (1)
|
||||
mov cr0, eax
|
||||
fninit
|
||||
|
||||
mov esp, [.stack]
|
||||
mov eax, [.args]
|
||||
push eax
|
||||
mov eax, [.func]
|
||||
call eax
|
||||
.halt32:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt32
|
||||
|
||||
USE64
|
||||
|
||||
.inner64:
|
||||
mov rsp, [.stack]
|
||||
mov rax, [.func]
|
||||
mov rdi, [.args]
|
||||
call rax
|
||||
.halt64:
|
||||
cli
|
||||
hlt
|
||||
jmp .halt64
|
264
bootloader/bootloader/Cargo.lock
generated
264
bootloader/bootloader/Cargo.lock
generated
@ -1,264 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endian-num"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8f59926911ef34d1efb9ea1ee8ca78385df62ce700ccf2bcb149011bd226888"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "linked_list_allocator"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
|
||||
dependencies = [
|
||||
"spinning_top",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "redox-path"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
|
||||
|
||||
[[package]]
|
||||
name = "redox_bootloader"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"linked_list_allocator",
|
||||
"log",
|
||||
"redox_syscall",
|
||||
"redoxfs",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redoxfs"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c8756cd15a32143479f4a723ececcbe2b8e422242924b0628a163826aea8c4d"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"argon2",
|
||||
"base64ct",
|
||||
"endian-num",
|
||||
"libc",
|
||||
"log",
|
||||
"redox-path",
|
||||
"redox_syscall",
|
||||
"seahash",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spinning_top"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
@ -1,5 +0,0 @@
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub use self::x86::*;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod x86;
|
@ -1,31 +0,0 @@
|
||||
use redoxfs::Disk;
|
||||
|
||||
use crate::os::{Os, OsVideoMode};
|
||||
|
||||
pub(crate) mod x32;
|
||||
pub(crate) mod x64;
|
||||
|
||||
pub unsafe fn paging_create<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
kernel_phys: u64,
|
||||
kernel_size: u64,
|
||||
) -> Option<usize> {
|
||||
if crate::KERNEL_64BIT {
|
||||
x64::paging_create(os, kernel_phys, kernel_size)
|
||||
} else {
|
||||
x32::paging_create(os, kernel_phys, kernel_size)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_framebuffer<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
page_phys: usize,
|
||||
framebuffer_phys: u64,
|
||||
framebuffer_size: u64,
|
||||
) -> Option<u64> {
|
||||
if crate::KERNEL_64BIT {
|
||||
x64::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size)
|
||||
} else {
|
||||
x32::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size)
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
use crate::area_add;
|
||||
use crate::os::{Os, OsMemoryEntry, OsMemoryKind, OsVideoMode};
|
||||
use core::slice;
|
||||
use redoxfs::Disk;
|
||||
|
||||
const PAGE_ENTRIES: usize = 1024;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
pub(crate) const PHYS_OFFSET: u32 = 0x8000_0000;
|
||||
|
||||
unsafe fn paging_allocate<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
) -> Option<&'static mut [u32]> {
|
||||
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
|
||||
if !ptr.is_null() {
|
||||
area_add(OsMemoryEntry {
|
||||
base: ptr as u64,
|
||||
size: PAGE_SIZE as u64,
|
||||
kind: OsMemoryKind::Reclaim,
|
||||
});
|
||||
Some(slice::from_raw_parts_mut(ptr as *mut u32, PAGE_ENTRIES))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn paging_create<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
kernel_phys: u64,
|
||||
kernel_size: u64,
|
||||
) -> Option<usize> {
|
||||
let pd = paging_allocate(os)?;
|
||||
//Identity map 1 GiB using 4 MiB pages, also map at PHYS_OFFSET
|
||||
for pd_i in 0..256 {
|
||||
let addr = pd_i as u32 * 0x40_0000;
|
||||
pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1;
|
||||
pd[pd_i + 512] = addr | 1 << 7 | 1 << 1 | 1;
|
||||
}
|
||||
|
||||
// Map kernel_size at kernel offset
|
||||
let mut kernel_mapped = 0;
|
||||
let mut pd_i = 0xC000_0000 / 0x40_0000;
|
||||
while kernel_mapped < kernel_size && pd_i < pd.len() {
|
||||
let pt = paging_allocate(os)?;
|
||||
pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1;
|
||||
pd_i += 1;
|
||||
|
||||
let mut pt_i = 0;
|
||||
while kernel_mapped < kernel_size && pt_i < pt.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
pt[pt_i] = addr as u32 | 1 << 1 | 1;
|
||||
pt_i += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
|
||||
Some(pd.as_ptr() as usize)
|
||||
}
|
||||
|
||||
pub unsafe fn paging_framebuffer<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
page_phys: usize,
|
||||
framebuffer_phys: u64,
|
||||
framebuffer_size: u64,
|
||||
) -> Option<u64> {
|
||||
let framebuffer_virt = 0xD000_0000; // 256 MiB after kernel mapping, but before heap mapping
|
||||
|
||||
let pd = slice::from_raw_parts_mut(page_phys as *mut u32, PAGE_ENTRIES);
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut framebuffer_mapped = 0;
|
||||
let mut pd_i = framebuffer_virt / 0x40_0000;
|
||||
while framebuffer_mapped < framebuffer_size && pd_i < pd.len() {
|
||||
let pt = paging_allocate(os)?;
|
||||
pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1;
|
||||
pd_i += 1;
|
||||
|
||||
let mut pt_i = 0;
|
||||
while framebuffer_mapped < framebuffer_size && pt_i < pt.len() {
|
||||
let addr = framebuffer_phys + framebuffer_mapped;
|
||||
pt[pt_i] = addr as u32 | 1 << 1 | 1;
|
||||
pt_i += 1;
|
||||
framebuffer_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(framebuffer_mapped >= framebuffer_size);
|
||||
|
||||
Some(framebuffer_virt as u64)
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
use core::slice;
|
||||
use redoxfs::Disk;
|
||||
|
||||
use crate::area_add;
|
||||
use crate::os::{Os, OsMemoryEntry, OsMemoryKind, OsVideoMode};
|
||||
|
||||
const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
|
||||
const PAGE_ENTRIES: usize = 512;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
|
||||
|
||||
unsafe fn paging_allocate<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
) -> Option<&'static mut [u64]> {
|
||||
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
|
||||
if !ptr.is_null() {
|
||||
area_add(OsMemoryEntry {
|
||||
base: ptr as u64,
|
||||
size: PAGE_SIZE as u64,
|
||||
kind: OsMemoryKind::Reclaim,
|
||||
});
|
||||
|
||||
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
const PRESENT: u64 = 1;
|
||||
const WRITABLE: u64 = 1 << 1;
|
||||
const LARGE: u64 = 1 << 7;
|
||||
|
||||
pub unsafe fn paging_create<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
kernel_phys: u64,
|
||||
kernel_size: u64,
|
||||
) -> Option<usize> {
|
||||
// Create PML4
|
||||
let pml4 = paging_allocate(os)?;
|
||||
|
||||
{
|
||||
// Create PDP for identity mapping
|
||||
let pdp = paging_allocate(os)?;
|
||||
|
||||
// Link first user and first kernel PML4 entry to PDP
|
||||
pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
|
||||
// Identity map 8 GiB using 2 MiB pages
|
||||
for pdp_i in 0..8 {
|
||||
let pd = paging_allocate(os)?;
|
||||
pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
for pd_i in 0..pd.len() {
|
||||
let addr = pdp_i as u64 * 0x4000_0000 + pd_i as u64 * 0x20_0000;
|
||||
pd[pd_i] = addr | LARGE | WRITABLE | PRESENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create PDP (spanning 512 GiB) for kernel mapping
|
||||
let pdp = paging_allocate(os)?;
|
||||
|
||||
// Link last PML4 entry to PDP
|
||||
pml4[511] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
|
||||
// Create PD (spanning 1 GiB) for kernel mapping.
|
||||
let pd = paging_allocate(os)?;
|
||||
|
||||
// The kernel is mapped at -2^31, i.e. 0xFFFF_FFFF_8000_0000. Since a PD is 1 GiB, link
|
||||
// the second last PDP entry to PD.
|
||||
pdp[510] = pd.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
|
||||
// Map kernel_size bytes to kernel offset, i.e. to the start of the PD.
|
||||
|
||||
let mut kernel_mapped = 0;
|
||||
|
||||
let mut pd_idx = 0;
|
||||
while kernel_mapped < kernel_size && pd_idx < pd.len() {
|
||||
let pt = paging_allocate(os)?;
|
||||
pd[pd_idx] = pt.as_ptr() as u64 | WRITABLE | PRESENT;
|
||||
pd_idx += 1;
|
||||
|
||||
let mut pt_idx = 0;
|
||||
while kernel_mapped < kernel_size && pt_idx < pt.len() {
|
||||
let addr = kernel_phys + kernel_mapped;
|
||||
pt[pt_idx] = addr | WRITABLE | PRESENT;
|
||||
pt_idx += 1;
|
||||
kernel_mapped += PAGE_SIZE as u64;
|
||||
}
|
||||
}
|
||||
assert!(kernel_mapped >= kernel_size);
|
||||
}
|
||||
|
||||
Some(pml4.as_ptr() as usize)
|
||||
}
|
||||
|
||||
pub unsafe fn paging_framebuffer<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
page_phys: usize,
|
||||
framebuffer_phys: u64,
|
||||
framebuffer_size: u64,
|
||||
) -> Option<u64> {
|
||||
//TODO: smarter test for framebuffer already mapped
|
||||
if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 {
|
||||
return Some(framebuffer_phys + PHYS_OFFSET);
|
||||
}
|
||||
|
||||
let pml4_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize;
|
||||
let mut pdp_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize;
|
||||
let mut pd_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize;
|
||||
assert_eq!(framebuffer_phys % 0x20_0000, 0);
|
||||
|
||||
let pml4 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
|
||||
|
||||
// Create PDP for framebuffer mapping
|
||||
let pdp = if pml4[pml4_i] == 0 {
|
||||
let pdp = paging_allocate(os)?;
|
||||
pml4[pml4_i] = pdp.as_ptr() as u64 | 1 << 1 | 1;
|
||||
pdp
|
||||
} else {
|
||||
slice::from_raw_parts_mut(
|
||||
(pml4[pml4_i] & ENTRY_ADDRESS_MASK) as *mut u64,
|
||||
PAGE_ENTRIES,
|
||||
)
|
||||
};
|
||||
|
||||
// Map framebuffer_size at framebuffer offset
|
||||
let mut framebuffer_mapped = 0;
|
||||
while framebuffer_mapped < framebuffer_size && pdp_i < pdp.len() {
|
||||
let pd = paging_allocate(os)?;
|
||||
assert_eq!(pdp[pdp_i], 0);
|
||||
pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1;
|
||||
|
||||
while framebuffer_mapped < framebuffer_size && pd_i < pd.len() {
|
||||
let addr = framebuffer_phys + framebuffer_mapped;
|
||||
assert_eq!(pd[pd_i], 0);
|
||||
pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1;
|
||||
framebuffer_mapped += 0x20_0000;
|
||||
pd_i += 1;
|
||||
}
|
||||
|
||||
pdp_i += 1;
|
||||
pd_i = 0;
|
||||
}
|
||||
assert!(framebuffer_mapped >= framebuffer_size);
|
||||
|
||||
Some(framebuffer_phys + PHYS_OFFSET)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
use log::{LevelFilter, Log, Metadata, Record};
|
||||
|
||||
pub static LOGGER: Logger = Logger;
|
||||
|
||||
pub struct Logger;
|
||||
|
||||
impl Logger {
|
||||
pub fn init(&'static self) {
|
||||
log::set_logger(self).unwrap();
|
||||
log::set_max_level(LevelFilter::Info);
|
||||
}
|
||||
}
|
||||
|
||||
impl Log for Logger {
|
||||
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record<'_>) {
|
||||
if self.enabled(record.metadata()) {
|
||||
println!("{} - {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
@ -1,642 +0,0 @@
|
||||
#![no_std]
|
||||
#![feature(alloc_error_handler)]
|
||||
#![feature(int_roundings)]
|
||||
#![feature(lang_items)]
|
||||
#![allow(internal_features)]
|
||||
#![feature(let_chains)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::{format, string::String, vec::Vec};
|
||||
use core::{
|
||||
cmp,
|
||||
fmt::{self, Write},
|
||||
mem, ptr, slice, str,
|
||||
};
|
||||
use redoxfs::Disk;
|
||||
|
||||
use self::arch::{paging_create, paging_framebuffer};
|
||||
use self::os::{Os, OsHwDesc, OsKey, OsMemoryEntry, OsMemoryKind, OsVideoMode};
|
||||
|
||||
#[macro_use]
|
||||
mod os;
|
||||
|
||||
mod arch;
|
||||
mod logger;
|
||||
mod serial_16550;
|
||||
|
||||
const KIBI: usize = 1024;
|
||||
const MIBI: usize = KIBI * KIBI;
|
||||
|
||||
//TODO: allocate this in a more reasonable manner
|
||||
static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry {
|
||||
base: 0,
|
||||
size: 0,
|
||||
kind: OsMemoryKind::Null,
|
||||
}; 1024];
|
||||
static mut AREAS_LEN: usize = 0;
|
||||
|
||||
pub fn area_add(area: OsMemoryEntry) {
|
||||
unsafe {
|
||||
for existing_area in &mut AREAS[0..AREAS_LEN] {
|
||||
if existing_area.kind == area.kind {
|
||||
if existing_area.base.unchecked_add(existing_area.size) == area.base {
|
||||
existing_area.size += area.size;
|
||||
return;
|
||||
}
|
||||
if area.base.unchecked_add(area.size) == existing_area.base {
|
||||
existing_area.base = area.base;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
*AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area;
|
||||
AREAS_LEN += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub static mut KERNEL_64BIT: bool = false;
|
||||
|
||||
pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None;
|
||||
|
||||
struct SliceWriter<'a> {
|
||||
slice: &'a mut [u8],
|
||||
i: usize,
|
||||
}
|
||||
|
||||
impl Write for SliceWriter<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for b in s.bytes() {
|
||||
if let Some(slice_b) = self.slice.get_mut(self.i) {
|
||||
*slice_b = b;
|
||||
self.i += 1;
|
||||
} else {
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
#[repr(C, packed(8))]
|
||||
pub struct KernelArgs {
|
||||
kernel_base: u64,
|
||||
kernel_size: u64,
|
||||
stack_base: u64,
|
||||
stack_size: u64,
|
||||
env_base: u64,
|
||||
env_size: u64,
|
||||
|
||||
/// The base pointer to the saved RSDP.
|
||||
///
|
||||
/// This field can be NULL, and if so, the system has not booted with UEFI or in some other way
|
||||
/// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS
|
||||
/// memory instead. On UEFI systems, searching is not guaranteed to actually work though.
|
||||
acpi_rsdp_base: u64,
|
||||
/// The size of the RSDP region.
|
||||
acpi_rsdp_size: u64,
|
||||
|
||||
areas_base: u64,
|
||||
areas_size: u64,
|
||||
|
||||
bootstrap_base: u64,
|
||||
bootstrap_size: u64,
|
||||
}
|
||||
|
||||
fn select_mode<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
output_i: usize,
|
||||
) -> Option<OsVideoMode> {
|
||||
let mut modes = Vec::new();
|
||||
for mode in os.video_modes(output_i) {
|
||||
let mut aspect_w = mode.width;
|
||||
let mut aspect_h = mode.height;
|
||||
for i in 2..cmp::min(aspect_w / 2, aspect_h / 2) {
|
||||
while aspect_w % i == 0 && aspect_h % i == 0 {
|
||||
aspect_w /= i;
|
||||
aspect_h /= i;
|
||||
}
|
||||
}
|
||||
|
||||
modes.push((
|
||||
mode,
|
||||
format!(
|
||||
"{:>4}x{:<4} {:>3}:{:<3}",
|
||||
mode.width, mode.height, aspect_w, aspect_h
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if modes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Sort modes by pixel area, reversed
|
||||
modes.sort_by(|a, b| (b.0.width * b.0.height).cmp(&(a.0.width * a.0.height)));
|
||||
|
||||
// Set selected based on best resolution
|
||||
print!("Output {}", output_i);
|
||||
let mut selected = modes.get(0).map_or(0, |x| x.0.id);
|
||||
if let Some((best_width, best_height)) = os.best_resolution(output_i) {
|
||||
print!(", best resolution: {}x{}", best_width, best_height);
|
||||
for (mode, _text) in modes.iter() {
|
||||
if mode.width == best_width && mode.height == best_height {
|
||||
selected = mode.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("Arrow keys and enter select mode");
|
||||
println!();
|
||||
print!(" ");
|
||||
|
||||
let (off_x, off_y) = os.get_text_position();
|
||||
let rows = 12;
|
||||
let mut mode_opt = None;
|
||||
while !modes.is_empty() {
|
||||
let mut row = 0;
|
||||
let mut col = 0;
|
||||
for (mode, text) in modes.iter() {
|
||||
if row >= rows {
|
||||
col += 1;
|
||||
row = 0;
|
||||
}
|
||||
|
||||
os.set_text_position(off_x + col * 20, off_y + row);
|
||||
os.set_text_highlight(mode.id == selected);
|
||||
|
||||
print!("{}", text);
|
||||
|
||||
row += 1;
|
||||
}
|
||||
|
||||
// Read keypress
|
||||
match os.get_key() {
|
||||
OsKey::Left => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
if mode_i < rows {
|
||||
while mode_i < modes.len() {
|
||||
mode_i += rows;
|
||||
}
|
||||
}
|
||||
mode_i -= rows;
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Right => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
mode_i += rows;
|
||||
if mode_i >= modes.len() {
|
||||
mode_i = mode_i % rows;
|
||||
}
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Up => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
if mode_i % rows == 0 {
|
||||
mode_i += rows;
|
||||
if mode_i > modes.len() {
|
||||
mode_i = modes.len();
|
||||
}
|
||||
}
|
||||
mode_i -= 1;
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Down => {
|
||||
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
mode_i += 1;
|
||||
if mode_i % rows == 0 {
|
||||
mode_i -= rows;
|
||||
}
|
||||
if mode_i >= modes.len() {
|
||||
mode_i = mode_i - mode_i % rows;
|
||||
}
|
||||
if let Some(new) = modes.get(mode_i) {
|
||||
selected = new.0.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
OsKey::Enter => {
|
||||
if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) {
|
||||
if let Some((mode, _text)) = modes.get(mode_i) {
|
||||
mode_opt = Some(*mode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
os.set_text_position(0, off_y + rows);
|
||||
os.set_text_highlight(false);
|
||||
println!();
|
||||
|
||||
mode_opt
|
||||
}
|
||||
|
||||
fn redoxfs<D: Disk, V: Iterator<Item = OsVideoMode>>(
|
||||
os: &dyn Os<D, V>,
|
||||
) -> (redoxfs::FileSystem<D>, Option<&'static [u8]>) {
|
||||
let attempts = 10;
|
||||
for attempt in 0..=attempts {
|
||||
let mut password_opt = None;
|
||||
if attempt > 0 {
|
||||
print!("\rRedoxFS password ({}/{}): ", attempt, attempts);
|
||||
|
||||
let mut password = String::new();
|
||||
|
||||
loop {
|
||||
match os.get_key() {
|
||||
OsKey::Backspace | OsKey::Delete => {
|
||||
if !password.is_empty() {
|
||||
print!("\x08 \x08");
|
||||
password.pop();
|
||||
}
|
||||
}
|
||||
OsKey::Char(c) => {
|
||||
print!("*");
|
||||
password.push(c)
|
||||
}
|
||||
OsKey::Enter => break,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Erase password information
|
||||
while os.get_text_position().0 > 0 {
|
||||
print!("\x08 \x08");
|
||||
}
|
||||
|
||||
if !password.is_empty() {
|
||||
password_opt = Some(password);
|
||||
}
|
||||
}
|
||||
match os.filesystem(password_opt.as_ref().map(|x| x.as_bytes())) {
|
||||
Ok(fs) => {
|
||||
return (
|
||||
fs,
|
||||
password_opt.map(|password| {
|
||||
// Copy password to page aligned memory
|
||||
let password_size = password.len();
|
||||
let password_base = os.alloc_zeroed_page_aligned(password_size);
|
||||
unsafe {
|
||||
ptr::copy(password.as_ptr(), password_base, password_size);
|
||||
slice::from_raw_parts(password_base, password_size)
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
Err(err) => match err.errno {
|
||||
// Incorrect password, try again
|
||||
syscall::ENOKEY => (),
|
||||
_ => {
|
||||
panic!("Failed to open RedoxFS: {}", err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
panic!("RedoxFS out of unlock attempts");
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Filetype {
|
||||
Elf,
|
||||
Initfs,
|
||||
}
|
||||
fn load_to_memory<D: Disk>(
|
||||
os: &dyn Os<D, impl Iterator<Item = OsVideoMode>>,
|
||||
fs: &mut redoxfs::FileSystem<D>,
|
||||
dirname: &str,
|
||||
filename: &str,
|
||||
filetype: Filetype,
|
||||
) -> &'static mut [u8] {
|
||||
fs.tx(|tx| {
|
||||
let dir_node = tx
|
||||
.find_node(redoxfs::TreePtr::root(), dirname)
|
||||
.unwrap_or_else(|err| panic!("Failed to find {} directory: {}", dirname, err));
|
||||
|
||||
let node = tx
|
||||
.find_node(dir_node.ptr(), filename)
|
||||
.unwrap_or_else(|err| panic!("Failed to find {} file: {}", filename, err));
|
||||
|
||||
let size = node.data().size();
|
||||
|
||||
print!("{}: 0/{} MiB", filename, size / MIBI as u64);
|
||||
|
||||
let ptr = os.alloc_zeroed_page_aligned(size as usize);
|
||||
if ptr.is_null() {
|
||||
panic!("Failed to allocate memory for {}", filename);
|
||||
}
|
||||
|
||||
let slice = unsafe { slice::from_raw_parts_mut(ptr, size as usize) };
|
||||
|
||||
let mut i = 0;
|
||||
for chunk in slice.chunks_mut(MIBI) {
|
||||
print!(
|
||||
"\r{}: {}/{} MiB",
|
||||
filename,
|
||||
i / MIBI as u64,
|
||||
size / MIBI as u64
|
||||
);
|
||||
i +=
|
||||
tx.read_node_inner(&node, i, chunk)
|
||||
.unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", filename, err)) as u64;
|
||||
}
|
||||
println!(
|
||||
"\r{}: {}/{} MiB",
|
||||
filename,
|
||||
i / MIBI as u64,
|
||||
size / MIBI as u64
|
||||
);
|
||||
|
||||
if filetype == Filetype::Elf {
|
||||
let magic = &slice[..4];
|
||||
if magic != b"\x7FELF" {
|
||||
panic!("{} has invalid magic number {:#X?}", filename, magic);
|
||||
}
|
||||
} else if filetype == Filetype::Initfs {
|
||||
let magic = &slice[..8];
|
||||
if magic != b"RedoxFtw" {
|
||||
panic!("{} has invalid magic number {:#X?}", filename, magic);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(slice)
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"RedoxFS transaction failed while loading `{}`: {}",
|
||||
filename, err
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn elf_entry(data: &[u8]) -> (u64, bool) {
|
||||
match (data[4], data[5]) {
|
||||
// 32-bit, little endian
|
||||
(1, 1) => (
|
||||
u32::from_le_bytes(
|
||||
<[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"),
|
||||
) as u64,
|
||||
false,
|
||||
),
|
||||
// 32-bit, big endian
|
||||
(1, 2) => (
|
||||
u32::from_be_bytes(
|
||||
<[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"),
|
||||
) as u64,
|
||||
false,
|
||||
),
|
||||
// 64-bit, little endian
|
||||
(2, 1) => (
|
||||
u64::from_le_bytes(
|
||||
<[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"),
|
||||
),
|
||||
true,
|
||||
),
|
||||
// 64-bit, big endian
|
||||
(2, 2) => (
|
||||
u64::from_be_bytes(
|
||||
<[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"),
|
||||
),
|
||||
true,
|
||||
),
|
||||
(ei_class, ei_data) => {
|
||||
panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main<D: Disk, V: Iterator<Item = OsVideoMode>>(os: &dyn Os<D, V>) -> (usize, u64, KernelArgs) {
|
||||
println!(
|
||||
"Redox OS Bootloader {} on {}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
os.name()
|
||||
);
|
||||
|
||||
let hwdesc = os.hwdesc();
|
||||
println!("Hardware descriptor: {:x?}", hwdesc);
|
||||
let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc {
|
||||
OsHwDesc::Acpi(base, size) => (base, size),
|
||||
OsHwDesc::DeviceTree(base, size) => (base, size),
|
||||
OsHwDesc::NotFound => (0, 0),
|
||||
};
|
||||
|
||||
let (mut fs, password_opt) = redoxfs(os);
|
||||
|
||||
print!("RedoxFS ");
|
||||
for i in 0..fs.header.uuid().len() {
|
||||
if i == 4 || i == 6 || i == 8 || i == 10 {
|
||||
print!("-");
|
||||
}
|
||||
|
||||
print!("{:>02x}", fs.header.uuid()[i]);
|
||||
}
|
||||
println!(": {} MiB", fs.header.size() / MIBI as u64);
|
||||
println!();
|
||||
|
||||
let mut mode_opts = Vec::new();
|
||||
for output_i in 0..os.video_outputs() {
|
||||
if output_i > 0 {
|
||||
os.clear_text();
|
||||
}
|
||||
mode_opts.push(select_mode(os, output_i));
|
||||
}
|
||||
|
||||
let stack_size = 128 * KIBI;
|
||||
let stack_base = os.alloc_zeroed_page_aligned(stack_size);
|
||||
if stack_base.is_null() {
|
||||
panic!("Failed to allocate memory for stack");
|
||||
}
|
||||
|
||||
let live_opt = if cfg!(feature = "live") {
|
||||
let size = fs.header.size();
|
||||
|
||||
print!("live: 0/{} MiB", size / MIBI as u64);
|
||||
|
||||
let ptr = os.alloc_zeroed_page_aligned(size as usize);
|
||||
if ptr.is_null() {
|
||||
panic!("Failed to allocate memory for live");
|
||||
}
|
||||
|
||||
let live = unsafe { slice::from_raw_parts_mut(ptr, size as usize) };
|
||||
|
||||
let mut i = 0;
|
||||
for chunk in live.chunks_mut(MIBI) {
|
||||
print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
|
||||
i += unsafe {
|
||||
fs.disk
|
||||
.read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk)
|
||||
.expect("Failed to read live disk") as u64
|
||||
};
|
||||
}
|
||||
println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
|
||||
|
||||
println!("Switching to live disk");
|
||||
unsafe {
|
||||
LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, size as usize)));
|
||||
}
|
||||
|
||||
area_add(OsMemoryEntry {
|
||||
base: live.as_ptr() as u64,
|
||||
size: live.len() as u64,
|
||||
kind: OsMemoryKind::Reserved,
|
||||
});
|
||||
|
||||
Some(live)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (kernel, kernel_entry) = {
|
||||
let kernel = load_to_memory(os, &mut fs, "boot", "kernel", Filetype::Elf);
|
||||
let (kernel_entry, kernel_64bit) = elf_entry(kernel);
|
||||
unsafe {
|
||||
KERNEL_64BIT = kernel_64bit;
|
||||
}
|
||||
(kernel, kernel_entry)
|
||||
};
|
||||
|
||||
let (bootstrap_size, bootstrap_base) = {
|
||||
let initfs_slice = load_to_memory(os, &mut fs, "boot", "initfs", Filetype::Initfs);
|
||||
|
||||
let memory = unsafe {
|
||||
let total_size = initfs_slice.len().next_multiple_of(4096);
|
||||
let ptr = os.alloc_zeroed_page_aligned(total_size);
|
||||
assert!(!ptr.is_null(), "failed to allocate bootstrap+initfs memory");
|
||||
core::slice::from_raw_parts_mut(ptr, total_size)
|
||||
};
|
||||
memory[..initfs_slice.len()].copy_from_slice(initfs_slice);
|
||||
|
||||
(memory.len() as u64, memory.as_mut_ptr() as u64)
|
||||
};
|
||||
|
||||
let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) }
|
||||
.expect("Failed to set up paging");
|
||||
|
||||
let mut env_size = 64 * KIBI;
|
||||
let env_base = os.alloc_zeroed_page_aligned(env_size);
|
||||
if env_base.is_null() {
|
||||
panic!("Failed to allocate memory for stack");
|
||||
}
|
||||
|
||||
{
|
||||
let mut w = SliceWriter {
|
||||
slice: unsafe { slice::from_raw_parts_mut(env_base, env_size) },
|
||||
i: 0,
|
||||
};
|
||||
|
||||
writeln!(w, "BOOT_MODE={}", os.name()).unwrap();
|
||||
|
||||
match hwdesc {
|
||||
OsHwDesc::Acpi(addr, size) => {
|
||||
writeln!(w, "RSDP_ADDR={:016x}", addr).unwrap();
|
||||
writeln!(w, "RSDP_SIZE={:016x}", size).unwrap();
|
||||
}
|
||||
OsHwDesc::DeviceTree(addr, size) => {
|
||||
writeln!(w, "DTB_ADDR={:016x}", addr).unwrap();
|
||||
writeln!(w, "DTB_SIZE={:016x}", size).unwrap();
|
||||
}
|
||||
OsHwDesc::NotFound => {}
|
||||
}
|
||||
|
||||
if let Some(live) = live_opt {
|
||||
writeln!(w, "DISK_LIVE_ADDR={:016x}", live.as_ptr() as usize).unwrap();
|
||||
writeln!(w, "DISK_LIVE_SIZE={:016x}", live.len()).unwrap();
|
||||
writeln!(w, "REDOXFS_BLOCK={:016x}", 0).unwrap();
|
||||
} else {
|
||||
writeln!(w, "REDOXFS_BLOCK={:016x}", fs.block).unwrap();
|
||||
}
|
||||
write!(w, "REDOXFS_UUID=").unwrap();
|
||||
for i in 0..fs.header.uuid().len() {
|
||||
if i == 4 || i == 6 || i == 8 || i == 10 {
|
||||
write!(w, "-").unwrap();
|
||||
}
|
||||
|
||||
write!(w, "{:>02x}", fs.header.uuid()[i]).unwrap();
|
||||
}
|
||||
writeln!(w).unwrap();
|
||||
if let Some(password) = password_opt {
|
||||
writeln!(
|
||||
w,
|
||||
"REDOXFS_PASSWORD_ADDR={:016x}",
|
||||
password.as_ptr() as usize
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(w, "REDOXFS_PASSWORD_SIZE={:016x}", password.len()).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
{
|
||||
let boot_hartid = os::efi_get_boot_hartid()
|
||||
.expect("Could not retrieve boot hart id from EFI implementation!");
|
||||
writeln!(w, "BOOT_HART_ID={:016x}", boot_hartid).unwrap();
|
||||
}
|
||||
|
||||
for output_i in 0..os.video_outputs() {
|
||||
if let Some(mut mode) = mode_opts[output_i] {
|
||||
// Set mode to get updated values
|
||||
os.set_video_mode(output_i, &mut mode);
|
||||
|
||||
if output_i == 0 {
|
||||
let virt = unsafe {
|
||||
paging_framebuffer(
|
||||
os,
|
||||
page_phys,
|
||||
mode.base,
|
||||
(mode.stride * mode.height * 4) as u64,
|
||||
)
|
||||
}
|
||||
.expect("Failed to map framebuffer");
|
||||
|
||||
writeln!(w, "FRAMEBUFFER_ADDR={:016x}", mode.base).unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_VIRT={:016x}", virt).unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_WIDTH={:016x}", mode.width).unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_HEIGHT={:016x}", mode.height).unwrap();
|
||||
writeln!(w, "FRAMEBUFFER_STRIDE={:016x}", mode.stride).unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"FRAMEBUFFER{}={:#x},{},{},{}",
|
||||
output_i, mode.base, mode.width, mode.height, mode.stride,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
env_size = w.i;
|
||||
}
|
||||
|
||||
(
|
||||
page_phys,
|
||||
kernel_entry,
|
||||
KernelArgs {
|
||||
kernel_base: kernel.as_ptr() as u64,
|
||||
kernel_size: kernel.len() as u64,
|
||||
stack_base: stack_base as u64,
|
||||
stack_size: stack_size as u64,
|
||||
env_base: env_base as u64,
|
||||
env_size: env_size as u64,
|
||||
acpi_rsdp_base,
|
||||
acpi_rsdp_size,
|
||||
areas_base: unsafe { AREAS.as_ptr() as u64 },
|
||||
areas_size: unsafe { (AREAS.len() * mem::size_of::<OsMemoryEntry>()) as u64 },
|
||||
bootstrap_base,
|
||||
bootstrap_size,
|
||||
},
|
||||
)
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
use core::{mem, ptr};
|
||||
use redoxfs::{Disk, BLOCK_SIZE};
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use super::{ThunkData, DISK_ADDRESS_PACKET_ADDR, DISK_BIOS_ADDR};
|
||||
|
||||
const SECTOR_SIZE: u64 = 512;
|
||||
const BLOCKS_PER_SECTOR: u64 = BLOCK_SIZE / SECTOR_SIZE;
|
||||
// 128 sectors is the amount allocated for DISK_BIOS_ADDR
|
||||
// 127 sectors is the maximum for many BIOSes
|
||||
const MAX_SECTORS: u64 = 127;
|
||||
const MAX_BLOCKS: u64 = MAX_SECTORS * SECTOR_SIZE / BLOCK_SIZE;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DiskAddressPacket {
|
||||
size: u8,
|
||||
reserved: u8,
|
||||
sectors: u16,
|
||||
buffer: u16,
|
||||
segment: u16,
|
||||
address: u64,
|
||||
}
|
||||
|
||||
impl DiskAddressPacket {
|
||||
pub fn from_block(block: u64, count: u64) -> DiskAddressPacket {
|
||||
let address = block * BLOCKS_PER_SECTOR;
|
||||
let sectors = count * BLOCKS_PER_SECTOR;
|
||||
assert!(sectors <= MAX_SECTORS);
|
||||
DiskAddressPacket {
|
||||
size: mem::size_of::<DiskAddressPacket>() as u8,
|
||||
reserved: 0,
|
||||
sectors: sectors as u16,
|
||||
buffer: (DISK_BIOS_ADDR & 0xF) as u16,
|
||||
segment: (DISK_BIOS_ADDR >> 4) as u16,
|
||||
address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiskBios {
|
||||
boot_disk: u8,
|
||||
thunk13: extern "C" fn(),
|
||||
chs_opt: Option<(u32, u32, u32)>,
|
||||
}
|
||||
|
||||
impl DiskBios {
|
||||
pub fn new(boot_disk: u8, thunk13: extern "C" fn()) -> Self {
|
||||
let chs_opt = unsafe {
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4100;
|
||||
data.ebx = 0x55AA;
|
||||
data.edx = boot_disk as u32;
|
||||
|
||||
data.with(thunk13);
|
||||
|
||||
if (data.ebx & 0xFFFF) == 0xAA55 {
|
||||
// Extensions are installed, do not use CHS
|
||||
None
|
||||
} else {
|
||||
// Extensions are not installed, get CHS geometry
|
||||
data = ThunkData::new();
|
||||
data.eax = 0x0800;
|
||||
data.edx = boot_disk as u32;
|
||||
data.edi = 0;
|
||||
|
||||
data.with(thunk13);
|
||||
|
||||
//TODO: return result on error
|
||||
let ah = ({ data.eax } >> 8) & 0xFF;
|
||||
assert_eq!(ah, 0);
|
||||
|
||||
let c = (data.ecx >> 8) & 0xFF | ((data.ecx >> 6) & 0x3) << 8;
|
||||
let h = ((data.edx >> 8) & 0xFF) + 1;
|
||||
let s = data.ecx & 0x3F;
|
||||
|
||||
Some((c, h, s))
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
boot_disk,
|
||||
thunk13,
|
||||
chs_opt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Disk for DiskBios {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
// Optimization for live disks
|
||||
if let Some(live) = crate::LIVE_OPT {
|
||||
if block >= live.0 {
|
||||
let start = ((block - live.0) * BLOCK_SIZE) as usize;
|
||||
let end = start + buffer.len();
|
||||
if end <= live.1.len() {
|
||||
buffer.copy_from_slice(&live.1[start..end]);
|
||||
return Ok(buffer.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, chunk) in buffer
|
||||
.chunks_mut((MAX_BLOCKS * BLOCK_SIZE) as usize)
|
||||
.enumerate()
|
||||
{
|
||||
let dap = DiskAddressPacket::from_block(
|
||||
block + i as u64 * MAX_BLOCKS,
|
||||
chunk.len() as u64 / BLOCK_SIZE,
|
||||
);
|
||||
|
||||
if let Some((_, h_max, s_max)) = self.chs_opt {
|
||||
let s = (dap.address % s_max as u64) + 1;
|
||||
assert!(s <= 63, "invalid sector {}", s);
|
||||
|
||||
let tmp = dap.address / s_max as u64;
|
||||
let h = tmp % h_max as u64;
|
||||
assert!(h <= 255, "invalid head {}", h);
|
||||
|
||||
let c = tmp / h_max as u64;
|
||||
assert!(c <= 1023, "invalid cylinder {}", c);
|
||||
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x0200 | (dap.sectors as u32);
|
||||
data.ebx = dap.buffer as u32;
|
||||
data.ecx =
|
||||
(s as u32) | (((c as u32) & 0xFF) << 8) | ((((c as u32) >> 8) & 0x3) << 6);
|
||||
data.edx = (self.boot_disk as u32) | ((h as u32) << 8);
|
||||
data.es = dap.segment;
|
||||
|
||||
data.with(self.thunk13);
|
||||
|
||||
//TODO: return result on error
|
||||
let ah = ({ data.eax } >> 8) & 0xFF;
|
||||
assert_eq!(ah, 0);
|
||||
} else {
|
||||
ptr::write(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket, dap);
|
||||
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4200;
|
||||
data.edx = self.boot_disk as u32;
|
||||
data.esi = DISK_ADDRESS_PACKET_ADDR as u32;
|
||||
|
||||
data.with(self.thunk13);
|
||||
|
||||
//TODO: return result on error
|
||||
let ah = ({ data.eax } >> 8) & 0xFF;
|
||||
assert_eq!(ah, 0);
|
||||
|
||||
//TODO: check blocks transferred
|
||||
// dap = ptr::read(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket);
|
||||
}
|
||||
|
||||
ptr::copy(DISK_BIOS_ADDR as *const u8, chunk.as_mut_ptr(), chunk.len());
|
||||
}
|
||||
|
||||
Ok(buffer.len())
|
||||
}
|
||||
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
log::error!(
|
||||
"DiskBios::write_at(0x{:X}, 0x{:X}:0x{:X}) not allowed",
|
||||
block,
|
||||
buffer.as_ptr() as usize,
|
||||
buffer.len()
|
||||
);
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
|
||||
fn size(&mut self) -> Result<u64> {
|
||||
log::error!("DiskBios::size not implemented");
|
||||
Err(Error::new(EIO))
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/// Print to console
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ({
|
||||
use core::fmt::Write;
|
||||
#[cfg(feature = "serial_debug")]
|
||||
{
|
||||
let _ = write!($crate::os::serial::COM1.lock(), $($arg)*);
|
||||
}
|
||||
let _ = write!($crate::os::VGA.lock(), $($arg)*);
|
||||
});
|
||||
}
|
||||
|
||||
/// Print with new line to console
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => (print!("\n"));
|
||||
($fmt:expr) => (print!(concat!($fmt, "\n")));
|
||||
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
use core::{cmp, mem, ptr};
|
||||
|
||||
use crate::area_add;
|
||||
use crate::os::{OsMemoryEntry, OsMemoryKind};
|
||||
|
||||
use super::{thunk::ThunkData, MEMORY_MAP_ADDR};
|
||||
|
||||
#[repr(C, packed)]
|
||||
struct MemoryMapEntry {
|
||||
pub base: u64,
|
||||
pub size: u64,
|
||||
pub kind: u32,
|
||||
}
|
||||
|
||||
pub struct MemoryMapIter {
|
||||
thunk15: extern "C" fn(),
|
||||
data: ThunkData,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
impl MemoryMapIter {
|
||||
pub fn new(thunk15: extern "C" fn()) -> Self {
|
||||
Self {
|
||||
thunk15,
|
||||
data: ThunkData::new(),
|
||||
first: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for MemoryMapIter {
|
||||
type Item = OsMemoryEntry;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.first {
|
||||
self.first = false;
|
||||
} else if self.data.ebx == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.data.eax = 0xE820;
|
||||
self.data.ecx = mem::size_of::<MemoryMapEntry>() as u32;
|
||||
self.data.edx = 0x534D4150;
|
||||
self.data.edi = MEMORY_MAP_ADDR as u32;
|
||||
|
||||
unsafe {
|
||||
self.data.with(self.thunk15);
|
||||
}
|
||||
|
||||
//TODO: return error?
|
||||
assert_eq!({ self.data.eax }, 0x534D4150);
|
||||
assert_eq!({ self.data.ecx }, mem::size_of::<MemoryMapEntry>() as u32);
|
||||
|
||||
let entry = unsafe { ptr::read(MEMORY_MAP_ADDR as *const MemoryMapEntry) };
|
||||
Some(Self::Item {
|
||||
base: entry.base,
|
||||
size: entry.size,
|
||||
kind: match entry.kind {
|
||||
0 => OsMemoryKind::Null,
|
||||
1 => OsMemoryKind::Free,
|
||||
3 => OsMemoryKind::Reclaim,
|
||||
_ => OsMemoryKind::Reserved,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> {
|
||||
let mut heap_limits = None;
|
||||
for entry in MemoryMapIter::new(thunk15) {
|
||||
let heap_start = 1 * 1024 * 1024;
|
||||
if { entry.kind } == OsMemoryKind::Free
|
||||
&& entry.base <= heap_start as u64
|
||||
&& (entry.base + entry.size) >= heap_start as u64
|
||||
{
|
||||
let heap_end = cmp::min(entry.base + entry.size, usize::MAX as u64) as usize;
|
||||
if heap_end >= heap_start {
|
||||
heap_limits = Some((heap_start, heap_end - heap_start));
|
||||
}
|
||||
}
|
||||
|
||||
area_add(entry);
|
||||
}
|
||||
heap_limits
|
||||
}
|
@ -1,313 +0,0 @@
|
||||
use alloc::alloc::{alloc_zeroed, Layout};
|
||||
use core::{convert::TryFrom, mem, ptr, slice};
|
||||
use linked_list_allocator::LockedHeap;
|
||||
use spin::Mutex;
|
||||
|
||||
use crate::logger::LOGGER;
|
||||
use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode};
|
||||
use crate::KernelArgs;
|
||||
|
||||
use self::disk::DiskBios;
|
||||
use self::memory_map::memory_map;
|
||||
use self::thunk::ThunkData;
|
||||
use self::vbe::VideoModeIter;
|
||||
use self::vga::{Vga, VgaTextColor};
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod disk;
|
||||
mod memory_map;
|
||||
mod panic;
|
||||
pub(crate) mod serial;
|
||||
mod thunk;
|
||||
mod vbe;
|
||||
mod vga;
|
||||
|
||||
// Real mode memory allocation, for use with thunk
|
||||
// 0x500 to 0x7BFF is free
|
||||
const DISK_BIOS_ADDR: usize = 0x70000; // 64 KiB at 448 KiB, ends at 512 KiB
|
||||
const VBE_CARD_INFO_ADDR: usize = 0x1000; // 512 bytes, ends at 0x11FF
|
||||
const VBE_MODE_INFO_ADDR: usize = 0x1200; // 256 bytes, ends at 0x12FF
|
||||
const VBE_EDID_ADDR: usize = 0x1300; // 128 bytes, ends at 0x137F
|
||||
const MEMORY_MAP_ADDR: usize = 0x1380; // 24 bytes, ends at 0x1397
|
||||
const DISK_ADDRESS_PACKET_ADDR: usize = 0x1398; // 16 bytes, ends at 0x13A7
|
||||
const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards
|
||||
const VGA_ADDR: usize = 0xB8000;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: LockedHeap = LockedHeap::empty();
|
||||
|
||||
pub(crate) static VGA: Mutex<Vga> = Mutex::new(unsafe { Vga::new(VGA_ADDR, 80, 25) });
|
||||
|
||||
pub struct OsBios {
|
||||
boot_disk: usize,
|
||||
thunk10: extern "C" fn(),
|
||||
thunk13: extern "C" fn(),
|
||||
thunk15: extern "C" fn(),
|
||||
thunk16: extern "C" fn(),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Rsdp {
|
||||
signature: [u8; 8],
|
||||
checksum: u8,
|
||||
oemid: [u8; 6],
|
||||
revision: u8,
|
||||
rsdt_address: u32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Xsdp {
|
||||
rsdp: Rsdp,
|
||||
|
||||
length: u32,
|
||||
xsdt_address: u64,
|
||||
extended_checksum: u8,
|
||||
reserved: [u8; 3],
|
||||
}
|
||||
|
||||
unsafe fn search_rsdp(start: usize, end: usize) -> Option<(u64, u64)> {
|
||||
// Align start up to 16 bytes
|
||||
let mut addr = ((start + 15) / 16) * 16;
|
||||
// Search until reading the end of the Rsdp would be past the end of the memory area
|
||||
while addr + mem::size_of::<Rsdp>() <= end {
|
||||
let rsdp = ptr::read(addr as *const Rsdp);
|
||||
if &rsdp.signature == b"RSD PTR " {
|
||||
//TODO: check checksum?
|
||||
if rsdp.revision == 0 {
|
||||
return Some((addr as u64, mem::size_of::<Rsdp>() as u64));
|
||||
} else if rsdp.revision == 2 {
|
||||
let xsdp = ptr::read(addr as *const Xsdp);
|
||||
//TODO: check extended checksum?
|
||||
return Some((addr as u64, xsdp.length as u64));
|
||||
}
|
||||
}
|
||||
|
||||
// Rsdp is always aligned to 16 bytes
|
||||
addr += 16;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl Os<DiskBios, VideoModeIter> for OsBios {
|
||||
fn name(&self) -> &str {
|
||||
"x86/BIOS"
|
||||
}
|
||||
|
||||
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 {
|
||||
assert!(size != 0);
|
||||
|
||||
let page_size = self.page_size();
|
||||
let pages = (size + page_size - 1) / page_size;
|
||||
|
||||
let ptr =
|
||||
unsafe { alloc_zeroed(Layout::from_size_align(pages * page_size, page_size).unwrap()) };
|
||||
|
||||
assert!(!ptr.is_null());
|
||||
ptr
|
||||
}
|
||||
|
||||
fn page_size(&self) -> usize {
|
||||
4096
|
||||
}
|
||||
|
||||
fn filesystem(
|
||||
&self,
|
||||
password_opt: Option<&[u8]>,
|
||||
) -> syscall::Result<redoxfs::FileSystem<DiskBios>> {
|
||||
let disk = DiskBios::new(u8::try_from(self.boot_disk).unwrap(), self.thunk13);
|
||||
|
||||
//TODO: get block from partition table
|
||||
//let block = 2 * crate::MIBI as u64 / redoxfs::BLOCK_SIZE;
|
||||
let block = 2048;
|
||||
redoxfs::FileSystem::open(disk, password_opt, Some(block), false)
|
||||
}
|
||||
|
||||
fn hwdesc(&self) -> OsHwDesc {
|
||||
// See ACPI specification - Finding the RSDP on IA-PC Systems
|
||||
unsafe {
|
||||
let ebda_segment = ptr::read(0x40E as *const u16);
|
||||
let ebda_addr = (ebda_segment as usize) << 4;
|
||||
if let Some((addr, size)) =
|
||||
search_rsdp(ebda_addr, ebda_addr + 1024).or(search_rsdp(0xE0000, 0xFFFFF))
|
||||
{
|
||||
// Copy to a page
|
||||
let page_aligned = self.alloc_zeroed_page_aligned(size as usize);
|
||||
ptr::copy(addr as *const u8, page_aligned, size as usize);
|
||||
return OsHwDesc::Acpi(page_aligned as u64, size);
|
||||
}
|
||||
}
|
||||
OsHwDesc::NotFound
|
||||
}
|
||||
|
||||
fn video_outputs(&self) -> usize {
|
||||
//TODO: return 1 only if vbe supported?
|
||||
1
|
||||
}
|
||||
|
||||
fn video_modes(&self, _output_i: usize) -> VideoModeIter {
|
||||
VideoModeIter::new(self.thunk10)
|
||||
}
|
||||
|
||||
fn set_video_mode(&self, _output_i: usize, mode: &mut OsVideoMode) {
|
||||
// Set video mode
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F02;
|
||||
data.ebx = mode.id;
|
||||
unsafe {
|
||||
data.with(self.thunk10);
|
||||
}
|
||||
//TODO: check result
|
||||
}
|
||||
|
||||
fn best_resolution(&self, _output_i: usize) -> Option<(u32, u32)> {
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F15;
|
||||
data.ebx = 0x01;
|
||||
data.ecx = 0;
|
||||
data.edx = 0;
|
||||
data.edi = VBE_EDID_ADDR as u32;
|
||||
unsafe {
|
||||
data.with(self.thunk10);
|
||||
}
|
||||
|
||||
if data.eax == 0x4F {
|
||||
let edid = unsafe { slice::from_raw_parts(VBE_EDID_ADDR as *const u8, 128) };
|
||||
|
||||
Some((
|
||||
(edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
|
||||
(edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
|
||||
))
|
||||
} else {
|
||||
log::warn!("Failed to get VBE EDID: 0x{:X}", { data.eax });
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> OsKey {
|
||||
// Read keypress
|
||||
let mut data = ThunkData::new();
|
||||
unsafe {
|
||||
data.with(self.thunk16);
|
||||
}
|
||||
match (data.eax >> 8) as u8 {
|
||||
0x4B => OsKey::Left,
|
||||
0x4D => OsKey::Right,
|
||||
0x48 => OsKey::Up,
|
||||
0x50 => OsKey::Down,
|
||||
0x0E => OsKey::Backspace,
|
||||
0x53 => OsKey::Delete,
|
||||
0x1C => OsKey::Enter,
|
||||
_ => match data.eax as u8 {
|
||||
0 => OsKey::Other,
|
||||
b => OsKey::Char(b as char),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_text(&self) {
|
||||
//TODO: clear screen for VGA
|
||||
}
|
||||
|
||||
fn get_text_position(&self) -> (usize, usize) {
|
||||
let vga = VGA.lock();
|
||||
(vga.x, vga.y)
|
||||
}
|
||||
|
||||
fn set_text_position(&self, x: usize, y: usize) {
|
||||
//TODO: ensure this is inside bounds!
|
||||
let mut vga = VGA.lock();
|
||||
vga.x = x;
|
||||
vga.y = y;
|
||||
}
|
||||
|
||||
fn set_text_highlight(&self, highlight: bool) {
|
||||
let mut vga = VGA.lock();
|
||||
if highlight {
|
||||
vga.bg = VgaTextColor::Gray;
|
||||
vga.fg = VgaTextColor::Black;
|
||||
} else {
|
||||
vga.bg = VgaTextColor::Black;
|
||||
vga.fg = VgaTextColor::Gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn start(
|
||||
kernel_entry: extern "C" fn(
|
||||
page_table: usize,
|
||||
stack: u64,
|
||||
func: u64,
|
||||
args: *const KernelArgs,
|
||||
long_mode: usize,
|
||||
) -> !,
|
||||
boot_disk: usize,
|
||||
thunk10: extern "C" fn(),
|
||||
thunk13: extern "C" fn(),
|
||||
thunk15: extern "C" fn(),
|
||||
thunk16: extern "C" fn(),
|
||||
) -> ! {
|
||||
#[cfg(feature = "serial_debug")]
|
||||
{
|
||||
let mut com1 = serial::COM1.lock();
|
||||
com1.init();
|
||||
com1.write(b"SERIAL\n");
|
||||
}
|
||||
|
||||
{
|
||||
// Make sure we are in mode 3 (80x25 text mode)
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x03;
|
||||
data.with(thunk10);
|
||||
}
|
||||
|
||||
{
|
||||
// Disable cursor
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x0100;
|
||||
data.ecx = 0x3F00;
|
||||
data.with(thunk10);
|
||||
}
|
||||
|
||||
// Clear screen
|
||||
VGA.lock().clear();
|
||||
|
||||
// Set logger
|
||||
LOGGER.init();
|
||||
|
||||
let mut os = OsBios {
|
||||
boot_disk,
|
||||
thunk10,
|
||||
thunk13,
|
||||
thunk15,
|
||||
thunk16,
|
||||
};
|
||||
|
||||
let (heap_start, heap_size) = memory_map(os.thunk15).expect("No memory for heap");
|
||||
|
||||
ALLOCATOR.lock().init(heap_start as *mut u8, heap_size);
|
||||
|
||||
let (page_phys, func, args) = crate::main(&mut os);
|
||||
|
||||
panic!("kernel");
|
||||
|
||||
kernel_entry(
|
||||
page_phys,
|
||||
args.stack_base
|
||||
+ args.stack_size
|
||||
+ if crate::KERNEL_64BIT {
|
||||
crate::arch::x64::PHYS_OFFSET as u64
|
||||
} else {
|
||||
crate::arch::x32::PHYS_OFFSET as u64
|
||||
},
|
||||
func,
|
||||
&args,
|
||||
if crate::KERNEL_64BIT { 1 } else { 0 },
|
||||
);
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
//! Intrinsics for panic handling
|
||||
|
||||
use core::alloc::Layout;
|
||||
use core::arch::asm;
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[lang = "eh_personality"]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_eh_personality() {}
|
||||
|
||||
/// Required to handle panics
|
||||
#[panic_handler]
|
||||
#[no_mangle]
|
||||
pub fn rust_begin_unwind(info: &PanicInfo<'_>) -> ! {
|
||||
unsafe {
|
||||
println!("BOOTLOADER PANIC:\n{}", info);
|
||||
loop {
|
||||
asm!("hlt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[alloc_error_handler]
|
||||
#[no_mangle]
|
||||
#[allow(improper_ctypes_definitions)] // Layout is not repr(C)
|
||||
pub extern "C" fn rust_oom(_layout: Layout) -> ! {
|
||||
panic!("memory allocation failed");
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
/// Required to handle panics
|
||||
pub extern "C" fn _Unwind_Resume() -> ! {
|
||||
loop {
|
||||
unsafe {
|
||||
asm!("hlt");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
use spin::Mutex;
|
||||
use syscall::Pio;
|
||||
|
||||
use crate::serial_16550::SerialPort;
|
||||
|
||||
pub static COM1: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x3F8));
|
||||
pub static COM2: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x2F8));
|
||||
pub static COM3: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x3E8));
|
||||
pub static COM4: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x2E8));
|
@ -1,46 +0,0 @@
|
||||
use core::ptr;
|
||||
|
||||
use super::THUNK_STACK_ADDR;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct ThunkData {
|
||||
pub es: u16,
|
||||
pub edi: u32,
|
||||
pub esi: u32,
|
||||
pub ebp: u32,
|
||||
pub ebx: u32,
|
||||
pub edx: u32,
|
||||
pub ecx: u32,
|
||||
pub eax: u32,
|
||||
}
|
||||
|
||||
impl ThunkData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
es: 0,
|
||||
edi: 0,
|
||||
esi: 0,
|
||||
ebp: 0,
|
||||
ebx: 0,
|
||||
edx: 0,
|
||||
ecx: 0,
|
||||
eax: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn save(&self) {
|
||||
ptr::write((THUNK_STACK_ADDR - 64) as *mut ThunkData, *self);
|
||||
}
|
||||
|
||||
pub unsafe fn load(&mut self) {
|
||||
*self = ptr::read((THUNK_STACK_ADDR - 64) as *const ThunkData);
|
||||
}
|
||||
|
||||
pub unsafe fn with(&mut self, f: extern "C" fn()) {
|
||||
self.save();
|
||||
f();
|
||||
self.load();
|
||||
}
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
use core::ptr;
|
||||
use log::error;
|
||||
|
||||
use crate::os::OsVideoMode;
|
||||
|
||||
use super::{ThunkData, VBE_CARD_INFO_ADDR, VBE_MODE_INFO_ADDR};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VbeFarPtr {
|
||||
pub offset: u16,
|
||||
pub segment: u16,
|
||||
}
|
||||
|
||||
impl VbeFarPtr {
|
||||
pub unsafe fn as_ptr<T>(&self) -> *const T {
|
||||
(((self.segment as usize) << 4) + (self.offset as usize)) as *const T
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VbeCardInfo {
|
||||
pub signature: [u8; 4],
|
||||
pub version: u16,
|
||||
pub oemstring: VbeFarPtr,
|
||||
pub capabilities: [u8; 4],
|
||||
pub videomodeptr: VbeFarPtr,
|
||||
pub totalmemory: u16,
|
||||
pub oemsoftwarerev: u16,
|
||||
pub oemvendornameptr: VbeFarPtr,
|
||||
pub oemproductnameptr: VbeFarPtr,
|
||||
pub oemproductrevptr: VbeFarPtr,
|
||||
pub reserved: [u8; 222],
|
||||
pub oemdata: [u8; 256],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VbeModeInfo {
|
||||
pub attributes: u16,
|
||||
pub win_a: u8,
|
||||
pub win_b: u8,
|
||||
pub granularity: u16,
|
||||
pub winsize: u16,
|
||||
pub segment_a: u16,
|
||||
pub segment_b: u16,
|
||||
pub winfuncptr: u32,
|
||||
pub bytesperscanline: u16,
|
||||
pub xresolution: u16,
|
||||
pub yresolution: u16,
|
||||
pub xcharsize: u8,
|
||||
pub ycharsize: u8,
|
||||
pub numberofplanes: u8,
|
||||
pub bitsperpixel: u8,
|
||||
pub numberofbanks: u8,
|
||||
pub memorymodel: u8,
|
||||
pub banksize: u8,
|
||||
pub numberofimagepages: u8,
|
||||
pub unused: u8,
|
||||
pub redmasksize: u8,
|
||||
pub redfieldposition: u8,
|
||||
pub greenmasksize: u8,
|
||||
pub greenfieldposition: u8,
|
||||
pub bluemasksize: u8,
|
||||
pub bluefieldposition: u8,
|
||||
pub rsvdmasksize: u8,
|
||||
pub rsvdfieldposition: u8,
|
||||
pub directcolormodeinfo: u8,
|
||||
pub physbaseptr: u32,
|
||||
pub offscreenmemoryoffset: u32,
|
||||
pub offscreenmemsize: u16,
|
||||
pub reserved: [u8; 206],
|
||||
}
|
||||
|
||||
pub struct VideoModeIter {
|
||||
thunk10: extern "C" fn(),
|
||||
mode_ptr: *const u16,
|
||||
}
|
||||
|
||||
impl VideoModeIter {
|
||||
pub fn new(thunk10: extern "C" fn()) -> Self {
|
||||
// Get card info
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F00;
|
||||
data.edi = VBE_CARD_INFO_ADDR as u32;
|
||||
unsafe {
|
||||
data.with(thunk10);
|
||||
}
|
||||
let mode_ptr = if data.eax == 0x004F {
|
||||
let card_info = unsafe { ptr::read(VBE_CARD_INFO_ADDR as *const VbeCardInfo) };
|
||||
unsafe { card_info.videomodeptr.as_ptr::<u16>() }
|
||||
} else {
|
||||
error!("Failed to read VBE card info: 0x{:04X}", { data.eax });
|
||||
ptr::null()
|
||||
};
|
||||
Self { thunk10, mode_ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for VideoModeIter {
|
||||
type Item = OsVideoMode;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.mode_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
loop {
|
||||
// Set bit 14 to get linear frame buffer
|
||||
let mode = unsafe { *self.mode_ptr } | (1 << 14);
|
||||
if mode == 0xFFFF {
|
||||
return None;
|
||||
}
|
||||
self.mode_ptr = unsafe { self.mode_ptr.add(1) };
|
||||
|
||||
// Get mode info
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x4F01;
|
||||
data.ecx = mode as u32;
|
||||
data.edi = VBE_MODE_INFO_ADDR as u32;
|
||||
unsafe {
|
||||
data.with(self.thunk10);
|
||||
}
|
||||
if data.eax == 0x004F {
|
||||
let mode_info = unsafe { ptr::read(VBE_MODE_INFO_ADDR as *const VbeModeInfo) };
|
||||
|
||||
// We only support 32-bits per pixel modes
|
||||
if mode_info.bitsperpixel != 32 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let width = mode_info.xresolution as u32;
|
||||
let height = mode_info.yresolution as u32;
|
||||
//TODO: support stride that is not a multiple of 4
|
||||
let stride = mode_info.bytesperscanline as u32 / 4;
|
||||
|
||||
return Some(OsVideoMode {
|
||||
id: mode as u32,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
base: mode_info.physbaseptr as u64,
|
||||
});
|
||||
} else {
|
||||
error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, {
|
||||
data.eax
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
use core::{fmt, slice};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VgaTextBlock {
|
||||
pub char: u8,
|
||||
pub color: u8,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum VgaTextColor {
|
||||
Black = 0,
|
||||
Blue = 1,
|
||||
Green = 2,
|
||||
Cyan = 3,
|
||||
Red = 4,
|
||||
Purple = 5,
|
||||
Brown = 6,
|
||||
Gray = 7,
|
||||
DarkGray = 8,
|
||||
LightBlue = 9,
|
||||
LightGreen = 10,
|
||||
LightCyan = 11,
|
||||
LightRed = 12,
|
||||
LightPurple = 13,
|
||||
Yellow = 14,
|
||||
White = 15,
|
||||
}
|
||||
|
||||
pub struct Vga {
|
||||
pub base: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub bg: VgaTextColor,
|
||||
pub fg: VgaTextColor,
|
||||
}
|
||||
|
||||
impl Vga {
|
||||
pub const unsafe fn new(base: usize, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
base,
|
||||
width,
|
||||
height,
|
||||
x: 0,
|
||||
y: 0,
|
||||
bg: VgaTextColor::Black,
|
||||
fg: VgaTextColor::Gray,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn blocks(&mut self) -> &'static mut [VgaTextBlock] {
|
||||
slice::from_raw_parts_mut(self.base as *mut VgaTextBlock, self.width * self.height)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.x = 0;
|
||||
self.y = 0;
|
||||
let blocks = unsafe { self.blocks() };
|
||||
for i in 0..blocks.len() {
|
||||
blocks[i] = VgaTextBlock {
|
||||
char: 0,
|
||||
color: ((self.bg as u8) << 4) | (self.fg as u8),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Vga {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||
let blocks = unsafe { self.blocks() };
|
||||
for c in s.chars() {
|
||||
if self.x >= self.width {
|
||||
self.x = 0;
|
||||
self.y += 1;
|
||||
}
|
||||
while self.y >= self.height {
|
||||
for y in 1..self.height {
|
||||
for x in 0..self.width {
|
||||
let i = y * self.width + x;
|
||||
let j = i - self.width;
|
||||
blocks[j] = blocks[i];
|
||||
if y + 1 == self.height {
|
||||
blocks[i].char = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.y -= 1;
|
||||
}
|
||||
match c {
|
||||
'\x08' => {
|
||||
if self.x > 0 {
|
||||
self.x -= 1;
|
||||
}
|
||||
}
|
||||
'\r' => {
|
||||
self.x = 0;
|
||||
}
|
||||
'\n' => {
|
||||
self.x = 0;
|
||||
self.y += 1;
|
||||
}
|
||||
_ => {
|
||||
let i = self.y * self.width + self.x;
|
||||
if let Some(block) = blocks.get_mut(i) {
|
||||
block.char = c as u8;
|
||||
block.color = ((self.bg as u8) << 4) | (self.fg as u8);
|
||||
}
|
||||
self.x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
use redoxfs::Disk;
|
||||
|
||||
#[cfg(all(target_arch = "x86", target_os = "none"))]
|
||||
pub use self::bios::*;
|
||||
|
||||
#[cfg(all(target_arch = "x86", target_os = "none"))]
|
||||
#[macro_use]
|
||||
mod bios;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum OsHwDesc {
|
||||
Acpi(u64, u64),
|
||||
DeviceTree(u64, u64),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum OsKey {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
Backspace,
|
||||
Delete,
|
||||
Enter,
|
||||
Char(char),
|
||||
Other,
|
||||
}
|
||||
|
||||
// Keep synced with BootloaderMemoryKind in kernel
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[repr(u64)]
|
||||
pub enum OsMemoryKind {
|
||||
Null = 0,
|
||||
Free = 1,
|
||||
Reclaim = 2,
|
||||
Reserved = 3,
|
||||
}
|
||||
|
||||
// Keep synced with BootloaderMemoryEntry in kernel
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed(8))]
|
||||
pub struct OsMemoryEntry {
|
||||
pub base: u64,
|
||||
pub size: u64,
|
||||
pub kind: OsMemoryKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct OsVideoMode {
|
||||
pub id: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub stride: u32,
|
||||
pub base: u64,
|
||||
}
|
||||
|
||||
pub trait Os<D: Disk, V: Iterator<Item = OsVideoMode>> {
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn page_size(&self) -> usize;
|
||||
|
||||
fn filesystem(&self, password_opt: Option<&[u8]>) -> syscall::Result<redoxfs::FileSystem<D>>;
|
||||
|
||||
fn hwdesc(&self) -> OsHwDesc;
|
||||
|
||||
fn video_outputs(&self) -> usize;
|
||||
fn video_modes(&self, output_i: usize) -> V;
|
||||
fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode);
|
||||
fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)>;
|
||||
|
||||
fn get_key(&self) -> OsKey;
|
||||
|
||||
fn clear_text(&self);
|
||||
fn get_text_position(&self) -> (usize, usize);
|
||||
fn set_text_position(&self, x: usize, y: usize);
|
||||
fn set_text_highlight(&self, highlight: bool);
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
use bitflags::bitflags;
|
||||
use core::convert::TryInto;
|
||||
use core::fmt;
|
||||
use core::ptr::{addr_of, addr_of_mut};
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use syscall::io::Pio;
|
||||
use syscall::io::{Io, Mmio, ReadOnly};
|
||||
|
||||
bitflags! {
|
||||
/// Interrupt enable flags
|
||||
struct IntEnFlags: u8 {
|
||||
const RECEIVED = 1;
|
||||
const SENT = 1 << 1;
|
||||
const ERRORED = 1 << 2;
|
||||
const STATUS_CHANGE = 1 << 3;
|
||||
// 4 to 7 are unused
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Line status flags
|
||||
struct LineStsFlags: u8 {
|
||||
const INPUT_FULL = 1;
|
||||
// 1 to 4 unknown
|
||||
const OUTPUT_EMPTY = 1 << 5;
|
||||
// 6 and 7 unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(C, packed)]
|
||||
pub struct SerialPort<T: Io> {
|
||||
/// Data register, read to receive, write to send
|
||||
data: T,
|
||||
/// Interrupt enable
|
||||
int_en: T,
|
||||
/// FIFO control
|
||||
fifo_ctrl: T,
|
||||
/// Line control
|
||||
line_ctrl: T,
|
||||
/// Modem control
|
||||
modem_ctrl: T,
|
||||
/// Line status
|
||||
line_sts: ReadOnly<T>,
|
||||
/// Modem status
|
||||
modem_sts: ReadOnly<T>,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl SerialPort<Pio<u8>> {
|
||||
pub const fn new(base: u16) -> SerialPort<Pio<u8>> {
|
||||
SerialPort {
|
||||
data: Pio::new(base),
|
||||
int_en: Pio::new(base + 1),
|
||||
fifo_ctrl: Pio::new(base + 2),
|
||||
line_ctrl: Pio::new(base + 3),
|
||||
modem_ctrl: Pio::new(base + 4),
|
||||
line_sts: ReadOnly::new(Pio::new(base + 5)),
|
||||
modem_sts: ReadOnly::new(Pio::new(base + 6)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerialPort<Mmio<u32>> {
|
||||
pub unsafe fn new(base: usize) -> &'static mut SerialPort<Mmio<u32>> {
|
||||
&mut *(base as *mut Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Io> SerialPort<T>
|
||||
where
|
||||
T::Value: From<u8> + TryInto<u8>,
|
||||
{
|
||||
pub fn init(&mut self) {
|
||||
unsafe {
|
||||
//TODO: Cleanup
|
||||
// FIXME: Fix UB if unaligned
|
||||
(&mut *addr_of_mut!(self.int_en)).write(0x00.into());
|
||||
(&mut *addr_of_mut!(self.line_ctrl)).write(0x80.into());
|
||||
(&mut *addr_of_mut!(self.data)).write(0x01.into());
|
||||
(&mut *addr_of_mut!(self.int_en)).write(0x00.into());
|
||||
(&mut *addr_of_mut!(self.line_ctrl)).write(0x03.into());
|
||||
(&mut *addr_of_mut!(self.fifo_ctrl)).write(0xC7.into());
|
||||
(&mut *addr_of_mut!(self.modem_ctrl)).write(0x0B.into());
|
||||
(&mut *addr_of_mut!(self.int_en)).write(0x01.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn line_sts(&self) -> LineStsFlags {
|
||||
LineStsFlags::from_bits_truncate(
|
||||
(unsafe { &*addr_of!(self.line_sts) }.read() & 0xFF.into())
|
||||
.try_into()
|
||||
.unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn receive(&mut self) -> Option<u8> {
|
||||
if self.line_sts().contains(LineStsFlags::INPUT_FULL) {
|
||||
Some(
|
||||
(unsafe { &*addr_of!(self.data) }.read() & 0xFF.into())
|
||||
.try_into()
|
||||
.unwrap_or(0),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self, data: u8) {
|
||||
while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {}
|
||||
unsafe { &mut *addr_of_mut!(self.data) }.write(data.into())
|
||||
}
|
||||
|
||||
pub fn write(&mut self, buf: &[u8]) {
|
||||
for &b in buf {
|
||||
match b {
|
||||
8 | 0x7F => {
|
||||
self.send(8);
|
||||
self.send(b' ');
|
||||
self.send(8);
|
||||
}
|
||||
b'\n' => {
|
||||
self.send(b'\r');
|
||||
self.send(b'\n');
|
||||
}
|
||||
_ => {
|
||||
self.send(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Io> fmt::Write for SerialPort<T>
|
||||
where
|
||||
T::Value: From<u8> + TryInto<u8>,
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||
self.write(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
{
|
||||
"llvm-target": "i686-unknown-none",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "32",
|
||||
"target-c-int-width": "32",
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"arch": "x86",
|
||||
"os": "none",
|
||||
"env": "",
|
||||
"vendor": "unknown",
|
||||
"linker-flavor": "gcc",
|
||||
"panic-strategy": "abort",
|
||||
"pre-link-args": {
|
||||
"gcc": ["-m32", "-nostdlib", "-static"]
|
||||
},
|
||||
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float",
|
||||
"dynamic-linking": false,
|
||||
"executables": false,
|
||||
"relocation-model": "static",
|
||||
"code-model": "large",
|
||||
"disable-redzone": true,
|
||||
"frame-pointer": "always",
|
||||
"exe-suffix": "",
|
||||
"has-rpath": false,
|
||||
"no-default-libraries": true,
|
||||
"position-independent-executables": false,
|
||||
"tls-model": "global-dynamic"
|
||||
}
|
180
tetros/Cargo.lock
generated
Normal file
180
tetros/Cargo.lock
generated
Normal file
@ -0,0 +1,180 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "10.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tetros"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"spin",
|
||||
"uart_16550",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uart_16550"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e492212ac378a5e00da953718dafb1340d9fbaf4f27d6f3c5cab03d931d1c049"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"rustversion",
|
||||
"x86",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
|
||||
[[package]]
|
||||
name = "x86"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"bitflags 1.3.2",
|
||||
"raw-cpuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
@ -1,18 +1,17 @@
|
||||
#
|
||||
# MARK: meta
|
||||
#
|
||||
|
||||
[package]
|
||||
name = "redox_bootloader"
|
||||
name = "tetros"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "bootloader"
|
||||
path = "src/main.rs"
|
||||
name = "tetros"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
# Silence rust-analyzer errors
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
#
|
||||
# MARK: lints
|
||||
@ -29,19 +28,13 @@ elided_lifetimes_in_paths = "deny"
|
||||
absolute_paths_not_starting_with_crate = "deny"
|
||||
explicit_outlives_requirements = "warn"
|
||||
unused_crate_dependencies = "warn"
|
||||
#variant_size_differences = "warn"
|
||||
redundant_lifetimes = "warn"
|
||||
missing_docs = "allow"
|
||||
missing_docs = "warn"
|
||||
|
||||
[lints.clippy]
|
||||
needless_return = "allow"
|
||||
new_without_default = "allow"
|
||||
tabs_in_doc_comments = "allow"
|
||||
|
||||
# Extra
|
||||
expect_used = "deny"
|
||||
#unwrap_used = "deny"
|
||||
#panic = "deny"
|
||||
dbg_macro = "deny"
|
||||
allow_attributes = "deny"
|
||||
create_dir = "deny"
|
||||
@ -59,18 +52,15 @@ string_to_string = "deny"
|
||||
unimplemented = "deny"
|
||||
use_debug = "deny"
|
||||
verbose_file_reads = "deny"
|
||||
#wildcard_enum_match_arm = "deny"
|
||||
|
||||
# Pedantic
|
||||
large_types_passed_by_value = "deny"
|
||||
match_on_vec_items = "deny"
|
||||
|
||||
# Cargo
|
||||
wildcard_dependencies = "deny"
|
||||
negative_feature_names = "deny"
|
||||
redundant_feature_names = "deny"
|
||||
multiple_crate_versions = "deny"
|
||||
|
||||
multiple_crate_versions = "allow"
|
||||
missing_safety_doc = "allow"
|
||||
identity_op = "allow"
|
||||
comparison_chain = "allow"
|
||||
|
||||
#
|
||||
# MARK: dependencies
|
||||
@ -78,21 +68,11 @@ multiple_crate_versions = "deny"
|
||||
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.3.2"
|
||||
linked_list_allocator = "0.10.5"
|
||||
log = "0.4.17"
|
||||
redox_syscall = "0.5"
|
||||
spin = "0.9.5"
|
||||
bitflags = "2.8.0"
|
||||
rand = { version = "0.9.0", features = ["small_rng"], default-features = false }
|
||||
spin = "0.9.8"
|
||||
uart_16550 = "0.3.2"
|
||||
|
||||
[dependencies.redoxfs]
|
||||
version = "0.6.0"
|
||||
default-features = false
|
||||
features = ["force-soft", "log"]
|
||||
|
||||
#
|
||||
# MARK: features
|
||||
#
|
||||
|
||||
[features]
|
||||
serial_debug = []
|
||||
live = []
|
||||
[dependencies.lazy_static]
|
||||
version = "1.0"
|
||||
features = ["spin_no_std"]
|
@ -1,9 +1,10 @@
|
||||
/* This is the name of the Rust function we start in */
|
||||
ENTRY(start)
|
||||
OUTPUT_FORMAT(elf32-i386)
|
||||
|
||||
SECTIONS {
|
||||
/* The start address must match bootloader.asm */
|
||||
. = 0x13000;
|
||||
/* The start address must match main.asm */
|
||||
. = 0x8000;
|
||||
|
||||
. += SIZEOF_HEADERS;
|
||||
. = ALIGN(4096);
|
@ -1,3 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-05-11"
|
||||
channel = "nightly-2025-02-01"
|
||||
components = ["rust-src"]
|
5
tetros/src/drivers/mod.rs
Normal file
5
tetros/src/drivers/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#[macro_use]
|
||||
pub mod serial;
|
||||
|
||||
pub mod pic;
|
||||
pub mod vga;
|
94
tetros/src/drivers/pic.rs
Normal file
94
tetros/src/drivers/pic.rs
Normal file
@ -0,0 +1,94 @@
|
||||
//! Control routines for the x86
|
||||
//! 8259 Programmable Interrupt Controller
|
||||
//!
|
||||
//! This helps us configure interrupts that receive
|
||||
//! keyboard input and timer pulses.
|
||||
|
||||
use crate::os::util::outb;
|
||||
|
||||
/// IO base address for master PIC
|
||||
const PIC_A: u32 = 0x20;
|
||||
/// Command address for master PIC
|
||||
const PIC_A_COMMAND: u32 = PIC_A;
|
||||
/// Data address for master PIC
|
||||
const PIC_A_DATA: u32 = PIC_A + 1;
|
||||
|
||||
/// IO base address for slave PIC
|
||||
const PIC_B: u32 = 0xA0;
|
||||
/// Command address for slave PIC
|
||||
const PIC_B_COMMAND: u32 = PIC_B;
|
||||
/// Data address for slave PIC
|
||||
const PIC_B_DATA: u32 = PIC_B + 1;
|
||||
|
||||
/// A driver for the PIC
|
||||
///
|
||||
/// Reference:
|
||||
/// - https://wiki.osdev.org/8259_PIC
|
||||
/// - https://os.phil-opp.com/hardware-interrupts
|
||||
pub struct PICDriver {
|
||||
offset_pic_a: u8,
|
||||
offset_pic_b: u8,
|
||||
}
|
||||
|
||||
impl PICDriver {
|
||||
/// Create a PIC driver with the given offsets
|
||||
pub const fn new(offset_pic_a: u8, offset_pic_b: u8) -> Self {
|
||||
Self {
|
||||
offset_pic_a,
|
||||
offset_pic_b,
|
||||
}
|
||||
}
|
||||
|
||||
fn send_a_cmd(&self, cmd: u8) {
|
||||
unsafe { outb(PIC_A_COMMAND, cmd) }
|
||||
}
|
||||
|
||||
fn send_a_data(&self, cmd: u8) {
|
||||
unsafe { outb(PIC_A_DATA, cmd) }
|
||||
}
|
||||
|
||||
fn send_b_cmd(&self, cmd: u8) {
|
||||
unsafe { outb(PIC_B_COMMAND, cmd) }
|
||||
}
|
||||
|
||||
fn send_b_data(&self, cmd: u8) {
|
||||
unsafe { outb(PIC_B_DATA, cmd) }
|
||||
}
|
||||
|
||||
/// Send an EOI for the given IRQ.
|
||||
///
|
||||
/// This needs to be called at the end of each interrupt handler.
|
||||
/// If `both` is true, reset both PICs. This is only necessary
|
||||
/// when we handle interrupts from PIC_B.
|
||||
pub fn send_eoi(&self, both: bool) {
|
||||
if both {
|
||||
self.send_b_cmd(0x20);
|
||||
}
|
||||
self.send_a_cmd(0x20);
|
||||
}
|
||||
|
||||
/// Initialize this PIC driver.
|
||||
/// This should be called as early as possible.
|
||||
pub fn init(&mut self) {
|
||||
const ICW1_ICW4: u8 = 0x01; /* Indicates that ICW4 will be present */
|
||||
const ICW1_INIT: u8 = 0x10; /* Initialization - required! */
|
||||
const ICW4_8086: u8 = 0x01; /* 8086/88 (MCS-80/85) mode */
|
||||
|
||||
self.send_a_cmd(ICW1_INIT | ICW1_ICW4);
|
||||
self.send_b_cmd(ICW1_INIT | ICW1_ICW4);
|
||||
|
||||
self.send_a_data(self.offset_pic_a); // ICW2: Master PIC vector offset
|
||||
self.send_b_data(self.offset_pic_b); // ICW2: Slave PIC vector offset
|
||||
|
||||
self.send_a_data(4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
|
||||
self.send_b_data(2); // ICW3: tell Slave PIC its cascade identity (0000 0010)
|
||||
|
||||
// ICW4: have the PICs use 8086 mode (and not 8080 mode)
|
||||
self.send_a_data(ICW4_8086);
|
||||
self.send_b_data(ICW4_8086);
|
||||
|
||||
// Unmask both PICs
|
||||
self.send_a_data(0);
|
||||
self.send_b_data(0);
|
||||
}
|
||||
}
|
51
tetros/src/drivers/serial.rs
Normal file
51
tetros/src/drivers/serial.rs
Normal file
@ -0,0 +1,51 @@
|
||||
//! Serial port driver, for debug.
|
||||
//!
|
||||
//! This file provides the usual `print`
|
||||
//! and `println` macros (which are usually
|
||||
//! provided by `std`) that send messages out
|
||||
//! of the serial port.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use spin::Mutex;
|
||||
use uart_16550::SerialPort;
|
||||
|
||||
use crate::os::util::without_interrupts;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SERIAL1: Mutex<SerialPort> = {
|
||||
let mut serial_port = unsafe { SerialPort::new(0x3F8) };
|
||||
serial_port.init();
|
||||
Mutex::new(serial_port)
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn _print(args: core::fmt::Arguments<'_>) {
|
||||
use core::fmt::Write;
|
||||
|
||||
// Disable interrupts to prevent deadlocks
|
||||
// (we might get an interrupt while printing)
|
||||
without_interrupts(|| {
|
||||
SERIAL1
|
||||
.lock()
|
||||
.write_fmt(args)
|
||||
.expect("Printing to serial failed");
|
||||
})
|
||||
}
|
||||
|
||||
/// Prints to the host through the serial interface.
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => {
|
||||
$crate::drivers::serial::_print(format_args!($($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
/// Prints to the host through the serial interface, appending a newline.
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => ($crate::print!("\n"));
|
||||
($fmt:expr) => ($crate::print!(concat!($fmt, "\n")));
|
||||
($fmt:expr, $($arg:tt)*) => ($crate::print!(
|
||||
concat!($fmt, "\n"), $($arg)*));
|
||||
}
|
116
tetros/src/drivers/vga.rs
Normal file
116
tetros/src/drivers/vga.rs
Normal file
@ -0,0 +1,116 @@
|
||||
use core::slice;
|
||||
use rand::seq::IndexedRandom;
|
||||
|
||||
use crate::RNG;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VgaColor {
|
||||
Black,
|
||||
Gray,
|
||||
Blue,
|
||||
Cyan,
|
||||
Orange,
|
||||
Red,
|
||||
Green,
|
||||
Purple,
|
||||
Yellow,
|
||||
}
|
||||
|
||||
impl VgaColor {
|
||||
pub fn as_u8(self) -> u8 {
|
||||
match self {
|
||||
Self::Black => 0b0000_0000,
|
||||
Self::Gray => 0b1110_0000,
|
||||
Self::Blue => 0b0000_0001,
|
||||
Self::Cyan => 0b0000_0011,
|
||||
Self::Orange => 0b0000_0110,
|
||||
Self::Red => 0b0000_0100,
|
||||
Self::Green => 0b0000_0010,
|
||||
Self::Purple => 0b0000_0101,
|
||||
Self::Yellow => 0b1100_0000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick a random non-utility color
|
||||
pub fn choose_rand() -> Self {
|
||||
let colors = [
|
||||
VgaColor::Blue,
|
||||
VgaColor::Cyan,
|
||||
VgaColor::Orange,
|
||||
VgaColor::Red,
|
||||
VgaColor::Green,
|
||||
VgaColor::Purple,
|
||||
VgaColor::Yellow,
|
||||
];
|
||||
|
||||
let mut rng = RNG.lock();
|
||||
*(colors.choose(&mut rng).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// VGA driver for mode 0x13:
|
||||
///
|
||||
/// - mode: graphics
|
||||
/// - text res: 40x25
|
||||
/// - pixel box: 8x8
|
||||
/// - pixel res: 320x200
|
||||
/// - colors: 256/256k
|
||||
/// - addr: A000
|
||||
/// - pixel format: RRRGGGBB
|
||||
pub struct Vga13h {
|
||||
// Double frame buffers
|
||||
fb_a: [u8; Vga13h::WIDTH * Vga13h::HEIGHT],
|
||||
fb_b: [u8; Vga13h::WIDTH * Vga13h::HEIGHT],
|
||||
|
||||
/// If true, show fb_a (and write to fb_b).
|
||||
/// if false, show fb_b.
|
||||
show_fb_a: bool,
|
||||
}
|
||||
|
||||
impl Vga13h {
|
||||
pub const WIDTH: usize = 320;
|
||||
pub const HEIGHT: usize = 200;
|
||||
pub const ADDR: usize = 0xA0000;
|
||||
|
||||
/// Initialize a new VGA driver.
|
||||
///
|
||||
/// Only one of these should exist.
|
||||
pub const unsafe fn new() -> Self {
|
||||
Self {
|
||||
fb_a: [0; Vga13h::WIDTH * Vga13h::HEIGHT],
|
||||
fb_b: [0; Vga13h::WIDTH * Vga13h::HEIGHT],
|
||||
show_fb_a: true,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn segment(&mut self) -> &'static mut [u8] {
|
||||
slice::from_raw_parts_mut(Vga13h::ADDR as *mut u8, Vga13h::WIDTH * Vga13h::HEIGHT)
|
||||
}
|
||||
|
||||
pub fn swap(&mut self) {
|
||||
let seg = unsafe { self.segment() };
|
||||
if self.show_fb_a {
|
||||
seg.copy_from_slice(&self.fb_b);
|
||||
self.show_fb_a = false;
|
||||
self.fb_a.fill(0);
|
||||
} else {
|
||||
seg.copy_from_slice(&self.fb_a);
|
||||
self.show_fb_a = true;
|
||||
self.fb_b.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fb(&mut self) -> &mut [u8; Vga13h::WIDTH * Vga13h::HEIGHT] {
|
||||
if self.show_fb_a {
|
||||
&mut self.fb_b
|
||||
} else {
|
||||
&mut self.fb_a
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pix_idx(x: usize, y: usize) -> usize {
|
||||
debug_assert!(x < Vga13h::WIDTH);
|
||||
debug_assert!(y < Vga13h::HEIGHT);
|
||||
return y * Vga13h::WIDTH + x;
|
||||
}
|
||||
}
|
183
tetros/src/game/board.rs
Normal file
183
tetros/src/game/board.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use crate::drivers::vga::{Vga13h, VgaColor};
|
||||
|
||||
use super::FallingTetromino;
|
||||
|
||||
/// The state of a cell in the game board
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TetrisCell {
|
||||
Empty,
|
||||
Filled { color: VgaColor },
|
||||
}
|
||||
|
||||
/// The tetris board
|
||||
pub struct TetrisBoard {
|
||||
board: [TetrisCell; TetrisBoard::BOARD_WIDTH * TetrisBoard::BOARD_HEIGHT],
|
||||
}
|
||||
|
||||
impl TetrisBoard {
|
||||
/// The width of this board, in cells
|
||||
const BOARD_WIDTH: usize = 10;
|
||||
|
||||
/// The height of this board, in cells
|
||||
const BOARD_HEIGHT: usize = 20;
|
||||
|
||||
/// The side length of a (square) cell, in pixels
|
||||
const CELL_SIZE: usize = 9;
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
board: [TetrisCell::Empty; TetrisBoard::BOARD_WIDTH * TetrisBoard::BOARD_HEIGHT],
|
||||
}
|
||||
}
|
||||
|
||||
/// Find and remove all filled rows,
|
||||
/// shifting upper rows down.
|
||||
pub fn collapse(&mut self) {
|
||||
let mut y = Self::BOARD_HEIGHT - 1;
|
||||
'outer: loop {
|
||||
for x in 0..Self::BOARD_WIDTH {
|
||||
let cell = self.get_cell(x, y);
|
||||
if cell == Some(&TetrisCell::Empty) {
|
||||
if y == 0 {
|
||||
break 'outer;
|
||||
}
|
||||
|
||||
y -= 1;
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
// We found a row that needs to be cleared
|
||||
|
||||
// Shift everything down
|
||||
for yy in (1..=y).rev() {
|
||||
for x in 0..Self::BOARD_WIDTH {
|
||||
let top = *self.get_cell(x, yy - 1).unwrap();
|
||||
let bot = self.get_cell_mut(x, yy).unwrap();
|
||||
*bot = top;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the top row
|
||||
for x in 0..Self::BOARD_WIDTH {
|
||||
*self.get_cell_mut(x, 0).unwrap() = TetrisCell::Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Place the given tetromino on the board,
|
||||
/// filling the cells it occupies.
|
||||
///
|
||||
/// If the tetromino cells that overlap
|
||||
/// non-empty board cells are ignored.
|
||||
pub fn place_tetromino(&mut self, tetromino: FallingTetromino) {
|
||||
for (x, y) in tetromino.tiles() {
|
||||
let cell = self.get_cell_mut(x, y);
|
||||
if let Some(cell) = cell {
|
||||
*cell = TetrisCell::Filled {
|
||||
color: tetromino.color,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `false` if the given tetromino intersects a filled cell
|
||||
/// or exits the board boundary
|
||||
pub fn tetromino_valid(&self, tetromino: &FallingTetromino) -> bool {
|
||||
for (x, y) in tetromino.tiles() {
|
||||
let cell = self.get_cell(x, y);
|
||||
if cell != Some(&TetrisCell::Empty) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Get the value of the cell at the given position.
|
||||
/// Returns [`None`] if (x, y) exceeds the board's bounds.
|
||||
pub fn get_cell(&self, x: usize, y: usize) -> Option<&TetrisCell> {
|
||||
return self.board.get(y * TetrisBoard::BOARD_WIDTH + x);
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the cell at the given position.
|
||||
/// Returns [`None`] if (x, y) exceeds the board's bounds.
|
||||
pub fn get_cell_mut(&mut self, x: usize, y: usize) -> Option<&mut TetrisCell> {
|
||||
return self.board.get_mut(y * TetrisBoard::BOARD_WIDTH + x);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MARK: draw routines
|
||||
//
|
||||
|
||||
impl TetrisBoard {
|
||||
/// Draw a cell of the given color on `fb`.
|
||||
/// (x, y) is the pixel position of the cell (NOT board coordinates).
|
||||
fn draw_cell(&self, fb: &mut [u8], color: VgaColor, x: usize, y: usize) {
|
||||
let color = color.as_u8();
|
||||
for yo in 0..TetrisBoard::CELL_SIZE {
|
||||
let left = Vga13h::pix_idx(x, y + yo);
|
||||
let right = Vga13h::pix_idx(x + TetrisBoard::CELL_SIZE, y + yo);
|
||||
fb[left..right].copy_from_slice(&[color; TetrisBoard::CELL_SIZE]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the tetris board's frame
|
||||
fn draw_frame(&self, fb: &mut [u8], x: usize, y: usize) {
|
||||
let color = VgaColor::Gray.as_u8();
|
||||
for yo in 0..TetrisBoard::CELL_SIZE {
|
||||
let left = Vga13h::pix_idx(x, y + yo);
|
||||
let right = Vga13h::pix_idx(x + TetrisBoard::CELL_SIZE, y + yo);
|
||||
fb[left..right].copy_from_slice(&[color; TetrisBoard::CELL_SIZE]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw this tetris board using the given VGA driver.
|
||||
pub fn draw(&self, vga: &mut Vga13h, falling: Option<&FallingTetromino>) {
|
||||
let fb = vga.get_fb();
|
||||
|
||||
// Draw cells
|
||||
for bx in 0..TetrisBoard::BOARD_WIDTH {
|
||||
for by in 0..TetrisBoard::BOARD_HEIGHT {
|
||||
let cell = self.board[by * TetrisBoard::BOARD_WIDTH + bx];
|
||||
let dx = (bx + 1) * TetrisBoard::CELL_SIZE;
|
||||
let dy = (by + 1) * TetrisBoard::CELL_SIZE;
|
||||
|
||||
if let TetrisCell::Filled { color } = cell {
|
||||
self.draw_cell(fb, color, dx, dy);
|
||||
} else {
|
||||
self.draw_cell(fb, VgaColor::Black, dx, dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw falling tetromino
|
||||
if let Some(falling) = falling {
|
||||
for (x, y) in falling.tiles() {
|
||||
let dx = (x + 1) * TetrisBoard::CELL_SIZE;
|
||||
let dy = (y + 1) * TetrisBoard::CELL_SIZE;
|
||||
|
||||
self.draw_cell(fb, falling.color, dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw frame
|
||||
for bx in 0..TetrisBoard::BOARD_WIDTH + 2 {
|
||||
self.draw_frame(fb, bx * TetrisBoard::CELL_SIZE, 0);
|
||||
self.draw_frame(
|
||||
fb,
|
||||
bx * TetrisBoard::CELL_SIZE,
|
||||
(TetrisBoard::BOARD_HEIGHT + 1) * TetrisBoard::CELL_SIZE,
|
||||
);
|
||||
}
|
||||
for by in 0..TetrisBoard::BOARD_HEIGHT + 2 {
|
||||
self.draw_frame(fb, 0, by * 9);
|
||||
self.draw_frame(
|
||||
fb,
|
||||
(TetrisBoard::BOARD_WIDTH + 1) * TetrisBoard::CELL_SIZE,
|
||||
by * TetrisBoard::CELL_SIZE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
320
tetros/src/game/falling.rs
Normal file
320
tetros/src/game/falling.rs
Normal file
@ -0,0 +1,320 @@
|
||||
use rand::seq::IndexedRandom;
|
||||
|
||||
use crate::{drivers::vga::VgaColor, RNG};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Tetromino {
|
||||
/// The 1x4 line
|
||||
Straight,
|
||||
|
||||
/// The 2x2 square
|
||||
Square,
|
||||
|
||||
/// The "L" shape
|
||||
AngleRight,
|
||||
|
||||
/// The mirror "L" shape
|
||||
AngleLeft,
|
||||
|
||||
/// The skew shape "Z"
|
||||
SkewRight,
|
||||
|
||||
/// The mirror skew shape
|
||||
SkewLeft,
|
||||
|
||||
/// The "T" shape
|
||||
Tee,
|
||||
}
|
||||
|
||||
impl Tetromino {
|
||||
/// Pick a random tetromino
|
||||
pub fn choose_rand() -> Self {
|
||||
let ominos = [
|
||||
Tetromino::Straight,
|
||||
Tetromino::Square,
|
||||
Tetromino::AngleRight,
|
||||
Tetromino::AngleLeft,
|
||||
Tetromino::SkewRight,
|
||||
Tetromino::SkewLeft,
|
||||
Tetromino::Tee,
|
||||
];
|
||||
|
||||
let mut rng = RNG.lock();
|
||||
*(ominos.choose(&mut rng).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Direction {
|
||||
North,
|
||||
East,
|
||||
South,
|
||||
West,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
/// Rotate this direction clockwise
|
||||
pub fn rot_cw(self) -> Self {
|
||||
match self {
|
||||
Self::North => Self::East,
|
||||
Self::East => Self::South,
|
||||
Self::South => Self::West,
|
||||
Self::West => Self::North,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FallingTetromino {
|
||||
tetromino: Tetromino,
|
||||
direction: Direction,
|
||||
center_x: usize,
|
||||
center_y: usize,
|
||||
|
||||
pub color: VgaColor,
|
||||
}
|
||||
|
||||
impl FallingTetromino {
|
||||
/// Make a new falling tetromino
|
||||
pub fn new(tetromino: Tetromino, color: VgaColor, center_x: usize, center_y: usize) -> Self {
|
||||
Self {
|
||||
tetromino,
|
||||
direction: Direction::North,
|
||||
color,
|
||||
center_x,
|
||||
center_y,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random tetromino at the given position
|
||||
pub fn random(center_x: usize, center_y: usize) -> Self {
|
||||
Self::new(
|
||||
Tetromino::choose_rand(),
|
||||
VgaColor::choose_rand(),
|
||||
center_x,
|
||||
center_y,
|
||||
)
|
||||
}
|
||||
|
||||
// Move this tetromino
|
||||
pub fn translate(&mut self, x: i16, y: i16) {
|
||||
if x > 0 {
|
||||
let x = usize::try_from(x).unwrap();
|
||||
self.center_x += x;
|
||||
} else if x < 0 {
|
||||
let x = usize::try_from(-x).unwrap();
|
||||
self.center_x -= x;
|
||||
}
|
||||
|
||||
if y > 0 {
|
||||
let y = usize::try_from(y).unwrap();
|
||||
self.center_y += y;
|
||||
} else if y < 0 {
|
||||
let y = usize::try_from(-y).unwrap();
|
||||
self.center_y -= y;
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotate this tetromino clockwise
|
||||
pub fn rotate_cw(&mut self) {
|
||||
self.direction = self.direction.rot_cw()
|
||||
}
|
||||
|
||||
/// Returns the positions of this falling tetromino's tiles.
|
||||
pub fn tiles(&self) -> [(usize, usize); 4] {
|
||||
match (&self.tetromino, self.direction) {
|
||||
(Tetromino::Square, _) => [
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x + 1, self.center_y + 1),
|
||||
],
|
||||
|
||||
//
|
||||
// Straight
|
||||
//
|
||||
(Tetromino::Straight, Direction::North) => [
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x, self.center_y + 2),
|
||||
],
|
||||
|
||||
(Tetromino::Straight, Direction::East) => [
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x + 2, self.center_y),
|
||||
],
|
||||
|
||||
(Tetromino::Straight, Direction::South) => [
|
||||
(self.center_x + 1, self.center_y - 1),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x + 1, self.center_y + 1),
|
||||
(self.center_x + 1, self.center_y + 2),
|
||||
],
|
||||
|
||||
(Tetromino::Straight, Direction::West) => [
|
||||
(self.center_x - 1, self.center_y + 1),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x + 1, self.center_y + 1),
|
||||
(self.center_x + 2, self.center_y + 1),
|
||||
],
|
||||
|
||||
//
|
||||
// Right Angle
|
||||
//
|
||||
(Tetromino::AngleRight, Direction::North) => [
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x + 1, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::AngleRight, Direction::East) => [
|
||||
(self.center_x - 1, self.center_y + 1),
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
],
|
||||
|
||||
(Tetromino::AngleRight, Direction::South) => [
|
||||
(self.center_x - 1, self.center_y - 1),
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::AngleRight, Direction::West) => [
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x + 1, self.center_y - 1),
|
||||
],
|
||||
|
||||
//
|
||||
// Left Angle
|
||||
//
|
||||
(Tetromino::AngleLeft, Direction::North) => [
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x - 1, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::AngleLeft, Direction::East) => [
|
||||
(self.center_x - 1, self.center_y - 1),
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
],
|
||||
|
||||
(Tetromino::AngleLeft, Direction::South) => [
|
||||
(self.center_x + 1, self.center_y - 1),
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::AngleLeft, Direction::West) => [
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x + 1, self.center_y + 1),
|
||||
],
|
||||
|
||||
//
|
||||
// Left Skew
|
||||
//
|
||||
(Tetromino::SkewLeft, Direction::North) => [
|
||||
(self.center_x - 1, self.center_y + 1),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
],
|
||||
|
||||
(Tetromino::SkewLeft, Direction::East) => [
|
||||
(self.center_x - 1, self.center_y - 1),
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::SkewLeft, Direction::South) => [
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x + 1, self.center_y - 1),
|
||||
],
|
||||
|
||||
(Tetromino::SkewLeft, Direction::West) => [
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x + 1, self.center_y + 1),
|
||||
],
|
||||
|
||||
//
|
||||
// Right Skew
|
||||
//
|
||||
(Tetromino::SkewRight, Direction::North) => [
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x + 1, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::SkewRight, Direction::East) => [
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x - 1, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::SkewRight, Direction::South) => [
|
||||
(self.center_x - 1, self.center_y - 1),
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
],
|
||||
|
||||
(Tetromino::SkewRight, Direction::West) => [
|
||||
(self.center_x + 1, self.center_y - 1),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
],
|
||||
|
||||
//
|
||||
// Tee
|
||||
//
|
||||
(Tetromino::Tee, Direction::North) => [
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x, self.center_y - 1),
|
||||
],
|
||||
|
||||
(Tetromino::Tee, Direction::East) => [
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x + 1, self.center_y),
|
||||
],
|
||||
|
||||
(Tetromino::Tee, Direction::South) => [
|
||||
(self.center_x - 1, self.center_y),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x + 1, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
],
|
||||
|
||||
(Tetromino::Tee, Direction::West) => [
|
||||
(self.center_x, self.center_y - 1),
|
||||
(self.center_x, self.center_y),
|
||||
(self.center_x, self.center_y + 1),
|
||||
(self.center_x - 1, self.center_y),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
8
tetros/src/game/mod.rs
Normal file
8
tetros/src/game/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
//! This crate contains all tetris game logic.
|
||||
//! No low-level magic here.
|
||||
|
||||
mod board;
|
||||
pub use board::*;
|
||||
|
||||
mod falling;
|
||||
pub use falling::*;
|
133
tetros/src/idt/entry.rs
Normal file
133
tetros/src/idt/entry.rs
Normal file
@ -0,0 +1,133 @@
|
||||
use core::{
|
||||
fmt::{self},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use crate::os::util::get_cs;
|
||||
|
||||
use super::{HandlerFuncType, VirtAddr};
|
||||
|
||||
/// An Interrupt Descriptor Table entry.
|
||||
///
|
||||
/// The generic parameter is some [`HandlerFuncType`], depending on the interrupt vector.
|
||||
///
|
||||
/// For reference, see https://wiki.osdev.org/Interrupt_Descriptor_Table#Gate_Descriptor
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Entry<F> {
|
||||
phantom: PhantomData<F>,
|
||||
|
||||
/// Low 16 bits of the 32-bit ISR pointer
|
||||
pointer_low: u16,
|
||||
|
||||
/// A segment selector that points to a valid code segment in the GDT
|
||||
segment: u16,
|
||||
|
||||
/// Always zero
|
||||
reserved: u8,
|
||||
|
||||
/// Interrupt options.
|
||||
///
|
||||
/// Bits 0-3: gate type. One of:
|
||||
/// - 0101: Task Gate, note that in this case, the Offset value is unused and should be set to zero.
|
||||
/// - 0110: 16-bit Interrupt Gate
|
||||
/// - 0111: 16-bit Trap Gate
|
||||
/// - 1110: 32-bit Interrupt Gate
|
||||
/// - 1111: 32-bit Trap Gate
|
||||
///
|
||||
/// Bit 4: always zero
|
||||
///
|
||||
/// Bits 5-6: DPL, defines cpu privilege level require to INT this interrupt.
|
||||
/// For our purposes, always zero.
|
||||
///
|
||||
/// Bit 7: 1 if present, 0 if not.
|
||||
///
|
||||
/// Note that these appear in reverse order in Rust.
|
||||
/// For example, 0b1000_1110 sets `present = 1` and `type = 1110`.
|
||||
options: u8,
|
||||
|
||||
/// High 16 bits of the 32-bit ISR pointer
|
||||
pointer_high: u16,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Entry<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Entry")
|
||||
.field("preset", &self.is_present())
|
||||
.field(
|
||||
"handler_addr",
|
||||
&format_args!("{:#x}", self.handler_addr().unwrap_or(VirtAddr::new(0))),
|
||||
)
|
||||
.field("options", &self.options)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// spell:off
|
||||
impl<F> Entry<F> {
|
||||
/// Create a valid non-present IDT entry.
|
||||
#[inline]
|
||||
pub const fn missing() -> Self {
|
||||
Entry {
|
||||
pointer_low: 0,
|
||||
segment: 0,
|
||||
reserved: 0,
|
||||
options: 0,
|
||||
pointer_high: 0,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the handler address for the IDT entry and sets the following defaults:
|
||||
/// - The code selector is the code segment currently active in the CPU
|
||||
/// - The present bit is set
|
||||
/// - Interrupts are disabled on handler invocation
|
||||
/// - The privilege level (DPL) is [`PrivilegeLevel::Ring0`]
|
||||
/// - No IST is configured (existing stack will be used)
|
||||
/// - This is a 32-bit interrupt gate
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `addr` is the address of a valid interrupt handler function,
|
||||
/// and the signature of such a function is correct for the entry type.
|
||||
#[inline]
|
||||
pub unsafe fn set_handler_addr(&mut self, addr: VirtAddr) {
|
||||
let ptr = addr.as_u32();
|
||||
|
||||
self.pointer_low = ptr as u16;
|
||||
self.pointer_high = (ptr >> 16) as u16;
|
||||
self.options = 0b1000_1110; // Present 32-bit interrupt gate
|
||||
|
||||
// SAFETY: The current CS is a valid, long-mode code segment.
|
||||
self.segment = get_cs();
|
||||
}
|
||||
|
||||
/// True if the "present" bit is set, false otherwise.
|
||||
pub fn is_present(&self) -> bool {
|
||||
return self.options & 0b1000_0000 == 0b1000_0000;
|
||||
}
|
||||
|
||||
/// Get the address of this entry's handler.
|
||||
pub fn handler_addr(&self) -> Option<VirtAddr> {
|
||||
self.is_present().then_some(VirtAddr::new(
|
||||
self.pointer_low as u32 + ((self.pointer_high as u32) << 16),
|
||||
))
|
||||
}
|
||||
}
|
||||
// spell:on
|
||||
|
||||
// spell:off
|
||||
impl<F: HandlerFuncType> Entry<F> {
|
||||
/// Sets the handler address for the IDT entry and sets the following defaults:
|
||||
/// - The code selector is the code segment currently active in the CPU
|
||||
/// - The present bit is set
|
||||
/// - Interrupts are disabled on handler invocation
|
||||
/// - The privilege level (DPL) is [`PrivilegeLevel::Ring0`]
|
||||
/// - No IST is configured (existing stack will be used)
|
||||
/// - This is a 32-bit interrupt gate
|
||||
#[inline]
|
||||
pub fn set_handler_fn(&mut self, handler: F) {
|
||||
unsafe { self.set_handler_addr(handler.to_virt_addr()) }
|
||||
}
|
||||
}
|
||||
// spell:on
|
122
tetros/src/idt/handler.rs
Normal file
122
tetros/src/idt/handler.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
use super::{InterruptStackFrame, VirtAddr};
|
||||
|
||||
bitflags! {
|
||||
/// Describes an page fault error code.
|
||||
///
|
||||
/// This structure is defined by the following manual sections:
|
||||
/// * AMD Volume 2: 8.4.2
|
||||
/// * Intel Volume 3A: 4.7
|
||||
#[repr(transparent)]
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
|
||||
pub struct PageFaultErrorCode: u64 {
|
||||
/// If this flag is set, the page fault was caused by a page-protection violation,
|
||||
/// else the page fault was caused by a not-present page.
|
||||
const PROTECTION_VIOLATION = 1;
|
||||
|
||||
/// If this flag is set, the memory access that caused the page fault was a write.
|
||||
/// Else the access that caused the page fault is a memory read. This bit does not
|
||||
/// necessarily indicate the cause of the page fault was a read or write violation.
|
||||
const CAUSED_BY_WRITE = 1 << 1;
|
||||
|
||||
/// If this flag is set, an access in user mode (CPL=3) caused the page fault. Else
|
||||
/// an access in supervisor mode (CPL=0, 1, or 2) caused the page fault. This bit
|
||||
/// does not necessarily indicate the cause of the page fault was a privilege violation.
|
||||
const USER_MODE = 1 << 2;
|
||||
|
||||
/// If this flag is set, the page fault is a result of the processor reading a 1 from
|
||||
/// a reserved field within a page-translation-table entry.
|
||||
const MALFORMED_TABLE = 1 << 3;
|
||||
|
||||
/// If this flag is set, it indicates that the access that caused the page fault was an
|
||||
/// instruction fetch.
|
||||
const INSTRUCTION_FETCH = 1 << 4;
|
||||
|
||||
/// If this flag is set, it indicates that the page fault was caused by a protection key.
|
||||
const PROTECTION_KEY = 1 << 5;
|
||||
|
||||
/// If this flag is set, it indicates that the page fault was caused by a shadow stack
|
||||
/// access.
|
||||
const SHADOW_STACK = 1 << 6;
|
||||
|
||||
/// If this flag is set, it indicates that the page fault was caused by SGX access-control
|
||||
/// requirements (Intel-only).
|
||||
const SGX = 1 << 15;
|
||||
|
||||
/// If this flag is set, it indicates that the page fault is a result of the processor
|
||||
/// encountering an RMP violation (AMD-only).
|
||||
const RMP = 1 << 31;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MARK: types
|
||||
//
|
||||
|
||||
/// A handler function for an interrupt or an exception without error code.
|
||||
pub type HandlerFunc = extern "x86-interrupt" fn(InterruptStackFrame);
|
||||
|
||||
/// A handler function for an exception that pushes an error code.
|
||||
pub type HandlerFuncWithErrCode = extern "x86-interrupt" fn(InterruptStackFrame, error_code: u32);
|
||||
|
||||
/// A handler function that must not return, e.g. for a machine check exception.
|
||||
pub type DivergingHandlerFunc = extern "x86-interrupt" fn(InterruptStackFrame) -> !;
|
||||
|
||||
/// A handler function with an error code that must not return, e.g. for a double fault exception.
|
||||
pub type DivergingHandlerFuncWithErrCode =
|
||||
extern "x86-interrupt" fn(InterruptStackFrame, error_code: u32) -> !;
|
||||
|
||||
/// A page fault handler function that pushes a page fault error code.
|
||||
pub type PageFaultHandlerFunc =
|
||||
extern "x86-interrupt" fn(InterruptStackFrame, error_code: PageFaultErrorCode);
|
||||
|
||||
/// A common trait for all handler functions usable in [`Entry`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Implementors have to ensure that `to_virt_addr` returns a valid address.
|
||||
pub unsafe trait HandlerFuncType {
|
||||
/// Get the virtual address of the handler function.
|
||||
fn to_virt_addr(self) -> VirtAddr;
|
||||
}
|
||||
|
||||
unsafe impl HandlerFuncType for HandlerFunc {
|
||||
#[inline]
|
||||
fn to_virt_addr(self) -> VirtAddr {
|
||||
#[expect(clippy::fn_to_numeric_cast_with_truncation)]
|
||||
VirtAddr::new(self as u32)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HandlerFuncType for HandlerFuncWithErrCode {
|
||||
#[inline]
|
||||
fn to_virt_addr(self) -> VirtAddr {
|
||||
#[expect(clippy::fn_to_numeric_cast_with_truncation)]
|
||||
VirtAddr::new(self as u32)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HandlerFuncType for DivergingHandlerFunc {
|
||||
#[inline]
|
||||
fn to_virt_addr(self) -> VirtAddr {
|
||||
#[expect(clippy::fn_to_numeric_cast_with_truncation)]
|
||||
VirtAddr::new(self as u32)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HandlerFuncType for DivergingHandlerFuncWithErrCode {
|
||||
#[inline]
|
||||
fn to_virt_addr(self) -> VirtAddr {
|
||||
#[expect(clippy::fn_to_numeric_cast_with_truncation)]
|
||||
VirtAddr::new(self as u32)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HandlerFuncType for PageFaultHandlerFunc {
|
||||
#[inline]
|
||||
fn to_virt_addr(self) -> VirtAddr {
|
||||
#[expect(clippy::fn_to_numeric_cast_with_truncation)]
|
||||
VirtAddr::new(self as u32)
|
||||
}
|
||||
}
|
20
tetros/src/idt/mod.rs
Normal file
20
tetros/src/idt/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
//! IDT structures and routines for 32-bit x86.
|
||||
//!
|
||||
//! Based on code from the `x86_64` crate.
|
||||
//! Many comments are copied verbatim.
|
||||
//! (most notably, in `table.rs`)
|
||||
|
||||
mod virtaddr;
|
||||
pub use virtaddr::*;
|
||||
|
||||
mod entry;
|
||||
pub use entry::*;
|
||||
|
||||
mod table;
|
||||
pub use table::*;
|
||||
|
||||
mod handler;
|
||||
pub use handler::*;
|
||||
|
||||
mod stackframe;
|
||||
pub use stackframe::*;
|
62
tetros/src/idt/stackframe.rs
Normal file
62
tetros/src/idt/stackframe.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use core::{fmt, ops::Deref};
|
||||
|
||||
use super::VirtAddr;
|
||||
use crate::os::EFlags;
|
||||
|
||||
/// Wrapper type for the interrupt stack frame pushed by the CPU.
|
||||
///
|
||||
/// This type derefs to an [`InterruptStackFrameValue`], which allows reading the actual values.
|
||||
///
|
||||
/// This wrapper ensures that the stack frame cannot be modified.
|
||||
/// This prevents undefined behavior.
|
||||
#[repr(transparent)]
|
||||
pub struct InterruptStackFrame(InterruptStackFrameValue);
|
||||
|
||||
impl Deref for InterruptStackFrame {
|
||||
type Target = InterruptStackFrameValue;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for InterruptStackFrame {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the interrupt stack frame pushed by the CPU on interrupt or exception entry.
|
||||
///
|
||||
/// See https://wiki.osdev.org/Interrupt_Service_Routines#x86
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct InterruptStackFrameValue {
|
||||
/// This value points to the instruction that should be executed when the interrupt
|
||||
/// handler returns. For most interrupts, this value points to the instruction immediately
|
||||
/// following the last executed instruction. However, for some exceptions (e.g., page faults),
|
||||
/// this value points to the faulting instruction, so that the instruction is restarted on
|
||||
/// return.
|
||||
pub eip: VirtAddr,
|
||||
|
||||
/// The code segment selector at the time of the interrupt.
|
||||
pub cs: u16,
|
||||
|
||||
/// Padding for CS
|
||||
_reserved1: [u8; 2],
|
||||
|
||||
/// The EFLAGS register before the interrupt handler was invoked.
|
||||
pub cpu_flags: EFlags,
|
||||
}
|
||||
|
||||
impl fmt::Debug for InterruptStackFrameValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut s = f.debug_struct("InterruptStackFrame");
|
||||
s.field("instruction_pointer", &self.eip);
|
||||
s.field("code_segment", &self.cs);
|
||||
s.field("cpu_flags", &self.cpu_flags);
|
||||
s.finish()
|
||||
}
|
||||
}
|
481
tetros/src/idt/table.rs
Normal file
481
tetros/src/idt/table.rs
Normal file
@ -0,0 +1,481 @@
|
||||
use core::{arch::asm, mem::size_of};
|
||||
|
||||
use super::{
|
||||
handler::HandlerFunc, DivergingHandlerFunc, DivergingHandlerFuncWithErrCode, Entry,
|
||||
HandlerFuncWithErrCode, PageFaultHandlerFunc,
|
||||
};
|
||||
|
||||
// spell:off
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[repr(align(8))]
|
||||
pub struct InterruptDescriptorTable {
|
||||
/// A divide error (`#DE`) occurs when the denominator of a DIV instruction or
|
||||
/// an IDIV instruction is 0. A `#DE` also occurs if the result is too large to be
|
||||
/// represented in the destination.
|
||||
///
|
||||
/// The saved instruction pointer points to the instruction that caused the `#DE`.
|
||||
///
|
||||
/// The vector number of the `#DE` exception is 0.
|
||||
pub divide_error: Entry<HandlerFunc>,
|
||||
|
||||
/// When the debug-exception mechanism is enabled, a `#DB` exception can occur under any
|
||||
/// of the following circumstances:
|
||||
///
|
||||
/// <details>
|
||||
///
|
||||
/// - Instruction execution.
|
||||
/// - Instruction single stepping.
|
||||
/// - Data read.
|
||||
/// - Data write.
|
||||
/// - I/O read.
|
||||
/// - I/O write.
|
||||
/// - Task switch.
|
||||
/// - Debug-register access, or general detect fault (debug register access when DR7.GD=1).
|
||||
/// - Executing the INT1 instruction (opcode 0F1h).
|
||||
///
|
||||
/// </details>
|
||||
///
|
||||
/// `#DB` conditions are enabled and disabled using the debug-control register, `DR7`
|
||||
/// and `RFLAGS.TF`.
|
||||
///
|
||||
/// In the following cases, the saved instruction pointer points to the instruction that
|
||||
/// caused the `#DB`:
|
||||
///
|
||||
/// - Instruction execution.
|
||||
/// - Invalid debug-register access, or general detect.
|
||||
///
|
||||
/// In all other cases, the instruction that caused the `#DB` is completed, and the saved
|
||||
/// instruction pointer points to the instruction after the one that caused the `#DB`.
|
||||
///
|
||||
/// The vector number of the `#DB` exception is 1.
|
||||
pub debug: Entry<HandlerFunc>,
|
||||
|
||||
/// An non maskable interrupt exception (NMI) occurs as a result of system logic
|
||||
/// signaling a non-maskable interrupt to the processor.
|
||||
///
|
||||
/// The processor recognizes an NMI at an instruction boundary.
|
||||
/// The saved instruction pointer points to the instruction immediately following the
|
||||
/// boundary where the NMI was recognized.
|
||||
///
|
||||
/// The vector number of the NMI exception is 2.
|
||||
pub non_maskable_interrupt: Entry<HandlerFunc>,
|
||||
|
||||
/// A breakpoint (`#BP`) exception occurs when an `INT3` instruction is executed. The
|
||||
/// `INT3` is normally used by debug software to set instruction breakpoints by replacing
|
||||
///
|
||||
/// The saved instruction pointer points to the byte after the `INT3` instruction.
|
||||
///
|
||||
/// The vector number of the `#BP` exception is 3.
|
||||
pub breakpoint: Entry<HandlerFunc>,
|
||||
|
||||
/// An overflow exception (`#OF`) occurs as a result of executing an `INTO` instruction
|
||||
/// while the overflow bit in `RFLAGS` is set to 1.
|
||||
///
|
||||
/// The saved instruction pointer points to the instruction following the `INTO`
|
||||
/// instruction that caused the `#OF`.
|
||||
///
|
||||
/// The vector number of the `#OF` exception is 4.
|
||||
pub overflow: Entry<HandlerFunc>,
|
||||
|
||||
/// A bound-range exception (`#BR`) exception can occur as a result of executing
|
||||
/// the `BOUND` instruction. The `BOUND` instruction compares an array index (first
|
||||
/// operand) with the lower bounds and upper bounds of an array (second operand).
|
||||
/// If the array index is not within the array boundary, the `#BR` occurs.
|
||||
///
|
||||
/// The saved instruction pointer points to the `BOUND` instruction that caused the `#BR`.
|
||||
///
|
||||
/// The vector number of the `#BR` exception is 5.
|
||||
pub bound_range_exceeded: Entry<HandlerFunc>,
|
||||
|
||||
/// An invalid opcode exception (`#UD`) occurs when an attempt is made to execute an
|
||||
/// invalid or undefined opcode. The validity of an opcode often depends on the
|
||||
/// processor operating mode.
|
||||
///
|
||||
/// <details><summary>A `#UD` occurs under the following conditions:</summary>
|
||||
///
|
||||
/// - Execution of any reserved or undefined opcode in any mode.
|
||||
/// - Execution of the `UD2` instruction.
|
||||
/// - Use of the `LOCK` prefix on an instruction that cannot be locked.
|
||||
/// - Use of the `LOCK` prefix on a lockable instruction with a non-memory target location.
|
||||
/// - Execution of an instruction with an invalid-operand type.
|
||||
/// - Execution of the `SYSENTER` or `SYSEXIT` instructions in long mode.
|
||||
/// - Execution of any of the following instructions in 64-bit mode: `AAA`, `AAD`,
|
||||
/// `AAM`, `AAS`, `BOUND`, `CALL` (opcode 9A), `DAA`, `DAS`, `DEC`, `INC`, `INTO`,
|
||||
/// `JMP` (opcode EA), `LDS`, `LES`, `POP` (`DS`, `ES`, `SS`), `POPA`, `PUSH` (`CS`,
|
||||
/// `DS`, `ES`, `SS`), `PUSHA`, `SALC`.
|
||||
/// - Execution of the `ARPL`, `LAR`, `LLDT`, `LSL`, `LTR`, `SLDT`, `STR`, `VERR`, or
|
||||
/// `VERW` instructions when protected mode is not enabled, or when virtual-8086 mode
|
||||
/// is enabled.
|
||||
/// - Execution of any legacy SSE instruction when `CR4.OSFXSR` is cleared to 0.
|
||||
/// - Execution of any SSE instruction (uses `YMM`/`XMM` registers), or 64-bit media
|
||||
/// instruction (uses `MMXTM` registers) when `CR0.EM` = 1.
|
||||
/// - Execution of any SSE floating-point instruction (uses `YMM`/`XMM` registers) that
|
||||
/// causes a numeric exception when `CR4.OSXMMEXCPT` = 0.
|
||||
/// - Use of the `DR4` or `DR5` debug registers when `CR4.DE` = 1.
|
||||
/// - Execution of `RSM` when not in `SMM` mode.
|
||||
///
|
||||
/// </details>
|
||||
///
|
||||
/// The saved instruction pointer points to the instruction that caused the `#UD`.
|
||||
///
|
||||
/// The vector number of the `#UD` exception is 6.
|
||||
pub invalid_opcode: Entry<HandlerFunc>,
|
||||
|
||||
/// A device not available exception (`#NM`) occurs under any of the following conditions:
|
||||
///
|
||||
/// <details>
|
||||
///
|
||||
/// - An `FWAIT`/`WAIT` instruction is executed when `CR0.MP=1` and `CR0.TS=1`.
|
||||
/// - Any x87 instruction other than `FWAIT` is executed when `CR0.EM=1`.
|
||||
/// - Any x87 instruction is executed when `CR0.TS=1`. The `CR0.MP` bit controls whether the
|
||||
/// `FWAIT`/`WAIT` instruction causes an `#NM` exception when `TS=1`.
|
||||
/// - Any 128-bit or 64-bit media instruction when `CR0.TS=1`.
|
||||
///
|
||||
/// </details>
|
||||
///
|
||||
/// The saved instruction pointer points to the instruction that caused the `#NM`.
|
||||
///
|
||||
/// The vector number of the `#NM` exception is 7.
|
||||
pub device_not_available: Entry<HandlerFunc>,
|
||||
|
||||
/// A double fault (`#DF`) exception can occur when a second exception occurs during
|
||||
/// the handling of a prior (first) exception or interrupt handler.
|
||||
///
|
||||
/// <details>
|
||||
///
|
||||
/// Usually, the first and second exceptions can be handled sequentially without
|
||||
/// resulting in a `#DF`. In this case, the first exception is considered _benign_, as
|
||||
/// it does not harm the ability of the processor to handle the second exception. In some
|
||||
/// cases, however, the first exception adversely affects the ability of the processor to
|
||||
/// handle the second exception. These exceptions contribute to the occurrence of a `#DF`,
|
||||
/// and are called _contributory exceptions_. The following exceptions are contributory:
|
||||
///
|
||||
/// - Invalid-TSS Exception
|
||||
/// - Segment-Not-Present Exception
|
||||
/// - Stack Exception
|
||||
/// - General-Protection Exception
|
||||
///
|
||||
/// A double-fault exception occurs in the following cases:
|
||||
///
|
||||
/// - If a contributory exception is followed by another contributory exception.
|
||||
/// - If a divide-by-zero exception is followed by a contributory exception.
|
||||
/// - If a page fault is followed by another page fault or a contributory exception.
|
||||
///
|
||||
/// If a third interrupting event occurs while transferring control to the `#DF` handler,
|
||||
/// the processor shuts down.
|
||||
///
|
||||
/// </details>
|
||||
///
|
||||
/// The returned error code is always zero. The saved instruction pointer is undefined,
|
||||
/// and the program cannot be restarted.
|
||||
///
|
||||
/// The vector number of the `#DF` exception is 8.
|
||||
pub double_fault: Entry<DivergingHandlerFuncWithErrCode>,
|
||||
|
||||
/// This interrupt vector is reserved. It is for a discontinued exception originally used
|
||||
/// by processors that supported external x87-instruction coprocessors. On those processors,
|
||||
/// the exception condition is caused by an invalid-segment or invalid-page access on an
|
||||
/// x87-instruction coprocessor-instruction operand. On current processors, this condition
|
||||
/// causes a general-protection exception to occur.
|
||||
coprocessor_segment_overrun: Entry<HandlerFunc>,
|
||||
|
||||
/// An invalid TSS exception (`#TS`) occurs only as a result of a control transfer through
|
||||
/// a gate descriptor that results in an invalid stack-segment reference using an `SS`
|
||||
/// selector in the TSS.
|
||||
///
|
||||
/// The returned error code is the `SS` segment selector. The saved instruction pointer
|
||||
/// points to the control-transfer instruction that caused the `#TS`.
|
||||
///
|
||||
/// The vector number of the `#TS` exception is 10.
|
||||
pub invalid_tss: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// An segment-not-present exception (`#NP`) occurs when an attempt is made to load a
|
||||
/// segment or gate with a clear present bit.
|
||||
///
|
||||
/// The returned error code is the segment-selector index of the segment descriptor
|
||||
/// causing the `#NP` exception. The saved instruction pointer points to the instruction
|
||||
/// that loaded the segment selector resulting in the `#NP`.
|
||||
///
|
||||
/// The vector number of the `#NP` exception is 11.
|
||||
pub segment_not_present: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// An stack segment exception (`#SS`) can occur in the following situations:
|
||||
///
|
||||
/// - Implied stack references in which the stack address is not in canonical
|
||||
/// form. Implied stack references include all push and pop instructions, and any
|
||||
/// instruction using `RSP` or `RBP` as a base register.
|
||||
/// - Attempting to load a stack-segment selector that references a segment descriptor
|
||||
/// containing a clear present bit.
|
||||
/// - Any stack access that fails the stack-limit check.
|
||||
///
|
||||
/// The returned error code depends on the cause of the `#SS`. If the cause is a cleared
|
||||
/// present bit, the error code is the corresponding segment selector. Otherwise, the
|
||||
/// error code is zero. The saved instruction pointer points to the instruction that
|
||||
/// caused the `#SS`.
|
||||
///
|
||||
/// The vector number of the `#NP` exception is 12.
|
||||
pub stack_segment_fault: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// A general protection fault (`#GP`) can occur in various situations. Common causes include:
|
||||
///
|
||||
/// - Executing a privileged instruction while `CPL > 0`.
|
||||
/// - Writing a 1 into any register field that is reserved, must be zero (MBZ).
|
||||
/// - Attempting to execute an SSE instruction specifying an unaligned memory operand.
|
||||
/// - Loading a non-canonical base address into the `GDTR` or `IDTR`.
|
||||
/// - Using WRMSR to write a read-only MSR.
|
||||
/// - Any long-mode consistency-check violation.
|
||||
///
|
||||
/// The returned error code is a segment selector, if the cause of the `#GP` is
|
||||
/// segment-related, and zero otherwise. The saved instruction pointer points to
|
||||
/// the instruction that caused the `#GP`.
|
||||
///
|
||||
/// The vector number of the `#GP` exception is 13.
|
||||
pub general_protection_fault: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// A page fault (`#PF`) can occur during a memory access in any of the following situations:
|
||||
///
|
||||
/// - A page-translation-table entry or physical page involved in translating the memory
|
||||
/// access is not present in physical memory. This is indicated by a cleared present
|
||||
/// bit in the translation-table entry.
|
||||
/// - An attempt is made by the processor to load the instruction TLB with a translation
|
||||
/// for a non-executable page.
|
||||
/// - The memory access fails the paging-protection checks (user/supervisor, read/write,
|
||||
/// or both).
|
||||
/// - A reserved bit in one of the page-translation-table entries is set to 1. A `#PF`
|
||||
/// occurs for this reason only when `CR4.PSE=1` or `CR4.PAE=1`.
|
||||
///
|
||||
/// The virtual (linear) address that caused the `#PF` is stored in the `CR2` register.
|
||||
/// The saved instruction pointer points to the instruction that caused the `#PF`.
|
||||
///
|
||||
/// The page-fault error code is described by the
|
||||
/// [`PageFaultErrorCode`](struct.PageFaultErrorCode.html) struct.
|
||||
///
|
||||
/// The vector number of the `#PF` exception is 14.
|
||||
pub page_fault: Entry<PageFaultHandlerFunc>,
|
||||
|
||||
/// vector nr. 15
|
||||
reserved_1: Entry<HandlerFunc>,
|
||||
|
||||
/// The x87 Floating-Point Exception-Pending exception (`#MF`) is used to handle unmasked x87
|
||||
/// floating-point exceptions. In 64-bit mode, the x87 floating point unit is not used
|
||||
/// anymore, so this exception is only relevant when executing programs in the 32-bit
|
||||
/// compatibility mode.
|
||||
///
|
||||
/// The vector number of the `#MF` exception is 16.
|
||||
pub x87_floating_point: Entry<HandlerFunc>,
|
||||
|
||||
/// An alignment check exception (`#AC`) occurs when an unaligned-memory data reference
|
||||
/// is performed while alignment checking is enabled. An `#AC` can occur only when CPL=3.
|
||||
///
|
||||
/// The returned error code is always zero. The saved instruction pointer points to the
|
||||
/// instruction that caused the `#AC`.
|
||||
///
|
||||
/// The vector number of the `#AC` exception is 17.
|
||||
pub alignment_check: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// The machine check exception (`#MC`) is model specific. Processor implementations
|
||||
/// are not required to support the `#MC` exception, and those implementations that do
|
||||
/// support `#MC` can vary in how the `#MC` exception mechanism works.
|
||||
///
|
||||
/// There is no reliable way to restart the program.
|
||||
///
|
||||
/// The vector number of the `#MC` exception is 18.
|
||||
pub machine_check: Entry<DivergingHandlerFunc>,
|
||||
|
||||
/// The SIMD Floating-Point Exception (`#XF`) is used to handle unmasked SSE
|
||||
/// floating-point exceptions. The SSE floating-point exceptions reported by
|
||||
/// the `#XF` exception are (including mnemonics):
|
||||
///
|
||||
/// - IE: Invalid-operation exception (also called #I).
|
||||
/// - DE: Denormalized-operand exception (also called #D).
|
||||
/// - ZE: Zero-divide exception (also called #Z).
|
||||
/// - OE: Overflow exception (also called #O).
|
||||
/// - UE: Underflow exception (also called #U).
|
||||
/// - PE: Precision exception (also called #P or inexact-result exception).
|
||||
///
|
||||
/// The saved instruction pointer points to the instruction that caused the `#XF`.
|
||||
///
|
||||
/// The vector number of the `#XF` exception is 19.
|
||||
pub simd_floating_point: Entry<HandlerFunc>,
|
||||
|
||||
/// vector nr. 20
|
||||
pub virtualization: Entry<HandlerFunc>,
|
||||
|
||||
/// A #CP exception is generated when shadow stacks are enabled and mismatch
|
||||
/// scenarios are detected (possible error code cases below).
|
||||
///
|
||||
/// The error code is the #CP error code, for each of the following situations:
|
||||
/// - A RET (near) instruction encountered a return address mismatch.
|
||||
/// - A RET (far) instruction encountered a return address mismatch.
|
||||
/// - A RSTORSSP instruction encountered an invalid shadow stack restore token.
|
||||
/// - A SETSSBY instruction encountered an invalid supervisor shadow stack token.
|
||||
/// - A missing ENDBRANCH instruction if indirect branch tracking is enabled.
|
||||
///
|
||||
/// vector nr. 21
|
||||
pub cp_protection_exception: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// vector nr. 22-27
|
||||
reserved_2: [Entry<HandlerFunc>; 6],
|
||||
|
||||
/// The Hypervisor Injection Exception (`#HV`) is injected by a hypervisor
|
||||
/// as a doorbell to inform an `SEV-SNP` enabled guest running with the
|
||||
/// `Restricted Injection` feature of events to be processed.
|
||||
///
|
||||
/// `SEV-SNP` stands for the _"Secure Nested Paging"_ feature of the _"AMD
|
||||
/// Secure Encrypted Virtualization"_ technology. The `Restricted
|
||||
/// Injection` feature disables all hypervisor-based interrupt queuing
|
||||
/// and event injection of all vectors except #HV.
|
||||
///
|
||||
/// The `#HV` exception is a benign exception and can only be injected as
|
||||
/// an exception and without an error code. `SEV-SNP` enabled guests are
|
||||
/// expected to communicate with the hypervisor about events via a
|
||||
/// software-managed para-virtualization interface.
|
||||
///
|
||||
/// The vector number of the ``#HV`` exception is 28.
|
||||
pub hv_injection_exception: Entry<HandlerFunc>,
|
||||
|
||||
/// The VMM Communication Exception (`#VC`) is always generated by hardware when an `SEV-ES`
|
||||
/// enabled guest is running and an `NAE` event occurs.
|
||||
///
|
||||
/// `SEV-ES` stands for the _"Encrypted State"_ feature of the _"AMD Secure Encrypted Virtualization"_
|
||||
/// technology. `NAE` stands for an _"Non-Automatic Exit"_, which is an `VMEXIT` event that requires
|
||||
/// hypervisor emulation. See
|
||||
/// [this whitepaper](https://www.amd.com/system/files/TechDocs/Protecting%20VM%20Register%20State%20with%20SEV-ES.pdf)
|
||||
/// for an overview of the `SEV-ES` feature.
|
||||
///
|
||||
/// The `#VC` exception is a precise, contributory, fault-type exception utilizing exception vector 29.
|
||||
/// This exception cannot be masked. The error code of the `#VC` exception is equal
|
||||
/// to the `#VMEXIT` code of the event that caused the `NAE`.
|
||||
///
|
||||
/// In response to a `#VC` exception, a typical flow would involve the guest handler inspecting the error
|
||||
/// code to determine the cause of the exception and deciding what register state must be copied to the
|
||||
/// `GHCB` (_"Guest Hypervisor Communication Block"_) for the event to be handled. The handler
|
||||
/// should then execute the `VMGEXIT` instruction to
|
||||
/// create an `AE` and invoke the hypervisor. After a later `VMRUN`, guest execution will resume after the
|
||||
/// `VMGEXIT` instruction where the handler can view the results from the hypervisor and copy state from
|
||||
/// the `GHCB` back to its internal state as needed.
|
||||
///
|
||||
/// Note that it is inadvisable for the hypervisor to set the `VMCB` (_"Virtual Machine Control Block"_)
|
||||
/// intercept bit for the `#VC` exception as
|
||||
/// this would prevent proper handling of `NAE`s by the guest. Similarly, the hypervisor should avoid
|
||||
/// setting intercept bits for events that would occur in the `#VC` handler (such as `IRET`).
|
||||
///
|
||||
/// The vector number of the ``#VC`` exception is 29.
|
||||
pub vmm_communication_exception: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// The Security Exception (`#SX`) signals security-sensitive events that occur while
|
||||
/// executing the VMM, in the form of an exception so that the VMM may take appropriate
|
||||
/// action. (A VMM would typically intercept comparable sensitive events in the guest.)
|
||||
/// In the current implementation, the only use of the `#SX` is to redirect external INITs
|
||||
/// into an exception so that the VMM may — among other possibilities.
|
||||
///
|
||||
/// The only error code currently defined is 1, and indicates redirection of INIT has occurred.
|
||||
///
|
||||
/// The vector number of the ``#SX`` exception is 30.
|
||||
pub security_exception: Entry<HandlerFuncWithErrCode>,
|
||||
|
||||
/// vector nr. 31
|
||||
reserved_3: Entry<HandlerFunc>,
|
||||
|
||||
/// User-defined interrupts can be initiated either by system logic or software.
|
||||
/// `interrupts[0]` is interrupt vector 32.
|
||||
///
|
||||
///
|
||||
/// These occur when:
|
||||
///
|
||||
/// - System logic signals an external interrupt request to the processor. The signaling
|
||||
/// mechanism and the method of communicating the interrupt vector to the processor are
|
||||
/// implementation dependent.
|
||||
/// - Software executes an `INTn` instruction. The `INTn` instruction operand provides
|
||||
/// the interrupt vector number.
|
||||
///
|
||||
/// Both methods can be used to initiate an interrupt into vectors 0 through 255. However,
|
||||
/// because vectors 0 through 31 are defined or reserved by the AMD64 architecture,
|
||||
/// software should not use vectors in this range for purposes other than their defined use.
|
||||
///
|
||||
/// The saved instruction pointer depends on the interrupt source:
|
||||
///
|
||||
/// - External interrupts are recognized on instruction boundaries. The saved instruction
|
||||
/// pointer points to the instruction immediately following the boundary where the
|
||||
/// external interrupt was recognized.
|
||||
/// - If the interrupt occurs as a result of executing the INTn instruction, the saved
|
||||
/// instruction pointer points to the instruction after the INTn.
|
||||
pub interrupts: [Entry<HandlerFunc>; 256 - 32],
|
||||
}
|
||||
// spell:on
|
||||
|
||||
//
|
||||
// MARK: impl
|
||||
//
|
||||
|
||||
impl InterruptDescriptorTable {
|
||||
/// Creates a new IDT filled with non-present entries.
|
||||
#[inline]
|
||||
pub const fn new() -> InterruptDescriptorTable {
|
||||
InterruptDescriptorTable {
|
||||
divide_error: Entry::missing(),
|
||||
debug: Entry::missing(),
|
||||
non_maskable_interrupt: Entry::missing(),
|
||||
breakpoint: Entry::missing(),
|
||||
overflow: Entry::missing(),
|
||||
bound_range_exceeded: Entry::missing(),
|
||||
invalid_opcode: Entry::missing(),
|
||||
device_not_available: Entry::missing(),
|
||||
double_fault: Entry::missing(),
|
||||
coprocessor_segment_overrun: Entry::missing(),
|
||||
invalid_tss: Entry::missing(),
|
||||
segment_not_present: Entry::missing(),
|
||||
stack_segment_fault: Entry::missing(),
|
||||
general_protection_fault: Entry::missing(),
|
||||
page_fault: Entry::missing(),
|
||||
reserved_1: Entry::missing(),
|
||||
x87_floating_point: Entry::missing(),
|
||||
alignment_check: Entry::missing(),
|
||||
machine_check: Entry::missing(),
|
||||
simd_floating_point: Entry::missing(),
|
||||
virtualization: Entry::missing(),
|
||||
cp_protection_exception: Entry::missing(),
|
||||
reserved_2: [Entry::missing(); 6],
|
||||
hv_injection_exception: Entry::missing(),
|
||||
vmm_communication_exception: Entry::missing(),
|
||||
security_exception: Entry::missing(),
|
||||
reserved_3: Entry::missing(),
|
||||
interrupts: [Entry::missing(); 256 - 32],
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the IDT in the CPU using the `lidt` command.
|
||||
#[inline]
|
||||
pub fn load(&'static self) {
|
||||
unsafe { self.load_unsafe() }
|
||||
}
|
||||
|
||||
/// Loads the IDT in the CPU using the `lidt` command.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// As long as it is the active IDT, you must ensure that:
|
||||
/// - `self` is never destroyed.
|
||||
/// - `self` always stays at the same memory location.
|
||||
#[inline]
|
||||
pub unsafe fn load_unsafe(&self) {
|
||||
/// The data we push to the IDTR register
|
||||
#[repr(C, packed(2))]
|
||||
struct Idtr {
|
||||
size: u16,
|
||||
offset: u32,
|
||||
}
|
||||
|
||||
let idtr = {
|
||||
Idtr {
|
||||
size: (size_of::<InterruptDescriptorTable>() - 1) as u16,
|
||||
offset: self as *const _ as u32,
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
asm!("lidt [{}]", in(reg) &idtr, options(readonly, nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
}
|
56
tetros/src/idt/virtaddr.rs
Normal file
56
tetros/src/idt/virtaddr.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use core::fmt::{self};
|
||||
|
||||
/// A canonical 32-bit virtual memory address.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct VirtAddr(u32);
|
||||
|
||||
impl VirtAddr {
|
||||
/// Create a new [`VirtAddr`] from a [`u32`].
|
||||
#[inline]
|
||||
pub const fn new(addr: u32) -> Self {
|
||||
Self(addr)
|
||||
}
|
||||
|
||||
/// Converts this address to a `u32`.
|
||||
#[inline]
|
||||
pub const fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for VirtAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("VirtAddr")
|
||||
.field(&format_args!("{:#x}", self.0))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Binary for VirtAddr {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Binary::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::LowerHex for VirtAddr {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::LowerHex::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Octal for VirtAddr {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Octal::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::UpperHex for VirtAddr {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::UpperHex::fmt(&self.0, f)
|
||||
}
|
||||
}
|
297
tetros/src/lib.rs
Normal file
297
tetros/src/lib.rs
Normal file
@ -0,0 +1,297 @@
|
||||
//! The main code of tetris
|
||||
|
||||
#![no_std]
|
||||
#![feature(int_roundings)]
|
||||
#![feature(lang_items)]
|
||||
#![feature(abi_x86_interrupt)]
|
||||
#![allow(internal_features)]
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
use spin::Mutex;
|
||||
|
||||
use drivers::{pic::PICDriver, vga::Vga13h};
|
||||
use game::{FallingTetromino, TetrisBoard};
|
||||
use idt::{InterruptDescriptorTable, InterruptStackFrame};
|
||||
use os::{
|
||||
util::{inb, sti, without_interrupts},
|
||||
ThunkData,
|
||||
};
|
||||
|
||||
mod game;
|
||||
mod idt;
|
||||
mod os;
|
||||
|
||||
#[macro_use]
|
||||
mod drivers;
|
||||
|
||||
//
|
||||
// MARK: globals
|
||||
//
|
||||
// This code has no parallelism, so we don't _really_
|
||||
// need locks. The Mutexes here satisfy Rust's
|
||||
// "no mutable global state" rule.
|
||||
//
|
||||
// They also help prevent bugs, since we get deadlocks
|
||||
// instead of hard-to-debug surprising behavior.
|
||||
//
|
||||
|
||||
const PIC_OFFSET: u8 = 32;
|
||||
static VGA: Mutex<Vga13h> = Mutex::new(unsafe { Vga13h::new() });
|
||||
static PIC: Mutex<PICDriver> = Mutex::new(PICDriver::new(PIC_OFFSET, PIC_OFFSET + 8));
|
||||
static TICK_COUNTER: Mutex<u32> = Mutex::new(0);
|
||||
static BOARD: Mutex<TetrisBoard> = Mutex::new(TetrisBoard::new());
|
||||
static FALLING: Mutex<Option<FallingTetromino>> = Mutex::new(None);
|
||||
static LAST_INPUT: Mutex<Option<InputKey>> = Mutex::new(None);
|
||||
|
||||
// These values can't be initialized statically,
|
||||
// so we cheat with `lazy_static`
|
||||
lazy_static! {
|
||||
static ref RNG: Mutex<SmallRng> = Mutex::new(SmallRng::seed_from_u64(1337));
|
||||
static ref IDT: InterruptDescriptorTable = {
|
||||
let mut idt = InterruptDescriptorTable::new();
|
||||
|
||||
idt.divide_error.set_handler_fn(divide_handler);
|
||||
idt.double_fault.set_handler_fn(double_fault_handler);
|
||||
idt.interrupts[InterruptIndex::Timer.as_idx()].set_handler_fn(timer_handler);
|
||||
idt.interrupts[InterruptIndex::Keyboard.as_idx()].set_handler_fn(keyboard_handler);
|
||||
|
||||
idt
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum InputKey {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
//
|
||||
// MARK: interrupts
|
||||
//
|
||||
// These functions are called when we receive interrupts.
|
||||
// This can occur between ANY two instructions---which is
|
||||
// why we use `without_interrupts` when acquiring locks.
|
||||
//
|
||||
// Notice how we do as little work as possible in our
|
||||
// interrupt handlers. All our business logic goes into
|
||||
// the main loop.
|
||||
|
||||
#[expect(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum InterruptIndex {
|
||||
Timer = PIC_OFFSET,
|
||||
Keyboard,
|
||||
}
|
||||
|
||||
impl InterruptIndex {
|
||||
fn as_u8(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
fn as_idx(self) -> usize {
|
||||
usize::from(self.as_u8() - PIC_OFFSET)
|
||||
}
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn divide_handler(stack_frame: InterruptStackFrame) {
|
||||
// Simple interrupt handler, as an example.
|
||||
// This can be triggered manually using `asm!("int 0")`,
|
||||
// even if interrupts are disabled.
|
||||
println!("DIVIDE ERROR {:?}", stack_frame);
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn keyboard_handler(_stack_frame: InterruptStackFrame) {
|
||||
{
|
||||
// Re-seed our rng using user input.
|
||||
// This is a simple hack that makes our
|
||||
// "random" tile selector less deterministic.
|
||||
//
|
||||
// Getting random seeds from hardware is
|
||||
// more trouble than its worth.
|
||||
let mut rng = RNG.lock();
|
||||
let past: u64 = rng.random();
|
||||
let tcr = u64::from(*TICK_COUNTER.lock());
|
||||
*rng = SmallRng::seed_from_u64(past + tcr);
|
||||
}
|
||||
|
||||
'key_block: {
|
||||
let scancode = unsafe { inb(0x60) };
|
||||
let key = match scancode {
|
||||
0x11 => Some(InputKey::Up), // W
|
||||
0x1E => Some(InputKey::Left), // A
|
||||
0x1F => Some(InputKey::Down), // S
|
||||
0x20 => Some(InputKey::Right), // D
|
||||
|
||||
// Extended codes
|
||||
0xE0 => {
|
||||
let scancode = unsafe { inb(0x60) };
|
||||
match scancode {
|
||||
0x48 => Some(InputKey::Up), // Up arrow
|
||||
0x4B => Some(InputKey::Left), // Left arrow
|
||||
0x50 => Some(InputKey::Down), // Down arrow
|
||||
0x4D => Some(InputKey::Right), // Right arrow
|
||||
_ => break 'key_block, // Ignore unknown codes
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore unrecognized keycodes.
|
||||
//
|
||||
// Note that this does NOT return to None!
|
||||
// If we do that, "release" keycodes will
|
||||
// immediately clear "press" keycodes.
|
||||
_ => break 'key_block,
|
||||
};
|
||||
|
||||
*LAST_INPUT.lock() = key;
|
||||
}
|
||||
|
||||
PIC.lock().send_eoi(false);
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn timer_handler(_stack_frame: InterruptStackFrame) {
|
||||
let mut t = TICK_COUNTER.lock();
|
||||
*t = (*t).wrapping_add(1);
|
||||
PIC.lock().send_eoi(false);
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn double_fault_handler(
|
||||
stack_frame: InterruptStackFrame,
|
||||
error_code: u32,
|
||||
) -> ! {
|
||||
panic!("DOUBLE FAULT (err = 0x{error_code:x}\n{:#?}", stack_frame);
|
||||
}
|
||||
|
||||
//
|
||||
// MARK: main
|
||||
//
|
||||
|
||||
#[expect(missing_docs)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn start(thunk10: extern "C" fn()) -> ! {
|
||||
println!("Entered Rust, serial ready.");
|
||||
|
||||
{
|
||||
// Set vga mode
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x13;
|
||||
data.with(thunk10);
|
||||
}
|
||||
|
||||
{
|
||||
// Disable cursor
|
||||
let mut data = ThunkData::new();
|
||||
data.eax = 0x0100;
|
||||
data.ecx = 0x3F00;
|
||||
data.with(thunk10);
|
||||
}
|
||||
|
||||
{
|
||||
// Initialize IDT
|
||||
IDT.load();
|
||||
let mut pic = PIC.lock();
|
||||
pic.init();
|
||||
}
|
||||
|
||||
// We're ready for interrupts, enable them
|
||||
sti();
|
||||
|
||||
let mut last_t = 0;
|
||||
loop {
|
||||
// All locks use `without_interrupts`
|
||||
// to prevent deadlocks
|
||||
let t = without_interrupts(|| {
|
||||
let l = TICK_COUNTER.lock();
|
||||
let t = *l;
|
||||
drop(l);
|
||||
return t;
|
||||
});
|
||||
|
||||
if t == last_t {
|
||||
continue;
|
||||
}
|
||||
last_t = t;
|
||||
|
||||
// MARK: input
|
||||
// Handle user input
|
||||
without_interrupts(|| {
|
||||
if let Some(fall) = &mut *FALLING.lock() {
|
||||
let board = BOARD.lock();
|
||||
let mut fall_test = fall.clone();
|
||||
let mut last_input = LAST_INPUT.lock();
|
||||
|
||||
match *last_input {
|
||||
Some(InputKey::Up) => {
|
||||
fall_test.rotate_cw();
|
||||
if board.tetromino_valid(&fall_test) {
|
||||
fall.rotate_cw()
|
||||
};
|
||||
}
|
||||
|
||||
Some(InputKey::Down) => loop {
|
||||
fall_test.translate(0, 1);
|
||||
if board.tetromino_valid(&fall_test) {
|
||||
fall.translate(0, 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
Some(InputKey::Left) => {
|
||||
fall_test.translate(-1, 0);
|
||||
if board.tetromino_valid(&fall_test) {
|
||||
fall.translate(-1, 0);
|
||||
};
|
||||
}
|
||||
|
||||
Some(InputKey::Right) => {
|
||||
fall_test.translate(1, 0);
|
||||
if board.tetromino_valid(&fall_test) {
|
||||
fall.translate(1, 0);
|
||||
};
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Clear last input, it was handled.
|
||||
*last_input = None;
|
||||
}
|
||||
});
|
||||
|
||||
// MARK: update board
|
||||
// Update board
|
||||
without_interrupts(|| {
|
||||
let mut v = VGA.lock();
|
||||
let mut board = BOARD.lock();
|
||||
let mut fall = FALLING.lock();
|
||||
|
||||
v.swap();
|
||||
board.draw(&mut v, fall.as_ref());
|
||||
|
||||
if let Some(fall_inner) = fall.as_mut() {
|
||||
if t % 4 == 0 {
|
||||
let mut fall_test = fall_inner.clone();
|
||||
fall_test.translate(0, 1);
|
||||
|
||||
if board.tetromino_valid(&fall_test) {
|
||||
fall_inner.translate(0, 1);
|
||||
} else {
|
||||
let mut x = None;
|
||||
core::mem::swap(&mut x, &mut fall);
|
||||
board.place_tetromino(x.unwrap());
|
||||
board.collapse();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*fall = Some(FallingTetromino::random(5, 1));
|
||||
if !board.tetromino_valid(fall.as_ref().unwrap()) {
|
||||
panic!("\nGAME OVER");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
99
tetros/src/os/eflags.rs
Normal file
99
tetros/src/os/eflags.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use bitflags::bitflags;
|
||||
use core::arch::asm;
|
||||
|
||||
bitflags! {
|
||||
/// The EFLAGS register. All bit patterns are valid representations for this type.
|
||||
///
|
||||
/// See https://wiki.osdev.org/CPU_Registers_x86#EFLAGS_Register
|
||||
#[repr(transparent)]
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
|
||||
pub struct EFlags: u32 {
|
||||
/// Processor feature identification flag.
|
||||
///
|
||||
/// If this flag is modifiable, the CPU supports CPUID.
|
||||
const ID = 1 << 21;
|
||||
|
||||
/// Indicates that an external, maskable interrupt is pending.
|
||||
///
|
||||
/// Used when virtual-8086 mode extensions (CR4.VME) or protected-mode virtual
|
||||
/// interrupts (CR4.PVI) are activated.
|
||||
const VIRTUAL_INTERRUPT_PENDING = 1 << 20;
|
||||
|
||||
/// Virtual image of the INTERRUPT_FLAG bit.
|
||||
///
|
||||
/// Used when virtual-8086 mode extensions (CR4.VME) or protected-mode virtual
|
||||
/// interrupts (CR4.PVI) are activated.
|
||||
const VIRTUAL_INTERRUPT = 1 << 19;
|
||||
|
||||
/// Enable automatic alignment checking if CR0.AM is set. Only works if CPL is 3.
|
||||
const ALIGNMENT_CHECK = 1 << 18;
|
||||
|
||||
/// Enable the virtual-8086 mode.
|
||||
const VIRTUAL_8086_MODE = 1 << 17;
|
||||
|
||||
/// Allows to restart an instruction following an instruction breakpoint.
|
||||
const RESUME_FLAG = 1 << 16;
|
||||
|
||||
/// Used by `iret` in hardware task switch mode to determine if current task is nested.
|
||||
const NESTED_TASK = 1 << 14;
|
||||
|
||||
/// The high bit of the I/O Privilege Level field.
|
||||
///
|
||||
/// Specifies the privilege level required for executing I/O address-space instructions.
|
||||
const IOPL_HIGH = 1 << 13;
|
||||
|
||||
/// The low bit of the I/O Privilege Level field.
|
||||
///
|
||||
/// Specifies the privilege level required for executing I/O address-space instructions.
|
||||
const IOPL_LOW = 1 << 12;
|
||||
|
||||
/// Set by hardware to indicate that the sign bit of the result of the last signed integer
|
||||
/// operation differs from the source operands.
|
||||
const OVERFLOW_FLAG = 1 << 11;
|
||||
|
||||
/// Determines the order in which strings are processed.
|
||||
const DIRECTION_FLAG = 1 << 10;
|
||||
|
||||
/// Enable interrupts.
|
||||
const INTERRUPT_FLAG = 1 << 9;
|
||||
|
||||
/// Enable single-step mode for debugging.
|
||||
const TRAP_FLAG = 1 << 8;
|
||||
|
||||
/// Set by hardware if last arithmetic operation resulted in a negative value.
|
||||
const SIGN_FLAG = 1 << 7;
|
||||
|
||||
/// Set by hardware if last arithmetic operation resulted in a zero value.
|
||||
const ZERO_FLAG = 1 << 6;
|
||||
|
||||
/// Set by hardware if last arithmetic operation generated a carry ouf of bit 3 of the
|
||||
/// result.
|
||||
const AUXILIARY_CARRY_FLAG = 1 << 4;
|
||||
|
||||
/// Set by hardware if last result has an even number of 1 bits (only for some operations).
|
||||
const PARITY_FLAG = 1 << 2;
|
||||
|
||||
/// Set by hardware if last arithmetic operation generated a carry out of the
|
||||
/// most-significant bit of the result.
|
||||
const CARRY_FLAG = 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl EFlags {
|
||||
/// Read the EFLAGS register
|
||||
#[inline]
|
||||
pub fn read() -> EFlags {
|
||||
EFlags::from_bits_truncate(EFlags::read_raw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_raw() -> u32 {
|
||||
let r: u32;
|
||||
|
||||
unsafe {
|
||||
asm!("pushfd; pop {0:e}", out(reg) r, options(nomem, preserves_flags));
|
||||
}
|
||||
|
||||
r
|
||||
}
|
||||
}
|
10
tetros/src/os/mod.rs
Normal file
10
tetros/src/os/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
mod thunk;
|
||||
pub use thunk::*;
|
||||
|
||||
mod eflags;
|
||||
pub use eflags::*;
|
||||
|
||||
#[macro_use]
|
||||
pub mod panic;
|
||||
|
||||
pub mod util;
|
34
tetros/src/os/panic.rs
Normal file
34
tetros/src/os/panic.rs
Normal file
@ -0,0 +1,34 @@
|
||||
//! Rust intrinsics for panic handling.
|
||||
//!
|
||||
//! These are usually provided by `std`,
|
||||
//! but we don't have that luxury!
|
||||
|
||||
use core::arch::asm;
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
// Use serial println
|
||||
use crate::println;
|
||||
|
||||
#[lang = "eh_personality"]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_eh_personality() {}
|
||||
|
||||
#[panic_handler]
|
||||
#[no_mangle]
|
||||
pub fn rust_begin_unwind(info: &PanicInfo<'_>) -> ! {
|
||||
unsafe {
|
||||
println!("TETRIS PANIC:\n{}", info);
|
||||
loop {
|
||||
asm!("hlt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _Unwind_Resume() -> ! {
|
||||
loop {
|
||||
unsafe {
|
||||
asm!("hlt");
|
||||
}
|
||||
}
|
||||
}
|
46
tetros/src/os/thunk.rs
Normal file
46
tetros/src/os/thunk.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use core::ptr;
|
||||
|
||||
// Grows downwards
|
||||
const THUNK_STACK_ADDR: usize = 0x7C00;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct ThunkData {
|
||||
pub es: u16,
|
||||
pub edi: u32,
|
||||
pub esi: u32,
|
||||
pub ebp: u32,
|
||||
pub ebx: u32,
|
||||
pub edx: u32,
|
||||
pub ecx: u32,
|
||||
pub eax: u32,
|
||||
}
|
||||
|
||||
impl ThunkData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
es: 0,
|
||||
edi: 0,
|
||||
esi: 0,
|
||||
ebp: 0,
|
||||
ebx: 0,
|
||||
edx: 0,
|
||||
ecx: 0,
|
||||
eax: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn save(&self) {
|
||||
ptr::write((THUNK_STACK_ADDR - 64) as *mut ThunkData, *self);
|
||||
}
|
||||
|
||||
pub unsafe fn load(&mut self) {
|
||||
*self = ptr::read((THUNK_STACK_ADDR - 64) as *const ThunkData);
|
||||
}
|
||||
|
||||
pub unsafe fn with(&mut self, f: extern "C" fn()) {
|
||||
self.save();
|
||||
f();
|
||||
self.load();
|
||||
}
|
||||
}
|
80
tetros/src/os/util.rs
Normal file
80
tetros/src/os/util.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use core::arch::asm;
|
||||
|
||||
use super::EFlags;
|
||||
|
||||
/// Disable interrupts
|
||||
#[inline]
|
||||
pub fn cli() {
|
||||
unsafe {
|
||||
asm!("cli", options(preserves_flags, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable interrupts
|
||||
#[inline]
|
||||
pub fn sti() {
|
||||
unsafe {
|
||||
asm!("sti", options(preserves_flags, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the given closure, disabling interrupts before running it (if they aren't already disabled).
|
||||
/// Afterwards, interrupts are enabling again if they were enabled before.
|
||||
///
|
||||
/// This helps us prevent deadlocks, which can occur if
|
||||
/// an interrupt handler tries to acquire a lock that was
|
||||
/// locked at the time of the interrupt.
|
||||
///
|
||||
/// If you have other `enable` and `disable` calls _within_ the closure, things may not work as expected.
|
||||
#[inline]
|
||||
pub fn without_interrupts<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
// true if the interrupt flag is set (i.e. interrupts are enabled)
|
||||
let saved_intpt_flag = EFlags::read().contains(EFlags::INTERRUPT_FLAG);
|
||||
|
||||
// if interrupts are enabled, disable them for now
|
||||
if saved_intpt_flag {
|
||||
cli();
|
||||
}
|
||||
|
||||
let ret = f();
|
||||
|
||||
if saved_intpt_flag {
|
||||
sti();
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Wraps the `in` instruction
|
||||
pub unsafe fn inb(port: u32) -> u8 {
|
||||
let mut out;
|
||||
|
||||
asm!(
|
||||
"in al, dx",
|
||||
out("al") out,
|
||||
in("dx") port,
|
||||
);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Wraps the `out` instruction
|
||||
pub unsafe fn outb(port: u32, value: u8) {
|
||||
asm!(
|
||||
"out dx, al",
|
||||
in("dx") port,
|
||||
in("al") value,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the current value of the CS register
|
||||
pub fn get_cs() -> u16 {
|
||||
let segment: u16;
|
||||
unsafe {
|
||||
asm!("mov {0:x}, cs", out(reg) segment, options(nomem, nostack, preserves_flags));
|
||||
}
|
||||
segment
|
||||
}
|
28
tetros/targets/x86-unknown-none.json
Normal file
28
tetros/targets/x86-unknown-none.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"llvm-target": "i686-unknown-none",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "32",
|
||||
"target-c-int-width": "32",
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"arch": "x86",
|
||||
"os": "none",
|
||||
"env": "",
|
||||
"vendor": "unknown",
|
||||
"linker-flavor": "gcc",
|
||||
"panic-strategy": "abort",
|
||||
"pre-link-args": {
|
||||
"gcc": ["-m32", "-nostdlib", "-static"]
|
||||
},
|
||||
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
|
||||
"dynamic-linking": false,
|
||||
"executables": false,
|
||||
"relocation-model": "static",
|
||||
"code-model": "large",
|
||||
"disable-redzone": true,
|
||||
"frame-pointer": "always",
|
||||
"exe-suffix": "",
|
||||
"has-rpath": false,
|
||||
"no-default-libraries": true,
|
||||
"position-independent-executables": false,
|
||||
"tls-model": "global-dynamic"
|
||||
}
|
63
tools/scripts/publish.py
Normal file
63
tools/scripts/publish.py
Normal file
@ -0,0 +1,63 @@
|
||||
# Publish the output of `build.py`
|
||||
# as a Gitea package.
|
||||
|
||||
from pathlib import Path
|
||||
import requests
|
||||
import os
|
||||
import re
|
||||
|
||||
URL = "https://git.betalupi.com"
|
||||
USER = os.environ["PUBLISH_USER"]
|
||||
PACKAGE = os.environ["PACKAGE"]
|
||||
VERSION = os.environ["VERSION"]
|
||||
AUTH = requests.auth.HTTPBasicAuth(USER, os.environ["PUBLISH_KEY"])
|
||||
|
||||
ROOT: Path = Path(os.getcwd())
|
||||
|
||||
|
||||
def log(msg):
|
||||
print(f"[PUBLISH.PY] {msg}")
|
||||
|
||||
|
||||
log(f"Version is {VERSION}")
|
||||
log(f"Package is {PACKAGE}")
|
||||
log(f"Running in {ROOT}")
|
||||
if not ROOT.is_dir():
|
||||
log("Root is not a directory, cannot continue")
|
||||
exit(1)
|
||||
|
||||
|
||||
def del_package():
|
||||
log(f"Deleting package {PACKAGE}/{VERSION}")
|
||||
res = requests.delete(
|
||||
f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}",
|
||||
auth=AUTH,
|
||||
)
|
||||
if res.status_code != 204 and res.status_code != 404:
|
||||
log(f"Deletion failed with code {res.status_code}")
|
||||
|
||||
|
||||
# Delete if already exists
|
||||
# (important for the `latest` package)
|
||||
del_package()
|
||||
|
||||
|
||||
def upload(data, target: str):
|
||||
target = re.sub("[^A-Za-z0-9_. -]+", "", target)
|
||||
|
||||
res = requests.put(
|
||||
f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}/{target}",
|
||||
auth=AUTH,
|
||||
data=data,
|
||||
)
|
||||
|
||||
if res.status_code != 201:
|
||||
log(f"Upload failed with code {res.status_code}")
|
||||
del_package() # Do not keep partial package if upload fails
|
||||
exit(1)
|
||||
|
||||
return f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}/{target}"
|
||||
|
||||
|
||||
log("Uploading disk.img")
|
||||
upload(Path("./build/disk.img").open("rb").read(), "disk.img")
|
18
tools/scripts/ruff.toml
Normal file
18
tools/scripts/ruff.toml
Normal file
@ -0,0 +1,18 @@
|
||||
exclude = ["venv"]
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
target-version = "py39"
|
||||
include = ["scripts/**/*.py"]
|
||||
|
||||
[lint]
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
ignore = []
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[format]
|
||||
quote-style = "double"
|
||||
indent-style = "tab"
|
||||
skip-magic-trailing-comma = false
|
||||
line-ending = "lf"
|
8
tools/typos.toml
Normal file
8
tools/typos.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
# spell:disable-line
|
||||
"(?Rm)^.*(%|#|//|;)\\s*spell:disable-line$",
|
||||
|
||||
# spell:<on|off>
|
||||
"(?s)(%|#|//|;)\\s*spell:off.*?\\n\\s*(%|#|//)\\s*spell:on",
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user