mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 08:24:32 -08:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6302e8b73a | ||
|
|
fd1adc0262 | ||
|
|
070be0b005 | ||
|
|
5034ffbe59 | ||
|
|
cd13b88540 | ||
|
|
e050d6f151 | ||
|
|
863555ec6b | ||
|
|
c605793a9d | ||
|
|
8375870af6 | ||
|
|
11a53e792f | ||
|
|
ca2362f858 | ||
|
|
5cdbedd4d1 | ||
|
|
ee6d69bcd2 | ||
|
|
f9193b4602 | ||
|
|
979e5f036e | ||
|
|
39baec852b | ||
|
|
ed3f9410a6 | ||
|
|
b18243f098 | ||
|
|
9472dab522 | ||
|
|
9067f8d298 | ||
|
|
866073ae8a | ||
|
|
91cb00fde2 | ||
|
|
c435bcfb67 | ||
|
|
7fe1b7d1f5 | ||
|
|
39524b2b9f | ||
|
|
c592f5f537 | ||
|
|
94c491d286 | ||
|
|
645938a194 | ||
|
|
73756820bd | ||
|
|
33374ab257 | ||
|
|
bf8286d15f | ||
|
|
6410c898c5 | ||
|
|
be007c6278 | ||
|
|
f7558e3d18 | ||
|
|
97e8f2c69a | ||
|
|
49bdf47514 | ||
|
|
81162f30dc | ||
|
|
4379256c11 | ||
|
|
870ca1db45 | ||
|
|
74a5c301f8 | ||
|
|
2955e6c9c1 | ||
|
|
cd2ccb4e06 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
/build*
|
||||
*.bak
|
||||
tags
|
||||
.gdbinit
|
||||
popcorn.log
|
||||
*.o
|
||||
*.a
|
||||
|
||||
27
LICENSE.md
27
LICENSE.md
@@ -81,6 +81,33 @@ MIT license:
|
||||
> 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.
|
||||
|
||||
## 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
|
||||
|
||||
Popcorn's UEFI bootloader initially used
|
||||
|
||||
27
README.md
27
README.md
@@ -27,11 +27,21 @@ The design goals of the project are:
|
||||
|
||||
## Building
|
||||
|
||||
Popcorn uses the `ninja` build tool, and generates the build files for it with
|
||||
the `generate_build.py` script. The other requirements are:
|
||||
Popcorn uses the [Ninja][] build tool, and generates the build files for it
|
||||
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
|
||||
* mtools
|
||||
* ninja
|
||||
@@ -45,7 +55,7 @@ Popcorn host binaries.
|
||||
|
||||
### 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
|
||||
have `qemu-system-x86_64` installed, the `qemu.sh` script will to run Popcorn
|
||||
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
|
||||
enough to set up such a system to build the kernel:
|
||||
|
||||
sudo apt install qemu-system-x86 nasm clang-6.0 mtools
|
||||
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 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 pip3 install bonnibel
|
||||
|
||||
|
||||
31
assets/debugging/popcorn.elf-gdb.py
Normal file
31
assets/debugging/popcorn.elf-gdb.py
Normal 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")
|
||||
@@ -14,10 +14,10 @@ source = "../assets/fonts/tamsyn8x16r.psf"
|
||||
|
||||
[[files]]
|
||||
dest = "nulldrv1"
|
||||
source = "host/nulldrv"
|
||||
source = "user/nulldrv"
|
||||
executable = true
|
||||
|
||||
[[files]]
|
||||
dest = "nulldrv2"
|
||||
source = "host/nulldrv"
|
||||
source = "user/nulldrv"
|
||||
executable = true
|
||||
|
||||
Binary file not shown.
BIN
assets/ovmf/x64/ovmf_code.fd
Normal file
BIN
assets/ovmf/x64/ovmf_code.fd
Normal file
Binary file not shown.
BIN
assets/ovmf/x64/ovmf_vars.fd
Normal file
BIN
assets/ovmf/x64/ovmf_vars.fd
Normal file
Binary file not shown.
BIN
assets/ovmf/x64/ovmf_vars_d.fd
Normal file
BIN
assets/ovmf/x64/ovmf_vars_d.fd
Normal file
Binary file not shown.
@@ -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:])
|
||||
234
modules.yaml
234
modules.yaml
@@ -1,116 +1,132 @@
|
||||
kernel:
|
||||
output: popcorn.elf
|
||||
target: host
|
||||
deps:
|
||||
- elf
|
||||
- initrd
|
||||
- kutil
|
||||
includes:
|
||||
- src/kernel
|
||||
source:
|
||||
- src/kernel/crti.s
|
||||
- src/kernel/apic.cpp
|
||||
- src/kernel/assert.cpp
|
||||
- src/kernel/boot.s
|
||||
- src/kernel/console.cpp
|
||||
- src/kernel/cpprt.cpp
|
||||
- src/kernel/cpu.cpp
|
||||
- src/kernel/debug.cpp
|
||||
- src/kernel/debug.s
|
||||
- src/kernel/device_manager.cpp
|
||||
- src/kernel/font.cpp
|
||||
- src/kernel/fs/gpt.cpp
|
||||
- src/kernel/gdt.cpp
|
||||
- src/kernel/gdt.s
|
||||
- src/kernel/interrupts.cpp
|
||||
- src/kernel/interrupts.s
|
||||
- src/kernel/io.cpp
|
||||
- src/kernel/loader.s
|
||||
- src/kernel/log.cpp
|
||||
- src/kernel/main.cpp
|
||||
- src/kernel/memory_bootstrap.cpp
|
||||
- src/kernel/msr.cpp
|
||||
- src/kernel/page_manager.cpp
|
||||
- src/kernel/pci.cpp
|
||||
- src/kernel/process.cpp
|
||||
- src/kernel/scheduler.cpp
|
||||
- src/kernel/screen.cpp
|
||||
- src/kernel/serial.cpp
|
||||
- src/kernel/syscall.cpp
|
||||
- src/kernel/syscall.s
|
||||
- src/kernel/crtn.s
|
||||
name: Popcorn
|
||||
templates: scripts/templates
|
||||
modules:
|
||||
kernel:
|
||||
output: popcorn.elf
|
||||
target: host
|
||||
deps:
|
||||
- elf
|
||||
- initrd
|
||||
- kutil
|
||||
includes:
|
||||
- src/kernel
|
||||
source:
|
||||
- src/kernel/crti.s
|
||||
- src/kernel/apic.cpp
|
||||
- src/kernel/assert.cpp
|
||||
- src/kernel/boot.s
|
||||
- src/kernel/console.cpp
|
||||
- src/kernel/cpprt.cpp
|
||||
- src/kernel/cpu.cpp
|
||||
- src/kernel/debug.cpp
|
||||
- src/kernel/debug.s
|
||||
- src/kernel/device_manager.cpp
|
||||
- src/kernel/font.cpp
|
||||
- src/kernel/frame_allocator.cpp
|
||||
- src/kernel/fs/gpt.cpp
|
||||
- src/kernel/gdt.cpp
|
||||
- src/kernel/gdt.s
|
||||
- src/kernel/interrupts.cpp
|
||||
- src/kernel/interrupts.s
|
||||
- src/kernel/io.cpp
|
||||
- src/kernel/loader.s
|
||||
- src/kernel/log.cpp
|
||||
- src/kernel/main.cpp
|
||||
- src/kernel/memory_bootstrap.cpp
|
||||
- src/kernel/msr.cpp
|
||||
- src/kernel/page_manager.cpp
|
||||
- src/kernel/pci.cpp
|
||||
- src/kernel/process.cpp
|
||||
- src/kernel/scheduler.cpp
|
||||
- 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:
|
||||
kind: exe
|
||||
target: boot
|
||||
output: boot.elf
|
||||
source:
|
||||
- src/boot/crt0.s
|
||||
- src/boot/console.cpp
|
||||
- src/boot/guids.cpp
|
||||
- src/boot/loader.cpp
|
||||
- src/boot/main.cpp
|
||||
- src/boot/memory.cpp
|
||||
- src/boot/reloc.cpp
|
||||
- src/boot/utility.cpp
|
||||
boot:
|
||||
kind: exe
|
||||
target: boot
|
||||
output: boot.elf
|
||||
source:
|
||||
- src/boot/crt0.s
|
||||
- src/boot/console.cpp
|
||||
- src/boot/guids.cpp
|
||||
- src/boot/loader.cpp
|
||||
- src/boot/main.cpp
|
||||
- src/boot/memory.cpp
|
||||
- src/boot/reloc.cpp
|
||||
- src/boot/utility.cpp
|
||||
|
||||
nulldrv:
|
||||
kind: exe
|
||||
target: host
|
||||
output: nulldrv
|
||||
source:
|
||||
- src/drivers/nulldrv/main.s
|
||||
nulldrv:
|
||||
kind: exe
|
||||
target: user
|
||||
output: nulldrv
|
||||
source:
|
||||
- src/drivers/nulldrv/main.cpp
|
||||
- src/drivers/nulldrv/main.s
|
||||
|
||||
elf:
|
||||
kind: lib
|
||||
output: libelf.a
|
||||
deps:
|
||||
- kutil
|
||||
includes:
|
||||
- src/libraries/elf/include
|
||||
source:
|
||||
- src/libraries/elf/elf.cpp
|
||||
elf:
|
||||
kind: lib
|
||||
output: libelf.a
|
||||
deps:
|
||||
- kutil
|
||||
includes:
|
||||
- src/libraries/elf/include
|
||||
source:
|
||||
- src/libraries/elf/elf.cpp
|
||||
|
||||
initrd:
|
||||
kind: lib
|
||||
output: libinitrd.a
|
||||
deps:
|
||||
- kutil
|
||||
includes:
|
||||
- src/libraries/initrd/include
|
||||
source:
|
||||
- src/libraries/initrd/initrd.cpp
|
||||
initrd:
|
||||
kind: lib
|
||||
output: libinitrd.a
|
||||
deps:
|
||||
- kutil
|
||||
includes:
|
||||
- src/libraries/initrd/include
|
||||
source:
|
||||
- src/libraries/initrd/initrd.cpp
|
||||
|
||||
kutil:
|
||||
kind: lib
|
||||
output: libkutil.a
|
||||
includes:
|
||||
- src/libraries/kutil/include
|
||||
source:
|
||||
- src/libraries/kutil/assert.cpp
|
||||
- src/libraries/kutil/frame_allocator.cpp
|
||||
- src/libraries/kutil/heap_manager.cpp
|
||||
- src/libraries/kutil/memory.cpp
|
||||
kutil:
|
||||
kind: lib
|
||||
output: libkutil.a
|
||||
includes:
|
||||
- src/libraries/kutil/include
|
||||
source:
|
||||
- src/libraries/kutil/assert.cpp
|
||||
- src/libraries/kutil/bip_buffer.cpp
|
||||
- src/libraries/kutil/heap_allocator.cpp
|
||||
- src/libraries/kutil/logger.cpp
|
||||
- src/libraries/kutil/memory.cpp
|
||||
- src/libraries/kutil/printf.c
|
||||
|
||||
makerd:
|
||||
kind: exe
|
||||
target: native
|
||||
output: makerd
|
||||
deps:
|
||||
- initrd
|
||||
source:
|
||||
- src/tools/makerd/entry.cpp
|
||||
- src/tools/makerd/main.cpp
|
||||
makerd:
|
||||
kind: exe
|
||||
target: native
|
||||
output: makerd
|
||||
deps:
|
||||
- initrd
|
||||
source:
|
||||
- src/tools/makerd/entry.cpp
|
||||
- src/tools/makerd/main.cpp
|
||||
|
||||
tests:
|
||||
kind: exe
|
||||
target: native
|
||||
output: tests
|
||||
deps:
|
||||
- kutil
|
||||
source:
|
||||
- src/tests/address_manager.cpp
|
||||
- src/tests/frame_allocator.cpp
|
||||
- src/tests/linked_list.cpp
|
||||
- src/tests/heap_manager.cpp
|
||||
- src/tests/main.cpp
|
||||
tests:
|
||||
kind: exe
|
||||
target: native
|
||||
output: tests
|
||||
deps:
|
||||
- kutil
|
||||
source:
|
||||
- src/tests/address_manager.cpp
|
||||
- src/tests/constexpr_hash.cpp
|
||||
- src/tests/linked_list.cpp
|
||||
- src/tests/logger.cpp
|
||||
- src/tests/heap_allocator.cpp
|
||||
- src/tests/main.cpp
|
||||
|
||||
31
qemu.sh
31
qemu.sh
@@ -1,33 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
build="$(dirname $0)/build"
|
||||
assets="$(dirname $0)/assets"
|
||||
debug=""
|
||||
flash_name="ovmf_vars"
|
||||
gfx="-nographic"
|
||||
kvm=""
|
||||
|
||||
for arg in $@; do
|
||||
case "${arg}" in
|
||||
--debug)
|
||||
debug="-s"
|
||||
flash_name="ovmf_vars_d"
|
||||
;;
|
||||
--gfx)
|
||||
gfx=""
|
||||
;;
|
||||
--kvm)
|
||||
kvm="-enable-kvm"
|
||||
;;
|
||||
*)
|
||||
build="${arg}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
kvm=""
|
||||
if [[ -c /dev/kvm ]]; then
|
||||
kvm="-enable-kvm"
|
||||
if [[ ! -c /dev/kvm ]]; then
|
||||
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
|
||||
|
||||
ninja -C "${build}" && \
|
||||
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" \
|
||||
-smp 1 \
|
||||
-monitor telnet:localhost:45454,server,nowait \
|
||||
-smp 4 \
|
||||
-m 512 \
|
||||
-d mmu,int,guest_errors \
|
||||
-D popcorn.log \
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
TARGET="x86_64-elf"
|
||||
NASM_VERSION="2.13.03"
|
||||
BINUTILS_VERSION="2.31.1"
|
||||
LLVM_BRANCH="release_80"
|
||||
|
||||
TOOLS="clang" # lld libunwind libcxxabi libcxx"
|
||||
PROJECTS="compiler-rt libcxxabi libcxx libunwind"
|
||||
@@ -77,7 +78,7 @@ function build_llvm() {
|
||||
if [[ ! -d "${WORK}/llvm" ]]; then
|
||||
echo "Downloading LLVM..."
|
||||
git clone -q \
|
||||
--branch release_70 \
|
||||
--branch "${LLVM_BRANCH}" \
|
||||
--depth 1 \
|
||||
"https://git.llvm.org/git/llvm.git" "${WORK}/llvm"
|
||||
fi
|
||||
@@ -86,7 +87,7 @@ function build_llvm() {
|
||||
if [[ ! -d "${WORK}/llvm/tools/${tool}" ]]; then
|
||||
echo "Downloading ${tool}..."
|
||||
git clone -q \
|
||||
--branch release_70 \
|
||||
--branch "${LLVM_BRANCH}" \
|
||||
--depth 1 \
|
||||
"https://git.llvm.org/git/${tool}.git" "${WORK}/llvm/tools/${tool}"
|
||||
fi
|
||||
@@ -95,7 +96,7 @@ function build_llvm() {
|
||||
if [[ ! -d "${WORK}/llvm/tools/clang/tools/extra" ]]; then
|
||||
echo "Downloading clang-tools-extra..."
|
||||
git clone -q \
|
||||
--branch release_70 \
|
||||
--branch "${LLVM_BRANCH}" \
|
||||
--depth 1 \
|
||||
"https://git.llvm.org/git/clang-tools-extra.git" "${WORK}/llvm/tools/clang/tools/extra"
|
||||
fi
|
||||
@@ -104,7 +105,7 @@ function build_llvm() {
|
||||
if [[ ! -d "${WORK}/llvm/projects/${proj}" ]]; then
|
||||
echo "Downloading ${proj}..."
|
||||
git clone -q \
|
||||
--branch release_70 \
|
||||
--branch "${LLVM_BRANCH}" \
|
||||
--depth 1 \
|
||||
"https://git.llvm.org/git/${proj}.git" "${WORK}/llvm/projects/${proj}"
|
||||
fi
|
||||
@@ -114,7 +115,7 @@ function build_llvm() {
|
||||
if [[ ! -d "${WORK}/llvm/runtimes/${proj}" ]]; then
|
||||
echo "Downloading ${proj}..."
|
||||
git clone -q \
|
||||
--branch release_70 \
|
||||
--branch "${LLVM_BRANCH}" \
|
||||
--depth 1 \
|
||||
"https://git.llvm.org/git/${proj}.git" "${WORK}/llvm/runtime/${proj}"
|
||||
fi
|
||||
|
||||
@@ -1,109 +1,14 @@
|
||||
ninja_required_version = 1.3
|
||||
builddir = {{ buildroot }}
|
||||
srcroot = {{ srcroot }}
|
||||
modulefile = {{ modulefile }}
|
||||
{% extends "build.base.j2" %}
|
||||
|
||||
warnflags = $
|
||||
-Wformat=2 $
|
||||
-Winit-self $
|
||||
-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 = $
|
||||
{% block variables %}
|
||||
{{ super() }}
|
||||
ccflags = $ccflags $
|
||||
-I${srcroot}/src/include $
|
||||
-I${srcroot}/src/include/x86_64 $
|
||||
-DVERSION_MAJOR={{ version.major }} $
|
||||
-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
|
||||
-I${srcroot}/src/include/x86_64
|
||||
{% endblock %}
|
||||
|
||||
{% block baserules %}
|
||||
{{ super() }}
|
||||
rule makerd
|
||||
description = Making init ramdisk
|
||||
command = $builddir/native/makerd $in $out
|
||||
@@ -135,29 +40,24 @@ rule strip
|
||||
objcopy --only-keep-debug $out $out.debug; $
|
||||
strip -g $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 %}
|
||||
subninja {{ target }}/target.ninja
|
||||
{% 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/ovmf_vars_d.fd : cp $srcroot/assets/ovmf/x64/ovmf_vars_d.fd
|
||||
name = ovmf_vars_d.fd
|
||||
|
||||
build $builddir/popcorn.elf | $builddir/popcorn.elf.debug : strip $builddir/host/popcorn.elf
|
||||
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
|
||||
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 | $
|
||||
${builddir}/native/makerd $
|
||||
${builddir}/host/nulldrv
|
||||
${builddir}/user/nulldrv
|
||||
|
||||
build $builddir/popcorn.img : makefat | $
|
||||
$builddir/fatroot/initrd.img $
|
||||
@@ -174,5 +74,13 @@ build $builddir/popcorn.img : makefat | $
|
||||
$builddir/fatroot/efi/boot/bootx64.efi
|
||||
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
|
||||
|
||||
|
||||
@@ -2,18 +2,13 @@
|
||||
{% block variables %}
|
||||
{{ super() }}
|
||||
|
||||
ld = ld
|
||||
cc = clang
|
||||
cxx = clang++
|
||||
|
||||
ccflags = $ccflags $
|
||||
-DKERNEL_FILENAME=L\"popcorn.elf\" $
|
||||
-DGNU_EFI_USE_MS_ABI $
|
||||
-DHAVE_USE_MS_ABI $
|
||||
-DEFI_DEBUG=0 $
|
||||
-DEFI_DEBUG_CLEAR_MEMORY=0 $
|
||||
-DBOOTLOADER_DEBUG $
|
||||
-fPIC
|
||||
-DBOOTLOADER_DEBUG
|
||||
|
||||
ldflags = $ldflags $
|
||||
-T ${srcroot}/src/arch/x86_64/boot.ld $
|
||||
@@ -27,3 +22,6 @@ build $builddir/boot.efi : makeefi ${builddir}/{{ module.output }}
|
||||
name = boot.efi
|
||||
|
||||
{% endblock %}
|
||||
|
||||
# vim: ft=ninja et ts=4 sts=4 sw=4
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
@@ -7,3 +7,6 @@ libs = $libs
|
||||
ldflags = $ldflags -T ${srcroot}/src/arch/x86_64/kernel.ld
|
||||
|
||||
{% endblock %}
|
||||
|
||||
# vim: ft=ninja et ts=4 sts=4 sw=4
|
||||
|
||||
|
||||
@@ -5,3 +5,6 @@
|
||||
ccflags = $ccflags -ggdb
|
||||
|
||||
{% endblock %}
|
||||
|
||||
# vim: ft=ninja et ts=4 sts=4 sw=4
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{% extends "module.base.j2" %}
|
||||
@@ -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
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
{% extends "target.default.j2" %}
|
||||
|
||||
{% block binaries %}
|
||||
ld = ld
|
||||
cc = clang
|
||||
cxx = clang++
|
||||
ld = ld
|
||||
ar = ar
|
||||
nasm = nasm
|
||||
objcopy = objcopy
|
||||
{% endblock %}
|
||||
|
||||
{% block variables %}
|
||||
|
||||
ccflags = $ccflags $
|
||||
-ggdb $
|
||||
-nostdlib $
|
||||
-ffreestanding $
|
||||
-nodefaultlibs $
|
||||
-fno-builtin $
|
||||
-mno-sse $
|
||||
-fno-omit-frame-pointer $
|
||||
-mno-red-zone $
|
||||
-fshort-wchar
|
||||
-ggdb $
|
||||
-nostdlib $
|
||||
-ffreestanding $
|
||||
-nodefaultlibs $
|
||||
-fno-builtin $
|
||||
-mno-sse $
|
||||
-fno-omit-frame-pointer $
|
||||
-mno-red-zone $
|
||||
-fshort-wchar $
|
||||
-D__ELF__ $
|
||||
-fPIC
|
||||
|
||||
cxxflags = $cxxflags $
|
||||
-nostdlibinc $
|
||||
-fno-exceptions $
|
||||
-fno-rtti
|
||||
-fno-exceptions $
|
||||
-fno-rtti $
|
||||
|
||||
ldflags = $ldflags $
|
||||
-g $
|
||||
-nostdlib $
|
||||
-znocombreloc $
|
||||
-g $
|
||||
-nostdlib $
|
||||
-znocombreloc $
|
||||
-Bsymbolic $
|
||||
-nostartfiles
|
||||
-nostartfiles
|
||||
|
||||
{% endblock %}
|
||||
|
||||
# vim: ft=ninja et ts=4 sts=4 sw=4
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -40,4 +40,6 @@ ldflags = $ldflags $
|
||||
-Bstatic
|
||||
|
||||
{% endblock %}
|
||||
# vim: et ts=4 sts=4 sw=4
|
||||
|
||||
# vim: ft=ninja et ts=4 sts=4 sw=4
|
||||
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
{% extends "target.default.j2" %}
|
||||
|
||||
{% block binaries %}
|
||||
{{ super() }}
|
||||
ld = clang++
|
||||
{% endblock %}
|
||||
|
||||
{% block variables %}
|
||||
|
||||
ccflags = $ccflags -g -ggdb
|
||||
|
||||
{% endblock %}
|
||||
# vim: et ts=4 sts=4 sw=4
|
||||
|
||||
# vim: ft=ninja et ts=4 sts=4 sw=4
|
||||
|
||||
|
||||
49
scripts/templates/target.user.j2
Normal file
49
scripts/templates/target.user.j2
Normal 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
|
||||
|
||||
@@ -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(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(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
|
||||
|
||||
@@ -9,31 +9,73 @@
|
||||
static wchar_t kernel_name[] = KERNEL_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
|
||||
loader_alloc_pages(
|
||||
EFI_BOOT_SERVICES *bootsvc,
|
||||
EFI_MEMORY_TYPE mem_type,
|
||||
size_t *length,
|
||||
void **pages,
|
||||
bool align)
|
||||
void **pages)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
|
||||
size_t page_count = ((*length - 1) / PAGE_SIZE) + 1;
|
||||
EFI_PHYSICAL_ADDRESS addr = (EFI_PHYSICAL_ADDRESS)*pages;
|
||||
|
||||
if (align) {
|
||||
// Align addr to the next multiple of N pages
|
||||
size_t align_size = page_count * PAGE_SIZE;
|
||||
addr = ((addr - 1) & ~(align_size - 1)) + align_size;
|
||||
}
|
||||
con_debug(L"Trying to find %d non-aligned pages for %x at %lx\n",
|
||||
page_count, mem_type, 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,
|
||||
L"Allocating %d kernel pages type %x",
|
||||
page_count, mem_type);
|
||||
@@ -69,12 +111,11 @@ loader_load_initrd(
|
||||
|
||||
data->initrd_length = ((EFI_FILE_INFO *)info)->FileSize;
|
||||
|
||||
status = loader_alloc_pages(
|
||||
status = loader_alloc_aligned(
|
||||
bootsvc,
|
||||
memtype_initrd,
|
||||
&data->initrd_length,
|
||||
&data->initrd,
|
||||
true);
|
||||
&data->initrd);
|
||||
CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating pages");
|
||||
|
||||
status = file->Read(file, &data->initrd_length, data->initrd);
|
||||
@@ -143,7 +184,7 @@ loader_load_elf(
|
||||
header.machine != 0x3e)
|
||||
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;
|
||||
|
||||
@@ -161,7 +202,7 @@ loader_load_elf(
|
||||
|
||||
length = prog_header.mem_size;
|
||||
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");
|
||||
|
||||
if (data->kernel == 0)
|
||||
@@ -236,20 +277,19 @@ loader_load_kernel(
|
||||
|
||||
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_elf: %s", kernel_name);
|
||||
|
||||
data->initrd = (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 = (void *)((uint64_t)data->kernel + data->kernel_length);
|
||||
data->data_length += PAGE_SIZE; // extra page for map growth
|
||||
status = loader_alloc_pages(
|
||||
|
||||
status = loader_alloc_aligned(
|
||||
bootsvc,
|
||||
memtype_data,
|
||||
&data->data_length,
|
||||
&data->data,
|
||||
true);
|
||||
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_alloc_pages: kernel data");
|
||||
&data->data);
|
||||
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_alloc_aligned: 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;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "console.h"
|
||||
#include "guids.h"
|
||||
#include "kernel_data.h"
|
||||
#include "kernel_args.h"
|
||||
#include "loader.h"
|
||||
#include "memory.h"
|
||||
#include "utility.h"
|
||||
@@ -34,7 +34,51 @@ struct kernel_header {
|
||||
};
|
||||
#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
|
||||
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);
|
||||
CHECK_EFI_STATUS_OR_FAIL(status);
|
||||
|
||||
size_t header_size = sizeof(popcorn_data);
|
||||
const size_t header_align = alignof(popcorn_data);
|
||||
size_t header_size = sizeof(kernel_args);
|
||||
const size_t header_align = alignof(kernel_args);
|
||||
if (header_size % header_align)
|
||||
header_size += header_align - (header_size % header_align);
|
||||
|
||||
data_length += header_size;
|
||||
|
||||
|
||||
// Load the kernel image from disk and check it
|
||||
//
|
||||
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);
|
||||
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" %u initrd bytes at 0x%x\r\n", load.initrd_length, load.initrd);
|
||||
console::print(L" %u data bytes at 0x%x\r\n", load.data_length, load.data);
|
||||
console::print(L" %x image bytes at 0x%x\r\n", load.kernel_length, load.kernel);
|
||||
console::print(L" %x 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;
|
||||
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",
|
||||
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);
|
||||
|
||||
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
|
||||
//
|
||||
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);
|
||||
|
||||
data_header->magic = DATA_HEADER_MAGIC;
|
||||
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->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_desc_size = map.size;
|
||||
|
||||
detect_debug_mode(runsvc, data_header);
|
||||
|
||||
// bootsvc->Stall(5000000);
|
||||
|
||||
status = bootsvc->ExitBootServices(image_handle, map.key);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,61 +1,79 @@
|
||||
section .bss
|
||||
mypid: resq 1
|
||||
mychild: resq 1
|
||||
mymessage:
|
||||
resq 1024
|
||||
|
||||
extern main
|
||||
extern exit
|
||||
|
||||
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
|
||||
_start:
|
||||
xor rbp, rbp ; Sentinel rbp
|
||||
push rbp
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
|
||||
mov rax, 5 ; GETPID syscall
|
||||
int 0xee
|
||||
mov [mypid], rax
|
||||
mov rdi, 0
|
||||
mov rsi, 0
|
||||
call main
|
||||
|
||||
mov rax, 8 ; FORK syscall
|
||||
int 0xee
|
||||
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
|
||||
mov rdi, rax
|
||||
call exit
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
#define DATA_HEADER_MAGIC 0x600dda7a
|
||||
#define DATA_HEADER_VERSION 1
|
||||
|
||||
#define POPCORN_FLAG_DEBUG 0x00000001
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct popcorn_data {
|
||||
struct kernel_args {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t length;
|
||||
@@ -2,6 +2,9 @@
|
||||
/// \file kernel_memory.h
|
||||
/// Constants related to the kernel's memory layout
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace memory {
|
||||
|
||||
/// Size of a single page frame.
|
||||
@@ -19,4 +22,11 @@ namespace memory {
|
||||
/// Initial process thread's stack size, in pages
|
||||
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
|
||||
9
src/include/log_areas.inc
Normal file
9
src/include/log_areas.inc
Normal 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);
|
||||
@@ -46,6 +46,7 @@ apic::apic(uint32_t *base) :
|
||||
lapic::lapic(uint32_t *base, isr spurious) :
|
||||
apic(base)
|
||||
{
|
||||
// TODO: This causes a "reserved" page fault under KVM
|
||||
apic_write(m_base, 0xf0, static_cast<uint32_t>(spurious));
|
||||
log::info(logs::apic, "LAPIC created, base %lx", m_base);
|
||||
}
|
||||
|
||||
@@ -8,19 +8,14 @@ __kernel_assert(const char *file, unsigned line, const char *message)
|
||||
if (cons) {
|
||||
cons->set_color(9 , 0);
|
||||
cons->puts("\n\n ERROR: ");
|
||||
cons->puts(message);
|
||||
cons->puts("\n ");
|
||||
cons->puts(file);
|
||||
cons->puts(":");
|
||||
cons->put_dec(line);
|
||||
cons->puts(": ");
|
||||
cons->puts(message);
|
||||
cons->puts("\n");
|
||||
}
|
||||
|
||||
__asm__ ( "int $0e7h" );
|
||||
while (1) __asm__ ("hlt");
|
||||
}
|
||||
|
||||
extern "C" [[noreturn]] void
|
||||
__assert_fail(const char *message, const char *file, unsigned int line, const char *function)
|
||||
{
|
||||
__kernel_assert(file, line, message);
|
||||
__asm__ ( "int $0xe4" );
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ global _start:function (_start.end - _start)
|
||||
_start:
|
||||
cli
|
||||
|
||||
mov rsp, stack_end
|
||||
push 0 ; signal end of stack with 0 return address
|
||||
push 0 ; and a few extra entries in case of stack
|
||||
push 0 ; problems
|
||||
push 0
|
||||
mov rsp, idle_stack_end
|
||||
mov qword [rsp + 0x00], 0 ; signal end of stack with 0 return address
|
||||
mov qword [rsp + 0x08], 0 ; and a few extra entries in case of stack
|
||||
mov qword [rsp + 0x10], 0 ; problems
|
||||
mov qword [rsp + 0x18], 0
|
||||
|
||||
mov rbp, rsp
|
||||
extern kernel_main
|
||||
@@ -47,6 +47,9 @@ interrupts_disable:
|
||||
|
||||
section .bss
|
||||
align 0x100
|
||||
stack_begin:
|
||||
resb 0x4000 ; 16KiB stack space
|
||||
stack_end:
|
||||
idle_stack_begin:
|
||||
resb 0x1000 ; 4KiB stack space
|
||||
|
||||
global idle_stack_end
|
||||
idle_stack_end:
|
||||
resq 4
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "kutil/coord.h"
|
||||
#include "kutil/guid.h"
|
||||
#include "kutil/memory.h"
|
||||
#include "kutil/printf.h"
|
||||
#include "console.h"
|
||||
#include "font.h"
|
||||
#include "screen.h"
|
||||
@@ -278,100 +279,9 @@ console::putc(char c)
|
||||
void console::vprintf(const char *fmt, va_list args)
|
||||
{
|
||||
static const unsigned buf_size = 256;
|
||||
char buffer[256];
|
||||
|
||||
const char *r = fmt;
|
||||
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();
|
||||
char buffer[buf_size];
|
||||
vsnprintf_(buffer, buf_size, fmt, args);
|
||||
puts(buffer);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "cpu.h"
|
||||
#include "log.h"
|
||||
|
||||
cpu_data bsp_cpu_data;
|
||||
|
||||
inline static void
|
||||
__cpuid(
|
||||
uint32_t leaf,
|
||||
|
||||
@@ -2,15 +2,27 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct process;
|
||||
|
||||
struct cpu_state
|
||||
{
|
||||
uint64_t ds;
|
||||
uint64_t r15, r14, r13, r12, r11, r10, r9, r8;
|
||||
uint64_t rdi, rsi, rbp, rbx, rdx, rcx, rax;
|
||||
uint64_t interrupt, errorcode;
|
||||
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
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -4,56 +4,65 @@
|
||||
#include "gdt.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
|
||||
print_regs(const cpu_state ®s)
|
||||
{
|
||||
console *cons = console::get();
|
||||
|
||||
print_reg("rax", regs.rax);
|
||||
print_reg("rbx", regs.rbx);
|
||||
print_reg("rcx", regs.rcx);
|
||||
print_reg("rdx", regs.rdx);
|
||||
print_reg("rdi", regs.rdi);
|
||||
print_reg("rsi", regs.rsi);
|
||||
uint64_t cr2 = 0;
|
||||
__asm__ __volatile__ ("mov %%cr2, %0" : "=r"(cr2));
|
||||
|
||||
print_regL("rax", regs.rax);
|
||||
print_regM("rbx", regs.rbx);
|
||||
print_regR("rcx", regs.rcx);
|
||||
print_regL("rdx", regs.rdx);
|
||||
print_regM("rdi", regs.rdi);
|
||||
print_regR("rsi", regs.rsi);
|
||||
|
||||
cons->puts("\n");
|
||||
print_reg(" r8", regs.r8);
|
||||
print_reg(" r9", regs.r9);
|
||||
print_reg("r10", regs.r10);
|
||||
print_reg("r11", regs.r11);
|
||||
print_reg("r12", regs.r12);
|
||||
print_reg("r13", regs.r13);
|
||||
print_reg("r14", regs.r14);
|
||||
print_reg("r15", regs.r15);
|
||||
print_regL(" r8", regs.r8);
|
||||
print_regM(" r9", regs.r9);
|
||||
print_regR("r10", regs.r10);
|
||||
print_regL("r11", regs.r11);
|
||||
print_regM("r12", regs.r12);
|
||||
print_regR("r13", regs.r13);
|
||||
print_regL("r14", regs.r14);
|
||||
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");
|
||||
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
|
||||
print_stacktrace(int skip)
|
||||
{
|
||||
console *cons = console::get();
|
||||
int frame = 0;
|
||||
uint64_t bp = get_frame(skip);
|
||||
while (bp) {
|
||||
cons->printf(" frame %2d: %lx\n", frame, bp);
|
||||
bp = get_frame(++frame + skip);
|
||||
|
||||
frame *fp = nullptr;
|
||||
int fi = -skip;
|
||||
__asm__ __volatile__ ( "mov %%rbp, %0" : "=r" (fp) );
|
||||
|
||||
while (fp && fp->return_addr) {
|
||||
if (fi++ >= 0)
|
||||
cons->printf(" frame %2d: %lx\n", fi-1, fp->return_addr);
|
||||
fp = fp->prev;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,18 @@ extern "C" {
|
||||
uintptr_t get_rsp();
|
||||
uintptr_t get_rip();
|
||||
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 ®s);
|
||||
void print_stack(const cpu_state ®s);
|
||||
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));
|
||||
|
||||
|
||||
@@ -8,24 +8,13 @@ get_rip:
|
||||
pop rax ; do the same thing as 'ret', except with 'jmp'
|
||||
jmp rax ; with the return address still in rax
|
||||
|
||||
global get_gsbase
|
||||
get_gsbase:
|
||||
rdgsbase rax
|
||||
ret
|
||||
|
||||
global _halt
|
||||
_halt:
|
||||
hlt
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
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
|
||||
{
|
||||
@@ -59,8 +59,13 @@ void irq4_callback(void *)
|
||||
}
|
||||
|
||||
|
||||
device_manager::device_manager(const void *root_table) :
|
||||
m_lapic(nullptr)
|
||||
device_manager::device_manager(const void *root_table, kutil::allocator &alloc) :
|
||||
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.");
|
||||
|
||||
@@ -93,7 +98,7 @@ device_manager::device_manager(const void *root_table) :
|
||||
ioapic *
|
||||
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
|
||||
@@ -148,26 +153,44 @@ device_manager::load_apic(const acpi_apic *apic)
|
||||
uint8_t const *p = apic->controller_data;
|
||||
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) {
|
||||
const uint8_t type = p[0];
|
||||
const uint8_t length = p[1];
|
||||
if (type == 1) {
|
||||
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);
|
||||
m_ioapics.append(new ioapic(base, base_gsr));
|
||||
m_ioapics.emplace(base, base_gsr);
|
||||
}
|
||||
p += length;
|
||||
}
|
||||
|
||||
// Pass two: configure APIC objects
|
||||
// Pass three: configure APIC objects
|
||||
p = apic->controller_data;
|
||||
while (p < end) {
|
||||
const uint8_t type = p[0];
|
||||
const uint8_t length = p[1];
|
||||
|
||||
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
|
||||
break;
|
||||
|
||||
@@ -180,7 +203,7 @@ device_manager::load_apic(const acpi_apic *apic)
|
||||
source, gsi, (flags & 0x3), ((flags >> 2) & 0x3));
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -206,10 +229,10 @@ device_manager::load_apic(const acpi_apic *apic)
|
||||
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) {
|
||||
case 2: break;
|
||||
default: m_ioapics[0]->mask(i, false);
|
||||
default: m_ioapics[0].mask(i, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
/// \file device_manager.h
|
||||
/// The device manager definition
|
||||
#include "kutil/vector.h"
|
||||
#include "apic.h"
|
||||
#include "pci.h"
|
||||
|
||||
struct acpi_xsdt;
|
||||
struct acpi_apic;
|
||||
struct acpi_mcfg;
|
||||
class block_device;
|
||||
class lapic;
|
||||
class ioapic;
|
||||
|
||||
using irq_callback = void (*)(void *);
|
||||
|
||||
@@ -19,8 +18,9 @@ class device_manager
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
/// \arg root_table Pointer to the ACPI RSDP
|
||||
device_manager(const void *root_table);
|
||||
/// \arg root_table Pointer to the ACPI RSDP
|
||||
/// \arg alloc Allocator for device arrays
|
||||
device_manager(const void *root_table, kutil::allocator &alloc);
|
||||
|
||||
/// Get the system global device manager.
|
||||
/// \returns A reference to the system device manager
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
void bad_irq(uint8_t irq);
|
||||
|
||||
lapic *m_lapic;
|
||||
kutil::vector<ioapic *> m_ioapics;
|
||||
kutil::vector<ioapic> m_ioapics;
|
||||
|
||||
kutil::vector<pci_group> m_pci;
|
||||
kutil::vector<pci_device> m_devices;
|
||||
|
||||
93
src/kernel/frame_allocator.cpp
Normal file
93
src/kernel/frame_allocator.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
70
src/kernel/frame_allocator.h
Normal file
70
src/kernel/frame_allocator.h
Normal 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;
|
||||
@@ -184,18 +184,25 @@ gdt_init()
|
||||
}
|
||||
|
||||
void
|
||||
gdt_dump()
|
||||
gdt_dump(int index)
|
||||
{
|
||||
const table_ptr &table = g_gdtr;
|
||||
|
||||
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);
|
||||
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 =
|
||||
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 =
|
||||
(gdt[i].base_high << 24) |
|
||||
(gdt[i].base_mid << 16) |
|
||||
@@ -233,17 +240,25 @@ gdt_dump()
|
||||
}
|
||||
|
||||
void
|
||||
idt_dump()
|
||||
idt_dump(int index)
|
||||
{
|
||||
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);
|
||||
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 =
|
||||
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 =
|
||||
(static_cast<uint64_t>(idt[i].base_high) << 32) |
|
||||
(static_cast<uint64_t>(idt[i].base_mid) << 16) |
|
||||
|
||||
@@ -25,7 +25,9 @@ void tss_set_stack(int ring, uintptr_t rsp);
|
||||
uintptr_t tss_get_stack(int ring);
|
||||
|
||||
/// 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
|
||||
void idt_dump();
|
||||
/// \arg index Which entry to print, or -1 for all entries
|
||||
void idt_dump(int index = -1);
|
||||
|
||||
@@ -18,9 +18,8 @@ static const uint16_t PIC2 = 0xa0;
|
||||
extern "C" {
|
||||
void _halt();
|
||||
|
||||
uintptr_t isr_handler(uintptr_t, cpu_state*);
|
||||
uintptr_t irq_handler(uintptr_t, cpu_state*);
|
||||
uintptr_t syscall_handler(uintptr_t, cpu_state);
|
||||
void isr_handler(cpu_state*);
|
||||
void irq_handler(cpu_state*);
|
||||
|
||||
#define ISR(i, name) extern void name ();
|
||||
#define EISR(i, name) extern void name ();
|
||||
@@ -104,13 +103,45 @@ interrupts_init()
|
||||
log::info(logs::boot, "Interrupts enabled.");
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
isr_handler(uintptr_t return_rsp, cpu_state *regs)
|
||||
void
|
||||
isr_handler(cpu_state *regs)
|
||||
{
|
||||
console *cons = console::get();
|
||||
|
||||
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: {
|
||||
cons->set_color(9);
|
||||
cons->puts("\nGeneral Protection Fault:\n");
|
||||
@@ -124,13 +155,13 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
|
||||
switch ((regs->errorcode & 0x07) >> 1) {
|
||||
case 0:
|
||||
cons->printf(" GDT[%x]\n", index);
|
||||
gdt_dump();
|
||||
gdt_dump(index);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
case 3:
|
||||
cons->printf(" IDT[%x]\n", index);
|
||||
idt_dump();
|
||||
idt_dump(index);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -145,41 +176,35 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
|
||||
print_stacktrace(2);
|
||||
print_stack(*regs);
|
||||
*/
|
||||
|
||||
}
|
||||
_halt();
|
||||
break;
|
||||
|
||||
case isr::isrPageFault: {
|
||||
cons->set_color(11);
|
||||
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;
|
||||
uintptr_t cr2 = 0;
|
||||
__asm__ __volatile__ ("mov %%cr2, %0" : "=r"(cr2));
|
||||
print_reg("cr2", cr2);
|
||||
|
||||
print_reg("rsp", regs->user_rsp);
|
||||
print_reg("rip", regs->rip);
|
||||
if (!page_manager::get()->fault_handler(cr2)) {
|
||||
cons->set_color(11);
|
||||
cons->puts("\nPage Fault:\n");
|
||||
cons->set_color();
|
||||
|
||||
cons->puts("\n");
|
||||
//print_stacktrace(2);
|
||||
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");
|
||||
print_regs(*regs);
|
||||
print_stacktrace(2);
|
||||
_halt();
|
||||
}
|
||||
}
|
||||
_halt();
|
||||
break;
|
||||
|
||||
case isr::isrTimer: {
|
||||
scheduler &s = scheduler::get();
|
||||
return_rsp = s.tick(return_rsp);
|
||||
}
|
||||
case isr::isrTimer:
|
||||
scheduler::get().tick();
|
||||
break;
|
||||
|
||||
case isr::isrLINT0:
|
||||
@@ -198,14 +223,15 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
|
||||
_halt();
|
||||
break;
|
||||
|
||||
case isr::isrSyscall: {
|
||||
return_rsp = syscall_dispatch(return_rsp, *regs);
|
||||
}
|
||||
/*
|
||||
case isr::isrSyscall:
|
||||
syscall_dispatch(regs);
|
||||
break;
|
||||
*/
|
||||
|
||||
case isr::isrSpurious:
|
||||
// No EOI for the spurious interrupt
|
||||
return return_rsp;
|
||||
return;
|
||||
|
||||
case isr::isrIgnore0:
|
||||
case isr::isrIgnore1:
|
||||
@@ -242,16 +268,14 @@ isr_handler(uintptr_t return_rsp, cpu_state *regs)
|
||||
regs->interrupt, regs->errorcode);
|
||||
|
||||
print_regs(*regs);
|
||||
//print_stacktrace(2);
|
||||
print_stacktrace(2);
|
||||
_halt();
|
||||
}
|
||||
*reinterpret_cast<uint32_t *>(0xffffff80fee000b0) = 0;
|
||||
|
||||
return return_rsp;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
irq_handler(uintptr_t return_rsp, cpu_state *regs)
|
||||
void
|
||||
irq_handler(cpu_state *regs)
|
||||
{
|
||||
console *cons = console::get();
|
||||
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;
|
||||
return return_rsp;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
syscall_handler(uintptr_t return_rsp, cpu_state regs)
|
||||
{
|
||||
return syscall_dispatch(return_rsp, regs);
|
||||
}
|
||||
|
||||
@@ -3,39 +3,36 @@
|
||||
extern isr_handler
|
||||
global isr_handler_prelude
|
||||
isr_handler_prelude:
|
||||
push_all_and_segments
|
||||
push_all
|
||||
check_swap_gs
|
||||
|
||||
mov rdi, rsp
|
||||
mov rsi, rsp
|
||||
call isr_handler
|
||||
mov rsp, rax
|
||||
|
||||
pop_all_and_segments
|
||||
|
||||
add rsp, 16 ; because the ISRs added err/num
|
||||
sti
|
||||
iretq
|
||||
jmp isr_handler_return
|
||||
|
||||
extern irq_handler
|
||||
global irq_handler_prelude
|
||||
irq_handler_prelude:
|
||||
push_all_and_segments
|
||||
push_all
|
||||
check_swap_gs
|
||||
|
||||
mov rdi, rsp
|
||||
mov rsi, rsp
|
||||
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
|
||||
sti
|
||||
iretq
|
||||
|
||||
%macro EMIT_ISR 2
|
||||
global %1
|
||||
%1:
|
||||
cli
|
||||
push 0
|
||||
push %2
|
||||
jmp isr_handler_prelude
|
||||
@@ -44,7 +41,6 @@ irq_handler_prelude:
|
||||
%macro EMIT_EISR 2
|
||||
global %1
|
||||
%1:
|
||||
cli
|
||||
push %2
|
||||
jmp isr_handler_prelude
|
||||
%endmacro
|
||||
@@ -52,7 +48,6 @@ irq_handler_prelude:
|
||||
%macro EMIT_IRQ 2
|
||||
global %1
|
||||
%1:
|
||||
cli
|
||||
push 0
|
||||
push %2
|
||||
jmp irq_handler_prelude
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
%include "push_all.inc"
|
||||
|
||||
extern load_process
|
||||
extern load_process_image
|
||||
|
||||
global ramdisk_process_loader
|
||||
ramdisk_process_loader:
|
||||
|
||||
; 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
|
||||
; 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
|
||||
; acts both as the cpu_state parameter to load_process_image, and the
|
||||
; saved state for the following iretq
|
||||
|
||||
pop_all_and_segments
|
||||
add rsp, 16 ; because the ISRs add err/num
|
||||
pop rdi ; the address of the program image
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -1,107 +1,55 @@
|
||||
#include <type_traits>
|
||||
#include "kutil/assert.h"
|
||||
#include "kutil/memory.h"
|
||||
#include "console.h"
|
||||
#include "log.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
|
||||
static const uint64_t default_enabled[] = {0x00, 0xff, 0xff, 0xff};
|
||||
static const uint8_t level_colors[] = {0x07, 0x0f, 0x0b, 0x09};
|
||||
static uint8_t log_buffer[0x10000];
|
||||
static log::logger g_logger(log_buffer, sizeof(log_buffer));
|
||||
|
||||
static const char *levels[] = {"debug", " info", " warn", "error", "fatal"};
|
||||
static const char *areas[] = {
|
||||
"boot ",
|
||||
"memory",
|
||||
"apic ",
|
||||
"device",
|
||||
"driver",
|
||||
"file ",
|
||||
"task ",
|
||||
"paging",
|
||||
static const uint8_t level_colors[] = {0x07, 0x07, 0x0f, 0x0b, 0x09};
|
||||
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
||||
log log::s_log;
|
||||
|
||||
log::log() : m_cons(nullptr)
|
||||
static void
|
||||
output_log(log::area_t area, log::level severity, const char *message)
|
||||
{
|
||||
kassert(0, "Invalid log constructor");
|
||||
}
|
||||
|
||||
log::log(console *cons) :
|
||||
m_cons(cons)
|
||||
{
|
||||
const int num_levels = static_cast<int>(level::max) - 1;
|
||||
for (int i = 0; i < num_levels; ++i)
|
||||
m_enabled[i] = default_enabled[i];
|
||||
auto *cons = console::get();
|
||||
cons->set_color(level_colors[static_cast<int>(severity)]);
|
||||
cons->printf("%7s %5s: %s\n",
|
||||
g_logger.area_name(area),
|
||||
g_logger.level_name(severity),
|
||||
message);
|
||||
cons->set_color();
|
||||
}
|
||||
|
||||
void
|
||||
log::init(console *cons)
|
||||
logger_task()
|
||||
{
|
||||
new (&s_log) log(cons);
|
||||
log::info(logs::boot, "Logging system initialized.");
|
||||
}
|
||||
uint8_t buffer[257];
|
||||
auto *ent = reinterpret_cast<log::logger::entry *>(buffer);
|
||||
auto *cons = console::get();
|
||||
|
||||
static inline uint64_t
|
||||
bit_mask(logs area) { return 1 << static_cast<uint64_t>(area); }
|
||||
g_logger.set_immediate(nullptr);
|
||||
log::info(logs::task, "Starting kernel logger task");
|
||||
|
||||
void
|
||||
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]);
|
||||
scheduler &s = scheduler::get();
|
||||
|
||||
for (under_t i = 0; i < max; ++i) {
|
||||
if (i >= at)
|
||||
s_log.m_enabled[i] |= bit_mask(type);
|
||||
else
|
||||
s_log.m_enabled[i] &= ~bit_mask(type);
|
||||
while (true) {
|
||||
if(g_logger.get_entry(buffer, sizeof(buffer))) {
|
||||
buffer[ent->bytes] = 0;
|
||||
cons->set_color(level_colors[static_cast<int>(ent->severity)]);
|
||||
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
|
||||
log::trylog<log::level::fatal>(logs area, const char *fmt, ...)
|
||||
void logger_init()
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
s_log.output(level::fatal, area, fmt, args);
|
||||
va_end(args);
|
||||
while(1) __asm__ ("hlt");
|
||||
new (&g_logger) log::logger(log_buffer, sizeof(log_buffer));
|
||||
g_logger.set_immediate(output_log);
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
@@ -1,52 +1,9 @@
|
||||
#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
|
||||
{
|
||||
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;
|
||||
};
|
||||
void logger_init();
|
||||
void logger_task();
|
||||
|
||||
@@ -11,16 +11,15 @@
|
||||
#include "gdt.h"
|
||||
#include "interrupts.h"
|
||||
#include "io.h"
|
||||
#include "kernel_data.h"
|
||||
#include "kernel_args.h"
|
||||
#include "log.h"
|
||||
#include "page_manager.h"
|
||||
#include "scheduler.h"
|
||||
#include "screen.h"
|
||||
#include "serial.h"
|
||||
#include "syscall.h"
|
||||
|
||||
extern "C" {
|
||||
void kernel_main(popcorn_data *header);
|
||||
void kernel_main(kernel_args *header);
|
||||
void *__bss_start, *__bss_end;
|
||||
}
|
||||
|
||||
@@ -37,37 +36,21 @@ init_console()
|
||||
cons->set_color(0x08, 0x00);
|
||||
cons->puts(GIT_VERSION " booting...\n");
|
||||
|
||||
log::init(cons);
|
||||
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);
|
||||
logger_init();
|
||||
}
|
||||
|
||||
void
|
||||
kernel_main(popcorn_data *header)
|
||||
kernel_main(kernel_args *header)
|
||||
{
|
||||
#ifdef 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;
|
||||
bool waiting = header && (header->flags && POPCORN_FLAG_DEBUG);
|
||||
while (waiting);
|
||||
#endif
|
||||
|
||||
kutil::assert_set_callback(__kernel_assert);
|
||||
|
||||
gdt_init();
|
||||
interrupts_init();
|
||||
|
||||
memory_initialize(
|
||||
kutil::allocator &heap = memory_initialize(
|
||||
header->scratch_pages,
|
||||
header->memory_map,
|
||||
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, "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());
|
||||
for (auto &f : ird.files())
|
||||
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 =
|
||||
new (&device_manager::get()) device_manager(header->acpi_table);
|
||||
new (&device_manager::get()) device_manager(header->acpi_table, heap);
|
||||
|
||||
interrupts_enable();
|
||||
|
||||
@@ -146,7 +129,9 @@ kernel_main(popcorn_data *header)
|
||||
devices->get_lapic()->calibrate_timer();
|
||||
|
||||
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()) {
|
||||
if (f.executable())
|
||||
|
||||
@@ -2,72 +2,26 @@
|
||||
#include <utility>
|
||||
#include "kutil/address_manager.h"
|
||||
#include "kutil/assert.h"
|
||||
#include "kutil/frame_allocator.h"
|
||||
#include "kutil/heap_manager.h"
|
||||
#include "kutil/heap_allocator.h"
|
||||
#include "frame_allocator.h"
|
||||
#include "io.h"
|
||||
#include "log.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::kernel_max_heap;
|
||||
using memory::kernel_offset;
|
||||
using memory::page_offset;
|
||||
|
||||
static const unsigned ident_page_flags = 0xb;
|
||||
|
||||
kutil::frame_allocator g_frame_allocator;
|
||||
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)
|
||||
{
|
||||
kassert(length % frame_size == 0,
|
||||
"Heap manager requested a fractional page.");
|
||||
|
||||
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 &>;
|
||||
}
|
||||
void * operator new(size_t size) { return g_kernel_heap.allocate(size); }
|
||||
void * operator new [] (size_t size) { return g_kernel_heap.allocate(size); }
|
||||
void operator delete (void *p) noexcept { return g_kernel_heap.free(p); }
|
||||
void operator delete [] (void *p) noexcept { return g_kernel_heap.free(p); }
|
||||
|
||||
enum class efi_memory_type : uint32_t
|
||||
{
|
||||
@@ -107,100 +61,114 @@ struct efi_memory_descriptor
|
||||
uint64_t flags;
|
||||
};
|
||||
|
||||
static const efi_memory_descriptor *
|
||||
desc_incr(const efi_memory_descriptor *d, size_t desc_length)
|
||||
struct memory_map
|
||||
{
|
||||
return reinterpret_cast<const efi_memory_descriptor *>(
|
||||
reinterpret_cast<const uint8_t *>(d) + desc_length);
|
||||
}
|
||||
memory_map(const void *efi_map, size_t map_length, size_t desc_length) :
|
||||
efi_map(efi_map), map_length(map_length), desc_length(desc_length) {}
|
||||
|
||||
void
|
||||
gather_block_lists(
|
||||
block_allocator &allocator,
|
||||
frame_block_list &used,
|
||||
frame_block_list &free,
|
||||
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);
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
iterator(const memory_map &map, efi_memory_descriptor const *item) :
|
||||
map(map), item(item) {}
|
||||
|
||||
while (desc < end) {
|
||||
auto *block = allocator.pop();
|
||||
block->address = desc->physical_start;
|
||||
block->count = desc->pages;
|
||||
bool block_used;
|
||||
|
||||
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;
|
||||
inline efi_memory_descriptor const * operator*() const { return item; }
|
||||
inline bool operator!=(const iterator &other) { return item != other.item; }
|
||||
inline iterator & operator++() {
|
||||
item = kutil::offset_pointer(item, map.desc_length);
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (block_used)
|
||||
used.push_back(block);
|
||||
else
|
||||
free.push_back(block);
|
||||
private:
|
||||
const memory_map ↦
|
||||
efi_memory_descriptor const *item;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
// make sure the options we want in CR4 are set
|
||||
uint64_t cr4;
|
||||
__asm__ __volatile__ ( "mov %%cr4, %0" : "=r" (cr4) );
|
||||
cr4 |= 0x00080; // Enable global pages
|
||||
cr4 |= 0x00200; // Enable FXSAVE/FXRSTOR
|
||||
cr4 |= 0x20000; // Enable PCIDs
|
||||
cr4 |=
|
||||
0x000080 | // Enable global pages
|
||||
0x000200 | // Enable FXSAVE/FXRSTOR
|
||||
0x010000 | // Enable FSGSBASE
|
||||
0x020000 | // Enable PCIDs
|
||||
0;
|
||||
__asm__ __volatile__ ( "mov %0, %%cr4" :: "r" (cr4) );
|
||||
|
||||
// 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();
|
||||
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;
|
||||
uint64_t used_pages = 2; // starts with PML4 + offset PDP
|
||||
page_consumer allocator(scratch_virt, scratch_pages, used_pages);
|
||||
memory_bootstrap bootstrap {memory_map, map_length, desc_length};
|
||||
|
||||
block_allocator block_slab(frame_size, allocator);
|
||||
frame_block_list used;
|
||||
frame_block_list free;
|
||||
// Now tell the frame allocator what's free
|
||||
frame_allocator *fa = new (&g_frame_allocator) frame_allocator;
|
||||
bootstrap.add_free_frames(*fa);
|
||||
|
||||
gather_block_lists(block_slab, used, free, memory_map, map_length, desc_length);
|
||||
block_slab.allocate(); // Make sure we have extra
|
||||
// Build an initial address manager that we'll copy into the real
|
||||
// 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
|
||||
block_slab.append(frame_block::consolidate(free));
|
||||
// Add the heap into the address manager
|
||||
uintptr_t heap_start = page_offset - kernel_max_heap;
|
||||
init_am.mark(heap_start, kernel_max_heap);
|
||||
|
||||
region_allocator region_slab(frame_size, allocator);
|
||||
region_slab.allocate(); // Allocate some buddy regions for the address_manager
|
||||
kutil::allocator *heap_alloc =
|
||||
new (&g_kernel_heap) kutil::heap_allocator(heap_start, kernel_max_heap);
|
||||
|
||||
// Copy everything into the real address manager
|
||||
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
|
||||
// (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));
|
||||
pml4->entries[511] = reinterpret_cast<uintptr_t>(id_pdp) | 0x10b;
|
||||
|
||||
kutil::frame_allocator *fa =
|
||||
new (&g_frame_allocator) kutil::frame_allocator(std::move(block_slab));
|
||||
page_manager *pm = new (&g_page_manager) page_manager(*fa, *am);
|
||||
bootstrap.page_in_kernel(*pm, pml4);
|
||||
|
||||
// Give the rest to the page_manager's cache for use in page_in
|
||||
pm->free_table_pages(
|
||||
reinterpret_cast<void *>(allocator.current),
|
||||
allocator.left());
|
||||
// Reclaim the old PML4
|
||||
fa->free(scratch_phys, 1);
|
||||
|
||||
for (auto *block : used) {
|
||||
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);
|
||||
return *heap_alloc;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
#include "kutil/assert.h"
|
||||
#include "console.h"
|
||||
#include "io.h"
|
||||
#include "log.h"
|
||||
#include "page_manager.h"
|
||||
|
||||
using memory::frame_size;
|
||||
using memory::kernel_offset;
|
||||
using memory::page_offset;
|
||||
using memory::page_mappable;
|
||||
|
||||
extern kutil::frame_allocator g_frame_allocator;
|
||||
extern kutil::address_manager g_kernel_address_manager;
|
||||
page_manager g_page_manager(
|
||||
g_frame_allocator,
|
||||
@@ -38,7 +39,7 @@ struct free_page_header
|
||||
|
||||
|
||||
page_manager::page_manager(
|
||||
kutil::frame_allocator &frames,
|
||||
frame_allocator &frames,
|
||||
kutil::address_manager &addrs) :
|
||||
m_page_cache(nullptr),
|
||||
m_frames(frames),
|
||||
@@ -68,28 +69,60 @@ page_manager::create_process_map()
|
||||
uintptr_t
|
||||
page_manager::copy_page(uintptr_t orig)
|
||||
{
|
||||
uintptr_t virt = m_addrs.allocate(2 * frame_size);
|
||||
uintptr_t copy = 0;
|
||||
bool paged_orig = false;
|
||||
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, ©);
|
||||
kassert(n, "copy_page could not allocate page");
|
||||
|
||||
page_in(get_pml4(), orig, virt, 1);
|
||||
page_in(get_pml4(), copy, virt + frame_size, 1);
|
||||
if (page_mappable(copy)) {
|
||||
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(
|
||||
reinterpret_cast<void *>(virt + frame_size),
|
||||
reinterpret_cast<void *>(virt),
|
||||
reinterpret_cast<void *>(copy_virt),
|
||||
reinterpret_cast<void *>(orig_virt),
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
|
||||
unsigned pages_copied = 0;
|
||||
uintptr_t from_addr = 0;
|
||||
uintptr_t to_addr = 0;
|
||||
|
||||
for (int i = 0; i < max; ++i) {
|
||||
if (!from->is_present(i)) {
|
||||
to->entries[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
index[lvl] = i;
|
||||
|
||||
bool is_page =
|
||||
lvl == page_table::level::pt ||
|
||||
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;
|
||||
uintptr_t orig = from->entries[i] & ~0xfffull;
|
||||
to->entries[i] = copy_page(orig) | flags;
|
||||
pages_copied++;
|
||||
if (!pages_copied++)
|
||||
from_addr = index.addr();
|
||||
to_addr = index.addr();
|
||||
} else {
|
||||
uint16_t flags = 0;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -137,13 +178,20 @@ page_manager::copy_table(page_table *from, page_table::level lvl)
|
||||
void
|
||||
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);
|
||||
}
|
||||
|
||||
void
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -178,6 +226,7 @@ page_manager::get_table_page()
|
||||
|
||||
free_page_header *page = m_page_cache;
|
||||
m_page_cache = page->next;
|
||||
|
||||
return reinterpret_cast<page_table *>(page);
|
||||
}
|
||||
|
||||
@@ -197,6 +246,11 @@ page_manager::free_table_pages(void *pages, size_t count)
|
||||
void *
|
||||
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);
|
||||
if (!pml4) pml4 = get_pml4();
|
||||
|
||||
@@ -217,7 +271,7 @@ page_manager::map_pages(uintptr_t address, size_t count, bool user, page_table *
|
||||
}
|
||||
|
||||
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 =
|
||||
lvl == page_table::level::pml4 ?
|
||||
@@ -225,6 +279,7 @@ page_manager::unmap_table(page_table *table, page_table::level lvl, bool free)
|
||||
512;
|
||||
|
||||
uintptr_t free_start = 0;
|
||||
uintptr_t free_start_virt = 0;
|
||||
uintptr_t free_count = 0;
|
||||
|
||||
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) {
|
||||
if (!table->is_present(i)) continue;
|
||||
|
||||
index[lvl] = i;
|
||||
|
||||
bool is_page =
|
||||
lvl == page_table::level::pt ||
|
||||
table->is_large_page(lvl, i);
|
||||
|
||||
if (is_page) {
|
||||
uintptr_t frame = table->entries[i] & ~0xfffull;
|
||||
if (!free_count || free_start != frame + free_count * size) {
|
||||
if (free_count && free)
|
||||
m_frames.free(free_start, free_count * size / frame_size);
|
||||
free_start = frame;
|
||||
free_count = 1;
|
||||
if (!free_count || frame != free_start + free_count * size) {
|
||||
if (free_count && free) {
|
||||
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_count = 0;
|
||||
}
|
||||
|
||||
if (!free_count) {
|
||||
free_start = frame;
|
||||
free_start_virt = index.addr();
|
||||
}
|
||||
}
|
||||
|
||||
free_count += 1;
|
||||
} else {
|
||||
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)
|
||||
m_frames.free(free_start, free_count * size / frame_size);
|
||||
if (free_count && free) {
|
||||
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);
|
||||
|
||||
log::debug(logs::paging, "Unmapped%s lv %d table at %016lx",
|
||||
free ? " (and freed)" : "", lvl, table);
|
||||
}
|
||||
|
||||
void
|
||||
page_manager::unmap_pages(void* address, size_t count, page_table *pml4)
|
||||
{
|
||||
if (!pml4) pml4 = get_pml4();
|
||||
page_out(pml4, reinterpret_cast<uintptr_t>(address), count, true);
|
||||
if (!pml4)
|
||||
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
|
||||
@@ -279,8 +378,10 @@ page_manager::check_needs_page(page_table *table, unsigned index, bool user)
|
||||
void
|
||||
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",
|
||||
pml4, phys_addr, virt_addr, count, user, large);
|
||||
*/
|
||||
|
||||
page_table_indices idx{virt_addr};
|
||||
page_table *tables[4] = {pml4, nullptr, nullptr, nullptr};
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
#include "kutil/address_manager.h"
|
||||
#include "kutil/enum_bitfields.h"
|
||||
#include "kutil/frame_allocator.h"
|
||||
#include "kutil/linked_list.h"
|
||||
#include "kutil/slab_allocator.h"
|
||||
#include "frame_allocator.h"
|
||||
#include "kernel_memory.h"
|
||||
#include "page_table.h"
|
||||
|
||||
@@ -20,7 +20,7 @@ class page_manager
|
||||
{
|
||||
public:
|
||||
page_manager(
|
||||
kutil::frame_allocator &frames,
|
||||
frame_allocator &frames,
|
||||
kutil::address_manager &addrs);
|
||||
|
||||
/// 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.
|
||||
static inline void set_pml4(page_table *pml4)
|
||||
{
|
||||
uintptr_t p = reinterpret_cast<uintptr_t>(pml4) - memory::page_offset;
|
||||
__asm__ __volatile__ ( "mov %0, %%cr3" :: "r" (p & ~0xfffull) );
|
||||
constexpr uint64_t phys_mask = ~memory::page_offset & ~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
|
||||
@@ -62,10 +63,12 @@ public:
|
||||
/// \arg lvl Level of the given tables (default is PML4)
|
||||
/// \returns The new page table
|
||||
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.
|
||||
/// \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 user True is this memory is user-accessible
|
||||
/// \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
|
||||
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:
|
||||
/// Copy a physical page
|
||||
/// \arg orig Physical address of the page to copy
|
||||
@@ -158,15 +169,16 @@ private:
|
||||
bool free = false);
|
||||
|
||||
/// 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
|
||||
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;
|
||||
|
||||
friend void memory_initialize(uint16_t, const void *, size_t, size_t);
|
||||
friend class memory_bootstrap;
|
||||
page_manager(const page_manager &) = delete;
|
||||
};
|
||||
|
||||
@@ -199,4 +211,8 @@ page_table_align(T p)
|
||||
|
||||
|
||||
/// 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);
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
#include "kutil/heap_allocator.h"
|
||||
#include "cpu.h"
|
||||
#include "debug.h"
|
||||
#include "log.h"
|
||||
#include "process.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
|
||||
process::fork(uintptr_t in_rsp)
|
||||
process::fork()
|
||||
{
|
||||
auto &sched = scheduler::get();
|
||||
auto *child = sched.create_process();
|
||||
@@ -18,40 +30,79 @@ process::fork(uintptr_t in_rsp)
|
||||
|
||||
sched.m_runlists[child->priority].push_back(child);
|
||||
|
||||
child->rsp = in_rsp;
|
||||
|
||||
child->pml4 = page_manager::get()->copy_table(pml4);
|
||||
kassert(child->pml4, "process::fork() got null pml4");
|
||||
|
||||
child->setup_kernel_stack(kernel_stack_size, kernel_stack);
|
||||
child->rsp = child->kernel_stack + (in_rsp - kernel_stack);
|
||||
child->rsp3 = bsp_cpu_data.rsp3;
|
||||
child->setup_kernel_stack();
|
||||
|
||||
log::debug(logs::task, "Copied process %d to %d, new PML4 %016lx.",
|
||||
pid, child->pid, child->pml4);
|
||||
log::debug(logs::task, " copied stack %016lx to %016lx, rsp %016lx to %016lx.",
|
||||
kernel_stack, child->kernel_stack, in_rsp, child->rsp);
|
||||
log::debug(logs::task, "Copied process %d to %d",
|
||||
pid, child->pid);
|
||||
|
||||
// Add in the faked fork return value
|
||||
cpu_state *regs = reinterpret_cast<cpu_state *>(child->rsp);
|
||||
regs->rax = 0;
|
||||
log::debug(logs::task, " PML4 %016lx", child->pml4);
|
||||
log::debug(logs::task, " RSP3 %016lx", child->rsp3);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
kutil::memcpy(stack0, reinterpret_cast<void*>(orig), size);
|
||||
else
|
||||
kutil::memset(stack0, 0, size);
|
||||
void *stack_bottom = g_kernel_heap.allocate(initial_stack_size);
|
||||
kutil::memset(stack_bottom, 0, initial_stack_size);
|
||||
|
||||
kernel_stack_size = size;
|
||||
kernel_stack = reinterpret_cast<uintptr_t>(stack0);
|
||||
log::debug(logs::memory, "Created kernel stack at %016lx size 0x%lx",
|
||||
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
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
#include "kutil/linked_list.h"
|
||||
#include "page_manager.h"
|
||||
|
||||
typedef uint32_t pid_t;
|
||||
typedef int32_t pid_t;
|
||||
struct cpu_state;
|
||||
|
||||
|
||||
enum class process_flags : uint32_t
|
||||
@@ -32,9 +33,20 @@ enum class process_wait : uint8_t
|
||||
receive
|
||||
};
|
||||
|
||||
/// A process
|
||||
/// A 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 ppid;
|
||||
|
||||
@@ -51,17 +63,17 @@ struct process
|
||||
|
||||
uint32_t reserved1;
|
||||
|
||||
uintptr_t rsp;
|
||||
page_table *pml4;
|
||||
|
||||
uintptr_t kernel_stack;
|
||||
size_t kernel_stack_size;
|
||||
|
||||
/// Terminate this process.
|
||||
/// \arg code The return code to exit with.
|
||||
void exit(unsigned code);
|
||||
|
||||
/// Copy this process.
|
||||
/// \arg in_rsp The RSP of the calling process
|
||||
/// \returns Returns the child's pid to the parent, and
|
||||
/// 0 to the child.
|
||||
pid_t fork(uint64_t in_rsp);
|
||||
pid_t fork();
|
||||
|
||||
/// Unready this process until it gets a signal
|
||||
/// \arg sigmask A bitfield of signals to wake on
|
||||
@@ -118,13 +130,15 @@ struct process
|
||||
private:
|
||||
friend class scheduler;
|
||||
|
||||
/// Set up a new kernel stack for this process, optionally copying the
|
||||
/// given stack. Sets the kernel stack on the process object, but also
|
||||
/// returns it.
|
||||
/// \arg size Size of the stack to allocate
|
||||
/// \arg orig Address of a stack to copy, or 0 for no copying.
|
||||
/// \returns The address of the new stack as a pointer
|
||||
void * setup_kernel_stack(size_t size, uintptr_t orig);
|
||||
/// Set up a new empty kernel stack for this process. Sets rsp0 on this
|
||||
/// process object, but also returns it.
|
||||
/// \returns The new rsp0 as a pointer
|
||||
void * setup_kernel_stack();
|
||||
|
||||
/// Initialize this process' kenrel stack with a fake return segment for
|
||||
/// 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>;
|
||||
|
||||
@@ -1,48 +1,82 @@
|
||||
%macro push_all_and_segments 0
|
||||
push rax
|
||||
push rcx
|
||||
push rdx
|
||||
push rbx
|
||||
push rbp
|
||||
push rsi
|
||||
push rdi
|
||||
struc REGS
|
||||
.r15 resq 1 ; 0x00
|
||||
.r14 resq 1 ; 0x08
|
||||
.r13 resq 1 ; 0x10
|
||||
.r12 resq 1 ; 0x18
|
||||
.r11 resq 1 ; 0x20
|
||||
.r10 resq 1 ; 0x28
|
||||
.r9 resq 1 ; 0x30
|
||||
.r8 resq 1 ; 0x38
|
||||
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
.rdi resq 1 ; 0x40
|
||||
.rsi resq 1 ; 0x48
|
||||
.rbp resq 1 ; 0x50
|
||||
.rbx resq 1 ; 0x58
|
||||
.rdx resq 1 ; 0x60
|
||||
.rcx resq 1 ; 0x68
|
||||
.rax resq 1 ; 0x70
|
||||
|
||||
mov ax, ds
|
||||
push rax
|
||||
.int resq 1 ; 0x78
|
||||
.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
|
||||
|
||||
%macro pop_all_and_segments 0
|
||||
pop rax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
%macro pop_all 0
|
||||
mov rax, [rsp + REGS.rax]
|
||||
mov rcx, [rsp + REGS.rcx]
|
||||
mov rdx, [rsp + REGS.rdx]
|
||||
mov rbx, [rsp + REGS.rbx]
|
||||
mov rbp, [rsp + REGS.rbp]
|
||||
mov rsi, [rsp + REGS.rsi]
|
||||
mov rdi, [rsp + REGS.rdi]
|
||||
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
mov r8, [rsp + REGS.r8 ]
|
||||
mov r9, [rsp + REGS.r9 ]
|
||||
mov r10, [rsp + REGS.r10]
|
||||
mov r11, [rsp + REGS.r11]
|
||||
mov r12, [rsp + REGS.r12]
|
||||
mov r13, [rsp + REGS.r13]
|
||||
mov r14, [rsp + REGS.r14]
|
||||
mov r15, [rsp + REGS.r15]
|
||||
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rbp
|
||||
pop rbx
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rax
|
||||
add rsp, regs_extra_size
|
||||
%endmacro
|
||||
|
||||
%macro check_swap_gs 0
|
||||
mov rax, [rsp+0x90]
|
||||
and rax, 0x03 ; mask out the RPL
|
||||
cmp rax, 0x03
|
||||
jne %%noswapgs
|
||||
swapgs
|
||||
%%noswapgs:
|
||||
%endmacro
|
||||
|
||||
; vim: ft=asm
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "apic.h"
|
||||
#include "console.h"
|
||||
#include "cpu.h"
|
||||
#include "debug.h"
|
||||
#include "gdt.h"
|
||||
#include "interrupts.h"
|
||||
#include "io.h"
|
||||
@@ -15,30 +16,33 @@
|
||||
|
||||
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_int = 0x202;
|
||||
|
||||
extern "C" {
|
||||
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_next_pid(1)
|
||||
m_next_pid(1),
|
||||
m_process_allocator(alloc)
|
||||
{
|
||||
auto *idle = m_process_allocator.pop();
|
||||
|
||||
uint8_t last_pri = num_priorities - 1;
|
||||
|
||||
// The kernel idle task, also the thread we're in now
|
||||
idle->pid = 0;
|
||||
idle->ppid = 0;
|
||||
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->quanta = process_quanta;
|
||||
idle->flags =
|
||||
@@ -48,10 +52,13 @@ scheduler::scheduler(lapic *apic) :
|
||||
|
||||
m_runlists[last_pri].push_back(idle);
|
||||
m_current = idle;
|
||||
|
||||
bsp_cpu_data.rsp0 = idle->rsp0;
|
||||
bsp_cpu_data.tcb = idle;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// We're now in the process space for this process, allocate memory for the
|
||||
// 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
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
state.rip = image.entrypoint();
|
||||
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 *
|
||||
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();
|
||||
proc->pid = m_next_pid++;
|
||||
proc->pid = pid ? pid : m_next_pid++;
|
||||
proc->priority = default_priority;
|
||||
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
|
||||
|
||||
// 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
|
||||
void *stack0 = proc->setup_kernel_stack(stack_size, 0);
|
||||
// Create an initial kernel stack space
|
||||
void *sp0 = proc->setup_kernel_stack();
|
||||
uintptr_t *stack = reinterpret_cast<uintptr_t *>(sp0) - 7;
|
||||
|
||||
// Stack grows down, point to the end
|
||||
void *sp0 = kutil::offset_pointer(stack0, stack_size);
|
||||
// Pass args to ramdisk_process_loader on the stack
|
||||
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
|
||||
// to iret to:
|
||||
state->ds = state->ss = ss;
|
||||
state->cs = cs;
|
||||
state->rflags = rflags_int;
|
||||
state->rip = 0; // to be filled by the loader
|
||||
state->user_rsp = initial_stack;
|
||||
// Arguments for iret - rip will be pushed on before these
|
||||
stack[3] = cs;
|
||||
stack[4] = rflags_int;
|
||||
stack[5] = initial_stack;
|
||||
stack[6] = ss;
|
||||
|
||||
// Next state in the stack is the loader's kernel stack. The scheduler will
|
||||
// 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->rsp3 = initial_stack;
|
||||
proc->quanta = process_quanta;
|
||||
proc->flags =
|
||||
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);
|
||||
|
||||
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, " RSP0 %016lx", state);
|
||||
log::debug(logs::task, " PML4 %016lx", pml4);
|
||||
log::debug(logs::task, " RSP %016lx", proc->rsp);
|
||||
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
|
||||
scheduler::start()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -233,16 +259,15 @@ void scheduler::prune(uint64_t now)
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
scheduler::schedule(uintptr_t rsp0)
|
||||
void
|
||||
scheduler::schedule()
|
||||
{
|
||||
|
||||
// TODO: lol a real clock
|
||||
static uint64_t now = 0;
|
||||
|
||||
m_current->rsp = rsp0;
|
||||
m_runlists[m_current->priority].remove(m_current);
|
||||
pid_t lastpid = m_current->pid;
|
||||
|
||||
m_runlists[m_current->priority].remove(m_current);
|
||||
if (m_current->flags && process_flags::ready) {
|
||||
m_runlists[m_current->priority].push_back(m_current);
|
||||
} else {
|
||||
@@ -258,32 +283,24 @@ scheduler::schedule(uintptr_t rsp0)
|
||||
}
|
||||
|
||||
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
|
||||
tss_set_stack(0, rsp0 + sizeof(cpu_state));
|
||||
wrmsr(msr::ia32_kernel_gs_base, rsp0);
|
||||
if (lastpid != m_current->pid) {
|
||||
task_switch(m_current);
|
||||
|
||||
// Swap page tables
|
||||
page_table *pml4 = m_current->pml4;
|
||||
page_manager::set_pml4(pml4);
|
||||
|
||||
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;
|
||||
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)" : "");
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
scheduler::tick(uintptr_t rsp0)
|
||||
void
|
||||
scheduler::tick()
|
||||
{
|
||||
if (--m_current->quanta == 0) {
|
||||
m_current->quanta = process_quanta;
|
||||
rsp0 = schedule(rsp0);
|
||||
schedule();
|
||||
}
|
||||
m_apic->reset_timer(m_tick_count);
|
||||
return rsp0;
|
||||
}
|
||||
|
||||
process_node *
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/// The task scheduler and related definitions
|
||||
|
||||
#include <stdint.h>
|
||||
#include "kutil/allocator.h"
|
||||
#include "kutil/slab_allocator.h"
|
||||
#include "process.h"
|
||||
|
||||
@@ -10,7 +11,9 @@ class lapic;
|
||||
struct page_table;
|
||||
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
|
||||
@@ -21,14 +24,15 @@ public:
|
||||
static const uint8_t default_priority = num_priorities / 2;
|
||||
|
||||
/// 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
|
||||
static const uint16_t process_quanta = 10;
|
||||
static const uint16_t process_quanta = 100;
|
||||
|
||||
/// Constructor.
|
||||
/// \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.
|
||||
/// \arg name Name of the program image
|
||||
@@ -36,14 +40,17 @@ public:
|
||||
/// \arg size Size of the program image, in bytes
|
||||
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
|
||||
/// timer interrupts or other preemption methods.
|
||||
void start();
|
||||
|
||||
/// Run the scheduler, possibly switching to a new task
|
||||
/// \arg rsp0 The stack pointer of the current interrupt handler
|
||||
/// \returns The stack pointer to switch to
|
||||
uintptr_t schedule(uintptr_t rsp0);
|
||||
void schedule();
|
||||
|
||||
/// Get the current process.
|
||||
/// \returns A pointer to the current process' process struct
|
||||
@@ -60,18 +67,17 @@ public:
|
||||
|
||||
private:
|
||||
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;
|
||||
|
||||
/// Create a new process object. This process will have its pid
|
||||
/// set but nothing else.
|
||||
/// \arg pid The pid to give the process (0 for automatic)
|
||||
/// \returns The new process object
|
||||
process_node * create_process();
|
||||
process_node * create_process(pid_t pid = 0);
|
||||
|
||||
/// Handle a timer tick
|
||||
/// \arg rsp0 The stack pointer of the current interrupt handler
|
||||
/// \returns The stack pointer to switch to
|
||||
uintptr_t tick(uintptr_t rsp0);
|
||||
void tick();
|
||||
|
||||
void prune(uint64_t now);
|
||||
|
||||
|
||||
@@ -1,16 +1,125 @@
|
||||
#include "console.h"
|
||||
#include "cpu.h"
|
||||
#include "debug.h"
|
||||
#include "log.h"
|
||||
#include "msr.h"
|
||||
#include "process.h"
|
||||
#include "scheduler.h"
|
||||
#include "syscall.h"
|
||||
|
||||
extern "C" {
|
||||
void _halt();
|
||||
void syscall_invalid(uint64_t call);
|
||||
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
|
||||
syscall_enable()
|
||||
{
|
||||
@@ -32,113 +141,20 @@ syscall_enable()
|
||||
|
||||
// IA32_FMASK - FLAGS mask inside syscall
|
||||
wrmsr(msr::ia32_fmask, 0x200);
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
syscall_dispatch(uintptr_t return_rsp, cpu_state ®s)
|
||||
{
|
||||
console *cons = console::get();
|
||||
syscall call = static_cast<syscall>(regs.rax);
|
||||
static constexpr unsigned num_calls =
|
||||
static_cast<unsigned>(syscall::MAX);
|
||||
|
||||
auto &s = scheduler::get();
|
||||
auto *p = s.current();
|
||||
|
||||
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;
|
||||
for (unsigned i = 0; i < num_calls; ++i) {
|
||||
syscall_registry[i] = reinterpret_cast<uintptr_t>(syscall_invalid);
|
||||
syscall_names[i] = nullptr;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -6,19 +6,20 @@ struct cpu_state;
|
||||
|
||||
enum class syscall : uint64_t
|
||||
{
|
||||
noop = 0x0000,
|
||||
debug = 0x0001,
|
||||
message = 0x0002,
|
||||
pause = 0x0003,
|
||||
sleep = 0x0004,
|
||||
getpid = 0x0005,
|
||||
send = 0x0006,
|
||||
receive = 0x0007,
|
||||
fork = 0x0008,
|
||||
#define SYSCALL(id, name, result, ...) name = id,
|
||||
#include "syscalls.inc"
|
||||
#undef SYSCALL
|
||||
|
||||
last_syscall
|
||||
// Maximum syscall id. If you change this, also change
|
||||
// MAX_SYSCALLS in syscall.s
|
||||
MAX = 0x40
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,24 +1,62 @@
|
||||
%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.return
|
||||
syscall_handler_prelude:
|
||||
push 0 ; ss, doesn't matter here
|
||||
push rsp
|
||||
pushf
|
||||
push 0 ; cs, doesn't matter here
|
||||
push rcx ; user rip
|
||||
push 0 ; bogus interrupt
|
||||
push 0 ; bogus errorcode
|
||||
push_all_and_segments
|
||||
swapgs
|
||||
mov [gs:CPU_DATA.rsp3], rsp
|
||||
mov rsp, [gs:CPU_DATA.rsp0]
|
||||
|
||||
mov rdi, rsp
|
||||
call syscall_handler
|
||||
mov rsp, rax
|
||||
push rcx
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
|
||||
pop_all_and_segments
|
||||
add rsp, 16 ; ignore bogus interrupt / error
|
||||
pop rcx ; user rip
|
||||
add rsp, 32 ; ignore cs, flags, rsp, ss
|
||||
push rbx
|
||||
push r11
|
||||
push r12
|
||||
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
|
||||
|
||||
12
src/kernel/syscalls.inc
Normal file
12
src/kernel/syscalls.inc
Normal 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)
|
||||
17
src/kernel/syscalls/exit.cpp
Normal file
17
src/kernel/syscalls/exit.cpp
Normal 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
|
||||
24
src/kernel/syscalls/fork.cpp
Normal file
24
src/kernel/syscalls/fork.cpp
Normal 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
|
||||
14
src/kernel/syscalls/getpid.cpp
Normal file
14
src/kernel/syscalls/getpid.cpp
Normal 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
|
||||
14
src/kernel/syscalls/message.cpp
Normal file
14
src/kernel/syscalls/message.cpp
Normal 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
|
||||
14
src/kernel/syscalls/noop.cpp
Normal file
14
src/kernel/syscalls/noop.cpp
Normal 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
|
||||
15
src/kernel/syscalls/pause.cpp
Normal file
15
src/kernel/syscalls/pause.cpp
Normal 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
|
||||
17
src/kernel/syscalls/sleep.cpp
Normal file
17
src/kernel/syscalls/sleep.cpp
Normal 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
63
src/kernel/task.s
Normal 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
33
src/kernel/tasking.inc
Normal 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
|
||||
@@ -49,7 +49,7 @@ class disk
|
||||
public:
|
||||
/// Constructor.
|
||||
/// \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
|
||||
const kutil::vector<file> & files() const { return m_files; }
|
||||
|
||||
@@ -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);
|
||||
size_t length = header->length;
|
||||
|
||||
95
src/libraries/kutil/bip_buffer.cpp
Normal file
95
src/libraries/kutil/bip_buffer.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -1,12 +1,11 @@
|
||||
#include <stdint.h>
|
||||
#include "kutil/assert.h"
|
||||
#include "kutil/memory.h"
|
||||
#include "kutil/heap_manager.h"
|
||||
#include "kutil/heap_allocator.h"
|
||||
|
||||
namespace kutil {
|
||||
|
||||
|
||||
struct heap_manager::mem_header
|
||||
struct heap_allocator::mem_header
|
||||
{
|
||||
mem_header(mem_header *prev, mem_header *next, uint8_t size) :
|
||||
m_prev(prev), m_next(next)
|
||||
@@ -14,34 +13,29 @@ struct heap_manager::mem_header
|
||||
set_size(size);
|
||||
}
|
||||
|
||||
inline void set_size(uint8_t size)
|
||||
{
|
||||
inline void set_size(uint8_t size) {
|
||||
m_prev = reinterpret_cast<mem_header *>(
|
||||
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 *>(
|
||||
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();
|
||||
m_next = next;
|
||||
set_used(u);
|
||||
}
|
||||
|
||||
inline void set_prev(mem_header *prev)
|
||||
{
|
||||
inline void set_prev(mem_header *prev) {
|
||||
uint8_t s = size();
|
||||
m_prev = prev;
|
||||
set_size(s);
|
||||
}
|
||||
|
||||
void remove()
|
||||
{
|
||||
void remove() {
|
||||
if (next()) next()->set_prev(prev());
|
||||
if (prev()) prev()->set_next(next());
|
||||
set_prev(nullptr);
|
||||
@@ -67,25 +61,24 @@ private:
|
||||
};
|
||||
|
||||
|
||||
heap_manager::heap_manager() :
|
||||
m_grow(nullptr)
|
||||
{
|
||||
}
|
||||
heap_allocator::heap_allocator() : m_next(0), m_size(0) {}
|
||||
|
||||
heap_manager::heap_manager(grow_callback grow_cb) :
|
||||
m_grow(grow_cb)
|
||||
heap_allocator::heap_allocator(uintptr_t start, size_t size) :
|
||||
m_next(start), m_size(size)
|
||||
{
|
||||
kutil::memset(m_free, 0, sizeof(m_free));
|
||||
grow_memory();
|
||||
}
|
||||
|
||||
void *
|
||||
heap_manager::allocate(size_t length)
|
||||
heap_allocator::allocate(size_t length)
|
||||
{
|
||||
size_t total = length + sizeof(mem_header);
|
||||
unsigned size = min_size;
|
||||
while (total > (1 << size)) 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);
|
||||
header->set_used(true);
|
||||
@@ -93,18 +86,28 @@ heap_manager::allocate(size_t length)
|
||||
}
|
||||
|
||||
void
|
||||
heap_manager::free(void *p)
|
||||
heap_allocator::free(void *p)
|
||||
{
|
||||
if (!p) return;
|
||||
|
||||
mem_header *header = reinterpret_cast<mem_header *>(p);
|
||||
header -= 1; // p points after the header
|
||||
header->set_used(false);
|
||||
|
||||
while (header->size() != max_size) {
|
||||
auto size = header->size();
|
||||
|
||||
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();
|
||||
|
||||
header = header->eldest() ? header : buddy;
|
||||
header->set_size(header->size() + 1);
|
||||
header->set_size(size + 1);
|
||||
}
|
||||
|
||||
uint8_t size = header->size();
|
||||
@@ -115,47 +118,60 @@ heap_manager::free(void *p)
|
||||
}
|
||||
|
||||
void
|
||||
heap_manager::grow_memory()
|
||||
heap_allocator::ensure_block(unsigned size)
|
||||
{
|
||||
size_t length = (1 << max_size);
|
||||
|
||||
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();
|
||||
if (get_free(size) != nullptr)
|
||||
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_manager::pop_free(unsigned size)
|
||||
heap_allocator::mem_header *
|
||||
heap_allocator::pop_free(unsigned size)
|
||||
{
|
||||
ensure_block(size);
|
||||
mem_header *block = get_free(size);
|
||||
get_free(size) = block->next();
|
||||
|
||||
block->remove();
|
||||
if (block) {
|
||||
get_free(size) = block->next();
|
||||
block->remove();
|
||||
}
|
||||
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
|
||||
31
src/libraries/kutil/include/kutil/allocator.h
Normal file
31
src/libraries/kutil/include/kutil/allocator.h
Normal 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
|
||||
59
src/libraries/kutil/include/kutil/bip_buffer.h
Normal file
59
src/libraries/kutil/include/kutil/bip_buffer.h
Normal 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
|
||||
@@ -3,6 +3,7 @@
|
||||
/// Helper base class for buddy allocators with external node storage.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <utility>
|
||||
#include "kutil/assert.h"
|
||||
#include "kutil/linked_list.h"
|
||||
#include "kutil/slab_allocator.h"
|
||||
@@ -25,15 +26,21 @@ public:
|
||||
static const size_t min_alloc = (1 << size_min);
|
||||
static const size_t max_alloc = (1 << size_max);
|
||||
|
||||
/// Constructor.
|
||||
buddy_allocator() {}
|
||||
/// Default constructor creates an invalid object.
|
||||
buddy_allocator() : m_alloc(allocator::invalid) {}
|
||||
|
||||
/// Constructor with an initial cache of region structs from bootstrapped
|
||||
/// memory.
|
||||
/// \arg cache List of pre-allocated ununused region_type structures
|
||||
buddy_allocator(region_list cache)
|
||||
/// Constructor.
|
||||
/// \arg alloc Allocator to use for region nodes
|
||||
buddy_allocator(allocator &alloc) : m_alloc(alloc) {}
|
||||
|
||||
/// 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.
|
||||
@@ -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:
|
||||
/// Split a region of the given size into two smaller regions, returning
|
||||
/// the new latter half
|
||||
@@ -266,6 +290,8 @@ struct buddy_region
|
||||
|
||||
inline uintptr_t end() const { return address + (1ull << size); }
|
||||
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 bool elder() const { return address < buddy(); }
|
||||
|
||||
|
||||
38
src/libraries/kutil/include/kutil/constexpr_hash.h
Normal file
38
src/libraries/kutil/include/kutil/constexpr_hash.h
Normal 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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,49 +1,45 @@
|
||||
#pragma once
|
||||
/// \file heap_manager.h
|
||||
/// A buddy allocator and related definitions.
|
||||
/// \file heap_allocator.h
|
||||
/// A buddy allocator for a memory heap
|
||||
|
||||
#include <stddef.h>
|
||||
#include "kutil/allocator.h"
|
||||
|
||||
namespace kutil {
|
||||
|
||||
|
||||
/// Manager for allocation of heap memory.
|
||||
class heap_manager
|
||||
/// Allocator for a given heap range
|
||||
class heap_allocator :
|
||||
public allocator
|
||||
{
|
||||
public:
|
||||
/// Callback signature for growth function. Memory returned does not need
|
||||
/// to be contiguous, but needs to be alined to the length requested.
|
||||
using grow_callback = void * (*)(size_t length);
|
||||
/// Default constructor creates a valid but empty heap.
|
||||
heap_allocator();
|
||||
|
||||
/// Default constructor. Creates an invalid manager.
|
||||
heap_manager();
|
||||
|
||||
/// Constructor.
|
||||
/// \arg grow_cb Function pointer to grow the heap size
|
||||
heap_manager(grow_callback grow_cb);
|
||||
/// Constructor. The given memory area must already have been reserved.
|
||||
/// \arg start Starting address of the heap
|
||||
/// \arg size Size of the heap in bytes
|
||||
heap_allocator(uintptr_t start, size_t size);
|
||||
|
||||
/// Allocate memory from the area managed.
|
||||
/// \arg length The amount of memory to allocate, in bytes
|
||||
/// \returns A pointer to the allocated memory, or nullptr if
|
||||
/// allocation failed.
|
||||
void * allocate(size_t length);
|
||||
virtual void * allocate(size_t length) override;
|
||||
|
||||
/// Free a previous allocation.
|
||||
/// \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.
|
||||
static const unsigned min_size = 6;
|
||||
|
||||
/// 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:
|
||||
class mem_header;
|
||||
|
||||
/// Expand the size of memory
|
||||
void grow_memory();
|
||||
|
||||
/// Ensure there is a block of a given size, recursively splitting
|
||||
/// \arg size Size category of the block we want
|
||||
void ensure_block(unsigned size);
|
||||
@@ -58,11 +54,11 @@ protected:
|
||||
/// \returns A detached block of the given size
|
||||
mem_header * pop_free(unsigned size);
|
||||
|
||||
uintptr_t m_next;
|
||||
size_t m_size;
|
||||
mem_header *m_free[max_size - min_size + 1];
|
||||
|
||||
grow_callback m_grow;
|
||||
|
||||
heap_manager(const heap_manager &) = delete;
|
||||
heap_allocator(const heap_allocator &) = delete;
|
||||
};
|
||||
|
||||
} // namespace kutil
|
||||
@@ -116,27 +116,47 @@ public:
|
||||
/// Constructor. Creates an empty list.
|
||||
linked_list() :
|
||||
m_head(nullptr),
|
||||
m_tail(nullptr)
|
||||
m_tail(nullptr),
|
||||
m_count(0)
|
||||
{}
|
||||
|
||||
/// Move constructor. Takes ownership of list elements.
|
||||
linked_list(linked_list<T> &&other) :
|
||||
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_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.
|
||||
/// \returns true if the list is empty
|
||||
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.
|
||||
/// \returns The number of entries in the list.
|
||||
size_t length() const
|
||||
size_t count_length()
|
||||
{
|
||||
size_t len = 0;
|
||||
for (item_type *cur = m_head; cur; cur = cur->m_next) ++len;
|
||||
m_count = len;
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -164,6 +184,8 @@ public:
|
||||
item->m_prev = nullptr;
|
||||
m_head = item;
|
||||
}
|
||||
|
||||
m_count += 1;
|
||||
}
|
||||
|
||||
/// Append an item to the end of this list.
|
||||
@@ -182,6 +204,8 @@ public:
|
||||
item->m_next = nullptr;
|
||||
m_tail = item;
|
||||
}
|
||||
|
||||
m_count += 1;
|
||||
}
|
||||
|
||||
/// Remove an item from the front of this list.
|
||||
@@ -217,6 +241,8 @@ public:
|
||||
m_tail = list.m_tail;
|
||||
}
|
||||
|
||||
m_count += list.m_count;
|
||||
list.m_count = 0;
|
||||
list.m_head = list.m_tail = nullptr;
|
||||
}
|
||||
|
||||
@@ -235,6 +261,8 @@ public:
|
||||
m_tail = list.m_tail;
|
||||
}
|
||||
|
||||
m_count += list.m_count;
|
||||
list.m_count = 0;
|
||||
list.m_head = list.m_tail = nullptr;
|
||||
}
|
||||
|
||||
@@ -248,6 +276,7 @@ public:
|
||||
if (item == m_tail)
|
||||
m_tail = item->m_prev;
|
||||
item->remove();
|
||||
m_count -= 1;
|
||||
}
|
||||
|
||||
/// Inserts an item into the list before another given item.
|
||||
@@ -257,12 +286,14 @@ public:
|
||||
{
|
||||
if (!item) return;
|
||||
|
||||
if (!existing)
|
||||
if (!existing) {
|
||||
push_back(item);
|
||||
else if (existing == m_head)
|
||||
} else if (existing == m_head) {
|
||||
push_front(item);
|
||||
else
|
||||
} else {
|
||||
existing->insert_before(item);
|
||||
m_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts an item into the list after another given item.
|
||||
@@ -272,12 +303,14 @@ public:
|
||||
{
|
||||
if (!item) return;
|
||||
|
||||
if (!existing)
|
||||
if (!existing) {
|
||||
push_front(item);
|
||||
else if (existing == m_tail)
|
||||
} else if (existing == m_tail) {
|
||||
push_back(item);
|
||||
else
|
||||
} else {
|
||||
existing->insert_after(item);
|
||||
m_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an item into the list in a sorted position. Depends on T
|
||||
@@ -309,6 +342,7 @@ public:
|
||||
private:
|
||||
item_type *m_head;
|
||||
item_type *m_tail;
|
||||
size_t m_count;
|
||||
};
|
||||
|
||||
|
||||
|
||||
118
src/libraries/kutil/include/kutil/logger.h
Normal file
118
src/libraries/kutil/include/kutil/logger.h
Normal 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
|
||||
@@ -9,15 +9,6 @@ void * operator new (size_t, void *p) noexcept;
|
||||
|
||||
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.
|
||||
/// \arg p The beginning of the memory area to fill
|
||||
/// \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
|
||||
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
|
||||
|
||||
96
src/libraries/kutil/include/kutil/printf.h
Normal file
96
src/libraries/kutil/include/kutil/printf.h
Normal 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_
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
/// \file slab_allocator.h
|
||||
/// A slab allocator and related definitions
|
||||
#include "kutil/allocator.h"
|
||||
#include "kutil/assert.h"
|
||||
#include "kutil/linked_list.h"
|
||||
#include "kutil/memory.h"
|
||||
@@ -9,7 +10,7 @@ namespace kutil {
|
||||
|
||||
|
||||
/// 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 :
|
||||
public linked_list<T>
|
||||
{
|
||||
@@ -19,8 +20,7 @@ public:
|
||||
/// Default constructor.
|
||||
/// \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().
|
||||
slab_allocator(size_t chunk_size = 0, Alloc alloc = malloc) :
|
||||
m_chunk_size(chunk_size),
|
||||
slab_allocator(allocator &alloc) :
|
||||
m_alloc(alloc)
|
||||
{
|
||||
}
|
||||
@@ -45,18 +45,16 @@ public:
|
||||
|
||||
void allocate()
|
||||
{
|
||||
size_t size = m_chunk_size ? m_chunk_size : 10 * sizeof(item_type);
|
||||
void *memory = m_alloc(size);
|
||||
size_t count = size / sizeof(item_type);
|
||||
constexpr unsigned count = N / sizeof(item_type);
|
||||
|
||||
void *memory = m_alloc.allocate(N);
|
||||
item_type *items = reinterpret_cast<item_type *>(memory);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
this->push_back(&items[i]);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_chunk_size;
|
||||
Alloc m_alloc;
|
||||
allocator& m_alloc;
|
||||
};
|
||||
|
||||
} // namespace kutil
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include "kutil/allocator.h"
|
||||
#include "kutil/memory.h"
|
||||
|
||||
namespace kutil {
|
||||
@@ -14,18 +15,20 @@ class vector
|
||||
{
|
||||
public:
|
||||
/// Default constructor. Creates an empty vector with no capacity.
|
||||
vector() :
|
||||
vector(kutil::allocator &alloc = allocator::invalid) :
|
||||
m_size(0),
|
||||
m_capacity(0),
|
||||
m_elements(nullptr)
|
||||
m_elements(nullptr),
|
||||
m_alloc(alloc)
|
||||
{}
|
||||
|
||||
/// Constructor. Creates an empty array with capacity.
|
||||
/// \arg capacity Initial capacity to allocate
|
||||
vector(size_t capacity) :
|
||||
vector(size_t capacity, allocator &alloc) :
|
||||
m_size(0),
|
||||
m_capacity(0),
|
||||
m_elements(nullptr)
|
||||
m_elements(nullptr),
|
||||
m_alloc(alloc)
|
||||
{
|
||||
set_capacity(capacity);
|
||||
}
|
||||
@@ -34,7 +37,8 @@ public:
|
||||
vector(const vector& other) :
|
||||
m_size(0),
|
||||
m_capacity(0),
|
||||
m_elements(nullptr)
|
||||
m_elements(nullptr),
|
||||
m_alloc(other.m_alloc)
|
||||
{
|
||||
set_capacity(other.m_capacity);
|
||||
kutil::memcpy(m_elements, other.m_elements, other.m_size * sizeof(T));
|
||||
@@ -45,7 +49,8 @@ public:
|
||||
vector(vector&& other) :
|
||||
m_size(other.m_size),
|
||||
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_capacity = 0;
|
||||
@@ -142,7 +147,7 @@ public:
|
||||
/// \arg capacity Number of elements to allocate
|
||||
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);
|
||||
|
||||
kutil::memcpy(new_array, m_elements, size * sizeof(T));
|
||||
@@ -151,7 +156,7 @@ public:
|
||||
m_size = size;
|
||||
m_capacity = capacity;
|
||||
|
||||
delete [] m_elements;
|
||||
m_alloc.free(m_elements);
|
||||
m_elements = new_array;
|
||||
}
|
||||
|
||||
@@ -159,6 +164,7 @@ private:
|
||||
size_t m_size;
|
||||
size_t m_capacity;
|
||||
T *m_elements;
|
||||
allocator &m_alloc;
|
||||
};
|
||||
|
||||
} // namespace kutil
|
||||
|
||||
165
src/libraries/kutil/logger.cpp
Normal file
165
src/libraries/kutil/logger.cpp
Normal 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
|
||||
@@ -1,46 +1,11 @@
|
||||
#include "kutil/memory.h"
|
||||
#include "kutil/heap_manager.h"
|
||||
|
||||
namespace std {
|
||||
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 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 *
|
||||
memset(void *s, uint8_t v, size_t n)
|
||||
{
|
||||
|
||||
766
src/libraries/kutil/printf.c
Normal file
766
src/libraries/kutil/printf.c
Normal 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;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "kutil/address_manager.h"
|
||||
#include "kutil/allocator.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
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 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]" )
|
||||
{
|
||||
address_manager am;
|
||||
malloc_allocator alloc;
|
||||
address_manager am(alloc);
|
||||
am.add_regions(start, max_block * 2);
|
||||
|
||||
// Blocks should be:
|
||||
|
||||
21
src/tests/constexpr_hash.cpp
Normal file
21
src/tests/constexpr_hash.cpp
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
170
src/tests/heap_allocator.cpp
Normal file
170
src/tests/heap_allocator.cpp
Normal 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);
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user