42 Commits

Author SHA1 Message Date
Justin C. Miller
6302e8b73a Overhaul memory allocation model
This commit makes several fundamental changes to memory handling:

- the frame allocator is now only an allocator for free frames, and does
  not track used frames.
- the frame allocator now stores its free list inside the free frames
  themselves, as a hybrid stack/span model.
  - This has the implication that all frames must currently fit within
    the offset area.
- kutil has a new allocator interface, which is the only allowed way for
  any code outside of src/kernel to allocate. Code under src/kernel
  _may_ use new/delete, but should prefer the allocator interface.
- the heap manager has become heap_allocator, which is merely an
  implementation of kutil::allocator which doles out sections of a given
  address range.
- the heap manager now only writes block headers when necessary,
  avoiding page faults until they're actually needed
- page_manager now has a page fault handler, which checks with the
  address_manager to see if the address is known, and provides a frame
  mapping if it is, allowing heap manager to work with its entire
  address size from the start. (Currently 32GiB.)
2019-04-16 01:13:09 -07:00
Justin C. Miller
fd1adc0262 Put MAX_SYSCALLS in hex to match syscalls list 2019-04-09 23:35:32 -07:00
Justin C. Miller
070be0b005 Allow map_page call with 0 address to allocate address space 2019-04-09 23:20:27 -07:00
Justin C. Miller
5034ffbe59 Increase max kernel heap allocation to 4MiB 2019-04-09 22:31:06 -07:00
Justin C. Miller
cd13b88540 Log about additional CPU/APICs 2019-04-08 14:33:10 -07:00
Justin C. Miller
e050d6f151 Move more logging infrastructure into kutil 2019-04-06 18:25:09 -07:00
Justin C. Miller
863555ec6b Clean up process memory on exit.
Additionally, there were several bug fixes needed to allow this:
- frame_allocator was allocating its frame_blocks from the heap, causing
  a circular dependency. Now it gives itself a page on its own when
  needed.
- frame_allocator::free was putting any trailing pages in a block back
  into the list after the current block, so they would be the next block
  iterated to.
- frame_allocator::free was updating the address it was looking for
  after freeing some pages, but not the count it was looking for, so it
  would eventually free all pages after the initial address.
2019-04-06 11:19:38 -07:00
Justin C. Miller
c605793a9d Fix fork() for new task switching model 2019-04-03 10:08:26 -07:00
Justin C. Miller
8375870af6 Improve syscall definitions
- Allow constant id specification
- Define function signature in SYSCALL macro
- Move implementation into src/kernel/syscalls/*.cpp
2019-04-03 10:03:15 -07:00
Justin C. Miller
11a53e792f Improve syscalls for new task switching
There are a lot of under the hood changes here:
- Move syscalls to be a dispatch table, defined by syscalls.inc
- Don't need a full process state (push_all) in syscalls now
- In push_all, define REGS instead of using offsets
- Save TWO stack pointers as well as current saved stack pointer in TCB:
  - rsp0 is the base of the kernel stack for interrupts
  - rsp3 is the saved user stack from cpu_data
- Update syscall numbers in nulldrv
- Some asm-debugging enhancements to the gdb script
- fork() still not working
2019-04-02 00:25:36 -07:00
Justin C. Miller
ca2362f858 Simplify task switches
No longer using the rsp from the entry to the kernel, but instead
switching rsp at task-switching time in assembly.

This currently breaks fork()
2019-03-31 22:49:24 -07:00
Justin C. Miller
5cdbedd4d1 Move console to use kutil's printf 2019-03-31 22:43:37 -07:00
Justin C. Miller
ee6d69bcd2 Move builds to bonnibel 0.2 2019-03-30 01:33:00 -07:00
Justin C. Miller
f9193b4602 Extract python build scripts as 'bonnibel' 2019-03-27 17:25:50 -07:00
Justin C. Miller
979e5f036e Improve QEMU settings for laptop 2019-03-26 15:27:10 -07:00
Justin C. Miller
39baec852b Allow larger initrd images 2019-03-25 01:42:36 -07:00
Justin C. Miller
ed3f9410a6 Make nulldrv a small C++ program 2019-03-24 13:44:25 -07:00
Justin C. Miller
b18243f098 Improve process switching and syscall log spam 2019-03-22 17:43:29 -07:00
Justin C. Miller
9472dab522 Fix asm int call for LLVM 8 2019-03-22 17:42:51 -07:00
Justin C. Miller
9067f8d298 Add kernel logging task
- Enable creating kernel tasks
- Create kernel task that disables immediate-mode logging and prints
  logs to the console forever
2019-03-20 23:45:01 -07:00
Justin C. Miller
866073ae8a Fix RFLAGS-clobbering syscalls 2019-03-20 23:42:42 -07:00
Justin C. Miller
91cb00fde2 Add immediate log output for early kernel 2019-03-20 20:58:44 -07:00
Justin C. Miller
c435bcfb67 Removed empty unused output_log 2019-03-20 20:58:44 -07:00
Justin C. Miller
7fe1b7d1f5 Add temporary log output
Currently the kernel idle process is an infinite loop printing logs,
with no locking.
2019-03-20 20:58:44 -07:00
Justin C. Miller
39524b2b9f Move kernel over to new logger 2019-03-20 20:58:44 -07:00
Justin C. Miller
c592f5f537 Add buffer-based logging system 2019-03-20 20:58:44 -07:00
Justin C. Miller
94c491d286 Update to LLVM 8.0 2019-03-20 15:40:01 -07:00
Justin C. Miller
645938a194 Add bip buffer and constexpr hash 2019-03-17 10:02:57 -07:00
Justin C. Miller
73756820bd Fix page_manager::unmap_pages() free tracking 2019-03-16 00:36:45 -07:00
Justin C. Miller
33374ab257 Clean up process loader
- cpu_state was being passed 'by value' to modify outer stack frame
- don't pass loader args as rax, rbx, etc - pass in ABI order
2019-03-15 01:05:45 -07:00
Justin C. Miller
bf8286d15f Improve debugging functions
- More sensible stack tracer, in C++ (no symbols yet)
- Was forgetting to add null frame to new kernel stacks
- __kernel_assert was using an old vector
- A GP fault will only print its associated table entry
2019-03-15 00:47:46 -07:00
Justin C. Miller
6410c898c5 Switch push/pop_all macros from push/pop to mov 2019-03-14 23:25:26 -07:00
Justin C. Miller
be007c6278 Implement exit syscall 2019-03-14 22:28:21 -07:00
Justin C. Miller
f7558e3d18 Implement fast syscall/sysret for sytem calls 2019-03-13 23:51:29 -07:00
Justin C. Miller
97e8f2c69a Add get_gsbase() 2019-03-13 22:49:20 -07:00
Justin C. Miller
49bdf47514 Remove segments from push_all 2019-03-13 22:45:02 -07:00
Justin C. Miller
81162f30dc Add popc_stack command to gdb 2019-03-13 22:37:28 -07:00
Justin C. Miller
4379256c11 Split OVMF into _code and _vars 2019-03-12 10:02:37 -07:00
Justin C. Miller
870ca1db45 Allow debug option to be communicated at boot 2019-03-11 03:04:57 -07:00
Justin C. Miller
74a5c301f8 Add guid_device_path GUIDs 2019-03-10 23:35:23 -07:00
Justin C. Miller
2955e6c9c1 Add debugging files to build process 2019-03-10 23:34:47 -07:00
Justin C. Miller
cd2ccb4e06 Move QEMU monitor to telnet 2019-03-10 12:57:43 -07:00
103 changed files with 3823 additions and 2321 deletions

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@
/build* /build*
*.bak *.bak
tags tags
.gdbinit
popcorn.log popcorn.log
*.o *.o
*.a *.a

View File

@@ -81,6 +81,33 @@ MIT license:
> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## printf
Popcorn uses 's tiny [printf](https://github.com/mpaland/printf) library for its
`*printf()` functions, which is also released under the terms of the MIT license:
> The MIT License (MIT)
>
> Copyright (c) 2014 Marco Paland
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
## GNU-EFI ## GNU-EFI
Popcorn's UEFI bootloader initially used Popcorn's UEFI bootloader initially used

View File

@@ -27,11 +27,21 @@ The design goals of the project are:
## Building ## Building
Popcorn uses the `ninja` build tool, and generates the build files for it with Popcorn uses the [Ninja][] build tool, and generates the build files for it
the `generate_build.py` script. The other requirements are: with a custom tool called [Bonnibel][]. Bonnibel requires [Python 3][] and can
be downloaded with `pip`:
* python 3 for generating the build config ```
* The Jinja2 package is also required pip3 install bonnibel
```
[Ninja]: https://ninja-build.org
[Bonnibel]: https://github.com/justinian/bonnibel
[Python 3]: https://python.org
Requrirements:
* python 3 (for installing and running Bonnibel)
* clang * clang
* mtools * mtools
* ninja * ninja
@@ -45,7 +55,7 @@ Popcorn host binaries.
### Building and running Popcorn ### Building and running Popcorn
Once the toolchain has been set up, running `generate_build.py` will set up the Once the toolchain has been set up, running Bonnibel's `pb` command will set up the
build configuration, and `ninja -C build` will actually run the build. If you build configuration, and `ninja -C build` will actually run the build. If you
have `qemu-system-x86_64` installed, the `qemu.sh` script will to run Popcorn have `qemu-system-x86_64` installed, the `qemu.sh` script will to run Popcorn
in QEMU `-nographic` mode. in QEMU `-nographic` mode.
@@ -54,7 +64,8 @@ I personally run this either from a real debian amd64 testing/buster machine or
a windows WSL debian testing/buster installation. The following should be a windows WSL debian testing/buster installation. The following should be
enough to set up such a system to build the kernel: enough to set up such a system to build the kernel:
sudo apt install qemu-system-x86 nasm clang-6.0 mtools sudo apt install qemu-system-x86 nasm clang-6.0 mtools python3-pip curl
sudo update-alternatives /usr/bin/clang clang /usr/bin/clang-6.0 1000 sudo update-alternatives /usr/bin/clang clang /usr/bin/clang-6.0 1000
sudo update-alternatives /usr/bin/clang++ clang++ /usr/bin/clang++-6.0 1000 sudo update-alternatives /usr/bin/clang++ clang++ /usr/bin/clang++-6.0 1000
sudo pip3 install bonnibel

View File

@@ -0,0 +1,31 @@
import gdb
class PrintStackCommand(gdb.Command):
def __init__(self):
super().__init__("popc_stack", gdb.COMMAND_DATA)
def invoke(self, arg, from_tty):
args = gdb.string_to_argv(arg)
base = "$rsp"
if len(args) > 0:
base = args[0]
depth = 22
if len(args) > 1:
depth = int(args[1])
for i in range(depth-1, -1, -1):
offset = i * 8
base_addr = gdb.parse_and_eval(base)
value = gdb.parse_and_eval(f"*(uint64_t*)({base} + {offset})")
print("{:016x} (+{:04x}): {:016x}".format(int(base_addr) + offset, offset, int(value)))
PrintStackCommand()
import time
time.sleep(3.5)
gdb.execute("target remote :1234")
gdb.execute("set waiting = false")
gdb.execute("display/i $rip")

View File

@@ -14,10 +14,10 @@ source = "../assets/fonts/tamsyn8x16r.psf"
[[files]] [[files]]
dest = "nulldrv1" dest = "nulldrv1"
source = "host/nulldrv" source = "user/nulldrv"
executable = true executable = true
[[files]] [[files]]
dest = "nulldrv2" dest = "nulldrv2"
source = "host/nulldrv" source = "user/nulldrv"
executable = true executable = true

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,199 +0,0 @@
#!/usr/bin/env python3
from collections import namedtuple
library = namedtuple('library', ['path', 'deps'])
program = namedtuple('program', ['path', 'deps', 'output', 'targets'])
source = namedtuple('source', ['name', 'input', 'output', 'action'])
version = namedtuple('version', ['major', 'minor', 'patch', 'sha', 'dirty'])
MODULES = {}
class Source:
Actions = {'.c': 'cc', '.cpp': 'cxx', '.s': 'nasm'}
def __init__(self, path, root, modroot):
from os.path import relpath, splitext
self.input = path
self.name = relpath(path, root)
self.output = relpath(path, modroot) + ".o"
self.action = self.Actions.get(splitext(path)[1], None)
def __str__(self):
return "{} {}:{}:{}".format(self.action, self.output, self.name, self.input)
class Module:
def __init__(self, name, output, root, **kwargs):
from os.path import commonpath, dirname, isdir, join
self.name = name
self.output = output
self.kind = kwargs.get("kind", "exe")
self.target = kwargs.get("target", None)
self.deps = kwargs.get("deps", tuple())
self.includes = kwargs.get("includes", tuple())
self.defines = kwargs.get("defines", tuple())
self.depmods = []
sources = [join(root, f) for f in kwargs.get("source", tuple())]
modroot = commonpath(sources)
while not isdir(modroot):
modroot = dirname(modroot)
self.sources = [Source(f, root, modroot) for f in sources]
def __str__(self):
return "Module {} {}\n\t".format(self.kind, self.name)
def __find_depmods(self, modules):
self.depmods = set()
open_list = set(self.deps)
closed_list = set()
while open_list:
dep = modules[open_list.pop()]
open_list |= (set(dep.deps) - closed_list)
self.depmods.add(dep)
self.libdeps = [d for d in self.depmods if d.kind == "lib"]
self.exedeps = [d for d in self.depmods if d.kind != "lib"]
@classmethod
def load(cls, filename):
from os.path import abspath, dirname
from yaml import load
root = dirname(filename)
modules = {}
moddata = load(open(filename, "r"))
for name, data in moddata.items():
modules[name] = cls(name, root=root, **data)
for mod in modules.values():
mod.__find_depmods(modules)
targets = {}
for mod in modules.values():
if mod.target is None: continue
if mod.target not in targets:
targets[mod.target] = set()
targets[mod.target].add(mod)
targets[mod.target] |= mod.depmods
return modules.values(), targets
def get_template(env, typename, name):
from jinja2.exceptions import TemplateNotFound
try:
return env.get_template("{}.{}.j2".format(typename, name))
except TemplateNotFound:
return env.get_template("{}.default.j2".format(typename))
def get_git_version():
from subprocess import run
cp = run(['git', 'describe', '--dirty'],
check=True, capture_output=True)
full_version = cp.stdout.decode('utf-8').strip()
cp = run(['git', 'rev-parse', 'HEAD'],
check=True, capture_output=True)
full_sha = cp.stdout.decode('utf-8').strip()
dirty = False
parts1 = full_version.split('-')
if parts1[-1] == "dirty":
dirty = True
parts1 = parts1[:-1]
parts2 = parts1[0].split('.')
return version(
parts2[0],
parts2[1],
parts2[2],
full_sha[:7],
dirty)
def main(buildroot="build", modulefile="modules.yaml"):
import os
from os.path import abspath, dirname, isabs, isdir, join
generator = abspath(__file__)
srcroot = dirname(generator)
if not isabs(modulefile):
modulefile = join(srcroot, modulefile)
if not isabs(buildroot):
buildroot = join(srcroot, buildroot)
if not isdir(buildroot):
os.mkdir(buildroot)
git_version = get_git_version()
print("Generating build files for Popcorn {}.{}.{}-{}...".format(
git_version.major, git_version.minor, git_version.patch, git_version.sha))
from jinja2 import Environment, FileSystemLoader
template_dir = join(srcroot, "scripts", "templates")
env = Environment(loader=FileSystemLoader(template_dir))
buildfiles = []
templates = set()
modules, targets = Module.load(modulefile)
for mod in modules:
buildfile = join(buildroot, mod.name + ".ninja")
buildfiles.append(buildfile)
with open(buildfile, 'w') as out:
template = get_template(env, mod.kind, mod.name)
templates.add(template.filename)
out.write(template.render(
module=mod,
buildfile=buildfile,
version=git_version))
for target, mods in targets.items():
root = join(buildroot, target)
if not isdir(root):
os.mkdir(root)
buildfile = join(root, "target.ninja")
buildfiles.append(buildfile)
with open(buildfile, 'w') as out:
template = get_template(env, "target", target)
templates.add(template.filename)
out.write(template.render(
target=target,
modules=mods,
buildfile=buildfile,
version=git_version))
# Top level buildfile cannot use an absolute path or ninja won't
# reload itself properly on changes.
# See: https://github.com/ninja-build/ninja/issues/1240
buildfile = "build.ninja"
buildfiles.append(buildfile)
with open(join(buildroot, buildfile), 'w') as out:
template = env.get_template("build.ninja.j2")
templates.add(template.filename)
out.write(template.render(
targets=targets,
buildroot=buildroot,
srcroot=srcroot,
buildfile=buildfile,
buildfiles=buildfiles,
templates=[abspath(f) for f in templates],
generator=generator,
modulefile=modulefile,
version=git_version))
if __name__ == "__main__":
import sys
main(*sys.argv[1:])

View File

@@ -1,116 +1,132 @@
kernel: name: Popcorn
output: popcorn.elf templates: scripts/templates
target: host modules:
deps: kernel:
- elf output: popcorn.elf
- initrd target: host
- kutil deps:
includes: - elf
- src/kernel - initrd
source: - kutil
- src/kernel/crti.s includes:
- src/kernel/apic.cpp - src/kernel
- src/kernel/assert.cpp source:
- src/kernel/boot.s - src/kernel/crti.s
- src/kernel/console.cpp - src/kernel/apic.cpp
- src/kernel/cpprt.cpp - src/kernel/assert.cpp
- src/kernel/cpu.cpp - src/kernel/boot.s
- src/kernel/debug.cpp - src/kernel/console.cpp
- src/kernel/debug.s - src/kernel/cpprt.cpp
- src/kernel/device_manager.cpp - src/kernel/cpu.cpp
- src/kernel/font.cpp - src/kernel/debug.cpp
- src/kernel/fs/gpt.cpp - src/kernel/debug.s
- src/kernel/gdt.cpp - src/kernel/device_manager.cpp
- src/kernel/gdt.s - src/kernel/font.cpp
- src/kernel/interrupts.cpp - src/kernel/frame_allocator.cpp
- src/kernel/interrupts.s - src/kernel/fs/gpt.cpp
- src/kernel/io.cpp - src/kernel/gdt.cpp
- src/kernel/loader.s - src/kernel/gdt.s
- src/kernel/log.cpp - src/kernel/interrupts.cpp
- src/kernel/main.cpp - src/kernel/interrupts.s
- src/kernel/memory_bootstrap.cpp - src/kernel/io.cpp
- src/kernel/msr.cpp - src/kernel/loader.s
- src/kernel/page_manager.cpp - src/kernel/log.cpp
- src/kernel/pci.cpp - src/kernel/main.cpp
- src/kernel/process.cpp - src/kernel/memory_bootstrap.cpp
- src/kernel/scheduler.cpp - src/kernel/msr.cpp
- src/kernel/screen.cpp - src/kernel/page_manager.cpp
- src/kernel/serial.cpp - src/kernel/pci.cpp
- src/kernel/syscall.cpp - src/kernel/process.cpp
- src/kernel/syscall.s - src/kernel/scheduler.cpp
- src/kernel/crtn.s - src/kernel/screen.cpp
- src/kernel/serial.cpp
- src/kernel/syscall.cpp
- src/kernel/syscall.s
- src/kernel/syscalls/exit.cpp
- src/kernel/syscalls/fork.cpp
- src/kernel/syscalls/getpid.cpp
- src/kernel/syscalls/message.cpp
- src/kernel/syscalls/noop.cpp
- src/kernel/syscalls/pause.cpp
- src/kernel/syscalls/sleep.cpp
- src/kernel/task.s
- src/kernel/crtn.s
boot: boot:
kind: exe kind: exe
target: boot target: boot
output: boot.elf output: boot.elf
source: source:
- src/boot/crt0.s - src/boot/crt0.s
- src/boot/console.cpp - src/boot/console.cpp
- src/boot/guids.cpp - src/boot/guids.cpp
- src/boot/loader.cpp - src/boot/loader.cpp
- src/boot/main.cpp - src/boot/main.cpp
- src/boot/memory.cpp - src/boot/memory.cpp
- src/boot/reloc.cpp - src/boot/reloc.cpp
- src/boot/utility.cpp - src/boot/utility.cpp
nulldrv: nulldrv:
kind: exe kind: exe
target: host target: user
output: nulldrv output: nulldrv
source: source:
- src/drivers/nulldrv/main.s - src/drivers/nulldrv/main.cpp
- src/drivers/nulldrv/main.s
elf: elf:
kind: lib kind: lib
output: libelf.a output: libelf.a
deps: deps:
- kutil - kutil
includes: includes:
- src/libraries/elf/include - src/libraries/elf/include
source: source:
- src/libraries/elf/elf.cpp - src/libraries/elf/elf.cpp
initrd: initrd:
kind: lib kind: lib
output: libinitrd.a output: libinitrd.a
deps: deps:
- kutil - kutil
includes: includes:
- src/libraries/initrd/include - src/libraries/initrd/include
source: source:
- src/libraries/initrd/initrd.cpp - src/libraries/initrd/initrd.cpp
kutil: kutil:
kind: lib kind: lib
output: libkutil.a output: libkutil.a
includes: includes:
- src/libraries/kutil/include - src/libraries/kutil/include
source: source:
- src/libraries/kutil/assert.cpp - src/libraries/kutil/assert.cpp
- src/libraries/kutil/frame_allocator.cpp - src/libraries/kutil/bip_buffer.cpp
- src/libraries/kutil/heap_manager.cpp - src/libraries/kutil/heap_allocator.cpp
- src/libraries/kutil/memory.cpp - src/libraries/kutil/logger.cpp
- src/libraries/kutil/memory.cpp
- src/libraries/kutil/printf.c
makerd: makerd:
kind: exe kind: exe
target: native target: native
output: makerd output: makerd
deps: deps:
- initrd - initrd
source: source:
- src/tools/makerd/entry.cpp - src/tools/makerd/entry.cpp
- src/tools/makerd/main.cpp - src/tools/makerd/main.cpp
tests: tests:
kind: exe kind: exe
target: native target: native
output: tests output: tests
deps: deps:
- kutil - kutil
source: source:
- src/tests/address_manager.cpp - src/tests/address_manager.cpp
- src/tests/frame_allocator.cpp - src/tests/constexpr_hash.cpp
- src/tests/linked_list.cpp - src/tests/linked_list.cpp
- src/tests/heap_manager.cpp - src/tests/logger.cpp
- src/tests/main.cpp - src/tests/heap_allocator.cpp
- src/tests/main.cpp

31
qemu.sh
View File

@@ -1,33 +1,52 @@
#!/usr/bin/env bash #!/usr/bin/env bash
build="$(dirname $0)/build" build="$(dirname $0)/build"
assets="$(dirname $0)/assets"
debug="" debug=""
flash_name="ovmf_vars"
gfx="-nographic" gfx="-nographic"
kvm=""
for arg in $@; do for arg in $@; do
case "${arg}" in case "${arg}" in
--debug) --debug)
debug="-s" debug="-s"
flash_name="ovmf_vars_d"
;; ;;
--gfx) --gfx)
gfx="" gfx=""
;; ;;
--kvm)
kvm="-enable-kvm"
;;
*) *)
build="${arg}" build="${arg}"
;; ;;
esac esac
done done
kvm="" if [[ ! -c /dev/kvm ]]; then
if [[ -c /dev/kvm ]]; then kvm=""
kvm="-enable-kvm" fi
if ! ninja -C "${build}"; then
exit 1
fi
if [[ -n $TMUX ]]; then
if [[ -n $debug ]]; then
tmux split-window "gdb ${build}/popcorn.elf" &
else
tmux split-window -l 10 "sleep 1; telnet localhost 45454" &
fi
fi fi
ninja -C "${build}" && \
exec qemu-system-x86_64 \ exec qemu-system-x86_64 \
-drive "if=pflash,format=raw,file=${build}/flash.img" \ -drive "if=pflash,format=raw,readonly,file=${assets}/ovmf/x64/ovmf_code.fd" \
-drive "if=pflash,format=raw,file=${build}/${flash_name}.fd" \
-drive "format=raw,file=${build}/popcorn.img" \ -drive "format=raw,file=${build}/popcorn.img" \
-smp 1 \ -monitor telnet:localhost:45454,server,nowait \
-smp 4 \
-m 512 \ -m 512 \
-d mmu,int,guest_errors \ -d mmu,int,guest_errors \
-D popcorn.log \ -D popcorn.log \

View File

@@ -3,6 +3,7 @@
TARGET="x86_64-elf" TARGET="x86_64-elf"
NASM_VERSION="2.13.03" NASM_VERSION="2.13.03"
BINUTILS_VERSION="2.31.1" BINUTILS_VERSION="2.31.1"
LLVM_BRANCH="release_80"
TOOLS="clang" # lld libunwind libcxxabi libcxx" TOOLS="clang" # lld libunwind libcxxabi libcxx"
PROJECTS="compiler-rt libcxxabi libcxx libunwind" PROJECTS="compiler-rt libcxxabi libcxx libunwind"
@@ -77,7 +78,7 @@ function build_llvm() {
if [[ ! -d "${WORK}/llvm" ]]; then if [[ ! -d "${WORK}/llvm" ]]; then
echo "Downloading LLVM..." echo "Downloading LLVM..."
git clone -q \ git clone -q \
--branch release_70 \ --branch "${LLVM_BRANCH}" \
--depth 1 \ --depth 1 \
"https://git.llvm.org/git/llvm.git" "${WORK}/llvm" "https://git.llvm.org/git/llvm.git" "${WORK}/llvm"
fi fi
@@ -86,7 +87,7 @@ function build_llvm() {
if [[ ! -d "${WORK}/llvm/tools/${tool}" ]]; then if [[ ! -d "${WORK}/llvm/tools/${tool}" ]]; then
echo "Downloading ${tool}..." echo "Downloading ${tool}..."
git clone -q \ git clone -q \
--branch release_70 \ --branch "${LLVM_BRANCH}" \
--depth 1 \ --depth 1 \
"https://git.llvm.org/git/${tool}.git" "${WORK}/llvm/tools/${tool}" "https://git.llvm.org/git/${tool}.git" "${WORK}/llvm/tools/${tool}"
fi fi
@@ -95,7 +96,7 @@ function build_llvm() {
if [[ ! -d "${WORK}/llvm/tools/clang/tools/extra" ]]; then if [[ ! -d "${WORK}/llvm/tools/clang/tools/extra" ]]; then
echo "Downloading clang-tools-extra..." echo "Downloading clang-tools-extra..."
git clone -q \ git clone -q \
--branch release_70 \ --branch "${LLVM_BRANCH}" \
--depth 1 \ --depth 1 \
"https://git.llvm.org/git/clang-tools-extra.git" "${WORK}/llvm/tools/clang/tools/extra" "https://git.llvm.org/git/clang-tools-extra.git" "${WORK}/llvm/tools/clang/tools/extra"
fi fi
@@ -104,7 +105,7 @@ function build_llvm() {
if [[ ! -d "${WORK}/llvm/projects/${proj}" ]]; then if [[ ! -d "${WORK}/llvm/projects/${proj}" ]]; then
echo "Downloading ${proj}..." echo "Downloading ${proj}..."
git clone -q \ git clone -q \
--branch release_70 \ --branch "${LLVM_BRANCH}" \
--depth 1 \ --depth 1 \
"https://git.llvm.org/git/${proj}.git" "${WORK}/llvm/projects/${proj}" "https://git.llvm.org/git/${proj}.git" "${WORK}/llvm/projects/${proj}"
fi fi
@@ -114,7 +115,7 @@ function build_llvm() {
if [[ ! -d "${WORK}/llvm/runtimes/${proj}" ]]; then if [[ ! -d "${WORK}/llvm/runtimes/${proj}" ]]; then
echo "Downloading ${proj}..." echo "Downloading ${proj}..."
git clone -q \ git clone -q \
--branch release_70 \ --branch "${LLVM_BRANCH}" \
--depth 1 \ --depth 1 \
"https://git.llvm.org/git/${proj}.git" "${WORK}/llvm/runtime/${proj}" "https://git.llvm.org/git/${proj}.git" "${WORK}/llvm/runtime/${proj}"
fi fi

View File

@@ -1,109 +1,14 @@
ninja_required_version = 1.3 {% extends "build.base.j2" %}
builddir = {{ buildroot }}
srcroot = {{ srcroot }}
modulefile = {{ modulefile }}
warnflags = $ {% block variables %}
-Wformat=2 $ {{ super() }}
-Winit-self $ ccflags = $ccflags $
-Wfloat-equal $
-Winline $
-Wmissing-format-attribute $
-Wmissing-include-dirs $
-Wswitch $
-Wundef $
-Wdisabled-optimization $
-Wpointer-arith $
-Wno-attributes $
-Wno-sign-compare $
-Wno-multichar $
-Wno-div-by-zero $
-Wno-endif-labels $
-Wno-pragmas $
-Wno-format-extra-args $
-Wno-unused-result $
-Wno-deprecated-declarations $
-Wno-unused-function $
-Werror
ccflags = $
-I${srcroot}/src/include $ -I${srcroot}/src/include $
-I${srcroot}/src/include/x86_64 $ -I${srcroot}/src/include/x86_64
-DVERSION_MAJOR={{ version.major }} $ {% endblock %}
-DVERSION_MINOR={{ version.minor }} $
-DVERSION_PATCH={{ version.patch }} $
-DVERSION_GITSHA=0x{% if version.dirty %}1{% else %}0{% endif %}{{ version.sha }} $
-DGIT_VERSION=\"{{ version.major }}.{{ version.minor }}.{{ version.patch }}-{{ version.sha }}\" $
-DGIT_VERSION_WIDE=L\"{{ version.major }}.{{ version.minor }}.{{ version.patch }}-{{ version.sha }}\" $
$warnflags
asflags = $
-DVERSION_MAJOR={{ version.major }} $
-DVERSION_MINOR={{ version.minor }} $
-DVERSION_PATCH={{ version.patch }} $
-DVERSION_GITSHA=0x{% if version.dirty %}1{% else %}0{% endif %}{{ version.sha }}
cflags = -std=c11
cxxflags = -std=c++14
libs =
rule cc
deps = gcc
depfile = $out.d
description = Compiling $name
command = $cc -MMD -MF $out.d $ccflags $cflags -o $out -c $in
rule dump_cc_defs
description = Dumping CC defines for $target
command = echo "" | $cc $ccflags $cflags -dM -E - > $out
rule dump_cc_run
description = Dumping CC arguments for $target
command = $
echo "#!/bin/bash" > $out; $
echo '$cc $ccflags $cflags $$*' > $out; $
chmod a+x $out
rule cxx
deps = gcc
depfile = $out.d
description = Compiling $name
command = $cxx -MMD -MF $out.d $cxxflags $ccflags -o $out -c $in
rule dump_cxx_defs
description = Dumping C++ defines for $target
command = echo "" | $cxx -x c++ $cxxflags $ccflags -dM -E - > $out
rule dump_cxx_run
description = Dumping C++ arguments for $target
command = $
echo "#!/bin/bash" > $out; $
echo '$cc $cxxflags $ccflags $$*' > $out; $
chmod a+x $out
rule nasm
deps = gcc
depfile = $out.d
description = Assembling $name
command = $nasm -o $out -felf64 -MD $out.d $asflags $in
rule exe
description = Linking $name
command = $ld $ldflags -o $out $in $libs
rule lib
description = Archiving $name
command = $ar qcs $out $in
rule regen
generator = true
description = Regenrating build files
command = {{ generator }} $builddir $modulefile
rule cp
description = Copying $name
command = cp $in $out
{% block baserules %}
{{ super() }}
rule makerd rule makerd
description = Making init ramdisk description = Making init ramdisk
command = $builddir/native/makerd $in $out command = $builddir/native/makerd $in $out
@@ -135,29 +40,24 @@ rule strip
objcopy --only-keep-debug $out $out.debug; $ objcopy --only-keep-debug $out $out.debug; $
strip -g $out; $ strip -g $out; $
objcopy --add-gnu-debuglink=$out.debug $out objcopy --add-gnu-debuglink=$out.debug $out
{% endblock %}
{% block extra %}
build $builddir/ovmf_vars.fd : cp $srcroot/assets/ovmf/x64/ovmf_vars.fd
name = ovmf_vars.fd
{% for target in targets %} build $builddir/ovmf_vars_d.fd : cp $srcroot/assets/ovmf/x64/ovmf_vars_d.fd
subninja {{ target }}/target.ninja name = ovmf_vars_d.fd
{% endfor %}
build $
{%- for buildfile in buildfiles %}
{{ buildfile }} $
{%- endfor %}
: regen | $
{%- for template in templates %}
{{ template }} $
{%- endfor %}
$modulefile $
{{ generator }}
build $builddir/flash.img : cp $srcroot/assets/ovmf/x64/OVMF.fd
name = flash.img
build $builddir/popcorn.elf | $builddir/popcorn.elf.debug : strip $builddir/host/popcorn.elf build $builddir/popcorn.elf | $builddir/popcorn.elf.debug : strip $builddir/host/popcorn.elf
name = kernel name = kernel
build $builddir/popcorn.dump : dump $builddir/host/popcorn.elf
name = kernel
build $builddir/popcorn.elf-gdb.py : cp ${srcroot}/assets/debugging/popcorn.elf-gdb.py
name = kernel debug python scripts
build $builddir/fatroot/popcorn.elf : cp $builddir/popcorn.elf build $builddir/fatroot/popcorn.elf : cp $builddir/popcorn.elf
name = kernel to FAT image name = kernel to FAT image
@@ -166,7 +66,7 @@ build $builddir/fatroot/efi/boot/bootx64.efi : cp $builddir/boot/boot.efi
build $builddir/fatroot/initrd.img : makerd ${srcroot}/assets/initrd.toml | $ build $builddir/fatroot/initrd.img : makerd ${srcroot}/assets/initrd.toml | $
${builddir}/native/makerd $ ${builddir}/native/makerd $
${builddir}/host/nulldrv ${builddir}/user/nulldrv
build $builddir/popcorn.img : makefat | $ build $builddir/popcorn.img : makefat | $
$builddir/fatroot/initrd.img $ $builddir/fatroot/initrd.img $
@@ -174,5 +74,13 @@ build $builddir/popcorn.img : makefat | $
$builddir/fatroot/efi/boot/bootx64.efi $builddir/fatroot/efi/boot/bootx64.efi
name = popcorn.img name = popcorn.img
# vim: et ts=4 sts=4 sw=4 default $
$builddir/ovmf_vars.fd $
$builddir/ovmf_vars_d.fd $
$builddir/popcorn.dump $
$builddir/popcorn.elf-gdb.py $
$builddir/popcorn.img
{% endblock %}
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -2,18 +2,13 @@
{% block variables %} {% block variables %}
{{ super() }} {{ super() }}
ld = ld
cc = clang
cxx = clang++
ccflags = $ccflags $ ccflags = $ccflags $
-DKERNEL_FILENAME=L\"popcorn.elf\" $ -DKERNEL_FILENAME=L\"popcorn.elf\" $
-DGNU_EFI_USE_MS_ABI $ -DGNU_EFI_USE_MS_ABI $
-DHAVE_USE_MS_ABI $ -DHAVE_USE_MS_ABI $
-DEFI_DEBUG=0 $ -DEFI_DEBUG=0 $
-DEFI_DEBUG_CLEAR_MEMORY=0 $ -DEFI_DEBUG_CLEAR_MEMORY=0 $
-DBOOTLOADER_DEBUG $ -DBOOTLOADER_DEBUG
-fPIC
ldflags = $ldflags $ ldflags = $ldflags $
-T ${srcroot}/src/arch/x86_64/boot.ld $ -T ${srcroot}/src/arch/x86_64/boot.ld $
@@ -27,3 +22,6 @@ build $builddir/boot.efi : makeefi ${builddir}/{{ module.output }}
name = boot.efi name = boot.efi
{% endblock %} {% endblock %}
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -1,12 +0,0 @@
{% extends "module.base.j2" %}
{% block variables %}
{{ super() }}
libs = $
-L${builddir} $
{%- for dep in module.libdeps %}
-l{{ dep.name }} $
{%- endfor %}
$libs
{% endblock %}

View File

@@ -7,3 +7,6 @@ libs = $libs
ldflags = $ldflags -T ${srcroot}/src/arch/x86_64/kernel.ld ldflags = $ldflags -T ${srcroot}/src/arch/x86_64/kernel.ld
{% endblock %} {% endblock %}
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -5,3 +5,6 @@
ccflags = $ccflags -ggdb ccflags = $ccflags -ggdb
{% endblock %} {% endblock %}
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -1 +0,0 @@
{% extends "module.base.j2" %}

View File

@@ -1,41 +0,0 @@
moddir = ${builddir}/{{ module.name }}.dir
{% block variables %}
ccflags = $ccflags $
{%- for dep in module.depmods %}
{%- for inc in dep.includes %}
-I${srcroot}/{{ inc }} $
{%- endfor %}
{%- endfor %}
{%- for inc in module.includes %}
-I${srcroot}/{{ inc }} $
{%- endfor %}
{%- for define in module.defines %}
-D{{ define }} $
{%- endfor %}
{% endblock %}
{% for source in module.sources %}
build ${moddir}/{{ source.output }} : {{ source.action }} {{ source.input }} || {{ buildfile }}
name = {{ source.name }}
{% endfor %}
build ${builddir}/{{ module.output }} : {{ module.kind }} $
{%- for source in module.sources %}
${moddir}/{{ source.output }} $
{%- endfor -%}
{%- for dep in module.libdeps %}
${builddir}/{{ dep.output }} $
{%- endfor %}
| $
{% for dep in module.exedeps %}
${builddir}/{{ dep.output }} $
{%- endfor -%}
{{ buildfile }}
name = {{ module.name }}
{% block extra %}
{% endblock %}
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -1,35 +1,41 @@
{% extends "target.default.j2" %} {% extends "target.default.j2" %}
{% block binaries %} {% block binaries %}
ld = ld
cc = clang cc = clang
cxx = clang++ cxx = clang++
ld = ld
ar = ar
nasm = nasm nasm = nasm
objcopy = objcopy
{% endblock %} {% endblock %}
{% block variables %} {% block variables %}
ccflags = $ccflags $ ccflags = $ccflags $
-ggdb $ -ggdb $
-nostdlib $ -nostdlib $
-ffreestanding $ -ffreestanding $
-nodefaultlibs $ -nodefaultlibs $
-fno-builtin $ -fno-builtin $
-mno-sse $ -mno-sse $
-fno-omit-frame-pointer $ -fno-omit-frame-pointer $
-mno-red-zone $ -mno-red-zone $
-fshort-wchar -fshort-wchar $
-D__ELF__ $
-fPIC
cxxflags = $cxxflags $ cxxflags = $cxxflags $
-nostdlibinc $ -fno-exceptions $
-fno-exceptions $ -fno-rtti $
-fno-rtti
ldflags = $ldflags $ ldflags = $ldflags $
-g $ -g $
-nostdlib $ -nostdlib $
-znocombreloc $ -znocombreloc $
-Bsymbolic $ -Bsymbolic $
-nostartfiles -nostartfiles
{% endblock %} {% endblock %}
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -1,24 +0,0 @@
builddir = $builddir/{{ target }}
target = {{ target }}
{% block variables %}
{% endblock %}
{% block binaries %}
cc = clang
cxx = clang++
ld = clang++
ar = ar
nasm = nasm
objcopy = objcopy
{% endblock %}
{% for module in modules %}
subninja {{ module.name }}.ninja
{% endfor %}
build ${builddir}/cc.defs : dump_cc_defs | {{ buildfile }}
build ${builddir}/cxx.defs : dump_cxx_defs | {{ buildfile }}
build ${builddir}/cc.run : dump_cc_run | {{ buildfile }}
build ${builddir}/cxx.run : dump_cxx_run | {{ buildfile }}

View File

@@ -40,4 +40,6 @@ ldflags = $ldflags $
-Bstatic -Bstatic
{% endblock %} {% endblock %}
# vim: et ts=4 sts=4 sw=4
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -1,8 +1,15 @@
{% extends "target.default.j2" %} {% extends "target.default.j2" %}
{% block binaries %}
{{ super() }}
ld = clang++
{% endblock %}
{% block variables %} {% block variables %}
ccflags = $ccflags -g -ggdb ccflags = $ccflags -g -ggdb
{% endblock %} {% endblock %}
# vim: et ts=4 sts=4 sw=4
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -0,0 +1,49 @@
{% extends "target.default.j2" %}
{% block binaries %}
cc = ${srcroot}/sysroot/bin/clang
cxx = ${srcroot}/sysroot/bin/clang++
ld = ${srcroot}/sysroot/bin/x86_64-elf-ld
ar = ${srcroot}/sysroot/bin/x86_64-elf-ar
nasm = ${srcroot}/sysroot/bin/nasm
objcopy = ${srcroot}/sysroot/bin/x86_64-elf-objcopy
{% endblock %}
{% block variables %}
ccflags = $ccflags $
-nostdlib $
-nodefaultlibs $
-fno-builtin $
-mno-sse $
-fno-omit-frame-pointer $
-mno-red-zone $
-g $
-mcmodel=large $
-D__ELF__ $
-D__POPCORN__ $
-isystem${srcroot}/sysroot/include $
--sysroot="${srcroot}/sysroot"
cxxflags = $cxxflags $
-fno-exceptions $
-fno-rtti $
-isystem${srcroot}/sysroot/include/c++/v1
ldflags = $ldflags $
-g $
-nostdlib $
-znocombreloc $
-Bsymbolic $
-nostartfiles $
-Bstatic $
--sysroot="${srcroot}/sysroot" $
-L "${srcroot}/sysroot/lib" $
libs = $libs $
-lc
{% endblock %}
# vim: ft=ninja et ts=4 sts=4 sw=4

View File

@@ -3,5 +3,9 @@ GUID(0x8868e871,0xe4f1,0x11d3,0xbc,0x22,0x00,0x80,0xc7,0x3c,0x88,0x81, guid_acpi
GUID(0x09576e92,0x6d3f,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b, guid_file_info); GUID(0x09576e92,0x6d3f,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b, guid_file_info);
GUID(0x9042a9de,0x23dc,0x4a38,0x96,0xfb,0x7a,0xde,0xd0,0x80,0x51,0x6a, guid_gfx_out); GUID(0x9042a9de,0x23dc,0x4a38,0x96,0xfb,0x7a,0xde,0xd0,0x80,0x51,0x6a, guid_gfx_out);
GUID(0x964e5b22,0x6459,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b, guid_simple_filesystem); GUID(0x964e5b22,0x6459,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b, guid_simple_filesystem);
GUID(0x09576e91,0x6d3f,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b, guid_device_path);
GUID(0x8b843e20,0x8132,0x4852,0x90,0xcc,0x55,0x1a,0x4e,0x4a,0x7f,0x1c, guid_device_path_to_text);
GUID(0x10d0669c,0x9ec6,0x4268,0xbc,0x48,0xff,0x74,0x75,0x21,0xfe,0x07, guid_popcorn_vendor);
// vim: ft=c // vim: ft=c

View File

@@ -9,31 +9,73 @@
static wchar_t kernel_name[] = KERNEL_FILENAME; static wchar_t kernel_name[] = KERNEL_FILENAME;
static wchar_t initrd_name[] = INITRD_FILENAME; static wchar_t initrd_name[] = INITRD_FILENAME;
EFI_STATUS
loader_alloc_aligned(
EFI_BOOT_SERVICES *bootsvc,
EFI_MEMORY_TYPE mem_type,
size_t *length,
void **pages)
{
EFI_STATUS status;
EFI_PHYSICAL_ADDRESS addr;
size_t alignment = PAGE_SIZE;
while (alignment < *length)
alignment *= 2;
size_t page_count = alignment / PAGE_SIZE;
*length = alignment;
con_debug(L"Trying to find %d aligned pages for %x\n", page_count, mem_type);
status = bootsvc->AllocatePages(AllocateAnyPages, mem_type, page_count * 2, &addr);
CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating %d pages for alignment", page_count * 2);
con_debug(L" Found %d pages at %lx\n", page_count * 2, addr);
EFI_PHYSICAL_ADDRESS aligned = addr;
aligned = ((aligned - 1) & ~(alignment - 1)) + alignment;
con_debug(L" Aligning %lx to %lx\n", addr, aligned);
size_t before =
(reinterpret_cast<uint64_t>(aligned) -
reinterpret_cast<uint64_t>(addr)) /
PAGE_SIZE;
if (before) {
con_debug(L" Freeing %d initial pages\n", before);
bootsvc->FreePages(addr, before);
}
size_t after = page_count - before;
if (after) {
EFI_PHYSICAL_ADDRESS end =
reinterpret_cast<EFI_PHYSICAL_ADDRESS>(
reinterpret_cast<uint64_t>(aligned) +
page_count * PAGE_SIZE);
con_debug(L" Freeing %d remaining pages\n", after);
bootsvc->FreePages(end, after);
}
*pages = (void *)aligned;
return EFI_SUCCESS;
}
EFI_STATUS EFI_STATUS
loader_alloc_pages( loader_alloc_pages(
EFI_BOOT_SERVICES *bootsvc, EFI_BOOT_SERVICES *bootsvc,
EFI_MEMORY_TYPE mem_type, EFI_MEMORY_TYPE mem_type,
size_t *length, size_t *length,
void **pages, void **pages)
bool align)
{ {
EFI_STATUS status; EFI_STATUS status;
size_t page_count = ((*length - 1) / PAGE_SIZE) + 1; size_t page_count = ((*length - 1) / PAGE_SIZE) + 1;
EFI_PHYSICAL_ADDRESS addr = (EFI_PHYSICAL_ADDRESS)*pages; EFI_PHYSICAL_ADDRESS addr = (EFI_PHYSICAL_ADDRESS)*pages;
if (align) { con_debug(L"Trying to find %d non-aligned pages for %x at %lx\n",
// Align addr to the next multiple of N pages page_count, mem_type, addr);
size_t align_size = page_count * PAGE_SIZE;
addr = ((addr - 1) & ~(align_size - 1)) + align_size;
}
status = bootsvc->AllocatePages(AllocateAddress, mem_type, page_count, &addr); status = bootsvc->AllocatePages(AllocateAddress, mem_type, page_count, &addr);
if (status == EFI_NOT_FOUND || status == EFI_OUT_OF_RESOURCES) {
// couldn't get the address we wanted, try loading the kernel anywhere
status =
bootsvc->AllocatePages(AllocateAnyPages, mem_type, page_count, &addr);
}
CHECK_EFI_STATUS_OR_RETURN(status, CHECK_EFI_STATUS_OR_RETURN(status,
L"Allocating %d kernel pages type %x", L"Allocating %d kernel pages type %x",
page_count, mem_type); page_count, mem_type);
@@ -69,12 +111,11 @@ loader_load_initrd(
data->initrd_length = ((EFI_FILE_INFO *)info)->FileSize; data->initrd_length = ((EFI_FILE_INFO *)info)->FileSize;
status = loader_alloc_pages( status = loader_alloc_aligned(
bootsvc, bootsvc,
memtype_initrd, memtype_initrd,
&data->initrd_length, &data->initrd_length,
&data->initrd, &data->initrd);
true);
CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating pages"); CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating pages");
status = file->Read(file, &data->initrd_length, data->initrd); status = file->Read(file, &data->initrd_length, data->initrd);
@@ -143,7 +184,7 @@ loader_load_elf(
header.machine != 0x3e) header.machine != 0x3e)
CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"ELF load error: wrong machine architecture"); CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"ELF load error: wrong machine architecture");
con_debug(L"ELF is valid, entrypoint %lu\r\n", header.entrypoint); con_debug(L"ELF is valid, entrypoint %lx\r\n", header.entrypoint);
data->kernel_entry = (void *)header.entrypoint; data->kernel_entry = (void *)header.entrypoint;
@@ -161,7 +202,7 @@ loader_load_elf(
length = prog_header.mem_size; length = prog_header.mem_size;
void *addr = (void *)(prog_header.vaddr - KERNEL_VIRT_ADDRESS); void *addr = (void *)(prog_header.vaddr - KERNEL_VIRT_ADDRESS);
status = loader_alloc_pages(bootsvc, memtype_kernel, &length, &addr, false); status = loader_alloc_pages(bootsvc, memtype_kernel, &length, &addr);
CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating kernel pages"); CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating kernel pages");
if (data->kernel == 0) if (data->kernel == 0)
@@ -236,20 +277,19 @@ loader_load_kernel(
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_elf: %s", kernel_name); CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_elf: %s", kernel_name);
data->initrd = (void *)((uint64_t)data->kernel + data->kernel_length); data->data = (void *)((uint64_t)data->kernel + data->kernel_length);
status = loader_load_initrd(bootsvc, root, data);
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_file: %s", initrd_name);
data->data = (void *)((uint64_t)data->initrd + data->initrd_length);
data->data_length += PAGE_SIZE; // extra page for map growth data->data_length += PAGE_SIZE; // extra page for map growth
status = loader_alloc_pages(
status = loader_alloc_aligned(
bootsvc, bootsvc,
memtype_data, memtype_data,
&data->data_length, &data->data_length,
&data->data, &data->data);
true); CHECK_EFI_STATUS_OR_RETURN(status, L"loader_alloc_aligned: kernel data");
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_alloc_pages: kernel data");
data->initrd = (void *)((uint64_t)data->data + data->data_length);
status = loader_load_initrd(bootsvc, root, data);
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_file: %s", initrd_name);
return EFI_SUCCESS; return EFI_SUCCESS;
} }

View File

@@ -5,7 +5,7 @@
#include "console.h" #include "console.h"
#include "guids.h" #include "guids.h"
#include "kernel_data.h" #include "kernel_args.h"
#include "loader.h" #include "loader.h"
#include "memory.h" #include "memory.h"
#include "utility.h" #include "utility.h"
@@ -34,7 +34,51 @@ struct kernel_header {
}; };
#pragma pack(pop) #pragma pack(pop)
using kernel_entry = void (*)(popcorn_data *); using kernel_entry = void (*)(kernel_args *);
static void
type_to_wchar(wchar_t *into, uint32_t type)
{
for (int j=0; j<4; ++j)
into[j] = static_cast<wchar_t>(reinterpret_cast<char *>(&type)[j]);
}
EFI_STATUS
detect_debug_mode(EFI_RUNTIME_SERVICES *run, kernel_args *header) {
wchar_t var_name[] = L"debug";
EFI_STATUS status;
uint8_t debug = 0;
UINTN var_size = sizeof(debug);
#ifdef __POPCORN_SET_DEBUG_UEFI_VAR__
debug = __POPCORN_SET_DEBUG_UEFI_VAR__;
uint32_t attrs =
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS;
status = run->SetVariable(
var_name,
&guid_popcorn_vendor,
attrs,
var_size,
&debug);
CHECK_EFI_STATUS_OR_RETURN(status, "detect_debug_mode::SetVariable");
#endif
status = run->GetVariable(
var_name,
&guid_popcorn_vendor,
nullptr,
&var_size,
&debug);
CHECK_EFI_STATUS_OR_RETURN(status, "detect_debug_mode::GetVariable");
if (debug)
header->flags |= POPCORN_FLAG_DEBUG;
return EFI_SUCCESS;
}
extern "C" EFI_STATUS extern "C" EFI_STATUS
efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table) efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
@@ -73,14 +117,13 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
status = memory_get_map_length(bootsvc, &data_length); status = memory_get_map_length(bootsvc, &data_length);
CHECK_EFI_STATUS_OR_FAIL(status); CHECK_EFI_STATUS_OR_FAIL(status);
size_t header_size = sizeof(popcorn_data); size_t header_size = sizeof(kernel_args);
const size_t header_align = alignof(popcorn_data); const size_t header_align = alignof(kernel_args);
if (header_size % header_align) if (header_size % header_align)
header_size += header_align - (header_size % header_align); header_size += header_align - (header_size % header_align);
data_length += header_size; data_length += header_size;
// Load the kernel image from disk and check it // Load the kernel image from disk and check it
// //
console::print(L"Loading kernel into memory...\r\n"); console::print(L"Loading kernel into memory...\r\n");
@@ -90,9 +133,9 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
status = loader_load_kernel(bootsvc, &load); status = loader_load_kernel(bootsvc, &load);
CHECK_EFI_STATUS_OR_FAIL(status); CHECK_EFI_STATUS_OR_FAIL(status);
console::print(L" %u image bytes at 0x%x\r\n", load.kernel_length, load.kernel); console::print(L" %x image bytes at 0x%x\r\n", load.kernel_length, load.kernel);
console::print(L" %u initrd bytes at 0x%x\r\n", load.initrd_length, load.initrd); console::print(L" %x data bytes at 0x%x\r\n", load.data_length, load.data);
console::print(L" %u data bytes at 0x%x\r\n", load.data_length, load.data); console::print(L" %x initrd bytes at 0x%x\r\n", load.initrd_length, load.initrd);
struct kernel_header *version = (struct kernel_header *)load.kernel; struct kernel_header *version = (struct kernel_header *)load.kernel;
if (version->magic != KERNEL_HEADER_MAGIC) { if (version->magic != KERNEL_HEADER_MAGIC) {
@@ -102,7 +145,7 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
console::print(L" Kernel version %d.%d.%d %x%s\r\n", console::print(L" Kernel version %d.%d.%d %x%s\r\n",
version->major, version->minor, version->patch, version->gitsha & 0x0fffffff, version->major, version->minor, version->patch, version->gitsha & 0x0fffffff,
version->gitsha & 0xf0000000 ? "*" : ""); version->gitsha & 0xf0000000 ? L"*" : L"");
console::print(L" Entrypoint 0x%x\r\n", load.kernel_entry); console::print(L" Entrypoint 0x%x\r\n", load.kernel_entry);
kernel_entry kernel_main = kernel_entry kernel_main =
@@ -111,12 +154,12 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
// Set up the kernel data pages to pass to the kernel // Set up the kernel data pages to pass to the kernel
// //
struct popcorn_data *data_header = (struct popcorn_data *)load.data; struct kernel_args *data_header = (struct kernel_args *)load.data;
memory_mark_pointer_fixup((void **)&data_header); memory_mark_pointer_fixup((void **)&data_header);
data_header->magic = DATA_HEADER_MAGIC; data_header->magic = DATA_HEADER_MAGIC;
data_header->version = DATA_HEADER_VERSION; data_header->version = DATA_HEADER_VERSION;
data_header->length = sizeof(struct popcorn_data); data_header->length = sizeof(struct kernel_args);
data_header->scratch_pages = SCRATCH_PAGES; data_header->scratch_pages = SCRATCH_PAGES;
data_header->flags = 0; data_header->flags = 0;
@@ -168,6 +211,8 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
data_header->memory_map_length = map.length; data_header->memory_map_length = map.length;
data_header->memory_map_desc_size = map.size; data_header->memory_map_desc_size = map.size;
detect_debug_mode(runsvc, data_header);
// bootsvc->Stall(5000000); // bootsvc->Stall(5000000);
status = bootsvc->ExitBootServices(image_handle, map.key); status = bootsvc->ExitBootServices(image_handle, map.key);

View File

@@ -1 +1,24 @@
int main(int argc, const char **argv) { return 0; } #include <stdint.h>
#include <stdlib.h>
extern "C" {
int32_t getpid();
int32_t fork();
void sleep(uint64_t til);
void debug();
void message(const char *msg);
int main(int, const char **);
}
int
main(int argc, const char **argv)
{
int32_t pid = getpid();
int32_t child = fork();
message("hello from nulldrv!");
for (int i = 1; i < 5; ++i)
sleep(i*10);
return 0;
}

View File

@@ -1,61 +1,79 @@
section .bss section .bss
mypid: resq 1 mymessage:
mychild: resq 1 resq 1024
extern main
extern exit
section .text section .text
global getpid
getpid:
push rbp
mov rbp, rsp
mov rax, 0x02 ; getpid syscall
syscall ; pid is now already in rax, so just return
pop rbp
ret
global debug
debug:
push rbp
mov rbp, rsp
mov rax, 0x00 ; debug syscall
syscall
pop rbp
ret
global sleep
sleep:
push rbp
mov rbp, rsp
mov rax, 0x21 ; sleep syscall
syscall
pop rbp
ret
global fork
fork:
push rbp
mov rbp, rsp
mov rax, 0x03
syscall ; pid left in rax
pop rbp
ret
global message
message:
push rbp
mov rbp, rsp
; message should already be in rdi
mov rax, 0x10
syscall
pop rbp
ret
global _start global _start
_start: _start:
xor rbp, rbp ; Sentinel rbp xor rbp, rbp ; Sentinel rbp
push rbp
push rbp
mov rbp, rsp
mov rax, 5 ; GETPID syscall mov rdi, 0
int 0xee mov rsi, 0
mov [mypid], rax call main
mov rax, 8 ; FORK syscall mov rdi, rax
int 0xee call exit
mov [mychild], rax
mov r12, [mypid]
mov r13, [mychild]
mov rax, 1 ; DEBUG syscall
int 0xee
cmp r12, 1
je .dosend
jne .doreceive
.preloop:
mov r11, 0 ; counter
mov rbx, 20 ; sleep timeout
.loop:
mov rax, 1 ; MESSAGE syscall
;mov rax, 0 ; NOOP syscall
;syscall
int 0xee
inc r11
cmp r11, 2
jle .loop
mov rax, 4 ; SLEEP syscall
; syscall
int 0xee
add rbx, 20
mov r11, 0
jmp .loop
.dosend:
mov rax, 6 ; SEND syscall
mov rdi, 2 ; target is pid 2
int 0xee
jmp .preloop
.doreceive:
mov rax, 7 ; RECEIVE syscall
mov rdi, 1 ; source is pid 2
int 0xee
jmp .preloop

View File

@@ -7,8 +7,10 @@
#define DATA_HEADER_MAGIC 0x600dda7a #define DATA_HEADER_MAGIC 0x600dda7a
#define DATA_HEADER_VERSION 1 #define DATA_HEADER_VERSION 1
#define POPCORN_FLAG_DEBUG 0x00000001
#pragma pack(push, 1) #pragma pack(push, 1)
struct popcorn_data { struct kernel_args {
uint32_t magic; uint32_t magic;
uint16_t version; uint16_t version;
uint16_t length; uint16_t length;

View File

@@ -2,6 +2,9 @@
/// \file kernel_memory.h /// \file kernel_memory.h
/// Constants related to the kernel's memory layout /// Constants related to the kernel's memory layout
#include <stddef.h>
#include <stdint.h>
namespace memory { namespace memory {
/// Size of a single page frame. /// Size of a single page frame.
@@ -19,4 +22,11 @@ namespace memory {
/// Initial process thread's stack size, in pages /// Initial process thread's stack size, in pages
static const unsigned initial_stack_pages = 1; static const unsigned initial_stack_pages = 1;
/// Max size of the kernel heap
static const size_t kernel_max_heap = 0x800000000; // 32GiB
/// Helper to determine if a physical address can be accessed
/// through the page_offset area.
inline bool page_mappable(uintptr_t a) { return (a & page_offset) == 0; }
} // namespace memory } // namespace memory

View File

@@ -0,0 +1,9 @@
LOG(apic, info);
LOG(device, info);
LOG(paging, warn);
LOG(driver, info);
LOG(memory, info);
LOG(fs, info);
LOG(task, info);
LOG(boot, info);
LOG(syscall,info);

View File

@@ -46,6 +46,7 @@ apic::apic(uint32_t *base) :
lapic::lapic(uint32_t *base, isr spurious) : lapic::lapic(uint32_t *base, isr spurious) :
apic(base) apic(base)
{ {
// TODO: This causes a "reserved" page fault under KVM
apic_write(m_base, 0xf0, static_cast<uint32_t>(spurious)); apic_write(m_base, 0xf0, static_cast<uint32_t>(spurious));
log::info(logs::apic, "LAPIC created, base %lx", m_base); log::info(logs::apic, "LAPIC created, base %lx", m_base);
} }

View File

@@ -8,19 +8,14 @@ __kernel_assert(const char *file, unsigned line, const char *message)
if (cons) { if (cons) {
cons->set_color(9 , 0); cons->set_color(9 , 0);
cons->puts("\n\n ERROR: "); cons->puts("\n\n ERROR: ");
cons->puts(message);
cons->puts("\n ");
cons->puts(file); cons->puts(file);
cons->puts(":"); cons->puts(":");
cons->put_dec(line); cons->put_dec(line);
cons->puts(": "); cons->puts("\n");
cons->puts(message);
} }
__asm__ ( "int $0e7h" );
while (1) __asm__ ("hlt"); while (1) __asm__ ("hlt");
} __asm__ ( "int $0xe4" );
extern "C" [[noreturn]] void
__assert_fail(const char *message, const char *file, unsigned int line, const char *function)
{
__kernel_assert(file, line, message);
} }

View File

@@ -18,11 +18,11 @@ global _start:function (_start.end - _start)
_start: _start:
cli cli
mov rsp, stack_end mov rsp, idle_stack_end
push 0 ; signal end of stack with 0 return address mov qword [rsp + 0x00], 0 ; signal end of stack with 0 return address
push 0 ; and a few extra entries in case of stack mov qword [rsp + 0x08], 0 ; and a few extra entries in case of stack
push 0 ; problems mov qword [rsp + 0x10], 0 ; problems
push 0 mov qword [rsp + 0x18], 0
mov rbp, rsp mov rbp, rsp
extern kernel_main extern kernel_main
@@ -47,6 +47,9 @@ interrupts_disable:
section .bss section .bss
align 0x100 align 0x100
stack_begin: idle_stack_begin:
resb 0x4000 ; 16KiB stack space resb 0x1000 ; 4KiB stack space
stack_end:
global idle_stack_end
idle_stack_end:
resq 4

View File

@@ -1,6 +1,7 @@
#include "kutil/coord.h" #include "kutil/coord.h"
#include "kutil/guid.h" #include "kutil/guid.h"
#include "kutil/memory.h" #include "kutil/memory.h"
#include "kutil/printf.h"
#include "console.h" #include "console.h"
#include "font.h" #include "font.h"
#include "screen.h" #include "screen.h"
@@ -278,100 +279,9 @@ console::putc(char c)
void console::vprintf(const char *fmt, va_list args) void console::vprintf(const char *fmt, va_list args)
{ {
static const unsigned buf_size = 256; static const unsigned buf_size = 256;
char buffer[256]; char buffer[buf_size];
vsnprintf_(buffer, buf_size, fmt, args);
const char *r = fmt; puts(buffer);
char *w = buffer;
char *wend = buffer + buf_size;
#define flush() do { *w = 0; puts(buffer); w = buffer; } while(0)
while (r && *r) {
if (w == wend) flush();
if (*r != '%') {
*w++ = *r++;
continue;
}
r++; // chomp the %
flush();
bool done = false;
bool right = true;
int width = 0;
char pad = ' ';
while (!done) {
char c = *r++;
switch (c) {
case '%': *w = '%'; done = true; break;
case '0':
if (width == 0) pad = '0';
// else fall through
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': width = width * 10 + (c - '0'); break;
case '-': right = !right; break;
case 'x': put_hex<uint32_t>(va_arg(args, uint32_t), right ? width : -width, pad); done = true; break;
case 'd':
case 'u': put_dec<uint32_t>(va_arg(args, uint32_t), right ? width : -width, pad); done = true; break;
case 's': {
const char *s = va_arg(args, const char*);
if (s) puts(s);
}
done = true;
break;
case 'G': {
// Special: GUID type
kutil::guid g = va_arg(args, kutil::guid);
put_hex<uint32_t>(g.a, 8, '0');
putc('-');
put_hex<uint16_t>((g.a >> 32) & 0xffff, 4, '0');
putc('-');
put_hex<uint16_t>((g.a >> 48) & 0xffff, 4, '0');
putc('-');
put_hex<uint16_t>((kutil::byteswap(g.b) >> 16) & 0xffff, 4, '0');
putc('-');
put_hex<uint16_t>(kutil::byteswap(g.b) & 0xffff, 4, '0');
put_hex<uint32_t>(kutil::byteswap(g.b >> 32), 8, '0');
}
done = true;
break;
case 'l':
switch (*r++) {
case 'x': put_hex<uint64_t>(va_arg(args, uint64_t), right ? width : -width, pad); done = true; break;
case 'd':
case 'u': put_dec<uint32_t>(va_arg(args, uint64_t), right ? width : -width, pad); done = true; break;
default:
done = true;
break;
}
break;
default:
done = true;
break;
}
}
}
flush();
} }
void void

View File

@@ -3,6 +3,8 @@
#include "cpu.h" #include "cpu.h"
#include "log.h" #include "log.h"
cpu_data bsp_cpu_data;
inline static void inline static void
__cpuid( __cpuid(
uint32_t leaf, uint32_t leaf,

View File

@@ -2,15 +2,27 @@
#include <stdint.h> #include <stdint.h>
struct process;
struct cpu_state struct cpu_state
{ {
uint64_t ds;
uint64_t r15, r14, r13, r12, r11, r10, r9, r8; uint64_t r15, r14, r13, r12, r11, r10, r9, r8;
uint64_t rdi, rsi, rbp, rbx, rdx, rcx, rax; uint64_t rdi, rsi, rbp, rbx, rdx, rcx, rax;
uint64_t interrupt, errorcode; uint64_t interrupt, errorcode;
uint64_t rip, cs, rflags, user_rsp, ss; uint64_t rip, cs, rflags, user_rsp, ss;
}; };
/// Per-cpu state data. If you change this, remember to update the assembly
/// version in 'tasking.inc'
struct cpu_data
{
uintptr_t rsp0;
uintptr_t rsp3;
process *tcb;
};
extern cpu_data bsp_cpu_data;
class cpu_id class cpu_id
{ {
public: public:

View File

@@ -4,56 +4,65 @@
#include "gdt.h" #include "gdt.h"
#include "page_manager.h" #include "page_manager.h"
#define print_reg(name, value) cons->printf(" %s: %016lx\n", name, (value)); size_t __counter_syscall_enter = 0;
size_t __counter_syscall_sysret = 0;
void void
print_regs(const cpu_state &regs) print_regs(const cpu_state &regs)
{ {
console *cons = console::get(); console *cons = console::get();
print_reg("rax", regs.rax); uint64_t cr2 = 0;
print_reg("rbx", regs.rbx); __asm__ __volatile__ ("mov %%cr2, %0" : "=r"(cr2));
print_reg("rcx", regs.rcx);
print_reg("rdx", regs.rdx); print_regL("rax", regs.rax);
print_reg("rdi", regs.rdi); print_regM("rbx", regs.rbx);
print_reg("rsi", regs.rsi); print_regR("rcx", regs.rcx);
print_regL("rdx", regs.rdx);
print_regM("rdi", regs.rdi);
print_regR("rsi", regs.rsi);
cons->puts("\n"); cons->puts("\n");
print_reg(" r8", regs.r8); print_regL(" r8", regs.r8);
print_reg(" r9", regs.r9); print_regM(" r9", regs.r9);
print_reg("r10", regs.r10); print_regR("r10", regs.r10);
print_reg("r11", regs.r11); print_regL("r11", regs.r11);
print_reg("r12", regs.r12); print_regM("r12", regs.r12);
print_reg("r13", regs.r13); print_regR("r13", regs.r13);
print_reg("r14", regs.r14); print_regL("r14", regs.r14);
print_reg("r15", regs.r15); print_regM("r15", regs.r15);
cons->puts("\n\n");
print_regL("rbp", regs.rbp);
print_regM("rsp", regs.user_rsp);
print_regR("sp0", bsp_cpu_data.rsp0);
print_regL("rip", regs.rip);
print_regM("cr3", page_manager::get()->get_pml4());
print_regR("cr2", cr2);
cons->puts("\n"); cons->puts("\n");
print_reg("rbp", regs.rbp);
print_reg("rsp", regs.user_rsp);
print_reg("sp0", tss_get_stack(0));
cons->puts("\n");
print_reg(" ds", regs.ds);
print_reg(" cs", regs.cs);
print_reg(" ss", regs.ss);
cons->puts("\n");
print_reg("rip", regs.rip);
cons->puts("\n");
print_reg("cr3", page_manager::get()->get_pml4());
} }
struct frame
{
frame *prev;
uintptr_t return_addr;
};
void void
print_stacktrace(int skip) print_stacktrace(int skip)
{ {
console *cons = console::get(); console *cons = console::get();
int frame = 0;
uint64_t bp = get_frame(skip); frame *fp = nullptr;
while (bp) { int fi = -skip;
cons->printf(" frame %2d: %lx\n", frame, bp); __asm__ __volatile__ ( "mov %%rbp, %0" : "=r" (fp) );
bp = get_frame(++frame + skip);
while (fp && fp->return_addr) {
if (fi++ >= 0)
cons->printf(" frame %2d: %lx\n", fi-1, fp->return_addr);
fp = fp->prev;
} }
} }

View File

@@ -8,12 +8,18 @@ extern "C" {
uintptr_t get_rsp(); uintptr_t get_rsp();
uintptr_t get_rip(); uintptr_t get_rip();
uintptr_t get_frame(int frame); uintptr_t get_frame(int frame);
uintptr_t get_gsbase();
void _halt();
} }
extern size_t __counter_syscall_enter;
extern size_t __counter_syscall_sysret;
void print_regs(const cpu_state &regs); void print_regs(const cpu_state &regs);
void print_stack(const cpu_state &regs); void print_stack(const cpu_state &regs);
void print_stacktrace(int skip = 0); void print_stacktrace(int skip);
#define print_reg(name, value) cons->printf(" %s: %016lx\n", name, (value)); #define print_regL(name, value) cons->printf(" %s: %016lx", name, (value));
#define print_regM(name, value) cons->printf(" %s: %016lx", name, (value));
#define print_regR(name, value) cons->printf(" %s: %016lx\n", name, (value));

View File

@@ -8,24 +8,13 @@ get_rip:
pop rax ; do the same thing as 'ret', except with 'jmp' pop rax ; do the same thing as 'ret', except with 'jmp'
jmp rax ; with the return address still in rax jmp rax ; with the return address still in rax
global get_gsbase
get_gsbase:
rdgsbase rax
ret
global _halt global _halt
_halt: _halt:
hlt hlt
jmp _halt jmp _halt
global get_frame
get_frame:
mov rcx, rbp
.loop:
mov rax, [rcx + 8]
mov rcx, [rcx]
cmp rdi, 0
je .done
sub rdi, 1
jmp .loop
.done:
ret

View File

@@ -14,7 +14,7 @@
static const char expected_signature[] = "RSD PTR "; static const char expected_signature[] = "RSD PTR ";
device_manager device_manager::s_instance(nullptr); device_manager device_manager::s_instance(nullptr, kutil::allocator::invalid);
struct acpi1_rsdp struct acpi1_rsdp
{ {
@@ -59,8 +59,13 @@ void irq4_callback(void *)
} }
device_manager::device_manager(const void *root_table) : device_manager::device_manager(const void *root_table, kutil::allocator &alloc) :
m_lapic(nullptr) m_lapic(nullptr),
m_ioapics(alloc),
m_pci(alloc),
m_devices(alloc),
m_irqs(alloc),
m_blockdevs(alloc)
{ {
kassert(root_table != 0, "ACPI root table pointer is null."); kassert(root_table != 0, "ACPI root table pointer is null.");
@@ -93,7 +98,7 @@ device_manager::device_manager(const void *root_table) :
ioapic * ioapic *
device_manager::get_ioapic(int i) device_manager::get_ioapic(int i)
{ {
return (i < m_ioapics.count()) ? m_ioapics[i] : nullptr; return (i < m_ioapics.count()) ? &m_ioapics[i] : nullptr;
} }
static void static void
@@ -148,26 +153,44 @@ device_manager::load_apic(const acpi_apic *apic)
uint8_t const *p = apic->controller_data; uint8_t const *p = apic->controller_data;
uint8_t const *end = p + count; uint8_t const *end = p + count;
// Pass one: set up IOAPIC objcts // Pass one: count IOAPIC objcts
int num_ioapics = 0;
while (p < end) {
const uint8_t type = p[0];
const uint8_t length = p[1];
if (type == 1) num_ioapics++;
p += length;
}
m_ioapics.set_capacity(num_ioapics);
// Pass two: set up IOAPIC objcts
p = apic->controller_data;
while (p < end) { while (p < end) {
const uint8_t type = p[0]; const uint8_t type = p[0];
const uint8_t length = p[1]; const uint8_t length = p[1];
if (type == 1) { if (type == 1) {
uint32_t *base = reinterpret_cast<uint32_t *>(kutil::read_from<uint32_t>(p+4)); uint32_t *base = reinterpret_cast<uint32_t *>(kutil::read_from<uint32_t>(p+4));
uint32_t base_gsr = kutil::read_from<uint32_t>(p+8); uint32_t base_gsr = kutil::read_from<uint32_t>(p+8);
m_ioapics.append(new ioapic(base, base_gsr)); m_ioapics.emplace(base, base_gsr);
} }
p += length; p += length;
} }
// Pass two: configure APIC objects // Pass three: configure APIC objects
p = apic->controller_data; p = apic->controller_data;
while (p < end) { while (p < end) {
const uint8_t type = p[0]; const uint8_t type = p[0];
const uint8_t length = p[1]; const uint8_t length = p[1];
switch (type) { switch (type) {
case 0: // Local APIC case 0: { // Local APIC
uint8_t uid = kutil::read_from<uint8_t>(p+2);
uint8_t id = kutil::read_from<uint8_t>(p+3);
log::debug(logs::device, " Local APIC uid %x id %x", id);
}
break;
case 1: // I/O APIC case 1: // I/O APIC
break; break;
@@ -180,7 +203,7 @@ device_manager::load_apic(const acpi_apic *apic)
source, gsi, (flags & 0x3), ((flags >> 2) & 0x3)); source, gsi, (flags & 0x3), ((flags >> 2) & 0x3));
// TODO: in a multiple-IOAPIC system this might be elsewhere // TODO: in a multiple-IOAPIC system this might be elsewhere
m_ioapics[0]->redirect(source, static_cast<isr>(gsi), flags, true); m_ioapics[0].redirect(source, static_cast<isr>(gsi), flags, true);
} }
break; break;
@@ -206,10 +229,10 @@ device_manager::load_apic(const acpi_apic *apic)
p += length; p += length;
} }
for (uint8_t i = 0; i < m_ioapics[0]->get_num_gsi(); ++i) { for (uint8_t i = 0; i < m_ioapics[0].get_num_gsi(); ++i) {
switch (i) { switch (i) {
case 2: break; case 2: break;
default: m_ioapics[0]->mask(i, false); default: m_ioapics[0].mask(i, false);
} }
} }

View File

@@ -2,14 +2,13 @@
/// \file device_manager.h /// \file device_manager.h
/// The device manager definition /// The device manager definition
#include "kutil/vector.h" #include "kutil/vector.h"
#include "apic.h"
#include "pci.h" #include "pci.h"
struct acpi_xsdt; struct acpi_xsdt;
struct acpi_apic; struct acpi_apic;
struct acpi_mcfg; struct acpi_mcfg;
class block_device; class block_device;
class lapic;
class ioapic;
using irq_callback = void (*)(void *); using irq_callback = void (*)(void *);
@@ -19,8 +18,9 @@ class device_manager
{ {
public: public:
/// Constructor. /// Constructor.
/// \arg root_table Pointer to the ACPI RSDP /// \arg root_table Pointer to the ACPI RSDP
device_manager(const void *root_table); /// \arg alloc Allocator for device arrays
device_manager(const void *root_table, kutil::allocator &alloc);
/// Get the system global device manager. /// Get the system global device manager.
/// \returns A reference to the system device manager /// \returns A reference to the system device manager
@@ -105,7 +105,7 @@ private:
void bad_irq(uint8_t irq); void bad_irq(uint8_t irq);
lapic *m_lapic; lapic *m_lapic;
kutil::vector<ioapic *> m_ioapics; kutil::vector<ioapic> m_ioapics;
kutil::vector<pci_group> m_pci; kutil::vector<pci_group> m_pci;
kutil::vector<pci_device> m_devices; kutil::vector<pci_device> m_devices;

View File

@@ -0,0 +1,93 @@
#include "kutil/assert.h"
#include "kutil/memory.h"
#include "frame_allocator.h"
using memory::frame_size;
using memory::page_offset;
using frame_block_node = kutil::list_node<frame_block>;
frame_allocator g_frame_allocator;
int
frame_block::compare(const frame_block *rhs) const
{
if (address < rhs->address)
return -1;
else if (address > rhs->address)
return 1;
return 0;
}
frame_allocator::raw_alloc::raw_alloc(frame_allocator &fa) : m_fa(fa) {}
void *
frame_allocator::raw_alloc::allocate(size_t size)
{
kassert(size <= frame_size, "Raw allocator only allocates a single page");
uintptr_t addr = 0;
if (size <= frame_size)
m_fa.allocate(1, &addr);
return reinterpret_cast<void*>(addr + page_offset);
}
void
frame_allocator::raw_alloc::free(void *p)
{
m_fa.free(reinterpret_cast<uintptr_t>(p), 1);
}
frame_allocator::frame_allocator() :
m_raw_alloc(*this)
{
}
size_t
frame_allocator::allocate(size_t count, uintptr_t *address)
{
kassert(!m_free.empty(), "frame_allocator::pop_frames ran out of free frames!");
if (m_free.empty())
return 0;
auto *first = m_free.front();
if (count >= first->count) {
*address = first->address;
m_free.remove(first);
return first->count;
} else {
first->count -= count;
*address = first->address + (first->count * frame_size);
return count;
}
}
inline uintptr_t end(frame_block *node) { return node->address + node->count * frame_size; }
void
frame_allocator::free(uintptr_t address, size_t count)
{
frame_block_node *node =
reinterpret_cast<frame_block_node*>(address + page_offset);
kutil::memset(node, 0, sizeof(frame_block_node));
node->address = address;
node->count = count;
m_free.sorted_insert(node);
frame_block_node *next = node->next();
if (next && end(node) == next->address) {
node->count += next->count;
m_free.remove(next);
}
frame_block_node *prev = node->prev();
if (prev && end(prev) == address) {
prev->count += node->count;
m_free.remove(node);
}
}

View File

@@ -0,0 +1,70 @@
#pragma once
/// \file frame_allocator.h
/// Allocator for physical memory frames
#include <stdint.h>
#include "kutil/allocator.h"
#include "kutil/linked_list.h"
struct frame_block;
using frame_block_list = kutil::linked_list<frame_block>;
/// Allocator for physical memory frames
class frame_allocator
{
public:
/// Default constructor
frame_allocator();
/// Get free frames from the free list. Only frames from the first free block
/// are returned, so the number may be less than requested, but they will
/// be contiguous.
/// \arg count The maximum number of frames to get
/// \arg address [out] The physical address of the first frame
/// \returns The number of frames retrieved
size_t allocate(size_t count, uintptr_t *address);
/// Free previously allocated frames.
/// \arg address The physical address of the first frame to free
/// \arg count The number of frames to be freed
void free(uintptr_t address, size_t count);
/// Get a memory allocator that allocates raw pages
/// \returns The allocator ojbect
kutil::allocator & raw_allocator() { return m_raw_alloc; }
private:
class raw_alloc :
public kutil::allocator
{
public:
raw_alloc(frame_allocator &fa);
virtual void * allocate(size_t size) override;
virtual void free(void *p) override;
private:
frame_allocator &m_fa;
};
raw_alloc m_raw_alloc;
frame_block_list m_free; ///< Free frames list
frame_allocator(const frame_allocator &) = delete;
};
/// A block of contiguous frames. Each `frame_block` represents contiguous
/// physical frames with the same attributes.
struct frame_block
{
uintptr_t address;
uint32_t count;
/// Compare two blocks by address.
/// \arg rhs The right-hand comparator
/// \returns <0 if this is sorts earlier, >0 if this sorts later, 0 for equal
int compare(const frame_block *rhs) const;
};
extern frame_allocator g_frame_allocator;

View File

@@ -184,18 +184,25 @@ gdt_init()
} }
void void
gdt_dump() gdt_dump(int index)
{ {
const table_ptr &table = g_gdtr; const table_ptr &table = g_gdtr;
console *cons = console::get(); console *cons = console::get();
cons->printf(" GDT: loc:%lx size:%d\n", table.base, table.limit+1);
int start = 0;
int count = (table.limit + 1) / sizeof(gdt_descriptor); int count = (table.limit + 1) / sizeof(gdt_descriptor);
if (index != -1) {
start = index;
count = 1;
} else {
cons->printf(" GDT: loc:%lx size:%d\n", table.base, table.limit+1);
}
const gdt_descriptor *gdt = const gdt_descriptor *gdt =
reinterpret_cast<const gdt_descriptor *>(table.base); reinterpret_cast<const gdt_descriptor *>(table.base);
for (int i = 0; i < count; ++i) { for (int i = start; i < start+count; ++i) {
uint32_t base = uint32_t base =
(gdt[i].base_high << 24) | (gdt[i].base_high << 24) |
(gdt[i].base_mid << 16) | (gdt[i].base_mid << 16) |
@@ -233,17 +240,25 @@ gdt_dump()
} }
void void
idt_dump() idt_dump(int index)
{ {
const table_ptr &table = g_idtr; const table_ptr &table = g_idtr;
log::info(logs::boot, "Loaded IDT at: %lx size: %d bytes", table.base, table.limit+1);
int start = 0;
int count = (table.limit + 1) / sizeof(idt_descriptor); int count = (table.limit + 1) / sizeof(idt_descriptor);
if (index != -1) {
start = index;
count = 1;
log::info(logs::boot, "IDT FOR INDEX %02x", index);
} else {
log::info(logs::boot, "Loaded IDT at: %lx size: %d bytes", table.base, table.limit+1);
}
const idt_descriptor *idt = const idt_descriptor *idt =
reinterpret_cast<const idt_descriptor *>(table.base); reinterpret_cast<const idt_descriptor *>(table.base);
for (int i = 0; i < count; ++i) { for (int i = start; i < start+count; ++i) {
uint64_t base = uint64_t base =
(static_cast<uint64_t>(idt[i].base_high) << 32) | (static_cast<uint64_t>(idt[i].base_high) << 32) |
(static_cast<uint64_t>(idt[i].base_mid) << 16) | (static_cast<uint64_t>(idt[i].base_mid) << 16) |

View File

@@ -25,7 +25,9 @@ void tss_set_stack(int ring, uintptr_t rsp);
uintptr_t tss_get_stack(int ring); uintptr_t tss_get_stack(int ring);
/// Dump information about the current GDT to the screen /// Dump information about the current GDT to the screen
void gdt_dump(); /// \arg index Which entry to print, or -1 for all entries
void gdt_dump(int index = -1);
/// Dump information about the current IDT to the screen /// Dump information about the current IDT to the screen
void idt_dump(); /// \arg index Which entry to print, or -1 for all entries
void idt_dump(int index = -1);

View File

@@ -18,9 +18,8 @@ static const uint16_t PIC2 = 0xa0;
extern "C" { extern "C" {
void _halt(); void _halt();
uintptr_t isr_handler(uintptr_t, cpu_state*); void isr_handler(cpu_state*);
uintptr_t irq_handler(uintptr_t, cpu_state*); void irq_handler(cpu_state*);
uintptr_t syscall_handler(uintptr_t, cpu_state);
#define ISR(i, name) extern void name (); #define ISR(i, name) extern void name ();
#define EISR(i, name) extern void name (); #define EISR(i, name) extern void name ();
@@ -104,13 +103,45 @@ interrupts_init()
log::info(logs::boot, "Interrupts enabled."); log::info(logs::boot, "Interrupts enabled.");
} }
uintptr_t void
isr_handler(uintptr_t return_rsp, cpu_state *regs) isr_handler(cpu_state *regs)
{ {
console *cons = console::get(); console *cons = console::get();
switch (static_cast<isr>(regs->interrupt & 0xff)) { switch (static_cast<isr>(regs->interrupt & 0xff)) {
case isr::isrDebug: {
cons->set_color(11);
cons->puts("\nDebug Exception:\n");
cons->set_color();
uint64_t dr = 0;
__asm__ __volatile__ ("mov %%dr0, %0" : "=r"(dr));
print_regL("dr0", dr);
__asm__ __volatile__ ("mov %%dr1, %0" : "=r"(dr));
print_regM("dr1", dr);
__asm__ __volatile__ ("mov %%dr2, %0" : "=r"(dr));
print_regM("dr2", dr);
__asm__ __volatile__ ("mov %%dr3, %0" : "=r"(dr));
print_regR("dr3", dr);
__asm__ __volatile__ ("mov %%dr6, %0" : "=r"(dr));
print_regL("dr6", dr);
__asm__ __volatile__ ("mov %%dr7, %0" : "=r"(dr));
print_regR("dr7", dr);
print_regL("rip", regs->rip);
print_regM("rsp", regs->user_rsp);
print_regM("fla", regs->rflags);
_halt();
}
break;
case isr::isrGPFault: { case isr::isrGPFault: {
cons->set_color(9); cons->set_color(9);
cons->puts("\nGeneral Protection Fault:\n"); cons->puts("\nGeneral Protection Fault:\n");
@@ -124,13 +155,13 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
switch ((regs->errorcode & 0x07) >> 1) { switch ((regs->errorcode & 0x07) >> 1) {
case 0: case 0:
cons->printf(" GDT[%x]\n", index); cons->printf(" GDT[%x]\n", index);
gdt_dump(); gdt_dump(index);
break; break;
case 1: case 1:
case 3: case 3:
cons->printf(" IDT[%x]\n", index); cons->printf(" IDT[%x]\n", index);
idt_dump(); idt_dump(index);
break; break;
default: default:
@@ -145,41 +176,35 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
print_stacktrace(2); print_stacktrace(2);
print_stack(*regs); print_stack(*regs);
*/ */
} }
_halt(); _halt();
break; break;
case isr::isrPageFault: { case isr::isrPageFault: {
cons->set_color(11); uintptr_t cr2 = 0;
cons->puts("\nPage Fault:\n");
cons->set_color();
cons->puts(" flags:");
if (regs->errorcode & 0x01) cons->puts(" present");
if (regs->errorcode & 0x02) cons->puts(" write");
if (regs->errorcode & 0x04) cons->puts(" user");
if (regs->errorcode & 0x08) cons->puts(" reserved");
if (regs->errorcode & 0x10) cons->puts(" ip");
cons->puts("\n");
uint64_t cr2 = 0;
__asm__ __volatile__ ("mov %%cr2, %0" : "=r"(cr2)); __asm__ __volatile__ ("mov %%cr2, %0" : "=r"(cr2));
print_reg("cr2", cr2);
print_reg("rsp", regs->user_rsp); if (!page_manager::get()->fault_handler(cr2)) {
print_reg("rip", regs->rip); cons->set_color(11);
cons->puts("\nPage Fault:\n");
cons->set_color();
cons->puts("\n"); cons->puts(" flags:");
//print_stacktrace(2); if (regs->errorcode & 0x01) cons->puts(" present");
if (regs->errorcode & 0x02) cons->puts(" write");
if (regs->errorcode & 0x04) cons->puts(" user");
if (regs->errorcode & 0x08) cons->puts(" reserved");
if (regs->errorcode & 0x10) cons->puts(" ip");
cons->puts("\n");
print_regs(*regs);
print_stacktrace(2);
_halt();
}
} }
_halt();
break; break;
case isr::isrTimer: { case isr::isrTimer:
scheduler &s = scheduler::get(); scheduler::get().tick();
return_rsp = s.tick(return_rsp);
}
break; break;
case isr::isrLINT0: case isr::isrLINT0:
@@ -198,14 +223,15 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
_halt(); _halt();
break; break;
case isr::isrSyscall: { /*
return_rsp = syscall_dispatch(return_rsp, *regs); case isr::isrSyscall:
} syscall_dispatch(regs);
break; break;
*/
case isr::isrSpurious: case isr::isrSpurious:
// No EOI for the spurious interrupt // No EOI for the spurious interrupt
return return_rsp; return;
case isr::isrIgnore0: case isr::isrIgnore0:
case isr::isrIgnore1: case isr::isrIgnore1:
@@ -242,16 +268,14 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
regs->interrupt, regs->errorcode); regs->interrupt, regs->errorcode);
print_regs(*regs); print_regs(*regs);
//print_stacktrace(2); print_stacktrace(2);
_halt(); _halt();
} }
*reinterpret_cast<uint32_t *>(0xffffff80fee000b0) = 0; *reinterpret_cast<uint32_t *>(0xffffff80fee000b0) = 0;
return return_rsp;
} }
uintptr_t void
irq_handler(uintptr_t return_rsp, cpu_state *regs) irq_handler(cpu_state *regs)
{ {
console *cons = console::get(); console *cons = console::get();
uint8_t irq = get_irq(regs->interrupt); uint8_t irq = get_irq(regs->interrupt);
@@ -265,11 +289,4 @@ irq_handler(uintptr_t return_rsp, cpu_state *regs)
} }
*reinterpret_cast<uint32_t *>(0xffffff80fee000b0) = 0; *reinterpret_cast<uint32_t *>(0xffffff80fee000b0) = 0;
return return_rsp;
}
uintptr_t
syscall_handler(uintptr_t return_rsp, cpu_state regs)
{
return syscall_dispatch(return_rsp, regs);
} }

View File

@@ -3,39 +3,36 @@
extern isr_handler extern isr_handler
global isr_handler_prelude global isr_handler_prelude
isr_handler_prelude: isr_handler_prelude:
push_all_and_segments push_all
check_swap_gs
mov rdi, rsp mov rdi, rsp
mov rsi, rsp mov rsi, rsp
call isr_handler call isr_handler
mov rsp, rax jmp isr_handler_return
pop_all_and_segments
add rsp, 16 ; because the ISRs added err/num
sti
iretq
extern irq_handler extern irq_handler
global irq_handler_prelude global irq_handler_prelude
irq_handler_prelude: irq_handler_prelude:
push_all_and_segments push_all
check_swap_gs
mov rdi, rsp mov rdi, rsp
mov rsi, rsp mov rsi, rsp
call irq_handler call irq_handler
mov rsp, rax ; fall through to isr_handler_return
pop_all_and_segments global isr_handler_return
isr_handler_return:
check_swap_gs
pop_all
add rsp, 16 ; because the ISRs added err/num add rsp, 16 ; because the ISRs added err/num
sti
iretq iretq
%macro EMIT_ISR 2 %macro EMIT_ISR 2
global %1 global %1
%1: %1:
cli
push 0 push 0
push %2 push %2
jmp isr_handler_prelude jmp isr_handler_prelude
@@ -44,7 +41,6 @@ irq_handler_prelude:
%macro EMIT_EISR 2 %macro EMIT_EISR 2
global %1 global %1
%1: %1:
cli
push %2 push %2
jmp isr_handler_prelude jmp isr_handler_prelude
%endmacro %endmacro
@@ -52,7 +48,6 @@ irq_handler_prelude:
%macro EMIT_IRQ 2 %macro EMIT_IRQ 2
global %1 global %1
%1: %1:
cli
push 0 push 0
push %2 push %2
jmp irq_handler_prelude jmp irq_handler_prelude

View File

@@ -1,25 +1,22 @@
%include "push_all.inc" %include "push_all.inc"
extern load_process extern load_process_image
global ramdisk_process_loader global ramdisk_process_loader
ramdisk_process_loader: ramdisk_process_loader:
; create_process already pushed a cpu_state onto the stack for us, this ; create_process already pushed a cpu_state onto the stack for us, this
; acts both as the cpu_state parameter to load_process, and the saved ; acts both as the cpu_state parameter to load_process_image, and the
; state for the following iretq ; saved state for the following iretq
;
; Additional parameters:
; rax - the address of the program image
; rbx - the size of the program image
; rcx - the address of this process' process structure
mov rdi, rax
mov rsi, rbx
mov rdx, rcx
call load_process
pop_all_and_segments pop rdi ; the address of the program image
add rsp, 16 ; because the ISRs add err/num pop rsi ; the size of the program image
pop rdx ; the address of this process' process structure
call load_process_image
push rax ; load_process_image returns the process entrypoint
swapgs
iretq iretq

View File

@@ -1,107 +1,55 @@
#include <type_traits>
#include "kutil/assert.h"
#include "kutil/memory.h" #include "kutil/memory.h"
#include "console.h" #include "console.h"
#include "log.h" #include "log.h"
#include "scheduler.h"
static const uint64_t default_enabled[] = {0x00, 0xff, 0xff, 0xff}; static uint8_t log_buffer[0x10000];
static const uint8_t level_colors[] = {0x07, 0x0f, 0x0b, 0x09}; static log::logger g_logger(log_buffer, sizeof(log_buffer));
static const char *levels[] = {"debug", " info", " warn", "error", "fatal"}; static const uint8_t level_colors[] = {0x07, 0x07, 0x0f, 0x0b, 0x09};
static const char *areas[] = {
"boot ",
"memory",
"apic ",
"device",
"driver",
"file ",
"task ",
"paging",
nullptr static void
}; output_log(log::area_t area, log::level severity, const char *message)
log log::s_log;
log::log() : m_cons(nullptr)
{ {
kassert(0, "Invalid log constructor"); auto *cons = console::get();
} cons->set_color(level_colors[static_cast<int>(severity)]);
cons->printf("%7s %5s: %s\n",
log::log(console *cons) : g_logger.area_name(area),
m_cons(cons) g_logger.level_name(severity),
{ message);
const int num_levels = static_cast<int>(level::max) - 1; cons->set_color();
for (int i = 0; i < num_levels; ++i)
m_enabled[i] = default_enabled[i];
} }
void void
log::init(console *cons) logger_task()
{ {
new (&s_log) log(cons); uint8_t buffer[257];
log::info(logs::boot, "Logging system initialized."); auto *ent = reinterpret_cast<log::logger::entry *>(buffer);
} auto *cons = console::get();
static inline uint64_t g_logger.set_immediate(nullptr);
bit_mask(logs area) { return 1 << static_cast<uint64_t>(area); } log::info(logs::task, "Starting kernel logger task");
void scheduler &s = scheduler::get();
log::enable(logs type, level at_level)
{
using under_t = std::underlying_type<level>::type;
under_t at = static_cast<under_t>(at_level);
under_t max = sizeof(m_enabled) / sizeof(m_enabled[0]);
for (under_t i = 0; i < max; ++i) { while (true) {
if (i >= at) if(g_logger.get_entry(buffer, sizeof(buffer))) {
s_log.m_enabled[i] |= bit_mask(type); buffer[ent->bytes] = 0;
else cons->set_color(level_colors[static_cast<int>(ent->severity)]);
s_log.m_enabled[i] &= ~bit_mask(type); cons->printf("%7s %5s: %s\n",
g_logger.area_name(ent->area),
g_logger.level_name(ent->severity),
ent->message);
cons->set_color();
} else {
s.schedule();
}
} }
} }
template <> void logger_init()
void
log::trylog<log::level::fatal>(logs area, const char *fmt, ...)
{ {
va_list args; new (&g_logger) log::logger(log_buffer, sizeof(log_buffer));
va_start(args, fmt); g_logger.set_immediate(output_log);
s_log.output(level::fatal, area, fmt, args);
va_end(args);
while(1) __asm__ ("hlt");
} }
template <log::level L>
void
log::trylog(logs area, const char *fmt, ...)
{
auto i = static_cast<std::underlying_type<level>::type>(L);
if ((s_log.m_enabled[i] & bit_mask(area)) == 0) return;
va_list args;
va_start(args, fmt);
s_log.output(L, area, fmt, args);
va_end(args);
}
void
log::output(level severity, logs area, const char *fmt, va_list args)
{
m_cons->set_color(level_colors[static_cast<int>(severity)]);
m_cons->printf("%s %s: ",
areas[static_cast<int>(area)],
levels[static_cast<int>(severity)]);
m_cons->vprintf(fmt, args);
m_cons->set_color();
m_cons->puts("\n");
}
const log::trylog_p log::debug = &trylog<level::debug>;
const log::trylog_p log::info = &trylog<level::info>;
const log::trylog_p log::warn = &trylog<level::warn>;
const log::trylog_p log::error = &trylog<level::error>;
const log::trylog_p log::fatal = &trylog<level::fatal>;

View File

@@ -1,52 +1,9 @@
#pragma once #pragma once
#include <stdarg.h>
#include <stdint.h>
class console; #include "kutil/logger.h"
namespace log = kutil::log;
namespace logs = kutil::logs;
enum class logs void logger_init();
{ void logger_task();
boot,
memory,
apic,
device,
driver,
fs,
task,
paging,
max
};
class log
{
public:
enum class level {debug, info, warn, error, fatal, max};
static void init(console *cons);
static void enable(logs type, level at_level);
template <level L>
static void trylog(logs area, const char *fmt, ...);
using trylog_p = void (*)(logs area, const char *fmt, ...);
static const trylog_p debug;
static const trylog_p info;
static const trylog_p warn;
static const trylog_p error;
static const trylog_p fatal;
private:
void output(level severity, logs area, const char *fmt, va_list args);
/// Bitmasks for what categories are enabled. fatal is
/// always enabled, so leave it out.
uint64_t m_enabled[static_cast<uint64_t>(level::max) - 1];
console *m_cons;
log();
log(console *cons);
static log s_log;
};

View File

@@ -11,16 +11,15 @@
#include "gdt.h" #include "gdt.h"
#include "interrupts.h" #include "interrupts.h"
#include "io.h" #include "io.h"
#include "kernel_data.h" #include "kernel_args.h"
#include "log.h" #include "log.h"
#include "page_manager.h" #include "page_manager.h"
#include "scheduler.h" #include "scheduler.h"
#include "screen.h"
#include "serial.h" #include "serial.h"
#include "syscall.h" #include "syscall.h"
extern "C" { extern "C" {
void kernel_main(popcorn_data *header); void kernel_main(kernel_args *header);
void *__bss_start, *__bss_end; void *__bss_start, *__bss_end;
} }
@@ -37,37 +36,21 @@ init_console()
cons->set_color(0x08, 0x00); cons->set_color(0x08, 0x00);
cons->puts(GIT_VERSION " booting...\n"); cons->puts(GIT_VERSION " booting...\n");
log::init(cons); logger_init();
log::enable(logs::apic, log::level::info);
log::enable(logs::device, log::level::info);
log::enable(logs::driver, log::level::debug);
log::enable(logs::memory, log::level::debug);
log::enable(logs::fs, log::level::debug);
log::enable(logs::task, log::level::debug);
log::enable(logs::boot, log::level::debug);
log::enable(logs::paging, log::level::debug);
} }
void void
kernel_main(popcorn_data *header) kernel_main(kernel_args *header)
{ {
#ifdef DEBUG bool waiting = header && (header->flags && POPCORN_FLAG_DEBUG);
// Run `waf configure --debug` to enable compiling with DEBUG turned on.
// Then attach to QEMU's gdb server and `set waiting = false` to start
// the kernel. This compensates for GDB's poor handling of QEMU going
// through the x86 PC startup and switching to 64 bit mode when you
// attach to qemu with the -S option.
bool waiting = true;
while (waiting); while (waiting);
#endif
kutil::assert_set_callback(__kernel_assert); kutil::assert_set_callback(__kernel_assert);
gdt_init(); gdt_init();
interrupts_init(); interrupts_init();
memory_initialize( kutil::allocator &heap = memory_initialize(
header->scratch_pages, header->scratch_pages,
header->memory_map, header->memory_map,
header->memory_map_length, header->memory_map_length,
@@ -88,7 +71,7 @@ kernel_main(popcorn_data *header)
log::debug(logs::boot, "ACPI root table is at: %016lx", header->acpi_table); log::debug(logs::boot, "ACPI root table is at: %016lx", header->acpi_table);
log::debug(logs::boot, "Runtime service is at: %016lx", header->runtime); log::debug(logs::boot, "Runtime service is at: %016lx", header->runtime);
initrd::disk ird(header->initrd); initrd::disk ird(header->initrd, heap);
log::info(logs::boot, "initrd loaded with %d files.", ird.files().count()); log::info(logs::boot, "initrd loaded with %d files.", ird.files().count());
for (auto &f : ird.files()) for (auto &f : ird.files())
log::info(logs::boot, " %s%s (%d bytes).", f.executable() ? "*" : "", f.name(), f.size()); log::info(logs::boot, " %s%s (%d bytes).", f.executable() ? "*" : "", f.name(), f.size());
@@ -99,7 +82,7 @@ kernel_main(popcorn_data *header)
*/ */
device_manager *devices = device_manager *devices =
new (&device_manager::get()) device_manager(header->acpi_table); new (&device_manager::get()) device_manager(header->acpi_table, heap);
interrupts_enable(); interrupts_enable();
@@ -146,7 +129,9 @@ kernel_main(popcorn_data *header)
devices->get_lapic()->calibrate_timer(); devices->get_lapic()->calibrate_timer();
syscall_enable(); syscall_enable();
scheduler *sched = new (&scheduler::get()) scheduler(devices->get_lapic()); scheduler *sched = new (&scheduler::get()) scheduler(devices->get_lapic(), heap);
sched->create_kernel_task(-1, logger_task);
for (auto &f : ird.files()) { for (auto &f : ird.files()) {
if (f.executable()) if (f.executable())

View File

@@ -2,72 +2,26 @@
#include <utility> #include <utility>
#include "kutil/address_manager.h" #include "kutil/address_manager.h"
#include "kutil/assert.h" #include "kutil/assert.h"
#include "kutil/frame_allocator.h" #include "kutil/heap_allocator.h"
#include "kutil/heap_manager.h" #include "frame_allocator.h"
#include "io.h" #include "io.h"
#include "log.h" #include "log.h"
#include "page_manager.h" #include "page_manager.h"
using kutil::frame_block;
using kutil::frame_block_flags;
using kutil::frame_block_list;
using memory::frame_size; using memory::frame_size;
using memory::kernel_max_heap;
using memory::kernel_offset; using memory::kernel_offset;
using memory::page_offset; using memory::page_offset;
static const unsigned ident_page_flags = 0xb; static const unsigned ident_page_flags = 0xb;
kutil::frame_allocator g_frame_allocator;
kutil::address_manager g_kernel_address_manager; kutil::address_manager g_kernel_address_manager;
kutil::heap_manager g_kernel_heap_manager; kutil::heap_allocator g_kernel_heap;
void * mm_grow_callback(size_t length) void * operator new(size_t size) { return g_kernel_heap.allocate(size); }
{ void * operator new [] (size_t size) { return g_kernel_heap.allocate(size); }
kassert(length % frame_size == 0, void operator delete (void *p) noexcept { return g_kernel_heap.free(p); }
"Heap manager requested a fractional page."); void operator delete [] (void *p) noexcept { return g_kernel_heap.free(p); }
size_t pages = length / frame_size;
log::info(logs::memory, "Heap manager growing heap by %d pages.", pages);
uintptr_t addr = g_kernel_address_manager.allocate(length);
g_page_manager.map_pages(addr, pages);
return reinterpret_cast<void *>(addr);
}
namespace {
// Page-by-page initial allocator for the initial frame_block allocator
struct page_consumer
{
page_consumer(uintptr_t start, unsigned count, unsigned used = 0) :
current(start + used * frame_size),
used(used),
max(count) {}
void * get_page() {
kassert(used++ < max, "page_consumer ran out of pages");
void *retval = reinterpret_cast<void *>(current);
current += frame_size;
return retval;
}
void * operator()(size_t size) {
kassert(size == frame_size, "page_consumer used with non-page size!");
return get_page();
}
unsigned left() const { return max - used; }
uintptr_t current;
unsigned used, max;
};
using block_allocator =
kutil::slab_allocator<kutil::frame_block, page_consumer &>;
using region_allocator =
kutil::slab_allocator<kutil::buddy_region, page_consumer &>;
}
enum class efi_memory_type : uint32_t enum class efi_memory_type : uint32_t
{ {
@@ -107,100 +61,114 @@ struct efi_memory_descriptor
uint64_t flags; uint64_t flags;
}; };
static const efi_memory_descriptor * struct memory_map
desc_incr(const efi_memory_descriptor *d, size_t desc_length)
{ {
return reinterpret_cast<const efi_memory_descriptor *>( memory_map(const void *efi_map, size_t map_length, size_t desc_length) :
reinterpret_cast<const uint8_t *>(d) + desc_length); efi_map(efi_map), map_length(map_length), desc_length(desc_length) {}
}
void class iterator
gather_block_lists( {
block_allocator &allocator, public:
frame_block_list &used, iterator(const memory_map &map, efi_memory_descriptor const *item) :
frame_block_list &free, map(map), item(item) {}
const void *memory_map,
size_t map_length,
size_t desc_length)
{
efi_memory_descriptor const *desc = reinterpret_cast<efi_memory_descriptor const *>(memory_map);
efi_memory_descriptor const *end = desc_incr(desc, map_length);
while (desc < end) { inline efi_memory_descriptor const * operator*() const { return item; }
auto *block = allocator.pop(); inline bool operator!=(const iterator &other) { return item != other.item; }
block->address = desc->physical_start; inline iterator & operator++() {
block->count = desc->pages; item = kutil::offset_pointer(item, map.desc_length);
bool block_used; return *this;
switch (desc->type) {
case efi_memory_type::loader_code:
case efi_memory_type::loader_data:
block_used = true;
block->flags = frame_block_flags::pending_free;
break;
case efi_memory_type::boot_services_code:
case efi_memory_type::boot_services_data:
case efi_memory_type::available:
block_used = false;
break;
case efi_memory_type::acpi_reclaim:
block_used = true;
block->flags =
frame_block_flags::acpi_wait |
frame_block_flags::map_ident;
break;
case efi_memory_type::persistent:
block_used = false;
block->flags = frame_block_flags::nonvolatile;
break;
case efi_memory_type::popcorn_kernel:
block_used = true;
block->flags =
frame_block_flags::permanent |
frame_block_flags::map_kernel;
break;
case efi_memory_type::popcorn_data:
case efi_memory_type::popcorn_initrd:
block_used = true;
block->flags =
frame_block_flags::pending_free |
frame_block_flags::map_kernel;
break;
case efi_memory_type::popcorn_scratch:
block_used = true;
block->flags = frame_block_flags::map_offset;
break;
default:
block_used = true;
block->flags = frame_block_flags::permanent;
break;
} }
if (block_used) private:
used.push_back(block); const memory_map &map;
else efi_memory_descriptor const *item;
free.push_back(block); };
desc = desc_incr(desc, desc_length); iterator begin() const {
return iterator(*this, reinterpret_cast<efi_memory_descriptor const *>(efi_map));
} }
}
void iterator end() const {
const void *end = kutil::offset_pointer(efi_map, map_length);
return iterator(*this, reinterpret_cast<efi_memory_descriptor const *>(end));
}
const void *efi_map;
size_t map_length;
size_t desc_length;
};
class memory_bootstrap
{
public:
memory_bootstrap(const void *memory_map, size_t map_length, size_t desc_length) :
map(memory_map, map_length, desc_length) {}
void add_free_frames(frame_allocator &fa) {
for (auto *desc : map) {
if (desc->type == efi_memory_type::loader_code ||
desc->type == efi_memory_type::loader_data ||
desc->type == efi_memory_type::boot_services_code ||
desc->type == efi_memory_type::boot_services_data ||
desc->type == efi_memory_type::available)
{
fa.free(desc->physical_start, desc->pages);
}
}
}
void add_used_frames(kutil::address_manager &am) {
for (auto *desc : map) {
if (desc->type == efi_memory_type::popcorn_data ||
desc->type == efi_memory_type::popcorn_initrd)
{
uintptr_t virt_addr = desc->physical_start + kernel_offset;
am.mark(virt_addr, desc->pages * frame_size);
}
else if (desc->type == efi_memory_type::popcorn_kernel)
{
uintptr_t virt_addr = desc->physical_start + kernel_offset;
am.mark_permanent(virt_addr, desc->pages * frame_size);
}
}
}
void page_in_kernel(page_manager &pm, page_table *pml4) {
for (auto *desc : map) {
if (desc->type == efi_memory_type::popcorn_kernel ||
desc->type == efi_memory_type::popcorn_data ||
desc->type == efi_memory_type::popcorn_initrd)
{
uintptr_t virt_addr = desc->physical_start + kernel_offset;
pm.page_in(pml4, desc->physical_start, virt_addr, desc->pages);
}
if (desc->type == efi_memory_type::acpi_reclaim) {
pm.page_in(pml4, desc->physical_start, desc->physical_start, desc->pages);
}
}
// Put our new PML4 into CR3 to start using it
page_manager::set_pml4(pml4);
pm.m_kernel_pml4 = pml4;
}
private:
const memory_map map;
};
kutil::allocator &
memory_initialize(uint16_t scratch_pages, const void *memory_map, size_t map_length, size_t desc_length) memory_initialize(uint16_t scratch_pages, const void *memory_map, size_t map_length, size_t desc_length)
{ {
// make sure the options we want in CR4 are set // make sure the options we want in CR4 are set
uint64_t cr4; uint64_t cr4;
__asm__ __volatile__ ( "mov %%cr4, %0" : "=r" (cr4) ); __asm__ __volatile__ ( "mov %%cr4, %0" : "=r" (cr4) );
cr4 |= 0x00080; // Enable global pages cr4 |=
cr4 |= 0x00200; // Enable FXSAVE/FXRSTOR 0x000080 | // Enable global pages
cr4 |= 0x20000; // Enable PCIDs 0x000200 | // Enable FXSAVE/FXRSTOR
0x010000 | // Enable FSGSBASE
0x020000 | // Enable PCIDs
0;
__asm__ __volatile__ ( "mov %0, %%cr4" :: "r" (cr4) ); __asm__ __volatile__ ( "mov %0, %%cr4" :: "r" (cr4) );
// The bootloader reserved "scratch_pages" pages for page tables and // The bootloader reserved "scratch_pages" pages for page tables and
@@ -224,81 +192,51 @@ memory_initialize(uint16_t scratch_pages, const void *memory_map, size_t map_len
__sync_synchronize(); __sync_synchronize();
io_wait(); io_wait();
// We now have pages starting at "scratch_virt" to bootstrap ourselves. Start by
// taking inventory of free pages.
uintptr_t scratch_virt = scratch_phys + page_offset; uintptr_t scratch_virt = scratch_phys + page_offset;
uint64_t used_pages = 2; // starts with PML4 + offset PDP memory_bootstrap bootstrap {memory_map, map_length, desc_length};
page_consumer allocator(scratch_virt, scratch_pages, used_pages);
block_allocator block_slab(frame_size, allocator); // Now tell the frame allocator what's free
frame_block_list used; frame_allocator *fa = new (&g_frame_allocator) frame_allocator;
frame_block_list free; bootstrap.add_free_frames(*fa);
gather_block_lists(block_slab, used, free, memory_map, map_length, desc_length); // Build an initial address manager that we'll copy into the real
block_slab.allocate(); // Make sure we have extra // address manager later (so that we can use a raw allocator now)
kutil::allocator &alloc = fa->raw_allocator();
kutil::address_manager init_am(alloc);
init_am.add_regions(kernel_offset, page_offset - kernel_offset);
bootstrap.add_used_frames(init_am);
// Now go back through these lists and consolidate // Add the heap into the address manager
block_slab.append(frame_block::consolidate(free)); uintptr_t heap_start = page_offset - kernel_max_heap;
init_am.mark(heap_start, kernel_max_heap);
region_allocator region_slab(frame_size, allocator); kutil::allocator *heap_alloc =
region_slab.allocate(); // Allocate some buddy regions for the address_manager new (&g_kernel_heap) kutil::heap_allocator(heap_start, kernel_max_heap);
// Copy everything into the real address manager
kutil::address_manager *am = kutil::address_manager *am =
new (&g_kernel_address_manager) kutil::address_manager(std::move(region_slab)); new (&g_kernel_address_manager) kutil::address_manager(
std::move(init_am), *heap_alloc);
am->add_regions(kernel_offset, page_offset - kernel_offset); // Create the page manager
page_manager *pm = new (&g_page_manager) page_manager(*fa, *am);
// Finally, build an acutal set of kernel page tables that just contains // Give the frame_allocator back the rest of the scratch pages
fa->free(scratch_phys + (3 * frame_size), scratch_pages - 3);
// Finally, build an acutal set of kernel page tables where we'll only add
// what the kernel actually has mapped, but making everything writable // what the kernel actually has mapped, but making everything writable
// (especially the page tables themselves) // (especially the page tables themselves)
page_table *pml4 = reinterpret_cast<page_table *>(allocator.get_page()); page_table *pml4 = &tables[2];
pml4 = kutil::offset_pointer(pml4, page_offset);
kutil::memset(pml4, 0, sizeof(page_table)); kutil::memset(pml4, 0, sizeof(page_table));
pml4->entries[511] = reinterpret_cast<uintptr_t>(id_pdp) | 0x10b; pml4->entries[511] = reinterpret_cast<uintptr_t>(id_pdp) | 0x10b;
kutil::frame_allocator *fa = bootstrap.page_in_kernel(*pm, pml4);
new (&g_frame_allocator) kutil::frame_allocator(std::move(block_slab));
page_manager *pm = new (&g_page_manager) page_manager(*fa, *am);
// Give the rest to the page_manager's cache for use in page_in // Reclaim the old PML4
pm->free_table_pages( fa->free(scratch_phys, 1);
reinterpret_cast<void *>(allocator.current),
allocator.left());
for (auto *block : used) { return *heap_alloc;
uintptr_t virt_addr = 0;
switch (block->flags & frame_block_flags::map_mask) {
case frame_block_flags::map_ident:
virt_addr = block->address;
break;
case frame_block_flags::map_kernel:
virt_addr = block->address + kernel_offset;
if (block->flags && frame_block_flags::permanent)
am->mark_permanent(virt_addr, block->count * frame_size);
else
am->mark(virt_addr, block->count * frame_size);
break;
default:
break;
}
block->flags -= frame_block_flags::map_mask;
if (virt_addr)
pm->page_in(pml4, block->address, virt_addr, block->count);
}
fa->init(std::move(free), std::move(used));
// Put our new PML4 into CR3 to start using it
page_manager::set_pml4(pml4);
pm->m_kernel_pml4 = pml4;
// Give the old pml4 back to the page_manager to recycle
pm->free_table_pages(reinterpret_cast<void *>(scratch_virt), 1);
// Set the heap manager
new (&g_kernel_heap_manager) kutil::heap_manager(mm_grow_callback);
kutil::setup::set_heap(&g_kernel_heap_manager);
} }

View File

@@ -2,14 +2,15 @@
#include "kutil/assert.h" #include "kutil/assert.h"
#include "console.h" #include "console.h"
#include "io.h"
#include "log.h" #include "log.h"
#include "page_manager.h" #include "page_manager.h"
using memory::frame_size; using memory::frame_size;
using memory::kernel_offset; using memory::kernel_offset;
using memory::page_offset; using memory::page_offset;
using memory::page_mappable;
extern kutil::frame_allocator g_frame_allocator;
extern kutil::address_manager g_kernel_address_manager; extern kutil::address_manager g_kernel_address_manager;
page_manager g_page_manager( page_manager g_page_manager(
g_frame_allocator, g_frame_allocator,
@@ -38,7 +39,7 @@ struct free_page_header
page_manager::page_manager( page_manager::page_manager(
kutil::frame_allocator &frames, frame_allocator &frames,
kutil::address_manager &addrs) : kutil::address_manager &addrs) :
m_page_cache(nullptr), m_page_cache(nullptr),
m_frames(frames), m_frames(frames),
@@ -68,28 +69,60 @@ page_manager::create_process_map()
uintptr_t uintptr_t
page_manager::copy_page(uintptr_t orig) page_manager::copy_page(uintptr_t orig)
{ {
uintptr_t virt = m_addrs.allocate(2 * frame_size); bool paged_orig = false;
uintptr_t copy = 0; bool paged_copy = false;
uintptr_t orig_virt;
if (page_mappable(orig)) {
orig_virt = orig + page_offset;
} else {
orig_virt = m_addrs.allocate(frame_size);
page_in(get_pml4(), orig, orig_virt, 1);
paged_orig = true;
}
uintptr_t copy = 0;
uintptr_t copy_virt;
size_t n = m_frames.allocate(1, &copy); size_t n = m_frames.allocate(1, &copy);
kassert(n, "copy_page could not allocate page"); kassert(n, "copy_page could not allocate page");
page_in(get_pml4(), orig, virt, 1); if (page_mappable(copy)) {
page_in(get_pml4(), copy, virt + frame_size, 1); copy_virt = copy + page_offset;
} else {
copy_virt = m_addrs.allocate(frame_size);
page_in(get_pml4(), copy, copy_virt, 1);
paged_copy = true;
}
// TODO: multiple page copies at a time, so that we don't have to keep
// paying this mapping penalty
if (paged_orig || paged_copy) {
set_pml4(get_pml4());
__sync_synchronize();
io_wait();
}
kutil::memcpy( kutil::memcpy(
reinterpret_cast<void *>(virt + frame_size), reinterpret_cast<void *>(copy_virt),
reinterpret_cast<void *>(virt), reinterpret_cast<void *>(orig_virt),
frame_size); frame_size);
page_out(get_pml4(), virt, 2); if (paged_orig) {
page_out(get_pml4(), orig_virt, 1);
m_addrs.free(orig_virt);
}
if (paged_copy) {
page_out(get_pml4(), copy_virt, 1);
m_addrs.free(copy_virt);
}
m_addrs.free(virt);
return copy; return copy;
} }
page_table * page_table *
page_manager::copy_table(page_table *from, page_table::level lvl) page_manager::copy_table(page_table *from, page_table::level lvl, page_table_indices index)
{ {
page_table *to = get_table_page(); page_table *to = get_table_page();
log::debug(logs::paging, "Page manager copying level %d table at %016lx to %016lx.", lvl, from, to); log::debug(logs::paging, "Page manager copying level %d table at %016lx to %016lx.", lvl, from, to);
@@ -105,12 +138,17 @@ page_manager::copy_table(page_table *from, page_table::level lvl)
512; 512;
unsigned pages_copied = 0; unsigned pages_copied = 0;
uintptr_t from_addr = 0;
uintptr_t to_addr = 0;
for (int i = 0; i < max; ++i) { for (int i = 0; i < max; ++i) {
if (!from->is_present(i)) { if (!from->is_present(i)) {
to->entries[i] = 0; to->entries[i] = 0;
continue; continue;
} }
index[lvl] = i;
bool is_page = bool is_page =
lvl == page_table::level::pt || lvl == page_table::level::pt ||
from->is_large_page(lvl, i); from->is_large_page(lvl, i);
@@ -119,17 +157,20 @@ page_manager::copy_table(page_table *from, page_table::level lvl)
uint16_t flags = from->entries[i] & 0xfffull; uint16_t flags = from->entries[i] & 0xfffull;
uintptr_t orig = from->entries[i] & ~0xfffull; uintptr_t orig = from->entries[i] & ~0xfffull;
to->entries[i] = copy_page(orig) | flags; to->entries[i] = copy_page(orig) | flags;
pages_copied++; if (!pages_copied++)
from_addr = index.addr();
to_addr = index.addr();
} else { } else {
uint16_t flags = 0; uint16_t flags = 0;
page_table *next_from = from->get(i, &flags); page_table *next_from = from->get(i, &flags);
page_table *next_to = copy_table(next_from, page_table::deeper(lvl)); page_table *next_to = copy_table(next_from, page_table::deeper(lvl), index);
to->set(i, next_to, flags); to->set(i, next_to, flags);
} }
} }
if (pages_copied) if (pages_copied)
log::debug(logs::paging, " copied %3u pages", pages_copied); log::debug(logs::paging, " copied %3u pages %016lx - %016lx",
pages_copied, from_addr, to_addr + frame_size);
return to; return to;
} }
@@ -137,13 +178,20 @@ page_manager::copy_table(page_table *from, page_table::level lvl)
void void
page_manager::delete_process_map(page_table *pml4) page_manager::delete_process_map(page_table *pml4)
{ {
bool was_pml4 = (pml4 == get_pml4());
if (was_pml4)
set_pml4(m_kernel_pml4);
log::info(logs::paging, "Deleting process pml4 at %016lx%s",
pml4, was_pml4 ? " (was current)" : "");
unmap_table(pml4, page_table::level::pml4, true); unmap_table(pml4, page_table::level::pml4, true);
} }
void void
page_manager::map_offset_pointer(void **pointer, size_t length) page_manager::map_offset_pointer(void **pointer, size_t length)
{ {
log::info(logs::paging, "Mapping offset pointer region at %016lx size 0x%lx", *pointer, length); log::debug(logs::paging, "Mapping offset pointer region at %016lx size 0x%lx", *pointer, length);
*pointer = kutil::offset_pointer(*pointer, page_offset); *pointer = kutil::offset_pointer(*pointer, page_offset);
} }
@@ -178,6 +226,7 @@ page_manager::get_table_page()
free_page_header *page = m_page_cache; free_page_header *page = m_page_cache;
m_page_cache = page->next; m_page_cache = page->next;
return reinterpret_cast<page_table *>(page); return reinterpret_cast<page_table *>(page);
} }
@@ -197,6 +246,11 @@ page_manager::free_table_pages(void *pages, size_t count)
void * void *
page_manager::map_pages(uintptr_t address, size_t count, bool user, page_table *pml4) page_manager::map_pages(uintptr_t address, size_t count, bool user, page_table *pml4)
{ {
if (!address) {
kassert(!user, "Cannot call map_pages with 0 address for user mapping");
address = m_addrs.allocate(count * frame_size);
}
void *ret = reinterpret_cast<void *>(address); void *ret = reinterpret_cast<void *>(address);
if (!pml4) pml4 = get_pml4(); if (!pml4) pml4 = get_pml4();
@@ -217,7 +271,7 @@ page_manager::map_pages(uintptr_t address, size_t count, bool user, page_table *
} }
void void
page_manager::unmap_table(page_table *table, page_table::level lvl, bool free) page_manager::unmap_table(page_table *table, page_table::level lvl, bool free, page_table_indices index)
{ {
const int max = const int max =
lvl == page_table::level::pml4 ? lvl == page_table::level::pml4 ?
@@ -225,6 +279,7 @@ page_manager::unmap_table(page_table *table, page_table::level lvl, bool free)
512; 512;
uintptr_t free_start = 0; uintptr_t free_start = 0;
uintptr_t free_start_virt = 0;
uintptr_t free_count = 0; uintptr_t free_count = 0;
size_t size = size_t size =
@@ -236,34 +291,78 @@ page_manager::unmap_table(page_table *table, page_table::level lvl, bool free)
for (int i = 0; i < max; ++i) { for (int i = 0; i < max; ++i) {
if (!table->is_present(i)) continue; if (!table->is_present(i)) continue;
index[lvl] = i;
bool is_page = bool is_page =
lvl == page_table::level::pt || lvl == page_table::level::pt ||
table->is_large_page(lvl, i); table->is_large_page(lvl, i);
if (is_page) { if (is_page) {
uintptr_t frame = table->entries[i] & ~0xfffull; uintptr_t frame = table->entries[i] & ~0xfffull;
if (!free_count || free_start != frame + free_count * size) { if (!free_count || frame != free_start + free_count * size) {
if (free_count && free) if (free_count && free) {
m_frames.free(free_start, free_count * size / frame_size); log::debug(logs::paging,
free_start = frame; " freeing v:%016lx-%016lx p:%016lx-%016lx",
free_count = 1; free_start_virt, free_start_virt + free_count * frame_size,
free_start, free_start + free_count * frame_size);
m_frames.free(free_start, (free_count * size) / frame_size);
free_count = 0;
}
if (!free_count) {
free_start = frame;
free_start_virt = index.addr();
}
} }
free_count += 1;
} else { } else {
page_table *next = table->get(i); page_table *next = table->get(i);
unmap_table(next, page_table::deeper(lvl), free); unmap_table(next, page_table::deeper(lvl), free, index);
} }
} }
if (free_count && free) if (free_count && free) {
m_frames.free(free_start, free_count * size / frame_size); log::debug(logs::paging,
" freeing v:%016lx-%016lx p:%016lx-%016lx",
free_start_virt, free_start_virt + free_count * frame_size,
free_start, free_start + free_count * frame_size);
m_frames.free(free_start, (free_count * size) / frame_size);
}
free_table_pages(table, 1); free_table_pages(table, 1);
log::debug(logs::paging, "Unmapped%s lv %d table at %016lx",
free ? " (and freed)" : "", lvl, table);
} }
void void
page_manager::unmap_pages(void* address, size_t count, page_table *pml4) page_manager::unmap_pages(void* address, size_t count, page_table *pml4)
{ {
if (!pml4) pml4 = get_pml4(); if (!pml4)
page_out(pml4, reinterpret_cast<uintptr_t>(address), count, true); pml4 = get_pml4();
uintptr_t iaddr = reinterpret_cast<uintptr_t>(address);
page_out(pml4, iaddr, count, true);
if (iaddr >= kernel_offset) {
// TODO
// m_addrs.free(address, count);
}
}
bool
page_manager::fault_handler(uintptr_t addr)
{
if (!m_addrs.contains(addr))
return false;
uintptr_t page = addr & ~0xfffull;
bool user = addr < kernel_offset;
map_pages(page, 1, user);
return true;
} }
void void
@@ -279,8 +378,10 @@ page_manager::check_needs_page(page_table *table, unsigned index, bool user)
void void
page_manager::page_in(page_table *pml4, uintptr_t phys_addr, uintptr_t virt_addr, size_t count, bool user, bool large) page_manager::page_in(page_table *pml4, uintptr_t phys_addr, uintptr_t virt_addr, size_t count, bool user, bool large)
{ {
/*
log::debug(logs::paging, "page_in for table %016lx p:%016lx v:%016lx c:%4d u:%d l:%d", log::debug(logs::paging, "page_in for table %016lx p:%016lx v:%016lx c:%4d u:%d l:%d",
pml4, phys_addr, virt_addr, count, user, large); pml4, phys_addr, virt_addr, count, user, large);
*/
page_table_indices idx{virt_addr}; page_table_indices idx{virt_addr};
page_table *tables[4] = {pml4, nullptr, nullptr, nullptr}; page_table *tables[4] = {pml4, nullptr, nullptr, nullptr};

View File

@@ -7,9 +7,9 @@
#include "kutil/address_manager.h" #include "kutil/address_manager.h"
#include "kutil/enum_bitfields.h" #include "kutil/enum_bitfields.h"
#include "kutil/frame_allocator.h"
#include "kutil/linked_list.h" #include "kutil/linked_list.h"
#include "kutil/slab_allocator.h" #include "kutil/slab_allocator.h"
#include "frame_allocator.h"
#include "kernel_memory.h" #include "kernel_memory.h"
#include "page_table.h" #include "page_table.h"
@@ -20,7 +20,7 @@ class page_manager
{ {
public: public:
page_manager( page_manager(
kutil::frame_allocator &frames, frame_allocator &frames,
kutil::address_manager &addrs); kutil::address_manager &addrs);
/// Helper to get the number of pages needed for a given number of bytes. /// Helper to get the number of pages needed for a given number of bytes.
@@ -44,8 +44,9 @@ public:
/// \arg pml4 A pointer to the PML4 table to install. /// \arg pml4 A pointer to the PML4 table to install.
static inline void set_pml4(page_table *pml4) static inline void set_pml4(page_table *pml4)
{ {
uintptr_t p = reinterpret_cast<uintptr_t>(pml4) - memory::page_offset; constexpr uint64_t phys_mask = ~memory::page_offset & ~0xfffull;
__asm__ __volatile__ ( "mov %0, %%cr3" :: "r" (p & ~0xfffull) ); uintptr_t p = reinterpret_cast<uintptr_t>(pml4) & phys_mask;
__asm__ __volatile__ ( "mov %0, %%cr3" :: "r" (p) );
} }
/// Allocate but don't switch to a new PML4 table. This table /// Allocate but don't switch to a new PML4 table. This table
@@ -62,10 +63,12 @@ public:
/// \arg lvl Level of the given tables (default is PML4) /// \arg lvl Level of the given tables (default is PML4)
/// \returns The new page table /// \returns The new page table
page_table * copy_table(page_table *from, page_table * copy_table(page_table *from,
page_table::level lvl = page_table::level::pml4); page_table::level lvl = page_table::level::pml4,
page_table_indices index = {});
/// Allocate and map pages into virtual memory. /// Allocate and map pages into virtual memory.
/// \arg address The virtual address at which to map the pages /// \arg address The virtual address at which to map the pages, or zero
/// for any free kernel space.
/// \arg count The number of pages to map /// \arg count The number of pages to map
/// \arg user True is this memory is user-accessible /// \arg user True is this memory is user-accessible
/// \arg pml4 The pml4 to map into - null for the current one /// \arg pml4 The pml4 to map into - null for the current one
@@ -108,6 +111,14 @@ public:
/// \returns A pointer to the system page manager /// \returns A pointer to the system page manager
static page_manager * get(); static page_manager * get();
/// Get a pointer to the kernel's PML4
inline page_table * get_kernel_pml4() { return m_kernel_pml4; }
/// Attempt to handle a page fault.
/// \arg addr Address that triggered the fault
/// \returns True if the fault was handled
bool fault_handler(uintptr_t addr);
private: private:
/// Copy a physical page /// Copy a physical page
/// \arg orig Physical address of the page to copy /// \arg orig Physical address of the page to copy
@@ -158,15 +169,16 @@ private:
bool free = false); bool free = false);
/// Low-level routine for unmapping an entire table of memory at once /// Low-level routine for unmapping an entire table of memory at once
void unmap_table(page_table *table, page_table::level lvl, bool free); void unmap_table(page_table *table, page_table::level lvl, bool free,
page_table_indices index = {});
page_table *m_kernel_pml4; ///< The PML4 of just kernel pages page_table *m_kernel_pml4; ///< The PML4 of just kernel pages
free_page_header *m_page_cache; ///< Cache of free pages to use for tables free_page_header *m_page_cache; ///< Cache of free pages to use for tables
kutil::frame_allocator &m_frames; frame_allocator &m_frames;
kutil::address_manager &m_addrs; kutil::address_manager &m_addrs;
friend void memory_initialize(uint16_t, const void *, size_t, size_t); friend class memory_bootstrap;
page_manager(const page_manager &) = delete; page_manager(const page_manager &) = delete;
}; };
@@ -199,4 +211,8 @@ page_table_align(T p)
/// Bootstrap the memory managers. /// Bootstrap the memory managers.
void memory_initialize(uint16_t scratch_pages, const void *memory_map, size_t map_length, size_t desc_length); kutil::allocator & memory_initialize(
uint16_t scratch_pages,
const void *memory_map,
size_t map_length,
size_t desc_length);

View File

@@ -1,11 +1,23 @@
#include "kutil/heap_allocator.h"
#include "cpu.h" #include "cpu.h"
#include "debug.h"
#include "log.h" #include "log.h"
#include "process.h" #include "process.h"
#include "scheduler.h" #include "scheduler.h"
extern "C" void task_fork_return_thunk();
extern kutil::heap_allocator g_kernel_heap; // TODO: this is a bad hack to get access to the heap
void
process::exit(uint32_t code)
{
return_code = code;
flags -= process_flags::running;
page_manager::get()->delete_process_map(pml4);
}
pid_t pid_t
process::fork(uintptr_t in_rsp) process::fork()
{ {
auto &sched = scheduler::get(); auto &sched = scheduler::get();
auto *child = sched.create_process(); auto *child = sched.create_process();
@@ -18,40 +30,79 @@ process::fork(uintptr_t in_rsp)
sched.m_runlists[child->priority].push_back(child); sched.m_runlists[child->priority].push_back(child);
child->rsp = in_rsp;
child->pml4 = page_manager::get()->copy_table(pml4); child->pml4 = page_manager::get()->copy_table(pml4);
kassert(child->pml4, "process::fork() got null pml4"); kassert(child->pml4, "process::fork() got null pml4");
child->setup_kernel_stack(kernel_stack_size, kernel_stack); child->rsp3 = bsp_cpu_data.rsp3;
child->rsp = child->kernel_stack + (in_rsp - kernel_stack); child->setup_kernel_stack();
log::debug(logs::task, "Copied process %d to %d, new PML4 %016lx.", log::debug(logs::task, "Copied process %d to %d",
pid, child->pid, child->pml4); pid, child->pid);
log::debug(logs::task, " copied stack %016lx to %016lx, rsp %016lx to %016lx.",
kernel_stack, child->kernel_stack, in_rsp, child->rsp);
// Add in the faked fork return value log::debug(logs::task, " PML4 %016lx", child->pml4);
cpu_state *regs = reinterpret_cast<cpu_state *>(child->rsp); log::debug(logs::task, " RSP3 %016lx", child->rsp3);
regs->rax = 0; log::debug(logs::task, " RSP0 %016lx", child->rsp0);
// Initialize a new empty stack with a fake saved state
// for returning out of syscall_handler_prelude
size_t ret_seg_size = sizeof(uintptr_t) * 8;
child->rsp -= ret_seg_size;
void *this_ret_seg =
reinterpret_cast<void*>(rsp0 - ret_seg_size);
void *child_ret_seg =
reinterpret_cast<void*>(child->rsp);
kutil::memcpy(child_ret_seg, this_ret_seg, ret_seg_size);
child->add_fake_task_return(
reinterpret_cast<uintptr_t>(task_fork_return_thunk));
log::debug(logs::task, " RSP %016lx", child->rsp);
return child->pid; return child->pid;
} }
void * void *
process::setup_kernel_stack(size_t size, uintptr_t orig) process::setup_kernel_stack()
{ {
void *stack0 = kutil::malloc(size); constexpr unsigned null_frame_entries = 2;
constexpr size_t null_frame_size = null_frame_entries * sizeof(uint64_t);
if (orig) void *stack_bottom = g_kernel_heap.allocate(initial_stack_size);
kutil::memcpy(stack0, reinterpret_cast<void*>(orig), size); kutil::memset(stack_bottom, 0, initial_stack_size);
else
kutil::memset(stack0, 0, size);
kernel_stack_size = size; log::debug(logs::memory, "Created kernel stack at %016lx size 0x%lx",
kernel_stack = reinterpret_cast<uintptr_t>(stack0); stack_bottom, initial_stack_size);
return stack0; void *stack_top =
kutil::offset_pointer(stack_bottom,
initial_stack_size - null_frame_size);
uint64_t *null_frame = reinterpret_cast<uint64_t*>(stack_top);
for (unsigned i = 0; i < null_frame_entries; ++i)
null_frame[i] = 0;
kernel_stack_size = initial_stack_size;
kernel_stack = reinterpret_cast<uintptr_t>(stack_bottom);
rsp0 = reinterpret_cast<uintptr_t>(stack_top);
rsp = rsp0;
return stack_top;
}
void
process::add_fake_task_return(uintptr_t rip)
{
rsp -= sizeof(uintptr_t) * 7;
uintptr_t *stack = reinterpret_cast<uintptr_t*>(rsp);
stack[6] = rip; // return rip
stack[5] = rsp0; // rbp
stack[4] = 0xbbbbbbbb; // rbx
stack[3] = 0x12121212; // r12
stack[2] = 0x13131313; // r13
stack[1] = 0x14141414; // r14
stack[0] = 0x15151515; // r15
} }
bool bool

View File

@@ -7,7 +7,8 @@
#include "kutil/linked_list.h" #include "kutil/linked_list.h"
#include "page_manager.h" #include "page_manager.h"
typedef uint32_t pid_t; typedef int32_t pid_t;
struct cpu_state;
enum class process_flags : uint32_t enum class process_flags : uint32_t
@@ -32,9 +33,20 @@ enum class process_wait : uint8_t
receive receive
}; };
/// A process /// A process.
///
struct process struct process
{ {
static const size_t initial_stack_size = 0x1000;
// Fields used by assembly routines go first. If you change any of these,
// be sure to change the assembly definitions in 'tasking.inc'
uintptr_t rsp;
uintptr_t rsp0;
uintptr_t rsp3;
page_table *pml4;
// End of assembly fields
pid_t pid; pid_t pid;
pid_t ppid; pid_t ppid;
@@ -51,17 +63,17 @@ struct process
uint32_t reserved1; uint32_t reserved1;
uintptr_t rsp;
page_table *pml4;
uintptr_t kernel_stack; uintptr_t kernel_stack;
size_t kernel_stack_size; size_t kernel_stack_size;
/// Terminate this process.
/// \arg code The return code to exit with.
void exit(unsigned code);
/// Copy this process. /// Copy this process.
/// \arg in_rsp The RSP of the calling process
/// \returns Returns the child's pid to the parent, and /// \returns Returns the child's pid to the parent, and
/// 0 to the child. /// 0 to the child.
pid_t fork(uint64_t in_rsp); pid_t fork();
/// Unready this process until it gets a signal /// Unready this process until it gets a signal
/// \arg sigmask A bitfield of signals to wake on /// \arg sigmask A bitfield of signals to wake on
@@ -118,13 +130,15 @@ struct process
private: private:
friend class scheduler; friend class scheduler;
/// Set up a new kernel stack for this process, optionally copying the /// Set up a new empty kernel stack for this process. Sets rsp0 on this
/// given stack. Sets the kernel stack on the process object, but also /// process object, but also returns it.
/// returns it. /// \returns The new rsp0 as a pointer
/// \arg size Size of the stack to allocate void * setup_kernel_stack();
/// \arg orig Address of a stack to copy, or 0 for no copying.
/// \returns The address of the new stack as a pointer /// Initialize this process' kenrel stack with a fake return segment for
void * setup_kernel_stack(size_t size, uintptr_t orig); /// returning out of task_switch.
/// \arg rip The rip to return to
void add_fake_task_return(uintptr_t rip);
}; };
using process_list = kutil::linked_list<process>; using process_list = kutil::linked_list<process>;

View File

@@ -1,48 +1,82 @@
%macro push_all_and_segments 0 struc REGS
push rax .r15 resq 1 ; 0x00
push rcx .r14 resq 1 ; 0x08
push rdx .r13 resq 1 ; 0x10
push rbx .r12 resq 1 ; 0x18
push rbp .r11 resq 1 ; 0x20
push rsi .r10 resq 1 ; 0x28
push rdi .r9 resq 1 ; 0x30
.r8 resq 1 ; 0x38
push r8 .rdi resq 1 ; 0x40
push r9 .rsi resq 1 ; 0x48
push r10 .rbp resq 1 ; 0x50
push r11 .rbx resq 1 ; 0x58
push r12 .rdx resq 1 ; 0x60
push r13 .rcx resq 1 ; 0x68
push r14 .rax resq 1 ; 0x70
push r15
mov ax, ds .int resq 1 ; 0x78
push rax .err resq 1 ; 0x80
.rip resq 1 ; 0x88
.cs3 resq 1 ; 0x90
.rflags resq 1 ; 0x98
.rsp3 resq 1 ; 0xa0
.ss3 resq 1 ; 0xa8
endstruc
regs_total_size equ 0xb0
regs_extra_size equ 0x78
%macro push_all 0
sub rsp, regs_extra_size
mov [rsp + REGS.rax], rax
mov [rsp + REGS.rcx], rcx
mov [rsp + REGS.rdx], rdx
mov [rsp + REGS.rbx], rbx
mov [rsp + REGS.rbp], rbp
mov [rsp + REGS.rsi], rsi
mov [rsp + REGS.rdi], rdi
mov [rsp + REGS.r8 ], r8
mov [rsp + REGS.r9 ], r9
mov [rsp + REGS.r10], r10
mov [rsp + REGS.r11], r11
mov [rsp + REGS.r12], r12
mov [rsp + REGS.r13], r13
mov [rsp + REGS.r14], r14
mov [rsp + REGS.r15], r15
%endmacro %endmacro
%macro pop_all_and_segments 0 %macro pop_all 0
pop rax mov rax, [rsp + REGS.rax]
mov ds, ax mov rcx, [rsp + REGS.rcx]
mov es, ax mov rdx, [rsp + REGS.rdx]
mov fs, ax mov rbx, [rsp + REGS.rbx]
mov gs, ax mov rbp, [rsp + REGS.rbp]
mov rsi, [rsp + REGS.rsi]
mov rdi, [rsp + REGS.rdi]
pop r15 mov r8, [rsp + REGS.r8 ]
pop r14 mov r9, [rsp + REGS.r9 ]
pop r13 mov r10, [rsp + REGS.r10]
pop r12 mov r11, [rsp + REGS.r11]
pop r11 mov r12, [rsp + REGS.r12]
pop r10 mov r13, [rsp + REGS.r13]
pop r9 mov r14, [rsp + REGS.r14]
pop r8 mov r15, [rsp + REGS.r15]
pop rdi add rsp, regs_extra_size
pop rsi %endmacro
pop rbp
pop rbx %macro check_swap_gs 0
pop rdx mov rax, [rsp+0x90]
pop rcx and rax, 0x03 ; mask out the RPL
pop rax cmp rax, 0x03
jne %%noswapgs
swapgs
%%noswapgs:
%endmacro %endmacro
; vim: ft=asm ; vim: ft=asm

View File

@@ -1,6 +1,7 @@
#include "apic.h" #include "apic.h"
#include "console.h" #include "console.h"
#include "cpu.h" #include "cpu.h"
#include "debug.h"
#include "gdt.h" #include "gdt.h"
#include "interrupts.h" #include "interrupts.h"
#include "io.h" #include "io.h"
@@ -15,30 +16,33 @@
using memory::initial_stack; using memory::initial_stack;
scheduler scheduler::s_instance(nullptr); scheduler scheduler::s_instance(nullptr, kutil::allocator::invalid);
const int stack_size = 0x1000;
const uint64_t rflags_noint = 0x002; const uint64_t rflags_noint = 0x002;
const uint64_t rflags_int = 0x202; const uint64_t rflags_int = 0x202;
extern "C" { extern "C" {
void ramdisk_process_loader(); void ramdisk_process_loader();
void load_process(const void *image_start, size_t bytes, process *proc, cpu_state state); uintptr_t load_process_image(const void *image_start, size_t bytes, process *proc);
}; };
scheduler::scheduler(lapic *apic) : extern uint64_t idle_stack_end;
scheduler::scheduler(lapic *apic, kutil::allocator &alloc) :
m_apic(apic), m_apic(apic),
m_next_pid(1) m_next_pid(1),
m_process_allocator(alloc)
{ {
auto *idle = m_process_allocator.pop(); auto *idle = m_process_allocator.pop();
uint8_t last_pri = num_priorities - 1; uint8_t last_pri = num_priorities - 1;
// The kernel idle task, also the thread we're in now // The kernel idle task, also the thread we're in now
idle->pid = 0; idle->pid = 0;
idle->ppid = 0; idle->ppid = 0;
idle->priority = last_pri; idle->priority = last_pri;
idle->rsp = 0; // This will get set when we switch away idle->rsp = 0; // This will get set when we switch away
idle->rsp3 = 0; // Never used for the idle task
idle->rsp0 = reinterpret_cast<uintptr_t>(&idle_stack_end);
idle->pml4 = page_manager::get_pml4(); idle->pml4 = page_manager::get_pml4();
idle->quanta = process_quanta; idle->quanta = process_quanta;
idle->flags = idle->flags =
@@ -48,10 +52,13 @@ scheduler::scheduler(lapic *apic) :
m_runlists[last_pri].push_back(idle); m_runlists[last_pri].push_back(idle);
m_current = idle; m_current = idle;
bsp_cpu_data.rsp0 = idle->rsp0;
bsp_cpu_data.tcb = idle;
} }
void uintptr_t
load_process(const void *image_start, size_t bytes, process *proc, cpu_state state) load_process_image(const void *image_start, size_t bytes, process *proc)
{ {
// We're now in the process space for this process, allocate memory for the // We're now in the process space for this process, allocate memory for the
// process code and load it // process code and load it
@@ -61,7 +68,7 @@ load_process(const void *image_start, size_t bytes, process *proc, cpu_state sta
// TODO: Handle bad images gracefully // TODO: Handle bad images gracefully
elf::elf image(image_start, bytes); elf::elf image(image_start, bytes);
kassert(image.valid(), "Invalid ELF passed to load_process"); kassert(image.valid(), "Invalid ELF passed to load_process_image");
const unsigned program_count = image.program_count(); const unsigned program_count = image.program_count();
for (unsigned i = 0; i < program_count; ++i) { for (unsigned i = 0; i < program_count; ++i) {
@@ -102,17 +109,20 @@ load_process(const void *image_start, size_t bytes, process *proc, cpu_state sta
kutil::memcpy(dest, src, header->size); kutil::memcpy(dest, src, header->size);
} }
state.rip = image.entrypoint();
proc->flags &= ~process_flags::loading; proc->flags &= ~process_flags::loading;
log::debug(logs::task, " Loaded! New process rip: %016lx", state.rip); uintptr_t entrypoint = image.entrypoint();
log::debug(logs::task, " Loaded! New process rip: %016lx", entrypoint);
return entrypoint;
} }
process_node * process_node *
scheduler::create_process() scheduler::create_process(pid_t pid)
{ {
kassert(pid <= 0, "Cannot specify a positive pid in create_process");
auto *proc = m_process_allocator.pop(); auto *proc = m_process_allocator.pop();
proc->pid = m_next_pid++; proc->pid = pid ? pid : m_next_pid++;
proc->priority = default_priority; proc->priority = default_priority;
return proc; return proc;
} }
@@ -129,39 +139,28 @@ scheduler::load_process(const char *name, const void *data, size_t size)
uint16_t ss = (4 << 3) | 3; // User SS is GDT entry 4, ring 3 uint16_t ss = (4 << 3) | 3; // User SS is GDT entry 4, ring 3
// Set up the page tables - this also allocates an initial user stack // Set up the page tables - this also allocates an initial user stack
page_table *pml4 = page_manager::get()->create_process_map(); proc->pml4 = page_manager::get()->create_process_map();
// Create a one-page kernel stack space // Create an initial kernel stack space
void *stack0 = proc->setup_kernel_stack(stack_size, 0); void *sp0 = proc->setup_kernel_stack();
uintptr_t *stack = reinterpret_cast<uintptr_t *>(sp0) - 7;
// Stack grows down, point to the end // Pass args to ramdisk_process_loader on the stack
void *sp0 = kutil::offset_pointer(stack0, stack_size); stack[0] = reinterpret_cast<uintptr_t>(data);
stack[1] = reinterpret_cast<uintptr_t>(size);
stack[2] = reinterpret_cast<uintptr_t>(proc);
cpu_state *state = reinterpret_cast<cpu_state *>(sp0) - 1; proc->rsp = reinterpret_cast<uintptr_t>(stack);
proc->add_fake_task_return(
reinterpret_cast<uintptr_t>(ramdisk_process_loader));
// Highest state in the stack is the process' kernel stack for the loader // Arguments for iret - rip will be pushed on before these
// to iret to: stack[3] = cs;
state->ds = state->ss = ss; stack[4] = rflags_int;
state->cs = cs; stack[5] = initial_stack;
state->rflags = rflags_int; stack[6] = ss;
state->rip = 0; // to be filled by the loader
state->user_rsp = initial_stack;
// Next state in the stack is the loader's kernel stack. The scheduler will proc->rsp3 = initial_stack;
// iret to this which will kick off the loading:
cpu_state *loader_state = reinterpret_cast<cpu_state *>(sp0) - 2;
loader_state->ds = loader_state->ss = kss;
loader_state->cs = kcs;
loader_state->rflags = rflags_noint;
loader_state->rip = reinterpret_cast<uint64_t>(ramdisk_process_loader);
loader_state->user_rsp = reinterpret_cast<uint64_t>(state);
loader_state->rax = reinterpret_cast<uint64_t>(data);
loader_state->rbx = size;
proc->rsp = reinterpret_cast<uintptr_t>(loader_state);
proc->pml4 = pml4;
proc->quanta = process_quanta; proc->quanta = process_quanta;
proc->flags = proc->flags =
process_flags::running | process_flags::running |
@@ -170,17 +169,44 @@ scheduler::load_process(const char *name, const void *data, size_t size)
m_runlists[default_priority].push_back(proc); m_runlists[default_priority].push_back(proc);
loader_state->rcx = reinterpret_cast<uint64_t>(proc);
log::debug(logs::task, "Creating process %s: pid %d pri %d", name, proc->pid, proc->priority); log::debug(logs::task, "Creating process %s: pid %d pri %d", name, proc->pid, proc->priority);
log::debug(logs::task, " RSP0 %016lx", state); log::debug(logs::task, " RSP %016lx", proc->rsp);
log::debug(logs::task, " PML4 %016lx", pml4); log::debug(logs::task, " RSP0 %016lx", proc->rsp0);
log::debug(logs::task, " PML4 %016lx", proc->pml4);
}
void
scheduler::create_kernel_task(pid_t pid, void (*task)())
{
auto *proc = create_process(pid);
uint16_t kcs = (1 << 3) | 0; // Kernel CS is GDT entry 1, ring 0
uint16_t kss = (2 << 3) | 0; // Kernel SS is GDT entry 2, ring 0
// Create an initial kernel stack space
proc->setup_kernel_stack();
proc->add_fake_task_return(
reinterpret_cast<uintptr_t>(task));
proc->pml4 = page_manager::get()->get_kernel_pml4();
proc->quanta = process_quanta;
proc->flags =
process_flags::running |
process_flags::ready;
m_runlists[default_priority].push_back(proc);
log::debug(logs::task, "Creating kernel task: pid %d pri %d", proc->pid, proc->priority);
log::debug(logs::task, " RSP0 %016lx", proc->rsp0);
log::debug(logs::task, " RSP %016lx", proc->rsp);
log::debug(logs::task, " PML4 %016lx", proc->pml4);
} }
void void
scheduler::start() scheduler::start()
{ {
log::info(logs::task, "Starting scheduler."); log::info(logs::task, "Starting scheduler.");
wrmsr(msr::ia32_gs_base, reinterpret_cast<uintptr_t>(&bsp_cpu_data));
m_tick_count = m_apic->enable_timer(isr::isrTimer, quantum_micros, false); m_tick_count = m_apic->enable_timer(isr::isrTimer, quantum_micros, false);
} }
@@ -233,16 +259,15 @@ void scheduler::prune(uint64_t now)
} }
} }
uintptr_t void
scheduler::schedule(uintptr_t rsp0) scheduler::schedule()
{ {
// TODO: lol a real clock // TODO: lol a real clock
static uint64_t now = 0; static uint64_t now = 0;
m_current->rsp = rsp0; pid_t lastpid = m_current->pid;
m_runlists[m_current->priority].remove(m_current);
m_runlists[m_current->priority].remove(m_current);
if (m_current->flags && process_flags::ready) { if (m_current->flags && process_flags::ready) {
m_runlists[m_current->priority].push_back(m_current); m_runlists[m_current->priority].push_back(m_current);
} else { } else {
@@ -258,32 +283,24 @@ scheduler::schedule(uintptr_t rsp0)
} }
m_current = m_runlists[pri].pop_front(); m_current = m_runlists[pri].pop_front();
rsp0 = m_current->rsp;
// Set rsp0 to after the end of the about-to-be-popped cpu state if (lastpid != m_current->pid) {
tss_set_stack(0, rsp0 + sizeof(cpu_state)); task_switch(m_current);
wrmsr(msr::ia32_kernel_gs_base, rsp0);
// Swap page tables bool loading = m_current->flags && process_flags::loading;
page_table *pml4 = m_current->pml4; log::debug(logs::task, "Scheduler switched to process %d, priority %d%s.",
page_manager::set_pml4(pml4); m_current->pid, m_current->priority, loading ? " (loading)" : "");
}
bool loading = m_current->flags && process_flags::loading;
log::debug(logs::task, "Scheduler switched to process %d, priority %d%s.",
m_current->pid, m_current->priority, loading ? " (loading)" : "");
return rsp0;
} }
uintptr_t void
scheduler::tick(uintptr_t rsp0) scheduler::tick()
{ {
if (--m_current->quanta == 0) { if (--m_current->quanta == 0) {
m_current->quanta = process_quanta; m_current->quanta = process_quanta;
rsp0 = schedule(rsp0); schedule();
} }
m_apic->reset_timer(m_tick_count); m_apic->reset_timer(m_tick_count);
return rsp0;
} }
process_node * process_node *

View File

@@ -3,6 +3,7 @@
/// The task scheduler and related definitions /// The task scheduler and related definitions
#include <stdint.h> #include <stdint.h>
#include "kutil/allocator.h"
#include "kutil/slab_allocator.h" #include "kutil/slab_allocator.h"
#include "process.h" #include "process.h"
@@ -10,7 +11,9 @@ class lapic;
struct page_table; struct page_table;
struct cpu_state; struct cpu_state;
extern "C" uintptr_t isr_handler(uintptr_t, cpu_state*); extern "C" void isr_handler(cpu_state*);
extern "C" void task_switch(process *next);
extern "C" void task_fork(process *child);
/// The task scheduler /// The task scheduler
@@ -21,14 +24,15 @@ public:
static const uint8_t default_priority = num_priorities / 2; static const uint8_t default_priority = num_priorities / 2;
/// How long the timer quantum is /// How long the timer quantum is
static const uint64_t quantum_micros = 100000; static const uint64_t quantum_micros = 1000;
/// How many quantums a process gets before being rescheduled /// How many quantums a process gets before being rescheduled
static const uint16_t process_quanta = 10; static const uint16_t process_quanta = 100;
/// Constructor. /// Constructor.
/// \arg apic Pointer to the local APIC object /// \arg apic Pointer to the local APIC object
scheduler(lapic *apic); /// \arg alloc Allocator to use for TCBs
scheduler(lapic *apic, kutil::allocator &alloc);
/// Create a new process from a program image in memory. /// Create a new process from a program image in memory.
/// \arg name Name of the program image /// \arg name Name of the program image
@@ -36,14 +40,17 @@ public:
/// \arg size Size of the program image, in bytes /// \arg size Size of the program image, in bytes
void load_process(const char *name, const void *data, size_t size); void load_process(const char *name, const void *data, size_t size);
/// Create a new kernel task
/// \arg pid Pid to use for this task, must be negative
/// \arg proc Function to run as a kernel task
void create_kernel_task(pid_t pid, void (*task)());
/// Start the scheduler working. This may involve starting /// Start the scheduler working. This may involve starting
/// timer interrupts or other preemption methods. /// timer interrupts or other preemption methods.
void start(); void start();
/// Run the scheduler, possibly switching to a new task /// Run the scheduler, possibly switching to a new task
/// \arg rsp0 The stack pointer of the current interrupt handler void schedule();
/// \returns The stack pointer to switch to
uintptr_t schedule(uintptr_t rsp0);
/// Get the current process. /// Get the current process.
/// \returns A pointer to the current process' process struct /// \returns A pointer to the current process' process struct
@@ -60,18 +67,17 @@ public:
private: private:
friend uintptr_t syscall_dispatch(uintptr_t, cpu_state &); friend uintptr_t syscall_dispatch(uintptr_t, cpu_state &);
friend uintptr_t isr_handler(uintptr_t, cpu_state*); friend void isr_handler(cpu_state*);
friend class process; friend class process;
/// Create a new process object. This process will have its pid /// Create a new process object. This process will have its pid
/// set but nothing else. /// set but nothing else.
/// \arg pid The pid to give the process (0 for automatic)
/// \returns The new process object /// \returns The new process object
process_node * create_process(); process_node * create_process(pid_t pid = 0);
/// Handle a timer tick /// Handle a timer tick
/// \arg rsp0 The stack pointer of the current interrupt handler void tick();
/// \returns The stack pointer to switch to
uintptr_t tick(uintptr_t rsp0);
void prune(uint64_t now); void prune(uint64_t now);

View File

@@ -1,16 +1,125 @@
#include "console.h" #include "console.h"
#include "cpu.h" #include "cpu.h"
#include "debug.h" #include "debug.h"
#include "log.h"
#include "msr.h" #include "msr.h"
#include "process.h" #include "process.h"
#include "scheduler.h" #include "scheduler.h"
#include "syscall.h" #include "syscall.h"
extern "C" { extern "C" {
void _halt(); void syscall_invalid(uint64_t call);
void syscall_handler_prelude(); void syscall_handler_prelude();
} }
namespace syscalls {
void send() {}
void receive() {}
} // namespace syscalls
uintptr_t syscall_registry[static_cast<unsigned>(syscall::MAX)];
const char * syscall_names[static_cast<unsigned>(syscall::MAX)];
void
syscall_invalid(uint64_t call)
{
console *cons = console::get();
cons->set_color(9);
cons->printf("\nReceived unknown syscall: %02x\n", call);
const unsigned num_calls =
static_cast<unsigned>(syscall::MAX);
cons->printf(" Known syscalls:\n");
cons->printf(" invalid %016lx\n", syscall_invalid);
for (unsigned i = 0; i < num_calls; ++i) {
const char *name = syscall_names[i];
uintptr_t handler = syscall_registry[i];
if (name)
cons->printf(" %02x %10s %016lx\n", i, name, handler);
}
cons->set_color();
_halt();
}
/*
void
syscall_dispatch(cpu_state *regs)
{
console *cons = console::get();
syscall call = static_cast<syscall>(regs->rax);
auto &s = scheduler::get();
auto *p = s.current();
switch (call) {
case syscall::noop:
break;
case syscall::debug:
cons->set_color(11);
cons->printf("\nProcess %d: Received DEBUG syscall\n", p->pid);
cons->set_color();
print_regs(*regs);
cons->printf("\n Syscall enters: %8d\n", __counter_syscall_enter);
cons->printf(" Syscall sysret: %8d\n", __counter_syscall_sysret);
break;
case syscall::send:
{
pid_t target = regs->rdi;
uintptr_t data = regs->rsi;
cons->set_color(11);
cons->printf("\nProcess %d: Received SEND syscall, target %d, data %016lx\n", p->pid, target, data);
cons->set_color();
if (p->wait_on_send(target))
s.schedule();
}
break;
case syscall::receive:
{
pid_t source = regs->rdi;
uintptr_t data = regs->rsi;
cons->set_color(11);
cons->printf("\nProcess %d: Received RECEIVE syscall, source %d, dat %016lx\n", p->pid, source, data);
cons->set_color();
if (p->wait_on_receive(source))
s.schedule();
}
break;
case syscall::fork:
{
cons->set_color(11);
cons->printf("\nProcess %d: Received FORK syscall\n", p->pid);
cons->set_color();
pid_t pid = p->fork(regs);
cons->printf("\n fork returning %d\n", pid);
regs->rax = pid;
}
break;
default:
cons->set_color(9);
cons->printf("\nReceived unknown syscall: %02x\n", call);
cons->set_color();
_halt();
break;
}
}
*/
void void
syscall_enable() syscall_enable()
{ {
@@ -32,113 +141,20 @@ syscall_enable()
// IA32_FMASK - FLAGS mask inside syscall // IA32_FMASK - FLAGS mask inside syscall
wrmsr(msr::ia32_fmask, 0x200); wrmsr(msr::ia32_fmask, 0x200);
}
uintptr_t static constexpr unsigned num_calls =
syscall_dispatch(uintptr_t return_rsp, cpu_state &regs) static_cast<unsigned>(syscall::MAX);
{
console *cons = console::get();
syscall call = static_cast<syscall>(regs.rax);
auto &s = scheduler::get(); for (unsigned i = 0; i < num_calls; ++i) {
auto *p = s.current(); syscall_registry[i] = reinterpret_cast<uintptr_t>(syscall_invalid);
syscall_names[i] = nullptr;
switch (call) {
case syscall::noop:
break;
case syscall::debug:
cons->set_color(11);
cons->printf("\nProcess %u: Received DEBUG syscall\n", p->pid);
cons->set_color();
print_regs(regs);
break;
case syscall::message:
cons->set_color(11);
cons->printf("\nProcess %u: Received MESSAGE syscall\n", p->pid);
cons->set_color();
break;
case syscall::pause:
{
cons->set_color(11);
auto &s = scheduler::get();
auto *p = s.current();
p->wait_on_signal(-1ull);
cons->printf("\nProcess %u: Received PAUSE syscall\n", p->pid);
cons->set_color();
return_rsp = s.schedule(return_rsp);
}
break;
case syscall::sleep:
{
cons->set_color(11);
cons->printf("\nProcess %u: Received SLEEP syscall\n", p->pid);
cons->printf("Sleeping until %lu\n", regs.rbx);
cons->set_color();
p->wait_on_time(regs.rbx);
return_rsp = s.schedule(return_rsp);
}
break;
case syscall::getpid:
cons->set_color(11);
cons->printf("\nProcess %u: Received GETPID syscall\n", p->pid);
cons->set_color();
regs.rax = p->pid;
break;
case syscall::send:
{
uint32_t target = regs.rdi;
cons->set_color(11);
cons->printf("\nProcess %u: Received SEND syscall, target %u\n", p->pid, target);
cons->set_color();
if (p->wait_on_send(target))
return_rsp = s.schedule(return_rsp);
}
break;
case syscall::receive:
{
uint32_t source = regs.rdi;
cons->set_color(11);
cons->printf("\nProcess %u: Received RECEIVE syscall, source %u\n", p->pid, source);
cons->set_color();
if (p->wait_on_receive(source))
return_rsp = s.schedule(return_rsp);
}
break;
case syscall::fork:
{
cons->set_color(11);
cons->printf("\nProcess %u: Received FORK syscall\n", p->pid);
cons->set_color();
pid_t pid = p->fork(return_rsp);
if (pid == scheduler::get().current()->pid)
pid = 0;
regs.rax = pid;
}
break;
default:
cons->set_color(9);
cons->printf("\nReceived unknown syscall: %02x\n", call);
cons->set_color();
_halt();
break;
} }
return return_rsp; #define SYSCALL(id, name, result, ...) \
syscall_registry[id] = reinterpret_cast<uintptr_t>(syscalls::name); \
syscall_names[id] = #name; \
static_assert( id <= num_calls, "Syscall " #name " has id > syscall::MAX" );
#include "syscalls.inc"
#undef SYSCALL
} }

View File

@@ -6,19 +6,20 @@ struct cpu_state;
enum class syscall : uint64_t enum class syscall : uint64_t
{ {
noop = 0x0000, #define SYSCALL(id, name, result, ...) name = id,
debug = 0x0001, #include "syscalls.inc"
message = 0x0002, #undef SYSCALL
pause = 0x0003,
sleep = 0x0004,
getpid = 0x0005,
send = 0x0006,
receive = 0x0007,
fork = 0x0008,
last_syscall // Maximum syscall id. If you change this, also change
// MAX_SYSCALLS in syscall.s
MAX = 0x40
}; };
void syscall_enable(); void syscall_enable();
uintptr_t syscall_dispatch(uintptr_t, cpu_state &);
namespace syscalls
{
#define SYSCALL(id, name, result, ...) result name (__VA_ARGS__);
#include "syscalls.inc"
#undef SYSCALL
}

View File

@@ -1,24 +1,62 @@
%include "push_all.inc" %include "push_all.inc"
%include "tasking.inc"
; Make sure to keep MAX_SYSCALLS in sync with
; syscall::MAX in syscall.h
MAX_SYSCALLS equ 0x40
extern __counter_syscall_enter
extern __counter_syscall_sysret
extern syscall_registry
extern syscall_invalid
extern syscall_handler
global syscall_handler_prelude global syscall_handler_prelude
global syscall_handler_prelude.return
syscall_handler_prelude: syscall_handler_prelude:
push 0 ; ss, doesn't matter here swapgs
push rsp mov [gs:CPU_DATA.rsp3], rsp
pushf mov rsp, [gs:CPU_DATA.rsp0]
push 0 ; cs, doesn't matter here
push rcx ; user rip
push 0 ; bogus interrupt
push 0 ; bogus errorcode
push_all_and_segments
mov rdi, rsp push rcx
call syscall_handler push rbp
mov rsp, rax mov rbp, rsp
pop_all_and_segments push rbx
add rsp, 16 ; ignore bogus interrupt / error push r11
pop rcx ; user rip push r12
add rsp, 32 ; ignore cs, flags, rsp, ss push r13
push r14
push r15
inc qword [rel __counter_syscall_enter]
cmp rax, MAX_SYSCALLS
jle .ok_syscall
mov rdi, rax
call syscall_invalid
.ok_syscall:
lea r11, [rel syscall_registry]
mov r11, [r11 + rax * 8]
call r11
inc qword [rel __counter_syscall_sysret]
.return:
pop r15
pop r14
pop r13
pop r12
pop r11
pop rbx
pop rbp
pop rcx
mov [gs:CPU_DATA.rsp0], rsp
mov rsp, [gs:CPU_DATA.rsp3]
swapgs
o64 sysret o64 sysret

12
src/kernel/syscalls.inc Normal file
View File

@@ -0,0 +1,12 @@
SYSCALL(0x00, noop, void)
SYSCALL(0x01, exit, void, int64_t)
SYSCALL(0x02, getpid, pid_t)
SYSCALL(0x03, fork, pid_t)
SYSCALL(0x10, message, void, const char *)
SYSCALL(0x20, pause, void)
SYSCALL(0x21, sleep, void, uint64_t)
SYSCALL(0x30, send, void)
SYSCALL(0x31, receive, void)

View File

@@ -0,0 +1,17 @@
#include "log.h"
#include "scheduler.h"
namespace syscalls {
void
exit(int64_t status)
{
auto &s = scheduler::get();
auto *p = s.current();
log::debug(logs::syscall, "Process %d exiting with code %d", p->pid, status);
p->exit(status);
s.schedule();
}
} // namespace syscalls

View File

@@ -0,0 +1,24 @@
#include "log.h"
#include "scheduler.h"
namespace syscalls {
pid_t
fork()
{
auto &s = scheduler::get();
auto *p = s.current();
pid_t ppid = p->pid;
log::debug(logs::syscall, "Process %d calling fork()", ppid);
pid_t pid = p->fork();
p = s.current();
log::debug(logs::syscall, "Process %d's fork: returning %d from process %d", ppid, pid, p->pid);
return pid;
}
} // namespace syscalls

View File

@@ -0,0 +1,14 @@
#include "log.h"
#include "scheduler.h"
namespace syscalls {
pid_t
getpid()
{
auto &s = scheduler::get();
auto *p = s.current();
return p->pid;
}
} // namespace syscalls

View File

@@ -0,0 +1,14 @@
#include "log.h"
#include "scheduler.h"
namespace syscalls {
void
message(const char *message)
{
auto &s = scheduler::get();
auto *p = s.current();
log::info(logs::syscall, "Message[%d]: %s", p->pid, message);
}
} // namespace syscalls

View File

@@ -0,0 +1,14 @@
#include "log.h"
#include "scheduler.h"
namespace syscalls {
void
noop()
{
auto &s = scheduler::get();
auto *p = s.current();
log::debug(logs::syscall, "Process %d called noop syscall.", p->pid);
}
} // namespace syscalls

View File

@@ -0,0 +1,15 @@
#include "log.h"
#include "scheduler.h"
namespace syscalls {
void
pause()
{
auto &s = scheduler::get();
auto *p = s.current();
p->wait_on_signal(-1ull);
s.schedule();
}
} // namespace syscalls

View File

@@ -0,0 +1,17 @@
#include "log.h"
#include "scheduler.h"
namespace syscalls {
void
sleep(uint64_t til)
{
auto &s = scheduler::get();
auto *p = s.current();
log::debug(logs::syscall, "Process %d sleeping until %d", p->pid, til);
p->wait_on_time(til);
s.schedule();
}
} // namespace syscalls

63
src/kernel/task.s Normal file
View File

@@ -0,0 +1,63 @@
%include "tasking.inc"
extern g_tss
global task_switch
task_switch:
push rbp
mov rbp, rsp
; Save the rest of the callee-saved regs
push rbx
push r12
push r13
push r14
push r15
; Update previous task's TCB
mov rax, [gs:CPU_DATA.tcb] ; rax: current task TCB
mov [rax + TCB.rsp], rsp
; Copy off saved user rsp
mov rcx, [gs:CPU_DATA.rsp3] ; rcx: curretn task's saved user rsp
mov [rax + TCB.rsp3], rcx
; Install next task's TCB
mov [gs:CPU_DATA.tcb], rdi ; rdi: next TCB (function param)
mov rsp, [rdi + TCB.rsp] ; next task's stack pointer
mov rax, 0x0000007fffffffff
and rax, [rdi + TCB.pml4] ; rax: next task's pml4 (phys portion of address)
; Update syscall/interrupt rsp
mov rcx, [rdi + TCB.rsp0] ; rcx: top of next task's kernel stack
mov [gs:CPU_DATA.rsp0], rcx
; Update saved user rsp
mov rcx, [rdi + TCB.rsp3] ; rcx: new task's saved user rsp
mov [gs:CPU_DATA.rsp3], rcx
lea rdx, [rel g_tss] ; rdx: address of TSS
mov [rdx + TSS.rsp0], rcx
; check if we need to update CR3
mov rdx, cr3 ; rdx: old CR3
cmp rax, rdx
je .no_cr3
mov cr3, rax
.no_cr3:
pop r15
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
extern syscall_handler_prelude.return
global task_fork_return_thunk
task_fork_return_thunk:
mov rax, 0
jmp syscall_handler_prelude.return

33
src/kernel/tasking.inc Normal file
View File

@@ -0,0 +1,33 @@
struc TCB
.rsp: resq 1
.rsp0: resq 1
.rsp3: resq 1
.pml4: resq 1
endstruc
struc CPU_DATA
.rsp0: resq 1
.rsp3: resq 1
.tcb: resq 1
endstruc
struc TSS
.res0: resd 1
.rsp0: resq 1
.rsp1: resq 1
.rsp2: resq 1
.ist0: resq 1
.ist1: resq 1
.ist2: resq 1
.ist3: resq 1
.ist4: resq 1
.ist5: resq 1
.ist6: resq 1
.ist7: resq 1
.res1: resq 1
.res2: resw 1
.iomap: resw 1
endstruc
; vim: ft=asm

View File

@@ -49,7 +49,7 @@ class disk
public: public:
/// Constructor. /// Constructor.
/// \arg start The start of the initrd in memory /// \arg start The start of the initrd in memory
disk(const void *start); disk(const void *start, kutil::allocator &alloc);
/// Get the vector of files on the disk /// Get the vector of files on the disk
const kutil::vector<file> & files() const { return m_files; } const kutil::vector<file> & files() const { return m_files; }

View File

@@ -23,7 +23,8 @@ file::executable() const {
} }
disk::disk(const void *start) disk::disk(const void *start, kutil::allocator &alloc) :
m_files(alloc)
{ {
auto *header = reinterpret_cast<const disk_header *>(start); auto *header = reinterpret_cast<const disk_header *>(start);
size_t length = header->length; size_t length = header->length;

View File

@@ -0,0 +1,95 @@
#include "kutil/assert.h"
#include "kutil/bip_buffer.h"
namespace kutil {
bip_buffer::bip_buffer() :
m_start_a(0),
m_start_b(0),
m_size_a(0),
m_size_b(0),
m_buffer_size(0),
m_buffer(nullptr)
{}
bip_buffer::bip_buffer(uint8_t *buffer, size_t size) :
m_start_a(0),
m_start_b(0),
m_size_a(0),
m_size_b(0),
m_buffer_size(size),
m_buffer(buffer)
{}
size_t bip_buffer::reserve(size_t size, void **area)
{
if (m_size_r) {
*area = nullptr;
return 0;
}
size_t remaining = 0;
if (m_size_b) {
// If B exists, we're appending there. Get space between
// the end of B and start of A.
remaining = m_start_a - m_start_b - m_size_b;
m_start_r = m_start_b + m_size_b;
} else {
// B doesn't exist, check the space both before and after A.
// If the end of A has enough room for this write, put it there.
remaining = m_buffer_size - m_start_a - m_size_a;
m_start_r = m_start_a + m_size_a;
// Otherwise use the bigger of the areas in front of and after A
if (remaining < size && m_start_a > remaining) {
remaining = m_start_a;
m_start_r = 0;
}
}
if (!remaining) {
*area = nullptr;
return 0;
}
m_size_r = (remaining < size) ? remaining : size;
*area = &m_buffer[m_start_r];
return m_size_r;
}
void bip_buffer::commit(size_t size)
{
kassert(size <= m_size_r, "Tried to commit more than reserved");
if (m_start_r == m_start_a + m_size_a) {
// We were adding to A
m_size_a += size;
} else {
// We were adding to B
kassert(m_start_r == m_start_b + m_size_b, "Bad m_start_r!");
m_size_b += size;
}
m_start_r = m_size_r = 0;
}
size_t bip_buffer::get_block(void **area) const
{
*area = m_size_a ? &m_buffer[m_start_a] : nullptr;
return m_size_a;
}
void bip_buffer::consume(size_t size)
{
kassert(size <= m_size_a, "Consumed more bytes than exist in A");
if (size >= m_size_a) {
m_size_a = m_size_b;
m_start_a = m_start_b;
m_size_b = m_start_b = 0;
} else {
m_size_a -= size;
m_start_a += size;
}
}
} // namespace kutil

View File

@@ -1,159 +0,0 @@
#include <algorithm>
#include "kutil/frame_allocator.h"
namespace kutil {
int
frame_block::compare(const frame_block *rhs) const
{
if (address < rhs->address)
return -1;
else if (address > rhs->address)
return 1;
return 0;
}
frame_block_list
frame_block::consolidate(frame_block_list &list)
{
frame_block_list freed;
for (auto *cur : list) {
auto *next = cur->next();
while ( next &&
cur->flags == next->flags &&
cur->end() == next->address) {
cur->count += next->count;
list.remove(next);
freed.push_back(next);
}
}
return freed;
}
void
frame_block::zero()
{
address = 0;
count = 0;
flags = frame_block_flags::none;
}
void
frame_block::copy(frame_block *other)
{
address = other->address;
count = other->count;
flags = other->flags;
}
frame_allocator::frame_allocator(
frame_block_list cache)
{
m_block_slab.append(cache);
}
void
frame_allocator::init(
frame_block_list free,
frame_block_list used)
{
m_free.append(free);
m_used.append(used);
}
void
frame_allocator::consolidate_blocks()
{
m_block_slab.append(frame_block::consolidate(m_free));
m_block_slab.append(frame_block::consolidate(m_used));
}
size_t
frame_allocator::allocate(size_t count, uintptr_t *address)
{
kassert(!m_free.empty(), "frame_allocator::pop_frames ran out of free frames!");
auto *first = m_free.front();
unsigned n = std::min(count, static_cast<size_t>(first->count));
*address = first->address;
if (count >= first->count) {
m_free.remove(first);
m_used.sorted_insert(first);
} else {
auto *used = m_block_slab.pop();
used->copy(first);
used->count = n;
m_used.sorted_insert(used);
first->address += n * frame_size;
first->count -= n;
}
m_block_slab.append(frame_block::consolidate(m_used));
return n;
}
void
frame_allocator::free(uintptr_t address, size_t count)
{
size_t block_count = 0;
for (auto *block : m_used) {
if (!block->contains(address)) continue;
size_t size = frame_size * count;
uintptr_t end = address + size;
size_t leading = address - block->address;
size_t trailing =
end > block->end() ?
0 : (block->end() - end);
if (leading) {
size_t frames = leading / frame_size;
auto *lead_block = m_block_slab.pop();
lead_block->copy(block);
lead_block->count = frames;
block->count -= frames;
block->address += leading;
m_used.insert_before(block, lead_block);
}
if (trailing) {
size_t frames = trailing / frame_size;
auto *trail_block = m_block_slab.pop();
trail_block->copy(block);
trail_block->count = frames;
trail_block->address += size;
trail_block->address += size;
block->count -= frames;
m_used.insert_after(block, trail_block);
}
address += block->count * frame_size;
m_used.remove(block);
m_free.sorted_insert(block);
++block_count;
}
kassert(block_count, "Couldn't find existing allocated frames to free");
}
} // namespace kutil

View File

@@ -1,12 +1,11 @@
#include <stdint.h> #include <stdint.h>
#include "kutil/assert.h" #include "kutil/assert.h"
#include "kutil/memory.h" #include "kutil/memory.h"
#include "kutil/heap_manager.h" #include "kutil/heap_allocator.h"
namespace kutil { namespace kutil {
struct heap_allocator::mem_header
struct heap_manager::mem_header
{ {
mem_header(mem_header *prev, mem_header *next, uint8_t size) : mem_header(mem_header *prev, mem_header *next, uint8_t size) :
m_prev(prev), m_next(next) m_prev(prev), m_next(next)
@@ -14,34 +13,29 @@ struct heap_manager::mem_header
set_size(size); set_size(size);
} }
inline void set_size(uint8_t size) inline void set_size(uint8_t size) {
{
m_prev = reinterpret_cast<mem_header *>( m_prev = reinterpret_cast<mem_header *>(
reinterpret_cast<uintptr_t>(prev()) | (size & 0x3f)); reinterpret_cast<uintptr_t>(prev()) | (size & 0x3f));
} }
inline void set_used(bool used) inline void set_used(bool used) {
{
m_next = reinterpret_cast<mem_header *>( m_next = reinterpret_cast<mem_header *>(
reinterpret_cast<uintptr_t>(next()) | (used ? 1 : 0)); reinterpret_cast<uintptr_t>(next()) | (used ? 1 : 0));
} }
inline void set_next(mem_header *next) inline void set_next(mem_header *next) {
{
bool u = used(); bool u = used();
m_next = next; m_next = next;
set_used(u); set_used(u);
} }
inline void set_prev(mem_header *prev) inline void set_prev(mem_header *prev) {
{
uint8_t s = size(); uint8_t s = size();
m_prev = prev; m_prev = prev;
set_size(s); set_size(s);
} }
void remove() void remove() {
{
if (next()) next()->set_prev(prev()); if (next()) next()->set_prev(prev());
if (prev()) prev()->set_next(next()); if (prev()) prev()->set_next(next());
set_prev(nullptr); set_prev(nullptr);
@@ -67,25 +61,24 @@ private:
}; };
heap_manager::heap_manager() : heap_allocator::heap_allocator() : m_next(0), m_size(0) {}
m_grow(nullptr)
{
}
heap_manager::heap_manager(grow_callback grow_cb) : heap_allocator::heap_allocator(uintptr_t start, size_t size) :
m_grow(grow_cb) m_next(start), m_size(size)
{ {
kutil::memset(m_free, 0, sizeof(m_free)); kutil::memset(m_free, 0, sizeof(m_free));
grow_memory();
} }
void * void *
heap_manager::allocate(size_t length) heap_allocator::allocate(size_t length)
{ {
size_t total = length + sizeof(mem_header); size_t total = length + sizeof(mem_header);
unsigned size = min_size; unsigned size = min_size;
while (total > (1 << size)) size++; while (total > (1 << size)) size++;
kassert(size <= max_size, "Tried to allocate a block bigger than max_size"); kassert(size <= max_size, "Tried to allocate a block bigger than max_size");
if (size > max_size)
return nullptr;
mem_header *header = pop_free(size); mem_header *header = pop_free(size);
header->set_used(true); header->set_used(true);
@@ -93,18 +86,28 @@ heap_manager::allocate(size_t length)
} }
void void
heap_manager::free(void *p) heap_allocator::free(void *p)
{ {
if (!p) return;
mem_header *header = reinterpret_cast<mem_header *>(p); mem_header *header = reinterpret_cast<mem_header *>(p);
header -= 1; // p points after the header header -= 1; // p points after the header
header->set_used(false); header->set_used(false);
while (header->size() != max_size) { while (header->size() != max_size) {
auto size = header->size();
mem_header *buddy = header->buddy(); mem_header *buddy = header->buddy();
if (buddy->used() || buddy->size() != header->size()) break; if (buddy->used() || buddy->size() != size)
break;
if (get_free(size) == buddy)
get_free(size) = buddy->next();
buddy->remove(); buddy->remove();
header = header->eldest() ? header : buddy; header = header->eldest() ? header : buddy;
header->set_size(header->size() + 1); header->set_size(size + 1);
} }
uint8_t size = header->size(); uint8_t size = header->size();
@@ -115,47 +118,60 @@ heap_manager::free(void *p)
} }
void void
heap_manager::grow_memory() heap_allocator::ensure_block(unsigned size)
{ {
size_t length = (1 << max_size); if (get_free(size) != nullptr)
kassert(m_grow, "Tried to grow heap without a growth callback");
void *next = m_grow(length);
mem_header *block = new (next) mem_header(nullptr, get_free(max_size), max_size);
get_free(max_size) = block;
if (block->next())
block->next()->set_prev(block);
}
void
heap_manager::ensure_block(unsigned size)
{
if (get_free(size) != nullptr) return;
else if (size == max_size) {
grow_memory();
return; return;
if (size == max_size) {
size_t bytes = (1 << max_size);
if (bytes <= m_size) {
mem_header *next = reinterpret_cast<mem_header *>(m_next);
new (next) mem_header(nullptr, nullptr, size);
get_free(size) = next;
m_next += bytes;
m_size -= bytes;
}
} else {
mem_header *orig = pop_free(size + 1);
if (orig) {
mem_header *next = kutil::offset_pointer(orig, 1 << size);
new (next) mem_header(orig, nullptr, size);
orig->set_next(next);
orig->set_size(size);
get_free(size) = orig;
}
} }
mem_header *orig = pop_free(size + 1);
mem_header *next = kutil::offset_pointer(orig, 1 << size);
new (next) mem_header(orig, nullptr, size);
orig->set_next(next);
orig->set_size(size);
get_free(size) = orig;
} }
heap_manager::mem_header * heap_allocator::mem_header *
heap_manager::pop_free(unsigned size) heap_allocator::pop_free(unsigned size)
{ {
ensure_block(size); ensure_block(size);
mem_header *block = get_free(size); mem_header *block = get_free(size);
get_free(size) = block->next(); if (block) {
get_free(size) = block->next();
block->remove(); block->remove();
}
return block; return block;
} }
class invalid_allocator :
public allocator
{
public:
virtual void * allocate(size_t) override {
kassert(false, "Attempting to allocate from allocator::invalid");
return nullptr;
}
virtual void free(void *) override {
kassert(false, "Attempting to free from allocator::invalid");
}
} _invalid_allocator;
allocator &allocator::invalid = _invalid_allocator;
} // namespace kutil } // namespace kutil

View File

@@ -0,0 +1,31 @@
#pragma once
/// \file allocator.h
/// Allocator interface
#include <stdint.h>
#include "kernel_memory.h"
namespace kutil {
class allocator
{
public:
/// Allocate memory.
/// \arg length The amount of memory to allocate, in bytes
/// \returns A pointer to the allocated memory, or nullptr if
/// allocation failed.
virtual void * allocate(size_t size) = 0;
/// Free a previous allocation.
/// \arg p A pointer previously retuned by allocate()
virtual void free(void *p) = 0;
template <typename T>
inline T * allocate(unsigned count) {
return reinterpret_cast<T*>(allocate(count * sizeof(T)));
}
static allocator &invalid;
};
} // namespace kutil

View File

@@ -0,0 +1,59 @@
#pragma once
/// \file bip_buffer.h
/// A Bip Buffer (bipartite circular buffer). For more on the Bip Buffer structure, see
/// https://www.codeproject.com/Articles/3479/The-Bip-Buffer-The-Circular-Buffer-with-a-Twist
#include <stddef.h>
#include <stdint.h>
namespace kutil {
class bip_buffer
{
public:
/// Default constructor. Creates a zero-size buffer.
bip_buffer();
/// Constructor.
bip_buffer(uint8_t *buffer, size_t size);
/// Reserve an area of buffer for a write.
/// \arg size Requested size, in bytes
/// \arg area [out] Pointer to returned area
/// \returns Size of returned area, in bytes, or 0 on failure
size_t reserve(size_t size, void **area);
/// Commit a pending write started by reserve()
/// \arg size Amount of data used, in bytes
void commit(size_t size);
/// Get a pointer to a block of data in the buffer.
/// \arg area [out] Pointer to the retuned area
/// \returns Size of the returned area, in bytes
size_t get_block(void **area) const;
/// Mark a number of bytes as consumed, freeing buffer space
/// \arg size Number of bytes to consume
void consume(size_t size);
/// Get total amount of data in the buffer.
/// \returns Number of bytes committed to the buffer
inline size_t size() const { return m_size_a + m_size_b; }
/// Get total amount of free buffer remaining
/// \returns Number of bytes of buffer that are free
inline size_t free_space() const { return m_buffer_size - size(); }
private:
size_t m_start_a;
size_t m_start_b;
size_t m_start_r;
size_t m_size_a;
size_t m_size_b;
size_t m_size_r;
const size_t m_buffer_size;
uint8_t * const m_buffer;
};
} // namespace kutil

View File

@@ -3,6 +3,7 @@
/// Helper base class for buddy allocators with external node storage. /// Helper base class for buddy allocators with external node storage.
#include <stdint.h> #include <stdint.h>
#include <utility>
#include "kutil/assert.h" #include "kutil/assert.h"
#include "kutil/linked_list.h" #include "kutil/linked_list.h"
#include "kutil/slab_allocator.h" #include "kutil/slab_allocator.h"
@@ -25,15 +26,21 @@ public:
static const size_t min_alloc = (1 << size_min); static const size_t min_alloc = (1 << size_min);
static const size_t max_alloc = (1 << size_max); static const size_t max_alloc = (1 << size_max);
/// Constructor. /// Default constructor creates an invalid object.
buddy_allocator() {} buddy_allocator() : m_alloc(allocator::invalid) {}
/// Constructor with an initial cache of region structs from bootstrapped /// Constructor.
/// memory. /// \arg alloc Allocator to use for region nodes
/// \arg cache List of pre-allocated ununused region_type structures buddy_allocator(allocator &alloc) : m_alloc(alloc) {}
buddy_allocator(region_list cache)
/// Move-like constructor. Takes ownership of existing regions.
buddy_allocator(buddy_allocator &&other, allocator &alloc) :
m_alloc(alloc)
{ {
m_alloc.append(cache); for (unsigned i = 0; i < buckets; ++i) {
m_free[i] = std::move(other.m_free[i]);
m_used[i] = std::move(other.m_used[i]);
}
} }
/// Add address space to be managed. /// Add address space to be managed.
@@ -173,6 +180,23 @@ public:
} }
} }
/// Check if an allocation exists
/// \arg addr Address within the managed space
/// \returns True if the address is in a region currently allocated
bool contains(uintptr_t addr)
{
for (unsigned i = size_max; i >= size_min; --i) {
for (auto *r : used_bucket(i)) {
if (r->contains(addr))
return true;
else if (r->address < addr)
break;
}
}
return false;
}
protected: protected:
/// Split a region of the given size into two smaller regions, returning /// Split a region of the given size into two smaller regions, returning
/// the new latter half /// the new latter half
@@ -266,6 +290,8 @@ struct buddy_region
inline uintptr_t end() const { return address + (1ull << size); } inline uintptr_t end() const { return address + (1ull << size); }
inline uintptr_t half() const { return address + (1ull << (size - 1)); } inline uintptr_t half() const { return address + (1ull << (size - 1)); }
inline bool contains(uintptr_t p) const { return p >= address && p < end(); }
inline uintptr_t buddy() const { return address ^ (1ull << size); } inline uintptr_t buddy() const { return address ^ (1ull << size); }
inline bool elder() const { return address < buddy(); } inline bool elder() const { return address < buddy(); }

View File

@@ -0,0 +1,38 @@
#pragma once
/// \file constexpr_hash.h
/// A complile-time hashing function
#include <stdint.h>
#include <stddef.h>
namespace kutil {
constexpr static const uint8_t pearson_hash_table[256] = {
0x76,0x07,0xbe,0x47,0xcf,0x41,0x0a,0xe8,0x01,0x5c,0x9f,0xc5,0x24,0x63,0x9a,0x85,
0x39,0x2c,0xe2,0x34,0xb9,0xf2,0xae,0x40,0x10,0x90,0x94,0xd1,0x98,0x2d,0x16,0xfd,
0xc6,0x48,0x0d,0xce,0x74,0x43,0x28,0xf9,0x61,0x12,0xd0,0xcd,0xd8,0xd7,0xa8,0x78,
0x73,0x70,0xcc,0x1e,0x17,0xa7,0x87,0x38,0x68,0x91,0xc1,0x04,0x3f,0xf5,0xde,0xa3,
0x8a,0xe5,0x9b,0xec,0x97,0xd5,0x71,0x4a,0x20,0xca,0xc8,0xc4,0x83,0x53,0xe7,0x7b,
0x64,0x31,0x06,0xe0,0x7a,0xb6,0x52,0x8c,0xba,0x58,0xcb,0xb5,0x37,0x51,0x59,0xa1,
0x11,0xe3,0x5a,0xdb,0xe1,0x6d,0x46,0x62,0xaf,0xbd,0x57,0xb8,0x0e,0xf4,0xdd,0xa6,
0x45,0xf8,0x35,0x42,0x56,0xdf,0xad,0x80,0xb2,0x0b,0x5b,0xd4,0x86,0xb3,0xf0,0xc9,
0x3c,0xa5,0xc0,0x8e,0x55,0x77,0xeb,0x36,0x79,0xab,0x4c,0x25,0xed,0xa9,0x75,0x8f,
0xee,0xc2,0x72,0x8b,0x60,0x2a,0xfa,0x32,0xe9,0xda,0x03,0x1b,0x27,0x69,0x18,0x9e,
0x88,0x96,0x54,0x81,0x30,0x22,0x7c,0x4f,0xc7,0xef,0x5d,0xa4,0x67,0x44,0xc3,0x99,
0xbb,0xd3,0x8d,0x65,0xb1,0x82,0x09,0x1a,0x13,0xd9,0x9c,0x4d,0xb0,0xfc,0xac,0xbc,
0x6a,0x29,0x95,0x19,0x92,0xaa,0x49,0x7d,0x3b,0xfb,0x50,0xb7,0xf3,0x5e,0x3e,0x6b,
0x3a,0x14,0x2b,0xb4,0xfe,0xe6,0x93,0x23,0xd6,0x1f,0xd2,0x0c,0x1d,0x9d,0x6c,0x66,
0x1c,0x89,0xbf,0xf6,0xff,0x6f,0x84,0x6e,0x2e,0xea,0x21,0xf7,0x7f,0x33,0xf1,0xe4,
0x3d,0x0f,0x05,0x08,0x4e,0xa2,0xa0,0x2f,0xdc,0x00,0x5f,0x15,0x7e,0x02,0x4b,0x26
};
constexpr inline uint8_t pearson_hash_8(const char *s, uint8_t inv) {
return (*s) ? pearson_hash_8(s + 1, pearson_hash_table[inv ^ *s]) : inv;
}
} // namespace kutil
constexpr inline uint8_t operator "" _h (const char *s, size_t len) {
return kutil::pearson_hash_8(s, static_cast<uint8_t>(len & 0xff));
}

View File

@@ -1,122 +0,0 @@
#pragma once
/// \file frame_allocator.h
/// Allocator for physical memory frames
#include <stdint.h>
#include "kutil/enum_bitfields.h"
#include "kutil/linked_list.h"
#include "kutil/slab_allocator.h"
namespace kutil {
struct frame_block;
using frame_block_list = kutil::linked_list<frame_block>;
using frame_block_slab = kutil::slab_allocator<frame_block>;
/// Allocator for physical memory frames
class frame_allocator
{
public:
/// Size of a single page frame.
static const size_t frame_size = 0x1000;
/// Default constructor
frame_allocator() = default;
/// Constructor with a provided initial frame_block cache.
/// \arg cache List of pre-allocated but unused frame_block structures
frame_allocator(frame_block_list cache);
/// Initialize the frame allocator from bootstraped memory.
/// \arg free List of free blocks
/// \arg used List of currently used blocks
void init(
frame_block_list free,
frame_block_list used);
/// Get free frames from the free list. Only frames from the first free block
/// are returned, so the number may be less than requested, but they will
/// be contiguous.
/// \arg count The maximum number of frames to get
/// \arg address [out] The physical address of the first frame
/// \returns The number of frames retrieved
size_t allocate(size_t count, uintptr_t *address);
/// Free previously allocated frames.
/// \arg address The physical address of the first frame to free
/// \arg count The number of frames to be freed
void free(uintptr_t address, size_t count);
/// Consolidate the free and used block lists. Return freed blocks
/// to the cache.
void consolidate_blocks();
private:
frame_block_list m_free; ///< Free frames list
frame_block_list m_used; ///< In-use frames list
frame_block_slab m_block_slab; ///< frame_block slab allocator
frame_allocator(const frame_allocator &) = delete;
};
/// Flags used by `frame_block`.
enum class frame_block_flags : uint32_t
{
none = 0x0000,
mmio = 0x0001, ///< Memory is a MMIO region
nonvolatile = 0x0002, ///< Memory is non-volatile storage
pending_free = 0x0020, ///< Memory should be freed
acpi_wait = 0x0040, ///< Memory should be freed after ACPI init
permanent = 0x0080, ///< Memory is permanently unusable
// The following are used only during the memory bootstraping
// process, and tell the page manager where to initially map
// the given block.
map_ident = 0x0100, ///< Identity map
map_kernel = 0x0200, ///< Map into normal kernel space
map_offset = 0x0400, ///< Map into offset kernel space
map_mask = 0x0700, ///< Mask of all map_* values
};
} // namespace kutil
IS_BITFIELD(kutil::frame_block_flags);
namespace kutil {
/// A block of contiguous frames. Each `frame_block` represents contiguous
/// physical frames with the same attributes.
struct frame_block
{
uintptr_t address;
uint32_t count;
frame_block_flags flags;
inline bool has_flag(frame_block_flags f) const { return bitfield_has(flags, f); }
inline uintptr_t end() const { return address + (count * frame_allocator::frame_size); }
inline bool contains(uintptr_t addr) const { return addr >= address && addr < end(); }
/// Helper to zero out a block and optionally set the next pointer.
void zero();
/// Helper to copy a bock from another block
/// \arg other The block to copy from
void copy(frame_block *other);
/// Compare two blocks by address.
/// \arg rhs The right-hand comparator
/// \returns <0 if this is sorts earlier, >0 if this sorts later, 0 for equal
int compare(const frame_block *rhs) const;
/// Traverse the list, joining adjacent blocks where possible.
/// \arg list The list to consolidate
/// \returns A linked list of freed frame_block structures.
static frame_block_list consolidate(frame_block_list &list);
};
} // namespace kutil

View File

@@ -1,49 +1,45 @@
#pragma once #pragma once
/// \file heap_manager.h /// \file heap_allocator.h
/// A buddy allocator and related definitions. /// A buddy allocator for a memory heap
#include <stddef.h> #include <stddef.h>
#include "kutil/allocator.h"
namespace kutil { namespace kutil {
/// Manager for allocation of heap memory. /// Allocator for a given heap range
class heap_manager class heap_allocator :
public allocator
{ {
public: public:
/// Callback signature for growth function. Memory returned does not need /// Default constructor creates a valid but empty heap.
/// to be contiguous, but needs to be alined to the length requested. heap_allocator();
using grow_callback = void * (*)(size_t length);
/// Default constructor. Creates an invalid manager. /// Constructor. The given memory area must already have been reserved.
heap_manager(); /// \arg start Starting address of the heap
/// \arg size Size of the heap in bytes
/// Constructor. heap_allocator(uintptr_t start, size_t size);
/// \arg grow_cb Function pointer to grow the heap size
heap_manager(grow_callback grow_cb);
/// Allocate memory from the area managed. /// Allocate memory from the area managed.
/// \arg length The amount of memory to allocate, in bytes /// \arg length The amount of memory to allocate, in bytes
/// \returns A pointer to the allocated memory, or nullptr if /// \returns A pointer to the allocated memory, or nullptr if
/// allocation failed. /// allocation failed.
void * allocate(size_t length); virtual void * allocate(size_t length) override;
/// Free a previous allocation. /// Free a previous allocation.
/// \arg p A pointer previously retuned by allocate() /// \arg p A pointer previously retuned by allocate()
void free(void *p); virtual void free(void *p) override;
/// Minimum block size is (2^min_size). Must be at least 6. /// Minimum block size is (2^min_size). Must be at least 6.
static const unsigned min_size = 6; static const unsigned min_size = 6;
/// Maximum block size is (2^max_size). Must be less than 64. /// Maximum block size is (2^max_size). Must be less than 64.
static const unsigned max_size = 16; static const unsigned max_size = 22;
protected: protected:
class mem_header; class mem_header;
/// Expand the size of memory
void grow_memory();
/// Ensure there is a block of a given size, recursively splitting /// Ensure there is a block of a given size, recursively splitting
/// \arg size Size category of the block we want /// \arg size Size category of the block we want
void ensure_block(unsigned size); void ensure_block(unsigned size);
@@ -58,11 +54,11 @@ protected:
/// \returns A detached block of the given size /// \returns A detached block of the given size
mem_header * pop_free(unsigned size); mem_header * pop_free(unsigned size);
uintptr_t m_next;
size_t m_size;
mem_header *m_free[max_size - min_size + 1]; mem_header *m_free[max_size - min_size + 1];
grow_callback m_grow; heap_allocator(const heap_allocator &) = delete;
heap_manager(const heap_manager &) = delete;
}; };
} // namespace kutil } // namespace kutil

View File

@@ -116,27 +116,47 @@ public:
/// Constructor. Creates an empty list. /// Constructor. Creates an empty list.
linked_list() : linked_list() :
m_head(nullptr), m_head(nullptr),
m_tail(nullptr) m_tail(nullptr),
m_count(0)
{} {}
/// Move constructor. Takes ownership of list elements. /// Move constructor. Takes ownership of list elements.
linked_list(linked_list<T> &&other) : linked_list(linked_list<T> &&other) :
m_head(other.m_head), m_head(other.m_head),
m_tail(other.m_tail) m_tail(other.m_tail),
m_count(other.m_count)
{ {
other.m_head = other.m_tail = nullptr; other.m_head = other.m_tail = nullptr;
other.m_count = 0;
}
/// Assignment operator. Takes ownership of list elements.
/// Destructive towards current data!
linked_list & operator=(linked_list &&other)
{
m_head = other.m_head;
m_tail = other.m_tail;
m_count = other.m_count;
other.m_head = other.m_tail = nullptr;
other.m_count = 0;
return *this;
} }
/// Check if the list is empty. /// Check if the list is empty.
/// \returns true if the list is empty /// \returns true if the list is empty
bool empty() const { return m_head == nullptr; } bool empty() const { return m_head == nullptr; }
/// Get the cached length of the list.
/// \returns The number of entries in the list.
size_t length() const { return m_count; }
/// Count the items in the list. /// Count the items in the list.
/// \returns The number of entries in the list. /// \returns The number of entries in the list.
size_t length() const size_t count_length()
{ {
size_t len = 0; size_t len = 0;
for (item_type *cur = m_head; cur; cur = cur->m_next) ++len; for (item_type *cur = m_head; cur; cur = cur->m_next) ++len;
m_count = len;
return len; return len;
} }
@@ -164,6 +184,8 @@ public:
item->m_prev = nullptr; item->m_prev = nullptr;
m_head = item; m_head = item;
} }
m_count += 1;
} }
/// Append an item to the end of this list. /// Append an item to the end of this list.
@@ -182,6 +204,8 @@ public:
item->m_next = nullptr; item->m_next = nullptr;
m_tail = item; m_tail = item;
} }
m_count += 1;
} }
/// Remove an item from the front of this list. /// Remove an item from the front of this list.
@@ -217,6 +241,8 @@ public:
m_tail = list.m_tail; m_tail = list.m_tail;
} }
m_count += list.m_count;
list.m_count = 0;
list.m_head = list.m_tail = nullptr; list.m_head = list.m_tail = nullptr;
} }
@@ -235,6 +261,8 @@ public:
m_tail = list.m_tail; m_tail = list.m_tail;
} }
m_count += list.m_count;
list.m_count = 0;
list.m_head = list.m_tail = nullptr; list.m_head = list.m_tail = nullptr;
} }
@@ -248,6 +276,7 @@ public:
if (item == m_tail) if (item == m_tail)
m_tail = item->m_prev; m_tail = item->m_prev;
item->remove(); item->remove();
m_count -= 1;
} }
/// Inserts an item into the list before another given item. /// Inserts an item into the list before another given item.
@@ -257,12 +286,14 @@ public:
{ {
if (!item) return; if (!item) return;
if (!existing) if (!existing) {
push_back(item); push_back(item);
else if (existing == m_head) } else if (existing == m_head) {
push_front(item); push_front(item);
else } else {
existing->insert_before(item); existing->insert_before(item);
m_count += 1;
}
} }
/// Inserts an item into the list after another given item. /// Inserts an item into the list after another given item.
@@ -272,12 +303,14 @@ public:
{ {
if (!item) return; if (!item) return;
if (!existing) if (!existing) {
push_front(item); push_front(item);
else if (existing == m_tail) } else if (existing == m_tail) {
push_back(item); push_back(item);
else } else {
existing->insert_after(item); existing->insert_after(item);
m_count += 1;
}
} }
/// Insert an item into the list in a sorted position. Depends on T /// Insert an item into the list in a sorted position. Depends on T
@@ -309,6 +342,7 @@ public:
private: private:
item_type *m_head; item_type *m_head;
item_type *m_tail; item_type *m_tail;
size_t m_count;
}; };

View File

@@ -0,0 +1,118 @@
#pragma once
/// \file logger.h
/// Kernel logging facility.
#include <stdarg.h>
#include <stdint.h>
#include "kutil/bip_buffer.h"
namespace kutil {
namespace log {
using area_t = uint8_t;
enum class level : uint8_t {
none, debug, info, warn, error, fatal, max
};
class logger
{
public:
/// Default constructor. Creates a logger without a backing store.
logger();
/// Constructor. Logs are written to the given buffer.
logger(uint8_t *buffer, size_t size);
/// Register a log area for future use.
/// \arg area The key for the new area
/// \arg name The area name
/// \arg verbosity What level of logs to print for this area
void register_area(area_t area, const char *name, level verbosity);
/// Callback type for immediate-mode logging
typedef void (*immediate)(area_t, level, const char *);
/// Register an immediate-mode log callback
inline void set_immediate(immediate cb) { m_immediate = cb; }
/// Get the default logger.
inline logger & get() { return *s_log; }
/// Get the registered name for a given area
inline const char * area_name(area_t area) const { return m_names[area]; }
/// Get the name of a level
inline const char * level_name(level l) const { return s_level_names[static_cast<unsigned>(l)]; }
/// Write to the log
/// \arg severity The severity of the message
/// \arg area The log area to write to
/// \arg fmt A printf-like format string
inline void log(level severity, area_t area, const char *fmt, ...)
{
level limit = get_level(area);
if (limit == level::none || severity < limit)
return;
va_list args;
va_start(args, fmt);
output(severity, area, fmt, args);
va_end(args);
}
struct entry
{
uint8_t bytes;
area_t area;
level severity;
uint8_t sequence;
char message[0];
};
/// Get the next log entry from the buffer
/// \arg buffer The buffer to copy the log message into
/// \arg size Size of the passed-in buffer, in bytes
/// \returns Number of bytes copied into the buffer
size_t get_entry(void *buffer, size_t size);
private:
friend void debug(area_t area, const char *fmt, ...);
friend void info (area_t area, const char *fmt, ...);
friend void warn (area_t area, const char *fmt, ...);
friend void error(area_t area, const char *fmt, ...);
friend void fatal(area_t area, const char *fmt, ...);
void output(level severity, area_t area, const char *fmt, va_list args);
void set_level(area_t area, level l);
level get_level(area_t area);
static const unsigned num_areas = 1 << (sizeof(area_t) * 8);
uint8_t m_levels[num_areas / 2];
const char *m_names[num_areas];
immediate m_immediate;
uint8_t m_sequence;
kutil::bip_buffer m_buffer;
static logger *s_log;
static const char *s_level_names[static_cast<unsigned>(level::max)];
};
void debug(area_t area, const char *fmt, ...);
void info (area_t area, const char *fmt, ...);
void warn (area_t area, const char *fmt, ...);
void error(area_t area, const char *fmt, ...);
void fatal(area_t area, const char *fmt, ...);
} // namespace log
namespace logs {
#define LOG(name, lvl) extern log::area_t name;
#include "log_areas.inc"
#undef LOG
} // namespace logs
} // namespace kutil

View File

@@ -9,15 +9,6 @@ void * operator new (size_t, void *p) noexcept;
namespace kutil { namespace kutil {
/// Allocate memory.
/// \arg n The number of bytes to allocate
/// \returns The allocated memory
void * malloc(size_t n);
/// Free memory allocated by malloc().
/// \arg p A pointer previously returned by malloc()
void free(void *p);
/// Fill memory with the given value. /// Fill memory with the given value.
/// \arg p The beginning of the memory area to fill /// \arg p The beginning of the memory area to fill
/// \arg v The byte value to fill memory with /// \arg v The byte value to fill memory with
@@ -67,14 +58,4 @@ inline T* mask_pointer(T *p, uintptr_t mask)
/// \arg off An optional offset into the region /// \arg off An optional offset into the region
uint8_t checksum(const void *p, size_t len, size_t off = 0); uint8_t checksum(const void *p, size_t len, size_t off = 0);
class heap_manager;
namespace setup {
/// Set the heap that malloc() / free() will use.
/// \arg mm The heap manager for the heap to use.
void set_heap(heap_manager *mm);
} // namespace kutil::setup
} // namespace kutil } // namespace kutil

View File

@@ -0,0 +1,96 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2018, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
// embedded systems with a very limited resources.
// Use this instead of bloated standard/newlib printf.
// These routines are thread safe and reentrant.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _PRINTF_H_
#define _PRINTF_H_
#include <stdarg.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Tiny printf implementation
* You have to implement _putchar if you use printf()
* To avoid conflicts with the regular printf() API it is overridden by macro defines
* and internal underscore-appended functions like printf_() are used
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
#define printf printf_
int printf_(const char* format, ...);
/**
* Tiny sprintf implementation
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
* \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
* \param format A string that specifies the format of the output
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
*/
#define sprintf sprintf_
int sprintf_(char* buffer, const char* format, ...);
/**
* Tiny snprintf/vsnprintf implementation
* \param buffer A pointer to the buffer where to store the formatted string
* \param count The maximum number of characters to store in the buffer, including a terminating null character
* \param format A string that specifies the format of the output
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
* If the formatted string is truncated the buffer size (count) is returned
*/
#define snprintf snprintf_
#define vsnprintf vsnprintf_
int snprintf_(char* buffer, size_t count, const char* format, ...);
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
/**
* printf with output function
* You may use this as dynamic alternative to printf() with its fixed _putchar() output
* \param out An output function which takes one character and an argument pointer
* \param arg An argument pointer for user data passed to output function
* \param format A string that specifies the format of the output
* \return The number of characters that are sent to the output function, not counting the terminating null character
*/
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
#ifdef __cplusplus
}
#endif
#endif // _PRINTF_H_

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
/// \file slab_allocator.h /// \file slab_allocator.h
/// A slab allocator and related definitions /// A slab allocator and related definitions
#include "kutil/allocator.h"
#include "kutil/assert.h" #include "kutil/assert.h"
#include "kutil/linked_list.h" #include "kutil/linked_list.h"
#include "kutil/memory.h" #include "kutil/memory.h"
@@ -9,7 +10,7 @@ namespace kutil {
/// A slab allocator for small structures kept in a linked list /// A slab allocator for small structures kept in a linked list
template <typename T, typename Alloc = void * (*)(size_t)> template <typename T, size_t N = memory::frame_size>
class slab_allocator : class slab_allocator :
public linked_list<T> public linked_list<T>
{ {
@@ -19,8 +20,7 @@ public:
/// Default constructor. /// Default constructor.
/// \arg chunk_size The size of chunk to allocate, in bytes. 0 means default. /// \arg chunk_size The size of chunk to allocate, in bytes. 0 means default.
/// \arg alloc The allocator to use to allocate chunks. Defaults to malloc(). /// \arg alloc The allocator to use to allocate chunks. Defaults to malloc().
slab_allocator(size_t chunk_size = 0, Alloc alloc = malloc) : slab_allocator(allocator &alloc) :
m_chunk_size(chunk_size),
m_alloc(alloc) m_alloc(alloc)
{ {
} }
@@ -45,18 +45,16 @@ public:
void allocate() void allocate()
{ {
size_t size = m_chunk_size ? m_chunk_size : 10 * sizeof(item_type); constexpr unsigned count = N / sizeof(item_type);
void *memory = m_alloc(size);
size_t count = size / sizeof(item_type);
void *memory = m_alloc.allocate(N);
item_type *items = reinterpret_cast<item_type *>(memory); item_type *items = reinterpret_cast<item_type *>(memory);
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
this->push_back(&items[i]); this->push_back(&items[i]);
} }
private: private:
size_t m_chunk_size; allocator& m_alloc;
Alloc m_alloc;
}; };
} // namespace kutil } // namespace kutil

View File

@@ -4,6 +4,7 @@
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
#include "kutil/allocator.h"
#include "kutil/memory.h" #include "kutil/memory.h"
namespace kutil { namespace kutil {
@@ -14,18 +15,20 @@ class vector
{ {
public: public:
/// Default constructor. Creates an empty vector with no capacity. /// Default constructor. Creates an empty vector with no capacity.
vector() : vector(kutil::allocator &alloc = allocator::invalid) :
m_size(0), m_size(0),
m_capacity(0), m_capacity(0),
m_elements(nullptr) m_elements(nullptr),
m_alloc(alloc)
{} {}
/// Constructor. Creates an empty array with capacity. /// Constructor. Creates an empty array with capacity.
/// \arg capacity Initial capacity to allocate /// \arg capacity Initial capacity to allocate
vector(size_t capacity) : vector(size_t capacity, allocator &alloc) :
m_size(0), m_size(0),
m_capacity(0), m_capacity(0),
m_elements(nullptr) m_elements(nullptr),
m_alloc(alloc)
{ {
set_capacity(capacity); set_capacity(capacity);
} }
@@ -34,7 +37,8 @@ public:
vector(const vector& other) : vector(const vector& other) :
m_size(0), m_size(0),
m_capacity(0), m_capacity(0),
m_elements(nullptr) m_elements(nullptr),
m_alloc(other.m_alloc)
{ {
set_capacity(other.m_capacity); set_capacity(other.m_capacity);
kutil::memcpy(m_elements, other.m_elements, other.m_size * sizeof(T)); kutil::memcpy(m_elements, other.m_elements, other.m_size * sizeof(T));
@@ -45,7 +49,8 @@ public:
vector(vector&& other) : vector(vector&& other) :
m_size(other.m_size), m_size(other.m_size),
m_capacity(other.m_capacity), m_capacity(other.m_capacity),
m_elements(other.m_elements) m_elements(other.m_elements),
m_alloc(other.m_alloc)
{ {
other.m_size = 0; other.m_size = 0;
other.m_capacity = 0; other.m_capacity = 0;
@@ -142,7 +147,7 @@ public:
/// \arg capacity Number of elements to allocate /// \arg capacity Number of elements to allocate
void set_capacity(size_t capacity) void set_capacity(size_t capacity)
{ {
T *new_array = reinterpret_cast<T *>(malloc(capacity * sizeof(T))); T *new_array = m_alloc.allocate<T>(capacity);
size_t size = std::min(capacity, m_size); size_t size = std::min(capacity, m_size);
kutil::memcpy(new_array, m_elements, size * sizeof(T)); kutil::memcpy(new_array, m_elements, size * sizeof(T));
@@ -151,7 +156,7 @@ public:
m_size = size; m_size = size;
m_capacity = capacity; m_capacity = capacity;
delete [] m_elements; m_alloc.free(m_elements);
m_elements = new_array; m_elements = new_array;
} }
@@ -159,6 +164,7 @@ private:
size_t m_size; size_t m_size;
size_t m_capacity; size_t m_capacity;
T *m_elements; T *m_elements;
allocator &m_alloc;
}; };
} // namespace kutil } // namespace kutil

View File

@@ -0,0 +1,165 @@
#include "kutil/assert.h"
#include "kutil/constexpr_hash.h"
#include "kutil/logger.h"
#include "kutil/memory.h"
#include "kutil/printf.h"
namespace kutil {
namespace logs {
#define LOG(name, lvl) \
log::area_t name = #name ## _h; \
const char * name ## _name = #name;
#include "log_areas.inc"
#undef LOG
}
namespace log {
using kutil::memset;
using kutil::memcpy;
logger *logger::s_log = nullptr;
const char *logger::s_level_names[] = {"", "debug", " info", " warn", "error", "fatal"};
logger::logger() :
m_buffer(nullptr, 0),
m_immediate(nullptr),
m_sequence(0)
{
memset(&m_levels, 0, sizeof(m_levels));
memset(&m_names, 0, sizeof(m_names));
s_log = this;
}
logger::logger(uint8_t *buffer, size_t size) :
m_buffer(buffer, size),
m_immediate(nullptr),
m_sequence(0)
{
memset(&m_levels, 0, sizeof(m_levels));
memset(&m_names, 0, sizeof(m_names));
s_log = this;
#define LOG(name, lvl) \
register_area(logs::name, logs::name ## _name, log::level::lvl);
#include "log_areas.inc"
#undef LOG
}
void
logger::set_level(area_t area, level l)
{
unsigned uarea = static_cast<unsigned>(area);
uint8_t ulevel = static_cast<uint8_t>(l) & 0x0f;
uint8_t &flags = m_levels[uarea / 2];
if (uarea & 1)
flags = (flags & 0x0f) | (ulevel << 4);
else
flags = (flags & 0xf0) | ulevel;
}
level
logger::get_level(area_t area)
{
unsigned uarea = static_cast<unsigned>(area);
uint8_t &flags = m_levels[uarea / 2];
if (uarea & 1)
return static_cast<level>((flags & 0xf0) >> 4);
else
return static_cast<level>(flags & 0x0f);
}
void
logger::register_area(area_t area, const char *name, level verbosity)
{
m_names[area] = name;
set_level(area, verbosity);
}
void
logger::output(level severity, area_t area, const char *fmt, va_list args)
{
uint8_t buffer[256];
entry *header = reinterpret_cast<entry *>(buffer);
header->bytes = sizeof(entry);
header->area = area;
header->severity = severity;
header->sequence = m_sequence++;
header->bytes +=
vsnprintf(header->message, sizeof(buffer) - sizeof(entry), fmt, args);
if (m_immediate) {
buffer[header->bytes] = 0;
m_immediate(area, severity, header->message);
return;
}
uint8_t *out;
size_t n = m_buffer.reserve(header->bytes, reinterpret_cast<void**>(&out));
if (n < sizeof(entry)) {
m_buffer.commit(0); // Cannot even write the header, abort
return;
}
if (n < header->bytes)
header->bytes = n;
memcpy(out, buffer, n);
m_buffer.commit(n);
}
size_t
logger::get_entry(void *buffer, size_t size)
{
void *out;
size_t out_size = m_buffer.get_block(&out);
entry *ent = reinterpret_cast<entry *>(out);
if (out_size == 0 || out == 0)
return 0;
kassert(out_size >= sizeof(entry), "Couldn't read a full entry");
if (out_size < sizeof(entry))
return 0;
kassert(size >= ent->bytes, "Didn't pass a big enough buffer");
if (size < ent->bytes)
return 0;
memcpy(buffer, out, ent->bytes);
m_buffer.consume(ent->bytes);
return ent->bytes;
}
#define LOG_LEVEL_FUNCTION(name) \
void name (area_t area, const char *fmt, ...) { \
logger *l = logger::s_log; \
if (!l) return; \
level limit = l->get_level(area); \
if (limit == level::none || level::name < limit) return; \
va_list args; \
va_start(args, fmt); \
l->output(level::name, area, fmt, args); \
va_end(args); \
}
LOG_LEVEL_FUNCTION(debug);
LOG_LEVEL_FUNCTION(info);
LOG_LEVEL_FUNCTION(warn);
LOG_LEVEL_FUNCTION(error);
void fatal(area_t area, const char *fmt, ...)
{
logger *l = logger::s_log;
if (!l) return;
va_list args;
va_start(args, fmt);
l->output(level::fatal, area, fmt, args);
va_end(args);
kassert(false, "log::fatal");
}
} // namespace log
} // namespace kutil

View File

@@ -1,46 +1,11 @@
#include "kutil/memory.h" #include "kutil/memory.h"
#include "kutil/heap_manager.h"
namespace std { namespace std {
enum class __attribute__ ((__type_visibility("default"))) align_val_t : size_t { }; enum class __attribute__ ((__type_visibility("default"))) align_val_t : size_t { };
} }
#ifdef __POPCORN__
void * operator new(size_t n, std::align_val_t) { return kutil::malloc(n); }
void * operator new (size_t n) { return kutil::malloc(n); }
void * operator new[] (size_t n) { return kutil::malloc(n); }
void operator delete (void *p) noexcept { return kutil::free(p); }
void operator delete[] (void *p) noexcept { return kutil::free(p); }
#endif
namespace kutil { namespace kutil {
namespace setup {
static heap_manager *heap_memory_manager;
void
set_heap(heap_manager *mm)
{
setup::heap_memory_manager = mm;
}
} // namespace kutil::setup
void *
malloc(size_t n)
{
return setup::heap_memory_manager->allocate(n);
}
void
free(void *p)
{
setup::heap_memory_manager->free(p);
}
void * void *
memset(void *s, uint8_t v, size_t n) memset(void *s, uint8_t v, size_t n)
{ {

View File

@@ -0,0 +1,766 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
// embedded systems with a very limited resources. These routines are thread
// safe and reentrant!
// Use this instead of the bloated standard/newlib printf cause these use
// malloc for printf (and may not be thread safe).
//
///////////////////////////////////////////////////////////////////////////////
#include <stdbool.h>
#include <stdint.h>
#include "kutil/printf.h"
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
// printf_config.h header file
// default: undefined
#ifdef PRINTF_INCLUDE_CONFIG_H
#include "printf_config.h"
#endif
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
// numeric number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_NTOA_BUFFER_SIZE
#define PRINTF_NTOA_BUFFER_SIZE 32U
#endif
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
// float number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_FTOA_BUFFER_SIZE
#define PRINTF_FTOA_BUFFER_SIZE 32U
#endif
// support for the floating point type (%f)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
#define PRINTF_SUPPORT_FLOAT
#endif
// support for the long long types (%llu or %p)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
#define PRINTF_SUPPORT_LONG_LONG
#endif
// support for the ptrdiff_t type (%t)
// ptrdiff_t is normally defined in <stddef.h> as long or long long type
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
#define PRINTF_SUPPORT_PTRDIFF_T
#endif
///////////////////////////////////////////////////////////////////////////////
// internal flag definitions
#define FLAGS_ZEROPAD (1U << 0U)
#define FLAGS_LEFT (1U << 1U)
#define FLAGS_PLUS (1U << 2U)
#define FLAGS_SPACE (1U << 3U)
#define FLAGS_HASH (1U << 4U)
#define FLAGS_UPPERCASE (1U << 5U)
#define FLAGS_CHAR (1U << 6U)
#define FLAGS_SHORT (1U << 7U)
#define FLAGS_LONG (1U << 8U)
#define FLAGS_LONG_LONG (1U << 9U)
#define FLAGS_PRECISION (1U << 10U)
// output function type
typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
// wrapper (used as buffer) for output function type
typedef struct {
void (*fct)(char character, void* arg);
void* arg;
} out_fct_wrap_type;
// internal buffer output
static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
{
if (idx < maxlen) {
((char*)buffer)[idx] = character;
}
}
// internal null output
static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)character; (void)buffer; (void)idx; (void)maxlen;
}
#ifdef PRINTF_PUTCHAR_FUNC
// internal screen ouput wrapper
static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)buffer; (void)idx; (void)maxlen;
if (character) {
PRINTF_PUTCHAR_FUNC (character);
}
}
#endif
// internal output function wrapper
static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)idx; (void)maxlen;
if (character) {
// buffer is the output fct pointer
((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
}
}
// internal secure strlen
// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
{
const char* s;
for (s = str; *s && maxsize--; ++s);
return (unsigned int)(s - str);
}
// internal test if char is a digit (0-9)
// \return true if char is a digit
static inline bool _is_digit(char ch)
{
return (ch >= '0') && (ch <= '9');
}
// internal ASCII string to unsigned int conversion
static unsigned int _atoi(const char** str)
{
unsigned int i = 0U;
while (_is_digit(**str)) {
i = i * 10U + (unsigned int)(*((*str)++) - '0');
}
return i;
}
// internal itoa format
static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
{
const size_t start_idx = idx;
// pad leading zeros
if (!(flags & FLAGS_LEFT)) {
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
// handle hash
if (flags & FLAGS_HASH) {
if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
len--;
if (len && (base == 16U)) {
len--;
}
}
if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'x';
}
else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'X';
}
else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'b';
}
if (len < PRINTF_NTOA_BUFFER_SIZE) {
buf[len++] = '0';
}
}
if (len < PRINTF_NTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
// pad spaces up to given width
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
for (size_t i = len; i < width; i++) {
out(' ', buffer, idx++, maxlen);
}
}
// reverse string
for (size_t i = 0U; i < len; i++) {
out(buf[len - i - 1U], buffer, idx++, maxlen);
}
// append pad spaces up to given width
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width) {
out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
// internal itoa for 'long' type
static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) {
flags &= ~FLAGS_HASH;
}
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
}
// internal itoa for 'long long' type
#if defined(PRINTF_SUPPORT_LONG_LONG)
static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) {
flags &= ~FLAGS_HASH;
}
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
}
#endif // PRINTF_SUPPORT_LONG_LONG
#if defined(PRINTF_SUPPORT_FLOAT)
static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
{
const size_t start_idx = idx;
char buf[PRINTF_FTOA_BUFFER_SIZE];
size_t len = 0U;
double diff = 0.0;
// if input is larger than thres_max, revert to exponential
const double thres_max = (double)0x7FFFFFFF;
// powers of 10
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
// test for NaN
if (value != value) {
out('n', buffer, idx++, maxlen);
out('a', buffer, idx++, maxlen);
out('n', buffer, idx++, maxlen);
return idx;
}
// test for negative
bool negative = false;
if (value < 0) {
negative = true;
value = 0 - value;
}
// set default precision to 6, if not set explicitly
if (!(flags & FLAGS_PRECISION)) {
prec = 6U;
}
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
buf[len++] = '0';
prec--;
}
int whole = (int)value;
double tmp = (value - whole) * pow10[prec];
unsigned long frac = (unsigned long)tmp;
diff = tmp - frac;
if (diff > 0.5) {
++frac;
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
if (frac >= pow10[prec]) {
frac = 0;
++whole;
}
}
else if (diff < 0.5) {
}
else if ((frac == 0U) || (frac & 1U)) {
// if halfway, round up if odd OR if last digit is 0
++frac;
}
// TBD: for very large numbers switch back to native sprintf for exponentials. Anyone want to write code to replace this?
// Normal printf behavior is to print EVERY whole number digit which can be 100s of characters overflowing your buffers == bad
if (value > thres_max) {
return 0U;
}
if (prec == 0U) {
diff = value - (double)whole;
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
// exactly 0.5 and ODD, then round up
// 1.5 -> 2, but 2.5 -> 2
++whole;
}
}
else {
unsigned int count = prec;
// now do fractional part, as an unsigned number
while (len < PRINTF_FTOA_BUFFER_SIZE) {
--count;
buf[len++] = (char)(48U + (frac % 10U));
if (!(frac /= 10U)) {
break;
}
}
// add extra 0s
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
buf[len++] = '0';
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
// add decimal
buf[len++] = '.';
}
}
// do whole part, number is reversed
while (len < PRINTF_FTOA_BUFFER_SIZE) {
buf[len++] = (char)(48 + (whole % 10));
if (!(whole /= 10)) {
break;
}
}
// pad leading zeros
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
// pad spaces up to given width
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
for (size_t i = len; i < width; i++) {
out(' ', buffer, idx++, maxlen);
}
}
// reverse string
for (size_t i = 0U; i < len; i++) {
out(buf[len - i - 1U], buffer, idx++, maxlen);
}
// append pad spaces up to given width
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width) {
out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
#endif // PRINTF_SUPPORT_FLOAT
// internal vsnprintf
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
{
unsigned int flags, width, precision, n;
size_t idx = 0U;
if (!buffer) {
// use null output function
out = _out_null;
}
while (*format)
{
// format specifier? %[flags][width][.precision][length]
if (*format != '%') {
// no
out(*format, buffer, idx++, maxlen);
format++;
continue;
}
else {
// yes, evaluate it
format++;
}
// evaluate flags
flags = 0U;
do {
switch (*format) {
case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
default : n = 0U; break;
}
} while (n);
// evaluate width field
width = 0U;
if (_is_digit(*format)) {
width = _atoi(&format);
}
else if (*format == '*') {
const int w = va_arg(va, int);
if (w < 0) {
flags |= FLAGS_LEFT; // reverse padding
width = (unsigned int)-w;
}
else {
width = (unsigned int)w;
}
format++;
}
// evaluate precision field
precision = 0U;
if (*format == '.') {
flags |= FLAGS_PRECISION;
format++;
if (_is_digit(*format)) {
precision = _atoi(&format);
}
else if (*format == '*') {
const int prec = (int)va_arg(va, int);
precision = prec > 0 ? (unsigned int)prec : 0U;
format++;
}
}
// evaluate length field
switch (*format) {
case 'l' :
flags |= FLAGS_LONG;
format++;
if (*format == 'l') {
flags |= FLAGS_LONG_LONG;
format++;
}
break;
case 'h' :
flags |= FLAGS_SHORT;
format++;
if (*format == 'h') {
flags |= FLAGS_CHAR;
format++;
}
break;
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
case 't' :
flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
#endif
case 'j' :
flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
case 'z' :
flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
default :
break;
}
// evaluate specifier
switch (*format) {
case 'd' :
case 'i' :
case 'u' :
case 'x' :
case 'X' :
case 'o' :
case 'b' : {
// set the base
unsigned int base;
if (*format == 'x' || *format == 'X') {
base = 16U;
}
else if (*format == 'o') {
base = 8U;
}
else if (*format == 'b') {
base = 2U;
}
else {
base = 10U;
flags &= ~FLAGS_HASH; // no hash for dec format
}
// uppercase
if (*format == 'X') {
flags |= FLAGS_UPPERCASE;
}
// no plus or space flag for u, x, X, o, b
if ((*format != 'i') && (*format != 'd')) {
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
}
// ignore '0' flag when precision is given
if (flags & FLAGS_PRECISION) {
flags &= ~FLAGS_ZEROPAD;
}
// convert the integer
if ((*format == 'i') || (*format == 'd')) {
// signed
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
const long long value = va_arg(va, long long);
idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
const long value = va_arg(va, long);
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
}
else {
const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
}
}
else {
// unsigned
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
}
else {
const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
}
}
format++;
break;
}
#if defined(PRINTF_SUPPORT_FLOAT)
case 'f' :
case 'F' :
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
format++;
break;
#endif // PRINTF_SUPPORT_FLOAT
case 'c' : {
unsigned int l = 1U;
// pre padding
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// char output
out((char)va_arg(va, int), buffer, idx++, maxlen);
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 's' : {
const char* p = va_arg(va, char*);
unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
// pre padding
if (flags & FLAGS_PRECISION) {
l = (l < precision ? l : precision);
}
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// string output
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
out(*(p++), buffer, idx++, maxlen);
}
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 'p' : {
width = sizeof(void*) * 2U;
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
#if defined(PRINTF_SUPPORT_LONG_LONG)
const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
if (is_ll) {
idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
}
else {
#endif
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
#if defined(PRINTF_SUPPORT_LONG_LONG)
}
#endif
format++;
break;
}
case '%' :
out('%', buffer, idx++, maxlen);
format++;
break;
default :
out(*format, buffer, idx++, maxlen);
format++;
break;
}
}
// termination
out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
return (int)idx;
}
///////////////////////////////////////////////////////////////////////////////
#ifdef PRINTF_PUTCHAR_FUNC
int printf_(const char* format, ...)
{
va_list va;
va_start(va, format);
char buffer[1];
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
#endif
int sprintf_(char* buffer, const char* format, ...)
{
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
int snprintf_(char* buffer, size_t count, const char* format, ...)
{
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
va_end(va);
return ret;
}
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
{
return _vsnprintf(_out_buffer, buffer, count, format, va);
}
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
{
va_list va;
va_start(va, format);
const out_fct_wrap_type out_fct_wrap = { out, arg };
const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
va_end(va);
return ret;
}

View File

@@ -6,6 +6,7 @@
#include <stdint.h> #include <stdint.h>
#include "kutil/address_manager.h" #include "kutil/address_manager.h"
#include "kutil/allocator.h"
#include "catch.hpp" #include "catch.hpp"
using namespace kutil; using namespace kutil;
@@ -14,9 +15,18 @@ static const size_t max_block = 1ull << 36;
static const size_t start = max_block; static const size_t start = max_block;
static const size_t GB = 1ull << 30; static const size_t GB = 1ull << 30;
class malloc_allocator :
public kutil::allocator
{
public:
virtual void * allocate(size_t n) override { return malloc(n); }
virtual void free(void *p) override { free(p); }
};
TEST_CASE( "Buddy addresses tests", "[address buddy]" ) TEST_CASE( "Buddy addresses tests", "[address buddy]" )
{ {
address_manager am; malloc_allocator alloc;
address_manager am(alloc);
am.add_regions(start, max_block * 2); am.add_regions(start, max_block * 2);
// Blocks should be: // Blocks should be:

View File

@@ -0,0 +1,21 @@
#include "kutil/constexpr_hash.h"
#include "catch.hpp"
using namespace kutil;
TEST_CASE( "constexpr hash", "[hash]" )
{
const unsigned hash1 = static_cast<unsigned>("hash1!"_h);
CHECK(hash1 == 210);
const unsigned hash2 = static_cast<unsigned>("hash1!"_h);
CHECK(hash1 == hash2);
const unsigned hash3 = static_cast<unsigned>("not hash1!"_h);
CHECK(hash1 != hash3);
CHECK(hash3 == 37);
const unsigned hash4 = static_cast<unsigned>("another thing that's longer"_h);
CHECK(hash1 != hash4);
CHECK(hash4 == 212);
}

View File

@@ -1,45 +0,0 @@
#include "kutil/frame_allocator.h"
#include "catch.hpp"
using namespace kutil;
TEST_CASE( "Frame allocator tests", "[memory frame]" )
{
frame_block_list free;
frame_block_list used;
frame_block_list cache;
auto *f = new frame_block_list::item_type;
f->address = 0x1000;
f->count = 1;
f->flags = kutil::frame_block_flags::none;
free.sorted_insert(f);
auto *g = new frame_block_list::item_type;
g->address = 0x2000;
g->count = 1;
g->flags = kutil::frame_block_flags::none;
free.sorted_insert(g);
frame_allocator fa(std::move(cache));
fa.init(std::move(free), std::move(used));
fa.consolidate_blocks();
uintptr_t a = 0;
size_t c = fa.allocate(2, &a);
CHECK( a == 0x1000 );
CHECK( c == 2 );
fa.free(a, 2);
a = 0;
fa.consolidate_blocks();
c = fa.allocate(2, &a);
CHECK( a == 0x1000 );
CHECK( c == 2 );
delete f;
delete g;
}

View File

@@ -0,0 +1,170 @@
#include <chrono>
#include <random>
#include <vector>
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>
#include "kutil/memory.h"
#include "kutil/heap_allocator.h"
#include "catch.hpp"
using namespace kutil;
const size_t hs = 0x10; // header size
const size_t max_block = 1 << 22;
int signalled = 0;
void *signalled_at = nullptr;
void *mem_base = nullptr;
std::vector<size_t> sizes = {
16000, 8000, 4000, 4000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 48, 48, 48, 13 };
void segfault_handler(int signum, siginfo_t *info, void *ctxp)
{
signalled += 1;
signalled_at = info->si_addr;
mprotect(signalled_at, max_block, PROT_READ|PROT_WRITE);
}
TEST_CASE( "Buddy blocks tests", "[memory buddy]" )
{
using clock = std::chrono::system_clock;
unsigned seed = clock::now().time_since_epoch().count();
std::default_random_engine rng(seed);
mem_base = aligned_alloc(max_block, max_block * 4);
// Catch segfaults so we can track memory access
struct sigaction sigact;
memset(&sigact, 0, sizeof(sigact));
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_NODEFER|SA_SIGINFO;
sigact.sa_sigaction = segfault_handler;
sigaction(SIGSEGV, &sigact, nullptr);
// Protect our memory arena so we trigger out fault handler
REQUIRE( mprotect(mem_base, max_block*4, PROT_NONE) == 0 );
heap_allocator mm(
reinterpret_cast<uintptr_t>(mem_base),
max_block * 4);
// Initial creation should not have allocated
CHECK( signalled == 0 );
signalled = 0;
// Allocating should signal just at the first page.
void *p = mm.allocate(max_block - hs);
CHECK( p == offset_pointer(mem_base, hs) );
CHECK( signalled == 1 );
CHECK( signalled_at == mem_base );
signalled = 0;
// Freeing and allocating should not allocate
mm.free(p);
p = mm.allocate(max_block - hs);
CHECK( p == offset_pointer(mem_base, hs) );
CHECK( signalled == 0 );
signalled = 0;
mm.free(p);
CHECK( signalled == 0 );
signalled = 0;
// Blocks should be:
// 22: 0-4M
std::vector<void *> allocs(6);
for (int i = 0; i < 6; ++i)
allocs[i] = mm.allocate(150); // size 8
// Should not have grown
CHECK( signalled == 0 );
signalled = 0;
// Blocks should be:
// 22: [0-4M]
// 21: [0-2M], 2-4M
// 20: [0-1M], 1-2M
// 19: [0-512K], 512K-1M
// 18: [0-256K], 256-512K
// 17: [0-128K], 128-256K
// 16: [0-64K], 64-128K
// 15: [0-32K], 32K-64K
// 14: [0-16K], 16K-32K
// 13: [0-8K], 8K-16K
// 12: [0-4K], 4K-8K
// 11: [0-2K], 2K-4K
// 10: [0-1K, 1-2K]
// 9: [0, 512, 1024], 1536
// 8: [0, 256, 512, 768, 1024, 1280]
// We have free memory at 1526 and 2K, but we should get 4K
void *big = mm.allocate(4000); // size 12
CHECK( signalled == 0 );
signalled = 0;
REQUIRE( big == offset_pointer(mem_base, 4096 + hs) );
mm.free(big);
// free up 512
mm.free(allocs[3]);
mm.free(allocs[4]);
// Blocks should be:
// ...
// 9: [0, 512, 1024], 1536
// 8: [0, 256, 512], 768, 1024, [1280]
// A request for a 512-block should not cross the buddy divide
big = mm.allocate(500); // size 9
REQUIRE( big >= offset_pointer(mem_base, 1536 + hs) );
mm.free(big);
mm.free(allocs[0]);
mm.free(allocs[1]);
mm.free(allocs[2]);
mm.free(allocs[5]);
allocs.clear();
std::shuffle(sizes.begin(), sizes.end(), rng);
allocs.reserve(sizes.size());
for (size_t size : sizes)
allocs.push_back(mm.allocate(size));
std::shuffle(allocs.begin(), allocs.end(), rng);
for (void *p: allocs)
mm.free(p);
allocs.clear();
big = mm.allocate(max_block / 2 + 1);
// If everything was freed / joined correctly, that should not have allocated
CHECK( signalled == 0 );
signalled = 0;
// And we should have gotten back the start of memory
CHECK( big == offset_pointer(mem_base, hs) );
// Allocating again should signal at the next page.
void *p2 = mm.allocate(max_block - hs);
CHECK( p2 == offset_pointer(mem_base, max_block + hs) );
CHECK( signalled == 1 );
CHECK( signalled_at == offset_pointer(mem_base, max_block) );
signalled = 0;
mm.free(p2);
CHECK( signalled == 0 );
signalled = 0;
free(mem_base);
}

View File

@@ -1,191 +0,0 @@
#include <chrono>
#include <random>
#include <vector>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include "kutil/memory.h"
#include "kutil/heap_manager.h"
#include "catch.hpp"
using namespace kutil;
static std::vector<void *> memory;
static size_t total_alloc_size = 0;
static size_t total_alloc_calls = 0;
const size_t hs = 0x10; // header size
const size_t max_block = 1 << 16;
std::vector<size_t> sizes = {
16000, 8000, 4000, 4000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 48, 48, 48, 13 };
void * grow_callback(size_t length)
{
total_alloc_calls += 1;
total_alloc_size += length;
void *p = aligned_alloc(max_block, length * 2);
memory.push_back(p);
return p;
}
void free_memory()
{
for (void *p : memory) ::free(p);
memory.clear();
total_alloc_size = 0;
total_alloc_calls = 0;
}
TEST_CASE( "Buddy blocks tests", "[memory buddy]" )
{
using clock = std::chrono::system_clock;
unsigned seed = clock::now().time_since_epoch().count();
std::default_random_engine rng(seed);
heap_manager mm(grow_callback);
// The ctor should have allocated an initial block
CHECK( total_alloc_size == max_block );
CHECK( total_alloc_calls == 1 );
// Blocks should be:
// 16: 0-64K
std::vector<void *> allocs(6);
for (int i = 0; i < 6; ++i)
allocs[i] = mm.allocate(150); // size 8
// Should not have grown
CHECK( total_alloc_size == max_block );
CHECK( total_alloc_calls == 1 );
CHECK( memory[0] != nullptr );
// Blocks should be:
// 16: [0-64K]
// 15: [0-32K], 32K-64K
// 14: [0-16K], 16K-32K
// 13: [0-8K], 8K-16K
// 12: [0-4K], 4K-8K
// 11: [0-2K], 2K-4K
// 10: [0-1K, 1-2K]
// 9: [0, 512, 1024], 1536
// 8: [0, 256, 512, 768, 1024, 1280]
// We have free memory at 1526 and 2K, but we should get 4K
void *big = mm.allocate(4000); // size 12
REQUIRE( big == offset_pointer(memory[0], 4096 + hs) );
mm.free(big);
// free up 512
mm.free(allocs[3]);
mm.free(allocs[4]);
// Blocks should be:
// ...
// 9: [0, 512, 1024], 1536
// 8: [0, 256, 512], 768, 1024, [1280]
// A request for a 512-block should not cross the buddy divide
big = mm.allocate(500); // size 9
REQUIRE( big >= offset_pointer(memory[0], 1536 + hs) );
mm.free(big);
mm.free(allocs[0]);
mm.free(allocs[1]);
mm.free(allocs[2]);
mm.free(allocs[5]);
allocs.clear();
std::shuffle(sizes.begin(), sizes.end(), rng);
allocs.reserve(sizes.size());
for (size_t size : sizes)
allocs.push_back(mm.allocate(size));
std::shuffle(allocs.begin(), allocs.end(), rng);
for (void *p: allocs)
mm.free(p);
allocs.clear();
big = mm.allocate(64000);
// If everything was freed / joined correctly, that should not have allocated
CHECK( total_alloc_size == max_block );
CHECK( total_alloc_calls == 1 );
// And we should have gotten back the start of memory
CHECK( big == offset_pointer(memory[0], hs) );
free_memory();
}
bool check_in_memory(void *p)
{
for (void *mem : memory)
if (p >= mem && p <= offset_pointer(mem, max_block))
return true;
return false;
}
TEST_CASE( "Non-contiguous blocks tests", "[memory buddy]" )
{
using clock = std::chrono::system_clock;
unsigned seed = clock::now().time_since_epoch().count();
std::default_random_engine rng(seed);
heap_manager mm(grow_callback);
std::vector<void *> allocs;
const int blocks = 3;
for (int i = 0; i < blocks; ++i) {
void *p = mm.allocate(64000);
REQUIRE( memory[i] != nullptr );
REQUIRE( p == offset_pointer(memory[i], hs) );
allocs.push_back(p);
}
CHECK( total_alloc_size == max_block * blocks );
CHECK( total_alloc_calls == blocks );
for (void *p : allocs)
mm.free(p);
allocs.clear();
allocs.reserve(sizes.size() * blocks);
for (int i = 0; i < blocks; ++i) {
std::shuffle(sizes.begin(), sizes.end(), rng);
for (size_t size : sizes)
allocs.push_back(mm.allocate(size));
}
for (void *p : allocs)
CHECK( check_in_memory(p) );
std::shuffle(allocs.begin(), allocs.end(), rng);
for (void *p: allocs)
mm.free(p);
allocs.clear();
CHECK( total_alloc_size == max_block * blocks );
CHECK( total_alloc_calls == blocks );
for (int i = 0; i < blocks; ++i)
allocs.push_back(mm.allocate(64000));
// If everything was freed / joined correctly, that should not have allocated
CHECK( total_alloc_size == max_block * blocks );
CHECK( total_alloc_calls == blocks );
for (void *p : allocs)
CHECK( check_in_memory(p) );
free_memory();
}

Some files were not shown because too many files have changed in this diff Show More