From 29891c3ccec4f1d28e0440d87ea2e2708377d2ee Mon Sep 17 00:00:00 2001 From: Minep Date: Thu, 4 Jul 2024 19:44:47 +0100 Subject: [PATCH 01/16] integrate C/LDFLAGS into LunaBuild flow * allow LBuild framework to accept custom generation implementation * allow LBuild framework to accept custom built-in function * separate the compilation flags setting by arch --- lunaix-os/LBuild | 18 +++++ lunaix-os/arch/i386/LBuild | 8 +++ lunaix-os/kernel.mk | 24 +++---- lunaix-os/makefile | 12 +--- lunaix-os/makeinc/toolchain.mkinc | 10 +-- .../build-tools/integration/build_gen.py | 67 +++++++++++++++++++ lunaix-os/scripts/build-tools/lbuild/api.py | 9 ++- .../scripts/build-tools/lbuild/common.py | 42 ++++++++---- .../scripts/build-tools/lbuild/contract.py | 3 +- .../scripts/build-tools/lcfg/builtins.py | 6 +- lunaix-os/scripts/build-tools/luna_build.py | 9 ++- lunaix-os/usr/makefile | 4 ++ 12 files changed, 165 insertions(+), 47 deletions(-) create mode 100644 lunaix-os/scripts/build-tools/integration/build_gen.py diff --git a/lunaix-os/LBuild b/lunaix-os/LBuild index f4bb7c8..dcee304 100644 --- a/lunaix-os/LBuild +++ b/lunaix-os/LBuild @@ -6,4 +6,22 @@ use("hal") headers([ "includes", "includes/usr" +]) + +# compliation setting + +compile_opts([ + "-ffreestanding", + "-fno-pie" +]) + +linking_opts([ + "-nostdlib", + "-nolibc", + "-z noexecstack", + "-no-pie", +]) + +linking_opts([ + "-Wl,--build-id=none" ]) \ No newline at end of file diff --git a/lunaix-os/arch/i386/LBuild b/lunaix-os/arch/i386/LBuild index d4c9a96..8a7f155 100644 --- a/lunaix-os/arch/i386/LBuild +++ b/lunaix-os/arch/i386/LBuild @@ -41,4 +41,12 @@ sources([ headers([ "includes" +]) + +compile_opts([ + "-m32" +]) + +linking_opts([ + "-m32" ]) \ No newline at end of file diff --git a/lunaix-os/kernel.mk b/lunaix-os/kernel.mk index 9399e71..dff0de2 100644 --- a/lunaix-os/kernel.mk +++ b/lunaix-os/kernel.mk @@ -1,37 +1,35 @@ include os.mkinc include toolchain.mkinc -ksrc_files = $(shell cat .builder/sources.list) -kinc_dirs = $(shell cat .builder/includes.list) -khdr_files = $(shell cat .builder/headers.list) -khdr_files += .builder/configs.h +include .builder/lbuild.mkinc kbin_dir := $(BUILD_DIR) kbin := $(BUILD_NAME) -ksrc_objs := $(addsuffix .o,$(ksrc_files)) -ksrc_deps := $(addsuffix .d,$(ksrc_files)) -khdr_opts := $(addprefix -include ,$(khdr_files)) -kinc_opts := $(addprefix -I,$(kinc_dirs)) +ksrc_objs := $(addsuffix .o,$(_LBUILD_SRCS)) +ksrc_deps := $(addsuffix .d,$(_LBUILD_SRCS)) +khdr_opts := $(addprefix -include ,$(_LBUILD_HDRS)) +kinc_opts := $(addprefix -I,$(_LBUILD_INCS)) +config_h += -include.builder/configs.h tmp_kbin := $(BUILD_DIR)/tmpk.bin ksymtable := lunaix_ksyms.o -CFLAGS += $(khdr_opts) +CFLAGS += $(khdr_opts) $(kinc_opts) $(config_h) -MMD -MP -include $(ksrc_deps) %.S.o: %.S $(khdr_files) kernel.mk $(call status_,AS,$<) - @$(CC) $(CFLAGS) $(kinc_opts) -MMD -MP -c $< -o $@ + @$(CC) $(CFLAGS) -c $< -o $@ %.c.o: %.c $(khdr_files) kernel.mk $(call status_,CC,$<) - @$(CC) $(CFLAGS) $(kinc_opts) -MMD -MP -c $< -o $@ + @$(CC) $(CFLAGS) -c $< -o $@ $(tmp_kbin): $(ksrc_objs) $(call status_,LD,$@) - @$(CC) -T link/linker.ld $(LDFLAGS) -o $@ $^ + @$(CC) -T link/linker.ld $(config_h) $(LDFLAGS) -o $@ $^ $(ksymtable): $(tmp_kbin) $(call status_,KSYM,$@) @@ -41,7 +39,7 @@ $(ksymtable): $(tmp_kbin) .PHONY: __do_relink __do_relink: $(ksrc_objs) $(ksymtable) $(call status_,LD,$(kbin)) - @$(CC) -T link/linker.ld $(LDFLAGS) -o $(kbin) $^ + @$(CC) -T link/linker.ld $(config_h) $(LDFLAGS) -o $(kbin) $^ @rm $(tmp_kbin) .PHONY: all diff --git a/lunaix-os/makefile b/lunaix-os/makefile index 4e13211..09ee4b9 100644 --- a/lunaix-os/makefile +++ b/lunaix-os/makefile @@ -28,12 +28,6 @@ $(DEPS): echo "failed" && exit 1;\ fi -define builder_data - .builder/sources.list - .builder/headers.list - .builder/includes.list -endef - all_lconfigs = $(shell find . -name "LConfig") $(kbuild_dir): @@ -48,13 +42,13 @@ $(kbuild_dir): @echo @./scripts/build-tools/luna_build.py --config --lconfig-file LConfig -o $(@D) -.builder/%.list: .builder/configs.h +.builder/lbuild.mkinc: .builder/configs.h @./scripts/build-tools/luna_build.py LBuild --lconfig-file LConfig -o $(@D) .PHONY: kernel export BUILD_DIR=$(kbin_dir) export BUILD_NAME=$(kbin) -kernel: $(builder_data) +kernel: .builder/lbuild.mkinc $(call status,TASK,$(notdir $@)) @$(MAKE) $(MKFLAGS) -I $(mkinc_dir) -f kernel.mk all @@ -96,7 +90,7 @@ clean: @$(MAKE) -C usr clean -I $(mkinc_dir) @$(MAKE) -f kernel.mk clean -I $(mkinc_dir) @rm -rf $(kbuild_dir) || exit 1 - @rm -f .builder/*.list || exit 1 + @rm -f .builder/lbuild.mkinc || exit 1 run: all @qemu-system-i386 $(call get_qemu_options,$(kimg)) diff --git a/lunaix-os/makeinc/toolchain.mkinc b/lunaix-os/makeinc/toolchain.mkinc index c9d085f..552c2fd 100644 --- a/lunaix-os/makeinc/toolchain.mkinc +++ b/lunaix-os/makeinc/toolchain.mkinc @@ -3,10 +3,6 @@ CC := $(CX_PREFIX)gcc AS := $(CX_PREFIX)as AR := $(CX_PREFIX)ar -STRIP_OSDEP_CC := -ffreestanding -fno-pie -STRIP_OSDEP_LD := -nostdlib -nolibc -z noexecstack -no-pie -Wl,--build-id=none - -ARCH_OPT := -m32 -D__ARCH_IA32 O := -O2 W := -Wall -Wextra -Werror \ -Wno-unknown-pragmas \ @@ -27,15 +23,15 @@ OFLAGS := -fno-gcse\ -fno-indirect-inlining\ -fno-omit-frame-pointer -CFLAGS := $(ARCH_OPT) -std=gnu99 $(OFLAGS) $(W) +CFLAGS := -std=gnu99 $(OFLAGS) $(W) ifeq ($(BUILD_MODE),debug) O = -Og CFLAGS += -g endif -CFLAGS += $(O) $(STRIP_OSDEP_CC) +CFLAGS += $(O) -LDFLAGS := $(ARCH_OPT) $(O) $(STRIP_OSDEP_LD) +LDFLAGS := $(O) MKFLAGS := --no-print-directory \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/integration/build_gen.py b/lunaix-os/scripts/build-tools/integration/build_gen.py new file mode 100644 index 0000000..83c9904 --- /dev/null +++ b/lunaix-os/scripts/build-tools/integration/build_gen.py @@ -0,0 +1,67 @@ +from lbuild.api import BuildGenerator +from lbuild.common import BuildEnvironment +from lib.utils import join_path +from os import getenv + +class MakefileBuildGen(BuildGenerator): + def __init__(self, out_dir, name = "lbuild.mkinc") -> None: + self.__path = join_path(out_dir, name) + + def emit_makearray(self, name, values): + r = [] + r.append(f"define {name}") + for v in values: + r.append(v) + r.append("endef") + return r + + def generate(self, env: BuildEnvironment): + path = env.to_wspath(self.__path) + lines = [] + + opts = env.get_object("CC_OPTS", []) + lines.append("CFLAGS += %s"%(" ".join(opts))) + + opts = env.get_object("LD_OPTS", []) + lines.append("LDFLAGS += %s"%(" ".join(opts))) + + arr = self.emit_makearray("_LBUILD_SRCS", env.srcs()) + lines += arr + + arr = self.emit_makearray("_LBUILD_HDRS", env.headers()) + lines += arr + + arr = self.emit_makearray("_LBUILD_INCS", env.includes()) + lines += arr + + with open(path, 'w') as f: + f.write("\n".join(lines)) + + +def install_lbuild_functions(_env: BuildEnvironment): + def set_opts(env: BuildEnvironment, name, opts, override): + if not isinstance(opts, list): + opts = [opts] + + _opts = env.get_object(name, []) + + if override: + _opts = opts + else: + _opts += opts + + env.set_object(name, _opts) + + def compile_opts(env: BuildEnvironment, opts, override=False): + set_opts(env, "CC_OPTS", opts, override) + + def linking_opts(env: BuildEnvironment, opts, override=False): + set_opts(env, "LD_OPTS", opts, override) + + def env(env, name, default=None): + return getenv(name, default) + + + _env.add_external_func(compile_opts) + _env.add_external_func(linking_opts) + _env.add_external_func(env) \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/lbuild/api.py b/lunaix-os/scripts/build-tools/lbuild/api.py index 2bc91fa..9f6f8d4 100644 --- a/lunaix-os/scripts/build-tools/lbuild/api.py +++ b/lunaix-os/scripts/build-tools/lbuild/api.py @@ -6,4 +6,11 @@ class ConfigProvider: raise ValueError(f"config '{name}' is undefined or disabled") def has_config(self, name): - return False \ No newline at end of file + return False + +class BuildGenerator: + def __init__(self) -> None: + pass + + def generate(self, env): + pass \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/lbuild/common.py b/lunaix-os/scripts/build-tools/lbuild/common.py index c559b0a..a973760 100644 --- a/lunaix-os/scripts/build-tools/lbuild/common.py +++ b/lunaix-os/scripts/build-tools/lbuild/common.py @@ -2,12 +2,15 @@ from lib.utils import join_path import os class BuildEnvironment: - def __init__(self, workspace_dir) -> None: + def __init__(self, workspace_dir, generator) -> None: self.__config_provider = None self.__sources = [] self.__headers = [] self.__inc_dir = [] self.__ws_dir = workspace_dir + self.__ext_object = {} + self.__ext_function = {} + self.__generator = generator def set_config_provider(self, provider): self.__config_provider = provider @@ -29,15 +32,28 @@ class BuildEnvironment: path = join_path(self.__ws_dir, file) return os.path.relpath(path, self.__ws_dir) - def export(self, out_dir): - path = os.path.join(out_dir, "sources.list") - with open(path, "w") as f: - f.write("\n".join(self.__sources)) - - path = os.path.join(out_dir, "headers.list") - with open(path, "w") as f: - f.write("\n".join(self.__headers)) - - path = os.path.join(out_dir, "includes.list") - with open(path, "w") as f: - f.write("\n".join(self.__inc_dir)) \ No newline at end of file + def export(self): + self.__generator.generate(self) + + def get_object(self, key, _default=None): + return _default if key not in self.__ext_object else self.__ext_object[key] + + def set_object(self, key, object): + self.__ext_object[key] = object + + def srcs(self): + return list(self.__sources) + + def headers(self): + return list(self.__headers) + + def includes(self): + return list(self.__inc_dir) + + def add_external_func(self, function): + name = function.__name__ + invk = lambda *args, **kwargs: function(self, *args, **kwargs) + self.__ext_function[name] = invk + + def external_func_table(self): + return self.__ext_function \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/lbuild/contract.py b/lunaix-os/scripts/build-tools/lbuild/contract.py index 1b818ab..643be14 100644 --- a/lunaix-os/scripts/build-tools/lbuild/contract.py +++ b/lunaix-os/scripts/build-tools/lbuild/contract.py @@ -18,7 +18,8 @@ class LunaBuildFile(Sandbox): "config": lambda name: self.read_config(name), "use": - lambda file: self.import_buildfile(file) + lambda file: self.import_buildfile(file), + **env.external_func_table() }) self.__srcs = [] diff --git a/lunaix-os/scripts/build-tools/lcfg/builtins.py b/lunaix-os/scripts/build-tools/lcfg/builtins.py index 9eb616f..7a46699 100644 --- a/lunaix-os/scripts/build-tools/lcfg/builtins.py +++ b/lunaix-os/scripts/build-tools/lcfg/builtins.py @@ -29,4 +29,8 @@ def parent(env, caller, ref): @contextual(caller_type=[LCTermNode]) def default(env, caller, val): - caller.set_default(val) \ No newline at end of file + caller.set_default(val) + +@builtin() +def env(env, key, default=None): + return os.getenv(key, default) \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/luna_build.py b/lunaix-os/scripts/build-tools/luna_build.py index 848ceb6..3d7f21a 100755 --- a/lunaix-os/scripts/build-tools/luna_build.py +++ b/lunaix-os/scripts/build-tools/luna_build.py @@ -7,6 +7,7 @@ from lcfg.common import LConfigEnvironment from integration.config_io import CHeaderConfigProvider from integration.lbuild_bridge import LConfigProvider from integration.render_ishell import InteractiveShell +from integration.build_gen import MakefileBuildGen, install_lbuild_functions import lcfg.types as lcfg_type import lcfg.builtins as builtin @@ -26,6 +27,7 @@ def prepare_lconfig_env(out_dir): env.register_builtin_func(builtin.parent) env.register_builtin_func(builtin.default) env.register_builtin_func(builtin.include) + env.register_builtin_func(builtin.env) env.type_factory().regitser(lcfg_type.PrimitiveType) env.type_factory().regitser(lcfg_type.MultipleChoiceType) @@ -46,7 +48,10 @@ def do_buildfile_gen(opts, lcfg_env): ws_path = dirname(root_path) root_name = basename(root_path) - env = BuildEnvironment(ws_path) + mkgen = MakefileBuildGen(opts.out_dir) + env = BuildEnvironment(ws_path, mkgen) + + install_lbuild_functions(env) cfg_provider = LConfigProvider(lcfg_env) env.set_config_provider(cfg_provider) @@ -59,7 +64,7 @@ def do_buildfile_gen(opts, lcfg_env): print("failed to resolve root build file") raise err - env.export(opts.out_dir) + env.export() def main(): parser = ArgumentParser() diff --git a/lunaix-os/usr/makefile b/lunaix-os/usr/makefile index e49d7d0..4dc8d0d 100644 --- a/lunaix-os/usr/makefile +++ b/lunaix-os/usr/makefile @@ -3,6 +3,10 @@ include toolchain.mkinc task := all +# TODO make this use LBuild +CFLAGS += -m32 -ffreestanding -fno-pie +LDFLAGS += -m32 -nostdlib -nolibc -z noexecstack -no-pie -Wl,--build-id=none + sys_include := $(CURDIR)/includes build_dir := $(CURDIR)/build libc_name := liblunac -- 2.27.0 From 32b9a6d76790c73d3d2d36d9081a2581cc65d184 Mon Sep 17 00:00:00 2001 From: Minep Date: Wed, 10 Jul 2024 19:08:56 +0100 Subject: [PATCH 02/16] ld-tool portability fix: MacOS build experience * fix: makefile fail to build due to line feed character not being recognise as valid separator * change shebang to more portable of /usr/bin/env --- lunaix-os/kernel.mk | 4 ++-- lunaix-os/scripts/build-tools/integration/build_gen.py | 7 +++---- lunaix-os/scripts/gen_ksymtable.sh | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lunaix-os/kernel.mk b/lunaix-os/kernel.mk index dff0de2..86f4668 100644 --- a/lunaix-os/kernel.mk +++ b/lunaix-os/kernel.mk @@ -19,11 +19,11 @@ CFLAGS += $(khdr_opts) $(kinc_opts) $(config_h) -MMD -MP -include $(ksrc_deps) -%.S.o: %.S $(khdr_files) kernel.mk +%.S.o: %.S kernel.mk $(call status_,AS,$<) @$(CC) $(CFLAGS) -c $< -o $@ -%.c.o: %.c $(khdr_files) kernel.mk +%.c.o: %.c kernel.mk $(call status_,CC,$<) @$(CC) $(CFLAGS) -c $< -o $@ diff --git a/lunaix-os/scripts/build-tools/integration/build_gen.py b/lunaix-os/scripts/build-tools/integration/build_gen.py index 83c9904..6b1cde7 100644 --- a/lunaix-os/scripts/build-tools/integration/build_gen.py +++ b/lunaix-os/scripts/build-tools/integration/build_gen.py @@ -9,11 +9,10 @@ class MakefileBuildGen(BuildGenerator): def emit_makearray(self, name, values): r = [] - r.append(f"define {name}") + r.append(f"{name} :=") for v in values: - r.append(v) - r.append("endef") - return r + r.append(f"{v}") + return [" ".join(r)] def generate(self, env: BuildEnvironment): path = env.to_wspath(self.__path) diff --git a/lunaix-os/scripts/gen_ksymtable.sh b/lunaix-os/scripts/gen_ksymtable.sh index 6b59e4d..720dc55 100755 --- a/lunaix-os/scripts/gen_ksymtable.sh +++ b/lunaix-os/scripts/gen_ksymtable.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash sym_types=$1 bin=$2 -- 2.27.0 From 28c176b668c841a3b7fb093faccf0efa39257603 Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Tue, 16 Jul 2024 22:17:07 +0100 Subject: [PATCH 03/16] Architectural Support: x86_64 (#37) * x86_64 port: exception model, syscall, arch utils * add port to exception model: hart state transistion, general interruption hanlding * add port to syscall: call convention, operand size * arch utils change: udiv64, add native implementation * makefile: add dedicated action to do configuration * trace: port register dumping * other refactorings * port: virtual memory inteface, vm partition * add support to 64bits virtual memory management scheme * add definitions for memory partition under 64 bits * refactor: routine clean up, remove deprecated MEM_* * fix: reverse the order of lo/hi in struct x86_sysdesc * rename i386 to x86 for clarity * port: boot code, preliminary memory setup * add boot code port to x86_64, include IA32e mode switching sequence recommended by Intel * add support of x86_64 to memory setup * refactor: move boot code into two separated directory for clarity * refactor: boot_helper: memory reservation code should go to arch-specific * feat: architectural-dependent linking * preprocess the linker script with gcc's preprocessor before commencing the linking * fix: LConfig: x86_feature setting missing when arch=x86_64 * decouple elf loading. Make thing compile * decouple the arch-related elf parsing and loading part from the kernel base * fix compiliation issues when compiling under 64 bit mode * fix a relocation issue where the boot code reference a symbol exported from relocated kernel's LMA for address calculation. * make thing more unified in LBuild * port other misc stuff to x86_64 * add boilerplate for multiboot2 bootloader interface * ensure the i386 invariant after refactroing for baseline * port: remove hidden assumption of VMS_SELF in vm model * creation of those lntep pteps mades a hidden assumption of VMS_SELF address used, which introduce inflexibility * adjust the vm map of x86_64 to make sure kernel's address can be relocated with R_86_64_32S policy * port: pplist mapping to x86_64 * refactor: rename all explicity comparision with VMS_SELF with an inline of active_vmnt() * refactor: encapsulate all explicity checking of ptep for unpacking into vmnt_packed() * fix: vmscpy, vmsfree and kmem_init assumption on vm map structure * fix: linking issue with 64bit addressing * previous: satisfy R_86_64_32S relocation by adjusting the kernel base address * fixup other issue due to previous refactoring * enable of x86_64 on usr/ * lunalibc: port: x86_64 support * lunalibc: fix: cut down the unnecessary register savings when invoking syscall. * lunalibc: refactor: better syscall invocation method * lunabuild: refactor: rename set() to set_value() to avoid confusion with set declaration * lunabuild: fix: cascade updating case stack overflow when triggered by itself through set_value() * makefile: fix: only triggered re-configuration shell when config save is deleted or explicit required. * makefile: feat: add a env variable ARCH= on make, to allow specifiy the targeted ISA without going through all the trouble of configuration shell * build: fix: use mcmode=kernel to allow linking with kernel base relocated to -2GiB while remove the need of the bloat of mcmode=large * make variadic syscall more portable * syscall: fix: va_list now only cross the kernel broader with a reference rather than value, which make the interface portable as some architecture abi require va_list to be a struct. * syscall: ref: (this is a refactor) remove the need of variadic syscall usage on sys_mmap and syslog. As they are point-less * fix: issues during the bootstrap stage * arch/boot: add missing mode switching from x86_64 compatibility mode to full 64-bits (long mode) * kpt_remap: rework the kernel high-mem remapping for x86_64. isolate the remap logic of x86_64 and i386 * gdt: when loading gdt, the descriptor must follow the i386's format (all field are required), as processor will perform all the checks upon loading * idt: remove the ist thing, we should reserve it for the "ABORT" interrupts to use. * ctx switch: fix issue of a register being left in stack * vm: add sign extend on virtual address to enforce consistency adjust the _PTE_X and introduce _PTE_NX minor adjustment on vm mapping for easy setup * kprintf: fix the 64-bit integer not being correctly displayed in hex. * acpi: fix the vague use of generic pointer type as acpi require all pointer type must be int32 * variadic: wrap the va_list unpacking method into arch-specific * makefile: invoke correct QEMU depend on build architecture * lunabuild: disable the gcc's SSE optimization when LConfig does enable this. * elf64: typo fix, should check for CLASS64 flag set, rather than CLASS32 * pmm: pplist mapping granularity fix. * reactor: some refactorings, clarify things * fix issues all the way to user space * interrupt64: incorrect stack address calculation temp saved %rax not being restored * kern/syscall: a confrontation to systemv abi, %rbx is trashed without being saved offset miscalculated, replaced with one defined in interrupt64.S.inc to avoid these magic number! * usr/syscall: there is no way to force param pass through stack under x86_64, roll back to use registers * gdt: it turns out you still need a valid data seg desc for both R0/R3 data selector * tss: fix incorrect offset for tss defined in interrupt64.S.inc * usr: use -mcmodel=large to link all user program, we may shift to PIC in the near future * pagetable: add __paddr to ensure we the calculated address (physical) does not exceed the maximium allowance (that is, 52 bits) * mempart: adjust the memory map to move user stack lower, so we can distinguish it from sign extended kernel address visually add extra term for specifiying max stack size PER THREAD, to avoid confusion. * tlb: add couple of invalidation to ensure freshness * procvm: fix the page table walking on step out single level when the structure has multiple level chained at the last pte. Which result incorrect L(n-1)TEP returned. fix the issue of ptep calculation for establishing self-reference on mounted vms, when we have ptw more than 2 levels. * thread: adjust the guardian pte injection logic, so it will no longer occupying the per-thread stack space * makefile: add qemu trace for tracking the interrupt event ( require my customized qemu version) * misc: refactorings of course. * fix issues in execve and lunalibc due to change of ABI * exec: fix: incorrect assumption on argv and envp, according to the POSIX, envp and argv in execve are mandatory (can not be null). * exec: ref: refactor the parameter injection code, so to be more uniform and readable. we also remove the auto injection of argv[0], and thus it is programmer's responsibility to ensure that. * libc: fix: x86_64 ABI for signal trampoline and crt*.S * i386: fix: i386 failed due to previous refactoring. The kernel stack page count must be napot. * an elegant way of configuring and creating QEMU instance * feat: rework the way to config and create QEMU instance, abstracting all the qemu specifications into a manifest file and thus more clear and less error-prone * feat: add ability of lazy evaluation of lunaconfig node, thus rendering the loading order insignificant * ref: separate the qemu launching task from the makefile. * ref: clean up and general house-keeping * always clear the LCR.DLAB bit before configuring * when two 16550 devices on different bus (isa and pci this case) the LCR.DLAB bit is somehow being set, causing subsequent write to register become undefined. It is unclear why qemu left this in such undefined state. * add a qemu-dir to qemu.py to allow override the qemu used * update readme * change the th_create syscall interface to be more flexible * th_create syscall need to be change in order to provide better integration with user space where trampoline must be used. * increase the limit of maximium syscall number * re-implement the pthread_create in lunalibc * fix issue in the threading interfacing * fix: thread handler calling in trampoline * fix: mismatch parameter count to the printf fmt in test_pthread, which causing segfault * fix: debug/trace does not print the $pc when a symbol is fail to locate * ref: refactor the /sys/version mapping to avoid doing formatting as they are all compile time constant * ref: remove the x86_recv_interrupt_all as it is too noisy. * ref: remove redundant cpu feature from the QEMU specification * update readme --- README.md | 157 ++--- lunaix-os/.gitignore | 2 + lunaix-os/.vscode/c_cpp_properties.json | 25 +- lunaix-os/LConfig | 2 +- lunaix-os/arch/LBuild | 3 +- lunaix-os/arch/LConfig | 23 +- lunaix-os/arch/README.md | 6 +- lunaix-os/arch/generic/includes/sys/hart.h | 3 - .../arch/generic/includes/sys/mm/mempart.h | 5 - .../arch/generic/includes/sys/mm/mm_defs.h | 2 +- lunaix-os/arch/i386/LBuild | 52 -- lunaix-os/arch/i386/LConfig | 23 - lunaix-os/arch/i386/exceptions/i386_isrdef.c | 537 ------------------ lunaix-os/arch/i386/includes/sys/abi.h | 81 --- .../arch/i386/includes/sys/boot/bstage.h | 8 - lunaix-os/arch/i386/includes/sys/i386_intr.h | 10 - lunaix-os/arch/i386/includes/sys/mm/mempart.h | 60 -- lunaix-os/arch/i386/includes/sys/x86_isa.h | 54 -- lunaix-os/arch/i386/klib/fast_str.c | 26 - lunaix-os/arch/i386/trace.c | 49 -- lunaix-os/arch/x86/LBuild | 82 +++ lunaix-os/arch/x86/LConfig | 34 ++ lunaix-os/arch/{i386 => x86}/arch.c | 15 +- lunaix-os/arch/x86/boot/boot_helper.c | 46 ++ .../boot/boot.S => x86/boot/i386/boot32.S} | 13 +- .../{i386/boot => x86/boot/i386}/init32.c | 6 +- .../kpt_setup.c => x86/boot/i386/kremap32.c} | 63 +- .../prologue.S => x86/boot/i386/prologue32.S} | 9 +- lunaix-os/arch/x86/boot/kpt_setup.c | 14 + lunaix-os/arch/{i386 => x86}/boot/mb_parser.c | 15 +- lunaix-os/arch/x86/boot/x86_64/boot64.S | 133 +++++ lunaix-os/arch/x86/boot/x86_64/init64.c | 17 + lunaix-os/arch/x86/boot/x86_64/kremap64.c | 188 ++++++ lunaix-os/arch/x86/boot/x86_64/prologue64.S | 73 +++ .../exceptions/interrupt32.S} | 10 +- lunaix-os/arch/x86/exceptions/interrupt64.S | 204 +++++++ .../{i386 => x86}/exceptions/interrupts.c | 12 +- .../{i386 => x86}/exceptions/intr_routines.c | 6 +- .../arch/{i386 => x86}/exceptions/intrhnds.S | 18 +- lunaix-os/arch/x86/exceptions/isrdef.c | 67 +++ .../i386_isrm.c => x86/exceptions/isrm.c} | 0 lunaix-os/arch/x86/exec/elf32.c | 13 + lunaix-os/arch/x86/exec/elf64.c | 14 + lunaix-os/arch/x86/exec/exec.c | 23 + lunaix-os/arch/{i386 => x86}/failsafe.S | 0 lunaix-os/arch/{i386 => x86}/gdbstub.c | 12 +- lunaix-os/arch/{i386 => x86}/hal/LBuild | 0 lunaix-os/arch/{i386 => x86}/hal/apic.c | 0 lunaix-os/arch/{i386 => x86}/hal/apic_timer.c | 0 lunaix-os/arch/{i386 => x86}/hal/apic_timer.h | 0 lunaix-os/arch/{i386 => x86}/hal/cpu.c | 0 lunaix-os/arch/{i386 => x86}/hal/ioapic.c | 0 lunaix-os/arch/{i386 => x86}/hal/mc146818a.c | 0 lunaix-os/arch/{i386 => x86}/hal/pic.h | 0 lunaix-os/arch/{i386 => x86}/hal/ps2kbd.c | 0 lunaix-os/arch/{i386 => x86}/hal/rngx86.c | 25 +- lunaix-os/arch/x86/hart.c | 22 + lunaix-os/arch/{i386/hart.c => x86/hart32.c} | 23 +- lunaix-os/arch/x86/hart64.c | 44 ++ .../x86/includes/linking/base_defs.ld.inc | 18 + .../arch/x86/includes/linking/boot_secs.ldx | 29 + lunaix-os/arch/x86/includes/sys/abi.h | 33 ++ lunaix-os/arch/x86/includes/sys/abi32.h | 41 ++ lunaix-os/arch/x86/includes/sys/abi64.h | 40 ++ .../arch/{i386 => x86}/includes/sys/apic.h | 0 .../includes/sys}/boot/archinit.h | 4 +- lunaix-os/arch/x86/includes/sys/boot/bstage.h | 37 ++ .../includes/sys/boot/mb.h} | 10 +- lunaix-os/arch/x86/includes/sys/boot/mb2.h | 414 ++++++++++++++ .../x86/includes/sys/boot/multiboot.S.inc | 39 ++ .../arch/x86/includes/sys/boot/multiboot.h | 14 + .../arch/{i386 => x86}/includes/sys/cpu.h | 44 +- .../arch/{i386 => x86}/includes/sys/crx.h | 24 + lunaix-os/arch/x86/includes/sys/exebi/elf.h | 69 +++ .../{i386 => x86}/includes/sys/failsafe.h | 18 +- .../arch/{i386 => x86}/includes/sys/gdbstub.h | 0 .../arch/{i386 => x86}/includes/sys/hart.h | 119 +++- lunaix-os/arch/x86/includes/sys/int_handler.h | 10 + .../includes/sys/interrupt32.S.inc} | 0 .../arch/x86/includes/sys/interrupt64.S.inc | 107 ++++ .../arch/{i386 => x86}/includes/sys/ioapic.h | 0 .../{i386 => x86}/includes/sys/mm/memory.h | 4 +- lunaix-os/arch/x86/includes/sys/mm/mempart.h | 10 + .../arch/x86/includes/sys/mm/mempart32.h | 67 +++ .../arch/x86/includes/sys/mm/mempart64.h | 71 +++ .../{i386 => x86}/includes/sys/mm/mm_defs.h | 26 +- .../{i386 => x86}/includes/sys/mm/pagetable.h | 69 +-- .../{i386 => x86}/includes/sys/mm/physical.h | 2 +- lunaix-os/arch/x86/includes/sys/mm/pt_def32.h | 64 +++ lunaix-os/arch/x86/includes/sys/mm/pt_def64.h | 71 +++ .../arch/{i386 => x86}/includes/sys/mm/tlb.h | 0 .../{i386 => x86}/includes/sys/muldiv64.h | 8 + .../arch/{i386 => x86}/includes/sys/pci_hba.h | 0 .../arch/{i386 => x86}/includes/sys/port_io.h | 0 .../arch/x86/includes/sys/syscall_utils.h | 17 + .../arch/{i386 => x86}/includes/sys/trace.h | 0 .../arch/{i386 => x86}/includes/sys/vectors.h | 0 lunaix-os/arch/x86/includes/sys/x86_isa.h | 96 ++++ lunaix-os/arch/{i386 => x86}/klib/fast_crc.c | 0 lunaix-os/arch/x86/klib/fast_str.c | 56 ++ lunaix-os/arch/{i386 => x86}/mm/fault.c | 2 +- lunaix-os/arch/{i386 => x86}/mm/gdt.c | 89 ++- lunaix-os/arch/{i386 => x86}/mm/pmm.c | 28 +- lunaix-os/arch/{i386 => x86}/mm/tlb.c | 0 lunaix-os/arch/{i386 => x86}/mm/vmutils.c | 11 + .../arch/{i386/syscall.S => x86/syscall32.S} | 52 +- lunaix-os/arch/x86/syscall64.S | 124 ++++ lunaix-os/arch/x86/trace.c | 102 ++++ lunaix-os/hal/acpi/acpi.c | 7 +- lunaix-os/hal/acpi/parser/madt_parser.c | 8 +- lunaix-os/hal/acpi/parser/mcfg_parser.c | 5 +- lunaix-os/hal/char/serial.c | 6 +- lunaix-os/hal/char/uart/16550.h | 6 +- lunaix-os/hal/char/uart/16550_base.c | 8 +- lunaix-os/includes/hal/acpi/fadt.h | 1 + lunaix-os/includes/hal/acpi/madt.h | 9 +- lunaix-os/includes/hal/acpi/sdt.h | 3 +- lunaix-os/includes/klibc/ia_utils.h | 2 +- lunaix-os/includes/lunaix/blkpart_gpt.h | 4 +- lunaix-os/includes/lunaix/boot_generic.h | 5 +- lunaix-os/includes/lunaix/compiler.h | 5 +- lunaix-os/includes/lunaix/exebi/elf.h | 106 ++++ lunaix-os/includes/lunaix/exebi/elf32.h | 130 ----- lunaix-os/includes/lunaix/exec.h | 35 +- lunaix-os/includes/lunaix/fs/iso9660.h | 36 +- lunaix-os/includes/lunaix/fs/twimap.h | 4 +- lunaix-os/includes/lunaix/mm/mm.h | 2 +- lunaix-os/includes/lunaix/mm/mmap.h | 1 + lunaix-os/includes/lunaix/mm/pagetable.h | 101 +++- lunaix-os/includes/lunaix/process.h | 2 +- lunaix-os/includes/lunaix/signal.h | 1 + lunaix-os/includes/lunaix/syscall_utils.h | 6 +- lunaix-os/includes/lunaix/types.h | 13 +- lunaix-os/includes/usr/lunaix/mann_flags.h | 10 + lunaix-os/includes/usr/lunaix/syscallid.h | 2 +- lunaix-os/includes/usr/lunaix/threads.h | 6 +- lunaix-os/kernel.mk | 36 +- lunaix-os/kernel/boot_helper.c | 21 +- lunaix-os/kernel/debug/trace.c | 10 +- lunaix-os/kernel/device/device.c | 6 +- lunaix-os/kernel/device/poll.c | 6 +- lunaix-os/kernel/exe/LBuild | 2 +- lunaix-os/kernel/exe/elf-generic/LBuild | 4 + .../elf32bfmt.c => elf-generic/elfbfmt.c} | 80 ++- .../{elf32/ldelf32.c => elf-generic/ldelf.c} | 41 +- lunaix-os/kernel/exe/elf32/LBuild | 4 - lunaix-os/kernel/exe/exec.c | 272 +++++---- lunaix-os/kernel/fs/fs_export.c | 8 +- lunaix-os/kernel/fs/iso9660/file.c | 4 +- lunaix-os/kernel/fs/twifs/twifs.c | 6 +- lunaix-os/kernel/fs/twimap.c | 6 +- lunaix-os/kernel/fs/vfs.c | 2 +- lunaix-os/kernel/kinit.c | 10 +- lunaix-os/kernel/kprint/kprintf.c | 22 +- lunaix-os/kernel/lunad.c | 4 +- lunaix-os/kernel/mm/dmm.c | 2 +- lunaix-os/kernel/mm/fault.c | 41 +- lunaix-os/kernel/mm/mmap.c | 40 +- lunaix-os/kernel/mm/procvm.c | 64 ++- lunaix-os/kernel/mm/region.c | 2 +- lunaix-os/kernel/process/process.c | 2 +- lunaix-os/kernel/process/thread.c | 46 +- lunaix-os/libs/klibc/itoa.c | 10 +- lunaix-os/link/base.ldx | 7 + lunaix-os/link/kernel.ldx | 31 + lunaix-os/link/lga.ldx | 101 ++++ lunaix-os/link/linker.ld | 203 ------- lunaix-os/link/lunaix.ldx | 51 ++ lunaix-os/live_debug.sh | 17 + lunaix-os/makefile | 75 +-- lunaix-os/makeinc/lunabuild.mkinc | 28 + lunaix-os/makeinc/os.mkinc | 5 - lunaix-os/makeinc/qemu.mkinc | 18 - lunaix-os/makeinc/toolchain.mkinc | 3 +- .../scripts/build-tools/lbuild/contract.py | 44 +- .../scripts/build-tools/lcfg/builtins.py | 7 +- lunaix-os/scripts/build-tools/lcfg/common.py | 13 +- lunaix-os/scripts/build-tools/lcfg/lcnodes.py | 7 + lunaix-os/scripts/build-tools/luna_build.py | 32 +- lunaix-os/scripts/gen_ksymtable.sh | 19 +- lunaix-os/scripts/grub/GRUB_TEMPLATE | 2 +- lunaix-os/scripts/qemu.py | 277 +++++++++ lunaix-os/scripts/qemus/qemu_x86_dev.json | 58 ++ lunaix-os/usr/.gitignore | 4 +- lunaix-os/usr/LBuild | 38 ++ lunaix-os/usr/LConfig | 11 + lunaix-os/usr/execs.list | 6 - lunaix-os/usr/init/init.c | 7 +- lunaix-os/usr/libc/LBuild | 28 + lunaix-os/usr/libc/arch/i386/LBuild | 8 + lunaix-os/usr/libc/arch/i386/crt0.S | 10 +- lunaix-os/usr/libc/arch/i386/dirent.c | 4 - lunaix-os/usr/libc/arch/i386/errno.c | 4 - lunaix-os/usr/libc/arch/i386/fcntl.c | 6 - lunaix-os/usr/libc/arch/i386/ioctl.c | 4 - lunaix-os/usr/libc/arch/i386/lunaix.c | 12 - lunaix-os/usr/libc/arch/i386/mann.c | 12 - lunaix-os/usr/libc/arch/i386/mount.c | 14 - lunaix-os/usr/libc/arch/i386/syscall.S | 23 +- lunaix-os/usr/libc/arch/i386/syscall.h | 69 --- lunaix-os/usr/libc/arch/i386/trampoline.S | 12 + lunaix-os/usr/libc/arch/i386/unistd.c | 124 ---- lunaix-os/usr/libc/arch/x86_64/LBuild | 8 + lunaix-os/usr/libc/arch/x86_64/crt0.S | 27 + lunaix-os/usr/libc/arch/x86_64/syscall.S | 43 ++ lunaix-os/usr/libc/arch/x86_64/trampoline.S | 36 ++ lunaix-os/usr/libc/includes/lunaix/mann.h | 4 +- lunaix-os/usr/libc/includes/lunaix/syscall.h | 2 +- lunaix-os/usr/libc/includes/stdio.h | 10 +- lunaix-os/usr/libc/makefile | 11 +- lunaix-os/usr/libc/src/_vprintf.c | 17 + lunaix-os/usr/libc/src/posix/dirent.c | 8 + lunaix-os/usr/libc/src/posix/errno.c | 8 + lunaix-os/usr/libc/src/posix/fcntl.c | 14 + lunaix-os/usr/libc/src/posix/ioctl.c | 15 + lunaix-os/usr/libc/src/posix/lunaix.c | 40 ++ lunaix-os/usr/libc/src/posix/mann.c | 24 + lunaix-os/usr/libc/src/posix/mount.c | 14 + .../libc/{arch/i386 => src/posix}/signal.c | 46 +- lunaix-os/usr/libc/src/posix/unistd.c | 211 +++++++ lunaix-os/usr/libc/src/pthread.c | 30 +- lunaix-os/usr/makefile | 54 +- lunaix-os/usr/sh/sh.c | 33 +- lunaix-os/usr/test_pthread.c | 17 +- lunaix-os/usr/{link-usr.ld => uexec.ldx} | 8 +- 225 files changed, 5244 insertions(+), 2473 deletions(-) delete mode 100644 lunaix-os/arch/i386/LBuild delete mode 100644 lunaix-os/arch/i386/LConfig delete mode 100644 lunaix-os/arch/i386/exceptions/i386_isrdef.c delete mode 100644 lunaix-os/arch/i386/includes/sys/abi.h delete mode 100644 lunaix-os/arch/i386/includes/sys/boot/bstage.h delete mode 100644 lunaix-os/arch/i386/includes/sys/i386_intr.h delete mode 100644 lunaix-os/arch/i386/includes/sys/mm/mempart.h delete mode 100644 lunaix-os/arch/i386/includes/sys/x86_isa.h delete mode 100644 lunaix-os/arch/i386/klib/fast_str.c delete mode 100644 lunaix-os/arch/i386/trace.c create mode 100644 lunaix-os/arch/x86/LBuild create mode 100644 lunaix-os/arch/x86/LConfig rename lunaix-os/arch/{i386 => x86}/arch.c (69%) create mode 100644 lunaix-os/arch/x86/boot/boot_helper.c rename lunaix-os/arch/{i386/boot/boot.S => x86/boot/i386/boot32.S} (74%) rename lunaix-os/arch/{i386/boot => x86/boot/i386}/init32.c (78%) rename lunaix-os/arch/{i386/boot/kpt_setup.c => x86/boot/i386/kremap32.c} (67%) rename lunaix-os/arch/{i386/boot/prologue.S => x86/boot/i386/prologue32.S} (82%) create mode 100644 lunaix-os/arch/x86/boot/kpt_setup.c rename lunaix-os/arch/{i386 => x86}/boot/mb_parser.c (89%) create mode 100644 lunaix-os/arch/x86/boot/x86_64/boot64.S create mode 100644 lunaix-os/arch/x86/boot/x86_64/init64.c create mode 100644 lunaix-os/arch/x86/boot/x86_64/kremap64.c create mode 100644 lunaix-os/arch/x86/boot/x86_64/prologue64.S rename lunaix-os/arch/{i386/exceptions/interrupt.S => x86/exceptions/interrupt32.S} (97%) create mode 100644 lunaix-os/arch/x86/exceptions/interrupt64.S rename lunaix-os/arch/{i386 => x86}/exceptions/interrupts.c (82%) rename lunaix-os/arch/{i386 => x86}/exceptions/intr_routines.c (93%) rename lunaix-os/arch/{i386 => x86}/exceptions/intrhnds.S (96%) create mode 100644 lunaix-os/arch/x86/exceptions/isrdef.c rename lunaix-os/arch/{i386/exceptions/i386_isrm.c => x86/exceptions/isrm.c} (100%) create mode 100644 lunaix-os/arch/x86/exec/elf32.c create mode 100644 lunaix-os/arch/x86/exec/elf64.c create mode 100644 lunaix-os/arch/x86/exec/exec.c rename lunaix-os/arch/{i386 => x86}/failsafe.S (100%) rename lunaix-os/arch/{i386 => x86}/gdbstub.c (92%) rename lunaix-os/arch/{i386 => x86}/hal/LBuild (100%) rename lunaix-os/arch/{i386 => x86}/hal/apic.c (100%) rename lunaix-os/arch/{i386 => x86}/hal/apic_timer.c (100%) rename lunaix-os/arch/{i386 => x86}/hal/apic_timer.h (100%) rename lunaix-os/arch/{i386 => x86}/hal/cpu.c (100%) rename lunaix-os/arch/{i386 => x86}/hal/ioapic.c (100%) rename lunaix-os/arch/{i386 => x86}/hal/mc146818a.c (100%) rename lunaix-os/arch/{i386 => x86}/hal/pic.h (100%) rename lunaix-os/arch/{i386 => x86}/hal/ps2kbd.c (100%) rename lunaix-os/arch/{i386 => x86}/hal/rngx86.c (64%) create mode 100644 lunaix-os/arch/x86/hart.c rename lunaix-os/arch/{i386/hart.c => x86/hart32.c} (79%) create mode 100644 lunaix-os/arch/x86/hart64.c create mode 100644 lunaix-os/arch/x86/includes/linking/base_defs.ld.inc create mode 100644 lunaix-os/arch/x86/includes/linking/boot_secs.ldx create mode 100644 lunaix-os/arch/x86/includes/sys/abi.h create mode 100644 lunaix-os/arch/x86/includes/sys/abi32.h create mode 100644 lunaix-os/arch/x86/includes/sys/abi64.h rename lunaix-os/arch/{i386 => x86}/includes/sys/apic.h (100%) rename lunaix-os/arch/{i386 => x86/includes/sys}/boot/archinit.h (77%) create mode 100644 lunaix-os/arch/x86/includes/sys/boot/bstage.h rename lunaix-os/arch/{i386/includes/sys/boot/multiboot.h => x86/includes/sys/boot/mb.h} (97%) create mode 100644 lunaix-os/arch/x86/includes/sys/boot/mb2.h create mode 100644 lunaix-os/arch/x86/includes/sys/boot/multiboot.S.inc create mode 100644 lunaix-os/arch/x86/includes/sys/boot/multiboot.h rename lunaix-os/arch/{i386 => x86}/includes/sys/cpu.h (77%) rename lunaix-os/arch/{i386 => x86}/includes/sys/crx.h (69%) create mode 100644 lunaix-os/arch/x86/includes/sys/exebi/elf.h rename lunaix-os/arch/{i386 => x86}/includes/sys/failsafe.h (73%) rename lunaix-os/arch/{i386 => x86}/includes/sys/gdbstub.h (100%) rename lunaix-os/arch/{i386 => x86}/includes/sys/hart.h (61%) create mode 100644 lunaix-os/arch/x86/includes/sys/int_handler.h rename lunaix-os/arch/{i386/includes/sys/interrupt.S.inc => x86/includes/sys/interrupt32.S.inc} (100%) create mode 100644 lunaix-os/arch/x86/includes/sys/interrupt64.S.inc rename lunaix-os/arch/{i386 => x86}/includes/sys/ioapic.h (100%) rename lunaix-os/arch/{i386 => x86}/includes/sys/mm/memory.h (87%) create mode 100644 lunaix-os/arch/x86/includes/sys/mm/mempart.h create mode 100644 lunaix-os/arch/x86/includes/sys/mm/mempart32.h create mode 100644 lunaix-os/arch/x86/includes/sys/mm/mempart64.h rename lunaix-os/arch/{i386 => x86}/includes/sys/mm/mm_defs.h (56%) rename lunaix-os/arch/{i386 => x86}/includes/sys/mm/pagetable.h (71%) rename lunaix-os/arch/{i386 => x86}/includes/sys/mm/physical.h (83%) create mode 100644 lunaix-os/arch/x86/includes/sys/mm/pt_def32.h create mode 100644 lunaix-os/arch/x86/includes/sys/mm/pt_def64.h rename lunaix-os/arch/{i386 => x86}/includes/sys/mm/tlb.h (100%) rename lunaix-os/arch/{i386 => x86}/includes/sys/muldiv64.h (91%) rename lunaix-os/arch/{i386 => x86}/includes/sys/pci_hba.h (100%) rename lunaix-os/arch/{i386 => x86}/includes/sys/port_io.h (100%) create mode 100644 lunaix-os/arch/x86/includes/sys/syscall_utils.h rename lunaix-os/arch/{i386 => x86}/includes/sys/trace.h (100%) rename lunaix-os/arch/{i386 => x86}/includes/sys/vectors.h (100%) create mode 100644 lunaix-os/arch/x86/includes/sys/x86_isa.h rename lunaix-os/arch/{i386 => x86}/klib/fast_crc.c (100%) create mode 100644 lunaix-os/arch/x86/klib/fast_str.c rename lunaix-os/arch/{i386 => x86}/mm/fault.c (91%) rename lunaix-os/arch/{i386 => x86}/mm/gdt.c (56%) rename lunaix-os/arch/{i386 => x86}/mm/pmm.c (73%) rename lunaix-os/arch/{i386 => x86}/mm/tlb.c (100%) rename lunaix-os/arch/{i386 => x86}/mm/vmutils.c (72%) rename lunaix-os/arch/{i386/syscall.S => x86/syscall32.S} (72%) create mode 100644 lunaix-os/arch/x86/syscall64.S create mode 100644 lunaix-os/arch/x86/trace.c create mode 100644 lunaix-os/includes/lunaix/exebi/elf.h delete mode 100644 lunaix-os/includes/lunaix/exebi/elf32.h create mode 100644 lunaix-os/kernel/exe/elf-generic/LBuild rename lunaix-os/kernel/exe/{elf32/elf32bfmt.c => elf-generic/elfbfmt.c} (61%) rename lunaix-os/kernel/exe/{elf32/ldelf32.c => elf-generic/ldelf.c} (73%) delete mode 100644 lunaix-os/kernel/exe/elf32/LBuild create mode 100644 lunaix-os/link/base.ldx create mode 100644 lunaix-os/link/kernel.ldx create mode 100644 lunaix-os/link/lga.ldx delete mode 100644 lunaix-os/link/linker.ld create mode 100644 lunaix-os/link/lunaix.ldx create mode 100755 lunaix-os/live_debug.sh create mode 100644 lunaix-os/makeinc/lunabuild.mkinc delete mode 100644 lunaix-os/makeinc/os.mkinc delete mode 100644 lunaix-os/makeinc/qemu.mkinc create mode 100755 lunaix-os/scripts/qemu.py create mode 100644 lunaix-os/scripts/qemus/qemu_x86_dev.json create mode 100644 lunaix-os/usr/LBuild create mode 100644 lunaix-os/usr/LConfig delete mode 100644 lunaix-os/usr/execs.list create mode 100644 lunaix-os/usr/libc/LBuild create mode 100644 lunaix-os/usr/libc/arch/i386/LBuild delete mode 100644 lunaix-os/usr/libc/arch/i386/dirent.c delete mode 100644 lunaix-os/usr/libc/arch/i386/errno.c delete mode 100644 lunaix-os/usr/libc/arch/i386/fcntl.c delete mode 100644 lunaix-os/usr/libc/arch/i386/ioctl.c delete mode 100644 lunaix-os/usr/libc/arch/i386/lunaix.c delete mode 100644 lunaix-os/usr/libc/arch/i386/mann.c delete mode 100644 lunaix-os/usr/libc/arch/i386/mount.c delete mode 100644 lunaix-os/usr/libc/arch/i386/syscall.h delete mode 100644 lunaix-os/usr/libc/arch/i386/unistd.c create mode 100644 lunaix-os/usr/libc/arch/x86_64/LBuild create mode 100644 lunaix-os/usr/libc/arch/x86_64/crt0.S create mode 100644 lunaix-os/usr/libc/arch/x86_64/syscall.S create mode 100644 lunaix-os/usr/libc/arch/x86_64/trampoline.S create mode 100644 lunaix-os/usr/libc/src/posix/dirent.c create mode 100644 lunaix-os/usr/libc/src/posix/errno.c create mode 100644 lunaix-os/usr/libc/src/posix/fcntl.c create mode 100644 lunaix-os/usr/libc/src/posix/ioctl.c create mode 100644 lunaix-os/usr/libc/src/posix/lunaix.c create mode 100644 lunaix-os/usr/libc/src/posix/mann.c create mode 100644 lunaix-os/usr/libc/src/posix/mount.c rename lunaix-os/usr/libc/{arch/i386 => src/posix}/signal.c (58%) create mode 100644 lunaix-os/usr/libc/src/posix/unistd.c rename lunaix-os/usr/{link-usr.ld => uexec.ldx} (62%) diff --git a/README.md b/README.md index 49ec98f..e891078 100644 --- a/README.md +++ b/README.md @@ -12,52 +12,70 @@ LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有 ## 1. 一些实用资源 -如果有意研读LunaixOS的内核代码和其中的设计,以下资料可能会对此有用。 +如果有意研读LunaixOS的内核代码和其中的设计,或欲开始属于自己的OS开发之道,以下资料可能会对此有用。 + [最新的LunaixOS源代码分析教程](docs/tutorial/0-教程介绍和环境搭建.md) + [内核虚拟内存的详细布局](docs/img/lunaix-os-mem.png) + [LunaixOS启动流程概览](docs/img/boot_sequence.jpeg) + LunaixOS总体架构概览(WIP) ++ [作者修改的QEMU](https://github.com/Minep/qemu) (添加了一些额外用于调试的功能) ## 2. 当前进度以及支持的功能 -该操作系统支持x86架构,运行在保护模式中,采用宏内核架构,目前仅支持单核心。架构与内核的解耦合工作正在进行中。 +Lunaix内核具有支持多种不同的指令集架构的能力,目前支持如下: -在下述列表中,则列出目前所支持的所用功能和特性。列表项按照项目时间戳进行升序排列。 ++ x86_32 ++ x86_64 + +Lunaix全部特性一览: + 使用Multiboot进行引导启动 + + Multiboot 1 + + Multiboot 2 (WIP) + APIC/IOAPIC作为中断管理器和计时器 + ACPI + 虚拟内存 -+ 内存管理与按需分页 -+ 键盘输入 + + 架构中性设计 + + 按需分页 + + Copy-on-Write ++ 内存管理 + 进程模型 -+ 54个常见的Linux/POSIX系统调用([附录1](#appendix1)) -+ 用户模式 ++ 61个常见的Linux/POSIX系统调用([附录1](#appendix1)) ++ 用户/内核态隔离 + 信号机制 + PCI 3.0 + PCIe 1.1 (WIP) -+ Serial ATA AHCI -+ 文件系统 ++ 块设备驱动 + + Serial ATA AHCI + + ATA设备 + + ATAPI封装的SCSI协议 ++ 文件系统(POSIX.1-2008, section 5 & 10) + 虚拟文件系统 + + 内核态文件系统(twifs, Lunaix自己的sysfs) + + 设备文件系统(devfs, Lunaix自己的udev) + + 进程文件系统(procfs) + ISO9660 - + 原生 - + Rock Ridge拓展 + + ECMA-119 + + IEEE P1282(Rock Ridge拓展) + 远程GDB串口调试 (COM1@9600Bd) + 用户程序加载与执行 -+ 动态链接 (WIP) + 通用设备抽象层 -+ 通用图形设备抽象层 - + 标准VGA实现 -+ 虚拟终端设备接口(兼容 POSIX.1-2008) + + 架构中性的设备支持位于:`lunaix-os/hal` + + 16550 UART + + ACPI (不完全实现) + + 架构耦合的设备支持位于:`lunaix-os/arch//hal` + + x86 + + APIC/IOAPIC 组合 + + MC146818 RTC + + i8042 PS/2 + + RNG(使用`rdrand`) ++ 通用图形设备抽象层 (Draft) + + 参考:`lunaix-os/hal/gfxa` ++ 虚拟终端设备接口(POSIX.1-2008, section 11) + + 参考:`lunaix-os/hal/term` + 线程模型 - -已经测试过的环境: - -+ QEMU (>=7.0.0) -+ Bochs(SATA功能不支持) -+ Virtualbox -+ Dell G3 3779 + + 用户线程支持(pthread系列) + + 内核线程支持(抢占式内核设计) ## 3. 目录结构 @@ -69,6 +87,8 @@ LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有 ## 4. 编译与构建 +**!如果想要立刻构建并运行,请参考4.6!** + 构建该项目需要满足以下条件: + gcc 工具链 @@ -78,7 +98,7 @@ LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有 ### 4.1 使用 GNU CC 工具链 -正如同大多数OS一样,LunaixOS 是一个混合了 C 和汇编的产物。这就意味着你得要使用一些标准的C编译器来构建Lunaix。在这里,我推荐使用 GNU CC 工具链来进行构建。至于其他的工具链,如llvm,也可以去尝试,但对此我就不能作任何的保证了。 +正如同大多数OS一样,LunaixOS 是一个混合了 C 和汇编的产物。这就意味着你得要使用一些标准的C编译器来构建Lunaix。在这里,我推荐使用 GNU CC 工具链来进行构建。因为Lunaix 在编写时使用了大量的GNU CC 相关编译器属性修饰 (`__attribute__`) 。假若使用其他工具链,如LLVM,我对此就不能做出任何保证了。 如果你使用的是基于 x86 指令集的Linux系统,不论是64位还是32位,**其本机自带的gcc就足以编译Lunaix**。 当然了,如果说你的平台是其他非x86的,你也可以指定使用某个针对x86_32的gcc套件来进行交叉编译——在`make`时通过`CX_PREFIX`变量来指定gcc套件的前缀。如下例所示,我们可以在任意平台上,如risc-v,单独使用一个面向x86_32的gcc来进行交叉编译: @@ -86,89 +106,82 @@ LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有 make CX_PREFIX=i686-linux-gnu- all ``` -由于目前Lunaix仅支持x86_32微架构, `CX_PREFIX` 指向的gcc必须具有针对x86_32架构进行交叉编译的能力。 - ### 4.2 Docker镜像 对于开发环境,本项目也提供了Docker镜像封装。开箱即用,无需配置,非常适合懒人或惜时者。详细使用方法请转到:[Lunaix OSDK项目](https://github.com/Minep/os-devkit)。 ### 4.3 构建选项 + 假若条件满足,那么可以直接执行`make all`进行构建,完成后可在生成的`build`目录下找到可引导的iso。 本项目支持的make命令: | 命令 | 用途 | | ------------------------ | ----------------------------------------------- | -| `make all` | 构建镜像(`-O2`,但禁用CSE相关的优化项 **※** ) | -| `make instable` | 构建镜像(`-O2`,开启CSE相关优化) | -| `make all-debug` | 构建适合调试用的镜像(`-Og`) | -| `make run` | 使用QEMU运行build目录下的镜像 | -| `make debug-qemu` | 构建并使用QEMU进行调试 | -| `make debug-bochs` | 构建并使用Bochs进行调试 | -| `make debug-qemu-vscode` | 用于vscode整合 | -| `make clean` | 删除build目录 | +| `make all` | 等价于 `make image` | +| `make image` | 构建ISO镜像,可直接启动,使用ISO9660文件系统 | +| `make kernel` | 构建内核ELF镜像,无法直接启动,需要引导程序 | +| `make clean` | 删除构建缓存,用于重新构建 | +| `make config` | 配置Lunaix | -**※:由于在`-O2`模式下,GCC会进行CSE优化,这导致LunaixOS会出现一些非常奇怪、离谱的bug,从而影响到基本运行。具体原因有待调查。** +与make命令配套的环境变量,Lunaix的makefile会自动检测这些环境变量,以更改构建行为 -### 4.4 设置内核启动参数 ++ `MODE={debug|release}` 使用debug模式构建(-Og)或者release模式(-O2) ++ `ARCH=` 为指定的指令集架构编译Lunaix。 所使用的配置选项均为选定架构默认,该环境变量 + 存在的目的就是方便用户进行快速编译,而无需钻研Lunaix的种种配置项。 -在 make 的时候通过`CMDLINE`变量可以设置内核启动参数列表。该列表可以包含多个参数,通过一个或多个空格来分割。每个参数可以为键值对 `=` 或者是开关标志位 ``。目前 Lunaix 支持以下参数: +### 4.4 Lunaix的功能配置 -+ `console=` 设置系统终端的输入输出设备(tty设备)。其中 `` 是设备文件路径 (注意,这里的设备文件路径是针对Lunaix,而非Linux)。关于LunaixOS设备文件系统的介绍可参考 Lunaix Wiki(WIP) +Lunaix是一个可配置的内核,允许用户在编译前选择应当包含或移除的功能。 -如果`CMDLINE`未指定,则将会载入默认参数: +使用`make config`来进行基于命令行的交互配置。呈现方式采用Shell的形式,所有的配置项按照类似于文件树的形式组织,如单个配置项为一个“文件”,多个配置项组成的配置组为一个目录,呈现形式为方括号`[]`包裹起来的项目。在提示符中输入`usage`并回车可以查看具体的使用方法。 + +一个最常用的配置可能就是`architecture_support/arch`了,也就是配置Lunaix所面向的指令集。比如,编译一个在x86_64平台上运行的Lunaix,在提示符中输入(**注意等号两侧的空格,这是不能省略的**): ``` -console=/dev/ttyFB0 +/architecture_support/arch = x86_64 ``` -其中,`/dev/ttyFB0` 指向基于VGA文本模式的tty设备,也就是平时启动QEMU时看到的黑色窗口。 - -当然,读者也可以使用 `/dev/ttyS0` 来作为默认tty设备,来验证 Lunaix 的灵活性与兼容性。该路径指向第一个串口设备。可以通过telnet协议在`12345`端口上进行访问——端口号可以自行修改QEMU启动参数(位于:`makeinc/qemu.mkinc`)来变更。 - -**注意:** 根据操作系统和键盘布局的不同,telnet客户端对一些关键键位的映射(如退格,回车)可能有所差别(如某些版本的Linux会将退格键映射为`0x7f`,也就是ASCII的``字符,而非我们熟知`0x08`)。如果读者想要通过串口方式把玩Lunaix,请修改`usr/init/init.c`里面的终端初始化代码,将`VERASE`设置为正确的映射(修改方式可以参考 POSIX termios 的使用方式。由于Lunaix的终端接口的实现是完全兼容POSIX的,读者可以直接去查阅Linux自带的帮助`man termios`,无需作任何的转换) - -## 5. 运行,分支以及 Issue +之后输入`exit`保存并推出。而后正常编译。 -### 5.1 虚拟磁盘(非必须) -你可以绑定一个虚拟磁盘镜像,可以使用如下命令快速创建一个: +### 4.5 设置内核启动参数 -```bash -qemu-img create -f vdi machine/disk0.vdi 128M -``` +在 make 的时候通过`CMDLINE`变量可以设置内核启动参数列表。该列表可以包含多个参数,通过一个或多个空格来分割。每个参数可以为键值对 `=` 或者是开关标志位 ``。目前 Lunaix 支持以下参数: -如果你想要使用别的磁盘镜像,需要修改`configs/make-debug-tool` ++ `console=` 设置系统终端的输入输出设备(tty设备)。其中 `` 是设备文件路径 (注意,这里的设备文件路径是针对Lunaix,而非Linux)。关于LunaixOS设备文件系统的介绍可参考 Lunaix Wiki(WIP) -找到这一行: +如果`CMDLINE`未指定,则将会载入默认参数: ``` --drive id=disk,file="machine/disk0.vdi",if=none \ +console=/dev/ttyFB0 ``` -然后把`machine/disk0.vdi`替换成你的磁盘路径。 +其中,`/dev/ttyFB0` 指向基于VGA文本模式的tty设备,也就是平时启动QEMU时看到的黑色窗口。 -有很多办法去创建一个虚拟磁盘,比如[qemu-img](https://qemu-project.gitlab.io/qemu/system/images.html)。 +当然,读者也可以使用 `/dev/ttyS0` 来作为默认tty设备,来验证 Lunaix 的灵活性与兼容性。该路径指向第一个串口设备。可以通过telnet协议在`12345`端口上进行访问——端口号可以自行修改QEMU启动参数(位于:`makeinc/qemu.mkinc`)来变更。 -### 5.2 代码稳定性 +**注意:** 根据操作系统和键盘布局的不同,telnet客户端对一些关键键位的映射(如退格,回车)可能有所差别(如某些版本的Linux会将退格键映射为`0x7f`,也就是ASCII的``字符,而非我们熟知`0x08`)。如果读者想要通过串口方式把玩Lunaix,请修改`usr/init/init.c`里面的终端初始化代码,将`VERASE`设置为正确的映射(修改方式可以参考 POSIX termios 的使用方式。由于Lunaix的终端接口的实现是完全兼容POSIX的,读者可以直接去查阅Linux自带的帮助`man termios`,无需作任何的转换) -主分支一般是稳定的。因为在大多数情况下,我都会尽量保证本机运行无误后,push到该分支中。至于其他的分支,则是作为标记或者是开发中的功能。前者标记用分支一般会很快删掉;后者开发分支不能保证稳定性,这些分支的代码有可能没有经过测试,但可以作为Lunaix当前开发进度的参考。 +### 4.6 测试与体验 Lunaix -该系统是经过虚拟机和真机测试。如果发现在使用`make all`之后,虚拟机中运行报错,则一般是编译器优化问题。这个问题笔者一般很快就会修复,如果你使用别的版本的gcc(笔者版本11.2),出现了此问题,欢迎提issue。请参考[附录3:Issue的提交](#appendix3) +用户可以使用脚本`live_debug.sh` 来快速运行Lunaix。 该脚本自动按照默认的选项构建Lunaix,而后调用 `scripts/qemu.py` 根据配置文件生成QEMU启动参数 +(配置文件位于`scripts/qemus/`) -下面列出一些可能会出现的问题。 +由于该脚本的主要用途是方便作者进行调试,所以在QEMU窗口打开后还需要进行以下动作: -#### 问题#1: QEMU下8042控制器提示找不到 +1. 使用telnet连接到`localhost:12345`,这里是Lunaix进行标准输入输出所使用的UART映射(QEMU为guest提供UART实现,并将其利用telnet协议重定向到宿主机) +2. 在GDB窗口中输入`c`然后回车,此时Lunaix开始运行。这样做的目的是允许在QEMU进行模拟前,事先打好感兴趣的断点。 -这是QEMU配置ACPI时的一个bug,在7.0.0版中修复了。 +该脚本的运行需要设置 `ARCH=` 环境变量,其值需要与编译时制定的值一致。 -#### 问题#2:多进程运行时,偶尔会出现General Protection错误 +## 5. 运行,分支以及 Issue -这很大概率是出现了竞态条件。虽然是相当不可能的。但如果出现了,还是请提issue。 +### 5.1 代码稳定性 -#### 问题#3:Bochs无法运行,提示找不到AHCI控制器 +主分支一般是稳定的。因为在大多数情况下,我都会尽量保证本机运行无误后,push到该分支中。至于其他的分支,则是作为标记或者是开发中的功能。前者标记用分支一般会很快删掉;后者开发分支不能保证稳定性,这些分支的代码有可能没有经过测试,但可以作为Lunaix当前开发进度的参考。 -正常,**因为Bochs不支持SATA**。请使用QEMU或VirtualBox。 +该系统是经过虚拟机和真机测试。如果发现在使用`make all`之后,虚拟机中运行报错,则一般是编译器优化问题。这个问题笔者一般很快就会修复,如果你使用别的版本的gcc(笔者版本11.2),出现了此问题,欢迎提issue。请参考[附录3:Issue的提交](#appendix3) ## 6. 调试 Lunaix 内核 @@ -189,7 +202,15 @@ qemu-img create -f vdi machine/disk0.vdi 128M ## 7. 参考教程 -**没有!!** 本教程以及该操作系统均为原创,没有基于任何市面上现行的操作系统开发教程,且并非是基于任何的开源内核的二次开发。 +#### 没有!! + +本教程以及该操作系统的所有的架构设计与实现**均为原创**。 + +对此,作者可以保证,该项目是做到了三个 “没有”: + ++ **没有** 参考任何现行的,关于操作系统开发的,教程或书籍。 ++ **没有** 参考任何开源内核的源代码(包括Linux) ++ **没有** 基于任何开源内核的二次开发行为。 为了制作LunaixOS,作者耗费大量时间和精力钻研技术文档,手册,理论书籍以及现行工业标准,从而尽量保证了知识的一手性。(这样一来,读者和听众们也算是拿到了二手的知识,而不是三手,四手,甚至n手的知识)。 @@ -227,7 +248,7 @@ qemu-img create -f vdi machine/disk0.vdi 128M #### 网站 -+ [OSDev](https://wiki.osdev.org/Main_Page) - 杂七杂八的参考,很多过来人的经验。作者主要用于上古资料查询以及收集;技术文献,手册,标准的粗略总结;以及开发环境/工具链的搭建。 ++ [OSDev](https://wiki.osdev.org/Main_Page) - 杂七杂八的参考,很多过来人的经验。作者主要用于上古资料查询以及收集;技术文献,手册,标准的粗略总结;以及开发环境/工具链的搭建。当然,上面的内容假设了x86_32架构的生态,对于其他的ISA支持,该网站便失去了其价值了。 + [FreeVGA](http://www.osdever.net/FreeVGA/home.htm) - 98年的资源!关于VGA编程技术的宝藏网站。 + GNU CC 和 GNU LD 的官方文档。 + [PCI Lookup](https://www.pcilookup.com/) - PCI设备编号查询 diff --git a/lunaix-os/.gitignore b/lunaix-os/.gitignore index eb55d3d..a8dbb20 100644 --- a/lunaix-os/.gitignore +++ b/lunaix-os/.gitignore @@ -14,6 +14,8 @@ __pycache__/ .builder/ .config.json +link/lunaix.ld + .gdb_history **.o diff --git a/lunaix-os/.vscode/c_cpp_properties.json b/lunaix-os/.vscode/c_cpp_properties.json index a0c612f..b315a23 100644 --- a/lunaix-os/.vscode/c_cpp_properties.json +++ b/lunaix-os/.vscode/c_cpp_properties.json @@ -6,16 +6,33 @@ "${workspaceFolder}/includes", "${workspaceFolder}/includes/usr", "${workspaceFolder}/usr/includes", - "${workspaceFolder}/arch/i386/includes", + "${workspaceFolder}/arch/x86/includes", ], "compilerArgs": [ "-ffreestanding", - "-D__ARCH__=i386", - "-D__LUNAIXOS_DEBUG__", + "-m32", "-include .builder/configs.h" ], "defines": [], - "compilerPath": "${HOME}/opt/i686-gcc-12/bin/i686-elf-gcc", + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu99", + "intelliSenseMode": "gcc-x86" + }, + { + "name": "OS-DEV64", + "includePath": [ + "${workspaceFolder}/includes", + "${workspaceFolder}/includes/usr", + "${workspaceFolder}/usr/includes", + "${workspaceFolder}/arch/x86/includes", + ], + "compilerArgs": [ + "-ffreestanding", + "-m64", + "-include .builder/configs.h" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", "cStandard": "gnu99", "intelliSenseMode": "gcc-x86" } diff --git a/lunaix-os/LConfig b/lunaix-os/LConfig index 3d0a179..ecd4a03 100644 --- a/lunaix-os/LConfig +++ b/lunaix-os/LConfig @@ -13,7 +13,7 @@ def lunaix_ver(): type(str) seq_num = int(time.time() / 3600) - default("dev-2024_%d"%(seq_num)) + default("%s dev-2024_%d"%(v(arch), seq_num)) @Collection def debug_and_testing(): diff --git a/lunaix-os/arch/LBuild b/lunaix-os/arch/LBuild index 8a4af8b..8617597 100644 --- a/lunaix-os/arch/LBuild +++ b/lunaix-os/arch/LBuild @@ -1,6 +1,7 @@ use({ config("arch"): { - "i386": "i386", + "i386": "x86", + "x86_64": "x86", "aarch64": "arm", "rv64": "riscv" } diff --git a/lunaix-os/arch/LConfig b/lunaix-os/arch/LConfig index 30c67b6..22f96c0 100644 --- a/lunaix-os/arch/LConfig +++ b/lunaix-os/arch/LConfig @@ -1,4 +1,4 @@ -include("i386/LConfig") +include("x86/LConfig") @Collection def architecture_support(): @@ -11,3 +11,24 @@ def architecture_support(): """ Config ISA support """ type(["i386", "x86_64", "aarch64", "rv64"]) default("i386") + + env_val = env("ARCH") + if env_val is not None: + set_value(env_val) + + @Term + @ReadOnly + def arch_bits(): + type(["64", "32"]) + match v(arch): + case "i386": + default("32") + case "aarch64": + default("64") + case "rv64": + default("64") + case "x86_64": + default("64") + case _: + default("32") + \ No newline at end of file diff --git a/lunaix-os/arch/README.md b/lunaix-os/arch/README.md index 0a2ead5..df5349f 100644 --- a/lunaix-os/arch/README.md +++ b/lunaix-os/arch/README.md @@ -19,13 +19,13 @@ Lunaix provide bunch of headers that **MUST** be implemented. + Add implementation to function signature defined in header files under `includes/lunaix/generic` -+ Add implementation of syscall dispatching (Reference: `arhc/i386/syscall.S`) ++ Add implementation of syscall dispatching (Reference: `arhc/x86/syscall.S`) + Add implementation of interrupt handler dispatching (Reference: - `arhc/i386/exceptions/intrhnds.S`) + `arhc/x86/exceptions/intrhnds.S`) + Add implementation of context switching, signal handling. (Reference: - `arhc/i386/exceptions/interrupt.S`) + `arhc/x86/exceptions/interrupt.S`) **TODO: make this procedure more standalone** ## Preparing the Flows diff --git a/lunaix-os/arch/generic/includes/sys/hart.h b/lunaix-os/arch/generic/includes/sys/hart.h index 340a0ee..e820608 100644 --- a/lunaix-os/arch/generic/includes/sys/hart.h +++ b/lunaix-os/arch/generic/includes/sys/hart.h @@ -22,9 +22,6 @@ struct exec_param struct hart_state* parent_state; } compact; -void -hart_flow_redirect(struct hart_state* state, ptr_t pc, ptr_t sp); - ptr_t hart_pc(struct hart_state* state); diff --git a/lunaix-os/arch/generic/includes/sys/mm/mempart.h b/lunaix-os/arch/generic/includes/sys/mm/mempart.h index 28d7ba6..055da0c 100644 --- a/lunaix-os/arch/generic/includes/sys/mm/mempart.h +++ b/lunaix-os/arch/generic/includes/sys/mm/mempart.h @@ -1,11 +1,6 @@ #ifndef __LUNAIX_ARCH_MEMPART_H #define __LUNAIX_ARCH_MEMPART_H -#define MEM_PAGE 0x1000UL -#define MEM_1M 0x100000UL -#define MEM_4M 0x400000UL -#define MEM_HUGE 0x400000UL -#define MEM_1G 0x40000000UL #define END_POINT(name) (name + name##_SIZE - 1) diff --git a/lunaix-os/arch/generic/includes/sys/mm/mm_defs.h b/lunaix-os/arch/generic/includes/sys/mm/mm_defs.h index 79ce14c..6370963 100644 --- a/lunaix-os/arch/generic/includes/sys/mm/mm_defs.h +++ b/lunaix-os/arch/generic/includes/sys/mm/mm_defs.h @@ -6,7 +6,7 @@ #include "pagetable.h" #define KSTACK_PAGES 3 -#define KSTACK_SIZE (KSTACK_PAGES * MEM_PAGE) +#define KSTACK_SIZE (KSTACK_PAGES * PAGE_SIZE) /* Regardless architecture we need to draw the line very carefully, and must diff --git a/lunaix-os/arch/i386/LBuild b/lunaix-os/arch/i386/LBuild deleted file mode 100644 index 8a7f155..0000000 --- a/lunaix-os/arch/i386/LBuild +++ /dev/null @@ -1,52 +0,0 @@ -use("hal") - -sources([ - "exceptions/interrupts.c", - "exceptions/i386_isrdef.c", - "exceptions/intr_routines.c", - "exceptions/i386_isrm.c", - - "exceptions/interrupt.S", - "exceptions/intrhnds.S", -]) - -sources([ - "boot/mb_parser.c", - "boot/kpt_setup.c", - "boot/init32.c", - - "boot/boot.S", - "boot/prologue.S" -]) - -sources([ - "mm/fault.c", - "mm/tlb.c", - "mm/pmm.c", - "mm/gdt.c", - "mm/vmutils.c" -]) - -sources([ - "klib/fast_crc.c", - "klib/fast_str.c", - "hart.c", - "arch.c", - "gdbstub.c", - "trace.c", - - "syscall.S", - "failsafe.S" -]) - -headers([ - "includes" -]) - -compile_opts([ - "-m32" -]) - -linking_opts([ - "-m32" -]) \ No newline at end of file diff --git a/lunaix-os/arch/i386/LConfig b/lunaix-os/arch/i386/LConfig deleted file mode 100644 index 1445686..0000000 --- a/lunaix-os/arch/i386/LConfig +++ /dev/null @@ -1,23 +0,0 @@ - -@Group -def x86_configurations(): - @Term - def x86_enable_sse_feature(): - """ - Config whether to allow using SSE feature for certain - optimization - """ - - type(bool) - default(False) - - add_to_collection(architecture_support) - - - @Term - def x86_boot_options(): - type(["multiboot"]) - # type(["multiboot", "none"]) - default("multiboot") - - return v(arch) == "i386" \ No newline at end of file diff --git a/lunaix-os/arch/i386/exceptions/i386_isrdef.c b/lunaix-os/arch/i386/exceptions/i386_isrdef.c deleted file mode 100644 index 8bebb96..0000000 --- a/lunaix-os/arch/i386/exceptions/i386_isrdef.c +++ /dev/null @@ -1,537 +0,0 @@ -/* Generated from i386_isrdef.c.j2. Do NOT modify */ - -#include -#include - -#define IDT_INTERRUPT 0x70 -#define KERNEL_CS 0x8 -#define IDT_ATTR(dpl, type) (((type) << 5) | ((dpl & 3) << 13) | (1 << 15)) -#define IDT_ENTRY 256 - -#define DECLARE_ISR(iv) extern void _asm_isr##iv(); - -#define ISR_INSTALL(idt, iv, isr, dpl) \ - _idt[iv] = ((ptr_t)isr & 0xffff0000) | IDT_ATTR(dpl, IDT_INTERRUPT); \ - _idt[iv] <<= 32; \ - _idt[iv] |= (KERNEL_CS << 16) | ((ptr_t)isr & 0x0000ffff); \ - -u64_t _idt[IDT_ENTRY]; -u16_t _idt_limit = sizeof(_idt) - 1; - -DECLARE_ISR(0) -DECLARE_ISR(1) -DECLARE_ISR(2) -DECLARE_ISR(3) -DECLARE_ISR(4) -DECLARE_ISR(5) -DECLARE_ISR(6) -DECLARE_ISR(7) -DECLARE_ISR(8) -DECLARE_ISR(9) -DECLARE_ISR(10) -DECLARE_ISR(11) -DECLARE_ISR(12) -DECLARE_ISR(13) -DECLARE_ISR(14) -DECLARE_ISR(15) -DECLARE_ISR(16) -DECLARE_ISR(17) -DECLARE_ISR(18) -DECLARE_ISR(19) -DECLARE_ISR(20) -DECLARE_ISR(21) -DECLARE_ISR(22) -DECLARE_ISR(23) -DECLARE_ISR(24) -DECLARE_ISR(25) -DECLARE_ISR(26) -DECLARE_ISR(27) -DECLARE_ISR(28) -DECLARE_ISR(29) -DECLARE_ISR(30) -DECLARE_ISR(31) -DECLARE_ISR(32) -DECLARE_ISR(33) -DECLARE_ISR(34) -DECLARE_ISR(35) -DECLARE_ISR(36) -DECLARE_ISR(37) -DECLARE_ISR(38) -DECLARE_ISR(39) -DECLARE_ISR(40) -DECLARE_ISR(41) -DECLARE_ISR(42) -DECLARE_ISR(43) -DECLARE_ISR(44) -DECLARE_ISR(45) -DECLARE_ISR(46) -DECLARE_ISR(47) -DECLARE_ISR(48) -DECLARE_ISR(49) -DECLARE_ISR(50) -DECLARE_ISR(51) -DECLARE_ISR(52) -DECLARE_ISR(53) -DECLARE_ISR(54) -DECLARE_ISR(55) -DECLARE_ISR(56) -DECLARE_ISR(57) -DECLARE_ISR(58) -DECLARE_ISR(59) -DECLARE_ISR(60) -DECLARE_ISR(61) -DECLARE_ISR(62) -DECLARE_ISR(63) -DECLARE_ISR(64) -DECLARE_ISR(65) -DECLARE_ISR(66) -DECLARE_ISR(67) -DECLARE_ISR(68) -DECLARE_ISR(69) -DECLARE_ISR(70) -DECLARE_ISR(71) -DECLARE_ISR(72) -DECLARE_ISR(73) -DECLARE_ISR(74) -DECLARE_ISR(75) -DECLARE_ISR(76) -DECLARE_ISR(77) -DECLARE_ISR(78) -DECLARE_ISR(79) -DECLARE_ISR(80) -DECLARE_ISR(81) -DECLARE_ISR(82) -DECLARE_ISR(83) -DECLARE_ISR(84) -DECLARE_ISR(85) -DECLARE_ISR(86) -DECLARE_ISR(87) -DECLARE_ISR(88) -DECLARE_ISR(89) -DECLARE_ISR(90) -DECLARE_ISR(91) -DECLARE_ISR(92) -DECLARE_ISR(93) -DECLARE_ISR(94) -DECLARE_ISR(95) -DECLARE_ISR(96) -DECLARE_ISR(97) -DECLARE_ISR(98) -DECLARE_ISR(99) -DECLARE_ISR(100) -DECLARE_ISR(101) -DECLARE_ISR(102) -DECLARE_ISR(103) -DECLARE_ISR(104) -DECLARE_ISR(105) -DECLARE_ISR(106) -DECLARE_ISR(107) -DECLARE_ISR(108) -DECLARE_ISR(109) -DECLARE_ISR(110) -DECLARE_ISR(111) -DECLARE_ISR(112) -DECLARE_ISR(113) -DECLARE_ISR(114) -DECLARE_ISR(115) -DECLARE_ISR(116) -DECLARE_ISR(117) -DECLARE_ISR(118) -DECLARE_ISR(119) -DECLARE_ISR(120) -DECLARE_ISR(121) -DECLARE_ISR(122) -DECLARE_ISR(123) -DECLARE_ISR(124) -DECLARE_ISR(125) -DECLARE_ISR(126) -DECLARE_ISR(127) -DECLARE_ISR(128) -DECLARE_ISR(129) -DECLARE_ISR(130) -DECLARE_ISR(131) -DECLARE_ISR(132) -DECLARE_ISR(133) -DECLARE_ISR(134) -DECLARE_ISR(135) -DECLARE_ISR(136) -DECLARE_ISR(137) -DECLARE_ISR(138) -DECLARE_ISR(139) -DECLARE_ISR(140) -DECLARE_ISR(141) -DECLARE_ISR(142) -DECLARE_ISR(143) -DECLARE_ISR(144) -DECLARE_ISR(145) -DECLARE_ISR(146) -DECLARE_ISR(147) -DECLARE_ISR(148) -DECLARE_ISR(149) -DECLARE_ISR(150) -DECLARE_ISR(151) -DECLARE_ISR(152) -DECLARE_ISR(153) -DECLARE_ISR(154) -DECLARE_ISR(155) -DECLARE_ISR(156) -DECLARE_ISR(157) -DECLARE_ISR(158) -DECLARE_ISR(159) -DECLARE_ISR(160) -DECLARE_ISR(161) -DECLARE_ISR(162) -DECLARE_ISR(163) -DECLARE_ISR(164) -DECLARE_ISR(165) -DECLARE_ISR(166) -DECLARE_ISR(167) -DECLARE_ISR(168) -DECLARE_ISR(169) -DECLARE_ISR(170) -DECLARE_ISR(171) -DECLARE_ISR(172) -DECLARE_ISR(173) -DECLARE_ISR(174) -DECLARE_ISR(175) -DECLARE_ISR(176) -DECLARE_ISR(177) -DECLARE_ISR(178) -DECLARE_ISR(179) -DECLARE_ISR(180) -DECLARE_ISR(181) -DECLARE_ISR(182) -DECLARE_ISR(183) -DECLARE_ISR(184) -DECLARE_ISR(185) -DECLARE_ISR(186) -DECLARE_ISR(187) -DECLARE_ISR(188) -DECLARE_ISR(189) -DECLARE_ISR(190) -DECLARE_ISR(191) -DECLARE_ISR(192) -DECLARE_ISR(193) -DECLARE_ISR(194) -DECLARE_ISR(195) -DECLARE_ISR(196) -DECLARE_ISR(197) -DECLARE_ISR(198) -DECLARE_ISR(199) -DECLARE_ISR(200) -DECLARE_ISR(201) -DECLARE_ISR(202) -DECLARE_ISR(203) -DECLARE_ISR(204) -DECLARE_ISR(205) -DECLARE_ISR(206) -DECLARE_ISR(207) -DECLARE_ISR(208) -DECLARE_ISR(209) -DECLARE_ISR(210) -DECLARE_ISR(211) -DECLARE_ISR(212) -DECLARE_ISR(213) -DECLARE_ISR(214) -DECLARE_ISR(215) -DECLARE_ISR(216) -DECLARE_ISR(217) -DECLARE_ISR(218) -DECLARE_ISR(219) -DECLARE_ISR(220) -DECLARE_ISR(221) -DECLARE_ISR(222) -DECLARE_ISR(223) -DECLARE_ISR(224) -DECLARE_ISR(225) -DECLARE_ISR(226) -DECLARE_ISR(227) -DECLARE_ISR(228) -DECLARE_ISR(229) -DECLARE_ISR(230) -DECLARE_ISR(231) -DECLARE_ISR(232) -DECLARE_ISR(233) -DECLARE_ISR(234) -DECLARE_ISR(235) -DECLARE_ISR(236) -DECLARE_ISR(237) -DECLARE_ISR(238) -DECLARE_ISR(239) -DECLARE_ISR(240) -DECLARE_ISR(241) -DECLARE_ISR(242) -DECLARE_ISR(243) -DECLARE_ISR(244) -DECLARE_ISR(245) -DECLARE_ISR(246) -DECLARE_ISR(247) -DECLARE_ISR(248) -DECLARE_ISR(249) -DECLARE_ISR(250) -DECLARE_ISR(251) -DECLARE_ISR(252) -DECLARE_ISR(253) -DECLARE_ISR(254) -DECLARE_ISR(255) - -void -exception_install_handler() -{ - ISR_INSTALL(_idt, 0, _asm_isr0, 0) - ISR_INSTALL(_idt, 1, _asm_isr1, 0) - ISR_INSTALL(_idt, 2, _asm_isr2, 0) - ISR_INSTALL(_idt, 3, _asm_isr3, 0) - ISR_INSTALL(_idt, 4, _asm_isr4, 0) - ISR_INSTALL(_idt, 5, _asm_isr5, 0) - ISR_INSTALL(_idt, 6, _asm_isr6, 0) - ISR_INSTALL(_idt, 7, _asm_isr7, 0) - ISR_INSTALL(_idt, 8, _asm_isr8, 0) - ISR_INSTALL(_idt, 9, _asm_isr9, 0) - ISR_INSTALL(_idt, 10, _asm_isr10, 0) - ISR_INSTALL(_idt, 11, _asm_isr11, 0) - ISR_INSTALL(_idt, 12, _asm_isr12, 0) - ISR_INSTALL(_idt, 13, _asm_isr13, 0) - ISR_INSTALL(_idt, 14, _asm_isr14, 0) - ISR_INSTALL(_idt, 15, _asm_isr15, 0) - ISR_INSTALL(_idt, 16, _asm_isr16, 0) - ISR_INSTALL(_idt, 17, _asm_isr17, 0) - ISR_INSTALL(_idt, 18, _asm_isr18, 0) - ISR_INSTALL(_idt, 19, _asm_isr19, 0) - ISR_INSTALL(_idt, 20, _asm_isr20, 0) - ISR_INSTALL(_idt, 21, _asm_isr21, 0) - ISR_INSTALL(_idt, 22, _asm_isr22, 0) - ISR_INSTALL(_idt, 23, _asm_isr23, 0) - ISR_INSTALL(_idt, 24, _asm_isr24, 0) - ISR_INSTALL(_idt, 25, _asm_isr25, 0) - ISR_INSTALL(_idt, 26, _asm_isr26, 0) - ISR_INSTALL(_idt, 27, _asm_isr27, 0) - ISR_INSTALL(_idt, 28, _asm_isr28, 0) - ISR_INSTALL(_idt, 29, _asm_isr29, 0) - ISR_INSTALL(_idt, 30, _asm_isr30, 0) - ISR_INSTALL(_idt, 31, _asm_isr31, 0) - ISR_INSTALL(_idt, 32, _asm_isr32, 0) - ISR_INSTALL(_idt, 33, _asm_isr33, 3) - ISR_INSTALL(_idt, 34, _asm_isr34, 0) - ISR_INSTALL(_idt, 35, _asm_isr35, 0) - ISR_INSTALL(_idt, 36, _asm_isr36, 0) - ISR_INSTALL(_idt, 37, _asm_isr37, 0) - ISR_INSTALL(_idt, 38, _asm_isr38, 0) - ISR_INSTALL(_idt, 39, _asm_isr39, 0) - ISR_INSTALL(_idt, 40, _asm_isr40, 0) - ISR_INSTALL(_idt, 41, _asm_isr41, 0) - ISR_INSTALL(_idt, 42, _asm_isr42, 0) - ISR_INSTALL(_idt, 43, _asm_isr43, 0) - ISR_INSTALL(_idt, 44, _asm_isr44, 0) - ISR_INSTALL(_idt, 45, _asm_isr45, 0) - ISR_INSTALL(_idt, 46, _asm_isr46, 0) - ISR_INSTALL(_idt, 47, _asm_isr47, 0) - ISR_INSTALL(_idt, 48, _asm_isr48, 0) - ISR_INSTALL(_idt, 49, _asm_isr49, 0) - ISR_INSTALL(_idt, 50, _asm_isr50, 0) - ISR_INSTALL(_idt, 51, _asm_isr51, 0) - ISR_INSTALL(_idt, 52, _asm_isr52, 0) - ISR_INSTALL(_idt, 53, _asm_isr53, 0) - ISR_INSTALL(_idt, 54, _asm_isr54, 0) - ISR_INSTALL(_idt, 55, _asm_isr55, 0) - ISR_INSTALL(_idt, 56, _asm_isr56, 0) - ISR_INSTALL(_idt, 57, _asm_isr57, 0) - ISR_INSTALL(_idt, 58, _asm_isr58, 0) - ISR_INSTALL(_idt, 59, _asm_isr59, 0) - ISR_INSTALL(_idt, 60, _asm_isr60, 0) - ISR_INSTALL(_idt, 61, _asm_isr61, 0) - ISR_INSTALL(_idt, 62, _asm_isr62, 0) - ISR_INSTALL(_idt, 63, _asm_isr63, 0) - ISR_INSTALL(_idt, 64, _asm_isr64, 0) - ISR_INSTALL(_idt, 65, _asm_isr65, 0) - ISR_INSTALL(_idt, 66, _asm_isr66, 0) - ISR_INSTALL(_idt, 67, _asm_isr67, 0) - ISR_INSTALL(_idt, 68, _asm_isr68, 0) - ISR_INSTALL(_idt, 69, _asm_isr69, 0) - ISR_INSTALL(_idt, 70, _asm_isr70, 0) - ISR_INSTALL(_idt, 71, _asm_isr71, 0) - ISR_INSTALL(_idt, 72, _asm_isr72, 0) - ISR_INSTALL(_idt, 73, _asm_isr73, 0) - ISR_INSTALL(_idt, 74, _asm_isr74, 0) - ISR_INSTALL(_idt, 75, _asm_isr75, 0) - ISR_INSTALL(_idt, 76, _asm_isr76, 0) - ISR_INSTALL(_idt, 77, _asm_isr77, 0) - ISR_INSTALL(_idt, 78, _asm_isr78, 0) - ISR_INSTALL(_idt, 79, _asm_isr79, 0) - ISR_INSTALL(_idt, 80, _asm_isr80, 0) - ISR_INSTALL(_idt, 81, _asm_isr81, 0) - ISR_INSTALL(_idt, 82, _asm_isr82, 0) - ISR_INSTALL(_idt, 83, _asm_isr83, 0) - ISR_INSTALL(_idt, 84, _asm_isr84, 0) - ISR_INSTALL(_idt, 85, _asm_isr85, 0) - ISR_INSTALL(_idt, 86, _asm_isr86, 0) - ISR_INSTALL(_idt, 87, _asm_isr87, 0) - ISR_INSTALL(_idt, 88, _asm_isr88, 0) - ISR_INSTALL(_idt, 89, _asm_isr89, 0) - ISR_INSTALL(_idt, 90, _asm_isr90, 0) - ISR_INSTALL(_idt, 91, _asm_isr91, 0) - ISR_INSTALL(_idt, 92, _asm_isr92, 0) - ISR_INSTALL(_idt, 93, _asm_isr93, 0) - ISR_INSTALL(_idt, 94, _asm_isr94, 0) - ISR_INSTALL(_idt, 95, _asm_isr95, 0) - ISR_INSTALL(_idt, 96, _asm_isr96, 0) - ISR_INSTALL(_idt, 97, _asm_isr97, 0) - ISR_INSTALL(_idt, 98, _asm_isr98, 0) - ISR_INSTALL(_idt, 99, _asm_isr99, 0) - ISR_INSTALL(_idt, 100, _asm_isr100, 0) - ISR_INSTALL(_idt, 101, _asm_isr101, 0) - ISR_INSTALL(_idt, 102, _asm_isr102, 0) - ISR_INSTALL(_idt, 103, _asm_isr103, 0) - ISR_INSTALL(_idt, 104, _asm_isr104, 0) - ISR_INSTALL(_idt, 105, _asm_isr105, 0) - ISR_INSTALL(_idt, 106, _asm_isr106, 0) - ISR_INSTALL(_idt, 107, _asm_isr107, 0) - ISR_INSTALL(_idt, 108, _asm_isr108, 0) - ISR_INSTALL(_idt, 109, _asm_isr109, 0) - ISR_INSTALL(_idt, 110, _asm_isr110, 0) - ISR_INSTALL(_idt, 111, _asm_isr111, 0) - ISR_INSTALL(_idt, 112, _asm_isr112, 0) - ISR_INSTALL(_idt, 113, _asm_isr113, 0) - ISR_INSTALL(_idt, 114, _asm_isr114, 0) - ISR_INSTALL(_idt, 115, _asm_isr115, 0) - ISR_INSTALL(_idt, 116, _asm_isr116, 0) - ISR_INSTALL(_idt, 117, _asm_isr117, 0) - ISR_INSTALL(_idt, 118, _asm_isr118, 0) - ISR_INSTALL(_idt, 119, _asm_isr119, 0) - ISR_INSTALL(_idt, 120, _asm_isr120, 0) - ISR_INSTALL(_idt, 121, _asm_isr121, 0) - ISR_INSTALL(_idt, 122, _asm_isr122, 0) - ISR_INSTALL(_idt, 123, _asm_isr123, 0) - ISR_INSTALL(_idt, 124, _asm_isr124, 0) - ISR_INSTALL(_idt, 125, _asm_isr125, 0) - ISR_INSTALL(_idt, 126, _asm_isr126, 0) - ISR_INSTALL(_idt, 127, _asm_isr127, 0) - ISR_INSTALL(_idt, 128, _asm_isr128, 0) - ISR_INSTALL(_idt, 129, _asm_isr129, 0) - ISR_INSTALL(_idt, 130, _asm_isr130, 0) - ISR_INSTALL(_idt, 131, _asm_isr131, 0) - ISR_INSTALL(_idt, 132, _asm_isr132, 0) - ISR_INSTALL(_idt, 133, _asm_isr133, 0) - ISR_INSTALL(_idt, 134, _asm_isr134, 0) - ISR_INSTALL(_idt, 135, _asm_isr135, 0) - ISR_INSTALL(_idt, 136, _asm_isr136, 0) - ISR_INSTALL(_idt, 137, _asm_isr137, 0) - ISR_INSTALL(_idt, 138, _asm_isr138, 0) - ISR_INSTALL(_idt, 139, _asm_isr139, 0) - ISR_INSTALL(_idt, 140, _asm_isr140, 0) - ISR_INSTALL(_idt, 141, _asm_isr141, 0) - ISR_INSTALL(_idt, 142, _asm_isr142, 0) - ISR_INSTALL(_idt, 143, _asm_isr143, 0) - ISR_INSTALL(_idt, 144, _asm_isr144, 0) - ISR_INSTALL(_idt, 145, _asm_isr145, 0) - ISR_INSTALL(_idt, 146, _asm_isr146, 0) - ISR_INSTALL(_idt, 147, _asm_isr147, 0) - ISR_INSTALL(_idt, 148, _asm_isr148, 0) - ISR_INSTALL(_idt, 149, _asm_isr149, 0) - ISR_INSTALL(_idt, 150, _asm_isr150, 0) - ISR_INSTALL(_idt, 151, _asm_isr151, 0) - ISR_INSTALL(_idt, 152, _asm_isr152, 0) - ISR_INSTALL(_idt, 153, _asm_isr153, 0) - ISR_INSTALL(_idt, 154, _asm_isr154, 0) - ISR_INSTALL(_idt, 155, _asm_isr155, 0) - ISR_INSTALL(_idt, 156, _asm_isr156, 0) - ISR_INSTALL(_idt, 157, _asm_isr157, 0) - ISR_INSTALL(_idt, 158, _asm_isr158, 0) - ISR_INSTALL(_idt, 159, _asm_isr159, 0) - ISR_INSTALL(_idt, 160, _asm_isr160, 0) - ISR_INSTALL(_idt, 161, _asm_isr161, 0) - ISR_INSTALL(_idt, 162, _asm_isr162, 0) - ISR_INSTALL(_idt, 163, _asm_isr163, 0) - ISR_INSTALL(_idt, 164, _asm_isr164, 0) - ISR_INSTALL(_idt, 165, _asm_isr165, 0) - ISR_INSTALL(_idt, 166, _asm_isr166, 0) - ISR_INSTALL(_idt, 167, _asm_isr167, 0) - ISR_INSTALL(_idt, 168, _asm_isr168, 0) - ISR_INSTALL(_idt, 169, _asm_isr169, 0) - ISR_INSTALL(_idt, 170, _asm_isr170, 0) - ISR_INSTALL(_idt, 171, _asm_isr171, 0) - ISR_INSTALL(_idt, 172, _asm_isr172, 0) - ISR_INSTALL(_idt, 173, _asm_isr173, 0) - ISR_INSTALL(_idt, 174, _asm_isr174, 0) - ISR_INSTALL(_idt, 175, _asm_isr175, 0) - ISR_INSTALL(_idt, 176, _asm_isr176, 0) - ISR_INSTALL(_idt, 177, _asm_isr177, 0) - ISR_INSTALL(_idt, 178, _asm_isr178, 0) - ISR_INSTALL(_idt, 179, _asm_isr179, 0) - ISR_INSTALL(_idt, 180, _asm_isr180, 0) - ISR_INSTALL(_idt, 181, _asm_isr181, 0) - ISR_INSTALL(_idt, 182, _asm_isr182, 0) - ISR_INSTALL(_idt, 183, _asm_isr183, 0) - ISR_INSTALL(_idt, 184, _asm_isr184, 0) - ISR_INSTALL(_idt, 185, _asm_isr185, 0) - ISR_INSTALL(_idt, 186, _asm_isr186, 0) - ISR_INSTALL(_idt, 187, _asm_isr187, 0) - ISR_INSTALL(_idt, 188, _asm_isr188, 0) - ISR_INSTALL(_idt, 189, _asm_isr189, 0) - ISR_INSTALL(_idt, 190, _asm_isr190, 0) - ISR_INSTALL(_idt, 191, _asm_isr191, 0) - ISR_INSTALL(_idt, 192, _asm_isr192, 0) - ISR_INSTALL(_idt, 193, _asm_isr193, 0) - ISR_INSTALL(_idt, 194, _asm_isr194, 0) - ISR_INSTALL(_idt, 195, _asm_isr195, 0) - ISR_INSTALL(_idt, 196, _asm_isr196, 0) - ISR_INSTALL(_idt, 197, _asm_isr197, 0) - ISR_INSTALL(_idt, 198, _asm_isr198, 0) - ISR_INSTALL(_idt, 199, _asm_isr199, 0) - ISR_INSTALL(_idt, 200, _asm_isr200, 0) - ISR_INSTALL(_idt, 201, _asm_isr201, 0) - ISR_INSTALL(_idt, 202, _asm_isr202, 0) - ISR_INSTALL(_idt, 203, _asm_isr203, 0) - ISR_INSTALL(_idt, 204, _asm_isr204, 0) - ISR_INSTALL(_idt, 205, _asm_isr205, 0) - ISR_INSTALL(_idt, 206, _asm_isr206, 0) - ISR_INSTALL(_idt, 207, _asm_isr207, 0) - ISR_INSTALL(_idt, 208, _asm_isr208, 0) - ISR_INSTALL(_idt, 209, _asm_isr209, 0) - ISR_INSTALL(_idt, 210, _asm_isr210, 0) - ISR_INSTALL(_idt, 211, _asm_isr211, 0) - ISR_INSTALL(_idt, 212, _asm_isr212, 0) - ISR_INSTALL(_idt, 213, _asm_isr213, 0) - ISR_INSTALL(_idt, 214, _asm_isr214, 0) - ISR_INSTALL(_idt, 215, _asm_isr215, 0) - ISR_INSTALL(_idt, 216, _asm_isr216, 0) - ISR_INSTALL(_idt, 217, _asm_isr217, 0) - ISR_INSTALL(_idt, 218, _asm_isr218, 0) - ISR_INSTALL(_idt, 219, _asm_isr219, 0) - ISR_INSTALL(_idt, 220, _asm_isr220, 0) - ISR_INSTALL(_idt, 221, _asm_isr221, 0) - ISR_INSTALL(_idt, 222, _asm_isr222, 0) - ISR_INSTALL(_idt, 223, _asm_isr223, 0) - ISR_INSTALL(_idt, 224, _asm_isr224, 0) - ISR_INSTALL(_idt, 225, _asm_isr225, 0) - ISR_INSTALL(_idt, 226, _asm_isr226, 0) - ISR_INSTALL(_idt, 227, _asm_isr227, 0) - ISR_INSTALL(_idt, 228, _asm_isr228, 0) - ISR_INSTALL(_idt, 229, _asm_isr229, 0) - ISR_INSTALL(_idt, 230, _asm_isr230, 0) - ISR_INSTALL(_idt, 231, _asm_isr231, 0) - ISR_INSTALL(_idt, 232, _asm_isr232, 0) - ISR_INSTALL(_idt, 233, _asm_isr233, 0) - ISR_INSTALL(_idt, 234, _asm_isr234, 0) - ISR_INSTALL(_idt, 235, _asm_isr235, 0) - ISR_INSTALL(_idt, 236, _asm_isr236, 0) - ISR_INSTALL(_idt, 237, _asm_isr237, 0) - ISR_INSTALL(_idt, 238, _asm_isr238, 0) - ISR_INSTALL(_idt, 239, _asm_isr239, 0) - ISR_INSTALL(_idt, 240, _asm_isr240, 0) - ISR_INSTALL(_idt, 241, _asm_isr241, 0) - ISR_INSTALL(_idt, 242, _asm_isr242, 0) - ISR_INSTALL(_idt, 243, _asm_isr243, 0) - ISR_INSTALL(_idt, 244, _asm_isr244, 0) - ISR_INSTALL(_idt, 245, _asm_isr245, 0) - ISR_INSTALL(_idt, 246, _asm_isr246, 0) - ISR_INSTALL(_idt, 247, _asm_isr247, 0) - ISR_INSTALL(_idt, 248, _asm_isr248, 0) - ISR_INSTALL(_idt, 249, _asm_isr249, 0) - ISR_INSTALL(_idt, 250, _asm_isr250, 0) - ISR_INSTALL(_idt, 251, _asm_isr251, 0) - ISR_INSTALL(_idt, 252, _asm_isr252, 0) - ISR_INSTALL(_idt, 253, _asm_isr253, 0) - ISR_INSTALL(_idt, 254, _asm_isr254, 0) - ISR_INSTALL(_idt, 255, _asm_isr255, 0) -} \ No newline at end of file diff --git a/lunaix-os/arch/i386/includes/sys/abi.h b/lunaix-os/arch/i386/includes/sys/abi.h deleted file mode 100644 index d847251..0000000 --- a/lunaix-os/arch/i386/includes/sys/abi.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef __LUNAIX_I386ABI_H -#define __LUNAIX_I386ABI_H - -#include "sys/x86_isa.h" - -#define stack_alignment 0xfffffff0 - -#ifndef __ASM__ -#define align_stack(ptr) ((ptr) & stack_alignment) -#define store_retval(retval) current_thread->hstate->registers.eax = (retval) - -#define store_retval_to(th, retval) (th)->hstate->registers.eax = (retval) - -static inline void must_inline -j_usr(ptr_t sp, ptr_t pc) -{ - asm volatile("movw %0, %%ax\n" - "movw %%ax, %%es\n" - "movw %%ax, %%ds\n" - "movw %%ax, %%fs\n" - "movw %%ax, %%gs\n" - "pushl %0\n" - "pushl %1\n" - "pushl %2\n" - "pushl %3\n" - "retf" ::"i"(UDATA_SEG), - "r"(sp), - "i"(UCODE_SEG), - "r"(pc) - : "eax", "memory"); -} - - -static inline void must_inline noret -switch_context() { - asm volatile("jmp do_switch\n"); - unreachable; -} - -#define push_arg1(stack_ptr, arg) *((typeof((arg))*)(stack_ptr)--) = arg -#define push_arg2(stack_ptr, arg1, arg2) \ - { \ - *((typeof((arg1))*)(stack_ptr)--) = arg1; \ - *((typeof((arg2))*)(stack_ptr)--) = arg2; \ - } -#define push_arg3(stack_ptr, arg1, arg2, arg3) \ - { \ - *((typeof((arg1))*)(stack_ptr)--) = arg1; \ - *((typeof((arg2))*)(stack_ptr)--) = arg2; \ - *((typeof((arg3))*)(stack_ptr)--) = arg3; \ - } -#define push_arg4(stack_ptr, arg1, arg2, arg3, arg4) \ - { \ - *((typeof((arg1))*)(stack_ptr)--) = arg1; \ - *((typeof((arg2))*)(stack_ptr)--) = arg2; \ - *((typeof((arg3))*)(stack_ptr)--) = arg3; \ - *((typeof((arg4))*)(stack_ptr)--) = arg4; \ - } - - -static inline ptr_t must_inline -abi_get_callframe() -{ - ptr_t val; - asm("movl %%ebp, %0" : "=r"(val)::); - return val; -} - -static inline ptr_t -abi_get_retaddr() -{ - return *((ptr_t*)abi_get_callframe() + 1); -} - -static inline ptr_t -abi_get_retaddrat(ptr_t fp) -{ - return *((ptr_t*)fp + 1); -} -#endif -#endif /* __LUNAIX_ABI_H */ diff --git a/lunaix-os/arch/i386/includes/sys/boot/bstage.h b/lunaix-os/arch/i386/includes/sys/boot/bstage.h deleted file mode 100644 index 0744481..0000000 --- a/lunaix-os/arch/i386/includes/sys/boot/bstage.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef __LUNAIX_BSTAGE_H -#define __LUNAIX_BSTAGE_H - -#define boot_text __attribute__((section(".boot.text"))) -#define boot_data __attribute__((section(".boot.data"))) -#define boot_bss __attribute__((section(".boot.bss"))) - -#endif /* __LUNAIX_BSTAGE_H */ diff --git a/lunaix-os/arch/i386/includes/sys/i386_intr.h b/lunaix-os/arch/i386/includes/sys/i386_intr.h deleted file mode 100644 index bf78c35..0000000 --- a/lunaix-os/arch/i386/includes/sys/i386_intr.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __LUNAIX_I386_INTR_H -#define __LUNAIX_I386_INTR_H - -void -exception_install_handler(); - -void -intr_routine_init(); - -#endif /* __LUNAIX_I386_INTR_H */ diff --git a/lunaix-os/arch/i386/includes/sys/mm/mempart.h b/lunaix-os/arch/i386/includes/sys/mm/mempart.h deleted file mode 100644 index aac7f9c..0000000 --- a/lunaix-os/arch/i386/includes/sys/mm/mempart.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef __LUNAIX_MEMPART_H -#define __LUNAIX_MEMPART_H - -#define MEM_PAGE 0x1000UL -#define MEM_1M 0x100000UL -#define MEM_4M 0x400000UL -#define MEM_HUGE 0x400000UL -#define MEM_1G 0x40000000UL - -#define END_POINT(name) (name + name##_SIZE - 1) - -#define KSTACK_AREA 0x100000UL -#define KSTACK_AREA_SIZE 0x300000UL -#define KSTACK_AREA_END END_POINT(KSTACK_AREA) - -#define USR_EXEC 0x400000UL -#define USR_EXEC_SIZE 0x20000000UL -#define USR_EXEC_END END_POINT(USR_EXEC) - -#define USR_MMAP 0x20400000UL -#define USR_MMAP_SIZE 0x9fbc0000UL -#define USR_MMAP_END END_POINT(USR_MMAP) - -#define USR_STACK 0xbffc0000UL -#define USR_STACK_SIZE 0x40000UL -#define USR_STACK_END END_POINT(USR_STACK) - -#define KERNEL_IMG 0xc0000000UL -#define KERNEL_IMG_SIZE 0x4000000UL -#define KERNEL_IMG_END END_POINT(KERNEL_IMG) - -#define PG_MOUNT_1 0xc4000000UL -#define PG_MOUNT_1_SIZE 0x1000UL -#define PG_MOUNT_1_END END_POINT(PG_MOUNT_1) - -#define PG_MOUNT_2 0xc4001000UL -#define PG_MOUNT_2_SIZE 0x1000UL -#define PG_MOUNT_2_END END_POINT(PG_MOUNT_2) - -#define PG_MOUNT_3 0xc4002000UL -#define PG_MOUNT_3_SIZE 0x1000UL -#define PG_MOUNT_3_END END_POINT(PG_MOUNT_3) - -#define PG_MOUNT_4 0xc4003000UL -#define PG_MOUNT_4_SIZE 0x1000UL -#define PG_MOUNT_4_END END_POINT(PG_MOUNT_4) - -#define PG_MOUNT_VAR 0xc4004000UL -#define PG_MOUNT_VAR_SIZE 0x3fc000UL -#define PG_MOUNT_VAR_END END_POINT(PG_MOUNT_VAR) - -#define VMAP 0xc4400000UL -#define VMAP_SIZE 0x3b400000UL -#define VMAP_END END_POINT(VMAP) - -#define VMS_MOUNT_1 0xff800000UL -#define VMS_MOUNT_1_SIZE 0x400000UL -#define VMS_MOUNT_1_END END_POINT(VMS_MOUNT_1) - -#endif \ No newline at end of file diff --git a/lunaix-os/arch/i386/includes/sys/x86_isa.h b/lunaix-os/arch/i386/includes/sys/x86_isa.h deleted file mode 100644 index 7db8807..0000000 --- a/lunaix-os/arch/i386/includes/sys/x86_isa.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef __LUNAIX_I386_ASM_H -#define __LUNAIX_I386_ASM_H - -#define KCODE_SEG 0x08 -#define KDATA_SEG 0x10 -#define UCODE_SEG 0x1B -#define UDATA_SEG 0x23 -#define TSS_SEG 0x28 - -#define tss_esp0_off 4 - -#ifndef __ASM__ -#include - -#define IRQ_TRIG_EDGE 0b0 -#define IRQ_TRIG_LEVEL 0b1 - -#define IRQ_TYPE_FIXED (0b01 << 1) -#define IRQ_TYPE_NMI (0b11 << 1) -#define IRQ_TYPE (0b11 << 1) - -#define IRQ_VE_HI (0b1 << 3) -#define IRQ_VE_LO (0b0 << 3) - -#define IRQ_DEFAULT (IRQ_TRIG_EDGE | IRQ_TYPE_FIXED | IRQ_VE_HI) - - -struct x86_tss -{ - u32_t link; - u32_t esp0; - u16_t ss0; - u8_t __padding[94]; -} __attribute__((packed)); - -void tss_update_esp(u32_t esp0); - -struct x86_intc -{ - char* name; - void* data; - - void (*irq_attach)(struct x86_intc*, - int irq, - int iv, - cpu_t dest, - u32_t flags); - void (*notify_eoi)(struct x86_intc*, cpu_t id, int iv); -}; - - -#endif - -#endif /* __LUNAIX_I386_ASM_H */ diff --git a/lunaix-os/arch/i386/klib/fast_str.c b/lunaix-os/arch/i386/klib/fast_str.c deleted file mode 100644 index 46dcf28..0000000 --- a/lunaix-os/arch/i386/klib/fast_str.c +++ /dev/null @@ -1,26 +0,0 @@ -#include - -void* -memcpy(void* dest, const void* src, unsigned long num) -{ - if (!num) - return dest; - - asm volatile("movl %1, %%edi\n" - "rep movsb\n" ::"S"(src), - "r"(dest), - "c"(num) - : "edi", "memory"); - return dest; -} - -void* -memset(void* ptr, int value, unsigned long num) -{ - asm volatile("movl %1, %%edi\n" - "rep stosb\n" ::"c"(num), - "r"(ptr), - "a"(value) - : "edi", "memory"); - return ptr; -} \ No newline at end of file diff --git a/lunaix-os/arch/i386/trace.c b/lunaix-os/arch/i386/trace.c deleted file mode 100644 index 71c3229..0000000 --- a/lunaix-os/arch/i386/trace.c +++ /dev/null @@ -1,49 +0,0 @@ -#include - -void -trace_print_transistion_short(struct hart_state* hstate) -{ - trace_log(" trigger: iv=%d, ecause=%p", - hart_vector_stamp(hstate), - hart_ecause(hstate)); -} - -void -trace_print_transition_full(struct hart_state* hstate) -{ - trace_log("hart state transition"); - trace_log(" vector=%d, ecause=0x%x", - hart_vector_stamp(hstate), - hart_ecause(hstate)); - trace_log(" eflags=0x%x", hstate->execp->eflags); - trace_log(" sp=%p, [seg_sel=0x%04x]", - hstate->execp->esp, - hstate->execp->esp); - trace_log(" ip=%p, seg_sel=0x%04x", - hstate->execp->eip, - hstate->execp->cs); -} - -void -trace_dump_state(struct hart_state* hstate) -{ - struct regcontext* rh = &hstate->registers; - struct exec_param* ep = hstate->execp; - trace_log("hart state dump (depth=%d)", hstate->depth); - trace_log(" eax=0x%08x, ebx=0x%08x, ecx=0x%08x", - rh->eax, rh->ebx, rh->ecx); - trace_log(" edx=0x%08x, ebp=0x%08x", - rh->edx, rh->ebp); - trace_log(" ds=0x%04x, edi=0x%08x", - rh->ds, rh->edi); - trace_log(" es=0x%04x, esi=0x%08x", - rh->es, rh->esi); - trace_log(" fs=0x%04x, gs=0x%x", - rh->fs, rh->gs); - trace_log(" cs=0x%04x, ip=0x%08x", - ep->cs, ep->eip); - trace_log(" [ss=0x%04x],sp=0x%08x", - ep->ss, ep->eip); - trace_log(" eflags=0x%08x", - ep->eflags); -} \ No newline at end of file diff --git a/lunaix-os/arch/x86/LBuild b/lunaix-os/arch/x86/LBuild new file mode 100644 index 0000000..7d50ca3 --- /dev/null +++ b/lunaix-os/arch/x86/LBuild @@ -0,0 +1,82 @@ +use("hal") + +sources([ + "exceptions/interrupts.c", + "exceptions/isrdef.c", + "exceptions/intr_routines.c", + "exceptions/isrm.c", + "exceptions/intrhnds.S", +]) + +sources([ + "boot/mb_parser.c", + "boot/kpt_setup.c", + "boot/boot_helper.c" +]) + +sources([ + "mm/fault.c", + "mm/tlb.c", + "mm/pmm.c", + "mm/gdt.c", + "mm/vmutils.c" +]) + +sources([ + "klib/fast_crc.c", + "klib/fast_str.c", + "exec/exec.c", + "hart.c", + "arch.c", + "gdbstub.c", + "trace.c", + "hart.c", + "failsafe.S" +]) + +sources({ + config("arch"): { + "x86_64": [ + "hart64.c", + "syscall64.S", + "exceptions/interrupt64.S", + "boot/x86_64/boot64.S", + "boot/x86_64/init64.c", + "boot/x86_64/prologue64.S", + "boot/x86_64/kremap64.c", + "exec/elf64.c" + ], + "i386": [ + "hart32.c", + "syscall32.S", + "exceptions/interrupt32.S", + "boot/i386/boot32.S", + "boot/i386/init32.c", + "boot/i386/prologue32.S", + "boot/i386/kremap32.c", + "exec/elf32.c" + ] + } +}) + +headers([ + "includes" +]) + + +if config("arch") == "x86_64": + compile_opts([ + "-m64", + "-fno-unwind-tables", + "-fno-asynchronous-unwind-tables", + "-mcmodel=large" + ]) + linking_opts([ + "-m64", + ]) +else: + compile_opts("-m32") + linking_opts("-m32") + +if not config("x86_enable_sse_feature"): + compile_opts("-mno-sse") \ No newline at end of file diff --git a/lunaix-os/arch/x86/LConfig b/lunaix-os/arch/x86/LConfig new file mode 100644 index 0000000..7580a36 --- /dev/null +++ b/lunaix-os/arch/x86/LConfig @@ -0,0 +1,34 @@ + +@Group +def x86_configurations(): + + add_to_collection(architecture_support) + + @Term + def x86_enable_sse_feature(): + """ + Config whether to allow using SSE feature for certain + optimization + """ + + type(bool) + default(False) + + + @Term + def x86_bl(): + """ + Select the bootloader interface + + Supported interface + mb: multiboot compliant + mb2: multiboot2 compliant + none: do not use any interface + """ + + type(["mb"]) + # type(["mb", "mb2", "none"]) + default("mb") + + + return v(arch) in ["i386", "x86_64"] \ No newline at end of file diff --git a/lunaix-os/arch/i386/arch.c b/lunaix-os/arch/x86/arch.c similarity index 69% rename from lunaix-os/arch/i386/arch.c rename to lunaix-os/arch/x86/arch.c index 5ad6cd7..988f976 100644 --- a/lunaix-os/arch/i386/arch.c +++ b/lunaix-os/arch/x86/arch.c @@ -2,8 +2,10 @@ #include #include +#include -#include "sys/i386_intr.h" +#include "sys/int_handler.h" +#include "sys/x86_isa.h" #include "sys/hart.h" #include "hal/apic_timer.h" @@ -40,4 +42,15 @@ select_platform_timer() // TODO select alternatives... panick("no timer to use."); +} + +void +update_tss() +{ + extern struct x86_tss _tss; +#ifdef CONFIG_ARCH_X86_64 + _tss.rsps[0] = (ptr_t)current_thread->hstate; +#else + _tss.esp0 = (u32_t)current_thread->hstate; +#endif } \ No newline at end of file diff --git a/lunaix-os/arch/x86/boot/boot_helper.c b/lunaix-os/arch/x86/boot/boot_helper.c new file mode 100644 index 0000000..c5f0c46 --- /dev/null +++ b/lunaix-os/arch/x86/boot/boot_helper.c @@ -0,0 +1,46 @@ +#include +#include + +#include "sys/mm/mm_defs.h" + +#ifdef CONFIG_ARCH_X86_64 + +void +boot_begin_arch_reserve(struct boot_handoff* bhctx) +{ + return; +} + + +void +boot_clean_arch_reserve(struct boot_handoff* bhctx) +{ + return; +} + +#else + +#include + +void +boot_begin_arch_reserve(struct boot_handoff* bhctx) +{ + // Identity-map the first 3GiB address spaces + pte_t* ptep = mkl0tep(mkptep_va(VMS_SELF, 0)); + pte_t pte = mkpte_prot(KERNEL_DATA); + size_t count = page_count(KERNEL_RESIDENT, L0T_SIZE); + + vmm_set_ptes_contig(ptep, pte_mkhuge(pte), L0T_SIZE, count); +} + + +void +boot_clean_arch_reserve(struct boot_handoff* bhctx) +{ + pte_t* ptep = mkl0tep(mkptep_va(VMS_SELF, 0)); + size_t count = page_count(KERNEL_RESIDENT, L0T_SIZE); + vmm_unset_ptes(ptep, count); +} + + +#endif \ No newline at end of file diff --git a/lunaix-os/arch/i386/boot/boot.S b/lunaix-os/arch/x86/boot/i386/boot32.S similarity index 74% rename from lunaix-os/arch/i386/boot/boot.S rename to lunaix-os/arch/x86/boot/i386/boot32.S index 2f48bbc..b969104 100644 --- a/lunaix-os/arch/i386/boot/boot.S +++ b/lunaix-os/arch/x86/boot/i386/boot32.S @@ -1,13 +1,8 @@ -#define __ASM__ 1 -#include +#define __ASM__ -#define MB_FLAGS (MULTIBOOT_MEMORY_INFO | MULTIBOOT_PAGE_ALIGN) -#define KPG_SIZE 10*4096 - -.section .multiboot - .long MULTIBOOT_MAGIC - .long MB_FLAGS - .long CHECKSUM(MB_FLAGS) +#if defined(CONFIG_X86_BL_MB) || defined(CONFIG_X86_BL_MB2) +#include "sys/boot/multiboot.S.inc" +#endif .section .boot.bss /* 根据System V ABI,栈地址必须16字节对齐 */ diff --git a/lunaix-os/arch/i386/boot/init32.c b/lunaix-os/arch/x86/boot/i386/init32.c similarity index 78% rename from lunaix-os/arch/i386/boot/init32.c rename to lunaix-os/arch/x86/boot/i386/init32.c index 1b908f8..d9cc249 100644 --- a/lunaix-os/arch/i386/boot/init32.c +++ b/lunaix-os/arch/x86/boot/i386/init32.c @@ -1,6 +1,6 @@ -#include "archinit.h" -#include -#include +#include "sys/boot/archinit.h" +#include "sys/crx.h" +#include "sys/cpu.h" void boot_text x86_init(struct multiboot_info* mb) diff --git a/lunaix-os/arch/i386/boot/kpt_setup.c b/lunaix-os/arch/x86/boot/i386/kremap32.c similarity index 67% rename from lunaix-os/arch/i386/boot/kpt_setup.c rename to lunaix-os/arch/x86/boot/i386/kremap32.c index c353d77..c73c2a3 100644 --- a/lunaix-os/arch/i386/boot/kpt_setup.c +++ b/lunaix-os/arch/x86/boot/i386/kremap32.c @@ -6,15 +6,17 @@ #include #include -// Provided by linker (see linker.ld) -extern u8_t __kexec_start[]; -extern u8_t __kexec_end[]; -extern u8_t __kexec_text_start[]; -extern u8_t __kexec_text_end[]; -extern u8_t __kboot_start[]; -extern u8_t __kboot_end[]; +bridge_farsym(__kexec_start); +bridge_farsym(__kexec_end); +bridge_farsym(__kexec_text_start); +bridge_farsym(__kexec_text_end); // define the initial page table layout +struct kernel_map; + +static struct kernel_map kernel_pt __section(".kpg"); +export_symbol(debug, boot, kernel_pt); + struct kernel_map { pte_t l0t[_PAGE_LEVEL_SIZE]; pte_t pg_mnt[_PAGE_LEVEL_SIZE]; @@ -24,17 +26,14 @@ struct kernel_map { } kernel_lfts[16]; } align(4); -static struct kernel_map kernel_pt __section(".kpg"); -export_symbol(debug, boot, kernel_pt); - - -void boot_text -_init_page() +static void boot_text +do_remap() { struct kernel_map* kpt_pa = (struct kernel_map*)to_kphysical(&kernel_pt); - - pte_t* kl0tep = (pte_t*) &kpt_pa->l0t[pfn_at(KERNEL_RESIDENT, L0T_SIZE)]; - pte_t* kl1tep = (pte_t*) kpt_pa->kernel_lfts; + + size_t mia_casa_i = pfn_at(KERNEL_RESIDENT, L0T_SIZE); + pte_t* klptep = (pte_t*) &kpt_pa->l0t[mia_casa_i]; + pte_t* ktep = (pte_t*) kpt_pa->kernel_lfts; pte_t* boot_l0tep = (pte_t*) kpt_pa; set_pte(boot_l0tep, pte_mkhuge(mkpte_prot(KERNEL_DATA))); @@ -42,18 +41,18 @@ _init_page() // --- 将内核重映射至高半区 --- // Hook the kernel reserved LFTs onto L0T - pte_t pte = mkpte((ptr_t)kl1tep, KERNEL_DATA); + pte_t pte = mkpte((ptr_t)ktep, KERNEL_DATA); for (u32_t i = 0; i < KEXEC_RSVD; i++) { pte = pte_setpaddr(pte, (ptr_t)&kpt_pa->kernel_lfts[i]); - set_pte(kl0tep, pte); + set_pte(klptep, pte); - kl0tep++; + klptep++; } // Ensure the size of kernel is within the reservation pfn_t kimg_pagecount = - pfn((ptr_t)__kexec_end - (ptr_t)__kexec_start); + pfn(__far(__kexec_end) - __far(__kexec_start)); if (kimg_pagecount > KEXEC_RSVD * _PAGE_LEVEL_SIZE) { // ERROR: require more pages // here should do something else other than head into blocking @@ -62,27 +61,27 @@ _init_page() // Now, map the kernel - pfn_t kimg_end = pfn(to_kphysical(__kexec_end)); - pfn_t i = pfn(to_kphysical(__kexec_text_start)); - kl1tep += i; + pfn_t kimg_end = pfn(to_kphysical(__far(__kexec_end))); + pfn_t i = pfn(to_kphysical(__far(__kexec_text_start))); + ktep += i; // kernel .text pte = pte_setprot(pte, KERNEL_EXEC); - pfn_t ktext_end = pfn(to_kphysical(__kexec_text_end)); + pfn_t ktext_end = pfn(to_kphysical(__far(__kexec_text_end))); for (; i < ktext_end; i++) { pte = pte_setpaddr(pte, page_addr(i)); - set_pte(kl1tep, pte); + set_pte(ktep, pte); - kl1tep++; + ktep++; } // all remaining kernel sections pte = pte_setprot(pte, KERNEL_DATA); for (; i < kimg_end; i++) { pte = pte_setpaddr(pte, page_addr(i)); - set_pte(kl1tep, pte); + set_pte(ktep, pte); - kl1tep++; + ktep++; } // XXX: Mapping the kernel .rodata section? @@ -92,19 +91,21 @@ _init_page() set_pte(kmntep, mkpte((ptr_t)kpt_pa->pg_mnt, KERNEL_DATA)); // Build up self-reference + int level = (VMS_SELF / L0T_SIZE) & _PAGE_LEVEL_MASK; + pte = mkpte_root((ptr_t)kpt_pa, KERNEL_DATA); - set_pte(boot_l0tep + _PAGE_LEVEL_MASK, pte); + set_pte(&boot_l0tep[level], pte); } ptr_t boot_text -kpg_init() +remap_kernel() { ptr_t kmap_pa = to_kphysical(&kernel_pt); for (size_t i = 0; i < sizeof(kernel_pt); i++) { ((u8_t*)kmap_pa)[i] = 0; } - _init_page(); + do_remap(); return kmap_pa; } \ No newline at end of file diff --git a/lunaix-os/arch/i386/boot/prologue.S b/lunaix-os/arch/x86/boot/i386/prologue32.S similarity index 82% rename from lunaix-os/arch/i386/boot/prologue.S rename to lunaix-os/arch/x86/boot/i386/prologue32.S index dc17242..b8b4866 100644 --- a/lunaix-os/arch/i386/boot/prologue.S +++ b/lunaix-os/arch/x86/boot/i386/prologue32.S @@ -11,12 +11,7 @@ __kinit_stack_end: .skip 2048, 0 __kinit_stack_top: - # TODO - # This stack was too small that corrupt the ambient kernel structures. - # (took me days to figure this out!) - # We should spent more time to implement a good strategy to detect such - # run-over (we can check these invariants when we trapped in some non-recoverable - # state and provide good feedback to user) + .section .text .global hhk_entry_ @@ -56,7 +51,7 @@ movw %cx, %ss /* 更新 CS:EIP */ - pushw $KCODE_SEG + pushl $KCODE_SEG pushl $_after_gdt retf diff --git a/lunaix-os/arch/x86/boot/kpt_setup.c b/lunaix-os/arch/x86/boot/kpt_setup.c new file mode 100644 index 0000000..ebcbc0e --- /dev/null +++ b/lunaix-os/arch/x86/boot/kpt_setup.c @@ -0,0 +1,14 @@ +#define __BOOT_CODE__ + +#include +#include + +#include +#include + + +ptr_t boot_text +kpg_init() +{ + return remap_kernel(); +} \ No newline at end of file diff --git a/lunaix-os/arch/i386/boot/mb_parser.c b/lunaix-os/arch/x86/boot/mb_parser.c similarity index 89% rename from lunaix-os/arch/i386/boot/mb_parser.c rename to lunaix-os/arch/x86/boot/mb_parser.c index aa9a5f0..b01ed19 100644 --- a/lunaix-os/arch/i386/boot/mb_parser.c +++ b/lunaix-os/arch/x86/boot/mb_parser.c @@ -6,6 +6,8 @@ #include #define BHCTX_ALLOC 4096 +#define MEM_1M 0x100000UL + u8_t bhctx_buffer[BHCTX_ALLOC] boot_bss; @@ -72,7 +74,7 @@ mb_parse_mmap(struct boot_handoff* bhctx, void* buffer) { struct multiboot_mmap_entry* mb_mmap = - (struct multiboot_mmap_entry*)mb->mmap_addr; + (struct multiboot_mmap_entry*)__ptr(mb->mmap_addr); size_t mmap_len = mb->mmap_length / sizeof(struct multiboot_mmap_entry); struct boot_mmapent* bmmap = (struct boot_mmapent*)buffer; @@ -110,16 +112,19 @@ mb_parse_mods(struct boot_handoff* bhctx, } struct boot_modent* modents = (struct boot_modent*)buffer; - struct multiboot_mod_list* mods = (struct multiboot_mod_list*)mb->mods_addr; + struct multiboot_mod_list* mods = + (struct multiboot_mod_list*)__ptr(mb->mods_addr); - ptr_t mod_str_ptr = (ptr_t)&modents[mb->mods_count]; + ptr_t mod_str_ptr = __ptr(&modents[mb->mods_count]); for (size_t i = 0; i < mb->mods_count; i++) { struct multiboot_mod_list* mod = &mods[i]; modents[i] = (struct boot_modent){ .start = mod->mod_start, .end = mod->mod_end, .str = (char*)mod_str_ptr }; - mod_str_ptr += mb_strcpy((char*)mod_str_ptr, (char*)mod->cmdline); + + mod_str_ptr += mb_strcpy((char*)mod_str_ptr, + (char*)__ptr(mod->cmdline)); } bhctx->mods.mods_num = mb->mods_count; @@ -159,7 +164,7 @@ mb_parse(struct multiboot_info* mb) /* Parse cmdline */ if ((mb->flags & MULTIBOOT_INFO_CMDLINE)) { bhctx_ex += - mb_parse_cmdline(bhctx, (void*)bhctx_ex, (char*)mb->cmdline); + mb_parse_cmdline(bhctx, (void*)bhctx_ex, (char*)__ptr(mb->cmdline)); bhctx_ex = align_addr(bhctx_ex); } diff --git a/lunaix-os/arch/x86/boot/x86_64/boot64.S b/lunaix-os/arch/x86/boot/x86_64/boot64.S new file mode 100644 index 0000000..b7c82e3 --- /dev/null +++ b/lunaix-os/arch/x86/boot/x86_64/boot64.S @@ -0,0 +1,133 @@ +#define __ASM__ + +#if defined(CONFIG_X86_BL_MB) || defined(CONFIG_X86_BL_MB2) +#include "sys/boot/multiboot.S.inc" +#endif + +#include "sys/mm/mempart64.h" + +.section .boot.bss + .align 8 + __tmp_gdt: + .long 0x0 + .long 0x0 + .long 0x0000ffff + .long 0x00ef9a00 + .long 0x0000ffff + .long 0x00cf9200 + +.section .boot.bss + + .align 16 + .skip 512, 0 + __boot_stack_top: + +.section .boot.bss + + .align 4096 + _tmp_l0: + .skip 4096 + _tmp_l1: + .skip 4096 + +.section .boot.text + .global start_ + .type start_, @function + + start_: + .code32 + cld + cli + + movl $__boot_stack_top, %esp + pushl $0 + pushl %ebx + + # first, setup a simple initial page table + # to enable transition to IA32e + + # L0 linkage to L1, RWX + movl $0x3, %eax + movl $_tmp_l1, %ebx + orl %eax, %ebx + movl %ebx, _tmp_l0 + xorl %ebx, %ebx + movl %ebx, _tmp_l0 + 4 + + # Entry 0 + orl $(1 << 7), %eax + movl %eax, _tmp_l1 + movl %ebx, _tmp_l1 + 4 + + # Entry 1 + addl $0x40000000, %eax + movl %eax, _tmp_l1 + 8 + movl %ebx, _tmp_l1 + 12 + + # Entry 2 + addl $0x40000000, %eax + movl %eax, _tmp_l1 + 16 + movl %ebx, _tmp_l1 + 20 + + # Entry 3 + addl $0x40000000, %eax + movl %eax, _tmp_l1 + 24 + movl %ebx, _tmp_l1 + 28 + + movl $_tmp_l0, %eax + movl %eax, %cr3 + + # now, commencing transition + + movl %cr4, %eax + orl $(1 << 5), %eax # PAE + movl %eax, %cr4 + + movl $0xc0000080, %ecx + rdmsr + orl $(1 << 8), %eax # IA32_EFER.LME + orl $(1 << 11), %eax # IA32_EFER.NXE + wrmsr + + movl %cr0, %eax + orl $(1 << 31), %eax # PG + movl %eax, %cr0 + + jmp _ia32e_compat + + # clear the pipeline, + # although cpu might already cleared for us upon mode switching + .nop + .nop + .nop + .nop + + # x86_64 compatibility mode + # load a temporary gdt for getting into long mode + _ia32e_compat: + .code32 + subl $16, %esp + movl $__tmp_gdt, 2(%esp) + movw $23, (%esp) + lgdtl (%esp) + + addl $16, %esp + + # do a far jump to switch cs + pushl $0x08 + pushl $_ia32e + retf + + _ia32e: + .code64 + movw $0x10, %cx + movw %cx, %ds + movw %cx, %ss + + popq %rbx + movq %rbx, %rdi + call x86_init + + movabsq $hhk_entry_, %rax + pushq %rax + retq \ No newline at end of file diff --git a/lunaix-os/arch/x86/boot/x86_64/init64.c b/lunaix-os/arch/x86/boot/x86_64/init64.c new file mode 100644 index 0000000..bab0173 --- /dev/null +++ b/lunaix-os/arch/x86/boot/x86_64/init64.c @@ -0,0 +1,17 @@ +#include "sys/boot/archinit.h" +#include "sys/crx.h" +#include "sys/cpu.h" + +void boot_text +x86_init(struct multiboot_info* mb) +{ + mb_parse(mb); + + cr4_setfeature(CR4_PCIDE); + + ptr_t pagetable = kpg_init(); + cpu_chvmspace(pagetable); + + cr0_unsetfeature(CR0_WP | CR0_EM); + cr0_setfeature(CR0_MP); +} \ No newline at end of file diff --git a/lunaix-os/arch/x86/boot/x86_64/kremap64.c b/lunaix-os/arch/x86/boot/x86_64/kremap64.c new file mode 100644 index 0000000..ee9d298 --- /dev/null +++ b/lunaix-os/arch/x86/boot/x86_64/kremap64.c @@ -0,0 +1,188 @@ +#define __BOOT_CODE__ + +#include +#include + +#include +#include + +#define RSVD_PAGES 32 + +bridge_farsym(__kexec_start); +bridge_farsym(__kexec_end); +bridge_farsym(__kexec_text_start); +bridge_farsym(__kexec_text_end); + +// define the initial page table layout +struct kernel_map; + +static struct kernel_map kpt __section(".kpg"); +export_symbol(debug, boot, kpt); + +struct kernel_map { + pte_t l0t[_PAGE_LEVEL_SIZE]; // root table + pte_t l1t_rsvd[_PAGE_LEVEL_SIZE]; // 0~4G reservation + + struct { + pte_t _lft[_PAGE_LEVEL_SIZE]; + } krsvd[RSVD_PAGES]; +} align(8); + +struct allocator +{ + struct kernel_map* kpt_pa; + int pt_usage; +}; + +static inline ptr_t +alloc_rsvd_page(struct allocator* _alloc) +{ + if (_alloc->pt_usage >= KEXEC_RSVD) { + asm ("ud2"); + } + + return __ptr(&_alloc->kpt_pa->krsvd[_alloc->pt_usage++]); +} + +static pte_t* boot_text +prealloc_pt(struct allocator* _allc, ptr_t va, + pte_attr_t prot, size_t to_gran) +{ + int lvl_i; + pte_t *ptep, pte; + size_t gran = L0T_SIZE; + + ptep = (pte_t*)&_allc->kpt_pa->l0t[0]; + + for (int i = 0; i < _PTW_LEVEL && gran > to_gran; i++) + { + lvl_i = va_level_index(va, gran); + ptep = &ptep[lvl_i]; + pte = pte_at(ptep); + + gran = gran >> _PAGE_LEVEL_SHIFT; + + if (pte_isnull(pte)) { + pte = mkpte(alloc_rsvd_page(_allc), KERNEL_DATA); + if (to_gran == gran) { + pte = pte_setprot(pte, prot); + } + + set_pte(ptep, pte); + } + ptep = (pte_t*) pte_paddr(pte); + } + + return ptep; +} + +static void boot_text +do_remap() +{ + struct kernel_map* kpt_pa = (struct kernel_map*)to_kphysical(&kpt); + + pte_t* boot_l0tep = (pte_t*) kpt_pa; + pte_t *klptep, pte; + + // identity map the first 4G for legacy compatibility + pte_t* l1_rsvd = (pte_t*) kpt_pa->l1t_rsvd; + pte_t id_map = pte_mkhuge(mkpte_prot(KERNEL_DATA)); + + set_pte(boot_l0tep, mkpte((ptr_t)l1_rsvd, KERNEL_DATA)); + + for (int i = 0; i < 4; i++, l1_rsvd++) + { + id_map = pte_setpaddr(id_map, (ptr_t)i << 30); + set_pte(l1_rsvd, id_map); + } + + // Remap the kernel to -2GiB + + int table_usage = 0; + unsigned int lvl_i = 0; + struct allocator alloc = { + .kpt_pa = kpt_pa, + .pt_usage = 0 + }; + + prealloc_pt(&alloc, VMAP, KERNEL_DATA, L1T_SIZE); + + prealloc_pt(&alloc, PG_MOUNT_1, KERNEL_DATA, LFT_SIZE); + + + ptr_t kstart = page_aligned(__far(__kexec_text_start)); + +#if LnT_ENABLED(3) + size_t gran = L3T_SIZE; +#else + size_t gran = L2T_SIZE; +#endif + + prealloc_pt(&alloc, PMAP, KERNEL_DATA, gran); + klptep = prealloc_pt(&alloc, kstart, KERNEL_DATA, gran); + klptep += va_level_index(kstart, gran); + + pte = mkpte(0, KERNEL_DATA); + for (int i = alloc.pt_usage; i < KEXEC_RSVD; i++) + { + pte = pte_setpaddr(pte, (ptr_t)&kpt_pa->krsvd[i]); + set_pte(klptep++, pte); + } + + // this is the first LFT we hooked on. + // all these LFT are contig in physical address + klptep = (pte_t*) &kpt_pa->krsvd[alloc.pt_usage]; + + // Ensure the size of kernel is within the reservation + int remain = KEXEC_RSVD - table_usage; + pfn_t kimg_pagecount = + pfn(__far(__kexec_end) - __far(__kexec_start)); + if (kimg_pagecount > remain * _PAGE_LEVEL_SIZE) { + // ERROR: require more pages + // here should do something else other than head into blocking + asm("ud2"); + } + + // kernel .text + pfn_t ktext_end = pfn(to_kphysical(__far(__kexec_text_end))); + pfn_t i = pfn(to_kphysical(kstart)); + + klptep += i; + pte = pte_setprot(pte, KERNEL_EXEC); + for (; i < ktext_end; i++) { + pte = pte_setpaddr(pte, page_addr(i)); + set_pte(klptep, pte); + + klptep++; + } + + pfn_t kimg_end = pfn(to_kphysical(__far(__kexec_end))); + + // all remaining kernel sections + pte = pte_setprot(pte, KERNEL_DATA); + for (; i < kimg_end; i++) { + pte = pte_setpaddr(pte, page_addr(i)); + set_pte(klptep, pte); + + klptep++; + } + + // Build up self-reference + lvl_i = va_level_index(VMS_SELF, L0T_SIZE); + pte = mkpte_root(__ptr(kpt_pa), KERNEL_DATA); + set_pte(boot_l0tep + lvl_i, pte); +} + + +ptr_t boot_text +remap_kernel() +{ + ptr_t kmap_pa = to_kphysical(&kpt); + for (size_t i = 0; i < sizeof(kpt); i++) { + ((u8_t*)kmap_pa)[i] = 0; + } + + do_remap(); + + return kmap_pa; +} \ No newline at end of file diff --git a/lunaix-os/arch/x86/boot/x86_64/prologue64.S b/lunaix-os/arch/x86/boot/x86_64/prologue64.S new file mode 100644 index 0000000..06b2b94 --- /dev/null +++ b/lunaix-os/arch/x86/boot/x86_64/prologue64.S @@ -0,0 +1,73 @@ +/* 高半核入口点 - 0xC0000000 */ + +#define __ASM__ +#include +#include + +.section .bss.kstack + .global __kinit_stack_end + + .align 16 + __kinit_stack_end: + .skip 2048, 0 + __kinit_stack_top: + + +.section .text + .global hhk_entry_ + hhk_entry_: + + movq $__kinit_stack_top, %rsp + andq $stack_alignment, %rsp + + movq $__kinit_stack_end, %rax + movl $STACK_SANITY, (%rax) + movl $STACK_SANITY, 4(%rax) + movl $STACK_SANITY, 8(%rax) + movl $STACK_SANITY, 12(%rax) + + andq $stack_alignment, %rsp + subq $16, %rsp + + # replace the temporary gdt + call _init_gdt + + movq $_gdt, 2(%rsp) + movw _gdt_limit, %ax + movw %ax, (%rsp) + lgdt (%rsp) + + # do a far jump to switch cs + pushq $KCODE_SEG + pushq $_after_gdt + retfq + + _after_gdt: + + # initialize segment registers + # we will not touch them again in x86_64 + movw $KDATA_SEG, %cx + movw %cx, %es + movw %cx, %ds + movw %cx, %ss + + # perform arch-specific initialization before diving into kernel + call arch_preinit + + # 加载 IDT + movq $_idt, 2(%rsp) + movw _idt_limit, %ax + movw %ax, (%rsp) + lidt (%rsp) + + /* 加载TSS段选择器 */ + movw $TSS_SEG, %ax + ltr %ax + + xorq %rbp, %rbp + movq $bhctx_buffer, %rdi # mb_parser.c + call kernel_bootstrap + + 1: + hlt + jmp 1b \ No newline at end of file diff --git a/lunaix-os/arch/i386/exceptions/interrupt.S b/lunaix-os/arch/x86/exceptions/interrupt32.S similarity index 97% rename from lunaix-os/arch/i386/exceptions/interrupt.S rename to lunaix-os/arch/x86/exceptions/interrupt32.S index 8d82d10..58c033a 100644 --- a/lunaix-os/arch/i386/exceptions/interrupt.S +++ b/lunaix-os/arch/x86/exceptions/interrupt32.S @@ -1,10 +1,11 @@ #define __ASM__ #include #include -#include +#include #include +#define tss_esp0_off 4 #define __ASM_INTR_DIAGNOSIS #ifdef __ASM_INTR_DIAGNOSIS @@ -38,7 +39,7 @@ interrupt_wrapper: cld - subl $4, %esp + subl $4, %esp # prealloc slot for parent linkage pushl %esp subl $16, %esp @@ -195,9 +196,10 @@ 由于这中间没有进行地址空间的交换,所以第二次跳转使用的是同一个内核栈,而之前默认tss.esp0的值是永远指向最顶部 这样一来就有可能会覆盖更早的上下文信息(比如嵌套的信号捕获函数) */ - movl thread_hstate(%ebx), %ecx # __current->hstate - movl %ecx, (tss_esp0_off + _tss) + pushl %eax + call update_tss + popl %eax jmp handle_signal 1: diff --git a/lunaix-os/arch/x86/exceptions/interrupt64.S b/lunaix-os/arch/x86/exceptions/interrupt64.S new file mode 100644 index 0000000..2b15410 --- /dev/null +++ b/lunaix-os/arch/x86/exceptions/interrupt64.S @@ -0,0 +1,204 @@ +#define __ASM__ +#include +#include +#include + +#include + +.section .bss + .align 16 + tmp_store: + .skip 8 + lo_tmp_stack: + .skip 256 + tmp_stack: + + +/* + This perhaps the ugliest part in the project. + It contains code to handle arbitrary depth of + nested interrupt and all those corner cases and + nasty gotchas. + + Be aware the twists, offsets and hidden dependencies! + +*/ + +.section .text + .type interrupt_wrapper, @function + .global interrupt_wrapper + interrupt_wrapper: + cld + + subq $8, %rsp + pushq %rsp + + pushq %r15 + pushq %r14 + pushq %r13 + pushq %r12 + pushq %r11 + pushq %r10 + pushq %r9 + pushq %r8 + + pushq %rsi + pushq %rbp + pushq %rdi + pushq %rdx + pushq %rcx + pushq %rbx + pushq %rax + + pushq $0 // placeholder for depth accounting + + movq ics(%rsp), %rax /* 取出 %cs */ + andq $0x3, %rax /* 判断 RPL */ + jz 1f + + /* crossing the user/kernel boundary */ + # x86_64 ignore {d,e}s, Lunaix does not use {f,g}s + + movq current_thread, %rbx + movq iursp(%rsp), %rax + + # Save x87 context to user stack, rather than kernel's memory. + # XXX what will happen if we triggered a page fault during fxsave? + # FIXME I think we should defer this to scheduler, and pratice lazy save/load + # Doing this will also make it safe from nested interrupt due to potential + # page fault when saving + # FIXME Also, generalise it to any FPU context, without constraining it to x87. + + #andl $stack_alignment, %eax + #subl $512, %eax + #fxsave (%eax) + + # 保存用户栈顶指针。因为我们允许同级中断的产生,所以需要该手段跟踪用户栈的地址。 + movq %rax, thread_ustack_top(%rbx) # 存入__current->ustack_top + + /* kernel space same-level switch */ + 1: + movq %rsp, %rax + andq $stack_alignment, %rsp + + movq %rax, %rdi + + xorq %rbp, %rbp # marks the boundary of stack walking + call intr_handler + + .global soft_iret + soft_iret: + movq %rax, %rsp + + movq ics(%rsp), %rax + andq $3, %rax + jz 1f + + # # FIXME x87 fpu context + # movl current_thread, %eax + # movl thread_ustack_top(%eax), %eax + # test %eax, %eax + # jz 1f + # fxrstor (%eax) + +1: + popq %rax # discard struct hart_state::depth + + popq %rax + popq %rbx + popq %rcx + popq %rdx + popq %rdi + popq %rbp + popq %rsi + + popq %r8 + popq %r9 + popq %r10 + popq %r11 + popq %r12 + popq %r13 + popq %r14 + popq %r15 + + popq %rsp + + movq %rax, tmp_store + movq current_thread, %rax + + # nested intr: restore saved context + popq thread_hstate(%rax) + + movq tmp_store, %rax + + addq $16, %rsp # skip: err_code and vector + + # calculate stack after iretq + # Thank god they finally make things consistent in x86_64 + addq $40, %rsp + movq %rsp, (_tss + rsp_0) + subq $40, %rsp + + iretq + + .type do_switch, @function + .global do_switch + do_switch: + # Assumption: __current already hold the target process + + call proc_vmroot + + movq %rax, %rbx + movq %cr3, %rax + xorq %rbx, %rax # avoid setting cr3 if just local thread switch. + jz 1f + + movq %rbx, %cr3 + + 1: + # the address space could be changed. A temporary stack + # is required to prevent corrupt existing stack + movq $tmp_stack, %rsp + + call signal_dispatch # kernel/signal.c + + movq current_thread, %rbx + test %rax, %rax # do we have signal to handle? + jz 1f + + /* + 将tss.esp0设置为上次调度前的esp值。 + 当处理信号时,上下文信息是不会恢复的,而是保存在用户栈中,然后直接跳转进位于用户空间的sig_wrapper进行 + 信号的处理。当用户自定义的信号处理函数返回时,sigreturn的系统调用才开始进行上下文的恢复(或者说是进行 + 另一次调度。 + 由于这中间没有进行地址空间的交换,所以第二次跳转使用的是同一个内核栈,而之前默认tss.esp0的值是永远指向最顶部 + 这样一来就有可能会覆盖更早的上下文信息(比如嵌套的信号捕获函数) + */ + pushq %rax + call update_tss + + popq %rax + jmp handle_signal + + 1: + movq thread_hstate(%rbx), %rax + jmp soft_iret + + .type handle_signal, @function + .global handle_signal + handle_signal: + # 注意1:任何对proc_sig的布局改动,都须及时的保证这里的一致性! + # 注意2:handle_signal在调用之前,须确保proc_sig已经写入用户栈! + # arg1 in %rax: addr of proc_sig structure in user stack + movq psig_saved_hstate(%rax), %rbx # %rbx = &proc_sig->saved_hstate + + pushq $UDATA_SEG + pushq %rax # esp + + movq iexecp(%rbx), %rbx + pushq exrflags(%rbx) # proc_sig->saved_hstate->execp->eflags + + pushq $UCODE_SEG # cs + pushq psig_sigact(%rax) # %rip = proc_sig->sigact + + iretq \ No newline at end of file diff --git a/lunaix-os/arch/i386/exceptions/interrupts.c b/lunaix-os/arch/x86/exceptions/interrupts.c similarity index 82% rename from lunaix-os/arch/i386/exceptions/interrupts.c rename to lunaix-os/arch/x86/exceptions/interrupts.c index d7eced3..9d127ba 100644 --- a/lunaix-os/arch/i386/exceptions/interrupts.c +++ b/lunaix-os/arch/x86/exceptions/interrupts.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include "sys/x86_isa.h" @@ -29,7 +29,7 @@ update_thread_context(struct hart_state* state) } } -void +struct hart_state* intr_handler(struct hart_state* state) { update_thread_context(state); @@ -41,17 +41,11 @@ intr_handler(struct hart_state* state) goto done; } - ERROR("INT %u: (%x) [%p: %p] Unknown", - execp->vector, - execp->err_code, - execp->cs, - execp->eip); - done: if (execp->vector > IV_BASE_END) { isrm_notify_eoi(0, execp->vector); } - return; + return state; } \ No newline at end of file diff --git a/lunaix-os/arch/i386/exceptions/intr_routines.c b/lunaix-os/arch/x86/exceptions/intr_routines.c similarity index 93% rename from lunaix-os/arch/i386/exceptions/intr_routines.c rename to lunaix-os/arch/x86/exceptions/intr_routines.c index ddd06a8..05f547e 100644 --- a/lunaix-os/arch/i386/exceptions/intr_routines.c +++ b/lunaix-os/arch/x86/exceptions/intr_routines.c @@ -11,7 +11,7 @@ #include #include "sys/apic.h" -#include +#include LOG_MODULE("INTR") @@ -42,7 +42,11 @@ intr_routine_general_protection(const struct hart_state* state) void intr_routine_sys_panic(const struct hart_state* state) { +#ifdef CONFIG_ARCH_X86_64 + __print_panic_msg((char*)(state->registers.rdi), state); +#else __print_panic_msg((char*)(state->registers.edi), state); +#endif } void diff --git a/lunaix-os/arch/i386/exceptions/intrhnds.S b/lunaix-os/arch/x86/exceptions/intrhnds.S similarity index 96% rename from lunaix-os/arch/i386/exceptions/intrhnds.S rename to lunaix-os/arch/x86/exceptions/intrhnds.S index e5fe775..f46e58d 100644 --- a/lunaix-os/arch/i386/exceptions/intrhnds.S +++ b/lunaix-os/arch/x86/exceptions/intrhnds.S @@ -1,9 +1,11 @@ -/* Generated from i386_intrhnds.S.j2. Do NOT modify */ +/* Generated from int_handlerhnds.S.j2. Do NOT modify */ #define __ASM__ +#ifdef CONFIG_ARCH_I386 .macro isr_template vector, no_error_code=1 .global _asm_isr\vector .type _asm_isr\vector, @function + .align 16 _asm_isr\vector: .if \no_error_code pushl $0x0 @@ -11,6 +13,20 @@ pushl $\vector jmp interrupt_wrapper .endm +#else +.macro isr_template vector, no_error_code=1 + .global _asm_isr\vector + .type _asm_isr\vector, @function + .align 16 + _asm_isr\vector: + .if \no_error_code + pushq $0x0 + .endif + pushq $\vector + jmp interrupt_wrapper +.endm +#endif + .section .text isr_template 0, no_error_code=1 isr_template 1, no_error_code=1 diff --git a/lunaix-os/arch/x86/exceptions/isrdef.c b/lunaix-os/arch/x86/exceptions/isrdef.c new file mode 100644 index 0000000..23c80b0 --- /dev/null +++ b/lunaix-os/arch/x86/exceptions/isrdef.c @@ -0,0 +1,67 @@ +/* Generated from i386_isrdef.c.j2. Do NOT modify */ + +#include +#include "sys/int_handler.h" +#include "sys/vectors.h" +#include "sys/x86_isa.h" + +#define IDT_INTERRUPT 0x70 +#define IDT_ATTR(dpl, type) (((type) << 5) | ((dpl & 3) << 13) | (1 << 15)) +#define IDT_ENTRY 256 + +#define __expand_iv(iv) _asm_isr##iv() +#define DECLARE_ISR(iv) extern void __expand_iv(iv); + +#ifndef CONFIG_ARCH_X86_64 + +static inline void +install_idte(struct x86_sysdesc* idt, int iv, ptr_t isr, int dpl) +{ + struct x86_sysdesc* idte = &idt[iv]; + + idte->hi = ((ptr_t)isr & 0xffff0000) | IDT_ATTR(dpl, IDT_INTERRUPT); + idte->lo = (KCODE_SEG << 16) | ((ptr_t)isr & 0x0000ffff); +} +struct x86_sysdesc _idt[IDT_ENTRY]; +u16_t _idt_limit = sizeof(_idt) - 1; + +#else + +static inline must_inline void +install_idte(struct x86_sysdesc* idt, int iv, ptr_t isr, int dpl) +{ + struct x86_sysdesc* idte = &idt[iv]; + + idte->hi = isr >> 32; + + idte->lo = (isr & 0xffff0000UL) | IDT_ATTR(dpl, IDT_INTERRUPT); + + idte->lo <<= 32; + idte->lo |= (KCODE_SEG << 16) | (isr & 0x0000ffffUL); +} + +struct x86_sysdesc _idt[IDT_ENTRY]; +u16_t _idt_limit = sizeof(_idt) - 1; + +#endif + +DECLARE_ISR(0) +DECLARE_ISR(33) + +void +exception_install_handler() +{ + ptr_t start = (ptr_t)_asm_isr0; + + for (int i = 0; i < 256; i++) { + install_idte(_idt, i, start, 0); + start += 16; + } + + install_idte(_idt, 33, (ptr_t)_asm_isr33, 3); + +#ifdef CONFIG_ARCH_X86_64 + // TODO set different IST to some exception so it can get a + // better and safe stack to work with +#endif +} \ No newline at end of file diff --git a/lunaix-os/arch/i386/exceptions/i386_isrm.c b/lunaix-os/arch/x86/exceptions/isrm.c similarity index 100% rename from lunaix-os/arch/i386/exceptions/i386_isrm.c rename to lunaix-os/arch/x86/exceptions/isrm.c diff --git a/lunaix-os/arch/x86/exec/elf32.c b/lunaix-os/arch/x86/exec/elf32.c new file mode 100644 index 0000000..f3bd47f --- /dev/null +++ b/lunaix-os/arch/x86/exec/elf32.c @@ -0,0 +1,13 @@ +#include + +#define EM_386 3 + +int +elf_check_arch(const struct elf* elf) +{ + const struct elf_ehdr* ehdr = &elf->eheader; + + return *(u32_t*)(ehdr->e_ident) == ELFMAGIC_LE && + ehdr->e_ident[EI_CLASS] == ELFCLASS32 && + ehdr->e_ident[EI_DATA] == ELFDATA2LSB && ehdr->e_machine == EM_386; +} \ No newline at end of file diff --git a/lunaix-os/arch/x86/exec/elf64.c b/lunaix-os/arch/x86/exec/elf64.c new file mode 100644 index 0000000..4bc9058 --- /dev/null +++ b/lunaix-os/arch/x86/exec/elf64.c @@ -0,0 +1,14 @@ +#include + +#define EM_X86_64 62 + +int +elf_check_arch(const struct elf* elf) +{ + const struct elf_ehdr* ehdr = &elf->eheader; + + return *(u32_t*)(ehdr->e_ident) == ELFMAGIC_LE && + ehdr->e_ident[EI_CLASS] == ELFCLASS64 && + ehdr->e_ident[EI_DATA] == ELFDATA2LSB && + ehdr->e_machine == EM_X86_64; +} \ No newline at end of file diff --git a/lunaix-os/arch/x86/exec/exec.c b/lunaix-os/arch/x86/exec/exec.c new file mode 100644 index 0000000..81ad0ec --- /dev/null +++ b/lunaix-os/arch/x86/exec/exec.c @@ -0,0 +1,23 @@ +#include +#include +#include + +int +exec_arch_prepare_entry(struct thread* thread, struct exec_host* container) +{ + struct hart_state* hstate; + + hstate = thread->hstate; + +#ifdef CONFIG_ARCH_X86_64 + hstate->execp->rip = container->exe.entry; + hstate->execp->rsp = container->stack_top; + +#else + hstate->execp->eip = container->exe.entry; + hstate->execp->esp = container->stack_top; + +#endif + + return 0; +} \ No newline at end of file diff --git a/lunaix-os/arch/i386/failsafe.S b/lunaix-os/arch/x86/failsafe.S similarity index 100% rename from lunaix-os/arch/i386/failsafe.S rename to lunaix-os/arch/x86/failsafe.S diff --git a/lunaix-os/arch/i386/gdbstub.c b/lunaix-os/arch/x86/gdbstub.c similarity index 92% rename from lunaix-os/arch/i386/gdbstub.c rename to lunaix-os/arch/x86/gdbstub.c index 78c3165..d7d131a 100644 --- a/lunaix-os/arch/i386/gdbstub.c +++ b/lunaix-os/arch/x86/gdbstub.c @@ -21,11 +21,12 @@ void arch_gdbstub_save_regs(struct gdb_state* state, struct hart_state* hstate) { /* Load Registers */ +#ifndef CONFIG_ARCH_X86_64 state->registers[GDB_CPU_I386_REG_EAX] = hstate->registers.eax; state->registers[GDB_CPU_I386_REG_ECX] = hstate->registers.ecx; state->registers[GDB_CPU_I386_REG_EDX] = hstate->registers.edx; state->registers[GDB_CPU_I386_REG_EBX] = hstate->registers.ebx; - state->registers[GDB_CPU_I386_REG_ESP] = hstate->esp; + state->registers[GDB_CPU_I386_REG_ESP] = hstate->sp; state->registers[GDB_CPU_I386_REG_EBP] = hstate->registers.ebp; state->registers[GDB_CPU_I386_REG_ESI] = hstate->registers.esi; state->registers[GDB_CPU_I386_REG_EDI] = hstate->registers.edi; @@ -37,17 +38,21 @@ arch_gdbstub_save_regs(struct gdb_state* state, struct hart_state* hstate) state->registers[GDB_CPU_I386_REG_ES] = hstate->registers.es; state->registers[GDB_CPU_I386_REG_FS] = hstate->registers.fs; state->registers[GDB_CPU_I386_REG_GS] = hstate->registers.gs; +#else + // TODO +#endif } void arch_gdbstub_restore_regs(struct gdb_state* state, struct hart_state* hstate) { /* Restore Registers */ +#ifndef CONFIG_ARCH_X86_64 hstate->registers.eax = state->registers[GDB_CPU_I386_REG_EAX]; hstate->registers.ecx = state->registers[GDB_CPU_I386_REG_ECX]; hstate->registers.edx = state->registers[GDB_CPU_I386_REG_EDX]; hstate->registers.ebx = state->registers[GDB_CPU_I386_REG_EBX]; - hstate->esp = state->registers[GDB_CPU_I386_REG_ESP]; + hstate->sp = state->registers[GDB_CPU_I386_REG_ESP]; hstate->registers.ebp = state->registers[GDB_CPU_I386_REG_EBP]; hstate->registers.esi = state->registers[GDB_CPU_I386_REG_ESI]; hstate->registers.edi = state->registers[GDB_CPU_I386_REG_EDI]; @@ -59,6 +64,9 @@ arch_gdbstub_restore_regs(struct gdb_state* state, struct hart_state* hstate) hstate->registers.es = state->registers[GDB_CPU_I386_REG_ES]; hstate->registers.fs = state->registers[GDB_CPU_I386_REG_FS]; hstate->registers.gs = state->registers[GDB_CPU_I386_REG_GS]; +#else + // TODO +#endif } diff --git a/lunaix-os/arch/i386/hal/LBuild b/lunaix-os/arch/x86/hal/LBuild similarity index 100% rename from lunaix-os/arch/i386/hal/LBuild rename to lunaix-os/arch/x86/hal/LBuild diff --git a/lunaix-os/arch/i386/hal/apic.c b/lunaix-os/arch/x86/hal/apic.c similarity index 100% rename from lunaix-os/arch/i386/hal/apic.c rename to lunaix-os/arch/x86/hal/apic.c diff --git a/lunaix-os/arch/i386/hal/apic_timer.c b/lunaix-os/arch/x86/hal/apic_timer.c similarity index 100% rename from lunaix-os/arch/i386/hal/apic_timer.c rename to lunaix-os/arch/x86/hal/apic_timer.c diff --git a/lunaix-os/arch/i386/hal/apic_timer.h b/lunaix-os/arch/x86/hal/apic_timer.h similarity index 100% rename from lunaix-os/arch/i386/hal/apic_timer.h rename to lunaix-os/arch/x86/hal/apic_timer.h diff --git a/lunaix-os/arch/i386/hal/cpu.c b/lunaix-os/arch/x86/hal/cpu.c similarity index 100% rename from lunaix-os/arch/i386/hal/cpu.c rename to lunaix-os/arch/x86/hal/cpu.c diff --git a/lunaix-os/arch/i386/hal/ioapic.c b/lunaix-os/arch/x86/hal/ioapic.c similarity index 100% rename from lunaix-os/arch/i386/hal/ioapic.c rename to lunaix-os/arch/x86/hal/ioapic.c diff --git a/lunaix-os/arch/i386/hal/mc146818a.c b/lunaix-os/arch/x86/hal/mc146818a.c similarity index 100% rename from lunaix-os/arch/i386/hal/mc146818a.c rename to lunaix-os/arch/x86/hal/mc146818a.c diff --git a/lunaix-os/arch/i386/hal/pic.h b/lunaix-os/arch/x86/hal/pic.h similarity index 100% rename from lunaix-os/arch/i386/hal/pic.h rename to lunaix-os/arch/x86/hal/pic.h diff --git a/lunaix-os/arch/i386/hal/ps2kbd.c b/lunaix-os/arch/x86/hal/ps2kbd.c similarity index 100% rename from lunaix-os/arch/i386/hal/ps2kbd.c rename to lunaix-os/arch/x86/hal/ps2kbd.c diff --git a/lunaix-os/arch/i386/hal/rngx86.c b/lunaix-os/arch/x86/hal/rngx86.c similarity index 64% rename from lunaix-os/arch/i386/hal/rngx86.c rename to lunaix-os/arch/x86/hal/rngx86.c index b108740..0d48d44 100644 --- a/lunaix-os/arch/i386/hal/rngx86.c +++ b/lunaix-os/arch/x86/hal/rngx86.c @@ -6,14 +6,31 @@ static inline void rng_fill(void* data, size_t len) { +#ifdef CONFIG_ARCH_X86_64 + asm volatile("1:\n" + "rdrand %%rax\n" + "movq %%rax, (%0)\n" + "addq $8, %%rax\n" + "subq $8, %1\n" + "jnz 1b" + :: + "r"((ptr_t)data), + "r"((len & ~0x7)) + : + "%eax"); +#else asm volatile("1:\n" "rdrand %%eax\n" "movl %%eax, (%0)\n" "addl $4, %%eax\n" "subl $4, %1\n" - "jnz 1b" ::"r"((ptr_t)data), + "jnz 1b" + :: + "r"((ptr_t)data), "r"((len & ~0x3)) - : "%eax"); + : + "%eax"); +#endif } static int @@ -26,9 +43,9 @@ __rand_rd_pg(struct device* dev, void* buf, size_t offset) static int __rand_rd(struct device* dev, void* buf, size_t offset, size_t len) { - if (unlikely(len < 4)) { + if (unlikely(len < sizeof(ptr_t))) { int tmp_buf = 0; - rng_fill(&tmp_buf, 4); + rng_fill(&tmp_buf, sizeof(ptr_t)); memcpy(buf, &tmp_buf, len); } else { rng_fill(buf, len); diff --git a/lunaix-os/arch/x86/hart.c b/lunaix-os/arch/x86/hart.c new file mode 100644 index 0000000..124ea6e --- /dev/null +++ b/lunaix-os/arch/x86/hart.c @@ -0,0 +1,22 @@ +#include +#include +#include + +#include + +bool +install_hart_transition(ptr_t vm_mnt, struct hart_transition* ht) +{ + pte_t pte; + if (!vmm_lookupat(vm_mnt, ht->inject, &pte)) { + return false; + } + + mount_page(PG_MOUNT_4, pte_paddr(pte)); + + ptr_t mount_inject = PG_MOUNT_4 + va_offset(ht->inject); + memcpy((void*)mount_inject, &ht->transfer, sizeof(ht->transfer)); + + unmount_page(PG_MOUNT_4); + return true; +} \ No newline at end of file diff --git a/lunaix-os/arch/i386/hart.c b/lunaix-os/arch/x86/hart32.c similarity index 79% rename from lunaix-os/arch/i386/hart.c rename to lunaix-os/arch/x86/hart32.c index 09bd2b3..7c34573 100644 --- a/lunaix-os/arch/i386/hart.c +++ b/lunaix-os/arch/x86/hart32.c @@ -1,3 +1,5 @@ +#ifdef CONFIG_ARCH_I386 + #include #include #include @@ -10,23 +12,6 @@ volatile struct x86_tss _tss = { .link = 0, .esp0 = 0, .ss0 = KDATA_SEG }; -bool -install_hart_transition(ptr_t vm_mnt, struct hart_transition* ht) -{ - pte_t pte; - if (!vmm_lookupat(vm_mnt, ht->inject, &pte)) { - return false; - } - - mount_page(PG_MOUNT_4, pte_paddr(pte)); - - ptr_t mount_inject = PG_MOUNT_4 + va_offset(ht->inject); - memcpy((void*)mount_inject, &ht->transfer, sizeof(ht->transfer)); - - unmount_page(PG_MOUNT_4); - return true; -} - void hart_prepare_transition(struct hart_transition* ht, ptr_t kstack_tp, ptr_t ustack_pt, @@ -57,4 +42,6 @@ hart_prepare_transition(struct hart_transition* ht, .ss = data_seg, .esp = align_stack(ustack_pt), .eflags = mstate }; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/lunaix-os/arch/x86/hart64.c b/lunaix-os/arch/x86/hart64.c new file mode 100644 index 0000000..af9da33 --- /dev/null +++ b/lunaix-os/arch/x86/hart64.c @@ -0,0 +1,44 @@ +#ifdef CONFIG_ARCH_X86_64 + +#include +#include +#include +#include + +#include +#include + +volatile struct x86_tss _tss = { }; + +void +hart_prepare_transition(struct hart_transition* ht, + ptr_t kstack_tp, ptr_t ustack_pt, + ptr_t entry, bool to_user) +{ + ptr_t stack = ustack_pt; + ptr_t offset = (ptr_t)&ht->transfer.eret - (ptr_t)&ht->transfer; + ht->inject = align_stack(kstack_tp - sizeof(ht->transfer)); + + ht->transfer.state = (struct hart_state){ + .registers = { }, + .execp = (struct exec_param*)(ht->inject + offset) + }; + + int code_seg = KCODE_SEG, data_seg = KDATA_SEG; + int mstate = cpu_ldstate(); + if (to_user) { + code_seg = UCODE_SEG, data_seg = UDATA_SEG; + mstate |= 0x200; // enable interrupt + } + else { + stack = kstack_tp; + } + + ht->transfer.eret = (struct exec_param) { + .cs = code_seg, .rip = entry, + .ss = data_seg, .rsp = align_stack(stack), + .rflags = mstate + }; +} + +#endif \ No newline at end of file diff --git a/lunaix-os/arch/x86/includes/linking/base_defs.ld.inc b/lunaix-os/arch/x86/includes/linking/base_defs.ld.inc new file mode 100644 index 0000000..cb74045 --- /dev/null +++ b/lunaix-os/arch/x86/includes/linking/base_defs.ld.inc @@ -0,0 +1,18 @@ +#ifndef __LUNAIX_BASE_DEFS_LD_INC +#define __LUNAIX_BASE_DEFS_LD_INC + +#define __LD__ +#include + +#define KEXEC_BASE KERNEL_IMG +#define PAGE_GRAN 4K + +#define ENTRY_POINT start_ + +#ifdef CONFIG_X86_BL_MB +# define LOAD_OFF 0x100000 +#else +# error "unknown boot option, can't infer load_off" +#endif + +#endif /* __LUNAIX_BASE_DEFS_LD_INC */ diff --git a/lunaix-os/arch/x86/includes/linking/boot_secs.ldx b/lunaix-os/arch/x86/includes/linking/boot_secs.ldx new file mode 100644 index 0000000..3c71be1 --- /dev/null +++ b/lunaix-os/arch/x86/includes/linking/boot_secs.ldx @@ -0,0 +1,29 @@ +#ifndef __LUNAIX_BOOT_SECS_LD_INC +#define __LUNAIX_BOOT_SECS_LD_INC + +#include "base_defs.ld.inc" + +__kboot_start = .; + +.boot.text BLOCK(PAGE_GRAN) : +{ +#if defined(CONFIG_X86_BL_MB) || defined(CONFIG_X86_BL_MB2) + *(.multiboot) +#endif + + *(.boot.text) +} + +.boot.bss BLOCK(PAGE_GRAN) : +{ + *(.boot.bss) +} + +.boot.data BLOCK(PAGE_GRAN) : +{ + *(.boot.data) +} + +__kboot_end = ALIGN(PAGE_GRAN); + +#endif /* __LUNAIX_BOOT_SECS_LD_INC */ diff --git a/lunaix-os/arch/x86/includes/sys/abi.h b/lunaix-os/arch/x86/includes/sys/abi.h new file mode 100644 index 0000000..bfa93a7 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/abi.h @@ -0,0 +1,33 @@ +#ifndef __LUNAIX_I386ABI_H +#define __LUNAIX_I386ABI_H + +#ifdef CONFIG_ARCH_X86_64 +# include "abi64.h" +#else +# include "abi32.h" +#endif + +#ifndef __ASM__ +#define align_stack(ptr) ((ptr) & stack_alignment) + +static inline void must_inline noret +switch_context() { + asm volatile("jmp do_switch\n"); + unreachable; +} + + +static inline ptr_t +abi_get_retaddr() +{ + return *((ptr_t*)abi_get_callframe() + 1); +} + +static inline ptr_t +abi_get_retaddrat(ptr_t fp) +{ + return *((ptr_t*)fp + 1); +} + +#endif +#endif /* __LUNAIX_ABI_H */ diff --git a/lunaix-os/arch/x86/includes/sys/abi32.h b/lunaix-os/arch/x86/includes/sys/abi32.h new file mode 100644 index 0000000..a5d3db7 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/abi32.h @@ -0,0 +1,41 @@ +#ifndef __LUNAIX_ARCH_ABI32_H +#define __LUNAIX_ARCH_ABI32_H + +#include "sys/x86_isa.h" + +#define stack_alignment 0xfffffff0 + +#ifndef __ASM__ +#define store_retval(retval) current_thread->hstate->registers.eax = (retval) + +#define store_retval_to(th, retval) (th)->hstate->registers.eax = (retval) + +static inline void must_inline +j_usr(ptr_t sp, ptr_t pc) +{ + asm volatile("movw %0, %%ax\n" + "movw %%ax, %%es\n" + "movw %%ax, %%ds\n" + "movw %%ax, %%fs\n" + "movw %%ax, %%gs\n" + "pushl %0\n" + "pushl %1\n" + "pushl %2\n" + "pushl %3\n" + "retf" ::"i"(UDATA_SEG), + "r"(sp), + "i"(UCODE_SEG), + "r"(pc) + : "eax", "memory"); +} + +static inline ptr_t must_inline +abi_get_callframe() +{ + ptr_t val; + asm("movl %%ebp, %0" : "=r"(val)::); + return val; +} + +#endif +#endif /* __LUNAIX_ARCH_ABI32_H */ diff --git a/lunaix-os/arch/x86/includes/sys/abi64.h b/lunaix-os/arch/x86/includes/sys/abi64.h new file mode 100644 index 0000000..1c93cc1 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/abi64.h @@ -0,0 +1,40 @@ +#ifndef __LUNAIX_ARCH_ABI64_H +#define __LUNAIX_ARCH_ABI64_H + +#include "sys/x86_isa.h" + +#define stack_alignment 0xfffffffffffffff0UL + +#ifndef __ASM__ +#define store_retval(retval) current_thread->hstate->registers.rax = (retval) + +#define store_retval_to(th, retval) (th)->hstate->registers.rax = (retval) + +static inline void must_inline +j_usr(ptr_t sp, ptr_t pc) +{ + asm volatile( + "pushq %0\n" + "pushq %1\n" + "pushq %2\n" + "pushq %3\n" + "retfq" + :: + "i"(UDATA_SEG), + "r"(sp), + "i"(UCODE_SEG), + "r"(pc) + : + "memory"); +} + +static inline ptr_t must_inline +abi_get_callframe() +{ + ptr_t val; + asm("movq %%rbp, %0" : "=r"(val)::); + return val; +} + +#endif +#endif /* __LUNAIX_ARCH_ABI64_H */ diff --git a/lunaix-os/arch/i386/includes/sys/apic.h b/lunaix-os/arch/x86/includes/sys/apic.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/apic.h rename to lunaix-os/arch/x86/includes/sys/apic.h diff --git a/lunaix-os/arch/i386/boot/archinit.h b/lunaix-os/arch/x86/includes/sys/boot/archinit.h similarity index 77% rename from lunaix-os/arch/i386/boot/archinit.h rename to lunaix-os/arch/x86/includes/sys/boot/archinit.h index b936ca1..5ac58ca 100644 --- a/lunaix-os/arch/i386/boot/archinit.h +++ b/lunaix-os/arch/x86/includes/sys/boot/archinit.h @@ -2,8 +2,8 @@ #define __LUNAIX_ARCHINIT_H #include -#include -#include +#include "bstage.h" +#include "multiboot.h" ptr_t boot_text kpg_init(); diff --git a/lunaix-os/arch/x86/includes/sys/boot/bstage.h b/lunaix-os/arch/x86/includes/sys/boot/bstage.h new file mode 100644 index 0000000..589f2a5 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/boot/bstage.h @@ -0,0 +1,37 @@ +#ifndef __LUNAIX_BSTAGE_H +#define __LUNAIX_BSTAGE_H +#include + +#define boot_text __attribute__((section(".boot.text"))) +#define boot_data __attribute__((section(".boot.data"))) +#define boot_bss __attribute__((section(".boot.bss"))) + +/* + Bridge the far symbol to the vicinity. + + Which is a workaround for relocation + issue where symbol define in kernel + code is too far away from the boot code. +*/ +#ifdef CONFIG_ARCH_X86_64 +#define bridge_farsym(far_sym) \ + asm( \ + ".section .boot.bss\n" \ + ".align 8\n" \ + ".globl __lc_" #far_sym "\n" \ + "__lc_" #far_sym ":\n" \ + ".8byte " #far_sym "\n" \ + ".previous\n" \ + ); \ + extern unsigned long __lc_##far_sym[]; +#define __far(far_sym) (__lc_##far_sym[0]) + +#else +#define bridge_farsym(far_sym) extern u8_t far_sym[]; +#define __far(far_sym) ((ptr_t)far_sym) + +#endif + +ptr_t remap_kernel(); + +#endif /* __LUNAIX_BSTAGE_H */ diff --git a/lunaix-os/arch/i386/includes/sys/boot/multiboot.h b/lunaix-os/arch/x86/includes/sys/boot/mb.h similarity index 97% rename from lunaix-os/arch/i386/includes/sys/boot/multiboot.h rename to lunaix-os/arch/x86/includes/sys/boot/mb.h index aca8f43..6dab643 100644 --- a/lunaix-os/arch/i386/includes/sys/boot/multiboot.h +++ b/lunaix-os/arch/x86/includes/sys/boot/mb.h @@ -1,11 +1,8 @@ -#ifndef MULTIBOOT_HEADER -#define MULTIBOOT_HEADER 1 +#ifndef LUNAIX_MULTIBOOT_HEADER +#define LUNAIX_MULTIBOOT_HEADER 1 // References https://www.gnu.org/software/grub/manual/multiboot/multiboot.html -#define MULTIBOOT_MAGIC 0x1BADB002 -#define CHECKSUM(flags) -(MULTIBOOT_MAGIC + flags) - /* This should be in %eax. */ #define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 @@ -227,6 +224,5 @@ struct multiboot_apm_info }; #define MB_MMAP_ENTRY_SIZE sizeof(multiboot_memory_map_t) - #endif -#endif \ No newline at end of file +#endif diff --git a/lunaix-os/arch/x86/includes/sys/boot/mb2.h b/lunaix-os/arch/x86/includes/sys/boot/mb2.h new file mode 100644 index 0000000..390d451 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/boot/mb2.h @@ -0,0 +1,414 @@ +/* multiboot2.h - Multiboot 2 header file. */ +/* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc. + * + * 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 ANY + * DEVELOPER OR DISTRIBUTOR 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. + */ + +#ifndef LUNAIX_MULTIBOOT_HEADER +#define LUNAIX_MULTIBOOT_HEADER 1 + +/* How many bytes from the start of the file we search for the header. */ +#define MULTIBOOT_SEARCH 32768 +#define MULTIBOOT_HEADER_ALIGN 8 + +/* This should be in %eax. */ +#define MULTIBOOT_BOOTLOADER_MAGIC 0x36d76289 + +/* Alignment of multiboot modules. */ +#define MULTIBOOT_MOD_ALIGN 0x00001000 + +/* Alignment of the multiboot info structure. */ +#define MULTIBOOT_INFO_ALIGN 0x00000008 + +/* Flags set in the ’flags’ member of the multiboot header. */ + +#define MULTIBOOT_TAG_ALIGN 8 +#define MULTIBOOT_TAG_TYPE_END 0 +#define MULTIBOOT_TAG_TYPE_CMDLINE 1 +#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2 +#define MULTIBOOT_TAG_TYPE_MODULE 3 +#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4 +#define MULTIBOOT_TAG_TYPE_BOOTDEV 5 +#define MULTIBOOT_TAG_TYPE_MMAP 6 +#define MULTIBOOT_TAG_TYPE_VBE 7 +#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8 +#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9 +#define MULTIBOOT_TAG_TYPE_APM 10 +#define MULTIBOOT_TAG_TYPE_EFI32 11 +#define MULTIBOOT_TAG_TYPE_EFI64 12 +#define MULTIBOOT_TAG_TYPE_SMBIOS 13 +#define MULTIBOOT_TAG_TYPE_ACPI_OLD 14 +#define MULTIBOOT_TAG_TYPE_ACPI_NEW 15 +#define MULTIBOOT_TAG_TYPE_NETWORK 16 +#define MULTIBOOT_TAG_TYPE_EFI_MMAP 17 +#define MULTIBOOT_TAG_TYPE_EFI_BS 18 +#define MULTIBOOT_TAG_TYPE_EFI32_IH 19 +#define MULTIBOOT_TAG_TYPE_EFI64_IH 20 +#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21 + +#define MULTIBOOT_HEADER_TAG_END 0 +#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1 +#define MULTIBOOT_HEADER_TAG_ADDRESS 2 +#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3 +#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4 +#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5 +#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6 +#define MULTIBOOT_HEADER_TAG_EFI_BS 7 +#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8 +#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9 +#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10 + +#define MULTIBOOT_ARCHITECTURE_I386 0 +#define MULTIBOOT_ARCHITECTURE_MIPS32 4 +#define MULTIBOOT_HEADER_TAG_OPTIONAL 1 + +#define MULTIBOOT_LOAD_PREFERENCE_NONE 0 +#define MULTIBOOT_LOAD_PREFERENCE_LOW 1 +#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2 + +#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1 +#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2 + +#ifndef __ASM__ + +typedef unsigned char multiboot_uint8_t; +typedef unsigned short multiboot_uint16_t; +typedef unsigned int multiboot_uint32_t; +typedef unsigned long long multiboot_uint64_t; + +struct multiboot_header +{ + /* Must be MULTIBOOT_MAGIC - see above. */ + multiboot_uint32_t magic; + + /* ISA */ + multiboot_uint32_t architecture; + + /* Total header length. */ + multiboot_uint32_t header_length; + + /* The above fields plus this one must equal 0 mod 2^32. */ + multiboot_uint32_t checksum; +}; + +struct multiboot_header_tag +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; +}; + +struct multiboot_header_tag_information_request +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; + multiboot_uint32_t requests[0]; +}; + +struct multiboot_header_tag_address +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; + multiboot_uint32_t header_addr; + multiboot_uint32_t load_addr; + multiboot_uint32_t load_end_addr; + multiboot_uint32_t bss_end_addr; +}; + +struct multiboot_header_tag_entry_address +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; + multiboot_uint32_t entry_addr; +}; + +struct multiboot_header_tag_console_flags +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; + multiboot_uint32_t console_flags; +}; + +struct multiboot_header_tag_framebuffer +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; + multiboot_uint32_t width; + multiboot_uint32_t height; + multiboot_uint32_t depth; +}; + +struct multiboot_header_tag_module_align +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; +}; + +struct multiboot_header_tag_relocatable +{ + multiboot_uint16_t type; + multiboot_uint16_t flags; + multiboot_uint32_t size; + multiboot_uint32_t min_addr; + multiboot_uint32_t max_addr; + multiboot_uint32_t align; + multiboot_uint32_t preference; +}; + +struct multiboot_color +{ + multiboot_uint8_t red; + multiboot_uint8_t green; + multiboot_uint8_t blue; +}; + +struct multiboot_mmap_entry +{ + multiboot_uint64_t addr; + multiboot_uint64_t len; +#define MULTIBOOT_MEMORY_AVAILABLE 1 +#define MULTIBOOT_MEMORY_RESERVED 2 +#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 +#define MULTIBOOT_MEMORY_NVS 4 +#define MULTIBOOT_MEMORY_BADRAM 5 + multiboot_uint32_t type; + multiboot_uint32_t zero; +}; +typedef struct multiboot_mmap_entry multiboot_memory_map_t; + +struct multiboot_tag +{ + multiboot_uint32_t type; + multiboot_uint32_t size; +}; + +struct multiboot_tag_string +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + char string[0]; +}; + +struct multiboot_tag_module +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t mod_start; + multiboot_uint32_t mod_end; + char cmdline[0]; +}; + +struct multiboot_tag_basic_meminfo +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t mem_lower; + multiboot_uint32_t mem_upper; +}; + +struct multiboot_tag_bootdev +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t biosdev; + multiboot_uint32_t slice; + multiboot_uint32_t part; +}; + +struct multiboot_tag_mmap +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t entry_size; + multiboot_uint32_t entry_version; + struct multiboot_mmap_entry entries[0]; +}; + +struct multiboot_vbe_info_block +{ + multiboot_uint8_t external_specification[512]; +}; + +struct multiboot_vbe_mode_info_block +{ + multiboot_uint8_t external_specification[256]; +}; + +struct multiboot_tag_vbe +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + + multiboot_uint16_t vbe_mode; + multiboot_uint16_t vbe_interface_seg; + multiboot_uint16_t vbe_interface_off; + multiboot_uint16_t vbe_interface_len; + + struct multiboot_vbe_info_block vbe_control_info; + struct multiboot_vbe_mode_info_block vbe_mode_info; +}; + +struct multiboot_tag_framebuffer_common +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + + multiboot_uint64_t framebuffer_addr; + multiboot_uint32_t framebuffer_pitch; + multiboot_uint32_t framebuffer_width; + multiboot_uint32_t framebuffer_height; + multiboot_uint8_t framebuffer_bpp; +#define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0 +#define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1 +#define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2 + multiboot_uint8_t framebuffer_type; + multiboot_uint16_t reserved; +}; + +struct multiboot_tag_framebuffer +{ + struct multiboot_tag_framebuffer_common common; + + union + { + struct + { + multiboot_uint16_t framebuffer_palette_num_colors; + struct multiboot_color framebuffer_palette[0]; + }; + struct + { + multiboot_uint8_t framebuffer_red_field_position; + multiboot_uint8_t framebuffer_red_mask_size; + multiboot_uint8_t framebuffer_green_field_position; + multiboot_uint8_t framebuffer_green_mask_size; + multiboot_uint8_t framebuffer_blue_field_position; + multiboot_uint8_t framebuffer_blue_mask_size; + }; + }; +}; + +struct multiboot_tag_elf_sections +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t num; + multiboot_uint32_t entsize; + multiboot_uint32_t shndx; + char sections[0]; +}; + +struct multiboot_tag_apm +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint16_t version; + multiboot_uint16_t cseg; + multiboot_uint32_t offset; + multiboot_uint16_t cseg_16; + multiboot_uint16_t dseg; + multiboot_uint16_t flags; + multiboot_uint16_t cseg_len; + multiboot_uint16_t cseg_16_len; + multiboot_uint16_t dseg_len; +}; + +struct multiboot_tag_efi32 +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t pointer; +}; + +struct multiboot_tag_efi64 +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint64_t pointer; +}; + +struct multiboot_tag_smbios +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint8_t major; + multiboot_uint8_t minor; + multiboot_uint8_t reserved[6]; + multiboot_uint8_t tables[0]; +}; + +struct multiboot_tag_old_acpi +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint8_t rsdp[0]; +}; + +struct multiboot_tag_new_acpi +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint8_t rsdp[0]; +}; + +struct multiboot_tag_network +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint8_t dhcpack[0]; +}; + +struct multiboot_tag_efi_mmap +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t descr_size; + multiboot_uint32_t descr_vers; + multiboot_uint8_t efi_mmap[0]; +}; + +struct multiboot_tag_efi32_ih +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t pointer; +}; + +struct multiboot_tag_efi64_ih +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint64_t pointer; +}; + +struct multiboot_tag_load_base_addr +{ + multiboot_uint32_t type; + multiboot_uint32_t size; + multiboot_uint32_t load_base_addr; +}; + +#endif /* ! __ASM__ */ + +#endif /* ! MULTIBOOT_HEADER */ \ No newline at end of file diff --git a/lunaix-os/arch/x86/includes/sys/boot/multiboot.S.inc b/lunaix-os/arch/x86/includes/sys/boot/multiboot.S.inc new file mode 100644 index 0000000..061680d --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/boot/multiboot.S.inc @@ -0,0 +1,39 @@ +#define __ASM__ + +#include "multiboot.h" + +.section .multiboot +__mb_start: + .4byte MULTIBOOT_MAGIC + +#ifdef CONFIG_X86_BL_MB + #define MB_FLAGS (MULTIBOOT_MEMORY_INFO | MULTIBOOT_PAGE_ALIGN) + .4byte MB_FLAGS + .4byte -(MULTIBOOT_MAGIC + MB_FLAGS) + +#elif CONFIG_X86_BL_MB2 + #define HDR_LEN (__mb_end - __mb_start) + + .4byte 0 + .4byte HDR_LEN + .4byte -(MULTIBOOT_MAGIC + HDR_LEN) + + .align MULTIBOOT_TAG_ALIGN + __mbir_tag_start: + .2byte MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST + .2byte 0 + .4byte __mbir_tag_end - __mbir_tag_start + .4byte MULTIBOOT_TAG_TYPE_CMDLINE + .4byte MULTIBOOT_TAG_TYPE_MMAP + .4byte MULTIBOOT_TAG_TYPE_BASIC_MEMINFO + .4byte MULTIBOOT_TAG_TYPE_MODULE + __mbir_tag_end: + + .align MULTIBOOT_TAG_ALIGN + .2byte MULTIBOOT_HEADER_TAG_END + .2byte 0 + .4byte 8 + +#endif + +__mb_end: \ No newline at end of file diff --git a/lunaix-os/arch/x86/includes/sys/boot/multiboot.h b/lunaix-os/arch/x86/includes/sys/boot/multiboot.h new file mode 100644 index 0000000..72e83fc --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/boot/multiboot.h @@ -0,0 +1,14 @@ +#ifndef LUNAIX_MULTIBOOT_H +#define LUNAIX_MULTIBOOT_H 1 + +#ifdef CONFIG_X86_BL_MB +# include "mb.h" +# define MULTIBOOT_MAGIC 0x1BADB002 +#elif CONFIG_X86_BL_MB2 +# include "mb2.h" +# define MULTIBOOT_MAGIC 0xe85250d6 +#else +#error "Multiboot interfacing is not enabled" +#endif + +#endif \ No newline at end of file diff --git a/lunaix-os/arch/i386/includes/sys/cpu.h b/lunaix-os/arch/x86/includes/sys/cpu.h similarity index 77% rename from lunaix-os/arch/i386/includes/sys/cpu.h rename to lunaix-os/arch/x86/includes/sys/cpu.h index ca7080a..2889234 100644 --- a/lunaix-os/arch/i386/includes/sys/cpu.h +++ b/lunaix-os/arch/x86/includes/sys/cpu.h @@ -3,6 +3,14 @@ #include +#ifdef CONFIG_ARCH_X86_64 +# define _POP "popq " +# define _MOV "movq " +#else +# define _POP "popl " +# define _MOV "movl " +#endif + /** * @brief Get processor ID string * @@ -17,7 +25,6 @@ cpu_trap_sched(); void cpu_trap_panic(char* message); - /** * @brief Load current processor state * @@ -28,7 +35,7 @@ cpu_ldstate() { ptr_t val; asm volatile("pushf\n" - "popl %0\n" + _POP "%0\n" : "=r"(val)::); return val; } @@ -42,7 +49,7 @@ static inline reg_t cpu_ldconfig() { reg_t val; - asm volatile("movl %%cr0,%0" : "=r"(val)); + asm volatile(_MOV "%%cr0,%0" : "=r"(val)); return val; } @@ -54,7 +61,7 @@ cpu_ldconfig() static inline void cpu_chconfig(reg_t val) { - asm("mov %0, %%cr0" ::"r"(val)); + asm(_MOV "%0, %%cr0" ::"r"(val)); } /** @@ -65,9 +72,23 @@ cpu_chconfig(reg_t val) static inline void cpu_chvmspace(reg_t val) { - asm("mov %0, %%cr3" ::"r"(val)); + asm(_MOV "%0, %%cr3" ::"r"(val)); +} + +/** + * @brief Read exeception address + * + * @return ptr_t + */ +static inline ptr_t +cpu_ldeaddr() +{ + ptr_t val; + asm volatile(_MOV "%%cr2,%0" : "=r"(val)); + return val; } + static inline void cpu_enable_interrupt() { @@ -86,17 +107,4 @@ cpu_wait() asm("hlt"); } -/** - * @brief Read exeception address - * - * @return ptr_t - */ -static inline ptr_t -cpu_ldeaddr() -{ - ptr_t val; - asm volatile("movl %%cr2,%0" : "=r"(val)); - return val; -} - #endif /* __LUNAIX_CPU_H */ diff --git a/lunaix-os/arch/i386/includes/sys/crx.h b/lunaix-os/arch/x86/includes/sys/crx.h similarity index 69% rename from lunaix-os/arch/i386/includes/sys/crx.h rename to lunaix-os/arch/x86/includes/sys/crx.h index 270a897..4eb56e0 100644 --- a/lunaix-os/arch/i386/includes/sys/crx.h +++ b/lunaix-os/arch/x86/includes/sys/crx.h @@ -13,6 +13,28 @@ #define CR0_EM ( 1UL << 2 ) #define CR0_MP ( 1UL << 1 ) +#ifdef CONFIG_ARCH_X86_64 + +#define crx_addflag(crx, flag) \ + asm( \ + "movq %%" #crx ", %%rax\n" \ + "orq %0, %%rax\n" \ + "movq %%rax, %%" #crx "\n" \ + ::"r"(flag) \ + :"rax" \ + ); + +#define crx_rmflag(crx, flag) \ + asm( \ + "movq %%" #crx ", %%rax\n" \ + "andq %0, %%rax\n" \ + "movq %%rax, %%" #crx "\n" \ + ::"r"(~(flag)) \ + :"rax" \ + ); + +#else + #define crx_addflag(crx, flag) \ asm( \ "movl %%" #crx ", %%eax\n" \ @@ -31,6 +53,8 @@ :"eax" \ ); +#endif + static inline void cr4_setfeature(unsigned long feature) { diff --git a/lunaix-os/arch/x86/includes/sys/exebi/elf.h b/lunaix-os/arch/x86/includes/sys/exebi/elf.h new file mode 100644 index 0000000..1ccf84a --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/exebi/elf.h @@ -0,0 +1,69 @@ +#ifndef __LUNAIX_ARCH_ELF_H +#define __LUNAIX_ARCH_ELF_H + +#include + +#define ELFCLASS32 1 +#define ELFCLASS64 2 + +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 + +#ifdef CONFIG_ARCH_X86_64 +typedef unsigned long elf_ptr_t; +typedef unsigned short elf_hlf_t; +typedef unsigned long elf_off_t; +typedef int elf_swd_t; +typedef unsigned int elf_wrd_t; +typedef unsigned long elf_xwrd_t; +typedef long elf_sxwrd_t; +#else +typedef unsigned int elf_ptr_t; +typedef unsigned short elf_hlf_t; +typedef unsigned int elf_off_t; +typedef unsigned int elf_swd_t; +typedef unsigned int elf_wrd_t; +#endif + +struct elf_ehdr +{ + u8_t e_ident[16]; + elf_hlf_t e_type; + elf_hlf_t e_machine; + elf_wrd_t e_version; + elf_ptr_t e_entry; + elf_off_t e_phoff; + elf_off_t e_shoff; + elf_wrd_t e_flags; + elf_hlf_t e_ehsize; + elf_hlf_t e_phentsize; + elf_hlf_t e_phnum; + elf_hlf_t e_shentsize; + elf_hlf_t e_shnum; + elf_hlf_t e_shstrndx; +}; + +struct elf_phdr +{ +#ifdef CONFIG_ARCH_X86_64 + elf_wrd_t p_type; + elf_wrd_t p_flags; + elf_off_t p_offset; + elf_ptr_t p_va; + elf_ptr_t p_pa; + elf_xwrd_t p_filesz; + elf_xwrd_t p_memsz; + elf_xwrd_t p_align; +#else + elf_wrd_t p_type; + elf_off_t p_offset; + elf_ptr_t p_va; + elf_ptr_t p_pa; + elf_wrd_t p_filesz; + elf_wrd_t p_memsz; + elf_wrd_t p_flags; + elf_wrd_t p_align; +#endif +}; + +#endif /* __LUNAIX_ARCH_ELF_H */ diff --git a/lunaix-os/arch/i386/includes/sys/failsafe.h b/lunaix-os/arch/x86/includes/sys/failsafe.h similarity index 73% rename from lunaix-os/arch/i386/includes/sys/failsafe.h rename to lunaix-os/arch/x86/includes/sys/failsafe.h index 149efd2..bf96d35 100644 --- a/lunaix-os/arch/i386/includes/sys/failsafe.h +++ b/lunaix-os/arch/x86/includes/sys/failsafe.h @@ -22,7 +22,22 @@ static inline void must_inline noret failsafe_diagnostic() { // asm ("jmp __fatal_state"); extern int failsafe_stack_top[]; - asm ( +#ifdef CONFIG_ARCH_X86_64 + asm ( + "movq %%rsp, %%rax\n" + "movq %%rbp, %%rbx\n" + + "movq %0, %%rsp\n" + + "pushq %%rax\n" + "pushq %%rbx\n" + + "call do_failsafe_unrecoverable\n" + ::"r"(failsafe_stack_top) + :"memory" + ); +#else + asm ( "movl %%esp, %%eax\n" "movl %%ebp, %%ebx\n" @@ -35,6 +50,7 @@ failsafe_diagnostic() { ::"r"(failsafe_stack_top) :"memory" ); +#endif unreachable; } diff --git a/lunaix-os/arch/i386/includes/sys/gdbstub.h b/lunaix-os/arch/x86/includes/sys/gdbstub.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/gdbstub.h rename to lunaix-os/arch/x86/includes/sys/gdbstub.h diff --git a/lunaix-os/arch/i386/includes/sys/hart.h b/lunaix-os/arch/x86/includes/sys/hart.h similarity index 61% rename from lunaix-os/arch/i386/includes/sys/hart.h rename to lunaix-os/arch/x86/includes/sys/hart.h index 9a6381a..ac3801e 100644 --- a/lunaix-os/arch/i386/includes/sys/hart.h +++ b/lunaix-os/arch/x86/includes/sys/hart.h @@ -7,8 +7,43 @@ #include #include -struct exec_param; +struct hart_state; + +#ifdef CONFIG_ARCH_X86_64 +struct regcontext +{ + reg_t rax; + reg_t rbx; + reg_t rcx; + reg_t rdx; + reg_t rdi; + reg_t rbp; + reg_t rsi; + reg_t r8; + reg_t r9; + reg_t r10; + reg_t r11; + reg_t r12; + reg_t r13; + reg_t r14; + reg_t r15; +} compact; + +struct exec_param +{ + struct hart_state* parent_state; + reg_t vector; + reg_t err_code; + reg_t rip; + reg_t cs; + reg_t rflags; + reg_t rsp; + reg_t ss; +} compact; + + +#else struct regcontext { reg_t eax; @@ -24,17 +59,6 @@ struct regcontext reg_t gs; } compact; -struct hart_state -{ - unsigned int depth; - struct regcontext registers; - union - { - reg_t esp; - volatile struct exec_param* execp; - }; -} compact; - struct exec_param { struct hart_state* parent_state; @@ -47,24 +71,54 @@ struct exec_param reg_t ss; } compact; +#endif + + +struct hart_state +{ + reg_t depth; + struct regcontext registers; + union + { + reg_t sp; + volatile struct exec_param* execp; + }; +} compact; + +static inline int +hart_vector_stamp(struct hart_state* hstate) { + return hstate->execp->vector; +} + +static inline unsigned int +hart_ecause(struct hart_state* hstate) { + return hstate->execp->err_code; +} + +static inline struct hart_state* +hart_parent_state(struct hart_state* hstate) +{ + return hstate->execp->parent_state; +} static inline void -hart_flow_redirect(struct hart_state* hstate, ptr_t pc, ptr_t sp) +hart_push_state(struct hart_state* p_hstate, struct hart_state* hstate) { - hstate->execp->eip = pc; - hstate->execp->esp = sp; + hstate->execp->parent_state = p_hstate; } + +#ifdef CONFIG_ARCH_X86_64 static inline ptr_t hart_pc(struct hart_state* hstate) { - return hstate->execp->eip; + return hstate->execp->rip; } static inline ptr_t hart_sp(struct hart_state* hstate) { - return hstate->execp->esp; + return hstate->execp->rsp; } static inline bool @@ -76,31 +130,36 @@ kernel_context(struct hart_state* hstate) static inline ptr_t hart_stack_frame(struct hart_state* hstate) { - return hstate->registers.ebp; + return hstate->registers.rbp; } -static inline int -hart_vector_stamp(struct hart_state* hstate) { - return hstate->execp->vector; +#else +static inline ptr_t +hart_pc(struct hart_state* hstate) +{ + return hstate->execp->eip; } -static inline unsigned int -hart_ecause(struct hart_state* hstate) { - return hstate->execp->err_code; +static inline ptr_t +hart_sp(struct hart_state* hstate) +{ + return hstate->execp->esp; } -static inline struct hart_state* -hart_parent_state(struct hart_state* hstate) +static inline bool +kernel_context(struct hart_state* hstate) { - return hstate->execp->parent_state; + return !((hstate->execp->cs) & 0b11); } -static inline void -hart_push_state(struct hart_state* p_hstate, struct hart_state* hstate) +static inline ptr_t +hart_stack_frame(struct hart_state* hstate) { - hstate->execp->parent_state = p_hstate; + return hstate->registers.ebp; } #endif +#endif + #endif /* __LUNAIX_ARCH_HART_H */ diff --git a/lunaix-os/arch/x86/includes/sys/int_handler.h b/lunaix-os/arch/x86/includes/sys/int_handler.h new file mode 100644 index 0000000..aac99d9 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/int_handler.h @@ -0,0 +1,10 @@ +#ifndef __LUNAIX_INT_HANDLER_H +#define __LUNAIX_INT_HANDLER_H + +void +exception_install_handler(); + +void +intr_routine_init(); + +#endif /* __LUNAIX_INT_HANDLER_H */ diff --git a/lunaix-os/arch/i386/includes/sys/interrupt.S.inc b/lunaix-os/arch/x86/includes/sys/interrupt32.S.inc similarity index 100% rename from lunaix-os/arch/i386/includes/sys/interrupt.S.inc rename to lunaix-os/arch/x86/includes/sys/interrupt32.S.inc diff --git a/lunaix-os/arch/x86/includes/sys/interrupt64.S.inc b/lunaix-os/arch/x86/includes/sys/interrupt64.S.inc new file mode 100644 index 0000000..befb702 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/interrupt64.S.inc @@ -0,0 +1,107 @@ + +#define regsize 8 +#define regsize32 4 + +/* stack layout: saved interrupt context */ + .struct 0 +idepth: + .struct idepth + regsize +irax: + .struct irax + regsize +irbx: + .struct irbx + regsize +ircx: + .struct ircx + regsize +irdx: + .struct irdx + regsize +irdi: + .struct irdi + regsize +irbp: + .struct irbp + regsize +irsi: + .struct irsi + regsize +ir8: + .struct ir8 + regsize +ir9: + .struct ir9 + regsize +ir10: + .struct ir10 + regsize +ir11: + .struct ir11 + regsize +ir12: + .struct ir12 + regsize +ir13: + .struct ir13 + regsize +ir14: + .struct ir14 + regsize +ir15: + .struct ir15 + regsize +iexecp: +irsp: + .struct irsp + regsize +isave_parent: + .struct isave_parent + regsize +ivec: + .struct ivec + regsize +iecode: + .struct iecode + regsize +irip: + .struct irip + regsize +ics: + .struct ics + regsize +irflags: + .struct irflags + regsize +iursp: + .struct iursp + regsize +iuss: + + +/* stack layout: execution (flow-control) state context */ + .struct 0 +exsave_prev: + .struct exsave_prev + regsize +exvec: + .struct exvec + regsize +execode: + .struct execode + regsize +exrip: + .struct exrip + regsize +excs: + .struct excs + regsize +exrflags: + .struct exrflags + regsize +exursp: + .struct exursp + regsize +exuss: + +/* struct layout: critical section of struct proc_info */ + .struct 0 +thread_hstate: + .struct thread_hstate + regsize +thread_ustack_top: + +/* struct layout: proc_sig */ + .struct 0 +psig_signum: + .struct psig_signum + regsize32 +psig_sigact: + .struct psig_sigact + regsize +psig_sighand: + .struct psig_sighand + regsize +psig_saved_hstate: + +/* struct layout: x86_tss */ + .struct 0 + .struct regsize32 +rsp_0: + .struct rsp_0 + regsize +rsp_1: + .struct rsp_1 + regsize +rsp_2: + .struct rsp_2 + regsize +rsp_3: + .struct rsp_3 + regsize +ist_null: + .struct ist_null + regsize +ist_1: + diff --git a/lunaix-os/arch/i386/includes/sys/ioapic.h b/lunaix-os/arch/x86/includes/sys/ioapic.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/ioapic.h rename to lunaix-os/arch/x86/includes/sys/ioapic.h diff --git a/lunaix-os/arch/i386/includes/sys/mm/memory.h b/lunaix-os/arch/x86/includes/sys/mm/memory.h similarity index 87% rename from lunaix-os/arch/i386/includes/sys/mm/memory.h rename to lunaix-os/arch/x86/includes/sys/mm/memory.h index a89d90a..cf9f91d 100644 --- a/lunaix-os/arch/i386/includes/sys/mm/memory.h +++ b/lunaix-os/arch/x86/includes/sys/mm/memory.h @@ -16,8 +16,8 @@ translate_vmr_prot(unsigned int vmr_prot) _pte_prot |= _PTE_W; } - if ((vmr_prot & PROT_EXEC)) { - _pte_prot |= _PTE_X; + if (!(vmr_prot & PROT_EXEC)) { + _pte_prot |= _PTE_NX; } return _pte_prot; diff --git a/lunaix-os/arch/x86/includes/sys/mm/mempart.h b/lunaix-os/arch/x86/includes/sys/mm/mempart.h new file mode 100644 index 0000000..a3ad646 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/mm/mempart.h @@ -0,0 +1,10 @@ +#ifndef __LUNAIX_MEMPART_H +#define __LUNAIX_MEMPART_H + +#ifdef CONFIG_ARCH_X86_64 +# include "mempart64.h" +#else +# include "mempart32.h" +#endif + +#endif \ No newline at end of file diff --git a/lunaix-os/arch/x86/includes/sys/mm/mempart32.h b/lunaix-os/arch/x86/includes/sys/mm/mempart32.h new file mode 100644 index 0000000..9a81d37 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/mm/mempart32.h @@ -0,0 +1,67 @@ +#ifndef __LUNAIX_MEMPART32_H +#define __LUNAIX_MEMPART32_H + +#define END_POINT(name) (name + name##_SIZE - 1) + +#ifdef __LD__ +#define __ulong(val) val +#else +#define __ulong(val) val##UL +#endif + +#define KSTACK_AREA __ulong(0x100000) +#define KSTACK_AREA_SIZE __ulong(0x300000) +#define KSTACK_AREA_END END_POINT(KSTACK_AREA) + +#define USR_EXEC __ulong(0x400000) +#define USR_EXEC_SIZE __ulong(0x20000000) +#define USR_EXEC_END END_POINT(USR_EXEC) + +#define USR_MMAP __ulong(0x20400000) +#define USR_MMAP_SIZE __ulong(0x9fbc0000) +#define USR_MMAP_END END_POINT(USR_MMAP) + +#define USR_STACK __ulong(0xbffc0000) +#define USR_STACK_SIZE __ulong(0x40000) +#define USR_STACK_SIZE_THREAD __ulong(0x40000) +#define USR_STACK_END END_POINT(USR_STACK) + +#define KERNEL_RESIDENT __ulong(0xc0000000) + +#define KERNEL_IMG KERNEL_RESIDENT +#define KERNEL_IMG_SIZE __ulong(0x4000000) +#define KERNEL_IMG_END END_POINT(KERNEL_IMG) + +#define PG_MOUNT_1 __ulong(0xc4000000) +#define PG_MOUNT_1_SIZE __ulong(0x1000) +#define PG_MOUNT_1_END END_POINT(PG_MOUNT_1) + +#define PG_MOUNT_2 __ulong(0xc4001000) +#define PG_MOUNT_2_SIZE __ulong(0x1000) +#define PG_MOUNT_2_END END_POINT(PG_MOUNT_2) + +#define PG_MOUNT_3 __ulong(0xc4002000) +#define PG_MOUNT_3_SIZE __ulong(0x1000) +#define PG_MOUNT_3_END END_POINT(PG_MOUNT_3) + +#define PG_MOUNT_4 __ulong(0xc4003000) +#define PG_MOUNT_4_SIZE __ulong(0x1000) +#define PG_MOUNT_4_END END_POINT(PG_MOUNT_4) + +#define PG_MOUNT_VAR __ulong(0xc4004000) +#define PG_MOUNT_VAR_SIZE __ulong(0x3fc000) +#define PG_MOUNT_VAR_END END_POINT(PG_MOUNT_VAR) + +#define VMAP __ulong(0xc4400000) +#define VMAP_SIZE __ulong(0x3b400000) +#define VMAP_END END_POINT(VMAP) + +#define PMAP VMAP + +#define VMS_MOUNT_1 __ulong(0xff800000) +#define VMS_MOUNT_1_SIZE __ulong(0x400000) +#define VMS_MOUNT_1_END END_POINT(VMS_MOUNT_1) + +#define VMS_SELF_MOUNT __ulong(0xffc00000) + +#endif /* __LUNAIX_MEMPART32_H */ diff --git a/lunaix-os/arch/x86/includes/sys/mm/mempart64.h b/lunaix-os/arch/x86/includes/sys/mm/mempart64.h new file mode 100644 index 0000000..9d3559a --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/mm/mempart64.h @@ -0,0 +1,71 @@ +#ifndef __LUNAIX_MEMPART64_H +#define __LUNAIX_MEMPART64_H + +#define END_POINT(name) (name + name##_SIZE - 1) + +#ifdef __LD__ +#define __ulong(val) val +#else +#define __ulong(val) val##UL +#endif + +#define KSTACK_AREA __ulong(0x0000000100000000) +#define KSTACK_AREA_SIZE __ulong(0x0000000040000000) +#define KSTACK_AREA_END END_POINT(KSTACK_AREA) + +#define USR_EXEC __ulong(0x0000008000000000) +#define USR_EXEC_SIZE __ulong(0x0000002000000000) +#define USR_EXEC_END END_POINT(USR_EXEC) + +#define USR_MMAP __ulong(0x0000010000000000) +#define USR_MMAP_SIZE __ulong(0x0000008000000000) +#define USR_MMAP_END END_POINT(USR_MMAP) + +#define USR_STACK __ulong(0x00007f8000000000) +#define USR_STACK_SIZE __ulong(0x0000001fc0000000) +#define USR_STACK_SIZE_THREAD __ulong(0x0000000000200000) +#define USR_STACK_END END_POINT(USR_STACK) + + +// la casa del kernel + +#define KERNEL_RESIDENT __ulong(0xfffffd8000000000) // -2.5T +#define VMAP KERNEL_RESIDENT // -2.5T +#define VMAP_SIZE __ulong(0x0000010000000000) +#define VMAP_END END_POINT(VMAP) + +#define VMS_MOUNT_1 __ulong(0xfffffe8000000000) // -1.5T +#define VMS_MOUNT_1_SIZE __ulong(0x0000008000000000) +#define VMS_MOUNT_1_END END_POINT(VMS_MOUNT_1) + +#define VMS_SELF_MOUNT __ulong(0xffffff0000000000) // -1T + +#define KMAP __ulong(0xffffff8000000000) +#define PG_MOUNT_1 KMAP // -512G +#define PG_MOUNT_1_SIZE __ulong(0x0000000000001000) +#define PG_MOUNT_1_END END_POINT(PG_MOUNT_1) + +#define PG_MOUNT_2 __ulong(0xffffff8000001000) +#define PG_MOUNT_2_SIZE __ulong(0x0000000000001000) +#define PG_MOUNT_2_END END_POINT(PG_MOUNT_2) + +#define PG_MOUNT_3 __ulong(0xffffff8000002000) +#define PG_MOUNT_3_SIZE __ulong(0x0000000000001000) +#define PG_MOUNT_3_END END_POINT(PG_MOUNT_3) + +#define PG_MOUNT_4 __ulong(0xffffff8000003000) +#define PG_MOUNT_4_SIZE __ulong(0x0000000000001000) +#define PG_MOUNT_4_END END_POINT(PG_MOUNT_4) + +#define PG_MOUNT_VAR __ulong(0xffffff8000004000) +#define PG_MOUNT_VAR_SIZE __ulong(0x000000003fffc000) +#define PG_MOUNT_VAR_END END_POINT(PG_MOUNT_VAR) + +#define PMAP __ulong(0xffffff8040000000) + +#define KERNEL_IMG __ulong(0xffffffff80000000) // -2G +#define KERNEL_IMG_SIZE __ulong(0x0000000080000000) +#define KERNEL_IMG_END END_POINT(KERNEL_IMG) + + +#endif \ No newline at end of file diff --git a/lunaix-os/arch/i386/includes/sys/mm/mm_defs.h b/lunaix-os/arch/x86/includes/sys/mm/mm_defs.h similarity index 56% rename from lunaix-os/arch/i386/includes/sys/mm/mm_defs.h rename to lunaix-os/arch/x86/includes/sys/mm/mm_defs.h index 5c098d3..40804d0 100644 --- a/lunaix-os/arch/i386/includes/sys/mm/mm_defs.h +++ b/lunaix-os/arch/x86/includes/sys/mm/mm_defs.h @@ -1,13 +1,9 @@ #ifndef __LUNAIX_MM_DEFS_H #define __LUNAIX_MM_DEFS_H - #include "mempart.h" #include "pagetable.h" -#define KSTACK_PAGES 3 -#define KSTACK_SIZE (KSTACK_PAGES * MEM_PAGE) - /* Regardless architecture we need to draw the line very carefully, and must take the size of VM into account. In general, we aims to achieve @@ -19,16 +15,26 @@ In light of upcomming x86_64 support (for Level 4&5 Paging): * #510 entry of PML4 (0x0000ff0000000000, ~512GiB) * #510 entry of PML5 (0x01fe000000000000, ~256TiB) + + + KERNEL_RESIDENT - a high-mem region, kernel should be + KSTACK_PAGES - kernel stack, pages allocated to + KEXEC_RSVD - page reserved for kernel images */ -// Where the kernel getting re-mapped. -#define KERNEL_RESIDENT 0xc0000000UL -// Pages reserved for kernel image -#define KEXEC_RSVD 16 +#ifdef CONFIG_ARCH_X86_64 +# define KSTACK_PAGES 4 +# define KEXEC_RSVD 32 +#else +# define KSTACK_PAGES 2 +# define KEXEC_RSVD 16 +#endif + +#define KSTACK_SIZE (KSTACK_PAGES * PAGE_SIZE) #define kernel_addr(addr) ((addr) >= KERNEL_RESIDENT || (addr) < USR_EXEC) -#define to_kphysical(k_va) ((ptr_t)(k_va) - KERNEL_RESIDENT) -#define to_kvirtual(k_pa) ((ptr_t)(k_pa) - KERNEL_RESIDENT) +#define to_kphysical(k_va) ((ptr_t)(k_va) - KERNEL_IMG) +#define to_kvirtual(k_pa) ((ptr_t)(k_pa) + KERNEL_IMG) #endif /* __LUNAIX_MM_DEFS_H */ diff --git a/lunaix-os/arch/i386/includes/sys/mm/pagetable.h b/lunaix-os/arch/x86/includes/sys/mm/pagetable.h similarity index 71% rename from lunaix-os/arch/i386/includes/sys/mm/pagetable.h rename to lunaix-os/arch/x86/includes/sys/mm/pagetable.h index 2b188cd..3888124 100644 --- a/lunaix-os/arch/i386/includes/sys/mm/pagetable.h +++ b/lunaix-os/arch/x86/includes/sys/mm/pagetable.h @@ -6,31 +6,15 @@ /* ******** Page Table Manipulation ******** */ -// Levels of page table to traverse for a single page walk -#define _PTW_LEVEL 2 +#ifdef CONFIG_ARCH_X86_64 -#define _PAGE_BASE_SHIFT 12 -#define _PAGE_BASE_SIZE ( 1UL << _PAGE_BASE_SHIFT ) -#define _PAGE_BASE_MASK ( _PAGE_BASE_SIZE - 1) +#include "pt_def64.h" -#define _PAGE_LEVEL_SHIFT 10 -#define _PAGE_LEVEL_SIZE ( 1UL << _PAGE_LEVEL_SHIFT ) -#define _PAGE_LEVEL_MASK ( _PAGE_LEVEL_SIZE - 1 ) -#define _PAGE_Ln_SIZE(n) ( 1UL << (_PAGE_BASE_SHIFT + _PAGE_LEVEL_SHIFT * (_PTW_LEVEL - (n) - 1)) ) +#else -// Note: we set VMS_SIZE = VMS_MASK as it is impossible -// to express 4Gi in 32bit unsigned integer +#include "pt_def32.h" -#define VMS_MASK ( -1UL ) -#define VMS_SIZE VMS_MASK - -/* General size of a LnT huge page */ - -#define L0T_SIZE _PAGE_Ln_SIZE(0) -#define L1T_SIZE _PAGE_Ln_SIZE(1) -#define L2T_SIZE _PAGE_Ln_SIZE(1) -#define L3T_SIZE _PAGE_Ln_SIZE(1) -#define LFT_SIZE _PAGE_Ln_SIZE(1) +#endif /* General mask to get page offset of a LnT huge page */ @@ -62,49 +46,28 @@ // max translation level supported #define MAX_LEVEL _PTW_LEVEL - -/* ******** PTE Manipulation ******** */ - -struct __pte { - unsigned int val; -} align(4); - #ifndef pte_t typedef struct __pte pte_t; #endif -typedef unsigned int pfn_t; -typedef unsigned int pte_attr_t; - -#define _PTE_P (1 << 0) -#define _PTE_W (1 << 1) -#define _PTE_U (1 << 2) -#define _PTE_WT (1 << 3) -#define _PTE_CD (1 << 4) -#define _PTE_A (1 << 5) -#define _PTE_D (1 << 6) -#define _PTE_PS (1 << 7) -#define _PTE_PAT (1 << 7) -#define _PTE_G (1 << 8) -#define _PTE_X (0) -#define _PTE_R (0) #define _PTE_PROT_MASK ( _PTE_W | _PTE_U | _PTE_X ) #define KERNEL_PAGE ( _PTE_P ) #define KERNEL_EXEC ( KERNEL_PAGE | _PTE_X ) -#define KERNEL_DATA ( KERNEL_PAGE | _PTE_W ) -#define KERNEL_RDONLY ( KERNEL_PAGE ) +#define KERNEL_DATA ( KERNEL_PAGE | _PTE_W | _PTE_NX ) +#define KERNEL_RDONLY ( KERNEL_PAGE | _PTE_NX ) +#define KERNEL_ROEXEC ( KERNEL_PAGE | _PTE_X ) #define USER_PAGE ( _PTE_P | _PTE_U ) #define USER_EXEC ( USER_PAGE | _PTE_X ) -#define USER_DATA ( USER_PAGE | _PTE_W ) -#define USER_RDONLY ( USER_PAGE ) +#define USER_DATA ( USER_PAGE | _PTE_W | _PTE_NX ) +#define USER_RDONLY ( USER_PAGE | _PTE_NX ) +#define USER_ROEXEC ( USER_PAGE | _PTE_X ) #define SELF_MAP ( KERNEL_DATA | _PTE_WT | _PTE_CD ) #define __mkpte_from(pte_val) ((pte_t){ .val = (pte_val) }) -#define __MEMGUARD 0xdeadc0deUL #define null_pte ( __mkpte_from(0) ) #define guard_pte ( __mkpte_from(__MEMGUARD) ) @@ -159,13 +122,13 @@ pte_setppfn(pte_t pte, pfn_t ppfn) static inline ptr_t pte_paddr(pte_t pte) { - return pte.val & ~_PAGE_BASE_MASK; + return __paddr(pte.val) & ~_PAGE_BASE_MASK; } static inline pfn_t pte_ppfn(pte_t pte) { - return pte.val >> _PAGE_BASE_SHIFT; + return __paddr(pte.val) >> _PAGE_BASE_SHIFT; } static inline pte_t @@ -273,19 +236,19 @@ pte_allow_user(pte_t pte) static inline pte_t pte_mkexec(pte_t pte) { - return __mkpte_from(pte.val | _PTE_X); + return __mkpte_from(pte.val & ~_PTE_NX); } static inline pte_t pte_mknonexec(pte_t pte) { - return __mkpte_from(pte.val & ~_PTE_X); + return __mkpte_from(pte.val | _PTE_NX); } static inline bool pte_isexec(pte_t pte) { - return !!(pte.val & _PTE_X); + return !(pte.val & _PTE_NX); } static inline pte_t diff --git a/lunaix-os/arch/i386/includes/sys/mm/physical.h b/lunaix-os/arch/x86/includes/sys/mm/physical.h similarity index 83% rename from lunaix-os/arch/i386/includes/sys/mm/physical.h rename to lunaix-os/arch/x86/includes/sys/mm/physical.h index a6528bd..722aa15 100644 --- a/lunaix-os/arch/i386/includes/sys/mm/physical.h +++ b/lunaix-os/arch/x86/includes/sys/mm/physical.h @@ -4,7 +4,7 @@ #include #include "mm_defs.h" -#define PPLIST_STARTVM VMAP +#define PPLIST_STARTVM PMAP struct ppage_arch { diff --git a/lunaix-os/arch/x86/includes/sys/mm/pt_def32.h b/lunaix-os/arch/x86/includes/sys/mm/pt_def32.h new file mode 100644 index 0000000..b8d30d4 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/mm/pt_def32.h @@ -0,0 +1,64 @@ +#ifdef CONFIG_ARCH_I386 + +#ifndef __LUNAIX_PT_DEF32_H +#define __LUNAIX_PT_DEF32_H + +#define _PTW_LEVEL 2 + +#define _PAGE_BASE_SHIFT 12 +#define _PAGE_BASE_SIZE ( 1UL << _PAGE_BASE_SHIFT ) +#define _PAGE_BASE_MASK ( _PAGE_BASE_SIZE - 1) + +#define _PAGE_LEVEL_SHIFT 10 +#define _PAGE_LEVEL_SIZE ( 1UL << _PAGE_LEVEL_SHIFT ) +#define _PAGE_LEVEL_MASK ( _PAGE_LEVEL_SIZE - 1 ) +#define _PAGE_Ln_SIZE(n) ( 1UL << (_PAGE_BASE_SHIFT + _PAGE_LEVEL_SHIFT * (_PTW_LEVEL - (n) - 1)) ) + +// Note: we set VMS_SIZE = VMS_MASK as it is impossible +// to express 4Gi in 32bit unsigned integer + +#define VMS_BITS 32 +#define PMS_BITS 32 + +#define VMS_SIZE ( -1UL ) +#define VMS_MASK ( -1UL ) +#define PMS_SIZE ( -1UL ) +#define PMS_MASK ( -1UL ) + +/* General size of a LnT huge page */ + +#define L0T_SIZE _PAGE_Ln_SIZE(0) +#define L1T_SIZE _PAGE_Ln_SIZE(1) +#define L2T_SIZE _PAGE_Ln_SIZE(1) +#define L3T_SIZE _PAGE_Ln_SIZE(1) +#define LFT_SIZE _PAGE_Ln_SIZE(1) + +struct __pte { + unsigned int val; +} align(4); + +#define _PTE_P (1 << 0) +#define _PTE_W (1 << 1) +#define _PTE_U (1 << 2) +#define _PTE_WT (1 << 3) +#define _PTE_CD (1 << 4) +#define _PTE_A (1 << 5) +#define _PTE_D (1 << 6) +#define _PTE_PS (1 << 7) +#define _PTE_PAT (1 << 7) +#define _PTE_G (1 << 8) +#define _PTE_X (0) +#define _PTE_NX (0) +#define _PTE_R (0) + +#define __MEMGUARD 0xdeadc0deUL + +typedef unsigned int pte_attr_t; +typedef unsigned int pfn_t; + +#define __index(va) ( (va) & VMS_MASK ) +#define __vaddr(va) ( va ) +#define __paddr(pa) ( (pa) & PMS_MASK ) + +#endif /* __LUNAIX_PT_DEF32_H */ +#endif diff --git a/lunaix-os/arch/x86/includes/sys/mm/pt_def64.h b/lunaix-os/arch/x86/includes/sys/mm/pt_def64.h new file mode 100644 index 0000000..13eafa3 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/mm/pt_def64.h @@ -0,0 +1,71 @@ +#ifdef CONFIG_ARCH_X86_64 +#ifndef __LUNAIX_PT_DEF64_H +#define __LUNAIX_PT_DEF64_H + +#include + +#define _PTW_LEVEL 4 + + +// Note: we set VMS_SIZE = VMS_MASK as it is impossible +// to express 4Gi in 32bit unsigned integer + +#define _PAGE_BASE_SHIFT 12 + +#define _PAGE_BASE_SIZE ( 1UL << _PAGE_BASE_SHIFT ) +#define _PAGE_BASE_MASK ( (_PAGE_BASE_SIZE - 1) & 0x000fffffffffffffUL ) + +#define _PAGE_LEVEL_SHIFT 9 +#define _PAGE_LEVEL_SIZE ( 1UL << _PAGE_LEVEL_SHIFT ) +#define _PAGE_LEVEL_MASK ( _PAGE_LEVEL_SIZE - 1 ) +#define _PAGE_Ln_SIZE(n) ( 1UL << (_PAGE_BASE_SHIFT + _PAGE_LEVEL_SHIFT * (_PTW_LEVEL - (n) - 1)) ) + +#define VMS_BITS 48 +#define PMS_BITS 52 + +#define VMS_SIZE ( 1UL << VMS_BITS) +#define VMS_MASK ( VMS_SIZE - 1 ) +#define PMS_SIZE ( 1UL << PMS_BITS ) +#define PMS_MASK ( PMS_SIZE - 1 ) + +/* General size of a LnT huge page */ + +#define L0T_SIZE _PAGE_Ln_SIZE(0) +#define L1T_SIZE _PAGE_Ln_SIZE(1) +#define L2T_SIZE _PAGE_Ln_SIZE(2) +#define L3T_SIZE _PAGE_Ln_SIZE(3) +#define LFT_SIZE _PAGE_Ln_SIZE(3) + + +struct __pte { + unsigned long val; +} align(8); + +#define _PTE_P (1UL << 0) +#define _PTE_W (1UL << 1) +#define _PTE_U (1UL << 2) +#define _PTE_WT (1UL << 3) +#define _PTE_CD (1UL << 4) +#define _PTE_A (1UL << 5) +#define _PTE_D (1UL << 6) +#define _PTE_PS (1UL << 7) +#define _PTE_PAT (1UL << 7) +#define _PTE_G (1UL << 8) +#define _PTE_NX (1UL << 63) +#define _PTE_X (0) +#define _PTE_R (0) + +#define __MEMGUARD 0xf0f0f0f0f0f0f0f0UL + +typedef unsigned long pte_attr_t; +typedef unsigned long pfn_t; + +// always do sign extend on x86_64 + +#define __index(va) ( (va) & VMS_MASK ) +#define __vaddr(va) \ + ( (__index(va) ^ ((VMS_MASK + 1) >> 1)) - ((VMS_MASK + 1) >> 1) ) +#define __paddr(pa) ( (pa) & PMS_MASK ) + +#endif /* __LUNAIX_PT_DEF64_H */ +#endif \ No newline at end of file diff --git a/lunaix-os/arch/i386/includes/sys/mm/tlb.h b/lunaix-os/arch/x86/includes/sys/mm/tlb.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/mm/tlb.h rename to lunaix-os/arch/x86/includes/sys/mm/tlb.h diff --git a/lunaix-os/arch/i386/includes/sys/muldiv64.h b/lunaix-os/arch/x86/includes/sys/muldiv64.h similarity index 91% rename from lunaix-os/arch/i386/includes/sys/muldiv64.h rename to lunaix-os/arch/x86/includes/sys/muldiv64.h index 940f7de..5905990 100644 --- a/lunaix-os/arch/i386/includes/sys/muldiv64.h +++ b/lunaix-os/arch/x86/includes/sys/muldiv64.h @@ -4,6 +4,7 @@ #include #include +#ifdef CONFIG_ARCH_I386 #define do_udiv64(n, base) \ ({ \ unsigned long __upper, __low, __high, __mod, __base; \ @@ -25,6 +26,13 @@ } \ __mod; \ }) +#else + #define do_udiv64(n, base) \ + ({ \ + n = (n) / (base); \ + (n) % (base); \ + }) +#endif static inline u64_t udiv64(u64_t n, unsigned int base) diff --git a/lunaix-os/arch/i386/includes/sys/pci_hba.h b/lunaix-os/arch/x86/includes/sys/pci_hba.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/pci_hba.h rename to lunaix-os/arch/x86/includes/sys/pci_hba.h diff --git a/lunaix-os/arch/i386/includes/sys/port_io.h b/lunaix-os/arch/x86/includes/sys/port_io.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/port_io.h rename to lunaix-os/arch/x86/includes/sys/port_io.h diff --git a/lunaix-os/arch/x86/includes/sys/syscall_utils.h b/lunaix-os/arch/x86/includes/sys/syscall_utils.h new file mode 100644 index 0000000..551626d --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/syscall_utils.h @@ -0,0 +1,17 @@ +#ifndef __LUNAIX_ARCH_SYSCALL_UTILS_H +#define __LUNAIX_ARCH_SYSCALL_UTILS_H + +#include +#include + +static inline void +convert_valist(va_list* ap_ref, sc_va_list syscall_ap) +{ +#ifdef CONFIG_ARCH_X86_64 + memcpy(ap_ref, syscall_ap, sizeof(va_list)); +#else + *ap_ref = *syscall_ap; +#endif +} + +#endif /* __LUNAIX_ARCH_SYSCALL_UTILS_H */ diff --git a/lunaix-os/arch/i386/includes/sys/trace.h b/lunaix-os/arch/x86/includes/sys/trace.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/trace.h rename to lunaix-os/arch/x86/includes/sys/trace.h diff --git a/lunaix-os/arch/i386/includes/sys/vectors.h b/lunaix-os/arch/x86/includes/sys/vectors.h similarity index 100% rename from lunaix-os/arch/i386/includes/sys/vectors.h rename to lunaix-os/arch/x86/includes/sys/vectors.h diff --git a/lunaix-os/arch/x86/includes/sys/x86_isa.h b/lunaix-os/arch/x86/includes/sys/x86_isa.h new file mode 100644 index 0000000..34510c8 --- /dev/null +++ b/lunaix-os/arch/x86/includes/sys/x86_isa.h @@ -0,0 +1,96 @@ +#ifndef __LUNAIX_I386_ASM_H +#define __LUNAIX_I386_ASM_H + +#define SEL_INDEX(i) ((i) << 3) +#define SEL_RPL(rpl) ((rpl) & 0b11) + +#define KCODE_SEG (SEL_INDEX(1) | SEL_RPL(0)) +#define UCODE_SEG (SEL_INDEX(2) | SEL_RPL(3)) + +#ifdef CONFIG_ARCH_I386 + #define KDATA_SEG (SEL_INDEX(3) | SEL_RPL(0)) + #define UDATA_SEG (SEL_INDEX(4) | SEL_RPL(3)) + #define TSS_SEG (SEL_INDEX(5) | SEL_RPL(0)) + +#else + #define KDATA_SEG (SEL_INDEX(3) | SEL_RPL(0)) + #define UDATA_SEG (SEL_INDEX(4) | SEL_RPL(3)) + #define TSS_SEG (SEL_INDEX(5) | SEL_RPL(0)) + +#endif + + +#ifndef __ASM__ +#include + +#define IRQ_TRIG_EDGE 0b0 +#define IRQ_TRIG_LEVEL 0b1 + +#define IRQ_TYPE_FIXED (0b01 << 1) +#define IRQ_TYPE_NMI (0b11 << 1) +#define IRQ_TYPE (0b11 << 1) + +#define IRQ_VE_HI (0b1 << 3) +#define IRQ_VE_LO (0b0 << 3) + +#define IRQ_DEFAULT (IRQ_TRIG_EDGE | IRQ_TYPE_FIXED | IRQ_VE_HI) + + +struct x86_intc +{ + char* name; + void* data; + + void (*irq_attach)(struct x86_intc*, + int irq, + int iv, + cpu_t dest, + u32_t flags); + void (*notify_eoi)(struct x86_intc*, cpu_t id, int iv); +}; + +#ifdef CONFIG_ARCH_X86_64 + +struct x86_tss +{ + u32_t rsvd_1; + ptr_t rsps[3]; + union { + struct { + ptr_t ist_null; + ptr_t valid_ists[7]; + }; + ptr_t ists[8]; + }; + + u8_t rsvd_3[12]; +} compact; + +struct x86_sysdesc { + u64_t lo; + u64_t hi; +} compact; + +typedef u64_t x86_segdesc_t; + +#else + +struct x86_tss +{ + u32_t link; + u32_t esp0; + u16_t ss0; + u8_t __padding[94]; +} compact; + +struct x86_sysdesc { + u32_t lo; + u32_t hi; +} compact; + +typedef struct x86_sysdesc x86_segdesc_t; + +#endif + +#endif +#endif /* __LUNAIX_I386_ASM_H */ diff --git a/lunaix-os/arch/i386/klib/fast_crc.c b/lunaix-os/arch/x86/klib/fast_crc.c similarity index 100% rename from lunaix-os/arch/i386/klib/fast_crc.c rename to lunaix-os/arch/x86/klib/fast_crc.c diff --git a/lunaix-os/arch/x86/klib/fast_str.c b/lunaix-os/arch/x86/klib/fast_str.c new file mode 100644 index 0000000..2704178 --- /dev/null +++ b/lunaix-os/arch/x86/klib/fast_str.c @@ -0,0 +1,56 @@ +#include + +#ifdef CONFIG_ARCH_X86_64 +void* +memcpy(void* dest, const void* src, unsigned long num) +{ + if (!num) + return dest; + + asm volatile("movq %1, %%rdi\n" + "rep movsb\n" ::"S"(src), + "r"(dest), + "c"(num) + : "rdi", "memory"); + return dest; +} + +void* +memset(void* ptr, int value, unsigned long num) +{ + asm volatile("movq %1, %%rdi\n" + "rep stosb\n" ::"c"(num), + "r"(ptr), + "a"(value) + : "rdi", "memory"); + return ptr; +} + +#else +void* +memcpy(void* dest, const void* src, unsigned long num) +{ + if (!num) + return dest; + + asm volatile("movl %1, %%edi\n" + "rep movsb\n" ::"S"(src), + "r"(dest), + "c"(num) + : "edi", "memory"); + return dest; +} + +void* +memset(void* ptr, int value, unsigned long num) +{ + asm volatile("movl %1, %%edi\n" + "rep stosb\n" ::"c"(num), + "r"(ptr), + "a"(value) + : "edi", "memory"); + return ptr; +} + +#endif + diff --git a/lunaix-os/arch/i386/mm/fault.c b/lunaix-os/arch/x86/mm/fault.c similarity index 91% rename from lunaix-os/arch/i386/mm/fault.c rename to lunaix-os/arch/x86/mm/fault.c index 4177147..62f9d80 100644 --- a/lunaix-os/arch/i386/mm/fault.c +++ b/lunaix-os/arch/x86/mm/fault.c @@ -17,7 +17,7 @@ __arch_prepare_fault_context(struct fault_context* fault) fault->fault_ptep = mkptep_va(VMS_SELF, ptr); fault->fault_data = ictx->execp->err_code; - fault->fault_instn = ictx->execp->eip; + fault->fault_instn = hart_pc(ictx); fault->fault_va = ptr; return true; diff --git a/lunaix-os/arch/i386/mm/gdt.c b/lunaix-os/arch/x86/mm/gdt.c similarity index 56% rename from lunaix-os/arch/i386/mm/gdt.c rename to lunaix-os/arch/x86/mm/gdt.c index 10b6bec..d1b3627 100644 --- a/lunaix-os/arch/i386/mm/gdt.c +++ b/lunaix-os/arch/x86/mm/gdt.c @@ -33,47 +33,106 @@ #define SEG_CODE_EXRDC 0x0E // Execute/Read, conforming #define SEG_CODE_EXRDCA 0x0F // Execute/Read, conforming, accessed +#define BIT64 (SD_64BITS(1) | SD_32BITS(0)) +#define BIT32 (SD_64BITS(0) | SD_32BITS(1)) + #define SEG_R0_CODE \ SD_TYPE(SEG_CODE_EXRD) | SD_CODE_DATA(1) | SD_DPL(0) | SD_PRESENT(1) | \ - SD_AVL(0) | SD_64BITS(0) | SD_32BITS(1) | SD_4K_GRAN(1) + SD_AVL(0) | SD_4K_GRAN(1) #define SEG_R0_DATA \ SD_TYPE(SEG_DATA_RDWR) | SD_CODE_DATA(1) | SD_DPL(0) | SD_PRESENT(1) | \ - SD_AVL(0) | SD_64BITS(0) | SD_32BITS(1) | SD_4K_GRAN(1) + SD_AVL(0) | SD_4K_GRAN(1) #define SEG_R3_CODE \ SD_TYPE(SEG_CODE_EXRD) | SD_CODE_DATA(1) | SD_DPL(3) | SD_PRESENT(1) | \ - SD_AVL(0) | SD_64BITS(0) | SD_32BITS(1) | SD_4K_GRAN(1) + SD_AVL(0) | SD_4K_GRAN(1) #define SEG_R3_DATA \ SD_TYPE(SEG_DATA_RDWR) | SD_CODE_DATA(1) | SD_DPL(3) | SD_PRESENT(1) | \ - SD_AVL(0) | SD_64BITS(0) | SD_32BITS(1) | SD_4K_GRAN(1) + SD_AVL(0) | SD_4K_GRAN(1) #define SEG_TSS SD_TYPE(9) | SD_DPL(0) | SD_PRESENT(1) -#define GDT_ENTRY 6 -u64_t _gdt[GDT_ENTRY]; +#ifdef CONFIG_ARCH_X86_64 + +x86_segdesc_t _gdt[8]; u16_t _gdt_limit = sizeof(_gdt) - 1; -void -_set_gdt_entry(u32_t index, u32_t base, u32_t limit, u32_t flags) +static inline void +_set_gdt_entry(int index, int dpl, unsigned int flags) { - _gdt[index] = + u64_t desc = 0; + desc |= flags | BIT64; + desc |= 0xf << 16; + desc = desc << 32; + desc |= 0xffff; + + _gdt[index] = desc; +} + +static inline void +_set_tss(int index, ptr_t base, size_t size) +{ + struct x86_sysdesc* tssd; + u32_t attr; + + size = size - 1; + tssd = (struct x86_sysdesc*)&_gdt[index]; + tssd->hi = base >> 32; + + attr = SD_4K_GRAN(1) | SD_PRESENT(1) | SD_TYPE(9) | SD_DPL(0); + attr |= (SEG_LIM_H(size) << 16); + + tssd->lo = (SEG_BASE_H(base) | attr | SEG_BASE_M(base)) << 32; + tssd->lo |= SEG_BASE_L(base) | SEG_LIM_L(size); +} + +#else + +x86_segdesc_t _gdt[6]; +u16_t _gdt_limit = sizeof(_gdt) - 1; + +static inline void +_set_gdt_entry(u32_t index, ptr_t base, u32_t limit, u32_t flags) +{ + x86_segdesc_t* gdte = &_gdt[index]; + + flags |= BIT32; + + gdte->hi = SEG_BASE_H(base) | flags | SEG_LIM_H(limit) | SEG_BASE_M(base); - _gdt[index] <<= 32; - _gdt[index] |= SEG_BASE_L(base) | SEG_LIM_L(limit); + gdte->lo |= SEG_BASE_L(base) | SEG_LIM_L(limit); } -extern struct x86_tss _tss; +static inline void +_set_tss(int index, ptr_t base, size_t size) +{ + _set_gdt_entry(index, base, size - 1, SEG_TSS); +} + +#endif + void _init_gdt() { + extern struct x86_tss _tss; + +#ifdef CONFIG_ARCH_X86_64 + _gdt[0] = 0; + _set_gdt_entry(1, 0, SEG_R0_CODE); // kernel code + _set_gdt_entry(2, 3, SEG_R3_CODE); // user code + _set_gdt_entry(3, 0, SEG_R0_DATA); // generic data + _set_gdt_entry(4, 4, SEG_R3_DATA); // generic data + _set_tss(5, (ptr_t)&_tss, sizeof(_tss)); +#else _set_gdt_entry(0, 0, 0, 0); _set_gdt_entry(1, 0, 0xfffff, SEG_R0_CODE); - _set_gdt_entry(2, 0, 0xfffff, SEG_R0_DATA); - _set_gdt_entry(3, 0, 0xfffff, SEG_R3_CODE); + _set_gdt_entry(2, 0, 0xfffff, SEG_R3_CODE); + _set_gdt_entry(3, 0, 0xfffff, SEG_R0_DATA); _set_gdt_entry(4, 0, 0xfffff, SEG_R3_DATA); - _set_gdt_entry(5, (u32_t)&_tss, sizeof(struct x86_tss) - 1, SEG_TSS); + _set_tss(5, (u32_t)&_tss, sizeof(struct x86_tss) - 1); +#endif } \ No newline at end of file diff --git a/lunaix-os/arch/i386/mm/pmm.c b/lunaix-os/arch/x86/mm/pmm.c similarity index 73% rename from lunaix-os/arch/i386/mm/pmm.c rename to lunaix-os/arch/x86/mm/pmm.c index 45b7e4e..aa35b86 100644 --- a/lunaix-os/arch/i386/mm/pmm.c +++ b/lunaix-os/arch/x86/mm/pmm.c @@ -31,7 +31,11 @@ found:; ptr_t kexec_end = to_kphysical(__kexec_end); ptr_t aligned_pplist = MAX(ent->start, kexec_end); +#ifdef CONFIG_ARCH_X86_64 + aligned_pplist = napot_upaligned(aligned_pplist, L2T_SIZE); +#else aligned_pplist = napot_upaligned(aligned_pplist, L0T_SIZE); +#endif if (aligned_pplist + pool_size > ent->start + ent->size) { return 0; @@ -44,18 +48,28 @@ found:; // regardless the actual physical memory size // anchor the pplist at vmap location (right after kernel) - memory->pplist = (struct ppage*)VMAP; + memory->pplist = (struct ppage*)PMAP; memory->list_len = ppfn_total; - pfn_t nhuge = page_count(pool_size, L0T_SIZE); - pte_t* ptep = mkl0tep_va(VMS_SELF, VMAP); + pfn_t nhuge; + pte_t* ptep; + pte_t pte = mkpte(aligned_pplist, KERNEL_DATA); - - vmm_set_ptes_contig(ptep, pte_mkhuge(pte), L0T_SIZE, nhuge); - tlb_flush_kernel(VMAP); - // shift the actual vmap start address +#ifdef CONFIG_ARCH_X86_64 + nhuge = page_count(pool_size, L2T_SIZE); + ptep = mkl2tep_va(VMS_SELF, PMAP); + vmm_set_ptes_contig(ptep, pte_mkhuge(pte), L2T_SIZE, nhuge); +#else + nhuge = page_count(pool_size, L0T_SIZE); + ptep = mkl0tep_va(VMS_SELF, PMAP); + + // since VMAP and PMAP share same address space + // we need to shift VMAP to make room vmap_set_start(VMAP + nhuge * L0T_SIZE); + vmm_set_ptes_contig(ptep, pte_mkhuge(pte), L0T_SIZE, nhuge); +#endif + tlb_flush_kernel(PMAP); return aligned_pplist; } \ No newline at end of file diff --git a/lunaix-os/arch/i386/mm/tlb.c b/lunaix-os/arch/x86/mm/tlb.c similarity index 100% rename from lunaix-os/arch/i386/mm/tlb.c rename to lunaix-os/arch/x86/mm/tlb.c diff --git a/lunaix-os/arch/i386/mm/vmutils.c b/lunaix-os/arch/x86/mm/vmutils.c similarity index 72% rename from lunaix-os/arch/i386/mm/vmutils.c rename to lunaix-os/arch/x86/mm/vmutils.c index c787892..df831c2 100644 --- a/lunaix-os/arch/i386/mm/vmutils.c +++ b/lunaix-os/arch/x86/mm/vmutils.c @@ -14,6 +14,15 @@ dup_leaflet(struct leaflet* leaflet) size_t cnt_wordsz = leaflet_size(new_leaflet) / sizeof(ptr_t); +#ifdef CONFIG_ARCH_X86_64 + asm volatile("movq %1, %%rdi\n" + "movq %2, %%rsi\n" + "rep movsq\n" ::"c"(cnt_wordsz), + "r"(dest_va), + "r"(src_va) + : "memory", "%edi", "%esi"); + +#else asm volatile("movl %1, %%edi\n" "movl %2, %%esi\n" "rep movsl\n" ::"c"(cnt_wordsz), @@ -21,6 +30,8 @@ dup_leaflet(struct leaflet* leaflet) "r"(src_va) : "memory", "%edi", "%esi"); +#endif + leaflet_unmount(leaflet); vunmap(dest_va, new_leaflet); diff --git a/lunaix-os/arch/i386/syscall.S b/lunaix-os/arch/x86/syscall32.S similarity index 72% rename from lunaix-os/arch/i386/syscall.S rename to lunaix-os/arch/x86/syscall32.S index 4682df4..4f0ad5b 100644 --- a/lunaix-os/arch/i386/syscall.S +++ b/lunaix-os/arch/x86/syscall32.S @@ -81,41 +81,41 @@ .type syscall_hndlr, @function .global syscall_hndlr syscall_hndlr: - pushl %ebp - movl %esp, %ebp - movl 8(%esp), %ebx // struct hart_state* + pushl %ebp + movl %esp, %ebp + movl 8(%esp), %ebx // struct hart_state* - addl $4, %ebx - movl (%ebx), %eax /* eax: call code as well as the return value from syscall */ - cmpl $__SYSCALL_MAX, %eax - jae 2f + addl $4, %ebx + movl (%ebx), %eax /* eax: call code as well as the return value from syscall */ + cmpl $__SYSCALL_MAX, %eax + jae 2f - shll $2, %eax - addl $syscall_table, %eax - cmpl $0, (%eax) - jne 1f + shll $2, %eax + addl $syscall_table, %eax + cmpl $0, (%eax) + jne 1f 2: - neg %eax - movl %ebp, %esp - popl %ebp + neg %eax + movl %ebp, %esp + popl %ebp ret 1: - pushl %ebx - pushl 24(%ebx) /* esi - #5 arg */ - pushl 16(%ebx) /* edi - #4 arg */ - pushl 12(%ebx) /* edx - #3 arg */ - pushl 8(%ebx) /* ecx - #2 arg */ - pushl 4(%ebx) /* ebx - #1 arg */ + pushl %ebx + pushl 24(%ebx) /* esi - #5 arg */ + pushl 16(%ebx) /* edi - #4 arg */ + pushl 12(%ebx) /* edx - #3 arg */ + pushl 8(%ebx) /* ecx - #2 arg */ + pushl 4(%ebx) /* ebx - #1 arg */ - call *(%eax) + call *(%eax) - addl $20, %esp /* remove the parameters from stack */ + addl $20, %esp /* remove the parameters from stack */ - popl %ebx - movl %eax, (%ebx) /* save the return value */ + popl %ebx + movl %eax, (%ebx) /* save the return value */ - movl %ebp, %esp - popl %ebp + movl %ebp, %esp + popl %ebp ret \ No newline at end of file diff --git a/lunaix-os/arch/x86/syscall64.S b/lunaix-os/arch/x86/syscall64.S new file mode 100644 index 0000000..f4ea8bd --- /dev/null +++ b/lunaix-os/arch/x86/syscall64.S @@ -0,0 +1,124 @@ +#define __ASM__ +#include +#include "sys/interrupt64.S.inc" + +.section .data + /* + 注意,这里的顺序非常重要。每个系统调用在这个地址表里的索引等于其调用号。 + */ + syscall_table: + 1: + .8byte 0 + .8byte __lxsys_fork /* 1 */ + .8byte __lxsys_yield + .8byte __lxsys_sbrk + .8byte __lxsys_brk + .8byte __lxsys_getpid /* 5 */ + .8byte __lxsys_getppid + .8byte __lxsys_sleep + .8byte __lxsys_exit + .8byte __lxsys_wait + .8byte __lxsys_waitpid /* 10 */ + .8byte __lxsys_sigreturn + .8byte __lxsys_sigprocmask + .8byte __lxsys_sys_sigaction + .8byte __lxsys_pause + .8byte __lxsys_kill /* 15 */ + .8byte __lxsys_alarm + .8byte __lxsys_sigpending + .8byte __lxsys_sigsuspend + .8byte __lxsys_open + .8byte __lxsys_close /* 20 */ + .8byte __lxsys_read + .8byte __lxsys_write + .8byte __lxsys_sys_readdir + .8byte __lxsys_mkdir + .8byte __lxsys_lseek /* 25 */ + .8byte __lxsys_geterrno + .8byte __lxsys_readlink + .8byte __lxsys_readlinkat + .8byte __lxsys_rmdir + .8byte __lxsys_unlink /* 30 */ + .8byte __lxsys_unlinkat + .8byte __lxsys_link + .8byte __lxsys_fsync + .8byte __lxsys_dup + .8byte __lxsys_dup2 /* 35 */ + .8byte __lxsys_realpathat + .8byte __lxsys_symlink + .8byte __lxsys_chdir + .8byte __lxsys_fchdir + .8byte __lxsys_getcwd /* 40 */ + .8byte __lxsys_rename + .8byte __lxsys_mount + .8byte __lxsys_unmount + .8byte __lxsys_getxattr + .8byte __lxsys_setxattr /* 45 */ + .8byte __lxsys_fgetxattr + .8byte __lxsys_fsetxattr + .8byte __lxsys_ioctl + .8byte __lxsys_getpgid + .8byte __lxsys_setpgid /* 50 */ + .8byte __lxsys_syslog + .8byte __lxsys_sys_mmap + .8byte __lxsys_munmap + .8byte __lxsys_execve + .8byte __lxsys_fstat /* 55 */ + .8byte __lxsys_pollctl + .8byte __lxsys_th_create + .8byte __lxsys_th_self + .8byte __lxsys_th_exit + .8byte __lxsys_th_join /* 60 */ + .8byte __lxsys_th_kill + .8byte __lxsys_th_detach + .8byte __lxsys_th_sigmask + 2: + .rept __SYSCALL_MAX - (2b - 1b) / 8 + .8byte 0 + .endr + + +.section .text + .type syscall_hndlr, @function + .global syscall_hndlr + syscall_hndlr: + pushq %rbp + movq %rsp, %rbp + pushq %rbx + + movq %rdi, %rbx // struct hart_state* + + movq irax(%rbx), %rax /* rax: call code as well as the return value from syscall */ + cmpq $__SYSCALL_MAX, %rax + jae 2f + + shlq $3, %rax // %rax * 8 + movabsq $syscall_table, %r8 + addq %r8, %rax + cmpq $0, (%rax) + jne 1f + 2: + negq %rax + popq %rbx + movq %rbp, %rsp + popq %rbp + + ret + + 1: + + movq irbx(%rbx), %rdi /* rbx -> rdi #1 arg */ + movq ircx(%rbx), %rsi /* rcx -> rsi #2 arg */ + movq irdx(%rbx), %rdx /* rdx -> rdx #3 arg */ + movq irdi(%rbx), %rcx /* rdi -> rcx #4 arg */ + movq irsi(%rbx), %r8 /* rsi -> r8 #5 arg */ + + call *(%rax) + + movq %rax, irax(%rbx) /* save the return value */ + + popq %rbx + movq %rbp, %rsp + popq %rbp + + ret \ No newline at end of file diff --git a/lunaix-os/arch/x86/trace.c b/lunaix-os/arch/x86/trace.c new file mode 100644 index 0000000..3e06895 --- /dev/null +++ b/lunaix-os/arch/x86/trace.c @@ -0,0 +1,102 @@ +#include + +void +trace_print_transistion_short(struct hart_state* hstate) +{ + trace_log(" trigger: iv=%d, ecause=%p", + hart_vector_stamp(hstate), + hart_ecause(hstate)); +} + +#ifdef CONFIG_ARCH_X86_64 + +void +trace_print_transition_full(struct hart_state* hstate) +{ + trace_log("hart state transition"); + trace_log(" vector=%d, ecause=0x%x", + hart_vector_stamp(hstate), + hart_ecause(hstate)); + + trace_log(" rflags=0x%016lx", hstate->execp->rflags); + trace_log(" sp=0x%016lx, seg_sel=0x%04x", + hstate->execp->rsp, + hstate->execp->ss); + trace_log(" ip=0x%016lx, seg_sel=0x%04x", + hstate->execp->rip, + hstate->execp->cs); +} + +void +trace_dump_state(struct hart_state* hstate) +{ + struct regcontext* rh = &hstate->registers; + struct exec_param* ep = hstate->execp; + trace_log("hart state dump (depth=%d)", hstate->depth); + trace_log(" rax=0x%016lx, rbx=0x%016lx", + rh->rax, rh->rbx); + trace_log(" rcx=0x%016lx, rdx=0x%016lx", + rh->rcx, rh->rdx); + trace_log(" rdi=0x%016lx, rsi=0x%016lx", + rh->rdi, rh->rsi); + + trace_log(" r8=0x%016lx, r9=0x%016lx", + rh->r8, rh->r9); + trace_log(" r10=0x%016lx, r11=0x%016lx", + rh->r10, rh->r11); + trace_log(" r12=0x%016lx, r13=0x%016lx", + rh->r12, rh->r13); + trace_log(" r14=0x%016lx, r15=0x%016lx", + rh->r14, rh->r15); + + trace_log(" cs=0x%04x, rip=0x%016lx", + ep->cs, ep->rip); + trace_log(" ss=0x%04x, rsp=0x%016lx", + ep->ss, ep->rsp); + trace_log(" rflags=0x%016lx", + ep->rflags); +} + +#else + +void +trace_print_transition_full(struct hart_state* hstate) +{ + trace_log("hart state transition"); + trace_log(" vector=%d, ecause=0x%x", + hart_vector_stamp(hstate), + hart_ecause(hstate)); + trace_log(" eflags=0x%x", hstate->execp->eflags); + trace_log(" sp=%p, [seg_sel=0x%04x]", + hstate->execp->esp, + hstate->execp->ss); + trace_log(" ip=%p, seg_sel=0x%04x", + hstate->execp->eip, + hstate->execp->cs); +} + +void +trace_dump_state(struct hart_state* hstate) +{ + struct regcontext* rh = &hstate->registers; + struct exec_param* ep = hstate->execp; + trace_log("hart state dump (depth=%d)", hstate->depth); + trace_log(" eax=0x%08x, ebx=0x%08x, ecx=0x%08x", + rh->eax, rh->ebx, rh->ecx); + trace_log(" edx=0x%08x, ebp=0x%08x", + rh->edx, rh->ebp); + trace_log(" ds=0x%04x, edi=0x%08x", + rh->ds, rh->edi); + trace_log(" es=0x%04x, esi=0x%08x", + rh->es, rh->esi); + trace_log(" fs=0x%04x, gs=0x%x", + rh->fs, rh->gs); + trace_log(" cs=0x%04x, ip=0x%08x", + ep->cs, ep->eip); + trace_log(" [ss=0x%04x],sp=0x%08x", + ep->ss, ep->esp); + trace_log(" eflags=0x%08x", + ep->eflags); +} + +#endif \ No newline at end of file diff --git a/lunaix-os/hal/acpi/acpi.c b/lunaix-os/hal/acpi/acpi.c index 5732ec8..bc90ba1 100644 --- a/lunaix-os/hal/acpi/acpi.c +++ b/lunaix-os/hal/acpi/acpi.c @@ -85,15 +85,14 @@ acpi_init(struct device_def* devdef) size_t entry_n = (rsdt->header.length - sizeof(acpi_sdthdr_t)) >> 2; for (size_t i = 0; i < entry_n; i++) { - acpi_sdthdr_t* sdthdr = - (acpi_sdthdr_t*)((acpi_apic_t**)&(rsdt->entry))[i]; + acpi_sdthdr_t* sdthdr = __acpi_sdthdr(rsdt->entry[i]); switch (sdthdr->signature) { case ACPI_MADT_SIG: - madt_parse((acpi_madt_t*)sdthdr, ctx); + madt_parse(__acpi_madt(sdthdr), ctx); break; case ACPI_FADT_SIG: // FADT just a plain structure, no need to parse. - ctx->fadt = *(acpi_fadt_t*)sdthdr; + ctx->fadt = *__acpi_fadt(sdthdr); break; case ACPI_MCFG_SIG: mcfg_parse(sdthdr, ctx); diff --git a/lunaix-os/hal/acpi/parser/madt_parser.c b/lunaix-os/hal/acpi/parser/madt_parser.c index 3c05de1..c0b059d 100644 --- a/lunaix-os/hal/acpi/parser/madt_parser.c +++ b/lunaix-os/hal/acpi/parser/madt_parser.c @@ -16,16 +16,16 @@ madt_parse(acpi_madt_t* madt, acpi_context* toc) size_t so_idx = 0; while (ics_start < ics_end) { - acpi_ics_hdr_t* entry = (acpi_ics_hdr_t*)ics_start; + acpi_ics_hdr_t* entry = __acpi_ics_hdr(ics_start); switch (entry->type) { case ACPI_MADT_LAPIC: - toc->madt.apic = (acpi_apic_t*)entry; + toc->madt.apic = __acpi_apic(entry); break; case ACPI_MADT_IOAPIC: - toc->madt.ioapic = (acpi_ioapic_t*)entry; + toc->madt.ioapic = __acpi_ioapic(entry); break; case ACPI_MADT_INTSO: { - acpi_intso_t* intso_tbl = (acpi_intso_t*)entry; + acpi_intso_t* intso_tbl = __acpi_intso(entry); toc->madt.irq_exception[intso_tbl->source] = intso_tbl; break; } diff --git a/lunaix-os/hal/acpi/parser/mcfg_parser.c b/lunaix-os/hal/acpi/parser/mcfg_parser.c index 9a9541d..cbd139e 100644 --- a/lunaix-os/hal/acpi/parser/mcfg_parser.c +++ b/lunaix-os/hal/acpi/parser/mcfg_parser.c @@ -7,8 +7,9 @@ LOG_MODULE("MCFG") void mcfg_parse(acpi_sdthdr_t* mcfg, acpi_context* toc) { - size_t alloc_num = (mcfg->length - sizeof(acpi_sdthdr_t) - 8) / - sizeof(struct acpi_mcfg_alloc); + size_t alloc_num = (mcfg->length - sizeof(acpi_sdthdr_t) - 8); + alloc_num = alloc_num / sizeof(struct acpi_mcfg_alloc); + struct acpi_mcfg_alloc* allocs = (struct acpi_mcfg_alloc*)((ptr_t)mcfg + (sizeof(acpi_sdthdr_t) + 8)); diff --git a/lunaix-os/hal/char/serial.c b/lunaix-os/hal/char/serial.c index 826b3a6..dea044f 100644 --- a/lunaix-os/hal/char/serial.c +++ b/lunaix-os/hal/char/serial.c @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -162,7 +162,7 @@ __serial_read_async(struct device* dev, void* buf, off_t fpos, size_t len) static int __serial_read_page(struct device* dev, void* buf, off_t fpos) { - return serial_readbuf(serial_device(dev), (u8_t*)buf, MEM_PAGE); + return serial_readbuf(serial_device(dev), (u8_t*)buf, PAGE_SIZE); } static int @@ -181,7 +181,7 @@ __serial_write_async(struct device* dev, void* buf, off_t fpos, size_t len) static int __serial_write_page(struct device* dev, void* buf, off_t fpos) { - return serial_writebuf(serial_device(dev), (u8_t*)buf, MEM_PAGE); + return serial_writebuf(serial_device(dev), (u8_t*)buf, PAGE_SIZE); } static int diff --git a/lunaix-os/hal/char/uart/16550.h b/lunaix-os/hal/char/uart/16550.h index ccee415..5b4040b 100644 --- a/lunaix-os/hal/char/uart/16550.h +++ b/lunaix-os/hal/char/uart/16550.h @@ -17,7 +17,6 @@ #define UART_rDLM 1 #define UART_INTRX 0x1 -#define UART_DLAB (1 << 7) #define UART_LOOP (1 << 4) #define UART_rIE_ERBFI 1 @@ -29,6 +28,7 @@ #define UART_rLC_PAREN (1 << 3) #define UART_rLC_PAREVN (1 << 4) #define UART_rLC_SETBRK (1 << 6) +#define UART_rLC_DLAB (1 << 7) #define UART_rLS_THRE (1 << 5) #define UART_rLS_DR 1 @@ -116,13 +116,13 @@ uart_baud_divisor(struct uart16550* uart, int div) { u32_t rlc = uart->read_reg(uart, UART_rLC); - uart->write_reg(uart, UART_rLC, UART_DLAB | rlc); + uart->write_reg(uart, UART_rLC, UART_rLC_DLAB | rlc); u8_t ls = (div & 0xff), ms = (div & 0xff00) >> 8; uart->write_reg(uart, UART_rLS, ls); uart->write_reg(uart, UART_rMS, ms); - uart->write_reg(uart, UART_rLC, rlc & ~UART_DLAB); + uart->write_reg(uart, UART_rLC, rlc & ~UART_rLC_DLAB); return 0; } diff --git a/lunaix-os/hal/char/uart/16550_base.c b/lunaix-os/hal/char/uart/16550_base.c index d78f3cf..a84e3d0 100644 --- a/lunaix-os/hal/char/uart/16550_base.c +++ b/lunaix-os/hal/char/uart/16550_base.c @@ -42,6 +42,12 @@ uart_general_tx(struct serial_dev* sdev, u8_t* data, size_t len) return RXTX_DONE; } +#define UART_LCR_RESET \ + (UART_rLC_STOPB | \ + UART_rLC_PAREN | \ + UART_rLC_PAREVN | \ + UART_rLC_DLAB | 0b11) + static void uart_set_control_mode(struct uart16550* uart, tcflag_t cflags) { @@ -50,7 +56,7 @@ uart_set_control_mode(struct uart16550* uart, tcflag_t cflags) uart->cntl_save.rie |= (!!(cflags & _CREAD)) * UART_rIE_ERBFI; uart_setie(uart); - uart->cntl_save.rlc &= ~(UART_rLC_STOPB | UART_rLC_PAREN | UART_rLC_PAREVN | 0b11); + uart->cntl_save.rlc &= ~UART_LCR_RESET; uart->cntl_save.rlc |= (!!(cflags & _CSTOPB)) * UART_rLC_STOPB; uart->cntl_save.rlc |= (!!(cflags & _CPARENB)) * UART_rLC_PAREN; uart->cntl_save.rlc |= (!(cflags & _CPARODD)) * UART_rLC_PAREVN; diff --git a/lunaix-os/includes/hal/acpi/fadt.h b/lunaix-os/includes/hal/acpi/fadt.h index 339486d..07359f0 100644 --- a/lunaix-os/includes/hal/acpi/fadt.h +++ b/lunaix-os/includes/hal/acpi/fadt.h @@ -70,6 +70,7 @@ typedef struct acpi_fadt u8_t time_info[3]; u16_t boot_arch; } ACPI_TABLE_PACKED acpi_fadt_t; +#define __acpi_fadt(acpi_ptr) ((acpi_fadt_t*)__ptr(acpi_ptr)) // TODO: FADT parser & support diff --git a/lunaix-os/includes/hal/acpi/madt.h b/lunaix-os/includes/hal/acpi/madt.h index 538e521..c12ab28 100644 --- a/lunaix-os/includes/hal/acpi/madt.h +++ b/lunaix-os/includes/hal/acpi/madt.h @@ -16,6 +16,7 @@ typedef struct u8_t type; u8_t length; } ACPI_TABLE_PACKED acpi_ics_hdr_t; +#define __acpi_ics_hdr(acpi_ptr) ((acpi_ics_hdr_t*)__ptr(acpi_ptr)) /** * @brief ACPI Processor Local APIC Structure (PLAS) @@ -30,6 +31,7 @@ typedef struct u8_t apic_id; u32_t flags; } ACPI_TABLE_PACKED acpi_apic_t; +#define __acpi_apic(acpi_ptr) ((acpi_apic_t*)__ptr(acpi_ptr)) /** * @brief ACPI IO APIC Structure (IOAS) @@ -48,6 +50,7 @@ typedef struct // for a slave IOAPIC) u32_t gis_offset; } ACPI_TABLE_PACKED acpi_ioapic_t; +#define __acpi_ioapic(acpi_ptr) ((acpi_ioapic_t*)__ptr(acpi_ptr)) /** * @brief ACPI Interrupt Source Override (INTSO) @@ -69,18 +72,20 @@ typedef struct u32_t gsi; u16_t flags; } ACPI_TABLE_PACKED acpi_intso_t; +#define __acpi_intso(acpi_ptr) ((acpi_intso_t*)__ptr(acpi_ptr)) typedef struct { acpi_sdthdr_t header; - void* apic_addr; + u32_t apic_addr; u32_t flags; // Here is a bunch of packed ICS reside here back-to-back. } ACPI_TABLE_PACKED acpi_madt_t; +#define __acpi_madt(acpi_ptr) ((acpi_madt_t*)__ptr(acpi_ptr)) typedef struct { - void* apic_addr; + u32_t apic_addr; acpi_apic_t* apic; acpi_ioapic_t* ioapic; acpi_intso_t** irq_exception; diff --git a/lunaix-os/includes/hal/acpi/sdt.h b/lunaix-os/includes/hal/acpi/sdt.h index b055aa3..de8dc7d 100644 --- a/lunaix-os/includes/hal/acpi/sdt.h +++ b/lunaix-os/includes/hal/acpi/sdt.h @@ -18,11 +18,12 @@ typedef struct acpi_sdthdr u32_t vendor_id; u32_t vendor_rev; } ACPI_TABLE_PACKED acpi_sdthdr_t; +#define __acpi_sdthdr(acpi_ptr) ((acpi_sdthdr_t*)__ptr(acpi_ptr)) typedef struct acpi_rsdt { acpi_sdthdr_t header; - acpi_sdthdr_t* entry; + u32_t entry[0]; } ACPI_TABLE_PACKED acpi_rsdt_t; #endif /* __LUNAIX_ACPI_SDT_H */ diff --git a/lunaix-os/includes/klibc/ia_utils.h b/lunaix-os/includes/klibc/ia_utils.h index df7704f..ef621e3 100644 --- a/lunaix-os/includes/klibc/ia_utils.h +++ b/lunaix-os/includes/klibc/ia_utils.h @@ -1,6 +1,6 @@ #ifndef __LUNAIX_IAUTILS_H #define __LUNAIX_IAUTILS_H -char* itoa(int value, char* str, int base); +char* itoa(long value, char* str, int base); #endif /* __LUNAIX_IAUTILS_H */ diff --git a/lunaix-os/includes/lunaix/blkpart_gpt.h b/lunaix-os/includes/lunaix/blkpart_gpt.h index fdce191..ec9c9be 100644 --- a/lunaix-os/includes/lunaix/blkpart_gpt.h +++ b/lunaix-os/includes/lunaix/blkpart_gpt.h @@ -31,7 +31,7 @@ struct gpt_header u32_t ent_size; u32_t ent_cksum; // reserved start here -} PACKED; +} compact; struct gpt_entry { @@ -41,7 +41,7 @@ struct gpt_entry u64_t end_lba; u64_t attr_flags; char name[72]; -} PACKED; +} compact; int blkpart_probegpt(struct device* master); diff --git a/lunaix-os/includes/lunaix/boot_generic.h b/lunaix-os/includes/lunaix/boot_generic.h index dca7b9a..cc6ea08 100644 --- a/lunaix-os/includes/lunaix/boot_generic.h +++ b/lunaix-os/includes/lunaix/boot_generic.h @@ -68,7 +68,10 @@ void boot_end(struct boot_handoff*); void -boot_cleanup(); +boot_begin_arch_reserve(struct boot_handoff*); + +void +boot_clean_arch_reserve(struct boot_handoff*); static inline bool free_memregion(struct boot_mmapent* mmapent) diff --git a/lunaix-os/includes/lunaix/compiler.h b/lunaix-os/includes/lunaix/compiler.h index 78387bd..28ae36e 100644 --- a/lunaix-os/includes/lunaix/compiler.h +++ b/lunaix-os/includes/lunaix/compiler.h @@ -14,6 +14,8 @@ #define unreachable __builtin_unreachable() #define no_inline __attribute__((noinline)) +#define defualt weak + #define clz(bits) __builtin_clz(bits) #define sadd_overflow(a, b, of) __builtin_sadd_overflow(a, b, of) #define umul_overflow(a, b, of) __builtin_umul_overflow(a, b, of) @@ -32,7 +34,8 @@ #define cacheline_align align(cacheline_size) #define export_symbol(domain, namespace, symbol)\ - typeof(symbol)* must_emit __SYMEXPORT_Z##domain##_N##namespace##_S##symbol = &(symbol) + typeof(symbol)* must_emit __SYMEXPORT_Z##domain##_N##namespace##_S##symbol \ + = &(symbol) inline static void noret spin() diff --git a/lunaix-os/includes/lunaix/exebi/elf.h b/lunaix-os/includes/lunaix/exebi/elf.h new file mode 100644 index 0000000..73a71e5 --- /dev/null +++ b/lunaix-os/includes/lunaix/exebi/elf.h @@ -0,0 +1,106 @@ +/** + * @file elf.h + * @author Lunaixsky (lunaxisky@qq.com) + * @brief + * @version 0.1 + * @date 2024-07-05 + * + * Define generic structure that described in Executable and Linking Format + * specification. + * + * Define interface on manipulate such structure + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef __LUNAIX_ELF32_H +#define __LUNAIX_ELF32_H + +#include +#include + +#define ET_EXEC 2 +#define ET_DYN 3 + +#define PT_LOAD 1 +#define PT_INTERP 3 + +#define PF_X 0x1 +#define PF_W 0x2 +#define PF_R 0x4 + +#define EV_CURRENT 1 + +// [0x7f, 'E', 'L', 'F'] +#define ELFMAGIC_LE 0x464c457fU + +#define EI_CLASS 4 +#define EI_DATA 5 + +#define NO_LOADER 0 +#define DEFAULT_LOADER "usr/ld" + +struct elf_ehdr; +struct elf_phdr; + +struct elf +{ + const void* elf_file; + struct elf_ehdr eheader; + struct elf_phdr* pheaders; +}; + +#define declare_elf32(elf, elf_vfile) \ + struct elf elf = { .elf_file = elf_vfile, .pheaders = (void*)0 } + +int +elf_check_exec(const struct elf* elf, int type); + +int +elf_check_arch(const struct elf* elf); + +int +elf_open(struct elf* elf, const char* path); + +int +elf_openat(struct elf* elf, void* elf_vfile); + +int +elf_static_linked(const struct elf* elf); + +int +elf_close(struct elf* elf); + +/** + * @brief Try to find the PT_INTERP section. If found, copy it's content to + * path_out + * + * @param elf Opened elf32 descriptor + * @param path_out + * @param len size of path_out buffer + * @return int + */ +int +elf_find_loader(const struct elf* elf, char* path_out, size_t len); + +int +elf_read_ehdr(struct elf* elf); + +int +elf_read_phdr(struct elf* elf); + +/** + * @brief Estimate how much memeory will be acquired if we load all loadable + * sections.、 + * + * @param elf + * @return size_t + */ +size_t +elf_loadable_memsz(const struct elf* elf); + +#define SIZE_EHDR sizeof(struct elf_ehdr) +#define SIZE_PHDR sizeof(struct elf_phdr) + +#endif /* __LUNAIX_ELF32_H */ diff --git a/lunaix-os/includes/lunaix/exebi/elf32.h b/lunaix-os/includes/lunaix/exebi/elf32.h deleted file mode 100644 index 1cda2c7..0000000 --- a/lunaix-os/includes/lunaix/exebi/elf32.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef __LUNAIX_ELF32_H -#define __LUNAIX_ELF32_H - -#include - -typedef unsigned int elf32_ptr_t; -typedef unsigned short elf32_hlf_t; -typedef unsigned int elf32_off_t; -typedef unsigned int elf32_swd_t; -typedef unsigned int elf32_wrd_t; - -#define ET_NONE 0 -#define ET_EXEC 2 -#define ET_DYN 3 - -#define PT_LOAD 1 -#define PT_INTERP 3 - -#define PF_X 0x1 -#define PF_W 0x2 -#define PF_R 0x4 - -#define EM_NONE 0 -#define EM_386 3 - -#define EV_CURRENT 1 - -// [0x7f, 'E', 'L', 'F'] -#define ELFMAGIC 0x464c457fU -#define ELFCLASS32 1 -#define ELFCLASS64 2 -#define ELFDATA2LSB 1 -#define ELFDATA2MSB 2 - -#define EI_CLASS 4 -#define EI_DATA 5 - -#define NO_LOADER 0 -#define DEFAULT_LOADER "usr/ld" - -struct elf32_ehdr -{ - u8_t e_ident[16]; - elf32_hlf_t e_type; - elf32_hlf_t e_machine; - elf32_wrd_t e_version; - elf32_ptr_t e_entry; - elf32_off_t e_phoff; - elf32_off_t e_shoff; - elf32_wrd_t e_flags; - elf32_hlf_t e_ehsize; - elf32_hlf_t e_phentsize; - elf32_hlf_t e_phnum; - elf32_hlf_t e_shentsize; - elf32_hlf_t e_shnum; - elf32_hlf_t e_shstrndx; -}; - -struct elf32_phdr -{ - elf32_wrd_t p_type; - elf32_off_t p_offset; - elf32_ptr_t p_va; - elf32_ptr_t p_pa; - elf32_wrd_t p_filesz; - elf32_wrd_t p_memsz; - elf32_wrd_t p_flags; - elf32_wrd_t p_align; -}; - -struct elf32 -{ - const void* elf_file; - struct elf32_ehdr eheader; - struct elf32_phdr* pheaders; -}; - -#define declare_elf32(elf, elf_vfile) \ - struct elf32 elf = { .elf_file = elf_vfile, .pheaders = (void*)0 } - -int -elf32_check_exec(const struct elf32* elf, int type); - -int -elf32_check_arch(const struct elf32* elf); - -int -elf32_open(struct elf32* elf, const char* path); - -int -elf32_openat(struct elf32* elf, void* elf_vfile); - -int -elf32_static_linked(const struct elf32* elf); - -int -elf32_close(struct elf32* elf); - -/** - * @brief Try to find the PT_INTERP section. If found, copy it's content to - * path_out - * - * @param elf Opened elf32 descriptor - * @param path_out - * @param len size of path_out buffer - * @return int - */ -int -elf32_find_loader(const struct elf32* elf, char* path_out, size_t len); - -int -elf32_read_ehdr(struct elf32* elf); - -int -elf32_read_phdr(struct elf32* elf); - -/** - * @brief Estimate how much memeory will be acquired if we load all loadable - * sections.、 - * - * @param elf - * @return size_t - */ -size_t -elf32_loadable_memsz(const struct elf32* elf); - -#define SIZE_EHDR sizeof(struct elf32_ehdr) -#define SIZE_PHDR sizeof(struct elf32_phdr) - -#endif /* __LUNAIX_ELF32_H */ diff --git a/lunaix-os/includes/lunaix/exec.h b/lunaix-os/includes/lunaix/exec.h index a49845e..64e0f2d 100644 --- a/lunaix-os/includes/lunaix/exec.h +++ b/lunaix-os/includes/lunaix/exec.h @@ -5,14 +5,15 @@ #include #include +#define MAX_PARAM_LEN 1024 +#define MAX_PARAM_SIZE 4096 + #define MAX_VAR_PAGES 8 #define DEFAULT_HEAP_PAGES 16 -struct exec_context; - struct load_context { - struct exec_container* container; + struct exec_host* container; ptr_t base; ptr_t end; ptr_t mem_sz; @@ -20,17 +21,24 @@ struct load_context ptr_t entry; }; -struct exec_container +struct exec_arrptr +{ + unsigned int len; + unsigned int size; + ptr_t raw; + ptr_t copied; +}; + + +struct exec_host { struct proc_info* proc; ptr_t vms_mnt; struct load_context exe; - // argv prependums - const char* argv_pp[2]; - const char** argv; - const char** envp; + struct exec_arrptr argv; + struct exec_arrptr envp; ptr_t stack_top; @@ -45,24 +53,23 @@ struct uexec_param char** envp; } compact; -#ifndef __USR_WRAPPER__ +int +exec_arch_prepare_entry(struct thread* thread, struct exec_host* container); int -exec_load_byname(struct exec_container* container, const char* filename); +exec_load_byname(struct exec_host* container, const char* filename); int -exec_load(struct exec_container* container, struct v_file* executable); +exec_load(struct exec_host* container, struct v_file* executable); int exec_kexecve(const char* filename, const char* argv[], const char* envp[]); void -exec_init_container(struct exec_container* param, +exec_init_container(struct exec_host* param, struct thread* thread, ptr_t vms, const char** argv, const char** envp); -#endif - #endif /* __LUNAIX_LOADER_H */ diff --git a/lunaix-os/includes/lunaix/fs/iso9660.h b/lunaix-os/includes/lunaix/fs/iso9660.h index 6fbdbbc..99fc484 100644 --- a/lunaix-os/includes/lunaix/fs/iso9660.h +++ b/lunaix-os/includes/lunaix/fs/iso9660.h @@ -49,7 +49,7 @@ struct iso_vol u8_t type; u8_t std_id[5]; // CD001 u8_t version; -} PACKED; +} compact; struct iso_vol_boot { @@ -57,7 +57,7 @@ struct iso_vol_boot u8_t sys_id[32]; u8_t boot_id[32]; u8_t reserved; // align to data line width -} PACKED; +} compact; struct iso_datetime { @@ -69,21 +69,21 @@ struct iso_datetime u8_t sec[2]; u8_t ms[2]; u8_t gmt; -} PACKED; +} compact; // 32bits both-byte-order integer typedef struct iso_bbo32 { u32_t le; // little-endian u32_t be; // big-endian -} PACKED iso_bbo32_t; +} compact iso_bbo32_t; // 16bits both-byte-order integer typedef struct iso_bbo16 { u16_t le; // little-endian u16_t be; // big-endian -} PACKED iso_bbo16_t; +} compact iso_bbo16_t; // (8.4) Describe a primary volume space struct iso_vol_primary @@ -114,7 +114,7 @@ struct iso_vol_primary struct iso_datetime ex_time; // expiration struct iso_datetime ef_time; // effective u8_t fstruct_ver; // file structure version, don't care! -} PACKED; // size 1124 +} compact; // size 1124 // Layout for Supplementary Vol. is almost identical to primary vol. // We ignore it for now. (see section 8.5, table 6) @@ -128,14 +128,14 @@ struct iso_partition u8_t part_id[32]; iso_bbo32_t part_addr; iso_bbo32_t part_size; -} PACKED; +} compact; // (6.10.4) MDU with variable record struct iso_var_mdu { u8_t len; u8_t content[0]; -} PACKED; +} compact; struct iso_datetime2 { @@ -146,7 +146,7 @@ struct iso_datetime2 u8_t min; u8_t sec; u8_t gmt; -} PACKED; +} compact; // (9.1) Directory Record [Embedded into Variable MDU] struct iso_drecord @@ -160,7 +160,7 @@ struct iso_drecord u8_t gap_sz; // size of gap if FU is interleaved. iso_bbo16_t vol_seq; struct iso_var_mdu name; -} PACKED; +} compact; struct iso_xattr { @@ -183,7 +183,7 @@ struct iso_xattr u8_t payload[0]; // There is also a escape sequence after payload, // It however marked as optional, hence we ignore it. -} PACKED; +} compact; /// /// -------- IEEE P1281 SUSP --------- @@ -197,7 +197,7 @@ struct isosu_base u16_t signature; u8_t length; u8_t version; -} PACKED; +} compact; struct isosu_er { @@ -207,7 +207,7 @@ struct isosu_er u8_t src_len; u8_t ext_ver; u8_t id_des_src[0]; -} PACKED; +} compact; /// /// -------- Rock Ridge Extension -------- @@ -234,35 +234,35 @@ struct isorr_px iso_bbo32_t uid; iso_bbo32_t gid; iso_bbo32_t sn; -} PACKED; +} compact; struct isorr_pn { struct isosu_base header; iso_bbo32_t dev_hi; iso_bbo32_t dev_lo; -} PACKED; +} compact; struct isorr_sl { struct isosu_base header; u8_t flags; char symlink[0]; -} PACKED; +} compact; struct isorr_nm { struct isosu_base header; u8_t flags; char name[0]; -} PACKED; +} compact; struct isorr_tf { struct isosu_base header; u8_t flags; char times[0]; -} PACKED; +} compact; /// /// -------- VFS integration --------- diff --git a/lunaix-os/includes/lunaix/fs/twimap.h b/lunaix-os/includes/lunaix/fs/twimap.h index 7adbc53..c9d56a2 100644 --- a/lunaix-os/includes/lunaix/fs/twimap.h +++ b/lunaix-os/includes/lunaix/fs/twimap.h @@ -3,8 +3,8 @@ #include -#define twimap_index(twimap, type) ((type)((twimap)->index)) -#define twimap_data(twimap, type) ((type)((twimap)->data)) +#define twimap_index(twimap, type) ((type)__ptr((twimap)->index)) +#define twimap_data(twimap, type) ((type)__ptr((twimap)->data)) extern struct v_file_ops twimap_file_ops; diff --git a/lunaix-os/includes/lunaix/mm/mm.h b/lunaix-os/includes/lunaix/mm/mm.h index d5b8edd..d72c405 100644 --- a/lunaix-os/includes/lunaix/mm/mm.h +++ b/lunaix-os/includes/lunaix/mm/mm.h @@ -55,11 +55,11 @@ struct mm_region // mapped file offset off_t foff; // mapped file length - u32_t flen; // XXX it seems that we don't need this actually.. ptr_t start; ptr_t end; u32_t attr; + size_t flen; void** index; // fast reference, to accelerate access to this very region. diff --git a/lunaix-os/includes/lunaix/mm/mmap.h b/lunaix-os/includes/lunaix/mm/mmap.h index 36f8904..307610b 100644 --- a/lunaix-os/includes/lunaix/mm/mmap.h +++ b/lunaix-os/includes/lunaix/mm/mmap.h @@ -11,6 +11,7 @@ struct mmap_param struct proc_mm* pvms; // process vm off_t offset; // mapped file offset size_t mlen; // mapped memory length + size_t flen; // mapped file length u32_t proct; // protections u32_t flags; // other options u32_t type; // region type diff --git a/lunaix-os/includes/lunaix/mm/pagetable.h b/lunaix-os/includes/lunaix/mm/pagetable.h index f289b7a..843ad03 100644 --- a/lunaix-os/includes/lunaix/mm/pagetable.h +++ b/lunaix-os/includes/lunaix/mm/pagetable.h @@ -99,25 +99,34 @@ struct __pte; typedef struct __pte pte_t; +#include #include #include -#define _LnTEP_AT(vm_mnt, sz) ( ((vm_mnt) | L0T_MASK) & ~(sz) ) -#define _L0TEP_AT(vm_mnt) ( ((vm_mnt) | L0T_MASK) & ~LFT_MASK ) -#define _L1TEP_AT(vm_mnt) ( ((vm_mnt) | L0T_MASK) & ~L3T_MASK ) -#define _L2TEP_AT(vm_mnt) ( ((vm_mnt) | L0T_MASK) & ~L2T_MASK ) -#define _L3TEP_AT(vm_mnt) ( ((vm_mnt) | L0T_MASK) & ~L1T_MASK ) -#define _LFTEP_AT(vm_mnt) ( ((vm_mnt) | L0T_MASK) & ~L0T_MASK ) +#define VMS_SELF VMS_SELF_MOUNT +#define VMS_SELF_L0TI (__index(VMS_SELF_MOUNT) / L0T_SIZE) + +#define _LnT_LEVEL_SIZE(n) ( L##n##T_SIZE / PAGE_SIZE ) +#define _LFTEP_SELF ( __index(VMS_SELF) ) +#define _L3TEP_SELF ( _LFTEP_SELF | (_LFTEP_SELF / _LnT_LEVEL_SIZE(3)) ) +#define _L2TEP_SELF ( _L3TEP_SELF | (_LFTEP_SELF / _LnT_LEVEL_SIZE(2)) ) +#define _L1TEP_SELF ( _L2TEP_SELF | (_LFTEP_SELF / _LnT_LEVEL_SIZE(1)) ) +#define _L0TEP_SELF ( _L1TEP_SELF | (_LFTEP_SELF / _LnT_LEVEL_SIZE(0)) ) + +#define _L0TEP_AT(vm_mnt) ( ((vm_mnt) | (_L0TEP_SELF & L0T_MASK)) ) +#define _L1TEP_AT(vm_mnt) ( ((vm_mnt) | (_L1TEP_SELF & L0T_MASK)) ) +#define _L2TEP_AT(vm_mnt) ( ((vm_mnt) | (_L2TEP_SELF & L0T_MASK)) ) +#define _L3TEP_AT(vm_mnt) ( ((vm_mnt) | (_L3TEP_SELF & L0T_MASK)) ) +#define _LFTEP_AT(vm_mnt) ( ((vm_mnt) | (_LFTEP_SELF & L0T_MASK)) ) #define _VM_OF(ptep) ( (ptr_t)(ptep) & ~L0T_MASK ) #define _VM_PFN_OF(ptep) ( ((ptr_t)(ptep) & L0T_MASK) / sizeof(pte_t) ) -#define VMS_SELF ( ~L0T_MASK & VMS_MASK ) #define __LnTI_OF(ptep, n)\ - (_VM_PFN_OF(ptep) * LFT_SIZE / L##n##T_SIZE) + ( __index(_VM_PFN_OF(ptep) * LFT_SIZE / L##n##T_SIZE) ) #define __LnTEP(ptep, va, n)\ - ( (pte_t*)_L##n##TEP_AT(_VM_OF(ptep)) + (((va) & VMS_MASK) / L##n##T_SIZE) ) + ( (pte_t*)_L##n##TEP_AT(_VM_OF(ptep)) + (__index(va) / L##n##T_SIZE) ) #define __LnTEP_OF(ptep, n)\ ( (pte_t*)_L##n##TEP_AT(_VM_OF(ptep)) + __LnTI_OF(ptep, n)) @@ -128,12 +137,6 @@ typedef struct __pte pte_t; #define _has_LnT(n) (L##n##T_SIZE != LFT_SIZE) #define LnT_ENABLED(n) _has_LnT(n) -#define ptep_with_level(ptep, lvl_size) \ - ({ \ - ptr_t __p = _LnTEP_AT(_VM_OF(ptep), lvl_size); \ - ((ptr_t)(ptep) & __p) == __p; \ - }) - extern pte_t alloc_kpage_at(pte_t* ptep, pte_t pte, int order); @@ -185,13 +188,13 @@ ptep_vfn(pte_t* ptep) static inline ptr_t ptep_va(pte_t* ptep, size_t lvl_size) { - return ((ptr_t)ptep) / sizeof(pte_t) * lvl_size; + return __vaddr(ptep_pfn(ptep) * lvl_size); } static inline ptr_t ptep_vm_mnt(pte_t* ptep) { - return _VM_OF(ptep); + return __vaddr(_VM_OF(ptep)); } /** @@ -408,7 +411,7 @@ l3te_index(pte_t* ptep) { static inline pfn_t pfn(ptr_t addr) { - return (addr / PAGE_SIZE) & VMS_MASK; + return __index(addr) / PAGE_SIZE; } static inline size_t @@ -428,7 +431,7 @@ va_offset(ptr_t addr) { static inline ptr_t page_addr(ptr_t pfn) { - return pfn * PAGE_SIZE; + return __vaddr(pfn * PAGE_SIZE); } static inline ptr_t @@ -465,7 +468,7 @@ mkptep_pn(ptr_t vm_mnt, ptr_t pn) static inline pfn_t pfn_at(ptr_t va, size_t lvl_size) { - return va / lvl_size; + return __index(va) / lvl_size; } @@ -508,6 +511,24 @@ mkl0tep_va(ptr_t mnt, ptr_t va) return mkl0tep(mkptep_va(mnt, va)); } +static inline pte_t* +mkl1tep_va(ptr_t mnt, ptr_t va) +{ + return mkl1tep(mkptep_va(mnt, va)); +} + +static inline pte_t* +mkl2tep_va(ptr_t mnt, ptr_t va) +{ + return mkl2tep(mkptep_va(mnt, va)); +} + +static inline pte_t* +mkl3tep_va(ptr_t mnt, ptr_t va) +{ + return mkl3tep(mkptep_va(mnt, va)); +} + static inline bool pt_last_level(int level) { @@ -517,13 +538,45 @@ pt_last_level(int level) static inline ptr_t va_mntpoint(ptr_t va) { - return _VM_OF(va); + return __vaddr(_VM_OF(va)); } -static inline ptr_t -va_actual(ptr_t va) +static inline unsigned int +va_level_index(ptr_t va, size_t lvl_size) +{ + return (va / lvl_size) & _PAGE_LEVEL_MASK; +} + +static inline bool +l0tep_implie(pte_t* ptep, ptr_t addr) +{ + return ptep_va(ptep, L0T_SIZE) == __vaddr(addr); +} + +static inline bool +is_ptep(ptr_t addr) +{ + ptr_t mnt = va_mntpoint(addr); + return mnt == VMS_MOUNT_1 || mnt == VMS_SELF; +} + +static inline bool +vmnt_packed(pte_t* ptep) +{ + return is_ptep(__ptr(ptep)); +} + +static inline bool +active_vms(ptr_t vmnt) +{ + return vmnt == VMS_SELF; +} + +static inline bool +l0tep_impile_vmnts(pte_t* ptep) { - return page_addr(_VM_OF(va) ^ va); + return l0tep_implie(ptep, VMS_SELF) || + l0tep_implie(ptep, VMS_MOUNT_1); } #endif /* __LUNAIX_PAGETABLE_H */ diff --git a/lunaix-os/includes/lunaix/process.h b/lunaix-os/includes/lunaix/process.h index fe33fa2..92b9407 100644 --- a/lunaix-os/includes/lunaix/process.h +++ b/lunaix-os/includes/lunaix/process.h @@ -76,7 +76,7 @@ struct thread { /* Any change to *critical section*, including layout, size - must be reflected in arch/i386/interrupt.S.inc to avoid + must be reflected in arch/x86/interrupt.S.inc to avoid disaster! */ struct diff --git a/lunaix-os/includes/lunaix/signal.h b/lunaix-os/includes/lunaix/signal.h index e983846..556823b 100644 --- a/lunaix-os/includes/lunaix/signal.h +++ b/lunaix-os/includes/lunaix/signal.h @@ -1,6 +1,7 @@ #ifndef __LUNAIX_SIGNAL_H #define __LUNAIX_SIGNAL_H +#include #include #define _SIG_NUM 16 diff --git a/lunaix-os/includes/lunaix/syscall_utils.h b/lunaix-os/includes/lunaix/syscall_utils.h index 640e386..d29056d 100644 --- a/lunaix-os/includes/lunaix/syscall_utils.h +++ b/lunaix-os/includes/lunaix/syscall_utils.h @@ -3,8 +3,10 @@ #include #include +#include -#define DO_STATUS(errno) SYSCALL_ESTATUS(syscall_result(errno)) -#define DO_STATUS_OR_RETURN(errno) ({ errno < 0 ? DO_STATUS(errno) : errno; }) +#define DO_STATUS(errno) SYSCALL_ESTATUS(syscall_result(errno)) +#define DO_STATUS_OR_RETURN(errno) \ + ({ errno < 0 ? DO_STATUS(errno) : errno; }) #endif /* __LUNAIX_SYSCALL_UTILS_H */ diff --git a/lunaix-os/includes/lunaix/types.h b/lunaix-os/includes/lunaix/types.h index 9aff5ab..52faac3 100644 --- a/lunaix-os/includes/lunaix/types.h +++ b/lunaix-os/includes/lunaix/types.h @@ -5,8 +5,6 @@ #include #include -#define PACKED __attribute__((packed)) - // TODO: WTERMSIG // TODO: replace the integer type with these. To make thing more portable. @@ -14,10 +12,15 @@ typedef unsigned char u8_t; typedef unsigned short u16_t; typedef unsigned int u32_t; -typedef unsigned long long u64_t; typedef unsigned long ptr_t; typedef unsigned long reg_t; +#ifndef CONFIG_ARCH_BITS_64 +typedef unsigned long long u64_t; +#else +typedef unsigned long u64_t; +#endif + typedef int pid_t; typedef signed long ssize_t; @@ -43,4 +46,8 @@ typedef int bool; (ptr) ? (type*)((char*)__mptr - offsetof(type, member)) : 0; \ }) +#define __ptr(val) ((ptr_t)(val)) + +typedef va_list* sc_va_list; + #endif /* __LUNAIX_TYPES_H */ diff --git a/lunaix-os/includes/usr/lunaix/mann_flags.h b/lunaix-os/includes/usr/lunaix/mann_flags.h index cf99def..dcd3a34 100644 --- a/lunaix-os/includes/usr/lunaix/mann_flags.h +++ b/lunaix-os/includes/usr/lunaix/mann_flags.h @@ -30,4 +30,14 @@ #define MS_INVALIDATE 0x4 #define MS_INVALIDATE_ALL 0x8 +struct usr_mmap_param +{ + void* addr; + unsigned long length; + int proct; + int flags; + int fd; + unsigned long offset; +}; + #endif /* __LUNAIX_MANN_FLAGS_H */ diff --git a/lunaix-os/includes/usr/lunaix/syscallid.h b/lunaix-os/includes/usr/lunaix/syscallid.h index ce55519..7e995f1 100644 --- a/lunaix-os/includes/usr/lunaix/syscallid.h +++ b/lunaix-os/includes/usr/lunaix/syscallid.h @@ -74,6 +74,6 @@ #define __SYSCALL_th_detach 62 #define __SYSCALL_th_sigmask 63 -#define __SYSCALL_MAX 0x100 +#define __SYSCALL_MAX 0x200 #endif /* __LUNAIX_SYSCALLID_H */ diff --git a/lunaix-os/includes/usr/lunaix/threads.h b/lunaix-os/includes/usr/lunaix/threads.h index a5d322e..a20b52f 100644 --- a/lunaix-os/includes/usr/lunaix/threads.h +++ b/lunaix-os/includes/usr/lunaix/threads.h @@ -3,9 +3,9 @@ #include "types.h" -struct uthread_info { - void* th_stack_top; - size_t th_stack_sz; +struct uthread_param { + void* th_handler; + void* arg1; }; #endif /* __LUNAIX_USR_THREADS_H */ diff --git a/lunaix-os/kernel.mk b/lunaix-os/kernel.mk index 86f4668..69a16db 100644 --- a/lunaix-os/kernel.mk +++ b/lunaix-os/kernel.mk @@ -1,7 +1,7 @@ -include os.mkinc include toolchain.mkinc +include lunabuild.mkinc -include .builder/lbuild.mkinc +include $(lbuild_mkinc) kbin_dir := $(BUILD_DIR) kbin := $(BUILD_NAME) @@ -10,16 +10,19 @@ ksrc_objs := $(addsuffix .o,$(_LBUILD_SRCS)) ksrc_deps := $(addsuffix .d,$(_LBUILD_SRCS)) khdr_opts := $(addprefix -include ,$(_LBUILD_HDRS)) kinc_opts := $(addprefix -I,$(_LBUILD_INCS)) -config_h += -include.builder/configs.h +config_h += -include $(lbuild_config_h) tmp_kbin := $(BUILD_DIR)/tmpk.bin ksymtable := lunaix_ksyms.o +klinking := link/lunaix.ld CFLAGS += $(khdr_opts) $(kinc_opts) $(config_h) -MMD -MP -include $(ksrc_deps) -%.S.o: %.S kernel.mk +all_linkable = $(filter-out $(klinking),$(1)) + +%.S.o: %.S $(khdr_files) kernel.mk $(call status_,AS,$<) @$(CC) $(CFLAGS) -c $< -o $@ @@ -27,25 +30,42 @@ CFLAGS += $(khdr_opts) $(kinc_opts) $(config_h) -MMD -MP $(call status_,CC,$<) @$(CC) $(CFLAGS) -c $< -o $@ -$(tmp_kbin): $(ksrc_objs) + +$(klinking): link/lunaix.ldx + $(call status_,PP,$<) + @$(CC) $(CFLAGS) -x c -E -P $< -o $@ + + +$(tmp_kbin): $(klinking) $(ksrc_objs) $(call status_,LD,$@) - @$(CC) -T link/linker.ld $(config_h) $(LDFLAGS) -o $@ $^ + + @$(CC) -T $(klinking) $(config_h) $(LDFLAGS) -o $@ \ + $(call all_linkable,$^) + $(ksymtable): $(tmp_kbin) $(call status_,KSYM,$@) @scripts/gen_ksymtable.sh DdRrTtAGg $< > .lunaix_ksymtable.S + @$(CC) $(CFLAGS) -c .lunaix_ksymtable.S -o $@ + .PHONY: __do_relink -__do_relink: $(ksrc_objs) $(ksymtable) +__do_relink: $(klinking) $(ksrc_objs) $(ksymtable) $(call status_,LD,$(kbin)) - @$(CC) -T link/linker.ld $(config_h) $(LDFLAGS) -o $(kbin) $^ + + @$(CC) -T $(klinking) $(config_h) $(LDFLAGS) -o $(kbin) \ + $(call all_linkable,$^) + @rm $(tmp_kbin) + .PHONY: all all: __do_relink + clean: @rm -f $(ksrc_objs) @rm -f $(ksrc_deps) + @rm -f $(klinking) @rm -f .lunaix_ksymtable.S $(ksymtable) \ No newline at end of file diff --git a/lunaix-os/kernel/boot_helper.c b/lunaix-os/kernel/boot_helper.c index 7f324e4..3652959 100644 --- a/lunaix-os/kernel/boot_helper.c +++ b/lunaix-os/kernel/boot_helper.c @@ -17,14 +17,9 @@ void boot_begin(struct boot_handoff* bhctx) { bhctx->prepare(bhctx); - - // Identity-map the first 3GiB address spaces - pte_t* ptep = mkl0tep(mkptep_va(VMS_SELF, 0)); - pte_t pte = mkpte_prot(KERNEL_DATA); - size_t count = page_count(KERNEL_RESIDENT, L0T_SIZE); - - vmm_set_ptes_contig(ptep, pte_mkhuge(pte), L0T_SIZE, count); + boot_begin_arch_reserve(bhctx); + // 将内核占据的页,包括前1MB,hhk_init 设为已占用 size_t pg_count = leaf_count(to_kphysical(__kexec_end)); pmm_onhold_range(0, pg_count); @@ -70,18 +65,8 @@ boot_end(struct boot_handoff* bhctx) } bhctx->release(bhctx); -} -/** - * @brief Clean up the boot stage code and data - * - */ -void -boot_cleanup() -{ - pte_t* ptep = mkl0tep(mkptep_va(VMS_SELF, 0)); - size_t count = page_count(KERNEL_RESIDENT, L0T_SIZE); - vmm_unset_ptes(ptep, count); + boot_clean_arch_reserve(bhctx); } void diff --git a/lunaix-os/kernel/debug/trace.c b/lunaix-os/kernel/debug/trace.c index 66e5505..e238526 100644 --- a/lunaix-os/kernel/debug/trace.c +++ b/lunaix-os/kernel/debug/trace.c @@ -71,14 +71,14 @@ static char* ksym_getstr(struct ksym_entry* sym) { if (!sym) { - return "???"; + return NULL; } return sym->label; } static inline bool valid_fp(ptr_t ptr) { - ptr_t start = ROUNDUP(current_thread->kstack - KSTACK_SIZE, MEM_PAGE); + ptr_t start = ROUNDUP(current_thread->kstack - KSTACK_SIZE, PAGE_SIZE); return (start < ptr && ptr < current_thread->kstack) || arch_valid_fp(ptr); @@ -121,10 +121,10 @@ trace_walkback(struct trace_record* tb_buffer, static inline void trace_print_code_entry(ptr_t sym_pc, ptr_t inst_pc, char* sym) { - if (sym_pc) { + if (sym) { trace_log("%s+%p", sym, inst_pc - sym_pc); } else { - trace_log("%s [%p]", sym, sym_pc); + trace_log("??? [%p]", inst_pc); } } @@ -161,7 +161,7 @@ static void trace_printswctx(const struct hart_state* hstate, bool from_usr, bool to_usr) { - struct ksym_entry* sym = trace_sym_lookup(hstate->execp->eip); + struct ksym_entry* sym = trace_sym_lookup(hart_pc(hstate)); trace_log("^^^^^ --- %s", to_usr ? "user" : "kernel"); trace_print_transistion_short(hstate); diff --git a/lunaix-os/kernel/device/device.c b/lunaix-os/kernel/device/device.c index d3552c6..d32ca32 100644 --- a/lunaix-os/kernel/device/device.c +++ b/lunaix-os/kernel/device/device.c @@ -292,10 +292,14 @@ device_alert_poller(struct device* dev, int poll_evt) iopoll_wake_pollers(&dev->pollers); } -__DEFINE_LXSYSCALL3(int, ioctl, int, fd, int, req, va_list, args) +__DEFINE_LXSYSCALL3(int, ioctl, int, fd, int, req, sc_va_list, _args) { int errno = -1; struct v_fd* fd_s; + va_list args; + + convert_valist(&args, _args); + if ((errno &= vfs_getfd(fd, &fd_s))) { goto done; } diff --git a/lunaix-os/kernel/device/poll.c b/lunaix-os/kernel/device/poll.c index 58d3621..493d5d3 100644 --- a/lunaix-os/kernel/device/poll.c +++ b/lunaix-os/kernel/device/poll.c @@ -244,9 +244,13 @@ iopoll_install(struct thread* thread, struct v_fd* fd) return pld; } -__DEFINE_LXSYSCALL2(int, pollctl, int, action, va_list, va) +__DEFINE_LXSYSCALL2(int, pollctl, int, action, sc_va_list, _ap) { int retcode = 0; + va_list va; + + convert_valist(&va, _ap); + switch (action) { case _SPOLL_ADD: { int* ds = va_arg(va, int*); diff --git a/lunaix-os/kernel/exe/LBuild b/lunaix-os/kernel/exe/LBuild index 8b24498..3ab25b4 100644 --- a/lunaix-os/kernel/exe/LBuild +++ b/lunaix-os/kernel/exe/LBuild @@ -1,4 +1,4 @@ -use("elf32") +use("elf-generic") sources([ "exec.c" diff --git a/lunaix-os/kernel/exe/elf-generic/LBuild b/lunaix-os/kernel/exe/elf-generic/LBuild new file mode 100644 index 0000000..80f3a1e --- /dev/null +++ b/lunaix-os/kernel/exe/elf-generic/LBuild @@ -0,0 +1,4 @@ +sources([ + "elfbfmt.c", + "ldelf.c" +]) \ No newline at end of file diff --git a/lunaix-os/kernel/exe/elf32/elf32bfmt.c b/lunaix-os/kernel/exe/elf-generic/elfbfmt.c similarity index 61% rename from lunaix-os/kernel/exe/elf32/elf32bfmt.c rename to lunaix-os/kernel/exe/elf-generic/elfbfmt.c index da94735..d7b09f1 100644 --- a/lunaix-os/kernel/exe/elf32/elf32bfmt.c +++ b/lunaix-os/kernel/exe/elf-generic/elfbfmt.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -6,34 +6,34 @@ #include static inline int -elf32_read(struct v_file* elf, void* data, size_t off, size_t len) +elf_read(struct v_file* elf, void* data, size_t off, size_t len) { // it is wise to do cached read return pcache_read(elf->inode, data, len, off); } static int -elf32_do_open(struct elf32* elf, struct v_file* elf_file) +elf_do_open(struct elf* elf, struct v_file* elf_file) { int status = 0; elf->pheaders = NULL; elf->elf_file = elf_file; - if ((status = elf32_read_ehdr(elf)) < 0) { - elf32_close(elf); + if ((status = elf_read_ehdr(elf)) < 0) { + elf_close(elf); return status; } - if ((status = elf32_read_phdr(elf)) < 0) { - elf32_close(elf); + if ((status = elf_read_phdr(elf)) < 0) { + elf_close(elf); return status; } return 0; } -int -elf32_open(struct elf32* elf, const char* path) +defualt int +elf_open(struct elf* elf, const char* path) { struct v_dnode* elfdn; struct v_file* elffile; @@ -47,19 +47,19 @@ elf32_open(struct elf32* elf, const char* path) return error; } - return elf32_do_open(elf, elffile); + return elf_do_open(elf, elffile); } -int -elf32_openat(struct elf32* elf, void* elf_vfile) +defualt int +elf_openat(struct elf* elf, void* elf_vfile) { // so the ref count kept in sync vfs_ref_file(elf_vfile); - return elf32_do_open(elf, elf_vfile); + return elf_do_open(elf, elf_vfile); } -int -elf32_close(struct elf32* elf) +defualt int +elf_close(struct elf* elf) { if (elf->pheaders) { vfree(elf->pheaders); @@ -74,11 +74,11 @@ elf32_close(struct elf32* elf) return 0; } -int -elf32_static_linked(const struct elf32* elf) +defualt int +elf_static_linked(const struct elf* elf) { for (size_t i = 0; i < elf->eheader.e_phnum; i++) { - struct elf32_phdr* phdre = &elf->pheaders[i]; + struct elf_phdr* phdre = &elf->pheaders[i]; if (phdre->p_type == PT_INTERP) { return 0; } @@ -86,8 +86,8 @@ elf32_static_linked(const struct elf32* elf) return 1; } -size_t -elf32_loadable_memsz(const struct elf32* elf) +defualt size_t +elf_loadable_memsz(const struct elf* elf) { // XXX: Hmmmm, I am not sure if we need this. This is designed to be handy // if we decided to map the heap region before transfer to loader. As @@ -96,7 +96,7 @@ elf32_loadable_memsz(const struct elf32* elf) size_t sz = 0; for (size_t i = 0; i < elf->eheader.e_phnum; i++) { - struct elf32_phdr* phdre = &elf->pheaders[i]; + struct elf_phdr* phdre = &elf->pheaders[i]; if (phdre->p_type == PT_LOAD) { sz += phdre->p_memsz; } @@ -105,8 +105,8 @@ elf32_loadable_memsz(const struct elf32* elf) return sz; } -int -elf32_find_loader(const struct elf32* elf, char* path_out, size_t len) +defualt int +elf_find_loader(const struct elf* elf, char* path_out, size_t len) { int retval = NO_LOADER; @@ -115,14 +115,14 @@ elf32_find_loader(const struct elf32* elf, char* path_out, size_t len) struct v_file* elfile = (struct v_file*)elf->elf_file; for (size_t i = 0; i < elf->eheader.e_phnum; i++) { - struct elf32_phdr* phdre = &elf->pheaders[i]; + struct elf_phdr* phdre = &elf->pheaders[i]; if (phdre->p_type == PT_INTERP) { if (len >= phdre->p_filesz) { return EINVAL; } retval = - elf32_read(elfile, path_out, phdre->p_offset, phdre->p_filesz); + elf_read(elfile, path_out, phdre->p_offset, phdre->p_filesz); if (retval < 0) { return retval; @@ -135,16 +135,16 @@ elf32_find_loader(const struct elf32* elf, char* path_out, size_t len) return retval; } -int -elf32_read_ehdr(struct elf32* elf) +defualt int +elf_read_ehdr(struct elf* elf) { struct v_file* elfile = (struct v_file*)elf->elf_file; - return elf32_read(elfile, (void*)&elf->eheader, 0, SIZE_EHDR); + return elf_read(elfile, (void*)&elf->eheader, 0, SIZE_EHDR); } -int -elf32_read_phdr(struct elf32* elf) +defualt int +elf_read_phdr(struct elf* elf) { int status = 0; @@ -153,13 +153,13 @@ elf32_read_phdr(struct elf32* elf) size_t entries = elf->eheader.e_phnum; size_t tbl_sz = entries * SIZE_PHDR; - struct elf32_phdr* phdrs = valloc(tbl_sz); + struct elf_phdr* phdrs = valloc(tbl_sz); if (!phdrs) { return ENOMEM; } - status = elf32_read(elfile, phdrs, elf->eheader.e_phoff, tbl_sz); + status = elf_read(elfile, phdrs, elf->eheader.e_phoff, tbl_sz); if (status < 0) { vfree(phdrs); @@ -170,20 +170,18 @@ elf32_read_phdr(struct elf32* elf) return entries; } -int -elf32_check_exec(const struct elf32* elf, int type) +defualt int +elf_check_exec(const struct elf* elf, int type) { - const struct elf32_ehdr* ehdr = &elf->eheader; + const struct elf_ehdr* ehdr = &elf->eheader; return (ehdr->e_entry) && ehdr->e_type == type; } -int -elf32_check_arch(const struct elf32* elf) +defualt int +elf_check_arch(const struct elf* elf) { - const struct elf32_ehdr* ehdr = &elf->eheader; + const struct elf_ehdr* ehdr = &elf->eheader; - return *(u32_t*)(ehdr->e_ident) == ELFMAGIC && - ehdr->e_ident[EI_CLASS] == ELFCLASS32 && - ehdr->e_ident[EI_DATA] == ELFDATA2LSB && ehdr->e_machine == EM_386; + return *(u32_t*)(ehdr->e_ident) == ELFMAGIC_LE; } \ No newline at end of file diff --git a/lunaix-os/kernel/exe/elf32/ldelf32.c b/lunaix-os/kernel/exe/elf-generic/ldelf.c similarity index 73% rename from lunaix-os/kernel/exe/elf32/ldelf32.c rename to lunaix-os/kernel/exe/elf-generic/ldelf.c index 69ae939..eaabf16 100644 --- a/lunaix-os/kernel/exe/elf32/ldelf32.c +++ b/lunaix-os/kernel/exe/elf-generic/ldelf.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -7,9 +7,9 @@ #include int -elf32_smap(struct load_context* ldctx, - const struct elf32* elf, - struct elf32_phdr* phdre, +elf_smap(struct load_context* ldctx, + const struct elf* elf, + struct elf_phdr* phdre, uintptr_t base_va) { struct v_file* elfile = (struct v_file*)elf->elf_file; @@ -28,12 +28,13 @@ elf32_smap(struct load_context* ldctx, } uintptr_t va = phdre->p_va + base_va; - struct exec_container* container = ldctx->container; + struct exec_host* container = ldctx->container; struct mmap_param param = { .vms_mnt = container->vms_mnt, .pvms = vmspace(container->proc), .proct = proct, .offset = page_aligned(phdre->p_offset), .mlen = page_upaligned(phdre->p_memsz), + .flen = phdre->p_filesz, .flags = MAP_FIXED | MAP_PRIVATE, .type = REGION_TYPE_CODE }; @@ -58,25 +59,25 @@ load_executable(struct load_context* context, const struct v_file* exefile) int errno = 0; char* ldpath = NULL; - struct elf32 elf; - struct exec_container* container = context->container; + struct elf elf; + struct exec_host* container = context->container; - if ((errno = elf32_openat(&elf, exefile))) { + if ((errno = elf_openat(&elf, exefile))) { goto done; } - if (!elf32_check_arch(&elf)) { + if (!elf_check_arch(&elf)) { errno = EINVAL; goto done; } - if (!(elf32_check_exec(&elf, ET_EXEC) || elf32_check_exec(&elf, ET_DYN))) { + if (!(elf_check_exec(&elf, ET_EXEC) || elf_check_exec(&elf, ET_DYN))) { errno = ENOEXEC; goto done; } ldpath = valloc(256); - errno = elf32_find_loader(&elf, ldpath, 256); + errno = elf_find_loader(&elf, ldpath, 256); uintptr_t load_base = 0; if (errno < 0) { @@ -84,20 +85,18 @@ load_executable(struct load_context* context, const struct v_file* exefile) } if (errno != NO_LOADER) { - container->argv_pp[1] = ldpath; - // close old elf - if ((errno = elf32_close(&elf))) { + if ((errno = elf_close(&elf))) { goto done; } // open the loader instead - if ((errno = elf32_open(&elf, ldpath))) { + if ((errno = elf_open(&elf, ldpath))) { goto done; } // Is this the valid loader? - if (!elf32_static_linked(&elf) || !elf32_check_exec(&elf, ET_DYN)) { + if (!elf_static_linked(&elf) || !elf_check_exec(&elf, ET_DYN)) { errno = ELIBBAD; goto done_close_elf32; } @@ -110,7 +109,7 @@ load_executable(struct load_context* context, const struct v_file* exefile) struct v_file* elfile = (struct v_file*)elf.elf_file; for (size_t i = 0; i < elf.eheader.e_phnum && !errno; i++) { - struct elf32_phdr* phdr = &elf.pheaders[i]; + struct elf_phdr* phdr = &elf.pheaders[i]; if (phdr->p_type != PT_LOAD) { continue; @@ -122,15 +121,13 @@ load_executable(struct load_context* context, const struct v_file* exefile) break; } - errno = elf32_smap(context, &elf, phdr, load_base); + errno = elf_smap(context, &elf, phdr, load_base); } done_close_elf32: - elf32_close(&elf); + elf_close(&elf); done: - if (!container->argv_pp[1]) { - vfree_safe(ldpath); - } + vfree_safe(ldpath); return errno; } diff --git a/lunaix-os/kernel/exe/elf32/LBuild b/lunaix-os/kernel/exe/elf32/LBuild deleted file mode 100644 index ef8cd25..0000000 --- a/lunaix-os/kernel/exe/elf32/LBuild +++ /dev/null @@ -1,4 +0,0 @@ -sources([ - "elf32bfmt.c", - "ldelf32.c" -]) \ No newline at end of file diff --git a/lunaix-os/kernel/exe/exec.c b/lunaix-os/kernel/exe/exec.c index 0f6e4ec..38c2121 100644 --- a/lunaix-os/kernel/exe/exec.c +++ b/lunaix-os/kernel/exe/exec.c @@ -16,8 +16,72 @@ #include +#define _push(ptr, type, val) \ + do { \ + ptr = __ptr(&((type*)ptr)[-1]); \ + *((type*)ptr) = (val); \ + } while(0) + +static int +__place_arrayptrs(struct exec_host* container, struct exec_arrptr* param) +{ + int len; + ptr_t usp, usp_top; + ptr_t* ptrs; + unsigned int* reloc_off; + + usp_top = container->stack_top; + usp = usp_top; + ptrs = (ptr_t*)param->raw; + + if (!param->len) { + _push(usp, unsigned int, 0); + + goto done; + } + + len = param->len; + reloc_off = valloc(len * sizeof(unsigned int)); + + char* el; + size_t el_sz, sz_acc = 0; + for (int i = 0; i < len; i++) + { + el= (char*)ptrs[i]; + el_sz = strnlen(el, MAX_PARAM_SIZE) + 1; + + usp -= el_sz; + sz_acc += el_sz; + strncpy((char*)usp, el, el_sz); + + reloc_off[i] = sz_acc; + } + + param->size = sz_acc; + + ptr_t* toc = (ptr_t*)(usp) - 1; + toc[0] = 0; + + toc = &toc[-1]; + for (int i = 0, j = len - 1; i < len; i++, j--) + { + toc[-i] = usp_top - (ptr_t)reloc_off[j]; + } + + toc[-len] = (ptr_t)len; + + usp = __ptr(&toc[-len]); + param->copied = __ptr(&toc[-len + 1]); + + vfree(reloc_off); + +done: + container->stack_top = usp; + return 0; +} + void -exec_init_container(struct exec_container* param, +exec_init_container(struct exec_host* param, struct thread* thread, ptr_t vms, const char** argv, @@ -25,90 +89,89 @@ exec_init_container(struct exec_container* param, { assert(thread->ustack); ptr_t ustack_top = align_stack(thread->ustack->end - 1); - *param = (struct exec_container){ .proc = thread->process, - .vms_mnt = vms, - .exe = { .container = param }, - .argv_pp = { 0, 0 }, - .argv = argv, - .envp = envp, - .stack_top = ustack_top }; + *param = (struct exec_host) + { + .proc = thread->process, + .vms_mnt = vms, + .exe = { + .container = param + }, + .argv = { + .raw = __ptr(argv) + }, + .envp = { + .raw = __ptr(envp) + }, + .stack_top = ustack_top + }; } -size_t -args_ptr_size(const char** paramv) -{ - size_t sz = 0; - while (*paramv) { - sz++; - paramv++; - } - - return (sz + 1) * sizeof(ptr_t); -} - -static ptr_t -copy_to_ustack(ptr_t stack_top, ptr_t* paramv) +int +count_length(struct exec_arrptr* param) { - ptr_t ptr; - size_t sz = 0; - - while ((ptr = *paramv)) { - sz = strlen((const char*)ptr) + 1; - - stack_top -= sz; - memcpy((void*)stack_top, (const void*)ptr, sz); - *paramv = stack_top; + int i = 0; + ptr_t* arr = (ptr_t*)param->raw; - paramv++; + if (!arr) { + param->len = 0; + return 0; } - return stack_top; + for (; i < MAX_PARAM_LEN && arr[i]; i++); + + param->len = i; + return i > 0 && arr[i] ? E2BIG : 0; } static void -save_process_cmd(struct proc_info* proc, ptr_t* argv) +save_process_cmd(struct proc_info* proc, struct exec_arrptr* argv) { - ptr_t ptr, *_argv = argv; - size_t total_sz = 0; - while ((ptr = *_argv)) { - total_sz += strlen((const char*)ptr) + 1; - _argv++; - } + ptr_t ptr, *argv_ptrs; + char* cmd_; if (proc->cmd) { vfree(proc->cmd); } - char* cmd_ = (char*)valloc(total_sz); - proc->cmd = cmd_; - proc->cmd_len = total_sz; + argv_ptrs = (ptr_t*)argv->copied; + cmd_ = (char*)valloc(argv->size); - while ((ptr = *argv)) { + proc->cmd = cmd_; + while ((ptr = *argv_ptrs)) { cmd_ = strcpy(cmd_, (const char*)ptr); cmd_[-1] = ' '; - argv++; + argv_ptrs++; } cmd_[-1] = '\0'; + + proc->cmd_len = argv->size; } -// externed from mm/dmm.c -extern int -create_heap(struct proc_mm* pvms, ptr_t addr); -int -exec_load(struct exec_container* container, struct v_file* executable) +static int +__place_params(struct exec_host* container) { int errno = 0; - const char **argv = container->argv, **envp = container->envp; - const char** argv_extra = container->argv_pp; - - argv_extra[0] = executable->dnode->name.value; - - if ((errno = load_executable(&container->exe, executable))) { + errno = __place_arrayptrs(container, &container->envp); + if (errno) { goto done; } + errno = __place_arrayptrs(container, &container->argv); + +done: + return errno; +} + +int +exec_load(struct exec_host* container, struct v_file* executable) +{ + int errno = 0; + + struct exec_arrptr* argv = &container->argv; + struct exec_arrptr* envp = &container->envp; + struct proc_info* proc = container->proc; struct proc_mm* pvms = vmspace(proc); @@ -117,77 +180,40 @@ exec_load(struct exec_container* container, struct v_file* executable) pvms->heap = NULL; } - if (!argv_extra[1]) { - // If loading a statically linked file, then heap remapping we can do, - // otherwise delayed. - create_heap(vmspace(proc), page_aligned(container->exe.end)); - } - - if (container->vms_mnt == VMS_SELF) { - // we are loading executable into current addr space - - ptr_t ustack = container->stack_top; - size_t argv_len = 0, envp_len = 0; - ptr_t argv_ptr = 0, envp_ptr = 0; - - if (envp) { - argv_len = args_ptr_size(envp); - ustack -= envp_len; - envp_ptr = ustack; - - memcpy((void*)ustack, (const void*)envp, envp_len); - ustack = copy_to_ustack(ustack, (ptr_t*)ustack); - } else { - ustack -= sizeof(ptr_t); - *((ptr_t*)ustack) = 0; - } - - if (argv) { - argv_len = args_ptr_size(argv); - ustack -= argv_len; - - memcpy((void*)ustack, (const void**)argv, argv_len); - } else { - ustack -= sizeof(ptr_t); - *((ptr_t*)ustack) = 0; - } - - for (size_t i = 0; i < 2 && argv_extra[i]; i++) { - ustack -= sizeof(ptr_t); - *((ptr_t*)ustack) = (ptr_t)argv_extra[i]; - argv_len += sizeof(ptr_t); - } - - argv_ptr = ustack; - ustack = copy_to_ustack(ustack, (ptr_t*)ustack); - - save_process_cmd(proc, (ptr_t*)argv_ptr); - - // four args (arg{c|v}, env{c|p}) for main - struct uexec_param* exec_param = &((struct uexec_param*)ustack)[-1]; - - container->stack_top = (ptr_t)exec_param; - - *exec_param = - (struct uexec_param){ .argc = (argv_len - 1) / sizeof(ptr_t), - .argv = (char**)argv_ptr, - .envc = (envp_len - 1) / sizeof(ptr_t), - .envp = (char**)envp_ptr }; - } else { + if (!active_vms(container->vms_mnt)) { /* - TODO Inject to remote user stack with our procvm_remote toolsets - Need a better way to factorise the argv/envp length calculating + TODO Setup remote mapping of user stack for later use */ fail("not implemented"); } + if ((errno = count_length(argv))) { + goto done; + } + + if ((errno = count_length(envp))) { + goto done; + } + + errno = __place_params(container); + if (errno) { + goto done; + } + + save_process_cmd(proc, argv); + + errno = load_executable(&container->exe, executable); + if (errno) { + goto done; + } + done: return errno; } int -exec_load_byname(struct exec_container* container, const char* filename) +exec_load_byname(struct exec_host* container, const char* filename) { int errno = 0; struct v_dnode* dnode; @@ -222,7 +248,9 @@ int exec_kexecve(const char* filename, const char* argv[], const char* envp[]) { int errno = 0; - struct exec_container container; + struct exec_host container; + + assert(argv && envp); exec_init_container(&container, current_thread, VMS_SELF, argv, envp); @@ -252,7 +280,12 @@ __DEFINE_LXSYSCALL3(int, envp[]) { int errno = 0; - struct exec_container container; + struct exec_host container; + + if (!argv || !envp) { + errno = EINVAL; + goto done; + } exec_init_container( &container, current_thread, VMS_SELF, argv, envp); @@ -263,8 +296,7 @@ __DEFINE_LXSYSCALL3(int, // we will jump to new entry point (_u_start) upon syscall's // return so execve 'will not return' from the perspective of it's invoker - hart_flow_redirect(current_thread->hstate, - container.exe.entry, container.stack_top); + exec_arch_prepare_entry(current_thread, &container); // these become meaningless once execved! current_thread->ustack_top = 0; diff --git a/lunaix-os/kernel/fs/fs_export.c b/lunaix-os/kernel/fs/fs_export.c index dffff0a..1fedef6 100644 --- a/lunaix-os/kernel/fs/fs_export.c +++ b/lunaix-os/kernel/fs/fs_export.c @@ -41,11 +41,9 @@ void __version_rd(struct twimap* map) { twimap_printf(map, - "LunaixOS version %s (gnu-cc %s) %s %s", - CONFIG_LUNAIX_VER, - __VERSION__, - __DATE__, - __TIME__); + "Lunaix " + CONFIG_LUNAIX_VER + " (gnu-cc " __VERSION__ ") " __DATE__ " " __TIME__); } void diff --git a/lunaix-os/kernel/fs/iso9660/file.c b/lunaix-os/kernel/fs/iso9660/file.c index a48da33..23c4066 100644 --- a/lunaix-os/kernel/fs/iso9660/file.c +++ b/lunaix-os/kernel/fs/iso9660/file.c @@ -4,7 +4,7 @@ #include #include -#include +#include int iso9660_open(struct v_inode* this, struct v_file* file) @@ -83,7 +83,7 @@ done: int iso9660_read_page(struct v_inode* inode, void* buffer, size_t fpos) { - return iso9660_read(inode, buffer, MEM_PAGE, fpos); + return iso9660_read(inode, buffer, PAGE_SIZE, fpos); } int diff --git a/lunaix-os/kernel/fs/twifs/twifs.c b/lunaix-os/kernel/fs/twifs/twifs.c index 6aa5f6a..2894f96 100644 --- a/lunaix-os/kernel/fs/twifs/twifs.c +++ b/lunaix-os/kernel/fs/twifs/twifs.c @@ -18,7 +18,7 @@ #include #include -#include +#include static struct twifs_node* fs_root; @@ -92,7 +92,7 @@ __twifs_fwrite(struct v_inode* inode, void* buffer, size_t len, size_t fpos) int __twifs_fwrite_pg(struct v_inode* inode, void* buffer, size_t fpos) { - return __twifs_fwrite(inode, buffer, MEM_PAGE, fpos); + return __twifs_fwrite(inode, buffer, PAGE_SIZE, fpos); } int @@ -108,7 +108,7 @@ __twifs_fread(struct v_inode* inode, void* buffer, size_t len, size_t fpos) int __twifs_fread_pg(struct v_inode* inode, void* buffer, size_t fpos) { - return __twifs_fread(inode, buffer, MEM_PAGE, fpos); + return __twifs_fread(inode, buffer, PAGE_SIZE, fpos); } struct twifs_node* diff --git a/lunaix-os/kernel/fs/twimap.c b/lunaix-os/kernel/fs/twimap.c index 6b74ad3..e72f6c2 100644 --- a/lunaix-os/kernel/fs/twimap.c +++ b/lunaix-os/kernel/fs/twimap.c @@ -6,9 +6,9 @@ #include #include -#include +#include -#define TWIMAP_BUFFER_SIZE MEM_PAGE +#define TWIMAP_BUFFER_SIZE PAGE_SIZE void __twimap_default_reset(struct twimap* map) @@ -32,7 +32,7 @@ __twimap_file_read(struct v_inode* inode, void* buf, size_t len, size_t fpos) static int __twimap_file_read_page(struct v_inode* inode, void* buf, size_t fpos) { - return __twimap_file_read(inode, buf, MEM_PAGE, fpos); + return __twimap_file_read(inode, buf, PAGE_SIZE, fpos); } int diff --git a/lunaix-os/kernel/fs/vfs.c b/lunaix-os/kernel/fs/vfs.c index eb42396..d4bc56a 100644 --- a/lunaix-os/kernel/fs/vfs.c +++ b/lunaix-os/kernel/fs/vfs.c @@ -116,7 +116,7 @@ __dcache_hash(struct v_dnode* parent, u32_t* hash) // 确保低位更加随机 _hash = _hash ^ (_hash >> VFS_HASHBITS); // 与parent的指针值做加法,来减小碰撞的可能性。 - _hash += (u32_t)parent; + _hash += (u32_t)__ptr(parent); *hash = _hash; return &dnode_cache[_hash & VFS_HASH_MASK]; } diff --git a/lunaix-os/kernel/kinit.c b/lunaix-os/kernel/kinit.c index c74f8b3..685c3d0 100644 --- a/lunaix-os/kernel/kinit.c +++ b/lunaix-os/kernel/kinit.c @@ -92,7 +92,6 @@ kernel_bootstrap(struct boot_handoff* bhctx) * and start geting into uspace */ boot_end(bhctx); - boot_cleanup(); spawn_lunad(); } @@ -122,9 +121,16 @@ void kmem_init(struct boot_handoff* bhctx) { pte_t* ptep = mkptep_va(VMS_SELF, KERNEL_RESIDENT); + ptep = mkl0tep(ptep); + unsigned int i = ptep_vfn(ptep); do { + if (l0tep_impile_vmnts(ptep)) { + ptep++; + continue; + } + #if LnT_ENABLED(1) assert(mkl1t(ptep++, 0, KERNEL_DATA)); #elif LnT_ENABLED(2) @@ -134,7 +140,7 @@ kmem_init(struct boot_handoff* bhctx) #else assert(mklft(ptep++, 0, KERNEL_DATA)); #endif - } while (ptep_vfn(ptep) < MAX_PTEN - 2); + } while (++i < MAX_PTEN); // allocators cake_init(); diff --git a/lunaix-os/kernel/kprint/kprintf.c b/lunaix-os/kernel/kprint/kprintf.c index 512e797..4c1ee40 100644 --- a/lunaix-os/kernel/kprint/kprintf.c +++ b/lunaix-os/kernel/kprint/kprintf.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,16 @@ shift_level(const char* str, int* level) return str; } +static inline void +kprintf_put(int level, const char* buf, size_t sz) +{ + kprec_put(&kprecs, level, buf, sz); + + if (likely(sysconsole)) { + sysconsole->ops.write(sysconsole, buf, 0, sz); + } +} + static inline void kprintf_ml(const char* component, int level, const char* fmt, va_list args) { @@ -45,11 +56,7 @@ kprintf_ml(const char* component, int level, const char* fmt, va_list args) ksnprintf(buf, MAX_BUFSZ_HLF, "%s: %s\n", component, fmt); size_t sz = ksnprintfv(tmp_buf, buf, MAX_BUFSZ_HLF, args); - kprec_put(&kprecs, level, tmp_buf, sz); - - if (likely(sysconsole)) { - sysconsole->ops.write(sysconsole, tmp_buf, 0, sz); - } + kprintf_put(level, tmp_buf, sz); } void @@ -100,7 +107,8 @@ kprintf_dump_logs() { } } -__DEFINE_LXSYSCALL3(void, syslog, int, level, const char*, fmt, va_list, args) +__DEFINE_LXSYSCALL3(void, syslog, int, level, + const char*, buf, unsigned int, size) { - kprintf_ml("syslog", level, fmt, args); + kprintf_put(level, buf, size); } \ No newline at end of file diff --git a/lunaix-os/kernel/lunad.c b/lunaix-os/kernel/lunad.c index 1f2fb95..ff52e71 100644 --- a/lunaix-os/kernel/lunad.c +++ b/lunaix-os/kernel/lunad.c @@ -41,8 +41,10 @@ int exec_initd() { int errno = 0; + const char* argv[] = { "/mnt/lunaix-os/usr/bin/init", 0 }; + const char* envp[] = { 0 }; - if ((errno = exec_kexecve("/mnt/lunaix-os/usr/bin/init", NULL, NULL))) { + if ((errno = exec_kexecve(argv[0], argv, envp))) { goto fail; } diff --git a/lunaix-os/kernel/mm/dmm.c b/lunaix-os/kernel/mm/dmm.c index f665e9d..50804db 100644 --- a/lunaix-os/kernel/mm/dmm.c +++ b/lunaix-os/kernel/mm/dmm.c @@ -42,7 +42,7 @@ __DEFINE_LXSYSCALL1(void*, sbrk, ssize_t, incr) assert(heap); int err = mem_adjust_inplace(&pvms->regions, heap, heap->end + incr); if (err) { - return (void*)DO_STATUS(err); + return (void*)__ptr(DO_STATUS(err)); } return (void*)heap->end; } diff --git a/lunaix-os/kernel/mm/fault.c b/lunaix-os/kernel/mm/fault.c index e840b76..5bbf6ee 100644 --- a/lunaix-os/kernel/mm/fault.c +++ b/lunaix-os/kernel/mm/fault.c @@ -25,23 +25,25 @@ __gather_memaccess_info(struct fault_context* context) context->mm = vmspace(__current); - if (mnt < VMS_MOUNT_1) { + if (!vmnt_packed(ptep)) { refva = (ptr_t)ptep; goto done; } context->ptep_fault = true; - context->remote_fault = (mnt != VMS_SELF); + context->remote_fault = !active_vms(mnt); if (context->remote_fault && context->mm) { context->mm = context->mm->guest_mm; assert(context->mm); } + // unpack the ptep to reveal the one true va! + #if LnT_ENABLED(1) ptep = (pte_t*)page_addr(ptep_pfn(ptep)); mnt = ptep_vm_mnt(ptep); - if (mnt < VMS_MOUNT_1) { + if (!vmnt_packed(ptep)) { refva = (ptr_t)ptep; goto done; } @@ -50,7 +52,7 @@ __gather_memaccess_info(struct fault_context* context) #if LnT_ENABLED(2) ptep = (pte_t*)page_addr(ptep_pfn(ptep)); mnt = ptep_vm_mnt(ptep); - if (mnt < VMS_MOUNT_1) { + if (!vmnt_packed(ptep)) { refva = (ptr_t)ptep; goto done; } @@ -59,7 +61,7 @@ __gather_memaccess_info(struct fault_context* context) #if LnT_ENABLED(3) ptep = (pte_t*)page_addr(ptep_pfn(ptep)); mnt = ptep_vm_mnt(ptep); - if (mnt < VMS_MOUNT_1) { + if (!vmnt_packed(ptep)) { refva = (ptr_t)ptep; goto done; } @@ -68,7 +70,7 @@ __gather_memaccess_info(struct fault_context* context) ptep = (pte_t*)page_addr(ptep_pfn(ptep)); mnt = ptep_vm_mnt(ptep); - assert(mnt < VMS_MOUNT_1); + assert(!vmnt_packed(ptep)); refva = (ptr_t)ptep; done: @@ -174,14 +176,17 @@ __handle_anon_region(struct fault_context* fault) static void __handle_named_region(struct fault_context* fault) { + int errno = 0; struct mm_region* vmr = fault->vmr; struct v_file* file = vmr->mfile; + struct v_file_ops * fops = file->ops; pte_t pte = fault->resolving; ptr_t fault_va = page_aligned(fault->fault_va); u32_t mseg_off = (fault_va - vmr->start); u32_t mfile_off = mseg_off + vmr->foff; + size_t mapped_len = vmr->flen; // TODO Potentially we can get different order of leaflet here struct leaflet* region_part = alloc_leaflet(0); @@ -189,7 +194,25 @@ __handle_named_region(struct fault_context* fault) pte = pte_setprot(pte, region_pteprot(vmr)); ptep_map_leaflet(fault->fault_ptep, pte, region_part); - int errno = file->ops->read_page(file->inode, (void*)fault_va, mfile_off); + if (mseg_off < mapped_len) { + mapped_len = MIN(mapped_len - mseg_off, PAGE_SIZE); + } + else { + mapped_len = 0; + } + + if (mapped_len == PAGE_SIZE) { + errno = fops->read_page(file->inode, (void*)fault_va, mfile_off); + } + else { + leaflet_wipe(region_part); + + if (mapped_len) { + errno = fops->read(file->inode, + (void*)fault_va, mapped_len, mfile_off); + } + } + if (errno < 0) { ERROR("fail to populate page (%d)", errno); @@ -208,7 +231,7 @@ static void __handle_kernel_page(struct fault_context* fault) { // we must ensure only ptep fault is resolvable - if (fault->fault_va < VMS_MOUNT_1) { + if (!is_ptep(fault->fault_va)) { return; } @@ -341,4 +364,6 @@ intr_routine_page_fault(const struct hart_state* hstate) leaflet_return(fault.prealloc); } } + + tlb_flush_kernel(fault.fault_va); } \ No newline at end of file diff --git a/lunaix-os/kernel/mm/mmap.c b/lunaix-os/kernel/mm/mmap.c index 10d4f2c..972bbee 100644 --- a/lunaix-os/kernel/mm/mmap.c +++ b/lunaix-os/kernel/mm/mmap.c @@ -232,6 +232,7 @@ found: ((param->proct | param->flags) & 0x3f) | (param->type & ~0xffff)); region->mfile = file; + region->flen = param->flen; region->foff = param->offset; region->proc_vms = param->pvms; @@ -459,9 +460,14 @@ mem_unmap(ptr_t mnt, vm_regions_t* regions, ptr_t addr, size_t length) } } - while (&pos->head != regions && length) { + size_t remaining = length; + while (&pos->head != regions && remaining) { n = container_of(pos->head.next, typeof(*pos), head); - __unmap_overlapped_cases(mnt, pos, &cur_addr, &length); + if (pos->start > cur_addr + length) { + break; + } + + __unmap_overlapped_cases(mnt, pos, &cur_addr, &remaining); pos = n; } @@ -469,16 +475,24 @@ mem_unmap(ptr_t mnt, vm_regions_t* regions, ptr_t addr, size_t length) return 0; } -__DEFINE_LXSYSCALL3(void*, sys_mmap, void*, addr, size_t, length, va_list, lst) +__DEFINE_LXSYSCALL1(void*, sys_mmap, struct usr_mmap_param*, mparam) { - int proct = va_arg(lst, int); - int fd = va_arg(lst, u32_t); - off_t offset = va_arg(lst, off_t); - int options = va_arg(lst, int); - int errno = 0; - void* result = (void*)-1; - - ptr_t addr_ptr = (ptr_t)addr; + off_t offset; + size_t length; + int proct, fd, options; + int errno; + void* result; + ptr_t addr_ptr; + + proct = mparam->proct; + fd = mparam->fd; + offset = mparam->offset; + options = mparam->flags; + addr_ptr = __ptr(mparam->addr); + length = mparam->length; + + errno = 0; + result = (void*)-1; if (!length || length > BS_SIZE || va_offset(addr_ptr)) { errno = EINVAL; @@ -509,8 +523,10 @@ __DEFINE_LXSYSCALL3(void*, sys_mmap, void*, addr, size_t, length, va_list, lst) } } + length = ROUNDUP(length, PAGE_SIZE); struct mmap_param param = { .flags = options, - .mlen = ROUNDUP(length, PAGE_SIZE), + .mlen = length, + .flen = length, .offset = offset, .type = REGION_TYPE_GENERAL, .proct = proct, diff --git a/lunaix-os/kernel/mm/procvm.c b/lunaix-os/kernel/mm/procvm.c index 7b07560..5b7afad 100644 --- a/lunaix-os/kernel/mm/procvm.c +++ b/lunaix-os/kernel/mm/procvm.c @@ -38,14 +38,50 @@ vmscpy(ptr_t dest_mnt, ptr_t src_mnt, bool only_kernel) pte_t* ptep_kernel = mkl0tep(mkptep_va(src_mnt, KERNEL_RESIDENT)); // Build the self-reference on dest vms - pte_t* ptep_sms = mkptep_va(VMS_SELF, (ptr_t)ptep_dest); - pte_t* ptep_ssm = mkptep_va(VMS_SELF, (ptr_t)ptep_sms); + + /* + * -- What the heck are ptep_ssm and ptep_sms ? -- + * + * ptep_dest point to the pagetable itself that is mounted + * at dest_mnt (or simply mnt): + * mnt -> self -> self -> self -> L0TE@offset + * + * ptep_sms shallowed the recursion chain: + * self -> mnt -> self -> self -> L0TE@self + * + * ptep_ssm shallowed the recursion chain: + * self -> self -> mnt -> self -> L0TE@self + * + * Now, here is the problem, back to x86_32, the translation is + * a depth-3 recursion: + * L0T -> LFT -> Page + * + * So ptep_ssm will terminate at mnt and give us a leaf + * slot for allocate a fresh page table for mnt: + * self -> self -> L0TE@mnt + * + * but in x86_64 translation has extra two more step: + * L0T -> L1T -> L2T -> LFT -> Page + * + * So we must continue push down.... + * ptep_sssms shallowed the recursion chain: + * self -> self -> self -> mnt -> L0TE@self + * + * ptep_ssssm shallowed the recursion chain: + * self -> self -> self -> self -> L0TE@mnt + * + * Note: PML4: 2 extra steps + * PML5: 3 extra steps + */ + pte_t* ptep_ssm = mkl0tep_va(VMS_SELF, dest_mnt); + pte_t* ptep_sms = mkl1tep_va(VMS_SELF, dest_mnt) + VMS_SELF_L0TI; pte_t pte_sms = mkpte_prot(KERNEL_DATA); pte_sms = alloc_kpage_at(ptep_ssm, pte_sms, 0); set_pte(ptep_sms, pte_sms); tlb_flush_kernel((ptr_t)dest_mnt); + tlb_flush_kernel((ptr_t)ptep_sms); if (only_kernel) { ptep = ptep_kernel; @@ -89,7 +125,7 @@ vmscpy(ptr_t dest_mnt, ptr_t src_mnt, bool only_kernel) } cont: - if (ptep_vfn(ptep) == MAX_PTEN - 1) { + while (ptep_vfn(ptep) == MAX_PTEN - 1) { assert(level > 0); ptep = ptep_step_out(ptep); ptep_dest = ptep_step_out(ptep_dest); @@ -105,8 +141,14 @@ vmscpy(ptr_t dest_mnt, ptr_t src_mnt, bool only_kernel) assert(ptep_dest == ptepd_kernel); // Carry over the kernel (exclude last two entry) - while (ptep_vfn(ptep) < MAX_PTEN - 2) { + unsigned int i = ptep_vfn(ptep); + while (i++ < MAX_PTEN) { pte_t pte = *ptep; + + if (l0tep_impile_vmnts(ptep)) { + goto _cont; + } + assert(!pte_isnull(pte)); // Ensure it is a next level pagetable, @@ -117,12 +159,13 @@ vmscpy(ptr_t dest_mnt, ptr_t src_mnt, bool only_kernel) set_pte(ptep_dest, pte); leaflet_borrow(leaflet); - + + _cont: ptep++; ptep_dest++; } - return pte_paddr(*(ptep_dest + 1)); + return pte_paddr(pte_sms); } static void @@ -130,6 +173,7 @@ vmsfree(ptr_t vm_mnt) { struct leaflet* leaflet; pte_t* ptep_head = mkl0tep(mkptep_va(vm_mnt, 0)); + pte_t* ptep_self = mkl0tep(mkptep_va(vm_mnt, VMS_SELF)); pte_t* ptep_kernel = mkl0tep(mkptep_va(vm_mnt, KERNEL_RESIDENT)); int level = 0; @@ -158,7 +202,7 @@ vmsfree(ptr_t vm_mnt) } cont: - if (ptep_vfn(ptep) == MAX_PTEN - 1) { + while (ptep_vfn(ptep) == MAX_PTEN - 1) { ptep = ptep_step_out(ptep); leaflet = pte_leaflet_aligned(pte_at(ptep)); @@ -171,7 +215,7 @@ vmsfree(ptr_t vm_mnt) ptep++; } - leaflet = pte_leaflet_aligned(ptep_head[MAX_PTEN - 1]); + leaflet = pte_leaflet_aligned(pte_at(ptep_self)); leaflet_return(leaflet); } @@ -279,7 +323,7 @@ procvm_mount_self(struct proc_mm* mm) void procvm_unmount_self(struct proc_mm* mm) { - assert(mm->vm_mnt == VMS_SELF); + assert(active_vms(mm->vm_mnt)); mm->vm_mnt = 0; } @@ -291,7 +335,7 @@ procvm_enter_remote(struct remote_vmctx* rvmctx, struct proc_mm* mm, ptr_t vm_mnt = mm->vm_mnt; assert(vm_mnt); - pfn_t size_pn = pfn(size + MEM_PAGE); + pfn_t size_pn = pfn(size + PAGE_SIZE); assert(size_pn < REMOTEVM_MAX_PAGES); struct mm_region* region = region_get(&mm->regions, remote_base); diff --git a/lunaix-os/kernel/mm/region.c b/lunaix-os/kernel/mm/region.c index 14cada1..d8035db 100644 --- a/lunaix-os/kernel/mm/region.c +++ b/lunaix-os/kernel/mm/region.c @@ -26,7 +26,7 @@ region_create_range(ptr_t start, size_t length, u32_t attr) struct mm_region* region = valloc(sizeof(struct mm_region)); *region = (struct mm_region){ .attr = attr, .start = start, - .end = ROUNDUP(start + length, MEM_PAGE) }; + .end = ROUNDUP(start + length, PAGE_SIZE) }; return region; } diff --git a/lunaix-os/kernel/process/process.c b/lunaix-os/kernel/process/process.c index 818493a..de533c7 100644 --- a/lunaix-os/kernel/process/process.c +++ b/lunaix-os/kernel/process/process.c @@ -105,7 +105,7 @@ spawn_process_usr(struct thread** created, char* path, goto fail; } - struct exec_container container; + struct exec_host container; exec_init_container(&container, main_thread, VMS_MOUNT_1, argv, envp); if ((errno = exec_load_byname(&container, path))) { goto fail; diff --git a/lunaix-os/kernel/process/thread.c b/lunaix-os/kernel/process/thread.c index 9d93566..ef207aa 100644 --- a/lunaix-os/kernel/process/thread.c +++ b/lunaix-os/kernel/process/thread.c @@ -17,14 +17,14 @@ static ptr_t __alloc_user_thread_stack(struct proc_info* proc, struct mm_region** stack_region, ptr_t vm_mnt) { - ptr_t th_stack_top = (proc->thread_count + 1) * USR_STACK_SIZE; - th_stack_top = ROUNDUP(USR_STACK_END - th_stack_top, MEM_PAGE); + ptr_t th_stack_top = (proc->thread_count + 1) * USR_STACK_SIZE_THREAD; + th_stack_top = ROUNDUP(USR_STACK_END - th_stack_top, PAGE_SIZE); struct mm_region* vmr; struct proc_mm* mm = vmspace(proc); struct mmap_param param = { .vms_mnt = vm_mnt, .pvms = mm, - .mlen = USR_STACK_SIZE, + .mlen = USR_STACK_SIZE_THREAD, .proct = PROT_READ | PROT_WRITE, .flags = MAP_ANON | MAP_PRIVATE, .type = REGION_TYPE_STACK }; @@ -37,11 +37,12 @@ __alloc_user_thread_stack(struct proc_info* proc, return 0; } - set_pte(mkptep_va(vm_mnt, vmr->start), guard_pte); + pte_t* guardp = mkptep_va(vm_mnt, vmr->start); + set_pte(guardp, guard_pte); *stack_region = vmr; - ptr_t stack_top = align_stack(th_stack_top + USR_STACK_SIZE - 1); + ptr_t stack_top = align_stack(th_stack_top + USR_STACK_SIZE_THREAD - 1); return stack_top; } @@ -52,10 +53,9 @@ __alloc_kernel_thread_stack(struct proc_info* proc, ptr_t vm_mnt) pfn_t kstack_end = pfn(KSTACK_AREA); pte_t* ptep = mkptep_pn(vm_mnt, kstack_top); while (ptep_pfn(ptep) > kstack_end) { - ptep -= KSTACK_PAGES; + ptep -= KSTACK_PAGES + 1; - // first page in the kernel stack is guardian page - pte_t pte = *(ptep + 1); + pte_t pte = pte_at(ptep + 1); if (pte_isnull(pte)) { goto found; } @@ -65,8 +65,8 @@ __alloc_kernel_thread_stack(struct proc_info* proc, ptr_t vm_mnt) return 0; found:; - // KSTACK_PAGES = 3, removal one guardian pte, give order 1 page - struct leaflet* leaflet = alloc_leaflet(1); + unsigned int po = count_order(KSTACK_PAGES); + struct leaflet* leaflet = alloc_leaflet(po); if (!leaflet) { WARN("failed to create kernel stack: nomem\n"); @@ -134,6 +134,10 @@ create_thread(struct proc_info* proc, bool with_ustack) th->kstack = kstack; th->ustack = ustack_region; + + if (ustack_region) { + th->ustack_top = align_stack(ustack_region->end - 1); + } return th; } @@ -150,11 +154,7 @@ start_thread(struct thread* th, ptr_t entry) if (!kernel_addr(entry)) { assert(th->ustack); - ptr_t ustack_top = align_stack(th->ustack->end - 1); - ustack_top -= 16; // pre_allocate a 16 byte for inject parameter - hart_user_transfer(&transition, th->kstack, ustack_top, entry); - - th->ustack_top = ustack_top; + hart_user_transfer(&transition, th->kstack, th->ustack_top, entry); } else { hart_kernel_transfer(&transition, th->kstack, entry); @@ -185,21 +185,23 @@ thread_find(struct proc_info* proc, tid_t tid) return NULL; } -__DEFINE_LXSYSCALL4(int, th_create, tid_t*, tid, struct uthread_info*, thinfo, - void*, entry, void*, param) +__DEFINE_LXSYSCALL3(int, th_create, tid_t*, tid, + struct uthread_param*, thparam, void*, entry) { struct thread* th = create_thread(__current, true); if (!th) { return EAGAIN; } - start_thread(th, (ptr_t)entry); + ptr_t ustack_top; + + ustack_top = th->ustack_top; + ustack_top = align_stack(ustack_top - sizeof(*thparam)); - ptr_t ustack_top = th->ustack_top; - *((void**)ustack_top) = param; + memcpy((void*)ustack_top, thparam, sizeof(*thparam)); - thinfo->th_stack_sz = region_size(th->ustack); - thinfo->th_stack_top = (void*)ustack_top; + th->ustack_top = ustack_top; + start_thread(th, (ptr_t)entry); if (tid) { *tid = th->tid; diff --git a/lunaix-os/libs/klibc/itoa.c b/lunaix-os/libs/klibc/itoa.c index cbffd52..9eaf55b 100644 --- a/lunaix-os/libs/klibc/itoa.c +++ b/lunaix-os/libs/klibc/itoa.c @@ -5,12 +5,12 @@ char base_char[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static char* -__uitoa_internal(unsigned int value, char* str, int base, unsigned int* size) +__uitoa_internal(unsigned long value, char* str, int base, unsigned int* size) { unsigned int ptr = 0; do { str[ptr] = base_char[value % base]; - value = value / base; + value = value / (unsigned long)base; ptr++; } while (value); @@ -27,11 +27,11 @@ __uitoa_internal(unsigned int value, char* str, int base, unsigned int* size) } static char* -__itoa_internal(int value, char* str, int base, unsigned int* size) +__itoa_internal(long value, char* str, int base, unsigned int* size) { if (value < 0 && base == 10) { str[0] = '-'; - unsigned int _v = (unsigned int)(-value); + unsigned long _v = (unsigned long)(-value); __uitoa_internal(_v, str + 1, base, size); } else { __uitoa_internal(value, str, base, size); @@ -41,7 +41,7 @@ __itoa_internal(int value, char* str, int base, unsigned int* size) } char* -itoa(int value, char* str, int base) +itoa(long value, char* str, int base) { return __itoa_internal(value, str, base, NULL); } \ No newline at end of file diff --git a/lunaix-os/link/base.ldx b/lunaix-os/link/base.ldx new file mode 100644 index 0000000..e62aae3 --- /dev/null +++ b/lunaix-os/link/base.ldx @@ -0,0 +1,7 @@ +#ifndef __LUNAIX_BASE_LD_INC +#define __LUNAIX_BASE_LD_INC + +#define __LD__ +#include + +#endif /* __LUNAIX_BASE_LD_INC */ diff --git a/lunaix-os/link/kernel.ldx b/lunaix-os/link/kernel.ldx new file mode 100644 index 0000000..a259e1b --- /dev/null +++ b/lunaix-os/link/kernel.ldx @@ -0,0 +1,31 @@ +.text BLOCK(PAGE_GRAN) : AT ( ADDR(.text) - KEXEC_BASE ) +{ + *(.text) +} + +.kf.preempt BLOCK(PAGE_GRAN) : AT ( ADDR(.kf.preempt) - KEXEC_BASE ) +{ + PROVIDE(__kf_preempt_start = .); + + KEEP(*(.kf.preempt)); + + PROVIDE(__kf_preempt_end = .); +} + +PROVIDE(__kexec_text_end = .); + +.data BLOCK(PAGE_GRAN) : AT ( ADDR(.data) - KEXEC_BASE ) +{ + *(.data) +} + +.rodata BLOCK(PAGE_GRAN) : AT ( ADDR(.rodata) - KEXEC_BASE ) +{ + *(.rodata) + *(.rodata.*) +} + +.kpg BLOCK(PAGE_GRAN) : AT ( ADDR(.kpg) - KEXEC_BASE ) +{ + *(.kpg) +} \ No newline at end of file diff --git a/lunaix-os/link/lga.ldx b/lunaix-os/link/lga.ldx new file mode 100644 index 0000000..3d8962a --- /dev/null +++ b/lunaix-os/link/lga.ldx @@ -0,0 +1,101 @@ +#include "base.ldx" + +.lga BLOCK(PAGE_GRAN) : AT ( ADDR(.lga) - KEXEC_BASE ) +{ + PROVIDE(__lga_twiplugin_inits_start = .); + + KEEP(*(.lga.twiplugin_inits)); + + PROVIDE(__lga_twiplugin_inits_end = .); + + /* ---- */ + + /* align to 8 bytes, so it can cover both 32 and 64 bits address line*/ + . = ALIGN(8); + + PROVIDE(__lga_devdefs_start = .); + + KEEP(*(.lga.devdefs)); + + PROVIDE(__lga_devdefs_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_dev_ld_kboot_start = .); + + KEEP(*(.lga.devdefs.ld_kboot)); + + PROVIDE(__lga_dev_ld_kboot_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_dev_ld_sysconf_start = .); + + KEEP(*(.lga.devdefs.ld_sysconf)); + + PROVIDE(__lga_dev_ld_sysconf_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_dev_ld_timedev_start = .); + + KEEP(*(.lga.devdefs.ld_timedev)); + + PROVIDE(__lga_dev_ld_timedev_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_dev_ld_post_start = .); + + KEEP(*(.lga.devdefs.ld_post)); + + PROVIDE(__lga_dev_ld_post_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_fs_start = .); + + KEEP(*(.lga.fs)); + + PROVIDE(__lga_fs_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_lunainit_on_earlyboot_start = .); + + KEEP(*(.lga.lunainit.c_earlyboot)); + + PROVIDE(__lga_lunainit_on_earlyboot_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_lunainit_on_boot_start = .); + + KEEP(*(.lga.lunainit.c_boot)); + + PROVIDE(__lga_lunainit_on_boot_end = .); + + /* ---- */ + + . = ALIGN(8); + + PROVIDE(__lga_lunainit_on_postboot_start = .); + + KEEP(*(.lga.lunainit.c_postboot)); + + PROVIDE(__lga_lunainit_on_postboot_end = .); +} \ No newline at end of file diff --git a/lunaix-os/link/linker.ld b/lunaix-os/link/linker.ld deleted file mode 100644 index c3d57da..0000000 --- a/lunaix-os/link/linker.ld +++ /dev/null @@ -1,203 +0,0 @@ -ENTRY(start_) - -SECTIONS { - . = 0x100000; - __kboot_start = .; - - /* 这里是我们的高半核初始化代码段和数据段 */ - .boot.text BLOCK(4K) : - { - *(.multiboot) - *(.boot.text) - } - - .boot.bss BLOCK(4K) : - { - *(.boot.bss) - } - - .boot.data BLOCK(4K) : - { - *(.boot.data) - } - - .boot.rodata BLOCK(4K) : - { - *(.boot.rodata) - } - - .boot.bss BLOCK(4K) : - { - *(.boot.rodata) - } - __kboot_end = ALIGN(4K); - - /* ---- boot end ---- */ - - /* ---- kernel start ---- */ - - . += 0xC0000000; - - /* 好了,我们的内核…… */ - - PROVIDE(__kexec_text_start = ALIGN(4K)); - - .text BLOCK(4K) : AT ( ADDR(.text) - 0xC0000000 ) - { - __kexec_start = .; - - *(.text) - } - - .kf.preempt BLOCK(4K) : AT ( ADDR(.kf.preempt) - 0xC0000000 ) - { - PROVIDE(__kf_preempt_start = .); - - KEEP(*(.kf.preempt)); - - PROVIDE(__kf_preempt_end = .); - } - - PROVIDE(__kexec_text_end = .); - - .data BLOCK(4K) : AT ( ADDR(.data) - 0xC0000000 ) - { - *(.data) - } - - .rodata BLOCK(4K) : AT ( ADDR(.rodata) - 0xC0000000 ) - { - *(.rodata) - } - - .kpg BLOCK(4K) : AT ( ADDR(.kpg) - 0xC0000000 ) - { - *(.kpg) - } - - . = ALIGN(4K); - - /* for generated array, we align to address line size */ - - .lga BLOCK(4K) : AT ( ADDR(.lga) - 0xC0000000 ) - { - PROVIDE(__lga_twiplugin_inits_start = .); - - KEEP(*(.lga.twiplugin_inits)); - - PROVIDE(__lga_twiplugin_inits_end = .); - - /* ---- */ - - /* align to 8 bytes, so it can cover both 32 and 64 bits address line*/ - . = ALIGN(8); - - PROVIDE(__lga_devdefs_start = .); - - KEEP(*(.lga.devdefs)); - - PROVIDE(__lga_devdefs_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_dev_ld_kboot_start = .); - - KEEP(*(.lga.devdefs.ld_kboot)); - - PROVIDE(__lga_dev_ld_kboot_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_dev_ld_sysconf_start = .); - - KEEP(*(.lga.devdefs.ld_sysconf)); - - PROVIDE(__lga_dev_ld_sysconf_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_dev_ld_timedev_start = .); - - KEEP(*(.lga.devdefs.ld_timedev)); - - PROVIDE(__lga_dev_ld_timedev_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_dev_ld_post_start = .); - - KEEP(*(.lga.devdefs.ld_post)); - - PROVIDE(__lga_dev_ld_post_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_fs_start = .); - - KEEP(*(.lga.fs)); - - PROVIDE(__lga_fs_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_lunainit_on_earlyboot_start = .); - - KEEP(*(.lga.lunainit.c_earlyboot)); - - PROVIDE(__lga_lunainit_on_earlyboot_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_lunainit_on_boot_start = .); - - KEEP(*(.lga.lunainit.c_boot)); - - PROVIDE(__lga_lunainit_on_boot_end = .); - - /* ---- */ - - . = ALIGN(8); - - PROVIDE(__lga_lunainit_on_postboot_start = .); - - KEEP(*(.lga.lunainit.c_postboot)); - - PROVIDE(__lga_lunainit_on_postboot_end = .); - - } - - .ksymtable BLOCK(4K) : AT ( ADDR(.ksymtable) - 0xC0000000 ) - { - *(.ksymtable) - } - - .bss BLOCK(4K) : AT ( ADDR(.bss) - 0xC0000000 ) - { - *(.bss) - } - - .bss.kstack BLOCK(4K) : AT ( ADDR(.bss.kstack) - 0xC0000000) - { - PROVIDE(__bsskstack_start = .); - - *(.bss.kstack) - - PROVIDE(__bsskstack_end = .); - } - - __kexec_end = ALIGN(4K); -} \ No newline at end of file diff --git a/lunaix-os/link/lunaix.ldx b/lunaix-os/link/lunaix.ldx new file mode 100644 index 0000000..e6f718a --- /dev/null +++ b/lunaix-os/link/lunaix.ldx @@ -0,0 +1,51 @@ +#define __LD__ +#include "base.ldx" + +ENTRY(ENTRY_POINT) + +SECTIONS { + . = LOAD_OFF; + + #include + + /* ---- kernel start ---- */ + + . += KEXEC_BASE; + + PROVIDE(__kexec_text_start = ALIGN(PAGE_GRAN)); + __kexec_start = ALIGN(PAGE_GRAN); + + + /* kernel executable sections */ + + #include "kernel.ldx" + + + /* link-time allocated array */ + + #include "lga.ldx" + + + /* All other stuff */ + + .ksymtable BLOCK(PAGE_GRAN) : AT ( ADDR(.ksymtable) - KEXEC_BASE ) + { + *(.ksymtable) + } + + .bss BLOCK(PAGE_GRAN) : AT ( ADDR(.bss) - KEXEC_BASE ) + { + *(.bss) + } + + .bss.kstack BLOCK(PAGE_GRAN) : AT ( ADDR(.bss.kstack) - KEXEC_BASE ) + { + PROVIDE(__bsskstack_start = .); + + *(.bss.kstack) + + PROVIDE(__bsskstack_end = .); + } + + __kexec_end = ALIGN(PAGE_GRAN); +} \ No newline at end of file diff --git a/lunaix-os/live_debug.sh b/lunaix-os/live_debug.sh new file mode 100755 index 0000000..e10f71c --- /dev/null +++ b/lunaix-os/live_debug.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +hmp_port=45454 +gdb_port=1234 +default_cmd="console=/dev/ttyS0" + +make CMDLINE=${default_cmd} ARCH=${ARCH} MODE=debug image -j5 || exit -1 + +./scripts/qemu.py \ + scripts/qemus/qemu_x86_dev.json \ + --qemu-dir "${QEMU_DIR}" \ + -v KIMG=build/lunaix.iso \ + -v QMPORT=${hmp_port} \ + -v GDB_PORT=${gdb_port} \ + -v ARCH=${ARCH} & + +QMPORT=${hmp_port} gdb build/bin/kernel.bin -ex "target remote localhost:${gdb_port}" \ No newline at end of file diff --git a/lunaix-os/makefile b/lunaix-os/makefile index 09ee4b9..5427275 100644 --- a/lunaix-os/makefile +++ b/lunaix-os/makefile @@ -1,11 +1,11 @@ mkinc_dir := $(CURDIR)/makeinc -include $(mkinc_dir)/os.mkinc include $(mkinc_dir)/toolchain.mkinc -include $(mkinc_dir)/qemu.mkinc include $(mkinc_dir)/utils.mkinc +include $(mkinc_dir)/lunabuild.mkinc ARCH ?= i386 +MODE ?= debug export ARCH DEPS := $(CC) $(LD) $(AR) xorriso grub-mkrescue @@ -16,9 +16,8 @@ kbuild_dir := build kbin_dir := $(kbuild_dir)/bin os_img_dir := $(kbuild_dir)/img -os_build_tag := $(OS_NAME)_$(ARCH)_$(OS_VER) kbin := $(kbin_dir)/kernel.bin -kimg := $(kbuild_dir)/$(os_build_tag).iso +kimg := $(kbuild_dir)/lunaix.iso $(DEPS): @echo -n "checking $@ .... " @@ -28,8 +27,6 @@ $(DEPS): echo "failed" && exit 1;\ fi -all_lconfigs = $(shell find . -name "LConfig") - $(kbuild_dir): @mkdir -p $(kbin_dir) @mkdir -p $(os_img_dir) @@ -37,80 +34,40 @@ $(kbuild_dir): @mkdir -p $(os_img_dir)/boot/grub @mkdir -p $(os_img_dir)/usr -.builder/configs.h: $(all_lconfigs) - @echo restarting configuration... - @echo - @./scripts/build-tools/luna_build.py --config --lconfig-file LConfig -o $(@D) - -.builder/lbuild.mkinc: .builder/configs.h - @./scripts/build-tools/luna_build.py LBuild --lconfig-file LConfig -o $(@D) - .PHONY: kernel export BUILD_DIR=$(kbin_dir) export BUILD_NAME=$(kbin) -kernel: .builder/lbuild.mkinc +kernel: $(lbuild_mkinc) $(call status,TASK,$(notdir $@)) + @$(MAKE) $(MKFLAGS) -I $(mkinc_dir) -f kernel.mk all +.NOTPARALLEL: .PHONY: image export KCMD=$(CMDLINE) -export _OS_NAME=$(OS_NAME) -image: usr/build kernel +export LBUILD ARCH MODE +image: $(kbuild_dir) kernel usr/build $(call status,TASK,$(notdir $@)) + $(call status,PACK,$(kimg)) + @./scripts/grub/config-grub.sh $(os_img_dir)/boot/grub/grub.cfg @cp -r usr/build/* $(os_img_dir)/usr @cp -r $(kbin_dir)/* $(os_img_dir)/boot @grub-mkrescue -o $(kimg) $(os_img_dir)\ - -- -volid "$(OS_ID) $(OS_VER)" -system_id "$(OS_NAME)" + -- -volid "$(OS_ID) $(OS_VER)" -system_id "$(OS_NAME)" \ + -report_about FAILURE -abort_on FAILURE usr/build: user -check: $(DEPS) check-cc scripts/grub/GRUB_TEMPLATE - -prepare: check $(os_img_dir) - -export BUILD_MODE=release -bootable: $(kbuild_dir) image - -export BUILD_MODE=debug -bootable-debug: $(kbuild_dir) image +prepare: $(os_img_dir) +export LBUILD ARCH MODE user: - $(call status,$@) + $(call status,TASK,$@) @$(MAKE) $(MKFLAGS) -C usr all -I $(mkinc_dir) -all: bootable - -instable: CFLAGS := -g -std=gnu99 -ffreestanding $(O) $(W) $(ARCH_OPT) -D__LUNAIXOS_DEBUG__ -instable: all - -all-debug: bootable-debug - clean: @$(MAKE) -C usr clean -I $(mkinc_dir) @$(MAKE) -f kernel.mk clean -I $(mkinc_dir) @rm -rf $(kbuild_dir) || exit 1 - @rm -f .builder/lbuild.mkinc || exit 1 - -run: all - @qemu-system-i386 $(call get_qemu_options,$(kimg)) - @sleep 1 - @telnet 127.0.0.1 $(QEMU_MON_PORT) - -debug-qemu: all-debug - @objcopy --only-keep-debug $(kbin) $(kbuild_dir)/kernel.dbg - @qemu-system-i386 $(call get_qemu_options,$(kimg)) - @sleep 1 - @QMPORT=$(QEMU_MON_PORT) gdb $(kbin) -ex "target remote localhost:1234" - -debug-qemu-vscode: all-debug - @qemu-system-i386 $(call get_qemu_options,$(kimg)) - @sleep 0.5 - @telnet 127.0.0.1 $(QEMU_MON_PORT) - -debug-bochs: all-debug - @bochs -q -f bochs.cfg - -debug-metal: - @printf "@cmc" > $(PORT) - @gdb -s $(kbuild_dir)/kernel.dbg -ex "target remote $(PORT)" \ No newline at end of file + @rm -rf .builder || exit 1 \ No newline at end of file diff --git a/lunaix-os/makeinc/lunabuild.mkinc b/lunaix-os/makeinc/lunabuild.mkinc new file mode 100644 index 0000000..d246a6e --- /dev/null +++ b/lunaix-os/makeinc/lunabuild.mkinc @@ -0,0 +1,28 @@ +lbuild_dir := $(CURDIR)/.builder +lbuild_config_h := $(lbuild_dir)/configs.h +lbuild_mkinc := $(lbuild_dir)/lbuild.mkinc +lconfig_save := $(CURDIR)/.config.json + +lbuild_opts := --lconfig-file LConfig + +all_lconfigs = $(shell find $(CURDIR) -name "LConfig") + +export +$(lconfig_save): $(all_lconfigs) + @echo restarting configuration... + @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) --force\ + -o $(lbuild_dir)/ + +export +$(lbuild_config_h): $(lconfig_save) + @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) -o $(@D) + +export +$(lbuild_mkinc): $(lbuild_config_h) + @$(LBUILD) LBuild $(lbuild_opts) -o $(@D) + +.PHONY: config +export +config: $(all_lconfigs) + @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) --force\ + -o $(lbuild_dir)/ diff --git a/lunaix-os/makeinc/os.mkinc b/lunaix-os/makeinc/os.mkinc deleted file mode 100644 index d1f0a7b..0000000 --- a/lunaix-os/makeinc/os.mkinc +++ /dev/null @@ -1,5 +0,0 @@ -OS_NAME := lunaix -OS_ID := LunaixOS -OS_VER := dev$(shell date +%Y%m%d) - -INCLUDES := -Iincludes -Iincludes/usr \ No newline at end of file diff --git a/lunaix-os/makeinc/qemu.mkinc b/lunaix-os/makeinc/qemu.mkinc deleted file mode 100644 index 1fc8a91..0000000 --- a/lunaix-os/makeinc/qemu.mkinc +++ /dev/null @@ -1,18 +0,0 @@ -QEMU_MON_TERM := gnome-terminal -QEMU_MON_PORT := 45454 - -get_qemu_options = -s -S -m 1G \ - -smp 1 \ - -rtc base=utc \ - -no-reboot \ - -machine q35 \ - -cpu pentium3,rdrand \ - -no-shutdown \ - -d cpu_reset \ - -d trace:ide_dma_cb \ - -vga std,retrace=precise \ - -serial telnet::12345,server,nowait,logfile=lunaix_ttyS0.log\ - -drive id=cdrom,file="$(1)",readonly=on,if=none,format=raw \ - -device ahci,id=ahci \ - -device ide-cd,drive=cdrom,bus=ahci.0 \ - -monitor telnet::$(QEMU_MON_PORT),server,nowait,logfile=qm.log & \ No newline at end of file diff --git a/lunaix-os/makeinc/toolchain.mkinc b/lunaix-os/makeinc/toolchain.mkinc index 552c2fd..a538488 100644 --- a/lunaix-os/makeinc/toolchain.mkinc +++ b/lunaix-os/makeinc/toolchain.mkinc @@ -2,6 +2,7 @@ CC := $(CX_PREFIX)gcc CC := $(CX_PREFIX)gcc AS := $(CX_PREFIX)as AR := $(CX_PREFIX)ar +LBUILD ?= $(shell realpath ./scripts/build-tools/luna_build.py) O := -O2 W := -Wall -Wextra -Werror \ @@ -25,7 +26,7 @@ OFLAGS := -fno-gcse\ CFLAGS := -std=gnu99 $(OFLAGS) $(W) -ifeq ($(BUILD_MODE),debug) +ifeq ($(MODE),debug) O = -Og CFLAGS += -g endif diff --git a/lunaix-os/scripts/build-tools/lbuild/contract.py b/lunaix-os/scripts/build-tools/lbuild/contract.py index 643be14..0484947 100644 --- a/lunaix-os/scripts/build-tools/lbuild/contract.py +++ b/lunaix-os/scripts/build-tools/lbuild/contract.py @@ -31,8 +31,8 @@ class LunaBuildFile(Sandbox): def resolve(self): self.execute(self.__path) - self.__env.add_sources(self.__do_process(self.__srcs)) - self.__env.add_headers(self.__do_process(self.__hdrs)) + self.__env.add_sources(self.__srcs) + self.__env.add_headers(self.__hdrs) def __do_process(self, list): resolved = [] @@ -56,40 +56,44 @@ class LunaBuildFile(Sandbox): def __resolve_value(self, source): resolved = source - while not isinstance(resolved, str): + while isinstance(resolved, dict): if isinstance(resolved, dict): resolved = self.expand_select(resolved) else: self.__raise(f"entry with unknown type: {resolved}") - resolved = resolved.strip() - resolved = join_path(self.__dir, resolved) + if isinstance(resolved, list): + rs = [] + for file in resolved: + file = join_path(self.__dir, file) + file = self.__env.to_wspath(file) + rs.append(file) + else: + rs = join_path(self.__dir, resolved) + rs = [self.__env.to_wspath(rs)] - return self.__env.to_wspath(resolved) + return rs def import_buildfile(self, path): path = self.__resolve_value(path) - path = self.__env.to_wspath(path) - - if (os.path.isdir(path)): - path = os.path.join(path, "LBuild") - - if not os.path.exists(path): - self.__raise("Build file not exist: %s", path) + for p in path: + if (os.path.isdir(p)): + p = os.path.join(p, "LBuild") + + if not os.path.exists(p): + self.__raise("Build file not exist: %s", p) - if os.path.abspath(path) == os.path.abspath(self.__path): - self.__raise("self dependency detected") + if os.path.abspath(p) == os.path.abspath(self.__path): + self.__raise("self dependency detected") - LunaBuildFile(self.__env, path).resolve() + LunaBuildFile(self.__env, p).resolve() def export_sources(self, src): - if not isinstance(src, list): - src = [src] + src = self.__resolve_value(src) self.__srcs += src def export_headers(self, hdr): - if not isinstance(hdr, list): - hdr = [hdr] + hdr = self.__resolve_value(hdr) self.__hdrs += hdr def check_config(self, name): diff --git a/lunaix-os/scripts/build-tools/lcfg/builtins.py b/lunaix-os/scripts/build-tools/lcfg/builtins.py index 7a46699..f32b9b6 100644 --- a/lunaix-os/scripts/build-tools/lcfg/builtins.py +++ b/lunaix-os/scripts/build-tools/lcfg/builtins.py @@ -7,8 +7,7 @@ import os def v(env, caller, term): node = env.lookup_node(term.__name__) env.dependency().add(node, caller) - - return env.lookup_value(node.get_name()) + return env.resolve_symbol(node.get_name()) @contextual(caller_type=[LCModuleNode]) def include(env, caller, file): @@ -31,6 +30,10 @@ def parent(env, caller, ref): def default(env, caller, val): caller.set_default(val) +@contextual(caller_type=[LCTermNode]) +def set_value(env, caller, val): + caller.set_value(val) + @builtin() def env(env, key, default=None): return os.getenv(key, default) \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/lcfg/common.py b/lunaix-os/scripts/build-tools/lcfg/common.py index b409f1d..25e8d1b 100644 --- a/lunaix-os/scripts/build-tools/lcfg/common.py +++ b/lunaix-os/scripts/build-tools/lcfg/common.py @@ -1,7 +1,7 @@ import os.path as path import ast, json -from .lcnodes import LCModuleNode +from .lcnodes import LCModuleNode, LCTermNode from .api import ( ConfigLoadException, Renderable @@ -75,7 +75,8 @@ class DependencyGraph: if current in self._edges: for x in self._edges[current]: q.append(x) - current.evaluate() + if current != start: + current.evaluate() class ConfigTypeFactory: def __init__(self) -> None: @@ -188,6 +189,14 @@ class LConfigEnvironment(Renderable): def lookup_value(self, key): return self.__config_val[key] + def resolve_symbol(self, sym): + term_node = self.__node_table[sym] + if isinstance(term_node, LCTermNode): + if not term_node.is_ready(): + term_node.evaluate() + return term_node.get_value() + raise Exception(f"fail to resolve symbol: {sym}, not resolvable") + def dependency(self): return self.__deps_graph diff --git a/lunaix-os/scripts/build-tools/lcfg/lcnodes.py b/lunaix-os/scripts/build-tools/lcfg/lcnodes.py index cf869b0..87aa20d 100644 --- a/lunaix-os/scripts/build-tools/lcfg/lcnodes.py +++ b/lunaix-os/scripts/build-tools/lcfg/lcnodes.py @@ -200,6 +200,7 @@ class LCTermNode(LCFuncNode): self._default = None self._type = None self._rdonly = False + self._ready = False super().__init__(fo, astn) @@ -240,8 +241,11 @@ class LCTermNode(LCFuncNode): self.__assert_type(val) self._value = val + + self._ready = True self.__update_value() self._env.dependency().cascade(self) + def set_default(self, val): self.__assert_type(val) @@ -253,6 +257,9 @@ class LCTermNode(LCFuncNode): def get_value(self): return self._value + def is_ready(self): + return self._ready + def evaluate(self): super().evaluate() self.__update_value() diff --git a/lunaix-os/scripts/build-tools/luna_build.py b/lunaix-os/scripts/build-tools/luna_build.py index 3d7f21a..f5893c6 100755 --- a/lunaix-os/scripts/build-tools/luna_build.py +++ b/lunaix-os/scripts/build-tools/luna_build.py @@ -28,21 +28,23 @@ def prepare_lconfig_env(out_dir): env.register_builtin_func(builtin.default) env.register_builtin_func(builtin.include) env.register_builtin_func(builtin.env) + env.register_builtin_func(builtin.set_value) env.type_factory().regitser(lcfg_type.PrimitiveType) env.type_factory().regitser(lcfg_type.MultipleChoiceType) return env -def do_config(lcfg_env): +def do_config(opt, lcfg_env): + redo_config = not exists(opt.config_save) or opt.force + if not redo_config: + return + shell = InteractiveShell(lcfg_env) if not shell.render_loop(): print("Configuration aborted.") exit(-1) - lcfg_env.export() - lcfg_env.save() - def do_buildfile_gen(opts, lcfg_env): root_path = abspath(opts.root) ws_path = dirname(root_path) @@ -70,6 +72,8 @@ def main(): parser = ArgumentParser() parser.add_argument("--config", action="store_true", default=False) parser.add_argument("--lconfig-file", default="LConfig") + parser.add_argument("--config-save", default=".config.json") + parser.add_argument("--force", action="store_true", default=False) parser.add_argument("root", nargs="?", default="LBuild") parser.add_argument("-o", "--out-dir", required=True) @@ -79,12 +83,22 @@ def main(): mkdir(out_dir) lcfg_env = prepare_lconfig_env(out_dir) - lcfg_env.resolve_module(opts.lconfig_file) - lcfg_env.update() - lcfg_env.load() - + require_config = exists(opts.lconfig_file) + try: + if require_config: + lcfg_env.resolve_module(opts.lconfig_file) + lcfg_env.update() + lcfg_env.load() + except Exception as e: + print(e) + if opts.config: - do_config(lcfg_env) + if require_config: + do_config(opts, lcfg_env) + else: + print("No configuration file detected, skipping...") + lcfg_env.save(opts.config_save) + lcfg_env.export() else: do_buildfile_gen(opts, lcfg_env) diff --git a/lunaix-os/scripts/gen_ksymtable.sh b/lunaix-os/scripts/gen_ksymtable.sh index 720dc55..1f974bd 100755 --- a/lunaix-os/scripts/gen_ksymtable.sh +++ b/lunaix-os/scripts/gen_ksymtable.sh @@ -4,9 +4,16 @@ sym_types=$1 bin=$2 nm_out=$(nm -nfbsd "$bin") +class_info=$(readelf -h "$bin" | grep 'Class:' | awk '{print $2}') + allsyms=($nm_out) allsyms_len=${#allsyms[@]} +dtype="4byte" +if [ "$class_info" == 'ELF64' ]; then + dtype="8byte" +fi + syms_idx=() for (( i=0; i None: + self.name = name + self._opt = opt + + def get_qemu_opts(self) -> list: + pass + +class BasicSerialDevice(QEMUPeripherals): + def __init__(self, opt) -> None: + super().__init__("serial", opt) + + def get_qemu_opts(self): + link, logfile = parse_protocol(self._opt) + + cmds = [ link, "server", "nowait" ] + if logfile: + cmds.append(f"logfile={logfile}") + return [ "-serial", join_attrs(cmds) ] + +class PCISerialDevice(QEMUPeripherals): + def __init__(self, opt) -> None: + super().__init__("pci-serial", opt) + + def get_qemu_opts(self): + name = f"chrdev.{hex(self.__hash__())[2:]}" + cmds = [ "pci-serial", f"chardev={name}" ] + chrdev = [ "file", f"id={name}" ] + + logfile = get_config(self._opt, "logfile", required=True) + chrdev.append(f"path={logfile}") + () + return [ + "-chardev", join_attrs(chrdev), + "-device", join_attrs(cmds) + ] + +class AHCIBus(QEMUPeripherals): + def __init__(self, opt) -> None: + super().__init__("ahci", opt) + + def get_qemu_opts(self): + opt = self._opt + name: str = get_config(opt, "name", required=True) + name = name.strip().replace(" ", "_") + cmds = [ "-device", f"ahci,id={name}" ] + + for i, disk in enumerate(get_config(opt, "disks", default=[])): + d_type = get_config(disk, "type", default="ide-hd") + d_img = get_config(disk, "img", required=True) + d_ro = get_config(disk, "ro", default=False) + d_fmt = get_config(disk, "format", default="raw") + d_id = f"disk_{i}" + + cmds += [ + "-drive", join_attrs([ + f"id={d_id}," + f"file={d_img}", + f"readonly={'on' if d_ro else 'off'}", + f"if=none", + f"format={d_fmt}" + ]), + "-device", join_attrs([ + d_type, + f"drive={d_id}", + f"bus={name}.{i}" + ]) + ] + + return cmds + +class RTCDevice(QEMUPeripherals): + def __init__(self, opt) -> None: + super().__init__("rtc", opt) + + def get_qemu_opts(self): + opt = self._opt + base = get_config(opt, "base", default="utc") + return [ "-rtc", f"base={base}" ] + +class QEMUMonitor(QEMUPeripherals): + def __init__(self, opt) -> None: + super().__init__("monitor", opt) + + def get_qemu_opts(self): + link, logfile = parse_protocol(self._opt) + + return [ + "-monitor", join_attrs([ + link, + "server", + "nowait", + f"logfile={logfile}" + ]) + ] + +class QEMUExec: + devices = { + "basic_serial": BasicSerialDevice, + "ahci": AHCIBus, + "rtc": RTCDevice, + "hmp": QEMUMonitor, + "pci-serial": PCISerialDevice + } + + def __init__(self, options) -> None: + self._opt = options + self._devices = [] + + for dev in get_config(options, "devices", default=[]): + dev_class = get_config(dev, "class") + if dev_class not in QEMUExec.devices: + raise Exception(f"device class: {dev_class} is not defined") + + self._devices.append(QEMUExec.devices[dev_class](dev)) + + def get_qemu_exec_name(self): + pass + + def get_qemu_arch_opts(self): + cmds = [ + "-machine", get_config(self._opt, "machine"), + "-cpu", join_attrs([ + get_config(self._opt, "cpu/type", required=True), + *get_config(self._opt, "cpu/features", default=[]), + ]) + ] + + return cmds + + def get_qemu_debug_opts(self): + cmds = [ "-no-reboot", "-no-shutdown" ] + debug = get_config(self._opt, "debug") + if not debug: + return cmds + + cmds.append("-S") + cmds += [ "-gdb", f"tcp::{get_config(debug, 'gdb_port', default=1234)}" ] + + trace_opts = get_config(debug, "traced", []) + for trace in trace_opts: + cmds += [ "-d", f"trace:{trace}"] + + return cmds + + def get_qemu_general_opts(self): + return [ + "-m", get_config(self._opt, "memory", required=True), + "-smp", get_config(self._opt, "ncpu", default=1) + ] + + def add_peripheral(self, peripheral): + self._devices.append(peripheral) + + def start(self, qemu_dir_override=""): + qemu_path = self.get_qemu_exec_name() + qemu_path = os.path.join(qemu_dir_override, qemu_path) + cmds = [ + qemu_path, + *self.get_qemu_arch_opts(), + *self.get_qemu_debug_opts() + ] + + for dev in self._devices: + cmds += dev.get_qemu_opts() + + print(" ".join(cmds), "\n") + + handle = subprocess.Popen(cmds) + + while True: + ret_code = handle.poll() + if ret_code is not None: + return ret_code + time.sleep(5) + +class QEMUx86Exec(QEMUExec): + def __init__(self, options) -> None: + super().__init__(options) + + def get_qemu_exec_name(self): + if get_config(self._opt, "arch") in ["i386", "x86_32"]: + return "qemu-system-i386" + else: + return "qemu-system-x86_64" + +def main(): + global g_lookup + + arg = argparse.ArgumentParser() + + arg.add_argument("config_file") + arg.add_argument("--qemu-dir", default="") + arg.add_argument("-v", "--values", action='append', default=[]) + + arg_opt = arg.parse_args() + + opts = {} + with open(arg_opt.config_file, 'r') as f: + opts.update(json.loads(f.read())) + + for kv in arg_opt.values: + [k, v] = kv.split('=') + g_lookup[k] = v + + arch = get_config(opts, "arch") + + q = None + if arch in ["i386", "x86_32", "x86_64"]: + q = QEMUx86Exec(opts) + else: + raise Exception(f"undefined arch: {arch}") + + q.start(arg_opt.qemu_dir) + +if __name__ == "__main__": + try: + main() + except Exception as e: + print(e) \ No newline at end of file diff --git a/lunaix-os/scripts/qemus/qemu_x86_dev.json b/lunaix-os/scripts/qemus/qemu_x86_dev.json new file mode 100644 index 0000000..474ad1a --- /dev/null +++ b/lunaix-os/scripts/qemus/qemu_x86_dev.json @@ -0,0 +1,58 @@ +{ + "arch": "$ARCH", + "memory": "1G", + "machine": "q35", + "cpu": { + "type": "base", + "features": [ + "rdrand", + "clflush", + "lm", + "nx", + "syscall", + "mca", + "pse36", + "pcid", + "invpcid", + "cmov", + "apic" + ] + }, + "debug": { + "gdb_port": "$GDB_PORT", + "traced": [ + "x86_recv_fault", + "ide_dma_cb" + ] + }, + "devices": [ + { + "class": "basic_serial", + "protocol": "telnet", + "addr": ":12345", + "logfile": "lunaix_ttyS0.log" + }, + { + "class": "rtc", + "base": "utc" + }, + { + "class": "ahci", + "name": "ahci_0", + "disks": [ + { + "type": "ide-cd", + "img": "$KIMG", + "ro": true, + "format": "raw" + } + ] + }, + { + "class": "hmp", + "protocol": "telnet", + "addr": ":$QMPORT", + "logfile": "qm.log" + } + ] +} \ No newline at end of file diff --git a/lunaix-os/usr/.gitignore b/lunaix-os/usr/.gitignore index 8597c11..0d76625 100644 --- a/lunaix-os/usr/.gitignore +++ b/lunaix-os/usr/.gitignore @@ -1,3 +1,5 @@ *.o build/ -.vscode/ \ No newline at end of file +.vscode/ + +uexec.ld \ No newline at end of file diff --git a/lunaix-os/usr/LBuild b/lunaix-os/usr/LBuild new file mode 100644 index 0000000..03a89ee --- /dev/null +++ b/lunaix-os/usr/LBuild @@ -0,0 +1,38 @@ +sources([ + "testp", + "ls", + "signal_demo", + "cat", + "stat", + "test_pthread" +]) + +compile_opts([ + "-ffreestanding", + "-fno-pie" +]) + +linking_opts([ + "-nostdlib", + "-nolibc", + "-z noexecstack", + "-no-pie", +]) + +linking_opts([ + "-Wl,--build-id=none" +]) + +if config("arch") == "x86_64": + compile_opts([ + "-m64", + "-fno-unwind-tables", + "-fno-asynchronous-unwind-tables", + "-mcmodel=large" + ]) + linking_opts([ + "-m64", + ]) +else: + compile_opts("-m32") + linking_opts("-m32") diff --git a/lunaix-os/usr/LConfig b/lunaix-os/usr/LConfig new file mode 100644 index 0000000..ea38d57 --- /dev/null +++ b/lunaix-os/usr/LConfig @@ -0,0 +1,11 @@ +@Term +def arch(): + """ + set the ISA target + """ + type(["i386", "x86_64", "aarch64", "rv64"]) + default("i386") + + env_val = env("ARCH") + if env_val is not None: + set_value(env_val) \ No newline at end of file diff --git a/lunaix-os/usr/execs.list b/lunaix-os/usr/execs.list deleted file mode 100644 index 282adc6..0000000 --- a/lunaix-os/usr/execs.list +++ /dev/null @@ -1,6 +0,0 @@ -testp -ls -signal_demo -cat -stat -test_pthread \ No newline at end of file diff --git a/lunaix-os/usr/init/init.c b/lunaix-os/usr/init/init.c index 45bbf92..2416e07 100644 --- a/lunaix-os/usr/init/init.c +++ b/lunaix-os/usr/init/init.c @@ -42,6 +42,9 @@ init_termios(int fd) { return 0; } +const char* sh_argv[] = { "/usr/bin/sh", 0 }; +const char* sh_envp[] = { 0 }; + int main(int argc, const char** argv) { @@ -64,7 +67,9 @@ main(int argc, const char** argv) pid_t pid; int err = 0; if (!(pid = fork())) { - err = execve("/usr/bin/sh", NULL, NULL); + + + err = execve(sh_argv[0], sh_argv, sh_envp); printf("fail to execute (%d)\n", errno); _exit(err); } diff --git a/lunaix-os/usr/libc/LBuild b/lunaix-os/usr/libc/LBuild new file mode 100644 index 0000000..5c77fe2 --- /dev/null +++ b/lunaix-os/usr/libc/LBuild @@ -0,0 +1,28 @@ +sources([ + "src/string.c", + "src/termios.c", + "src/itoa.c", + "src/_vprintf.c", + "src/readdir.c", + "src/pthread.c", + "src/printf.c" +]) + +sources([ + "src/posix/signal.c", + "src/posix/mount.c", + "src/posix/errno.c", + "src/posix/ioctl.c", + "src/posix/fcntl.c", + "src/posix/dirent.c", + "src/posix/unistd.c", + "src/posix/mann.c", + "src/posix/lunaix.c" +]) + +use({ + env("ARCH"): { + "i386": "arch/i386", + "x86_64": "arch/x86_64", + } +}) \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/i386/LBuild b/lunaix-os/usr/libc/arch/i386/LBuild new file mode 100644 index 0000000..be547c8 --- /dev/null +++ b/lunaix-os/usr/libc/arch/i386/LBuild @@ -0,0 +1,8 @@ +sources([ + "crt0.S", + "syscall.S", + "trampoline.S", +]) + +compile_opts("-m32") +linking_opts("-m32") \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/i386/crt0.S b/lunaix-os/usr/libc/arch/i386/crt0.S index a449ac3..1418068 100644 --- a/lunaix-os/usr/libc/arch/i386/crt0.S +++ b/lunaix-os/usr/libc/arch/i386/crt0.S @@ -9,9 +9,17 @@ .section .text .global _start _start: - xorl %eax, %eax xorl %ebp, %ebp + movl %esp, %eax + andl $-16, %esp + + leal 4(%eax), %ebx + pushl %ebx + pushl (%eax) + fninit + + xorl %eax, %eax call main 1: diff --git a/lunaix-os/usr/libc/arch/i386/dirent.c b/lunaix-os/usr/libc/arch/i386/dirent.c deleted file mode 100644 index 1c45242..0000000 --- a/lunaix-os/usr/libc/arch/i386/dirent.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "syscall.h" -#include - -__LXSYSCALL2(int, sys_readdir, int, fd, struct lx_dirent*, dent) diff --git a/lunaix-os/usr/libc/arch/i386/errno.c b/lunaix-os/usr/libc/arch/i386/errno.c deleted file mode 100644 index 3ad6d52..0000000 --- a/lunaix-os/usr/libc/arch/i386/errno.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "syscall.h" -#include - -__LXSYSCALL(int, geterrno); \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/i386/fcntl.c b/lunaix-os/usr/libc/arch/i386/fcntl.c deleted file mode 100644 index c345ab7..0000000 --- a/lunaix-os/usr/libc/arch/i386/fcntl.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "syscall.h" -#include - -__LXSYSCALL2(int, open, const char*, path, int, options) - -__LXSYSCALL2(int, fstat, int, fd, struct file_stat*, stat) diff --git a/lunaix-os/usr/libc/arch/i386/ioctl.c b/lunaix-os/usr/libc/arch/i386/ioctl.c deleted file mode 100644 index 8675d22..0000000 --- a/lunaix-os/usr/libc/arch/i386/ioctl.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "syscall.h" -#include - -__LXSYSCALL2_VARG(int, ioctl, int, fd, int, req); \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/i386/lunaix.c b/lunaix-os/usr/libc/arch/i386/lunaix.c deleted file mode 100644 index 87f30bb..0000000 --- a/lunaix-os/usr/libc/arch/i386/lunaix.c +++ /dev/null @@ -1,12 +0,0 @@ -#include "syscall.h" -#include - -__LXSYSCALL(void, yield); - -__LXSYSCALL1(pid_t, wait, int*, status); - -__LXSYSCALL3(pid_t, waitpid, pid_t, pid, int*, status, int, options); - -__LXSYSCALL2_VARG(void, syslog, int, level, const char*, fmt); - -__LXSYSCALL3(int, realpathat, int, fd, char*, buf, size_t, size) diff --git a/lunaix-os/usr/libc/arch/i386/mann.c b/lunaix-os/usr/libc/arch/i386/mann.c deleted file mode 100644 index b21ea3f..0000000 --- a/lunaix-os/usr/libc/arch/i386/mann.c +++ /dev/null @@ -1,12 +0,0 @@ -#include "syscall.h" -#include - -__LXSYSCALL2_VARG(void*, sys_mmap, void*, addr, size_t, length); - -void* -mmap(void* addr, size_t length, int proct, int flags, int fd, off_t offset) -{ - return sys_mmap(addr, length, proct, fd, offset, flags); -} - -__LXSYSCALL2(void, munmap, void*, addr, size_t, length) diff --git a/lunaix-os/usr/libc/arch/i386/mount.c b/lunaix-os/usr/libc/arch/i386/mount.c deleted file mode 100644 index 637f451..0000000 --- a/lunaix-os/usr/libc/arch/i386/mount.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "syscall.h" - -__LXSYSCALL4(int, - mount, - const char*, - source, - const char*, - target, - const char*, - fstype, - int, - options) - -__LXSYSCALL1(int, unmount, const char*, target) \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/i386/syscall.S b/lunaix-os/usr/libc/arch/i386/syscall.S index 83320ea..9348988 100644 --- a/lunaix-os/usr/libc/arch/i386/syscall.S +++ b/lunaix-os/usr/libc/arch/i386/syscall.S @@ -3,9 +3,7 @@ #define LUNAIX_SYSCALL 33 #define regsize 4 - .struct 8 -saved_registers: - .struct saved_registers + 5 * regsize + .struct 2 * regsize # eip, ebp id: .struct id + regsize a1: @@ -21,29 +19,26 @@ a5: .section .text .type do_lunaix_syscall, @function .global do_lunaix_syscall + do_lunaix_syscall: push %ebp movl %esp, %ebp pushl %ebx - pushl %ecx - pushl %edx pushl %edi pushl %esi - - movl id(%esp), %eax - movl a1(%esp), %ebx - movl a2(%esp), %ecx - movl a3(%esp), %edx - movl a4(%esp), %edi - movl a5(%esp), %esi + + movl id(%ebp), %eax + movl a1(%ebp), %ebx + movl a2(%ebp), %ecx + movl a3(%ebp), %edx + movl a4(%ebp), %edi + movl a5(%ebp), %esi int $LUNAIX_SYSCALL popl %esi popl %edi - popl %edx - popl %ecx popl %ebx leave diff --git a/lunaix-os/usr/libc/arch/i386/syscall.h b/lunaix-os/usr/libc/arch/i386/syscall.h deleted file mode 100644 index 4747678..0000000 --- a/lunaix-os/usr/libc/arch/i386/syscall.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef __LUNAIX_SYSCALL_H -#define __LUNAIX_SYSCALL_H -#include - -#define asmlinkage __attribute__((regparm(0))) - -#define __PARAM_MAP1(t1, p1) t1 p1 -#define __PARAM_MAP2(t1, p1, ...) t1 p1, __PARAM_MAP1(__VA_ARGS__) -#define __PARAM_MAP3(t1, p1, ...) t1 p1, __PARAM_MAP2(__VA_ARGS__) -#define __PARAM_MAP4(t1, p1, ...) t1 p1, __PARAM_MAP3(__VA_ARGS__) -#define __PARAM_MAP5(t1, p1, ...) t1 p1, __PARAM_MAP4(__VA_ARGS__) -#define __PARAM_MAP6(t1, p1, ...) t1 p1, __PARAM_MAP5(__VA_ARGS__) - -#define ___DOINT33(callcode, rettype) \ - int v; \ - asm volatile("int %1\n" : "=a"(v) : "i"(33), "a"(callcode)); \ - return (rettype)v; - -#define __LXSYSCALL(rettype, name) \ - rettype name() \ - { \ - ___DOINT33(__SYSCALL_##name, rettype) \ - } - -#define __LXSYSCALL1(rettype, name, t1, p1) \ - rettype name(__PARAM_MAP1(t1, p1)) \ - { \ - asm("" ::"b"(p1)); \ - ___DOINT33(__SYSCALL_##name, rettype) \ - } - -#define __LXSYSCALL2(rettype, name, t1, p1, t2, p2) \ - rettype name(__PARAM_MAP2(t1, p1, t2, p2)) \ - { \ - asm("\n" ::"b"(p1), "c"(p2)); \ - ___DOINT33(__SYSCALL_##name, rettype) \ - } - -#define __LXSYSCALL3(rettype, name, t1, p1, t2, p2, t3, p3) \ - rettype name(__PARAM_MAP3(t1, p1, t2, p2, t3, p3)) \ - { \ - asm("\n" ::"b"(p1), "c"(p2), "d"(p3)); \ - ___DOINT33(__SYSCALL_##name, rettype) \ - } - -#define __LXSYSCALL4(rettype, name, t1, p1, t2, p2, t3, p3, t4, p4) \ - rettype name(__PARAM_MAP4(t1, p1, t2, p2, t3, p3, t4, p4)) \ - { \ - asm("\n" ::"b"(p1), "c"(p2), "d"(p3), "D"(p4)); \ - ___DOINT33(__SYSCALL_##name, rettype) \ - } - -#define __LXSYSCALL5(rettype, name, t1, p1, t2, p2, t3, p3, t4, p4, t5, p5) \ - rettype name(__PARAM_MAP5(t1, p1, t2, p2, t3, p3, t4, p4, t5, p5)) \ - { \ - asm("" ::"r"(p5), "b"(p1), "c"(p2), "d"(p3), "D"(p4), "S"(p5)); \ - ___DOINT33(__SYSCALL_##name, rettype) \ - } - -#define __LXSYSCALL2_VARG(rettype, name, t1, p1, t2, p2) \ - __attribute__((noinline)) rettype name(__PARAM_MAP2(t1, p1, t2, p2), ...) \ - { \ - /* No inlining! This depends on the call frame assumption */ \ - void* _last = (void*)&p2 + sizeof(void*); \ - asm("\n" ::"b"(p1), "c"(p2), "d"(_last)); \ - ___DOINT33(__SYSCALL_##name, rettype) \ - } - -#endif /* __LUNAIX_SYSCALL_H */ diff --git a/lunaix-os/usr/libc/arch/i386/trampoline.S b/lunaix-os/usr/libc/arch/i386/trampoline.S index 9261e4b..a2d84b6 100644 --- a/lunaix-os/usr/libc/arch/i386/trampoline.S +++ b/lunaix-os/usr/libc/arch/i386/trampoline.S @@ -17,4 +17,16 @@ movl $__SYSCALL_sigreturn, %eax popl %ebx + int $33 + + .global th_trampoline + th_trampoline: + movl (%esp), %eax + movl 4(%esp), %ebx + pushl %ebx + + call *(%eax) + + movl %eax, %ebx + movl $__SYSCALL_th_exit, %eax int $33 \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/i386/unistd.c b/lunaix-os/usr/libc/arch/i386/unistd.c deleted file mode 100644 index a8c8443..0000000 --- a/lunaix-os/usr/libc/arch/i386/unistd.c +++ /dev/null @@ -1,124 +0,0 @@ -#include "syscall.h" -#include - -__LXSYSCALL(pid_t, fork) - -__LXSYSCALL1(int, brk, void*, addr) - -__LXSYSCALL1(void*, sbrk, ssize_t, size) - -__LXSYSCALL(pid_t, getpid) - -__LXSYSCALL(pid_t, getppid) - -__LXSYSCALL(pid_t, getpgid) - -__LXSYSCALL2(pid_t, setpgid, pid_t, pid, pid_t, pgid) - -__LXSYSCALL1(void, _exit, int, status) - -__LXSYSCALL1(unsigned int, sleep, unsigned int, seconds) - -__LXSYSCALL(int, pause) - -__LXSYSCALL1(unsigned int, alarm, unsigned int, seconds) - -__LXSYSCALL2(int, link, const char*, oldpath, const char*, newpath) - -__LXSYSCALL1(int, rmdir, const char*, pathname) - -__LXSYSCALL3(int, read, int, fd, void*, buf, size_t, count) - -__LXSYSCALL3(int, write, int, fd, void*, buf, size_t, count) - -__LXSYSCALL3(int, readlink, const char*, path, char*, buf, size_t, size) - -__LXSYSCALL3(int, lseek, int, fd, off_t, offset, int, options) - -__LXSYSCALL1(int, unlink, const char*, pathname) - -__LXSYSCALL1(int, close, int, fd) - -__LXSYSCALL2(int, dup2, int, oldfd, int, newfd) - -__LXSYSCALL1(int, dup, int, oldfd) - -__LXSYSCALL1(int, fsync, int, fildes) - -__LXSYSCALL2(int, symlink, const char*, pathname, const char*, link_target) - -__LXSYSCALL1(int, chdir, const char*, path) - -__LXSYSCALL1(int, fchdir, int, fd) - -__LXSYSCALL2(char*, getcwd, char*, buf, size_t, size) - -__LXSYSCALL2(int, rename, const char*, oldpath, const char*, newpath) - -__LXSYSCALL4(int, - getxattr, - const char*, - path, - const char*, - name, - void*, - value, - size_t, - len) - -__LXSYSCALL4(int, - setxattr, - const char*, - path, - const char*, - name, - void*, - value, - size_t, - len) - -__LXSYSCALL4(int, - fgetxattr, - int, - fd, - const char*, - name, - void*, - value, - size_t, - len) - -__LXSYSCALL4(int, - fsetxattr, - int, - fd, - const char*, - name, - void*, - value, - size_t, - len) - -__LXSYSCALL4(int, - readlinkat, - int, - dirfd, - const char*, - pathname, - char*, - buf, - size_t, - size) - -__LXSYSCALL2(int, unlinkat, int, fd, const char*, pathname) - -__LXSYSCALL1(int, mkdir, const char*, path) - -__LXSYSCALL3(int, - execve, - const char*, - filename, - const char**, - argv, - const char**, - envp) \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/x86_64/LBuild b/lunaix-os/usr/libc/arch/x86_64/LBuild new file mode 100644 index 0000000..8e0ed54 --- /dev/null +++ b/lunaix-os/usr/libc/arch/x86_64/LBuild @@ -0,0 +1,8 @@ +sources([ + "crt0.S", + "syscall.S", + "trampoline.S", +]) + +compile_opts("-m64") +linking_opts("-m64") \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/x86_64/crt0.S b/lunaix-os/usr/libc/arch/x86_64/crt0.S new file mode 100644 index 0000000..25786df --- /dev/null +++ b/lunaix-os/usr/libc/arch/x86_64/crt0.S @@ -0,0 +1,27 @@ +#define __ASM__ +#include + +.section .data + .global environ + environ: + .long 0 + +.section .text + .global _start + _start: + xorq %rbp, %rbp + + movq %rsp, %rax + movq (%rax), %rdi + leaq 8(%rax), %rsi + + fninit + xorq %rax, %rax + call main + + 1: + movq %rax, %rbx + movq $__SYSCALL__exit, %rax + int $33 + + ud2 // should not reach \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/x86_64/syscall.S b/lunaix-os/usr/libc/arch/x86_64/syscall.S new file mode 100644 index 0000000..def99e5 --- /dev/null +++ b/lunaix-os/usr/libc/arch/x86_64/syscall.S @@ -0,0 +1,43 @@ +#include + +#define LUNAIX_SYSCALL 33 +#define regsize 8 + + .struct 2 * regsize # rip, rbp +id: + .struct id + regsize +a1: + .struct a1 + regsize +a2: + .struct a2 + regsize +a3: + .struct a3 + regsize +a4: + .struct a4 + regsize +a5: + +.section .text + .type do_lunaix_syscall, @function + .global do_lunaix_syscall + + do_lunaix_syscall: + pushq %rbp + movq %rsp, %rbp + + pushq %rbx + + movq %rcx, %r10 + + movq %rdi, %rax + movq %rsi, %rbx + movq %rdx, %rcx + movq %r10, %rdx + movq %r8, %rdi + movq %r9, %rsi + + int $LUNAIX_SYSCALL + + popq %rbx + + leave + ret \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/x86_64/trampoline.S b/lunaix-os/usr/libc/arch/x86_64/trampoline.S new file mode 100644 index 0000000..86bdfd1 --- /dev/null +++ b/lunaix-os/usr/libc/arch/x86_64/trampoline.S @@ -0,0 +1,36 @@ +#include + +.section .text + /* + stack structure: + + ...saved_state \ + void* sighand; > struct siguctx + void* sigact; / + int sig_num; <--- %rsp + */ + .global sigtrampoline + sigtrampoline: + movq %rsp, %rax + andq $-16, %rsp + pushq %rax + + movl (%rax), %edi // signum + xorq %rsi, %rsi // siginfo = NULL + leaq 4(%rax), %rdx // (struct siguctx*)&sigact + call sig_dohandling + + movq $__SYSCALL_sigreturn, %rax + popq %rbx + int $33 + + .global th_trampoline + th_trampoline: + movq (%rsp), %rax + movq 8(%rsp), %rdi + + callq %rax + + movq %rax, %rbx + movq $__SYSCALL_th_exit, %rax + int $33 \ No newline at end of file diff --git a/lunaix-os/usr/libc/includes/lunaix/mann.h b/lunaix-os/usr/libc/includes/lunaix/mann.h index fb34d99..2527572 100644 --- a/lunaix-os/usr/libc/includes/lunaix/mann.h +++ b/lunaix-os/usr/libc/includes/lunaix/mann.h @@ -5,8 +5,8 @@ #include #include -extern void* mmap(void* addr, size_t length, int proct, int flags, int fd, off_t offset); +void* mmap(void* addr, size_t length, int proct, int flags, int fd, off_t offset); -extern void munmap(void* addr, size_t length); +int munmap(void* addr, size_t length); #endif /* __LUNAIX_MANN_H */ diff --git a/lunaix-os/usr/libc/includes/lunaix/syscall.h b/lunaix-os/usr/libc/includes/lunaix/syscall.h index 9449999..26ab167 100644 --- a/lunaix-os/usr/libc/includes/lunaix/syscall.h +++ b/lunaix-os/usr/libc/includes/lunaix/syscall.h @@ -3,7 +3,7 @@ #include -extern unsigned long +extern unsigned long do_lunaix_syscall(unsigned long call_id, ...); #endif /* __LUNAIX_OSDEPS_SYSCALL_H */ diff --git a/lunaix-os/usr/libc/includes/stdio.h b/lunaix-os/usr/libc/includes/stdio.h index 26c9b56..f62e46e 100644 --- a/lunaix-os/usr/libc/includes/stdio.h +++ b/lunaix-os/usr/libc/includes/stdio.h @@ -1,10 +1,18 @@ #ifndef __LUNAIX_USTDIO_H #define __LUNAIX_USTDIO_H +#include + #define stdout 0 #define stdin 1 -extern int +int printf(const char* fmt, ...); +int +vsnprintf(char* buffer, unsigned int size, const char* fmt, va_list ap); + +int +snprintf(char* buffer, unsigned int size, const char* fmt, ...); + #endif /* __LUNAIX_USTDIO_H */ diff --git a/lunaix-os/usr/libc/makefile b/lunaix-os/usr/libc/makefile index 5c4d3b0..d2f2a06 100644 --- a/lunaix-os/usr/libc/makefile +++ b/lunaix-os/usr/libc/makefile @@ -1,20 +1,22 @@ +include lunabuild.mkinc + +include $(lbuild_mkinc) + libc_include := $(CURDIR)/includes -ARCH ?= i386 BUILD_DIR ?= bin BUILD_NAME ?= liblunac src_dirs := src src_dirs += arch/$(ARCH) -src_files := $(foreach f, $(src_dirs), $(shell find $(f) -name "*.[cS]")) -obj_files := $(addsuffix .o, $(src_files)) +obj_files := $(addsuffix .o, $(_LBUILD_SRCS)) build_lib := $(BUILD_DIR)/lib build_include := $(BUILD_DIR)/includes libc_include_opt = $(addprefix -I, $(libc_include)) -global_include_opt = $(addprefix -I, $(INCLUDES)) +global_include_opt = $(addprefix -I, $(INCLUDES) $(_LBUILD_INCS)) check_folders := $(src_dirs) check_folders += $(build_lib) $(build_include) @@ -40,6 +42,7 @@ $(build_lib)/$(BUILD_NAME): $(obj_files) @$(AR) rcs $@ $^ clean: + @rm -rf $(lbuild_dir) @rm -f $(obj_files) headers: $(libc_include) diff --git a/lunaix-os/usr/libc/src/_vprintf.c b/lunaix-os/usr/libc/src/_vprintf.c index 28dea37..16d8133 100644 --- a/lunaix-os/usr/libc/src/_vprintf.c +++ b/lunaix-os/usr/libc/src/_vprintf.c @@ -2,6 +2,7 @@ #include #include #include +#include #define NUMBUFSIZ 24 @@ -196,3 +197,19 @@ __vprintf_internal(char* buffer, const char* fmt, size_t max_len, va_list vargs) return ptr; } + +int +vsnprintf(char* buffer, unsigned int size, const char* fmt, va_list ap) +{ + return __vprintf_internal(buffer, fmt, size, ap); +} + +int +snprintf(char* buffer, unsigned int size, const char* fmt, ...) +{ + va_list l; + va_start(l, fmt); + int r = __vprintf_internal(buffer, fmt, size, l); + va_end(l); + return r; +} \ No newline at end of file diff --git a/lunaix-os/usr/libc/src/posix/dirent.c b/lunaix-os/usr/libc/src/posix/dirent.c new file mode 100644 index 0000000..6651d5e --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/dirent.c @@ -0,0 +1,8 @@ +#include +#include + +int +sys_readdir(int fd, struct lx_dirent* dirent) +{ + return do_lunaix_syscall(__SYSCALL_sys_readdir, fd, dirent); +} \ No newline at end of file diff --git a/lunaix-os/usr/libc/src/posix/errno.c b/lunaix-os/usr/libc/src/posix/errno.c new file mode 100644 index 0000000..1652995 --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/errno.c @@ -0,0 +1,8 @@ +#include +#include + +int +geterrno() +{ + return do_lunaix_syscall(__SYSCALL_geterrno); +} \ No newline at end of file diff --git a/lunaix-os/usr/libc/src/posix/fcntl.c b/lunaix-os/usr/libc/src/posix/fcntl.c new file mode 100644 index 0000000..28f1ff3 --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/fcntl.c @@ -0,0 +1,14 @@ +#include +#include + +int +open(const char* path, int options) +{ + return do_lunaix_syscall(__SYSCALL_open, path, options); +} + +int +fstat(int fd, struct file_stat* stat) +{ + return do_lunaix_syscall(__SYSCALL_fstat, fd, stat); +} \ No newline at end of file diff --git a/lunaix-os/usr/libc/src/posix/ioctl.c b/lunaix-os/usr/libc/src/posix/ioctl.c new file mode 100644 index 0000000..d3a89a8 --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/ioctl.c @@ -0,0 +1,15 @@ +#include +#include +#include + +int __attribute__((noinline)) +ioctl(int fd, int req, ...) +{ + va_list ap; + va_start(ap, req); + + int ret = do_lunaix_syscall(__SYSCALL_ioctl, fd, req, &ap); + + va_end(ap); + return ret; +} \ No newline at end of file diff --git a/lunaix-os/usr/libc/src/posix/lunaix.c b/lunaix-os/usr/libc/src/posix/lunaix.c new file mode 100644 index 0000000..452f02d --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/lunaix.c @@ -0,0 +1,40 @@ +#include +#include +#include + +void +yield() +{ + do_lunaix_syscall(__SYSCALL_yield); +} + +pid_t +wait(int* status) +{ + return do_lunaix_syscall(__SYSCALL_wait, status); +} + +pid_t +waitpid(pid_t pid, int* status, int options) +{ + return do_lunaix_syscall(__SYSCALL_waitpid, pid, status, options); +} + +void +syslog(int level, const char* fmt, ...) +{ + char buf[1024]; + va_list ap; + va_start(ap, fmt); + + unsigned int size = vsnprintf(buf, 1024, fmt, ap); + do_lunaix_syscall(__SYSCALL_syslog, level, buf, size); + + va_end(ap); +} + +int +realpathat(int fd, char* buf, size_t size) +{ + return do_lunaix_syscall(__SYSCALL_realpathat, fd, buf, size); +} \ No newline at end of file diff --git a/lunaix-os/usr/libc/src/posix/mann.c b/lunaix-os/usr/libc/src/posix/mann.c new file mode 100644 index 0000000..f2fe186 --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/mann.c @@ -0,0 +1,24 @@ +#include +#include +#include + +void* +mmap(void* addr, size_t length, int proct, int flags, int fd, off_t offset) +{ + struct usr_mmap_param mparam = { + .addr = addr, + .length = length, + .proct = proct, + .flags = flags, + .fd = fd, + .offset = offset + }; + + return (void*)do_lunaix_syscall(__SYSCALL_sys_mmap, &mparam); +} + +int +munmap(void* addr, size_t length) +{ + return do_lunaix_syscall(__SYSCALL_munmap, addr, length); +} diff --git a/lunaix-os/usr/libc/src/posix/mount.c b/lunaix-os/usr/libc/src/posix/mount.c new file mode 100644 index 0000000..930930c --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/mount.c @@ -0,0 +1,14 @@ +#include + +int +mount(const char* source, const char* target, + const char* fstype, int options) +{ + return do_lunaix_syscall(__SYSCALL_mount, source, target, fstype, options); +} + +int +unmount(const char* target) +{ + return do_lunaix_syscall(__SYSCALL_unmount, target); +} \ No newline at end of file diff --git a/lunaix-os/usr/libc/arch/i386/signal.c b/lunaix-os/usr/libc/src/posix/signal.c similarity index 58% rename from lunaix-os/usr/libc/arch/i386/signal.c rename to lunaix-os/usr/libc/src/posix/signal.c index 2434fc9..8c93fa2 100644 --- a/lunaix-os/usr/libc/arch/i386/signal.c +++ b/lunaix-os/usr/libc/src/posix/signal.c @@ -1,26 +1,43 @@ -#include "syscall.h" +#include #include #include -__LXSYSCALL1(int, sigpending, sigset_t, *set); -__LXSYSCALL1(int, sigsuspend, const sigset_t, *mask); +int +sigpending(sigset_t *set) +{ + return do_lunaix_syscall(__SYSCALL_sigpending, set); +} -__LXSYSCALL3(int, - sigprocmask, - int, - how, - const sigset_t, - *set, - sigset_t, - *oldset); +int +sigsuspend(const sigset_t *mask) +{ + return do_lunaix_syscall(__SYSCALL_sigsuspend, mask); +} -__LXSYSCALL2(int, sys_sigaction, int, signum, struct sigaction*, action); +int +sigprocmask(int how, const sigset_t *set, sigset_t *oldset) +{ + return do_lunaix_syscall(__SYSCALL_sigprocmask, how, set, oldset); +} + +int +sys_sigaction(int signum, struct sigaction* action) +{ + return do_lunaix_syscall(__SYSCALL_sys_sigaction, signum, action); +} -__LXSYSCALL2(int, kill, pid_t, pid, int, signum); +int +kill(pid_t pid, int signum) +{ + return do_lunaix_syscall(__SYSCALL_kill, pid, signum); +} extern void sigtrampoline(); +extern pid_t +getpid(); + sighandler_t signal(int signum, sighandler_t handler) { @@ -33,9 +50,6 @@ signal(int signum, sighandler_t handler) return handler; } -extern pid_t -getpid(); - int raise(int signum) { diff --git a/lunaix-os/usr/libc/src/posix/unistd.c b/lunaix-os/usr/libc/src/posix/unistd.c new file mode 100644 index 0000000..8e17324 --- /dev/null +++ b/lunaix-os/usr/libc/src/posix/unistd.c @@ -0,0 +1,211 @@ +#include +#include + +pid_t +fork() +{ + return do_lunaix_syscall(__SYSCALL_fork); +} + +int +brk(void* addr) +{ + return do_lunaix_syscall(__SYSCALL_brk, addr); +} + +void* +sbrk(ssize_t size) +{ + return (void*)do_lunaix_syscall(__SYSCALL_sbrk, size); +} + +pid_t +getpid() +{ + return do_lunaix_syscall(__SYSCALL_getpid); +} + +pid_t +getppid() +{ + return do_lunaix_syscall(__SYSCALL_getppid); +} + +pid_t +getpgid() +{ + return do_lunaix_syscall(__SYSCALL_getpgid); +} + +pid_t +setpgid(pid_t pid, pid_t pgid) +{ + return do_lunaix_syscall(__SYSCALL_setpgid, pid, pgid); +} +void +_exit(int status) +{ + do_lunaix_syscall(__SYSCALL__exit, status); +} + +unsigned int +sleep(unsigned int seconds) +{ + return do_lunaix_syscall(__SYSCALL_sleep, seconds); +} + +int +pause() +{ + return do_lunaix_syscall(__SYSCALL_pause); +} + +unsigned int +alarm(unsigned int seconds) +{ + return do_lunaix_syscall(__SYSCALL_alarm, seconds); +} + +int +link(const char* oldpath, const char* newpath) +{ + return do_lunaix_syscall(__SYSCALL_link, oldpath, newpath); +} + +int +rmdir(const char* pathname) +{ + return do_lunaix_syscall(__SYSCALL_rmdir, pathname); +} + +int +read(int fd, void* buf, size_t count) +{ + return do_lunaix_syscall(__SYSCALL_read, fd, buf, count); +} + +int +write(int fd, void* buf, size_t count) +{ + return do_lunaix_syscall(__SYSCALL_write, fd, buf, count); +} + +int +readlink(const char* path, char* buf, size_t size) +{ + return do_lunaix_syscall(__SYSCALL_readlink, path, buf, size); +} + +int +lseek(int fd, off_t offset, int options) +{ + return do_lunaix_syscall(__SYSCALL_lseek, fd, offset, options); +} + +int +unlink(const char* pathname) +{ + return do_lunaix_syscall(__SYSCALL_unlink, pathname); +} + +int +close(int fd) +{ + return do_lunaix_syscall(__SYSCALL_close, fd); +} + +int +dup2(int oldfd, int newfd) +{ + return do_lunaix_syscall(__SYSCALL_dup2, oldfd, newfd); +} + +int +dup(int oldfd) +{ + return do_lunaix_syscall(__SYSCALL_dup, oldfd); +} + +int +fsync(int fildes) +{ + return do_lunaix_syscall(__SYSCALL_fsync, fildes); +} + +int +symlink(const char* pathname, const char* link_target) +{ + return do_lunaix_syscall(__SYSCALL_symlink, pathname, link_target); +} + +int +chdir(const char* path) +{ + return do_lunaix_syscall(__SYSCALL_chdir, path); +} + +int +fchdir(int fd) +{ + return do_lunaix_syscall(__SYSCALL_fchdir, fd); +} + +char* +getcwd(char* buf, size_t size) +{ + return (char*)do_lunaix_syscall(__SYSCALL_getcwd, buf, size); +} + +int +rename(const char* oldpath, const char* newpath) +{ + return do_lunaix_syscall(__SYSCALL_rename, oldpath, newpath); +} + +int +getxattr(const char* path, const char* name, void* value, size_t len) +{ + return do_lunaix_syscall(__SYSCALL_getxattr, path, name, value, len); +} + +int +setxattr(const char* path, const char* name, void* value, size_t len) +{ + return do_lunaix_syscall(__SYSCALL_setxattr, path, name, value, len); +} + +int +fgetxattr(int fd, const char* name, void* value, size_t len) +{ + return do_lunaix_syscall(__SYSCALL_fgetxattr, fd, name, value, len); +} + +int +fsetxattr(int fd, const char* name, void* value, size_t len) +{ + return do_lunaix_syscall(__SYSCALL_fsetxattr, fd, name, value, len); +} + +int +readlinkat(int dirfd, const char* pathname, char* buf, size_t size) +{ + return do_lunaix_syscall(__SYSCALL_readlinkat, dirfd, pathname, buf, size); +} + +int +unlinkat(int fd, const char* pathname) +{ + return do_lunaix_syscall(__SYSCALL_unlinkat, fd, pathname); +} + +int +mkdir(const char* path) +{ + return do_lunaix_syscall(__SYSCALL_mkdir, path); +} + +int +execve(const char* filename, const char** argv, const char** envp) +{ + return do_lunaix_syscall(__SYSCALL_execve, filename, argv, envp); +} diff --git a/lunaix-os/usr/libc/src/pthread.c b/lunaix-os/usr/libc/src/pthread.c index 08c82e0..26d6abf 100644 --- a/lunaix-os/usr/libc/src/pthread.c +++ b/lunaix-os/usr/libc/src/pthread.c @@ -1,37 +1,21 @@ #include #include -static void* -__pthread_routine_wrapper(void *(*start_routine)(void*), void* arg) -{ - void* ret = start_routine(arg); - - do_lunaix_syscall(__SYSCALL_th_exit, ret); - - return ret; // should not reach -} - int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void *(*start_routine)(void*), void* arg) { // FIXME attr currently not used + int ret; + struct uthread_param th_param; - struct uthread_info th_info; - int ret = do_lunaix_syscall(__SYSCALL_th_create, thread, &th_info, __pthread_routine_wrapper, NULL); - - if (ret) { - return ret; - } - - // FIXME we should encapsulate these parameter into struct - // and pass it as a single thread param. - - void** th_stack = (void**) th_info.th_stack_top; - th_stack[1] = (void*)start_routine; - th_stack[2] = arg; + th_param.th_handler = start_routine; + th_param.arg1 = arg; + extern void th_trampoline(); + ret = do_lunaix_syscall(__SYSCALL_th_create, thread, + &th_param, th_trampoline); return ret; } diff --git a/lunaix-os/usr/makefile b/lunaix-os/usr/makefile index 4dc8d0d..54f7260 100644 --- a/lunaix-os/usr/makefile +++ b/lunaix-os/usr/makefile @@ -1,25 +1,34 @@ include utils.mkinc include toolchain.mkinc +include lunabuild.mkinc -task := all +include $(lbuild_mkinc) + +ifndef ARCH +$(error ARCH is not set) +endif -# TODO make this use LBuild -CFLAGS += -m32 -ffreestanding -fno-pie -LDFLAGS += -m32 -nostdlib -nolibc -z noexecstack -no-pie -Wl,--build-id=none +task := all sys_include := $(CURDIR)/includes build_dir := $(CURDIR)/build libc_name := liblunac libc_files := $(libc_name).a libc := $(addprefix $(build_dir)/lib/,$(libc_files)) -ldscript := $(CURDIR)/link-usr.ld -common_param := CC AR INCLUDES BUILD_DIR BUILD_NAME CFLAGS LDFLAGS +common_param := CC AR INCLUDES BUILD_DIR BUILD_NAME\ + CFLAGS LDFLAGS ARCH LBUILD INCLUDES := $(sys_include) BUILD_DIR := $(build_dir) BUILD_NAME := $(libc_name).a +mkapp-list := $(addprefix app-, $(shell cat apps.list)) +mkexec-list := $(addprefix $(build_dir)/bin/, $(_LBUILD_SRCS)) + +uexec_ld := $(CURDIR)/uexec.ld + +# Directory structure prerequisites $(build_dir)/bin: @mkdir -p $(build_dir)/bin @@ -29,43 +38,48 @@ $(build_dir)/lib: $(build_dir)/includes: @mkdir -p $(build_dir)/includes +# LibC export $(common_param) $(build_dir)/$(libc_name).a: $(build_dir)/bin $(build_dir)/lib $(build_dir)/includes $(call status,TASK,$(BUILD_NAME)) @$(MAKE) $(MKFLAGS) -C libc/ $(task) -app-list = $(shell cat apps.list) -exec-list = $(shell cat execs.list) +$(uexec_ld): $(uexec_ld)x + @$(CC) -include $(lbuild_config_h) -x c -P -E $< -o $@ -mkapp-list := $(addprefix app-, $(app-list)) -mkexec-list := $(addprefix $(build_dir)/bin/, $(exec-list)) - -export LD_SCRIPT := $(ldscript) +# Application (with standalone makefile) +export LD_SCRIPT := $(uexec_ld) export LIBC := $(libc) -app-%: +export $(common_param) +app-%: $(uexec_ld) $(call status,TASK,$*) @$(MAKE) $(MKFLAGS) -C $* $(task) BUILD_NAME="$*" +app: task := all +app: INCLUDES += $(build_dir)/includes +app: $(mkapp-list) + + +# Programs (single file) exec_%.o: %.c $(call status,CC,$<) @$(CC) $(CFLAGS) $(addprefix -I,$(INCLUDES)) -c $< -o $@ -$(build_dir)/bin/%: exec_%.o +$(build_dir)/bin/%: exec_%.o $(uexec_ld) $(call status,LD,$(@F)) - @$(CC) -T $(ldscript) -o $@ $< $(libc) $(LDFLAGS) - -app: task := all -app: INCLUDES += $(build_dir)/includes -app: $(mkapp-list) + @$(CC) -T $(uexec_ld) -o $@ $< $(libc) $(LDFLAGS) exec: task := all exec: INCLUDES += $(build_dir)/includes exec: $(mkexec-list) + +# General recipes clean: task := clean clean: $(mkapp-list) - @rm -rf $(build_dir) + @rm -rf $(build_dir) $(lbuild_dir) $(uexec_ld) @$(MAKE) $(MKFLAGS) -C libc/ $(task) +.NOTPARALLEL: all: task := all all: $(build_dir)/$(libc_name).a exec app \ No newline at end of file diff --git a/lunaix-os/usr/sh/sh.c b/lunaix-os/usr/sh/sh.c index 2c9bb8d..2ac473a 100644 --- a/lunaix-os/usr/sh/sh.c +++ b/lunaix-os/usr/sh/sh.c @@ -46,8 +46,8 @@ strltrim_safe(char* str) return strcpy(str, str + l); } -void -parse_cmdline(char* line, char** cmd, char** arg_part) +int +parse_cmdline(char* line, char** args) { strrtrim(line); line = strltrim_safe(line); @@ -60,12 +60,15 @@ parse_cmdline(char* line, char** cmd, char** arg_part) } l++; } - *cmd = line; - if (c) { - *arg_part = strltrim_safe(line + l); + + args[0] = line; + if (c && l) { + args[1] = strltrim_safe(line + l); } else { - *arg_part = NULL; + args[1] = NULL; } + + return !!l; } void @@ -108,17 +111,19 @@ sigint_handle(int signum) } void -sh_exec(const char* name, const char** argv) +sh_exec(const char** argv) { + const char* envp[] = { 0 }; + char* name = argv[0]; if (!strcmp(name, "cd")) { - chdir(argv[0] ? argv[0] : "."); + chdir(argv[1] ? argv[1] : "."); sh_printerr(); return; } pid_t p; if (!(p = fork())) { - if (execve(name, argv, NULL)) { + if (execve(name, argv, envp)) { sh_printerr(); } _exit(1); @@ -139,7 +144,7 @@ sh_loop() // stdout (by default, unless user did smth) is the tty we are currently at ioctl(stdout, TIOCSPGRP, getpgid()); - char* argv[] = {0, 0}; + char* argv[] = {0, 0, 0}; while (1) { getcwd(pwd, 512); @@ -154,19 +159,17 @@ sh_loop() buf[sz] = '\0'; // currently, this shell only support single argument - parse_cmdline(buf, &cmd, &argv[0]); - - if (cmd[0] == 0) { + if (!parse_cmdline(buf, argv)) { printf("\n"); goto cont; } // cmd=="exit" - if (*(unsigned int*)cmd == 0x74697865U) { + if (*(unsigned int*)argv[0] == 0x74697865U) { break; } - sh_exec(cmd, (const char**)&argv); + sh_exec((const char**)argv); cont: printf("\n"); } diff --git a/lunaix-os/usr/test_pthread.c b/lunaix-os/usr/test_pthread.c index 247c368..0c1dfe6 100644 --- a/lunaix-os/usr/test_pthread.c +++ b/lunaix-os/usr/test_pthread.c @@ -8,11 +8,14 @@ Test payloads */ +#define __int(ptr) ((int)(unsigned long)(ptr)) +#define __ptr(ptr) ((void*)(unsigned long)(ptr)) + static void* __print_and_sleep_randsec(void* value) { pthread_t tid = pthread_self(); - printf("thread %d: gets number %d\n", tid, (int)value); + printf("thread %d: gets number %d\n", tid, __int(value)); int fd = open("/dev/rand", O_RDONLY | O_DIRECT); if (fd < 0) { @@ -40,9 +43,9 @@ static void* __print_and_sleep_seq(void* value) { pthread_t tid = pthread_self(); - printf("thread %d: gets number %d\n", tid, (int)value); + printf("thread %d: gets number %d\n", tid, __int(value)); - int second = (int)value % 30; + int second = __int(value) % 30; printf("thread %d: going to sleep %ds\n", tid, second); sleep(second); @@ -55,7 +58,7 @@ static void* __print_and_sleep(void* value) { pthread_t tid = pthread_self(); - printf("thread %d: gets number %d\n", tid, (int)value); + printf("thread %d: gets number %d\n", tid, __int(value)); sleep(1); printf("thread %d: exit\n", tid); @@ -95,7 +98,7 @@ spawn_detached_thread(void* (*fn)(void *), int amount) int err; pthread_t created; for (int i = 0; i < amount; i++) { - err = pthread_create(&created, NULL, fn, (void*)i); + err = pthread_create(&created, NULL, fn, __ptr(i)); if (err) { printf("unable to create thread: %d\n", err); continue; @@ -136,7 +139,7 @@ pthread_test_join(int param) void* v; for (int i = 0; i < param; i++) { - err = pthread_create(&created, NULL, __print_and_sleep, (void*)i); + err = pthread_create(&created, NULL, __print_and_sleep, __ptr(i)); if (err) { printf("unable to create thread: %d\n", err); } @@ -169,7 +172,7 @@ pthread_test_quit(int param) do { \ printf("** [%s] test start\n", note); \ pthread_test_##testn(__VA_ARGS__); \ - printf("** [%s] test passed\n"); \ + printf("** [%s] test passed\n", note); \ } while (0) int main() diff --git a/lunaix-os/usr/link-usr.ld b/lunaix-os/usr/uexec.ldx similarity index 62% rename from lunaix-os/usr/link-usr.ld rename to lunaix-os/usr/uexec.ldx index ab77bb4..6703b9e 100644 --- a/lunaix-os/usr/link-usr.ld +++ b/lunaix-os/usr/uexec.ldx @@ -1,7 +1,13 @@ ENTRY(_start) +#ifdef CONFIG_ARCH_X86_64 +# define EXEC_START 0x0000008000000000 +#else +# define EXEC_START 0x400000 +#endif + SECTIONS { - . = 0x400000; + . = EXEC_START; .text BLOCK(4K) : { *(.text) -- 2.27.0 From 04f32c3e67120f8498cdcf7926bab88e4e405258 Mon Sep 17 00:00:00 2001 From: Minep Date: Wed, 17 Jul 2024 00:55:33 +0100 Subject: [PATCH 04/16] add mem-map for x86_64 --- README.md | 4 +++- docs/img/lunaix-mem-map/lunaix-mem-x86_32.png | Bin 0 -> 186741 bytes docs/img/lunaix-mem-map/lunaix-mem-x86_64.png | Bin 0 -> 204497 bytes docs/img/lunaix-os-mem.png | Bin 378802 -> 0 bytes 4 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/img/lunaix-mem-map/lunaix-mem-x86_32.png create mode 100644 docs/img/lunaix-mem-map/lunaix-mem-x86_64.png delete mode 100644 docs/img/lunaix-os-mem.png diff --git a/README.md b/README.md index e891078..aaf28e3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有 如果有意研读LunaixOS的内核代码和其中的设计,或欲开始属于自己的OS开发之道,以下资料可能会对此有用。 + [最新的LunaixOS源代码分析教程](docs/tutorial/0-教程介绍和环境搭建.md) -+ [内核虚拟内存的详细布局](docs/img/lunaix-os-mem.png) ++ 内核虚拟内存的详细布局 + + [x86_32](docs/img/lunaix-mem-map/lunaix-mem-x86_32.png) + + [x86_64](docs/img/lunaix-mem-map/lunaix-mem-x86_64.png) + [LunaixOS启动流程概览](docs/img/boot_sequence.jpeg) + LunaixOS总体架构概览(WIP) + [作者修改的QEMU](https://github.com/Minep/qemu) (添加了一些额外用于调试的功能) diff --git a/docs/img/lunaix-mem-map/lunaix-mem-x86_32.png b/docs/img/lunaix-mem-map/lunaix-mem-x86_32.png new file mode 100644 index 0000000000000000000000000000000000000000..93889a3dac8b3d911d9d1fa5a8c9e60cebe4c3e8 GIT binary patch literal 186741 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

WW~`6AgDbah(CL66=_z~r-Mn?<5eR= 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

pYAwLb-@coNi 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 wXMvs?Do}mp9tK&+Cmo)~eJk(jc&p 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%0AXNe20AC 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 MH?c6zH*}@^7a(H^+W-In literal 0 HcmV?d00001 diff --git a/docs/img/lunaix-os-mem.png b/docs/img/lunaix-os-mem.png deleted file mode 100644 index 519ea3483024affcdc222d71e59fc73880d66481..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378802 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 ?4$U|&VL3_#)~;J2E^FKoF8#cXpJ8EfT4!iYXa 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 }GtR*}6=^x-l|Z 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 GgbO9M#=#qIENubD*%9qC@0e-pfaiM_oNdl72 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⑓D9;6_uD49uQVEp1gZ@qi3EgljB-GjSz2060VGdQNy6o z%ST^mtX&Az(8HAm#{Q*tXp8o-`9UJP!TK`x3GS6$Wwsa-!~ei#rMe71zpodH4Et|y znHmCs7g&FYzQQG+mzNh@A%plr1aTxKVd>^;eRa$ygEElm3wddjjC+&h&4xv?E0-d{ zj?M3usHjr7j601>9ye>72g=QZ6vXvzJnw`G^w*5t#tEGLhx4wjs7~+eJMU%x*wM$X zcgz)AjY2_*D{*EzJpSicz5XLJ?Cj0gN4<+26>@qWG@Mfp$s7JMRK95K{buJk!Yf=9 zym@x+EX{b{{qh>#scZ&caq4Dk&nK3yu98fzOG@~(GZy_Xw!Q=$%RX)YtteY~tSu5x zc0$=@DLo2VvqV|4B`W(CqGWl{f^5kWl_belwj^5=MM)^KQ=w3#ME~>lzVm(a&HU$> zV~#mydgH$D-|t$^>pahkMXIHknOBAG4miBe4K}0tIiRm^#&HX>IP@G(H0q_nf!WH% zCDh^xPEnyD)iT1?!NH-qI4w4|(0x!tJ3c!*8?-Gs#Jt8B?lxTGp@yiTTU~#G6f-8c zuPXb9sdVADjOfh=hme~aA$3V>9*vgHB0my6mg+`kx}jqjl68ImaD9cvpP9=c<`2go zbWLm*7l)M;{|7S=BFnPp!*)>U+_IE6MtI~eUPLh_b5a5Ll zWguq);3zm!Rv@4~I5-Fo8PfW*bRraynczSWK8qz{dO}A7g}=@PtXsdyyCAr>;6c#+ zB?tl!-q^R^B5B*UZ9qo+ahF-+>IeNl-AbPP5ESy*#{b4+l=h2Jojzwk0*;G|E0*ZW zH5x`diAdT{0gErRFR|^{8DmEI@T9m3^UYtyf1F5w zRLh}sOQ0!K5gC~dH}-mIBBr4ASVE8N-)g%AZsta#Y^+ErlQz3Z1q z+Q-IgHRxC~lzkXB*S#)B_iI$mz*sbhc9`b=PjF*DO!>zAIQ@59CM}R8W#g)T#oxjl zSpr0{#pWGM!+(~){+afjM#j5eFtbhn@j~klr-(aDHf%%)7Y&wldon!p2sI57V+@r_ z$G}htn_YsSou!g%SOvFP4GNNxn$PEi!d)}A$Q!7~2i3NBc2S^t#p z-Xuhe*tz!!bal5k(*>@HGIpz)lE3?!y}S-O4#tQ>{x=in|a30x8k z)opusqrVM8zg3rO2!3^{(BVr~ps25j1U>(3H_bas)NU{?r+$QcX`#dffo{E*ORy^5 zS~ukjLQ*Of8bS}VGpq$au#A$UN!5RvFChKuRcD)y;TgWgCWt|V^->8|kY#p}C$r?; znO5m!{vd4>B$l_jm+B_D^7`+dBr<@aOR5kJ-=e22MI()^h|^rJ;eyG0H=DENU`vQ*UN{Wi+NseK(G_;UEbR)Ozb$GPFGGh3ut)zH4WfR(9Q-`H_0O-7RgMQfI5< z?ib$a=5)Db8>QGl`zcvb_8|i^^S<+>`18Jp)fP=lZ4wk3NmJrPec5`Nk5|yYHOp0$ zH?&y7{`E(9MF$IJX6Cm(TP_EFG(3t>iGq2~Uj+q`E9Bw!c6L}4D-H1yjnj&KxrynH=XeW(AgTnd>0Cl^(L}`#?PgE|{yz~2SF7PpJezW(_ zjd5smQm7o7=d4x%3uL;CoO4=**CGj7>GNCwns?>&B*(16PXzl5tFzzBzMy9p0{8$* z8v@AI+Z;k%x;X%-t`icl^Pg5MNc{+bHH%1 z(^@WZYE?u|a%<#UEUWWxTvrsez}Haqd>4tcJnWon@>2or*?w6^JC(IKJ-n?^=^^R!gP&Gy^NA}Fssl+z<@ z8=gL`w3NKxb$24b-Ca60vWsvco397+JuEEj`VK;Cj0kcDcHv}56&X?}yO z%|5GW$Diffb6yp-@y9a#QC95G;^rx)im0gkM%$gS(@_Um1MN(os~<)zh@2A4-0bXl z_>-x}U?tehZEL~h8_MHCw^LJBu<&-`^f&J+!#DSBGO4;997P2q>^#qe!>;CtI!MKt z)lJM{@Nx~KnXC1K=5WD78w6Ap{I`ZEjpJe@6f}D-i%Upw1Y{e^M>S9R*8B0+tR~&M zdGlyGNb`IX;xvkS9qwZ!<_prcc%mcb0rr2x7pR(b&KYw?UsdM0g!_V8ajTj{woXy1 zqXkjpSddhBSwaPk4)V09^0Kg%)t-X=d4)imHNWpn9 zGsfv(RZCLpqH1-Nf>#cpu@_1h9DEt38~DpNZrm6zpbDrOtyjGZ@8xyp?qZ*!a5g3_ zXTT92N*l`#1+n*RwV|JbSa&luP05Qb zXWl`a92&9tc&CLYZD+=whp!aiX2Tm#|1vsEOk}#CSpz76v!cj@j=K4bJS;RdjrLvU zO^=T+#-6Y$9w0oAKq@8E$jQsgD=Xuzb-)s;{U!d4D*o_nqUc$__b3_zzke(O8Qdc2 z1oTem8Af)>Fl`eO8pAdcR}fGY3Ghuzs~IybW#36H3UN@etX5N*lQ2Jkoo!UxojZ2` zi=gC0Zl-)EygyOpQA{LWzs8KA2BM(K0t`zFF>`J2GcoOyy?68GO(0c3LwPX|fiiK@ z^j%C$%vaoJoMoMO5STf(ZVf>zT)qy_KQ=(z#{ryPVAtd&e4QX2Mc7>8NG2CDc;n^gLLjOAgt#7N1rERxE2lBlp;@i6fAQv*Uj_O zdgrzu>+@b=j>5(k`bK5wfyx;DvuDrjgqge?ZGYV6BU^Q$#n^F`yQf{R!-Ru&O-9hN z+yY9?4v@-gvaDe(7KkKl(dW9uzDDDm!(TP>k8;bBgU>h9C6_@hG{brDGq&*3M$9%_j#A?cD0z9G zE0+--;HMHwjXn4rC9>r92F1U|%MV<+lWrR#K9 zvW&k8@93MiZkhV27o7a}$KPb<2^vg4)9>e+*k5iGX2mF~r{!i>(KPI#$YY+7ocseS zEr(;r?0omstT%H^5vLO{V*6wF?!qYI_lxJxb@cUx`k5^Ag(TNknY@rrrpNEFd!YAX zWFN}B%REYOUX z)~sjdmjRG0AlJSTMPZJ9dg`@n6P^`@3ESo59w34NtLlDKxvN$>WP@G@20sPI7d$og z@1^tg^qhTiZVj=T$&%6CXHSk-gCpZ@qn95WEY4kY*da<^U%*LtI~Hv4mAu$n9^bO~bXR z-Il*w(zdE(Y|L@_J7KL}uUlfY#F~BNsO}>59iDhZ`-VW3~=IHyL~K2bT_vblfjvdaJYO=^@Qz%+aD;Rfm|STi{yGBrC1 zw|HXwLL_8Nbo1H`|m= zX|fLa-Q76$r~Y6g?Kn&LrC-lZolCfueRu#ZwrM>vF-4DU*)WnBd@cIrS8>u#VTdY%D&V2=VT#L3eFXEO0G1ID`cAxq3`?qTt&%j)Oqs7Df5iS?n zX}ztaL`AQvoe3~A<67-3sGSF!C&=`ckp$$AxBo>BEttq2S$)7wclW_L|ECXfCVnBM z$?V*DtTiP|M5nLPM@B{le(uq3;n5dwM&{-HQhK(tRq*_O{qT=`uZ@!&n_UWZX6~z= z=VVn=1i$4p_T1o~50BKx89uve&A_m)*|XivzC$)Z#m)}q5BIdGn^@W*C2qyuFVxnK z7BH#UD07TSWv_VT&7!W&#@nZtuF?zq+pUu3Ce>I}XUYnV$SmaVIsYD?17l;OyBv&& z#k*h#zPz-Ed=T}~q(gQo$QQc^Bw8}MFX`C|#VU6Bb4i|@Ph4LwA}NU!_zO+|aGTT9 zo5IP{*T0l|R-%Age@&AKV^GMk)%3h=OvZA5m!Yki>pq;)IM041B(R_i#Sp5Zv5}G8 zd!+Sb5wXB-hbWkA@s+c5@TnHuN`AGRphPZ8r=li%CeN!RSz5 zpP3{mAfPheZI2o{A(zw|)p_-E=zqP^@(mHI{NzbI>+W)7LPPZv&B-xGM-mLKk{lbK zK1Jg825coNDmv2f%G(NF`&~X54y|uHT+u1{gmkS=_o&fnm6%P8^S>h&xGYHF1|RWh zi76v$Dw2_gMjhT8rWQ{sHydC4@3#z;%)~TN;2E!VWyqgpJn$tLNPhCh{7%z|jh!7O zz%H-{(7Ba7cUg@Nyn|y?+H}_4M?(0)d>&?>E}pc1&--F~(uN2C2^dMYjHcp^i{F1# z3?ns7Bf{PjMJJd|B+{Y7My-3)Y?16NoMrcHQ5eYt!t6IE7c_<>eKAMc>YD2tnBB?wjCz z#%o{FiQ}~JZP>!HZXGkv2CO)T`Jnp_w54!wPkCuY7aqL1J?KCa_t;Cxk_9;V6#P{hetEFeg64C} z2mhZRuM0n0ukzN9m5;$VBc!ItJ9&A}i}Rf~Wp@$_qj3f3dzfF{sVvyV!yK4(u*Ux9 z=#@&tLQzbMLV{Id_&n;R4Yz4p_LXaGZz1jwws8~!>nA^d@nQZh zX7e$UvL0SZMvSI|CPA#Vw>6IJf7^&M!QkpnK?Q|m;`?KV9V&x*(GMw%jQ@4Uxqe%I zNCGzG@QiK5rs;-^l2Qe}G7%*8^|`o&kIjyVpQr0#c6F`mx#!7M5o2_8&tw|z)Y0-n zPW`3X?Z55}rsgCjBse%a0t?@pGz!!!KR+L#J?3cVXSI#3od0>Rbg%PMBzL42r15OY z+}@(GTZD^Ykcnl#c08KchhVe9y@&30%HIh$+e~yxp6fy7;u)@PBIUYlY-8k>gfvgv%K%TcdoLOY+ ze^nMBVFv)F208Rmwn(=vMpnQ}FfE9WjXj$_K_I|=G6j`Y!Sf}xNE6bQzh;;`Q2(3) zOToj}_o0BcyB{jiS+5HZB5yE!RkG^xV6l}_%Gvp!zreXD)$R_r_>(p-c)$LH6d0YA znv4wlOcuoiHw`rd)=BXxDgDehI>s$$`Ms?kKTXTztN*^$Cg(omd^pO${Z~)O*4&bfL-iqEEAfz2X z*{Rj09QTmXJ(-^vdoEavbCxoA?y$H&`JcATfOnx`U@!L^B?6M(%V&7}TS{-bM3VErNH zbN1xO03W~ae(4NEZI!jD$qU$`p&{$%A}X7ByucKIbU0gpn3vHq3M}F+217_)T54_< zfkKKAGyhr930-G(PGqX@1|Jw3a8Z6JIlbfwx+|iCO71*T6ZHL5y-DW*%3q=*@{4Tb z;{A!%W3*h9ZA9FNaG41xiOgKInp4Cm9UE|Y!4C)yRwIGRsxB-nj9`2GM{&VNr&e9C-2h$6mE{5PMFGP}GYO6=pQ=89xFcTeh4v_oL~96UWTK+Cg_<+64Nfq@*-b#ZL|F>y-ZLxRR3Z&YTPhk7BXDPxyHO zP=#@T>kN#OE^SqpeD{~1jN)62olhVn&CJZi3&3}v3@{}COHqeC_0nPL zbfhh)*I+M!ULh+l4`2rJIKfmzi+hx@?f@#V-d;=XcoWn0%*?(OmWpc@bmDIUl}ca+ zO%0v%f|*eQh&tNwv1@6aS_j=Fu!g?O)?n(q^jx}P~~!*pEfmtr4(5G0h2RgC97gsHl~dGAL;QwWZW2` zWHWu3V{2i{bv-&KULW+cR z6_Q)f9UC+|;gw&ybjcbY^cuW4K$1od&IjhKQD@BIh@KMQ9!e0PZCI__0wxw{Lgi4t zAUznQDZb*pUy1*VSSyC=8{jOQKc)k!fudr{7KHlc}HahX3Y*Jyhor}LFIvaco=r`VbU4N5jiV5xaP2x=`0~#7Y5KMb#VhYR^ zEmSuRvqWqgd_7o1^=N($NrXp4#2W}VrediPKR-WYUB4ZO;)r00f95r0s!tOxf8V|2 z)mTki)(#93Ei0M~pbE4n*3!}<^%KVrR)S`xrX8}qA(NM$&b!dB`a1hmx>a*^hHD-B zR=EUC{x_&CS#TtY zw)1ImKENo#j?%n#c;+boTUca5jX)-wngSiQpWM?mhqtXA&%w)UeehstxcNqFxB2=W zZZvO`Z)IPAE|*RvJWSxMcfTvfhgkKN_nFvl=1UdoBnDZSyI+0%`rxEHZW1n7ya2IC z)ZA`EMANYL3JD4A3Pb}9=Lqcyyo<;>+S;TOn<92VGeZ8{p0(yuXv7|gz4qE3n7N>zSt`uh4v=+DLr^F?$pe*vd!V~d}7NpRp` z%=w)@IQ(CpGd+<5QC7wpI8QIGvm8{jkIY7C?j+1&TN)WBDD-A+q0r2l1s zmGrO3_4UuT*LMe}ZaJrdNCt~gKC*+;;#|VdIvNquv2p(~ji(5zw+GAT;kerxEZ=jmCJOAj<-X5lU+@&PP zJ9z=PFhK|l0+aeD1z(D(B_}0C+tA$BOi0|#C-ye7WwXjauWQ-T{+s=4<1Ba#pGqHE z%^bP+jWfx*U^*?)@ibok)vH%gVxGc0eBZvO2o5HudT@q&`BHNG_CBN$h%&WckI|ullPOpHIxy>5{W?5kMrDl#+6TyEtz8r%#_S zN>1BhAJH|T>3QMv7n+q(VDjpu55bz{`cITrXI};nO-f4m2p7xQqiraX4#d;6F$ydS zlk25Hx4{a-Wq7UC?b!y0S46AzEG%0AEAfmDkj7^G@dx%(2qU(M&byKH<7g2;{KohO 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 z zaKRX#_u%v2wlQ<)&rR+ivjl~ z_AT5AT4fY!2PZ&~gw;TPpLJ$7Mh|2v3F-`h3_v)xGR0$RVixBPR?c|Bz<#;kZ?tLO z*u+k|rVJzcW2dG!A|4;r&E*qGGyjb1%w_9sG}#od^XH6~-Sf*L!!Z z#Y{b9@b0Bf76 zfjYv;Mwc>MxrcoAO9es1KsUKBEk`zw6#ni*JB`2V3=hWIe-0v1vvrimMtn zb65l(7-8e^XQ6IWV?A&RiH!IcXt0nJ_Uv-X!tPo9?4-Nt0{m1B&2~wdo9*#+9W|-Q zizp1{M_M)piQX&zj>wC8!nEl2fNzYRcX>P98%L0__bi6RVZ+9xkb0XeM(Hw)iv0$I zna}pr;Ju0oQk;%OGx+7&z9JnpC}iwl-^8-Kp1rWN^cR>20iVP9L>Xq^f2){)k?_re zm$qW6Pc9tNoI(6lT?|%fS1CFG8x$kV;pD)R57W2FY;y6g9F;pK!XhoH9ne+wDE^i0Bd}Wtiy3C-23s-T)}&t+Dnwa8E@aEVvu(bc9jd_H;7Z&q z@W^vP_?8#EFIRsg3`ta-1DZ(El(&u8C=jaR?S4zN+?`;%rZcyA8_)#*??ytjUyB6l=#j z-KDQQXBTq_(o-YfJf!0E#Yo5rXFTZTVN%w~`mt=C?K3jIpCDP=M=Bb}iOskz0$J8lx>bPR%NmW19 zjFu7D$ujSU)X;!2LW%cKpmM92?;o%w*YM=5QVbWmyevcPAU{+@oDGCG@J+*T%2%x~ zHaVD?nwomUU=$fh5lWcQLqbuexZ~*T{rn<@N?qV>_2mzD1S_D)@Pf!&qtzUAGdmM( zaXDEnfhT7_9_Vx@hyrwvgz|AyY`j1AS;(SkCrxOylIne1^{m9k@UZukv_0+3>HVCS?Ac@fA_%H01jODP726GolaRW&tm+imHY(+4eDTpFgE9&SH9HsT_ujU z^{8H6=KolKK*+rOA+Q_}>l%!X_x3yH+zq)y{pN0Fh&Bq{jLZ+ms^2>7b4ol7@7*EE zaPo%y5V`GrVcaHx=pm{Ws+Xw7t!>O%ID9S6KD@p%0D3AN$%L?OE0?GOB)z%7*V(&!r< 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

-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 z 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 -- 2.27.0 From ebb55b7e5f0b8f31328950ec383b77b208ffbb64 Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Sun, 21 Jul 2024 20:45:46 +0100 Subject: [PATCH 05/16] PCI 16x50 UART Controller, O2 Enablement (#39) * add PCI 16x50 uart controller, refactor the 16x50 structure * refactor the pci detection mechanism, allow custom compatibility check logic. * fix bug in pci device definition binding where only bind once * refactor the 16x50 controller structure, move pmio and mmio as separated component and does not need to couple with interface * fix issue where x86:isrm_ivexalloc alloc duplicated interrupt vector * change the signature of ioremap to return ptr_t instead of generic pointer * fix issue in LunaConfig header export, where non string type being joined as a string type * add config term for 16x50 controller. * rework the usr/testp, allow user to specify serial interface * usr/ls, add a slash after directory file name to distinguish * add the implementation for iounmap * refactor pci dev to use latest change in pci * remove the out-dated, mask based compatibility check scheme * rectify the issues with -O2 optimization * refactor: allow Lunaix to compile with -O2 optimization * add LConfig terms for PCI related features * add LConfig terms for AHCI related features * refactor the pci device code base with latest pci refactoring * fix: fail to read bool type config term when the value of the term is False * refactor: LConfig: allow omit the `LConfig` when doing include * remove legacy file --- lunaix-os/LConfig | 5 +- lunaix-os/arch/generic/includes/sys/pci_hba.h | 31 ----- lunaix-os/arch/x86/boot/x86_64/kremap64.c | 9 +- lunaix-os/arch/x86/exceptions/intr_routines.c | 7 + lunaix-os/arch/x86/exceptions/isrm.c | 5 + lunaix-os/arch/x86/hal/LBuild | 3 +- lunaix-os/arch/x86/hal/apic.c | 2 +- lunaix-os/arch/x86/hal/ioapic.c | 2 +- .../x86/{includes/sys/pci_hba.h => hal/pci.c} | 20 ++- lunaix-os/arch/x86/includes/sys/abi64.h | 2 +- lunaix-os/hal/LConfig | 9 ++ lunaix-os/hal/ahci/LBuild | 19 +-- lunaix-os/hal/ahci/LConfig | 18 +++ lunaix-os/hal/ahci/ahci.c | 1 - lunaix-os/hal/ahci/ahci_pci.c | 49 ++++--- lunaix-os/hal/bus/LBuild | 6 +- lunaix-os/hal/bus/LConfig | 35 +++++ lunaix-os/hal/bus/pci.c | 56 ++++---- lunaix-os/hal/char/LConfig | 7 + lunaix-os/hal/char/serial.c | 8 +- lunaix-os/hal/char/uart/16550_pmio.c | 105 --------------- lunaix-os/hal/char/uart/{16550.h => 16x50.h} | 36 ++++- .../char/uart/{16550_base.c => 16x50_base.c} | 32 ++--- lunaix-os/hal/char/uart/16x50_isa.c | 64 +++++++++ lunaix-os/hal/char/uart/16x50_mmio.c | 39 ++++++ lunaix-os/hal/char/uart/16x50_pci.c | 125 ++++++++++++++++++ lunaix-os/hal/char/uart/16x50_pmio.c | 54 ++++++++ lunaix-os/hal/char/uart/LBuild | 13 +- lunaix-os/hal/char/uart/LConfig | 25 ++++ lunaix-os/hal/gfxa/vga/vga_pci.c | 19 ++- lunaix-os/includes/hal/pci.h | 97 +++++++++++++- lunaix-os/includes/lunaix/kpreempt.h | 3 +- lunaix-os/includes/lunaix/mm/mmio.h | 2 +- lunaix-os/includes/lunaix/mm/page.h | 7 + lunaix-os/kernel/LConfig | 2 +- lunaix-os/kernel/kinit.c | 2 +- lunaix-os/kernel/mm/mmio.c | 18 +-- lunaix-os/live_debug.sh | 2 +- lunaix-os/makeinc/toolchain.mkinc | 13 +- .../build-tools/integration/config_io.py | 2 +- .../build-tools/integration/render_ishell.py | 2 +- .../scripts/build-tools/lcfg/builtins.py | 6 +- lunaix-os/scripts/qemu.py | 9 +- lunaix-os/scripts/qemus/qemu_x86_dev.json | 8 ++ lunaix-os/usr/LBuild | 2 + lunaix-os/usr/ls.c | 2 +- lunaix-os/usr/testp.c | 36 ++--- 47 files changed, 723 insertions(+), 296 deletions(-) delete mode 100644 lunaix-os/arch/generic/includes/sys/pci_hba.h rename lunaix-os/arch/x86/{includes/sys/pci_hba.h => hal/pci.c} (69%) create mode 100644 lunaix-os/hal/LConfig create mode 100644 lunaix-os/hal/ahci/LConfig create mode 100644 lunaix-os/hal/bus/LConfig create mode 100644 lunaix-os/hal/char/LConfig delete mode 100644 lunaix-os/hal/char/uart/16550_pmio.c rename lunaix-os/hal/char/uart/{16550.h => 16x50.h} (84%) rename lunaix-os/hal/char/uart/{16550_base.c => 16x50_base.c} (84%) create mode 100644 lunaix-os/hal/char/uart/16x50_isa.c create mode 100644 lunaix-os/hal/char/uart/16x50_mmio.c create mode 100644 lunaix-os/hal/char/uart/16x50_pci.c create mode 100644 lunaix-os/hal/char/uart/16x50_pmio.c create mode 100644 lunaix-os/hal/char/uart/LConfig diff --git a/lunaix-os/LConfig b/lunaix-os/LConfig index ecd4a03..fef1221 100644 --- a/lunaix-os/LConfig +++ b/lunaix-os/LConfig @@ -1,7 +1,8 @@ import time -include("kernel/LConfig") -include("arch/LConfig") +include("kernel") +include("arch") +include("hal") @Term("Version") @ReadOnly diff --git a/lunaix-os/arch/generic/includes/sys/pci_hba.h b/lunaix-os/arch/generic/includes/sys/pci_hba.h deleted file mode 100644 index f3747bf..0000000 --- a/lunaix-os/arch/generic/includes/sys/pci_hba.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef __LUNAIX_ARCH_PCI_HBA_H -#define __LUNAIX_ARCH_PCI_HBA_H - -#include -#include - -#define PCI_MSI_BASE 0 - -static inline pci_reg_t -pci_read_cspace(ptr_t base, int offset) -{ - return 0; -} - -static inline void -pci_write_cspace(ptr_t base, int offset, pci_reg_t data) -{ - return; -} - -static inline u16_t -pci_config_msi_data(int vector) { - return vector; -} - -static inline ptr_t -pci_get_msi_base() { - return 0; -} - -#endif /* __LUNAIX_ARCH_PCI_HBA_H */ diff --git a/lunaix-os/arch/x86/boot/x86_64/kremap64.c b/lunaix-os/arch/x86/boot/x86_64/kremap64.c index ee9d298..bf3bb33 100644 --- a/lunaix-os/arch/x86/boot/x86_64/kremap64.c +++ b/lunaix-os/arch/x86/boot/x86_64/kremap64.c @@ -178,9 +178,12 @@ ptr_t boot_text remap_kernel() { ptr_t kmap_pa = to_kphysical(&kpt); - for (size_t i = 0; i < sizeof(kpt); i++) { - ((u8_t*)kmap_pa)[i] = 0; - } + + asm volatile("movq %1, %%rdi\n" + "rep stosb\n" ::"c"(sizeof(kpt)), + "r"(kmap_pa), + "a"(0) + : "rdi", "memory"); do_remap(); diff --git a/lunaix-os/arch/x86/exceptions/intr_routines.c b/lunaix-os/arch/x86/exceptions/intr_routines.c index 05f547e..d355927 100644 --- a/lunaix-os/arch/x86/exceptions/intr_routines.c +++ b/lunaix-os/arch/x86/exceptions/intr_routines.c @@ -39,6 +39,12 @@ intr_routine_general_protection(const struct hart_state* state) __print_panic_msg("general protection", state); } +void +intr_routine_invl_opcode(const struct hart_state* state) +{ + __print_panic_msg("invalid opcode", state); +} + void intr_routine_sys_panic(const struct hart_state* state) { @@ -89,6 +95,7 @@ intr_routine_init() isrm_bindiv(FAULT_GENERAL_PROTECTION, intr_routine_general_protection); isrm_bindiv(FAULT_PAGE_FAULT, intr_routine_page_fault); isrm_bindiv(FAULT_STACK_SEG_FAULT, intr_routine_page_fault); + isrm_bindiv(FAULT_INVALID_OPCODE, intr_routine_invl_opcode); isrm_bindiv(LUNAIX_SYS_PANIC, intr_routine_sys_panic); isrm_bindiv(LUNAIX_SCHED, intr_routine_sched); diff --git a/lunaix-os/arch/x86/exceptions/isrm.c b/lunaix-os/arch/x86/exceptions/isrm.c index 89ff616..dcd576a 100644 --- a/lunaix-os/arch/x86/exceptions/isrm.c +++ b/lunaix-os/arch/x86/exceptions/isrm.c @@ -51,6 +51,11 @@ __ivalloc_within(size_t a, size_t b, isr_cb handler) k++; } + if (j == 8) { + j = 0; + continue; + } + if (k > b) { break; } diff --git a/lunaix-os/arch/x86/hal/LBuild b/lunaix-os/arch/x86/hal/LBuild index 66fff6a..f3a3cae 100644 --- a/lunaix-os/arch/x86/hal/LBuild +++ b/lunaix-os/arch/x86/hal/LBuild @@ -5,5 +5,6 @@ sources([ "ps2kbd.c", "apic_timer.c", "ioapic.c", - "mc146818a.c" + "mc146818a.c", + "pci.c" ]) \ No newline at end of file diff --git a/lunaix-os/arch/x86/hal/apic.c b/lunaix-os/arch/x86/hal/apic.c index 4e2501f..34b243e 100644 --- a/lunaix-os/arch/x86/hal/apic.c +++ b/lunaix-os/arch/x86/hal/apic.c @@ -43,7 +43,7 @@ apic_init() // As we are going to use APIC, disable the old 8259 PIC pic_disable(); - _apic_base = (ptr_t)ioremap(__APIC_BASE_PADDR, 4096); + _apic_base = ioremap(__APIC_BASE_PADDR, 4096); // Hardware enable the APIC // By setting bit 11 of IA32_APIC_BASE register diff --git a/lunaix-os/arch/x86/hal/ioapic.c b/lunaix-os/arch/x86/hal/ioapic.c index 83ce6a0..1e0755a 100644 --- a/lunaix-os/arch/x86/hal/ioapic.c +++ b/lunaix-os/arch/x86/hal/ioapic.c @@ -38,7 +38,7 @@ ioapic_init() acpi_context* acpi_ctx = acpi_get_context(); _ioapic_base = - (ptr_t)ioremap(acpi_ctx->madt.ioapic->ioapic_addr & ~0xfff, 4096); + ioremap(acpi_ctx->madt.ioapic->ioapic_addr & ~0xfff, 4096); } void diff --git a/lunaix-os/arch/x86/includes/sys/pci_hba.h b/lunaix-os/arch/x86/hal/pci.c similarity index 69% rename from lunaix-os/arch/x86/includes/sys/pci_hba.h rename to lunaix-os/arch/x86/hal/pci.c index 74d9c03..f1321b2 100644 --- a/lunaix-os/arch/x86/includes/sys/pci_hba.h +++ b/lunaix-os/arch/x86/hal/pci.c @@ -1,37 +1,33 @@ -#ifndef __LUNAIX_PCI_HBA_H -#define __LUNAIX_PCI_HBA_H - #include -#include - -#include "port_io.h" +#include +#ifdef CONFIG_PCI_PMIO #define PCI_CONFIG_ADDR 0xcf8 #define PCI_CONFIG_DATA 0xcfc -static inline pci_reg_t +pci_reg_t pci_read_cspace(ptr_t base, int offset) { port_wrdword(PCI_CONFIG_ADDR, base | (offset & ~0x3)); return port_rddword(PCI_CONFIG_DATA); } -static inline void +void pci_write_cspace(ptr_t base, int offset, pci_reg_t data) { port_wrdword(PCI_CONFIG_ADDR, base | (offset & ~0x3)); port_wrdword(PCI_CONFIG_DATA, data); } -static inline u16_t +#endif + +u16_t pci_config_msi_data(int vector) { return vector; } -static inline ptr_t +ptr_t pci_get_msi_base() { return 0xFEE00000; } - -#endif /* __LUNAIX_PCI_HBA_H */ diff --git a/lunaix-os/arch/x86/includes/sys/abi64.h b/lunaix-os/arch/x86/includes/sys/abi64.h index 1c93cc1..1dce9b0 100644 --- a/lunaix-os/arch/x86/includes/sys/abi64.h +++ b/lunaix-os/arch/x86/includes/sys/abi64.h @@ -32,7 +32,7 @@ static inline ptr_t must_inline abi_get_callframe() { ptr_t val; - asm("movq %%rbp, %0" : "=r"(val)::); + asm volatile("movq %%rbp, %0" : "=r"(val)::"memory"); return val; } diff --git a/lunaix-os/hal/LConfig b/lunaix-os/hal/LConfig new file mode 100644 index 0000000..35385c9 --- /dev/null +++ b/lunaix-os/hal/LConfig @@ -0,0 +1,9 @@ +include("char") +include("bus") +include("ahci") + +@Collection +def hal(): + """ Lunaix hardware asbtraction layer """ + + pass \ No newline at end of file diff --git a/lunaix-os/hal/ahci/LBuild b/lunaix-os/hal/ahci/LBuild index 8c081c6..7da89ab 100644 --- a/lunaix-os/hal/ahci/LBuild +++ b/lunaix-os/hal/ahci/LBuild @@ -1,9 +1,10 @@ -sources([ - "ahci_pci.c", - "hbadev_export.c", - "ahci.c", - "utils.c", - "io_event.c", - "atapi.c", - "ata.c" -]) \ No newline at end of file +if config("ahci_enable"): + sources([ + "ahci_pci.c", + "hbadev_export.c", + "ahci.c", + "utils.c", + "io_event.c", + "atapi.c", + "ata.c" + ]) \ No newline at end of file diff --git a/lunaix-os/hal/ahci/LConfig b/lunaix-os/hal/ahci/LConfig new file mode 100644 index 0000000..b957881 --- /dev/null +++ b/lunaix-os/hal/ahci/LConfig @@ -0,0 +1,18 @@ + +@Collection +def sata_ahci(): + + add_to_collection(hal) + + @Term + def ahci_enable(): + """ Enable the support of SATA AHCI. + Must require PCI at current stage """ + + type(bool) + default(True) + + if not v(pci_enable): + set_value(False) + + \ No newline at end of file diff --git a/lunaix-os/hal/ahci/ahci.c b/lunaix-os/hal/ahci/ahci.c index bd84bf8..82375e0 100644 --- a/lunaix-os/hal/ahci/ahci.c +++ b/lunaix-os/hal/ahci/ahci.c @@ -14,7 +14,6 @@ #include #include -#include #include #include diff --git a/lunaix-os/hal/ahci/ahci_pci.c b/lunaix-os/hal/ahci/ahci_pci.c index 0ecde58..6c59cf8 100644 --- a/lunaix-os/hal/ahci/ahci_pci.c +++ b/lunaix-os/hal/ahci/ahci_pci.c @@ -2,25 +2,33 @@ #include #include -#include static int ahci_pci_bind(struct device_def* def, struct device* dev) { - struct pci_device* ahci_dev = container_of(dev, struct pci_device, dev); + struct pci_device* ahci_dev; + struct pci_base_addr* bar6; + struct ahci_driver* ahci_drv; - struct pci_base_addr* bar6 = &ahci_dev->bar[5]; - assert_msg(bar6->type & BAR_TYPE_MMIO, "AHCI: BAR#6 is not MMIO."); + ahci_dev = PCI_DEVICE(dev); + bar6 = pci_device_bar(ahci_dev, 5); + assert_msg(pci_bar_mmio_space(bar6), "AHCI: BAR#6 is not MMIO."); - pci_reg_t cmd = pci_read_cspace(ahci_dev->cspace_base, PCI_REG_STATUS_CMD); + pci_reg_t cmd = 0; + pci_cmd_set_bus_master(&cmd); + pci_cmd_set_mmio(&cmd); + pci_cmd_set_msi(&cmd); + pci_apply_command(ahci_dev, cmd); - // 禁用传统中断(因为我们使用MSI),启用MMIO访问,允许PCI设备间访问 - cmd |= (PCI_RCMD_MM_ACCESS | PCI_RCMD_DISABLE_INTR | PCI_RCMD_BUS_MASTER); - - pci_write_cspace(ahci_dev->cspace_base, PCI_REG_STATUS_CMD, cmd); - - int iv = isrm_ivexalloc(ahci_hba_isr); - pci_setup_msi(ahci_dev, iv); + int iv; + if (pci_capability_msi(ahci_dev)) { + iv = isrm_ivexalloc(ahci_hba_isr); + pci_setup_msi(ahci_dev, iv); + } + else { + iv = pci_intr_irq(ahci_dev); + iv = isrm_bindirq(iv, ahci_hba_isr); + } struct ahci_driver_param param = { .mmio_base = bar6->start, @@ -28,7 +36,7 @@ ahci_pci_bind(struct device_def* def, struct device* dev) .ahci_iv = iv, }; - struct ahci_driver* ahci_drv = ahci_driver_init(¶m); + ahci_drv = ahci_driver_init(¶m); pci_bind_instance(ahci_dev, ahci_drv); return 0; @@ -40,12 +48,19 @@ ahci_pci_init(struct device_def* def) return pci_bind_definition_all(pcidev_def(def)); } +static bool +ahci_pci_compat(struct pci_device_def* def, + struct pci_device* pcidev) +{ + return pci_device_class(pcidev) == AHCI_HBA_CLASS; +} + + static struct pci_device_def ahcidef = { - .dev_class = AHCI_HBA_CLASS, - .ident_mask = PCI_MATCH_ANY, .devdef = { .class = DEVCLASS(DEVIF_PCI, DEVFN_STORAGE, DEV_SATA), - .name = "Generic SATA", + .name = "Generic AHCI", .init = ahci_pci_init, - .bind = ahci_pci_bind } + .bind = ahci_pci_bind }, + .test_compatibility = ahci_pci_compat }; EXPORT_PCI_DEVICE(ahci, &ahcidef, load_postboot); \ No newline at end of file diff --git a/lunaix-os/hal/bus/LBuild b/lunaix-os/hal/bus/LBuild index a485ce7..3b5ff38 100644 --- a/lunaix-os/hal/bus/LBuild +++ b/lunaix-os/hal/bus/LBuild @@ -1,3 +1,3 @@ -sources([ - "pci.c" -]) \ No newline at end of file + +if config("pci_enable"): + sources("pci.c") \ No newline at end of file diff --git a/lunaix-os/hal/bus/LConfig b/lunaix-os/hal/bus/LConfig new file mode 100644 index 0000000..74e3dbf --- /dev/null +++ b/lunaix-os/hal/bus/LConfig @@ -0,0 +1,35 @@ + +@Collection +def bus_if(): + """ System/platform bus interface """ + + add_to_collection(hal) + + @Term + def pci_enable(): + """ Peripheral Component Interconnect (PCI) Bus """ + type(bool) + default(True) + + @Term + def pcie_ext(): + """ Enable support of PCI-Express extension """ + type(bool) + default(False) + + return v(pci_enable) + + @Term + def pci_pmio(): + """ Use port-mapped I/O interface for controlling PCI """ + type(bool) + + has_pcie = v(pcie_ext) + is_x86 = v(arch) in [ "i386", "x86_64" ] + + default(not has_pcie) + + if not is_x86 or has_pcie: + set_value(False) + + return is_x86 and v(pci_enable) \ No newline at end of file diff --git a/lunaix-os/hal/bus/pci.c b/lunaix-os/hal/bus/pci.c index af7a256..6d26d09 100644 --- a/lunaix-os/hal/bus/pci.c +++ b/lunaix-os/hal/bus/pci.c @@ -9,7 +9,6 @@ * */ #include -#include #include #include @@ -71,23 +70,19 @@ pci_create_device(pciaddr_t loc, ptr_t pci_base, int devinfo) } int -pci_bind_definition(struct pci_device_def* pcidev_def, int* more) +pci_bind_definition(struct pci_device_def* pcidef, bool* more) { - u32_t class = pcidev_def->dev_class; - u32_t devid_mask = pcidev_def->ident_mask; - u32_t devid = pcidev_def->dev_ident & devid_mask; - - if (!pcidev_def->devdef.bind) { + if (!pcidef->devdef.bind) { ERROR("pcidev %xh:%xh.%d is unbindable", - pcidev_def->devdef.class.fn_grp, - pcidev_def->devdef.class.device, - pcidev_def->devdef.class.variant); + pcidef->devdef.class.fn_grp, + pcidef->devdef.class.device, + pcidef->devdef.class.variant); return EINVAL; } - *more = 0; + *more = false; - int bind_attempted = 0; + bool bind_attempted = 0; int errno = 0; struct device_def* devdef; @@ -98,23 +93,22 @@ pci_bind_definition(struct pci_device_def* pcidev_def, int* more) continue; } - if (class != PCI_DEV_CLASS(pos->class_info)) { - continue; - } + bool matched; - int matched = (pos->device_info & devid_mask) == devid; + assert(pcidef->test_compatibility); + matched = pcidef->test_compatibility(pcidef, pos); if (!matched) { continue; } if (bind_attempted) { - *more = 1; + *more = true; break; } - bind_attempted = 1; - devdef = &pcidev_def->devdef; + bind_attempted = true; + devdef = &pcidef->devdef; errno = devdef->bind(devdef, &pos->dev); if (errno) { @@ -127,7 +121,7 @@ pci_bind_definition(struct pci_device_def* pcidev_def, int* more) continue; } - pos->binding.def = &pcidev_def->devdef; + pos->binding.def = &pcidef->devdef; } return errno; @@ -136,9 +130,10 @@ pci_bind_definition(struct pci_device_def* pcidev_def, int* more) int pci_bind_definition_all(struct pci_device_def* pcidef) { - int more = 0, e = 0; + int e = 0; + bool more = false; do { - if (!(e = pci_bind_definition(pcidef, &more))) { + if ((e = pci_bind_definition(pcidef, &more))) { break; } } while (more); @@ -201,7 +196,7 @@ pci_probe_bar_info(struct pci_device* device) { u32_t bar; struct pci_base_addr* ba; - for (size_t i = 0; i < 6; i++) { + for (size_t i = 0; i < PCI_BAR_COUNT; i++) { ba = &device->bar[i]; ba->size = pci_bar_sizing(device, &bar, i + 1); if (PCI_BAR_MMIO(bar)) { @@ -327,6 +322,21 @@ pci_get_device_by_class(u32_t class) return NULL; } +void +pci_apply_command(struct pci_device* pcidev, pci_reg_t cmd) +{ + pci_reg_t rcmd; + ptr_t base; + + base = pcidev->cspace_base; + rcmd = pci_read_cspace(base, PCI_REG_STATUS_CMD); + + cmd = cmd & 0xffff; + rcmd = (rcmd & 0xffff0000) | cmd; + + pci_write_cspace(base, PCI_REG_STATUS_CMD, rcmd); +} + static void __pci_read_cspace(struct twimap* map) { diff --git a/lunaix-os/hal/char/LConfig b/lunaix-os/hal/char/LConfig new file mode 100644 index 0000000..1b5ead4 --- /dev/null +++ b/lunaix-os/hal/char/LConfig @@ -0,0 +1,7 @@ +include("uart") + +@Collection +def char_device(): + """ Controlling support of character devices """ + + add_to_collection(hal) diff --git a/lunaix-os/hal/char/serial.c b/lunaix-os/hal/char/serial.c index dea044f..15a9c9e 100644 --- a/lunaix-os/hal/char/serial.c +++ b/lunaix-os/hal/char/serial.c @@ -3,12 +3,15 @@ #include #include #include +#include #include #include #include +LOG_MODULE("serial") + #define lock_sdev(sdev) device_lock((sdev)->dev) #define unlock_sdev(sdev) device_unlock((sdev)->dev) #define unlock_and_wait(sdev, wq) \ @@ -267,10 +270,13 @@ serial_create(struct devclass* class, char* if_ident) device_grant_capability(dev, cap_meta(tp_cap)); - register_device(dev, class, "s%d", class->variant); + register_device(dev, class, "%s%d", if_ident, class->variant); term_create(dev, if_ident); + INFO("interface: %s, %xh:%xh.%d", dev->name_val, + class->fn_grp, class->device, class->variant); + class->variant++; return sdev; } diff --git a/lunaix-os/hal/char/uart/16550_pmio.c b/lunaix-os/hal/char/uart/16550_pmio.c deleted file mode 100644 index 62d403a..0000000 --- a/lunaix-os/hal/char/uart/16550_pmio.c +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @file 16550_pmio.c - * @author Lunaixsky (lunaxisky@qq.com) - * @brief 16550 UART with port mapped IO - * @version 0.1 - * @date 2023-08-30 - * - * @copyright Copyright (c) 2023 - * - */ -#include -#include - -#include - -#include "16550.h" - -#define DELAY 50 - -static DEFINE_LLIST(com_ports); - -static u32_t -com_regread(struct uart16550* uart, ptr_t regoff) -{ - u8_t val = port_rdbyte(uart->base_addr + regoff); - port_delay(DELAY); - - return (u32_t)val; -} - -static void -com_regwrite(struct uart16550* uart, ptr_t regoff, u32_t val) -{ - port_wrbyte(uart->base_addr + regoff, (u8_t)val); - port_delay(DELAY); -} - -static void -com_irq_handler(const struct hart_state* hstate) -{ - int vector = hart_vector_stamp(hstate); - uart_general_irq_handler(vector, &com_ports); -} - -static int -upiom_init(struct device_def* def) -{ - int irq3 = 3, irq4 = 4; - u16_t ioports[] = { 0x3F8, 0x2F8, 0x3E8, 0x2E8 }; - int* irqs[] = { &irq4, &irq3, &irq4, &irq3 }; - - struct uart16550* uart = NULL; - - // COM 1...4 - for (size_t i = 0; i < 4; i++) { - if (!uart) { - uart = uart_alloc(ioports[i]); - uart->read_reg = com_regread; - uart->write_reg = com_regwrite; - } else { - uart->base_addr = ioports[i]; - } - - if (!uart_testport(uart, 0xe3)) { - continue; - } - - int irq = *irqs[i]; - if (irq) { - /* - * Since these irqs are overlapped, this particular setup is needed - * to avoid double-bind - */ - uart->iv = isrm_bindirq(irq, com_irq_handler); - *((volatile int*)irqs[i]) = 0; - } - - uart_enable_fifo(uart, UART_FIFO8); - llist_append(&com_ports, &uart->local_ports); - - struct serial_dev* sdev = serial_create(&def->class, "S"); - sdev->backend = uart; - sdev->write = uart_general_tx; - sdev->exec_cmd = uart_general_exec_cmd; - - uart->sdev = sdev; - - uart_setup(uart); - uart_setie(uart); - uart = NULL; - } - - if (uart) { - uart_free(uart); - } - - return 0; -} - -static struct device_def uart_pmio_def = { - .class = DEVCLASS(DEVIF_SOC, DEVFN_CHAR, DEV_UART16550), - .name = "16550 Generic UART (I/O)", - .init = upiom_init -}; -EXPORT_DEVICE(uart16550_pmio, &uart_pmio_def, load_onboot); \ No newline at end of file diff --git a/lunaix-os/hal/char/uart/16550.h b/lunaix-os/hal/char/uart/16x50.h similarity index 84% rename from lunaix-os/hal/char/uart/16550.h rename to lunaix-os/hal/char/uart/16x50.h index 5b4040b..addedfa 100644 --- a/lunaix-os/hal/char/uart/16550.h +++ b/lunaix-os/hal/char/uart/16x50.h @@ -58,6 +58,12 @@ #define UART_SENT_ALL 0b0010 #define UART_MODEM_UPDATE 0b0000 +#define UART_LCR_RESET \ + (UART_rLC_STOPB | \ + UART_rLC_PAREN | \ + UART_rLC_PAREVN | \ + UART_rLC_DLAB | 0b11) + struct uart16550 { struct llist_header local_ports; @@ -220,6 +226,34 @@ int uart_general_tx(struct serial_dev* sdev, u8_t* data, size_t len); void -uart_general_irq_handler(int iv, struct llist_header* ports); +uart_handle_irq_overlap(int iv, struct llist_header* ports); + +void +uart_handle_irq(int iv, struct uart16550 *uart); + +static inline struct serial_dev* +uart_create_serial(struct uart16550* uart, struct devclass* class, + struct llist_header* ports, char* if_ident) +{ + llist_append(ports, &uart->local_ports); + + struct serial_dev* sdev = serial_create(class, if_ident); + sdev->backend = uart; + sdev->write = uart_general_tx; + sdev->exec_cmd = uart_general_exec_cmd; + + uart->sdev = sdev; + + uart_setup(uart); + uart_setie(uart); + + return sdev; +} + +struct uart16550* +uart16x50_pmio_create(ptr_t base); + +struct uart16550* +uart16x50_mmio_create(ptr_t base, ptr_t size); #endif /* __LUNAIX_16550_H */ diff --git a/lunaix-os/hal/char/uart/16550_base.c b/lunaix-os/hal/char/uart/16x50_base.c similarity index 84% rename from lunaix-os/hal/char/uart/16550_base.c rename to lunaix-os/hal/char/uart/16x50_base.c index a84e3d0..6e94b84 100644 --- a/lunaix-os/hal/char/uart/16550_base.c +++ b/lunaix-os/hal/char/uart/16x50_base.c @@ -4,7 +4,7 @@ #include #include -#include "16550.h" +#include "16x50.h" struct uart16550* uart_alloc(ptr_t base_addr) @@ -42,12 +42,6 @@ uart_general_tx(struct serial_dev* sdev, u8_t* data, size_t len) return RXTX_DONE; } -#define UART_LCR_RESET \ - (UART_rLC_STOPB | \ - UART_rLC_PAREN | \ - UART_rLC_PAREVN | \ - UART_rLC_DLAB | 0b11) - static void uart_set_control_mode(struct uart16550* uart, tcflag_t cflags) { @@ -88,9 +82,8 @@ uart_general_exec_cmd(struct serial_dev* sdev, u32_t req, va_list args) } void -uart_general_irq_handler(int iv, struct llist_header* ports) +uart_handle_irq_overlap(int iv, struct llist_header* ports) { - char tmpbuf[32]; struct uart16550 *pos, *n; llist_for_each(pos, n, ports, local_ports) { @@ -103,24 +96,31 @@ uart_general_irq_handler(int iv, struct llist_header* ports) return; done: + uart_handle_irq(iv, pos); +} + +void +uart_handle_irq(int iv, struct uart16550 *uart) +{ + char tmpbuf[32]; char recv; int i = 0; - while ((recv = uart_read_byte(pos))) { + while ((recv = uart_read_byte(uart))) { tmpbuf[i++] = recv; if (likely(i < 32)) { continue; } - if (!serial_accept_buffer(pos->sdev, tmpbuf, i)) { - uart_clear_rxfifo(pos); + if (!serial_accept_buffer(uart->sdev, tmpbuf, i)) { + uart_clear_rxfifo(uart); return; } i = 0; } - serial_accept_buffer(pos->sdev, tmpbuf, i); - serial_accept_one(pos->sdev, 0); + serial_accept_buffer(uart->sdev, tmpbuf, i); + serial_accept_one(uart->sdev, 0); - serial_end_recv(pos->sdev); -} + serial_end_recv(uart->sdev); +} \ No newline at end of file diff --git a/lunaix-os/hal/char/uart/16x50_isa.c b/lunaix-os/hal/char/uart/16x50_isa.c new file mode 100644 index 0000000..d8d2e1b --- /dev/null +++ b/lunaix-os/hal/char/uart/16x50_isa.c @@ -0,0 +1,64 @@ +#include +#include +#include + +#include + +#include "16x50.h" + +LOG_MODULE("16x50-isa"); + +static DEFINE_LLIST(com_ports); + +static void +com_irq_handler(const struct hart_state* hstate) +{ + int vector = hart_vector_stamp(hstate); + uart_handle_irq_overlap(vector, &com_ports); +} + +static int +upiom_init(struct device_def* def) +{ + int irq3 = 3, irq4 = 4; + u16_t ioports[] = { 0x3F8, 0x2F8, 0x3E8, 0x2E8 }; + int* irqs[] = { &irq4, &irq3, &irq4, &irq3 }; + + struct uart16550* uart = NULL; + ptr_t base; + + // COM 1...4 + for (size_t i = 0; i < 4; i++) { + + base = ioports[i]; + uart = uart16x50_pmio_create(base); + if (!uart) { + WARN("port 0x%x not accessible", base); + continue; + } + + int irq = *irqs[i]; + if (irq) { + /* + * Since these irqs are overlapped, this particular setup is needed + * to avoid double-bind + */ + uart->iv = isrm_bindirq(irq, com_irq_handler); + *((volatile int*)irqs[i]) = 0; + } + + INFO("base: 0x%x, irq=%d", + base, irq, uart->iv); + + uart_create_serial(uart, &def->class, &com_ports, "S"); + } + + return 0; +} + +static struct device_def uart_pmio_def = { + .class = DEVCLASS(DEVIF_SOC, DEVFN_CHAR, DEV_UART16550), + .name = "16550 UART (PIO)", + .init = upiom_init +}; +EXPORT_DEVICE(uart16550_pmio, &uart_pmio_def, load_onboot); \ No newline at end of file diff --git a/lunaix-os/hal/char/uart/16x50_mmio.c b/lunaix-os/hal/char/uart/16x50_mmio.c new file mode 100644 index 0000000..2e32b75 --- /dev/null +++ b/lunaix-os/hal/char/uart/16x50_mmio.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "16x50.h" + +static u32_t +uart_mmio_regread(struct uart16550* uart, ptr_t regoff) +{ + return (u32_t)(*(u8_t*)(uart->base_addr + regoff)); +} + +static void +uart_mmio_regwrite(struct uart16550* uart, ptr_t regoff, u32_t val) +{ + *(u8_t*)(uart->base_addr + regoff) = (u8_t)val; +} + +struct uart16550* +uart16x50_mmio_create(ptr_t base, ptr_t size) +{ + ptr_t mmio_base; + struct uart16550* uart; + + base = ioremap(base, size); + uart = uart_alloc(base); + uart->read_reg = uart_mmio_regread; + uart->write_reg = uart_mmio_regwrite; + + if (!uart_testport(uart, 0xe3)) { + iounmap(base, size); + uart_free(uart); + return NULL; + } + + uart_enable_fifo(uart, UART_FIFO8); + + return uart; +} \ No newline at end of file diff --git a/lunaix-os/hal/char/uart/16x50_pci.c b/lunaix-os/hal/char/uart/16x50_pci.c new file mode 100644 index 0000000..880fd2d --- /dev/null +++ b/lunaix-os/hal/char/uart/16x50_pci.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include + +#include + +#include "16x50.h" + +#define PCI_DEVICE_16x50_UART 0x070000 + +LOG_MODULE("16x50-pci") + +static DEFINE_LLIST(pci_ports); + +static void +uart_msi_irq_handler(const struct hart_state* hstate) +{ + int vector; + struct uart16550* uart; + + vector = hart_vector_stamp(hstate); + uart = (struct uart16550*)isrm_get_payload(hstate); + + assert(uart); + uart_handle_irq(vector, uart); +} + +static void +uart_intx_irq_handler(const struct hart_state* hstate) +{ + int vector = hart_vector_stamp(hstate); + uart_handle_irq_overlap(vector, &pci_ports); +} + +static int +pci16550_init(struct device_def* def) +{ + return pci_bind_definition_all(pcidev_def(def)); +} + +static bool +pci16650_check_compat(struct pci_device_def* def, + struct pci_device* pcidev) +{ + unsigned int classid; + classid = pci_device_class(pcidev); + + return (classid & 0xffff00) == PCI_DEVICE_16x50_UART; +} + +static int +pci16650_binder(struct device_def* def, struct device* dev) +{ + int irq; + struct pci_base_addr* bar; + struct pci_device* pcidev; + struct uart16550* uart; + struct serial_dev* sdev; + + pcidev = PCI_DEVICE(dev); + + pci_reg_t cmd = 0; + + for (int i = 0; i < PCI_BAR_COUNT; i++) + { + cmd = 0; + pci_cmd_set_msi(&cmd); + + bar = pci_device_bar(pcidev, i); + if (bar->size == 0) { + continue; + } + + if (!pci_bar_mmio_space(bar)) { + pci_cmd_set_pmio(&cmd); + pci_apply_command(pcidev, cmd); + + uart = uart16x50_pmio_create(bar->start); + } + else { + pci_cmd_set_mmio(&cmd); + pci_apply_command(pcidev, cmd); + + uart = uart16x50_mmio_create(bar->start, bar->size); + } + + if (!uart) { + WARN("not accessible (BAR #%d)", i); + continue; + } + + if (pci_capability_msi(pcidev)) { + irq = isrm_ivexalloc(uart_msi_irq_handler); + isrm_set_payload(irq, __ptr(uart)); + pci_setup_msi(pcidev, irq); + } + else { + irq = pci_intr_irq(pcidev); + irq = isrm_bindirq(irq, uart_intx_irq_handler); + } + + INFO("base: 0x%x (%s), irq=%d (%s)", + bar->start, + pci_bar_mmio_space(bar) ? "mmio" : "pmio", + irq, + pci_capability_msi(pcidev) ? "msi" : "intx, re-routed"); + + uart->iv = irq; + + sdev = uart_create_serial(uart, &def->class, &pci_ports, "PCI"); + pci_bind_instance(pcidev, sdev); + } + + return 0; +} + +static struct pci_device_def uart_pci_def = { + .devdef = { .class = DEVCLASS(DEVIF_PCI, DEVFN_CHAR, DEV_UART16550), + .name = "16550 UART (PCI/MMIO)", + .init = pci16550_init, + .bind = pci16650_binder }, + .test_compatibility = pci16650_check_compat +}; +EXPORT_PCI_DEVICE(uart16550_pci, &uart_pci_def, load_onboot); \ No newline at end of file diff --git a/lunaix-os/hal/char/uart/16x50_pmio.c b/lunaix-os/hal/char/uart/16x50_pmio.c new file mode 100644 index 0000000..76ab3bc --- /dev/null +++ b/lunaix-os/hal/char/uart/16x50_pmio.c @@ -0,0 +1,54 @@ +/** + * @file 16550_pmio.c + * @author Lunaixsky (lunaxisky@qq.com) + * @brief 16550 UART with port mapped IO + * @version 0.1 + * @date 2023-08-30 + * + * @copyright Copyright (c) 2023 + * + */ +#include +#include + +#include + +#include "16x50.h" + +#define DELAY 50 + +static u32_t +com_regread(struct uart16550* uart, ptr_t regoff) +{ + u8_t val = port_rdbyte(uart->base_addr + regoff); + port_delay(DELAY); + + return (u32_t)val; +} + +static void +com_regwrite(struct uart16550* uart, ptr_t regoff, u32_t val) +{ + port_wrbyte(uart->base_addr + regoff, (u8_t)val); + port_delay(DELAY); +} + + +struct uart16550* +uart16x50_pmio_create(ptr_t base) +{ + struct uart16550* uart; + + uart = uart_alloc(base); + uart->read_reg = com_regread; + uart->write_reg = com_regwrite; + + if (!uart_testport(uart, 0xe3)) { + uart_free(uart); + return NULL; + } + + uart_enable_fifo(uart, UART_FIFO8); + + return uart; +} diff --git a/lunaix-os/hal/char/uart/LBuild b/lunaix-os/hal/char/uart/LBuild index 1fbc933..83088c6 100644 --- a/lunaix-os/hal/char/uart/LBuild +++ b/lunaix-os/hal/char/uart/LBuild @@ -1,4 +1,11 @@ sources([ - "16550_base.c", - "16550_pmio.c" -]) \ No newline at end of file + "16x50_base.c", + "16x50_pmio.c", + "16x50_mmio.c", +]) + +if config("xt_16x50"): + sources("16x50_isa.c") + +if config("pci_16x50"): + sources("16x50_pci.c") \ No newline at end of file diff --git a/lunaix-os/hal/char/uart/LConfig b/lunaix-os/hal/char/uart/LConfig new file mode 100644 index 0000000..9ed1f09 --- /dev/null +++ b/lunaix-os/hal/char/uart/LConfig @@ -0,0 +1,25 @@ + +@Collection("16x50 Serial Controller") +def uart_16x50(): + """ 16x50 serial controller """ + + # hal/char/LConfig::char_device + add_to_collection(char_device) + + @Term("16x50 XT-Compat") + def xt_16x50(): + """ Enable the 16x50 for PC-compatible platform """ + + type(bool) + + is_x86 = v(arch) in ["i386", "x86_64"] + default(is_x86) + + return is_x86 + + @Term("16x50 PCI") + def pci_16x50(): + """ Enable the support of PCI 16x50 """ + type(bool) + + default(True) \ No newline at end of file diff --git a/lunaix-os/hal/gfxa/vga/vga_pci.c b/lunaix-os/hal/gfxa/vga/vga_pci.c index 09476db..1109e5a 100644 --- a/lunaix-os/hal/gfxa/vga/vga_pci.c +++ b/lunaix-os/hal/gfxa/vga/vga_pci.c @@ -6,7 +6,6 @@ #include #include -#include #include @@ -76,8 +75,8 @@ vga_pci_bind(struct device_def* devdef, struct device* pcidev_base) pci_write_cspace(pcidev->cspace_base, PCI_REG_STATUS_CMD, cmd); - ptr_t fb_mapped = (ptr_t)ioremap(fb->start, FB256K); - ptr_t mmio_mapped = (ptr_t)ioremap(mmio->start, mmio->size); + ptr_t fb_mapped = ioremap(fb->start, FB256K); + ptr_t mmio_mapped = ioremap(mmio->start, mmio->size); struct vga* vga_state = vga_new_state(mmio_mapped + VGA_REG_OFF, fb_mapped, FB256K); @@ -105,13 +104,19 @@ vga_pci_init(struct device_def* def) #define VGA_PCI_CLASS 0x30000 +static bool +vga_pci_compat(struct pci_device_def* def, + struct pci_device* pcidev) +{ + return pci_device_class(pcidev) == VGA_PCI_CLASS; +} + + static struct pci_device_def vga_pci_devdef = { - .dev_class = VGA_PCI_CLASS, - .dev_ident = PCI_DEVIDENT(0x1234, 0x1111), - .ident_mask = PCI_MATCH_EXACT, .devdef = { .class = DEVCLASS(DEVIF_PCI, DEVFN_DISP, DEV_VGA), .name = "Generic VGA", .init = vga_pci_init, - .bind = vga_pci_bind } + .bind = vga_pci_bind }, + .test_compatibility = vga_pci_compat }; EXPORT_PCI_DEVICE(vga_pci, &vga_pci_devdef, load_onboot); \ No newline at end of file diff --git a/lunaix-os/includes/hal/pci.h b/lunaix-os/includes/hal/pci.h index da45090..d457a1e 100644 --- a/lunaix-os/includes/hal/pci.h +++ b/lunaix-os/includes/hal/pci.h @@ -38,6 +38,7 @@ #define PCI_BAR_TYPE(x) ((x) & 0x6) #define PCI_BAR_ADDR_MM(x) ((x) & ~0xf) #define PCI_BAR_ADDR_IO(x) ((x) & ~0x3) +#define PCI_BAR_COUNT 6 #define PCI_MSI_ADDR_LO(msi_base) ((msi_base) + 4) #define PCI_MSI_ADDR_HI(msi_base) ((msi_base) + 8) @@ -116,10 +117,9 @@ typedef void* (*pci_drv_init)(struct pci_device*); struct pci_device_def { - u32_t dev_class; - u32_t dev_ident; - u32_t ident_mask; struct device_def devdef; + + bool (*test_compatibility)(struct pci_device_def*, struct pci_device*); }; #define pcidev_def(dev_def_ptr) \ container_of((dev_def_ptr), struct pci_device_def, devdef) @@ -176,9 +176,98 @@ void pci_probe_msi_info(struct pci_device* device); int -pci_bind_definition(struct pci_device_def* pcidev_def, int* more); +pci_bind_definition(struct pci_device_def* pcidev_def, bool* more); int pci_bind_definition_all(struct pci_device_def* pcidef); +static inline unsigned int +pci_device_vendor(struct pci_device* pcidev) +{ + return PCI_DEV_VENDOR(pcidev->device_info); +} + +static inline unsigned int +pci_device_devid(struct pci_device* pcidev) +{ + return PCI_DEV_DEVID(pcidev->device_info); +} + +static inline unsigned int +pci_device_class(struct pci_device* pcidev) +{ + return PCI_DEV_CLASS(pcidev->class_info); +} + +static inline struct pci_base_addr* +pci_device_bar(struct pci_device* pcidev, int index) +{ + return &pcidev->bar[index]; +} + +static inline void +pci_cmd_set_mmio(pci_reg_t* cmd) +{ + *cmd |= PCI_RCMD_MM_ACCESS; +} + + +static inline void +pci_cmd_set_pmio(pci_reg_t* cmd) +{ + *cmd |= PCI_RCMD_IO_ACCESS; +} + +static inline void +pci_cmd_set_msi(pci_reg_t* cmd) +{ + *cmd |= PCI_RCMD_DISABLE_INTR; +} + +static inline void +pci_cmd_set_bus_master(pci_reg_t* cmd) +{ + *cmd |= PCI_RCMD_BUS_MASTER; +} + +static inline void +pci_cmd_set_fast_b2b(pci_reg_t* cmd) +{ + *cmd |= PCI_RCMD_FAST_B2B; +} + +static inline bool +pci_bar_mmio_space(struct pci_base_addr* bar) +{ + return (bar->type & BAR_TYPE_MMIO); +} + +static inline bool +pci_capability_msi(struct pci_device* pcidev) +{ + return !!pcidev->msi_loc; +} + +static inline int +pci_intr_irq(struct pci_device* pcidev) +{ + return PCI_INTR_IRQ(pcidev->intr_info); +} + +void +pci_apply_command(struct pci_device* pcidev, pci_reg_t cmd); + +pci_reg_t +pci_read_cspace(ptr_t base, int offset); + +void +pci_write_cspace(ptr_t base, int offset, pci_reg_t data); + +u16_t +pci_config_msi_data(int vector); + +ptr_t +pci_get_msi_base(); + + #endif /* __LUNAIX_PCI_H */ diff --git a/lunaix-os/includes/lunaix/kpreempt.h b/lunaix-os/includes/lunaix/kpreempt.h index d20deba..e622e05 100644 --- a/lunaix-os/includes/lunaix/kpreempt.h +++ b/lunaix-os/includes/lunaix/kpreempt.h @@ -3,7 +3,8 @@ #include -#define _preemptible __attribute__((section(".kf.preempt"))) +#define _preemptible \ + __attribute__((section(".kf.preempt"))) no_inline #define ensure_preempt_caller() \ do { \ diff --git a/lunaix-os/includes/lunaix/mm/mmio.h b/lunaix-os/includes/lunaix/mm/mmio.h index 18d1c4f..29b0786 100644 --- a/lunaix-os/includes/lunaix/mm/mmio.h +++ b/lunaix-os/includes/lunaix/mm/mmio.h @@ -3,7 +3,7 @@ #include -void* +ptr_t ioremap(ptr_t paddr, u32_t size); void diff --git a/lunaix-os/includes/lunaix/mm/page.h b/lunaix-os/includes/lunaix/mm/page.h index 23d5c64..0f9fb0c 100644 --- a/lunaix-os/includes/lunaix/mm/page.h +++ b/lunaix-os/includes/lunaix/mm/page.h @@ -284,6 +284,13 @@ vmap_range(pfn_t start, size_t npages, pte_attr_t prot) return vmap_ptes_at(_pte, LFT_SIZE, npages); } +static inline void +vunmap_range(pfn_t start, size_t npages) +{ + pte_t* ptep = mkptep_va(VMS_SELF, start); + vmm_set_ptes_contig(ptep, null_pte, LFT_SIZE, npages); +} + /** * @brief Allocate a page in kernel space. diff --git a/lunaix-os/kernel/LConfig b/lunaix-os/kernel/LConfig index c820fd0..37b9005 100644 --- a/lunaix-os/kernel/LConfig +++ b/lunaix-os/kernel/LConfig @@ -3,4 +3,4 @@ def kernel_feature(): """ Config kernel features """ pass -include("mm/LConfig") \ No newline at end of file +include("mm") \ No newline at end of file diff --git a/lunaix-os/kernel/kinit.c b/lunaix-os/kernel/kinit.c index 685c3d0..ca28001 100644 --- a/lunaix-os/kernel/kinit.c +++ b/lunaix-os/kernel/kinit.c @@ -43,7 +43,7 @@ kernel_bootstrap(struct boot_handoff* bhctx) /* Begin kernel bootstrapping sequence */ boot_begin(bhctx); - tty_init(ioremap(0xB8000, PAGE_SIZE)); + tty_init((void*)ioremap(0xB8000, PAGE_SIZE)); /* Setup kernel memory layout and services */ kmem_init(bhctx); diff --git a/lunaix-os/kernel/mm/mmio.c b/lunaix-os/kernel/mm/mmio.c index b91ab0e..dbb1870 100644 --- a/lunaix-os/kernel/mm/mmio.c +++ b/lunaix-os/kernel/mm/mmio.c @@ -2,7 +2,7 @@ #include #include -void* +ptr_t ioremap(ptr_t paddr, u32_t size) { // FIXME implement a page policy interface allow to decouple the @@ -14,21 +14,13 @@ ioremap(ptr_t paddr, u32_t size) // Ensure the range is reservable (not already in use) assert(pmm_onhold_range(start, npages)); - return (void*)vmap_range(start, npages, KERNEL_DATA); + ptr_t addr = vmap_range(start, npages, KERNEL_DATA); + return addr + va_offset(paddr); } void iounmap(ptr_t vaddr, u32_t size) { - // FIXME - fail("need fix"); - - // pte_t* ptep = mkptep_va(VMS_SELF, vaddr); - // for (size_t i = 0; i < size; i += PAGE_SIZE, ptep++) { - // pte_t pte = pte_at(ptep); - - // set_pte(ptep, null_pte); - // if (pte_isloaded(pte)) - // return_page(ppage_pa(pte_paddr(pte))); - // } + assert(vaddr >= VMAP && vaddr < VMAP_END); + vunmap_range(pfn(vaddr), leaf_count(size)); } \ No newline at end of file diff --git a/lunaix-os/live_debug.sh b/lunaix-os/live_debug.sh index e10f71c..6462f5d 100755 --- a/lunaix-os/live_debug.sh +++ b/lunaix-os/live_debug.sh @@ -4,7 +4,7 @@ hmp_port=45454 gdb_port=1234 default_cmd="console=/dev/ttyS0" -make CMDLINE=${default_cmd} ARCH=${ARCH} MODE=debug image -j5 || exit -1 +make CMDLINE=${default_cmd} ARCH=${ARCH} MODE=${MODE:-debug} image -j5 || exit -1 ./scripts/qemu.py \ scripts/qemus/qemu_x86_dev.json \ diff --git a/lunaix-os/makeinc/toolchain.mkinc b/lunaix-os/makeinc/toolchain.mkinc index a538488..486e9ce 100644 --- a/lunaix-os/makeinc/toolchain.mkinc +++ b/lunaix-os/makeinc/toolchain.mkinc @@ -14,21 +14,12 @@ W := -Wall -Wextra -Werror \ -Wno-discarded-qualifiers\ -Werror=incompatible-pointer-types -OFLAGS := -fno-gcse\ - -fno-gcse-lm\ - -fno-cse-follow-jumps\ - -fno-cse-skip-blocks\ - -fno-optimize-strlen\ - -fno-inline-functions-called-once \ - -fno-inline-small-functions \ - -fno-indirect-inlining\ - -fno-omit-frame-pointer +OFLAGS := -fno-omit-frame-pointer -CFLAGS := -std=gnu99 $(OFLAGS) $(W) +CFLAGS := -std=gnu99 $(OFLAGS) $(W) -g ifeq ($(MODE),debug) O = -Og - CFLAGS += -g endif CFLAGS += $(O) diff --git a/lunaix-os/scripts/build-tools/integration/config_io.py b/lunaix-os/scripts/build-tools/integration/config_io.py index 33079ff..15e1ba8 100644 --- a/lunaix-os/scripts/build-tools/integration/config_io.py +++ b/lunaix-os/scripts/build-tools/integration/config_io.py @@ -26,7 +26,7 @@ class CHeaderConfigProvider(ConfigIOProvider): v = self.serialize_value(v) if v is None or v is False: result.insert(0, "//") - else: + elif isinstance(v, str): result.append(v) lines.append(" ".join(result)) diff --git a/lunaix-os/scripts/build-tools/integration/render_ishell.py b/lunaix-os/scripts/build-tools/integration/render_ishell.py index 5eacf55..4fdee85 100644 --- a/lunaix-os/scripts/build-tools/integration/render_ishell.py +++ b/lunaix-os/scripts/build-tools/integration/render_ishell.py @@ -207,7 +207,7 @@ class InteractiveShell(InteractiveRenderer): def do_read(self, node_name): view, _ = self.resolve(node_name) rd_val = view.read(self.__sctx) - if not rd_val: + if rd_val is None: raise ShellException(f"config node {view.label} is not readable") print(rd_val) diff --git a/lunaix-os/scripts/build-tools/lcfg/builtins.py b/lunaix-os/scripts/build-tools/lcfg/builtins.py index f32b9b6..f072303 100644 --- a/lunaix-os/scripts/build-tools/lcfg/builtins.py +++ b/lunaix-os/scripts/build-tools/lcfg/builtins.py @@ -13,8 +13,12 @@ def v(env, caller, term): def include(env, caller, file): fobj = caller.get_fo() path = os.path.dirname(fobj.filename()) + path = join_path(path, file) + + if os.path.isdir(path): + path = join_path(path, "LConfig") - env.resolve_module(join_path(path, file)) + env.resolve_module(path) @contextual("type", caller_type=[LCTermNode]) def term_type(env, caller, type): diff --git a/lunaix-os/scripts/qemu.py b/lunaix-os/scripts/qemu.py index 5338326..ce843da 100755 --- a/lunaix-os/scripts/qemu.py +++ b/lunaix-os/scripts/qemu.py @@ -80,13 +80,14 @@ class PCISerialDevice(QEMUPeripherals): super().__init__("pci-serial", opt) def get_qemu_opts(self): - name = f"chrdev.{hex(self.__hash__())[2:]}" - cmds = [ "pci-serial", f"chardev={name}" ] + uniq = hex(self.__hash__())[2:] + name = f"chrdev.{uniq}" + cmds = [ "pci-serial", f"id=uart.{uniq}", f"chardev={name}" ] chrdev = [ "file", f"id={name}" ] logfile = get_config(self._opt, "logfile", required=True) chrdev.append(f"path={logfile}") - () + return [ "-chardev", join_attrs(chrdev), "-device", join_attrs(cmds) @@ -196,7 +197,7 @@ class QEMUExec: trace_opts = get_config(debug, "traced", []) for trace in trace_opts: - cmds += [ "-d", f"trace:{trace}"] + cmds += [ "--trace", f"{trace}"] return cmds diff --git a/lunaix-os/scripts/qemus/qemu_x86_dev.json b/lunaix-os/scripts/qemus/qemu_x86_dev.json index 474ad1a..e006691 100644 --- a/lunaix-os/scripts/qemus/qemu_x86_dev.json +++ b/lunaix-os/scripts/qemus/qemu_x86_dev.json @@ -32,6 +32,14 @@ "addr": ":12345", "logfile": "lunaix_ttyS0.log" }, + { + "class": "pci-serial", + "logfile": "ttyPCI0.log" + }, + { + "class": "pci-serial", + "logfile": "ttyPCI1.log" + }, { "class": "rtc", "base": "utc" diff --git a/lunaix-os/usr/LBuild b/lunaix-os/usr/LBuild index 03a89ee..ec56dcb 100644 --- a/lunaix-os/usr/LBuild +++ b/lunaix-os/usr/LBuild @@ -36,3 +36,5 @@ if config("arch") == "x86_64": else: compile_opts("-m32") linking_opts("-m32") + +compile_opts("-mno-sse") \ No newline at end of file diff --git a/lunaix-os/usr/ls.c b/lunaix-os/usr/ls.c index ece96e4..698d025 100644 --- a/lunaix-os/usr/ls.c +++ b/lunaix-os/usr/ls.c @@ -20,7 +20,7 @@ main(int argc, const char* argv[]) while ((dent = readdir(dir))) { if (dent->d_type == DT_DIR) { - printf(" %s\n", dent->d_name); + printf(" %s/\n", dent->d_name); } else if (dent->d_type == DT_SYMLINK) { printf(" %s@\n", dent->d_name); } else { diff --git a/lunaix-os/usr/testp.c b/lunaix-os/usr/testp.c index 8011c30..ee33fe6 100644 --- a/lunaix-os/usr/testp.c +++ b/lunaix-os/usr/testp.c @@ -6,26 +6,32 @@ #include void -test_serial() +test_serial(char* dev, int wr) { - int fd = open("/dev/ttyS0", FO_WRONLY); + int fd = open(dev, FO_WRONLY); if (fd < 0) { - printf("ttyS0 not accessable (%d)\n", errno); + printf("tty %s not accessable (%d)\n", dev, errno); return; } - char buf[32]; int sz = 0; + char buf[256]; - printf("ttyS0 input: "); + if (!wr) { + printf("tty input: "); - if ((sz = read(fd, buf, 31)) < 0) { - printf("write to ttyS0 failed (%d)\n", errno); - } + if ((sz = read(fd, buf, 31)) < 0) { + printf("read to tty failed (%d)\n", errno); + } - buf[sz] = 0; + buf[sz] = 0; - printf("%s", buf); + printf("%s", buf); + } + else { + int size = snprintf(buf, 256, "serial test: %s", dev); + write(fd, buf, size); + } close(fd); } @@ -33,18 +39,14 @@ test_serial() int main(int argc, char* argv[]) { - if (argc <= 1) { + if (argc != 2) { + printf("usage: testp path_to_dev\n"); return 1; } char* target = argv[1]; - if (!strcmp(target, "serial")) { - test_serial(); - } else { - printf("unknown test: %s\n", target); - return 1; - } + test_serial(target, 1); return 0; } \ No newline at end of file -- 2.27.0 From c4565f97ce186d1999082ae4c6d56c97c426fede Mon Sep 17 00:00:00 2001 From: Minep Date: Sun, 21 Jul 2024 21:30:14 +0100 Subject: [PATCH 06/16] add pre-commit hooks to regulate things --- lunaix-os/.pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lunaix-os/.pre-commit-config.yaml diff --git a/lunaix-os/.pre-commit-config.yaml b/lunaix-os/.pre-commit-config.yaml new file mode 100644 index 0000000..c64ee57 --- /dev/null +++ b/lunaix-os/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-merge-conflict + - id: check-added-large-files + - id: check-shebang-scripts-are-executable + - id: check-executables-have-shebangs -- 2.27.0 From 383318a4381375add624530005a803bd934ceec7 Mon Sep 17 00:00:00 2001 From: Minep Date: Sat, 27 Jul 2024 14:50:32 +0100 Subject: [PATCH 07/16] fix: gen_ksymtable does not work on non-English platform * the grep based method to access ELF class based on readelf output assume the English-language output * fix lunaconfig do not update the config tree when saving --- lunaix-os/arch/LConfig | 2 +- lunaix-os/kernel.mk | 2 +- lunaix-os/scripts/build-tools/lcfg/common.py | 2 +- lunaix-os/scripts/build-tools/luna_build.py | 2 ++ lunaix-os/scripts/gen_ksymtable.sh | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lunaix-os/arch/LConfig b/lunaix-os/arch/LConfig index 22f96c0..95403e3 100644 --- a/lunaix-os/arch/LConfig +++ b/lunaix-os/arch/LConfig @@ -13,7 +13,7 @@ def architecture_support(): default("i386") env_val = env("ARCH") - if env_val is not None: + if env_val: set_value(env_val) @Term diff --git a/lunaix-os/kernel.mk b/lunaix-os/kernel.mk index 69a16db..be10da2 100644 --- a/lunaix-os/kernel.mk +++ b/lunaix-os/kernel.mk @@ -45,7 +45,7 @@ $(tmp_kbin): $(klinking) $(ksrc_objs) $(ksymtable): $(tmp_kbin) $(call status_,KSYM,$@) - @scripts/gen_ksymtable.sh DdRrTtAGg $< > .lunaix_ksymtable.S + @ARCH=$(ARCH) scripts/gen_ksymtable.sh DdRrTtAGg $< > .lunaix_ksymtable.S @$(CC) $(CFLAGS) -c .lunaix_ksymtable.S -o $@ diff --git a/lunaix-os/scripts/build-tools/lcfg/common.py b/lunaix-os/scripts/build-tools/lcfg/common.py index 25e8d1b..383d1ec 100644 --- a/lunaix-os/scripts/build-tools/lcfg/common.py +++ b/lunaix-os/scripts/build-tools/lcfg/common.py @@ -114,7 +114,7 @@ class LConfigEnvironment(Renderable): super().__init__() self.__ws_path = path.abspath(workspace) - self.__exec_globl = globals() + self.__exec_globl = {} self.__eval_stack = [] self.__lc_modules = [] self.__config_val = {} diff --git a/lunaix-os/scripts/build-tools/luna_build.py b/lunaix-os/scripts/build-tools/luna_build.py index f5893c6..e3c6908 100755 --- a/lunaix-os/scripts/build-tools/luna_build.py +++ b/lunaix-os/scripts/build-tools/luna_build.py @@ -97,6 +97,8 @@ def main(): do_config(opts, lcfg_env) else: print("No configuration file detected, skipping...") + + lcfg_env.update() lcfg_env.save(opts.config_save) lcfg_env.export() else: diff --git a/lunaix-os/scripts/gen_ksymtable.sh b/lunaix-os/scripts/gen_ksymtable.sh index 1f974bd..748678e 100755 --- a/lunaix-os/scripts/gen_ksymtable.sh +++ b/lunaix-os/scripts/gen_ksymtable.sh @@ -4,13 +4,13 @@ sym_types=$1 bin=$2 nm_out=$(nm -nfbsd "$bin") -class_info=$(readelf -h "$bin" | grep 'Class:' | awk '{print $2}') +# class_info=$(readelf -h "$bin" | grep 'Class:' | awk '{print $2}') allsyms=($nm_out) allsyms_len=${#allsyms[@]} dtype="4byte" -if [ "$class_info" == 'ELF64' ]; then +if [ "$ARCH" == 'x86_64' ]; then dtype="8byte" fi -- 2.27.0 From bdc143a7aa3f51a46eceec62b0b364599533fa21 Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Sun, 28 Jul 2024 22:50:17 +0100 Subject: [PATCH 08/16] Change of vterm handling logic on backend chardev input event (#40) * fix: term should not ignore underlying input event * term should always respond the input event from underlying termport awared character device, by process the raw input buffer according one's lflags.ICANNO setting when the data is avaliable * fix: race when parent get destroyed before children * fix: orphan a process's children when it is about to destroy * fix: devzero: mis-used offset cause segfault * add support of setting serial io speed * fix: truncate the overflowed input instead of running over. * rewrite the line control * rewrite the line control implementation to make it clean and less hacky. * correct some POSIX compliance issue related to some control characters. * fix issue where ANSI line control failed to echo the transformed special character * add a sanity filter to sh to filter out any unprintable character. * fix: make lxconsole work with vterm refactor * fix: tdev_do_read do not check the linebuffer::sflag * add a config term for lxconsole (vgacon) * rename tty/tty.c to vga_rawtty.c for clarity --- lunaix-os/hal/char/LBuild | 6 +- lunaix-os/hal/char/LConfig | 7 + lunaix-os/hal/char/devzero.c | 4 +- lunaix-os/hal/char/lxconsole.c | 28 +- lunaix-os/hal/char/serial.c | 30 +- lunaix-os/hal/char/uart/16x50.h | 5 +- lunaix-os/hal/char/uart/16x50_base.c | 27 +- lunaix-os/hal/gfxa/vga/LBuild | 3 +- .../tty/tty.c => hal/gfxa/vga/vga_rawtty.c} | 14 +- lunaix-os/hal/term/LBuild | 1 + lunaix-os/hal/term/default_ops.c | 25 ++ lunaix-os/hal/term/lcntls/ansi_cntl.c | 21 +- lunaix-os/hal/term/lcntls/lcntl.c | 257 +++++++++++------- lunaix-os/hal/term/lcntls/lcntl.h | 77 ++++++ lunaix-os/hal/term/term.c | 61 +++-- lunaix-os/hal/term/term_io.c | 112 ++++---- lunaix-os/includes/hal/serial.h | 2 + lunaix-os/includes/hal/term.h | 58 ++-- lunaix-os/includes/lunaix/ds/rbuffer.h | 26 +- lunaix-os/includes/usr/lunaix/serial.h | 13 +- lunaix-os/kernel/LBuild | 1 - lunaix-os/kernel/ds/rbuffer.c | 6 +- lunaix-os/kernel/kinit.c | 5 - lunaix-os/kernel/process/sched.c | 21 +- lunaix-os/kernel/process/signal.c | 11 +- lunaix-os/usr/init/init.c | 3 + lunaix-os/usr/libc/includes/termios.h | 9 +- lunaix-os/usr/sh/sh.c | 22 ++ 28 files changed, 600 insertions(+), 255 deletions(-) rename lunaix-os/{kernel/tty/tty.c => hal/gfxa/vga/vga_rawtty.c} (87%) create mode 100644 lunaix-os/hal/term/default_ops.c create mode 100644 lunaix-os/hal/term/lcntls/lcntl.h diff --git a/lunaix-os/hal/char/LBuild b/lunaix-os/hal/char/LBuild index 88b3310..c00a394 100644 --- a/lunaix-os/hal/char/LBuild +++ b/lunaix-os/hal/char/LBuild @@ -4,5 +4,7 @@ sources([ "devnull.c", "serial.c", "devzero.c", - "lxconsole.c", -]) \ No newline at end of file +]) + +if config("vga_console"): + sources("lxconsole.c") \ No newline at end of file diff --git a/lunaix-os/hal/char/LConfig b/lunaix-os/hal/char/LConfig index 1b5ead4..157e2d7 100644 --- a/lunaix-os/hal/char/LConfig +++ b/lunaix-os/hal/char/LConfig @@ -5,3 +5,10 @@ def char_device(): """ Controlling support of character devices """ add_to_collection(hal) + + @Term + def vga_console(): + """ Enable VGA console device (text mode only) """ + + type(bool) + default(True) diff --git a/lunaix-os/hal/char/devzero.c b/lunaix-os/hal/char/devzero.c index 9013a47..c637a34 100644 --- a/lunaix-os/hal/char/devzero.c +++ b/lunaix-os/hal/char/devzero.c @@ -6,14 +6,14 @@ static int __zero_rd_pg(struct device* dev, void* buf, size_t offset) { - memset(&((u8_t*)buf)[offset], 0, PAGE_SIZE); + memset(buf, 0, PAGE_SIZE); return PAGE_SIZE; } static int __zero_rd(struct device* dev, void* buf, size_t offset, size_t len) { - memset(&((u8_t*)buf)[offset], 0, len); + memset(buf, 0, len); return len; } diff --git a/lunaix-os/hal/char/lxconsole.c b/lunaix-os/hal/char/lxconsole.c index 6387670..4922297 100644 --- a/lunaix-os/hal/char/lxconsole.c +++ b/lunaix-os/hal/char/lxconsole.c @@ -27,6 +27,7 @@ struct console { + struct capability_meta* tp_cap; struct lx_timer* flush_timer; struct fifo_buf output; struct fifo_buf input; @@ -90,8 +91,12 @@ __lxconsole_listener(struct input_device* dev) } fifo_putone(&lx_console.input, ttychr); - pwake_all(&lx_reader); + struct termport_capability* tpcap; + tpcap = get_capability(lx_console.tp_cap, typeof(*tpcap)); + term_notify_data_avaliable(tpcap); + + pwake_all(&lx_reader); done: return INPUT_EVT_NEXT; } @@ -132,6 +137,14 @@ __tty_read(struct device* dev, void* buf, size_t offset, size_t len) return count + fifo_read(&console->input, buf + count, len - count); } +int +__tty_read_async(struct device* dev, void* buf, size_t offset, size_t len) +{ + struct console* console = (struct console*)dev->underlay; + + return fifo_read(&console->input, buf, len); +} + size_t __find_next_line(size_t start) { @@ -279,13 +292,23 @@ lxconsole_spawn_ttydev(struct device_def* devdef) tty_dev->ops.write_page = __tty_write_pg; tty_dev->ops.read = __tty_read; tty_dev->ops.read_page = __tty_read_pg; + tty_dev->ops.write_async = __tty_write; + tty_dev->ops.read_async = __tty_read_async; waitq_init(&lx_reader); input_add_listener(__lxconsole_listener); + struct termport_capability* tp_cap; + + tp_cap = new_capability(TERMPORT_CAP, struct termport_capability); + term_cap_set_operations(tp_cap, termport_default_ops); + + lx_console.tp_cap = cap_meta(tp_cap); + device_grant_capability(tty_dev, lx_console.tp_cap); + register_device(tty_dev, &devdef->class, "vcon"); - term_create(tty_dev, "FB"); + term_create(tty_dev, "VCON"); return 0; } @@ -295,5 +318,4 @@ static struct device_def lxconsole_def = { .class = DEVCLASS(DEVIF_NON, DEVFN_TTY, DEV_BUILTIN), .init = lxconsole_spawn_ttydev }; -// FIXME EXPORT_DEVICE(lxconsole, &lxconsole_def, load_onboot); \ No newline at end of file diff --git a/lunaix-os/hal/char/serial.c b/lunaix-os/hal/char/serial.c index 15a9c9e..803fdb5 100644 --- a/lunaix-os/hal/char/serial.c +++ b/lunaix-os/hal/char/serial.c @@ -46,6 +46,10 @@ serial_end_recv(struct serial_dev* sdev) mark_device_done_read(sdev->dev); pwake_one(&sdev->wq_rxdone); + + struct termport_capability* tpcap; + tpcap = get_capability(sdev->tp_cap, typeof(*tpcap)); + term_notify_data_avaliable(tpcap); } void @@ -223,7 +227,18 @@ __serial_set_speed(struct device* dev, speed_t speed) struct serial_dev* sdev = serial_device(dev); lock_sdev(sdev); - sdev_execmd(sdev, SERIO_SETBRDIV, speed); + sdev_execmd(sdev, SERIO_SETBRDRATE, speed); + + unlock_sdev(sdev); +} + +static void +__serial_set_baseclk(struct device* dev, unsigned int base) +{ + struct serial_dev* sdev = serial_device(dev); + lock_sdev(sdev); + + sdev_execmd(sdev, SERIO_SETBRDBASE, base); unlock_sdev(sdev); } @@ -241,10 +256,16 @@ __serial_set_cntrl_mode(struct device* dev, tcflag_t cflag) #define RXBUF_SIZE 512 +static struct termport_cap_ops tpcap_ops = { + .set_cntrl_mode = __serial_set_cntrl_mode, + .set_clkbase = __serial_set_baseclk, + .set_speed = __serial_set_speed +}; + struct serial_dev* serial_create(struct devclass* class, char* if_ident) { - struct serial_dev* sdev = valloc(sizeof(struct serial_dev)); + struct serial_dev* sdev = vzalloc(sizeof(struct serial_dev)); struct device* dev = device_allocseq(dev_meta(serial_cat), sdev); dev->ops.read = __serial_read; dev->ops.read_page = __serial_read_page; @@ -260,8 +281,9 @@ serial_create(struct devclass* class, char* if_ident) struct termport_capability* tp_cap = new_capability(TERMPORT_CAP, struct termport_capability); - tp_cap->set_speed = __serial_set_speed; - tp_cap->set_cntrl_mode = __serial_set_cntrl_mode; + + term_cap_set_operations(tp_cap, &tpcap_ops); + sdev->tp_cap = cap_meta(tp_cap); waitq_init(&sdev->wq_rxdone); waitq_init(&sdev->wq_txdone); diff --git a/lunaix-os/hal/char/uart/16x50.h b/lunaix-os/hal/char/uart/16x50.h index addedfa..40ebe94 100644 --- a/lunaix-os/hal/char/uart/16x50.h +++ b/lunaix-os/hal/char/uart/16x50.h @@ -69,6 +69,7 @@ struct uart16550 struct llist_header local_ports; struct serial_dev* sdev; ptr_t base_addr; + unsigned int base_clk; int iv; struct @@ -118,12 +119,12 @@ void uart_free(struct uart16550*); static inline int -uart_baud_divisor(struct uart16550* uart, int div) +uart_baud_divisor(struct uart16550* uart, unsigned int div) { u32_t rlc = uart->read_reg(uart, UART_rLC); uart->write_reg(uart, UART_rLC, UART_rLC_DLAB | rlc); - u8_t ls = (div & 0xff), ms = (div & 0xff00) >> 8; + u8_t ls = (div & 0x00ff), ms = (div & 0xff00) >> 8; uart->write_reg(uart, UART_rLS, ls); uart->write_reg(uart, UART_rMS, ms); diff --git a/lunaix-os/hal/char/uart/16x50_base.c b/lunaix-os/hal/char/uart/16x50_base.c index 6e94b84..8b3930f 100644 --- a/lunaix-os/hal/char/uart/16x50_base.c +++ b/lunaix-os/hal/char/uart/16x50_base.c @@ -16,6 +16,7 @@ uart_alloc(ptr_t base_addr) uart->cntl_save.rie = 0; uart->base_addr = base_addr; + uart->base_clk = 115200U; return uart; } @@ -69,9 +70,29 @@ uart_general_exec_cmd(struct serial_dev* sdev, u32_t req, va_list args) case SERIO_RXDA: uart_clrie(uart); break; - case SERIO_SETBRDIV: - // TODO - break; + case SERIO_SETBRDRATE: + { + unsigned int div, rate; + + rate = va_arg(args, speed_t); + if (!rate) { + return EINVAL; + } + + div = uart->base_clk / va_arg(args, speed_t); + uart_baud_divisor(uart, div); + break; + } + case SERIO_SETBRDBASE: + { + int clk = va_arg(args, unsigned int); + if (!clk) { + return EINVAL; + } + + uart->base_clk = clk; + break; + } case SERIO_SETCNTRLMODE: uart_set_control_mode(uart, va_arg(args, tcflag_t)); break; diff --git a/lunaix-os/hal/gfxa/vga/LBuild b/lunaix-os/hal/gfxa/vga/LBuild index b05da8c..dc8991a 100644 --- a/lunaix-os/hal/gfxa/vga/LBuild +++ b/lunaix-os/hal/gfxa/vga/LBuild @@ -3,5 +3,6 @@ sources([ "vga_gfxm_ops.c", "vga.c", "vga_mmio_ops.c", - "vga_pci.c" + "vga_pci.c", + "vga_rawtty.c", ]) \ No newline at end of file diff --git a/lunaix-os/kernel/tty/tty.c b/lunaix-os/hal/gfxa/vga/vga_rawtty.c similarity index 87% rename from lunaix-os/kernel/tty/tty.c rename to lunaix-os/hal/gfxa/vga/vga_rawtty.c index e5a913f..21fcd82 100644 --- a/lunaix-os/kernel/tty/tty.c +++ b/lunaix-os/hal/gfxa/vga/vga_rawtty.c @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include #include @@ -113,4 +116,13 @@ tty_put_str_at(char* str, int x, int y) } str++; } -} \ No newline at end of file +} + +static void +vga_rawtty_init() +{ + // FIXME we should get rid of it at some point + tty_init((void*)ioremap(0xB8000, PAGE_SIZE)); + tty_set_theme(VGA_COLOR_WHITE, VGA_COLOR_BLACK); +} +owloysius_fetch_init(vga_rawtty_init, on_earlyboot); \ No newline at end of file diff --git a/lunaix-os/hal/term/LBuild b/lunaix-os/hal/term/LBuild index 9325917..ea5b2c3 100644 --- a/lunaix-os/hal/term/LBuild +++ b/lunaix-os/hal/term/LBuild @@ -2,6 +2,7 @@ sources([ "term.c", "console.c", "term_io.c", + "default_ops.c", "lcntls/ansi_cntl.c", "lcntls/lcntl.c", ]) \ No newline at end of file diff --git a/lunaix-os/hal/term/default_ops.c b/lunaix-os/hal/term/default_ops.c new file mode 100644 index 0000000..fea8355 --- /dev/null +++ b/lunaix-os/hal/term/default_ops.c @@ -0,0 +1,25 @@ +#include + +static void +__tpcap_default_set_speed(struct device* dev, speed_t speed) +{ + +} + +static void +__tpcap_default_set_baseclk(struct device* dev, unsigned int base) +{ + +} + +static void +__tpcap_default_set_cntrl_mode(struct device* dev, tcflag_t cflag) +{ + +} + +struct termport_cap_ops default_termport_cap_ops = { + .set_cntrl_mode = __tpcap_default_set_cntrl_mode, + .set_clkbase = __tpcap_default_set_baseclk, + .set_speed = __tpcap_default_set_speed +}; \ No newline at end of file diff --git a/lunaix-os/hal/term/lcntls/ansi_cntl.c b/lunaix-os/hal/term/lcntls/ansi_cntl.c index 778eb4b..bc66139 100644 --- a/lunaix-os/hal/term/lcntls/ansi_cntl.c +++ b/lunaix-os/hal/term/lcntls/ansi_cntl.c @@ -9,25 +9,18 @@ * @copyright Copyright (c) 2023 * */ -#include +#include "lcntl.h" #include #define CTRL_MNEMO(chr) (chr - 'A' + 1) -static inline int -__ansi_actcontrol(struct term* termdev, struct linebuffer* lbuf, char chr) +int +__ansi_actcontrol(struct lcntl_state* state, char chr) { - struct rbuffer* cooked = lbuf->next; - switch (chr) { - default: - if ((int)chr < 32) { - rbuffer_put(cooked, '^'); - return rbuffer_put(cooked, chr += 64); - } - break; + if (chr < 32 && chr != '\n') { + lcntl_put_char(state, '^'); + return lcntl_put_char(state, chr += 64); } - return rbuffer_put_nof(cooked, chr); + return lcntl_put_char(state, chr); } - -struct term_lcntl ansi_line_controller = {.process_and_put = __ansi_actcontrol}; \ No newline at end of file diff --git a/lunaix-os/hal/term/lcntls/lcntl.c b/lunaix-os/hal/term/lcntls/lcntl.c index db146e1..fb855cc 100644 --- a/lunaix-os/hal/term/lcntls/lcntl.c +++ b/lunaix-os/hal/term/lcntls/lcntl.c @@ -11,30 +11,85 @@ #include #include +#include "lcntl.h" + #include #include static inline void -raise_sig(struct term* at_term, struct linebuffer* lbuf, int sig) +init_lcntl_state(struct lcntl_state* state, + struct term* tdev, enum lcntl_dir direction) { - term_sendsig(at_term, sig); - lbuf->sflags |= LSTATE_SIGRAISE; + *state = (struct lcntl_state) { + .tdev = tdev, + ._lf = tdev->lflags, + ._cf = tdev->cflags, + .direction = direction, + .echobuf = tdev->line_out.current, + }; + + struct linebuffer* lb; + if (direction == INBOUND) { + state->_if = tdev->iflags; + lb = &tdev->line_in; + } + else { + state->_of = tdev->oflags; + lb = &tdev->line_out; + } + + state->inbuf = lb->current; + state->outbuf = lb->next; + state->active_line = lb; } -static inline int must_inline optimize("-fipa-cp-clone") -lcntl_transform_seq(struct term* tdev, struct linebuffer* lbuf, bool out) +static inline void +raise_sig(struct lcntl_state* state, int sig) { - struct rbuffer* raw = lbuf->current; - struct rbuffer* cooked = lbuf->next; - struct rbuffer* output = tdev->line_out.current; + term_sendsig(state->tdev, sig); + lcntl_raise_line_event(state, LEVT_SIGRAISE); +} - int i = 0, _if = tdev->iflags & -!out, _of = tdev->oflags & -!!out, - _lf = tdev->lflags; - int allow_more = 1, latest_eol = cooked->ptr; - char c; +static inline char +__remap_character(struct lcntl_state* state, char c) +{ + if (c == '\r') { + if ((state->_if & _ICRNL) || (state->_of & _OCRNL)) { + return '\n'; + } + } + else if (c == '\n') { + if ((state->_if & _INLCR) || (state->_of & (_ONLRET))) { + return '\r'; + } + } + else if ('a' <= c && c <= 'z') { + if ((state->_if & _IUCLC)) { + return c | 0b100000; + } else if ((state->_of & _OLCUC)) { + return c & ~0b100000; + } + } + + return c; +} - int (*lcntl_slave_put)(struct term*, struct linebuffer*, char) = - tdev->lcntl->process_and_put; +static inline void +lcntl_echo_char(struct lcntl_state* state, char c) +{ + rbuffer_put(state->echobuf, c); +} + +int +__ansi_actcontrol(struct lcntl_state* state, char chr); + +static inline int must_inline +lcntl_transform_seq(struct lcntl_state* state) +{ + struct term* tdev = state->tdev; + char c; + int i = 0; + bool full = false; #define EOL tdev->cc[_VEOL] #define EOF tdev->cc[_VEOF] @@ -44,121 +99,115 @@ lcntl_transform_seq(struct term* tdev, struct linebuffer* lbuf, bool out) #define QUIT tdev->cc[_VQUIT] #define SUSP tdev->cc[_VSUSP] -#define putc_safe(rb, chr) \ - ({ \ - if (!rbuffer_put_nof(rb, chr)) { \ - break; \ - } \ - }) + while (!lcntl_test_flag(state, LCNTLF_STOP)) + { + lcntl_unset_flag(state, LCNTLF_SPECIAL_CHAR); - if (!out) { - // Keep all cc's (except VMIN & VTIME) up to L2 cache. - for (size_t i = 0; i < _VMIN; i++) { - prefetch_rd(&tdev->cc[i], 2); + if (!rbuffer_get(state->inbuf, &c)) { + break; } - } - while (allow_more && rbuffer_get(raw, &c)) { - - if (c == '\r' && ((_if & _ICRNL) || (_of & _OCRNL))) { - c = '\n'; - } else if (c == '\n') { - if ((_if & _INLCR) || (_of & (_ONLRET))) { - c = '\r'; - } - } + c = __remap_character(state, c); if (c == '\0') { - if ((_if & _IGNBRK)) { + if ((state->_if & _IGNBRK)) { continue; } - if ((_if & _BRKINT)) { - raise_sig(tdev, lbuf, SIGINT); + if ((state->_if & _BRKINT)) { + raise_sig(state, SIGINT); break; } } - if ('a' <= c && c <= 'z') { - if ((_if & _IUCLC)) { - c = c | 0b100000; - } else if ((_of & _OLCUC)) { - c = c & ~0b100000; - } - } - - if (out) { + if (lcntl_outbound(state)) { goto do_out; } if (c == '\n') { - latest_eol = cooked->ptr + 1; - if ((_lf & _ECHONL)) { - rbuffer_put(output, c); + if (lcntl_check_echo(state, _ECHONL)) { + lcntl_echo_char(state, c); } } // For input procesing + lcntl_set_flag(state, LCNTLF_SPECIAL_CHAR); + if (c == '\n' || c == EOL) { - lbuf->sflags |= LSTATE_EOL; - } else if (c == EOF) { - lbuf->sflags |= LSTATE_EOF; - rbuffer_clear(raw); - break; - } else if (c == INTR) { - raise_sig(tdev, lbuf, SIGINT); - } else if (c == QUIT) { - raise_sig(tdev, lbuf, SIGKILL); - break; - } else if (c == SUSP) { - raise_sig(tdev, lbuf, SIGSTOP); - } else if (c == ERASE) { - if (!rbuffer_erase(cooked)) - continue; - } else if (c == KILL) { - // TODO shrink the rbuffer - } else { - goto keep; + lcntl_raise_line_event(state, LEVT_EOL); } - - if ((_lf & _ECHOE) && c == ERASE) { - rbuffer_put(output, '\x8'); - rbuffer_put(output, ' '); - rbuffer_put(output, '\x8'); + else if (c == EOF) { + lcntl_raise_line_event(state, LEVT_EOF); + lcntl_set_flag(state, LCNTLF_CLEAR_INBUF); + lcntl_set_flag(state, LCNTLF_STOP); } - if ((_lf & _ECHOK) && c == KILL) { - rbuffer_put(output, c); - rbuffer_put(output, '\n'); + else if (c == INTR) { + raise_sig(state, SIGINT); + lcntl_set_flag(state, LCNTLF_CLEAR_OUTBUF); } - - continue; - - keep: - if ((_lf & _ECHO)) { - rbuffer_put(output, c); + else if (c == QUIT) { + raise_sig(state, SIGKILL); + lcntl_set_flag(state, LCNTLF_CLEAR_OUTBUF); + lcntl_set_flag(state, LCNTLF_STOP); + } + else if (c == SUSP) { + raise_sig(state, SIGSTOP); + } + else if (c == ERASE) { + if (rbuffer_erase(state->outbuf) && + lcntl_check_echo(state, _ECHOE)) + { + lcntl_echo_char(state, '\x8'); + lcntl_echo_char(state, ' '); + lcntl_echo_char(state, '\x8'); + } + continue; + } + else if (c == KILL) { + lcntl_set_flag(state, LCNTLF_CLEAR_OUTBUF); + } + else { + lcntl_unset_flag(state, LCNTLF_SPECIAL_CHAR); } - goto put_char; + if (lcntl_check_echo(state, _ECHOK) && c == KILL) { + lcntl_echo_char(state, c); + lcntl_echo_char(state, '\n'); + } do_out: - if (c == '\n' && (_of & _ONLCR)) { - putc_safe(cooked, '\r'); + if (c == '\n' && (state->_of & _ONLCR)) { + full = !rbuffer_put_nof(state->outbuf, '\r'); } - put_char: - if (!out && (_lf & _IEXTEN) && lcntl_slave_put) { - allow_more = lcntl_slave_put(tdev, lbuf, c); - } else { - allow_more = rbuffer_put_nof(cooked, c); + if (!full) { + if (lcntl_inbound(state) && (state->_lf & _IEXTEN)) { + full = !__ansi_actcontrol(state, c); + } + else { + full = !lcntl_put_char(state, c); + } + } + + if (lcntl_test_flag(state, LCNTLF_CLEAR_INBUF)) { + rbuffer_clear(state->inbuf); + lcntl_unset_flag(state, LCNTLF_CLEAR_INBUF); + } + + if (lcntl_test_flag(state, LCNTLF_CLEAR_OUTBUF)) { + rbuffer_clear(state->outbuf); + lcntl_unset_flag(state, LCNTLF_CLEAR_OUTBUF); } + + i++; } - if (!out && !rbuffer_empty(output) && !(_lf & _NOFLSH)) { + if (state->direction != OUTBOUND && !(state->_lf & _NOFLSH)) { term_flush(tdev); } - line_flip(lbuf); + line_flip(state->active_line); return i; } @@ -166,11 +215,31 @@ lcntl_transform_seq(struct term* tdev, struct linebuffer* lbuf, bool out) int lcntl_transform_inseq(struct term* tdev) { - return lcntl_transform_seq(tdev, &tdev->line_in, false); + struct lcntl_state state; + + init_lcntl_state(&state, tdev, INBOUND); + return lcntl_transform_seq(&state); } int lcntl_transform_outseq(struct term* tdev) { - return lcntl_transform_seq(tdev, &tdev->line_out, true); + struct lcntl_state state; + + init_lcntl_state(&state, tdev, OUTBOUND); + return lcntl_transform_seq(&state); +} + +int +lcntl_put_char(struct lcntl_state* state, char c) +{ + if (lcntl_check_echo(state, _ECHO)) { + lcntl_echo_char(state, c); + } + + if (!lcntl_test_flag(state, LCNTLF_SPECIAL_CHAR)) { + return rbuffer_put_nof(state->outbuf, c); + } + + return 1; } \ No newline at end of file diff --git a/lunaix-os/hal/term/lcntls/lcntl.h b/lunaix-os/hal/term/lcntls/lcntl.h new file mode 100644 index 0000000..f867fa1 --- /dev/null +++ b/lunaix-os/hal/term/lcntls/lcntl.h @@ -0,0 +1,77 @@ +#ifndef __LUNAIX_LCNTL_H +#define __LUNAIX_LCNTL_H + +#include + +#define LCNTLF_SPECIAL_CHAR 0b000001 +#define LCNTLF_CLEAR_INBUF 0b000010 +#define LCNTLF_CLEAR_OUTBUF 0b000100 +#define LCNTLF_STOP 0b001000 + +enum lcntl_dir { + INBOUND, + OUTBOUND +}; + +struct lcntl_state { + struct term* tdev; + tcflag_t _if; // iflags + tcflag_t _of; // oflags + tcflag_t _lf; // local flags + tcflag_t _cf; // control flags + tcflag_t _sf; // state flags + enum lcntl_dir direction; + + struct linebuffer* active_line; + struct rbuffer* inbuf; + struct rbuffer* outbuf; + struct rbuffer* echobuf; +}; + +int +lcntl_put_char(struct lcntl_state* state, char c); + +static inline void +lcntl_set_flag(struct lcntl_state* state, int flags) +{ + state->_sf |= flags; +} + +static inline void +lcntl_raise_line_event(struct lcntl_state* state, int event) +{ + state->active_line->sflags |= event; +} + +static inline void +lcntl_unset_flag(struct lcntl_state* state, int flags) +{ + state->_sf &= ~flags; +} + +static inline bool +lcntl_test_flag(struct lcntl_state* state, int flags) +{ + return !!(state->_sf & flags); +} + +static inline bool +lcntl_outbound(struct lcntl_state* state) +{ + return (state->direction == OUTBOUND); +} + +static inline bool +lcntl_inbound(struct lcntl_state* state) +{ + return (state->direction == INBOUND); +} + +static inline bool +lcntl_check_echo(struct lcntl_state* state, int echo_type) +{ + return lcntl_inbound(state) && (state->_lf & echo_type); +} + + +#endif /* __LUNAIX_LCNTL_H */ diff --git a/lunaix-os/hal/term/term.c b/lunaix-os/hal/term/term.c index 3db8759..9f65d38 100644 --- a/lunaix-os/hal/term/term.c +++ b/lunaix-os/hal/term/term.c @@ -8,15 +8,11 @@ #include -#define ANSI_LCNTL 0 #define termdev(dev) ((struct term*)(dev)->underlay) -extern struct term_lcntl ansi_line_controller; -static struct term_lcntl* line_controls[] = {[ANSI_LCNTL] = - &ansi_line_controller}; #define LCNTL_TABLE_LEN (sizeof(line_controls) / sizeof(struct term_lcntl*)) -static struct devclass termdev = DEVCLASS(DEVIF_NON, DEVFN_TTY, DEV_VTERM); +static struct devclass termdev_class = DEVCLASS(DEVIF_NON, DEVFN_TTY, DEV_VTERM); struct device* sysconsole = NULL; @@ -85,7 +81,9 @@ term_exec_cmd(struct device* dev, u32_t req, va_list args) tios->c_baud = term->iospeed; } break; case TDEV_TCSETATTR: { + struct termport_cap_ops* cap_ops; struct termios* tios = va_arg(args, struct termios*); + term->iflags = tios->c_iflag; term->oflags = tios->c_oflag; term->lflags = tios->c_lflag; @@ -98,14 +96,16 @@ term_exec_cmd(struct device* dev, u32_t req, va_list args) goto done; } + cap_ops = term->tp_cap->cap_ops; + if (tios->c_baud != term->iospeed) { term->iospeed = tios->c_baud; - term->tp_cap->set_speed(term->chdev, tios->c_baud); + cap_ops->set_speed(term->chdev, tios->c_baud); } if (old_cf != tios->c_cflag) { - term->tp_cap->set_cntrl_mode(term->chdev, tios->c_cflag); + cap_ops->set_cntrl_mode(term->chdev, tios->c_cflag); } } break; default: @@ -146,16 +146,14 @@ tdev_do_read(struct device* dev, void* buf, off_t fpos, size_t len) lbuf_ref_t current = ref_current(&tdev->line_in); bool cont = true; size_t rdsz = 0; - while (cont && rdsz < len) { - if (rbuffer_empty(deref(current))) { - tdev->line_in.sflags = 0; - cont = term_read(tdev); - } + while (cont && rdsz < len) { + cont = term_read(tdev); rdsz += rbuffer_gets( deref(current), &((char*)buf)[rdsz], len - rdsz); } - + + tdev->line_in.sflags = 0; return rdsz; } @@ -167,6 +165,7 @@ load_default_setting(struct term* tdev) tdev->lflags = _ICANON | _IEXTEN | _ISIG | _ECHO | _ECHOE | _ECHONL; tdev->iflags = _ICRNL | _IGNBRK; tdev->oflags = _ONLCR | _OPOST; + tdev->iospeed = _B9600; memcpy(tdev->cc, default_cc, _NCCS * sizeof(cc_t)); } @@ -181,38 +180,46 @@ alloc_term_buffer(struct term* terminal, size_t sz_hlf) struct term* term_create(struct device* chardev, char* suffix) { - struct term* terminal = vzalloc(sizeof(struct term)); + struct term* terminal; + struct device* tdev; + struct capability_meta* termport_cap; + struct capability_meta* tios_cap; + terminal = vzalloc(sizeof(struct term)); if (!terminal) { return NULL; } - terminal->dev = device_allocseq(NULL, terminal); + tdev = device_allocseq(NULL, terminal); + terminal->dev = tdev; terminal->chdev = chardev; - terminal->dev->ops.read = tdev_do_read; - terminal->dev->ops.write = tdev_do_write; - terminal->dev->ops.exec_cmd = term_exec_cmd; + tdev->ops.read = tdev_do_read; + tdev->ops.write = tdev_do_write; + tdev->ops.exec_cmd = term_exec_cmd; - // TODO choice of lcntl can be flexible - terminal->lcntl = line_controls[ANSI_LCNTL]; + waitq_init(&terminal->line_in_event); alloc_term_buffer(terminal, 1024); if (chardev) { int cdev_var = DEV_VAR_FROM(chardev->ident.unique); - register_device(terminal->dev, &termdev, "tty%s%d", suffix, cdev_var); + register_device(tdev, &termdev_class, "tty%s%d", suffix, cdev_var); } else { - register_device(terminal->dev, &termdev, "tty%d", termdev.variant++); + register_device(tdev, &termdev_class, "tty%d", termdev_class.variant++); } - struct capability_meta* termport_cap = device_get_capability(chardev, TERMPORT_CAP); + termport_cap = device_get_capability(chardev, TERMPORT_CAP); if (termport_cap) { - terminal->tp_cap = get_capability(termport_cap, struct termport_capability); + terminal->tp_cap = + get_capability(termport_cap, struct termport_capability); + + assert(terminal->tp_cap->cap_ops); + terminal->tp_cap->term = terminal; } - struct capability_meta* term_cap = new_capability_marker(TERMIOS_CAP); - device_grant_capability(terminal->dev, term_cap); + tios_cap = new_capability_marker(TERMIOS_CAP); + device_grant_capability(tdev, tios_cap); load_default_setting(terminal); @@ -253,6 +260,6 @@ void term_sendsig(struct term* tdev, int signal) { if ((tdev->lflags & _ISIG)) { - proc_setsignal(get_process(tdev->fggrp), signal); + signal_send(-tdev->fggrp, signal); } } \ No newline at end of file diff --git a/lunaix-os/hal/term/term_io.c b/lunaix-os/hal/term/term_io.c index b1115c8..cb93c4e 100644 --- a/lunaix-os/hal/term/term_io.c +++ b/lunaix-os/hal/term/term_io.c @@ -5,77 +5,45 @@ #include -#define ONBREAK (LSTATE_EOF | LSTATE_SIGRAISE) -#define ONSTOP (LSTATE_SIGRAISE | LSTATE_EOL | LSTATE_EOF) +#define ONBREAK (LEVT_EOF | LEVT_SIGRAISE) +#define ONSTOP (LEVT_SIGRAISE | LEVT_EOL | LEVT_EOF) static int do_read_raw(struct term* tdev) { - struct device* chdev = tdev->chdev; - - struct linebuffer* line_in = &tdev->line_in; - size_t max_lb_sz = line_in->sz_hlf; - - line_flip(line_in); - - char* inbuffer = line_in->current->buffer; - size_t min = tdev->cc[_VMIN] - 1; - size_t sz = chdev->ops.read_async(chdev, inbuffer, 0, max_lb_sz); - time_t t = clock_systime(), dt = 0; - time_t expr = (tdev->cc[_VTIME] * 100) - 1; - + struct linebuffer* line_in; + lbuf_ref_t current_buf; + size_t min, sz = 0; + time_t t, expr, dt = 0; + + line_in = &tdev->line_in; + current_buf = ref_current(line_in); + + min = tdev->cc[_VMIN] - 1; + t = clock_systime(); + expr = (tdev->cc[_VTIME] * 100) - 1; + + min = MIN(min, (size_t)line_in->sz_hlf); while (sz <= min && dt <= expr) { // XXX should we held the device lock while we are waiting? sched_pass(); dt = clock_systime() - t; t += dt; - max_lb_sz -= sz; - - // TODO pass a flags to read to indicate it is non blocking ops - sz += chdev->ops.read_async(chdev, &inbuffer[sz], 0, max_lb_sz); + sz = deref(current_buf)->len; } - rbuffer_puts(line_in->next, inbuffer, sz); - line_flip(line_in); - return 0; } -static int -do_read_raw_canno(struct term* tdev) -{ - struct device* chdev = tdev->chdev; - struct linebuffer* line_in = &tdev->line_in; - struct rbuffer* current_buf = line_in->current; - int sz = chdev->ops.read(chdev, current_buf->buffer, 0, line_in->sz_hlf); - - current_buf->ptr = sz; - current_buf->len = sz; - - return sz; -} - -static int -term_read_noncano(struct term* tdev) -{ - struct device* chdev = tdev->chdev; - return do_read_raw(tdev); -} - -static int +static inline int term_read_cano(struct term* tdev) { - struct device* chdev = tdev->chdev; - struct linebuffer* line_in = &tdev->line_in; - int size = 0; + struct linebuffer* line_in; + line_in = &tdev->line_in; while (!(line_in->sflags & ONSTOP)) { - // move all hold-out content to 'next' buffer - line_flip(line_in); - - size += do_read_raw_canno(tdev); - lcntl_transform_inseq(tdev); + pwait(&tdev->line_in_event); } return 0; @@ -87,18 +55,22 @@ term_read(struct term* tdev) if ((tdev->lflags & _ICANON)) { return term_read_cano(tdev); } - return term_read_noncano(tdev); + + return do_read_raw(tdev); } int term_flush(struct term* tdev) { + struct device* chardev; struct linebuffer* line_out = &tdev->line_out; char* xmit_buf = tdev->scratch_pad; lbuf_ref_t current_ref = ref_current(line_out); - + int count = 0; + chardev = tdev->chdev; + while (!rbuffer_empty(deref(current_ref))) { if ((tdev->oflags & _OPOST)) { lcntl_transform_outseq(tdev); @@ -111,7 +83,7 @@ term_flush(struct term* tdev) off_t off = 0; int ret = 0; while (xmit_len && ret >= 0) { - ret = tdev->chdev->ops.write(tdev->chdev, &xmit_buf[off], 0, xmit_len); + ret = chardev->ops.write(chardev, &xmit_buf[off], 0, xmit_len); xmit_len -= ret; off += ret; count += ret; @@ -124,4 +96,34 @@ term_flush(struct term* tdev) } return count; +} + +void +term_notify_data_avaliable(struct termport_capability* cap) +{ + struct term* term; + struct device* term_chrdev; + struct linebuffer* line_in; + lbuf_ref_t current_ref; + char* buf; + int sz; + + term = cap->term; + term_chrdev = term->chdev; + line_in = &term->line_in; + current_ref = ref_current(line_in); + + // make room for current buf + line_flip(line_in); + buf = deref(current_ref)->buffer; + + sz = term_chrdev->ops.read_async(term_chrdev, buf, 0, line_in->sz_hlf); + rbuffer_setcontent(deref(current_ref), sz); + + if ((term->lflags & _ICANON)) { + lcntl_transform_inseq(term); + // write all processed to next, and flip back to current + } + + pwake_all(&term->line_in_event); } \ No newline at end of file diff --git a/lunaix-os/includes/hal/serial.h b/lunaix-os/includes/hal/serial.h index f65fd08..af345da 100644 --- a/lunaix-os/includes/hal/serial.h +++ b/lunaix-os/includes/hal/serial.h @@ -33,6 +33,8 @@ struct serial_dev struct rbuffer rxbuf; int wr_len; + struct capability_meta* tp_cap; + /** * @brief Write buffer to TX. The return code indicate * the transaction is either done in synced mode (TX_DONE) or will be diff --git a/lunaix-os/includes/hal/term.h b/lunaix-os/includes/hal/term.h index 4ddf74d..407707e 100644 --- a/lunaix-os/includes/hal/term.h +++ b/lunaix-os/includes/hal/term.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -16,21 +17,15 @@ struct linebuffer short sflags; short sz_hlf; }; -#define LSTATE_EOL (1) -#define LSTATE_EOF (1 << 1) -#define LSTATE_SIGRAISE (1 << 2) +#define LEVT_EOL (1) +#define LEVT_EOF (1 << 1) +#define LEVT_SIGRAISE (1 << 2) typedef struct rbuffer** lbuf_ref_t; #define ref_current(lbuf) (&(lbuf)->current) #define ref_next(lbuf) (&(lbuf)->next) #define deref(bref) (*(bref)) -struct term_lcntl -{ - struct term* term; - int (*process_and_put)(struct term*, struct linebuffer*, char); -}; - /** * @brief Communication port capability that a device is supported natively, * or able to emulate low level serial transmission behaviour specify @@ -46,25 +41,33 @@ struct term_lcntl */ #define TERMIOS_CAP 0x534f4954U -struct termport_capability -{ - CAPABILITY_META; +struct term; +struct termport_cap_ops +{ void (*set_speed)(struct device*, speed_t); + void (*set_clkbase)(struct device*, unsigned int); void (*set_cntrl_mode)(struct device*, tcflag_t); }; +struct termport_capability +{ + CAPABILITY_META; + struct termport_cap_ops* cap_ops; + struct term* term; +}; + struct term { struct device* dev; struct device* chdev; - struct term_lcntl* lcntl; struct linebuffer line_out; struct linebuffer line_in; char* scratch_pad; pid_t fggrp; struct termport_capability* tp_cap; + waitq_t line_in_event; /* -- POSIX.1-2008 compliant fields -- */ tcflag_t iflags; @@ -72,7 +75,11 @@ struct term tcflag_t lflags; tcflag_t cflags; cc_t cc[_NCCS]; + + /* -- END POSIX.1-2008 compliant fields -- */ speed_t iospeed; + speed_t clkbase; + tcflag_t tflags; // temp flags }; extern struct device* sysconsole; @@ -83,15 +90,6 @@ term_create(struct device* chardev, char* suffix); int term_bind(struct term* tdev, struct device* chdev); -int -term_push_lcntl(struct term* tdev, struct term_lcntl* lcntl); - -int -term_pop_lcntl(struct term* tdev); - -struct term_lcntl* -term_get_lcntl(u32_t lcntl_index); - static inline void line_flip(struct linebuffer* lbf) { @@ -121,4 +119,20 @@ lcntl_transform_inseq(struct term* tdev); int lcntl_transform_outseq(struct term* tdev); +static inline void +term_cap_set_operations(struct termport_capability* cap, + struct termport_cap_ops* ops) +{ + cap->cap_ops = ops; +} + +void +term_notify_data_avaliable(struct termport_capability* cap); + +#define termport_default_ops \ + ({ \ + extern struct termport_cap_ops default_termport_cap_ops;\ + &default_termport_cap_ops; \ + }) + #endif /* __LUNAIX_TERM_H */ diff --git a/lunaix-os/includes/lunaix/ds/rbuffer.h b/lunaix-os/includes/lunaix/ds/rbuffer.h index d45c9fd..ffbb99b 100644 --- a/lunaix-os/includes/lunaix/ds/rbuffer.h +++ b/lunaix-os/includes/lunaix/ds/rbuffer.h @@ -30,11 +30,22 @@ int rbuffer_puts(struct rbuffer* rb, char* c, size_t len); int -rbuffer_gets(struct rbuffer* rb, char* buf, size_t len); +rbuffer_gets_opt(struct rbuffer* rb, char* buf, size_t len, bool consumed); int rbuffer_get(struct rbuffer* rb, char* c); +static inline int +rbuffer_gets(struct rbuffer* rb, char* buf, size_t len) +{ + return rbuffer_gets_opt(rb, buf, len, true); +} + +static inline int +rbuffer_gets_no_consume(struct rbuffer* rb, char* buf, size_t len) +{ + return rbuffer_gets_opt(rb, buf, len, false); +} static inline void rbuffer_clear(struct rbuffer* rb) @@ -48,6 +59,19 @@ rbuffer_empty(struct rbuffer* rb) return rb->len == 0; } +static inline unsigned int +rbuffer_len(struct rbuffer* rb) +{ + return rb->len; +} + +static inline void +rbuffer_setcontent(struct rbuffer* rb, size_t content_len) +{ + rb->ptr = content_len; + rb->len = content_len; +} + static inline bool rbuffer_full(struct rbuffer* rb) { diff --git a/lunaix-os/includes/usr/lunaix/serial.h b/lunaix-os/includes/usr/lunaix/serial.h index c01bb15..6f73155 100644 --- a/lunaix-os/includes/usr/lunaix/serial.h +++ b/lunaix-os/includes/usr/lunaix/serial.h @@ -3,13 +3,14 @@ #include "ioctl_defs.h" -#define SERIO_RXEN IOREQ(1, 0) -#define SERIO_RXDA IOREQ(2, 0) +#define SERIO_RXEN IOREQ(1, 0) +#define SERIO_RXDA IOREQ(2, 0) -#define SERIO_TXEN IOREQ(3, 0) -#define SERIO_TXDA IOREQ(4, 0) +#define SERIO_TXEN IOREQ(3, 0) +#define SERIO_TXDA IOREQ(4, 0) -#define SERIO_SETBRDIV IOREQ(5, 0) -#define SERIO_SETCNTRLMODE IOREQ(6, 0) +#define SERIO_SETBRDRATE IOREQ(5, 0) +#define SERIO_SETCNTRLMODE IOREQ(6, 0) +#define SERIO_SETBRDBASE IOREQ(7, 0) #endif /* __LUNAIX_USERIAL_H */ diff --git a/lunaix-os/kernel/LBuild b/lunaix-os/kernel/LBuild index b52bc29..600b512 100644 --- a/lunaix-os/kernel/LBuild +++ b/lunaix-os/kernel/LBuild @@ -13,7 +13,6 @@ sources([ "kinit.c", "lunad.c", "spike.c", - "tty/tty.c", "kprint/kp_records.c", "kprint/kprintf.c", "time/clock.c", diff --git a/lunaix-os/kernel/ds/rbuffer.c b/lunaix-os/kernel/ds/rbuffer.c index eea75d6..82d1402 100644 --- a/lunaix-os/kernel/ds/rbuffer.c +++ b/lunaix-os/kernel/ds/rbuffer.c @@ -61,7 +61,7 @@ rbuffer_puts(struct rbuffer* rb, char* buf, size_t len) } int -rbuffer_gets(struct rbuffer* rb, char* buf, size_t len) +rbuffer_gets_opt(struct rbuffer* rb, char* buf, size_t len, bool consumed) { if (!len || !rb->len) return 0; @@ -79,7 +79,9 @@ rbuffer_gets(struct rbuffer* rb, char* buf, size_t len) memcpy(&buf[-nlen], &rb->buffer[ptr_start], nlen); } - rb->len -= nlen; + if (consumed) { + rb->len -= nlen; + } return nlen; } diff --git a/lunaix-os/kernel/kinit.c b/lunaix-os/kernel/kinit.c index ca28001..24550cf 100644 --- a/lunaix-os/kernel/kinit.c +++ b/lunaix-os/kernel/kinit.c @@ -43,14 +43,9 @@ kernel_bootstrap(struct boot_handoff* bhctx) /* Begin kernel bootstrapping sequence */ boot_begin(bhctx); - tty_init((void*)ioremap(0xB8000, PAGE_SIZE)); - /* Setup kernel memory layout and services */ kmem_init(bhctx); - // FIXME this goes to hal/gfxa - tty_set_theme(VGA_COLOR_WHITE, VGA_COLOR_BLACK); - boot_parse_cmdline(bhctx); /* Prepare stack trace environment */ diff --git a/lunaix-os/kernel/process/sched.c b/lunaix-os/kernel/process/sched.c index 91cf61b..82a2aa9 100644 --- a/lunaix-os/kernel/process/sched.c +++ b/lunaix-os/kernel/process/sched.c @@ -33,6 +33,8 @@ struct scheduler sched_ctx; struct cake_pile *proc_pile ,*thread_pile; +#define root_process (sched_ctx.procs[1]) + LOG_MODULE("SCHED") void @@ -256,7 +258,6 @@ __DEFINE_LXSYSCALL1(unsigned int, alarm, unsigned int, seconds) bed->alarm_time = seconds ? now + seconds : 0; - struct proc_info* root_proc = sched_ctx.procs[0]; if (llist_empty(&bed->sleepers)) { llist_append(&sched_ctx.sleepers, &bed->sleepers); } @@ -435,7 +436,7 @@ commit_process(struct proc_info* process) // every process is the child of first process (pid=1) if (!process->parent) { if (likely(!kernel_process(process))) { - process->parent = sched_ctx.procs[1]; + process->parent = root_process; } else { process->parent = process; } @@ -474,6 +475,20 @@ destory_thread(struct thread* thread) cake_release(thread_pile, thread); } +static void +orphan_children(struct proc_info* proc) +{ + struct proc_info *root; + struct proc_info *pos, *n; + + root = root_process; + + llist_for_each(pos, n, &proc->children, siblings) { + pos->parent = root; + llist_append(&root->children, &pos->siblings); + } +} + void delete_process(struct proc_info* proc) { @@ -520,6 +535,8 @@ delete_process(struct proc_info* proc) destory_thread(pos); } + orphan_children(proc); + procvm_unmount_release(mm); cake_release(proc_pile, proc); diff --git a/lunaix-os/kernel/process/signal.c b/lunaix-os/kernel/process/signal.c index e40526e..eef5c7b 100644 --- a/lunaix-os/kernel/process/signal.c +++ b/lunaix-os/kernel/process/signal.c @@ -147,8 +147,7 @@ int signal_send(pid_t pid, signum_t signum) { if (signum >= _SIG_NUM) { - syscall_result(EINVAL); - return -1; + return EINVAL; } pid_t sender_pid = __current->pid; @@ -166,8 +165,7 @@ signal_send(pid_t pid, signum_t signum) } else { // TODO: send to all process. // But I don't want to support it yet. - syscall_result(EINVAL); - return -1; + return EINVAL; } send_grp: ; @@ -179,8 +177,7 @@ send_grp: ; send_single: if (proc_terminated(proc)) { - syscall_result(EINVAL); - return -1; + return EINVAL; } proc_setsignal(proc, signum); @@ -358,7 +355,7 @@ __DEFINE_LXSYSCALL(int, pause) __DEFINE_LXSYSCALL2(int, kill, pid_t, pid, int, signum) { - return signal_send(pid, signum); + return syscall_result(signal_send(pid, signum)); } __DEFINE_LXSYSCALL1(int, sigpending, sigset_t, *sigset) diff --git a/lunaix-os/usr/init/init.c b/lunaix-os/usr/init/init.c index 2416e07..47fcf68 100644 --- a/lunaix-os/usr/init/init.c +++ b/lunaix-os/usr/init/init.c @@ -36,6 +36,9 @@ init_termios(int fd) { term.c_oflag = ONLCR | OPOST; term.c_cflag = CREAD | CLOCAL | CS8 | CPARENB; term.c_cc[VERASE] = 0x7f; + + cfsetispeed(&term, B9600); + cfsetospeed(&term, B9600); check(tcsetattr(fd, 0, &term)); diff --git a/lunaix-os/usr/libc/includes/termios.h b/lunaix-os/usr/libc/includes/termios.h index 42ad0ac..abdcf85 100644 --- a/lunaix-os/usr/libc/includes/termios.h +++ b/lunaix-os/usr/libc/includes/termios.h @@ -89,12 +89,20 @@ static inline speed_t cfgetospeed(const struct termios* termios) { return termio static inline int cfsetispeed(struct termios* termios, speed_t baud) { + if (baud > B38400) { + return -1; + } + termios->c_baud = baud; return 0; } static inline int cfsetospeed(struct termios* termios, speed_t baud) { + if (baud > B38400) { + return -1; + } + termios->c_baud = baud; return 0; } @@ -110,5 +118,4 @@ int tcgetattr(int, struct termios *); int tcsendbreak(int, int); int tcsetattr(int, int, const struct termios *); - #endif /* __LUNAIX_TERMIOS_H */ diff --git a/lunaix-os/usr/sh/sh.c b/lunaix-os/usr/sh/sh.c index 2ac473a..c2c8347 100644 --- a/lunaix-os/usr/sh/sh.c +++ b/lunaix-os/usr/sh/sh.c @@ -132,6 +132,27 @@ sh_exec(const char** argv) waitpid(p, NULL, 0); } +static char* +sanity_filter(char* buf) +{ + int off = 0, i = 0; + char c; + do { + c = buf[i]; + + if ((32 <= c && c <= 126) || !c) { + buf[i - off] = c; + } + else { + off++; + } + + i++; + } while(c); + + return buf; +} + void sh_loop() { @@ -157,6 +178,7 @@ sh_loop() } buf[sz] = '\0'; + sanity_filter(buf); // currently, this shell only support single argument if (!parse_cmdline(buf, argv)) { -- 2.27.0 From 09497c11197e622fc2657e82b4da7cb2e4b6bb22 Mon Sep 17 00:00:00 2001 From: Minep Date: Mon, 29 Jul 2024 01:10:38 +0100 Subject: [PATCH 09/16] fix: false positive when looking for room to host pmem_list some weird mem region reported by multiboot might be "just" enough to house pmem_list, but not considering the L0T alignment make it to retry instead of fold up instantly --- lunaix-os/arch/x86/mm/pmm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lunaix-os/arch/x86/mm/pmm.c b/lunaix-os/arch/x86/mm/pmm.c index aa35b86..8f350f4 100644 --- a/lunaix-os/arch/x86/mm/pmm.c +++ b/lunaix-os/arch/x86/mm/pmm.c @@ -17,8 +17,10 @@ pmm_arch_init_remap(struct pmem* memory, struct boot_handoff* bctx) size_t i = 0; struct boot_mmapent* ent; - for (; i < bctx->mem.mmap_len; i++) { - ent = &bctx->mem.mmap[i]; + +restart:; + while (i < bctx->mem.mmap_len) { + ent = &bctx->mem.mmap[i++]; if (free_memregion(ent) && ent->size > pool_size) { goto found; } @@ -38,7 +40,7 @@ found:; #endif if (aligned_pplist + pool_size > ent->start + ent->size) { - return 0; + goto restart; } // for x86_32, the upper bound of memory requirement for pplist -- 2.27.0 From baca54322c66983205edecd2ebb00d997878be50 Mon Sep 17 00:00:00 2001 From: FFreestanding <62629010+FFreestanding@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:19:43 +0800 Subject: [PATCH 10/16] userspace fun: maze game and a new device to support it Maze Game Introduction WASD: move H: get cue Q: quit N: new maze --- lunaix-os/hal/char/LBuild | 7 +- lunaix-os/hal/char/LConfig | 7 + lunaix-os/hal/char/chargame.c | 161 ++++++++ lunaix-os/includes/lunaix/tty/tty.h | 3 + lunaix-os/usr/LBuild | 3 +- lunaix-os/usr/maze.c | 585 ++++++++++++++++++++++++++++ 6 files changed, 764 insertions(+), 2 deletions(-) create mode 100644 lunaix-os/hal/char/chargame.c create mode 100644 lunaix-os/usr/maze.c diff --git a/lunaix-os/hal/char/LBuild b/lunaix-os/hal/char/LBuild index c00a394..dbc15be 100644 --- a/lunaix-os/hal/char/LBuild +++ b/lunaix-os/hal/char/LBuild @@ -7,4 +7,9 @@ sources([ ]) if config("vga_console"): - sources("lxconsole.c") \ No newline at end of file + + sources("lxconsole.c") + +if config("chargame_console"): + + sources("chargame.c") \ No newline at end of file diff --git a/lunaix-os/hal/char/LConfig b/lunaix-os/hal/char/LConfig index 157e2d7..788bfb4 100644 --- a/lunaix-os/hal/char/LConfig +++ b/lunaix-os/hal/char/LConfig @@ -12,3 +12,10 @@ def char_device(): type(bool) default(True) + + @Term + def chargame_console(): + """ Enable VGA Charactor Game console device (text mode only) """ + + type(bool) + default(False) \ No newline at end of file diff --git a/lunaix-os/hal/char/chargame.c b/lunaix-os/hal/char/chargame.c new file mode 100644 index 0000000..67b286c --- /dev/null +++ b/lunaix-os/hal/char/chargame.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct console +{ + struct lx_timer* flush_timer; + struct fifo_buf output; + struct fifo_buf input; + size_t wnd_start; + size_t lines; +}; + +typedef struct write_cmd { + int x; + int y; + char data[1024]; +}write_cmd; + +static struct console game_console; + +static waitq_t lx_reader; + +int +__game_listener(struct input_device* dev) +{ + u32_t key = dev->current_pkt.sys_code; + u32_t type = dev->current_pkt.pkt_type; + kbd_kstate_t state = key >> 16; + u8_t ttychr = key & 0xff; + key = key & 0xffff; + + if (type == PKT_RELEASE) { + goto done; + } + + if ((state & KBD_KEY_FLCTRL_HELD)) { + char cntrl = (char)(ttychr | 0x20); + if ('a' > cntrl || cntrl > 'z') { + goto done; + } + ttychr = cntrl - 'a' + 1; + } else if ((key & 0xff00) <= KEYPAD) { + ttychr = key; + } else { + goto done; + } + + fifo_putone(&game_console.input, ttychr); + pwake_all(&lx_reader); + +done: + return INPUT_EVT_NEXT; +} + +int +__game_write(struct device* dev, void* buf, size_t offset, size_t len); + +int +__game_read(struct device* dev, void* buf, size_t offset, size_t len); + +int +__game_write_pg(struct device* dev, void* buf, size_t offset) +{ + return __game_write(dev, buf, offset, 0x1000); +} + +int +__game_read_pg(struct device* dev, void* buf, size_t offset) +{ + return __game_read(dev, buf, offset, 0x1000); +} + +int +__game_write(struct device* dev, void* buf, size_t offset, size_t len) +{ + static bool first = 1; + if (first) + { + for (int i = 0; i < TTY_HEIGHT; i++) + { + tty_clear_line(i); + } + first = 0; + tty_set_cursor(TTY_WIDTH-1, TTY_HEIGHT-1); + } + + write_cmd *cmd = (write_cmd *)buf; + if (cmd->x==0x0a0d) { + cmd->x= 0x0a; + memcpy(cmd->data, cmd->data+1, TTY_WIDTH+1); + }else if (cmd->y==0x0a0d) { + cmd->y = 0xa; + memcpy(cmd->data, cmd->data+1, TTY_WIDTH+1); + } + + tty_put_str_at((char*)&(cmd->data), cmd->x, cmd->y); + + return len; +} + +int +__game_read(struct device* dev, void* buf, size_t offset, size_t len) +{ + struct console* console = (struct console*)dev->underlay; + size_t count = fifo_read(&console->input, buf, len); + + if (count > 0) { + return count; + } + + pwait(&lx_reader); + + return count + fifo_read(&console->input, buf + count, len - count); +} + +static int +chargame_init(struct device_def* devdef) +{ + struct device* tty_dev = device_allocseq(NULL, &game_console); + tty_dev->ops.write = __game_write; + tty_dev->ops.write_page = __game_write_pg; + tty_dev->ops.read = __game_read; + tty_dev->ops.read_page = __game_read_pg; + tty_dev->ops.read_async = __game_read; + + waitq_init(&lx_reader); + input_add_listener(__game_listener); + + register_device(tty_dev, &devdef->class, "game"); + + term_create(tty_dev, "G"); + + memset(&game_console, 0, sizeof(game_console)); + fifo_init(&game_console.output, valloc(8192), 8192, 0); + fifo_init(&game_console.input, valloc(4096), 4096, 0); + + game_console.flush_timer = NULL; + + return 0; +} + +static struct device_def chargame_def = { + .name = "Character Game Console", + .class = DEVCLASS(DEVIF_NON, DEVFN_TTY, DEV_BUILTIN), + .init = chargame_init, +}; +// FIXME +EXPORT_DEVICE(chargame, &chargame_def, load_onboot); \ No newline at end of file diff --git a/lunaix-os/includes/lunaix/tty/tty.h b/lunaix-os/includes/lunaix/tty/tty.h index 96fe485..b4a847a 100644 --- a/lunaix-os/includes/lunaix/tty/tty.h +++ b/lunaix-os/includes/lunaix/tty/tty.h @@ -43,4 +43,7 @@ tty_clear_line(int line_num); void tty_put_str_at(char* str, int x, int y); +void +tty_set_cursor(u8_t x, u8_t y); + #endif /* __LUNAIX_TTY_H */ diff --git a/lunaix-os/usr/LBuild b/lunaix-os/usr/LBuild index ec56dcb..a637308 100644 --- a/lunaix-os/usr/LBuild +++ b/lunaix-os/usr/LBuild @@ -4,7 +4,8 @@ sources([ "signal_demo", "cat", "stat", - "test_pthread" + "test_pthread", + "maze" ]) compile_opts([ diff --git a/lunaix-os/usr/maze.c b/lunaix-os/usr/maze.c new file mode 100644 index 0000000..748b814 --- /dev/null +++ b/lunaix-os/usr/maze.c @@ -0,0 +1,585 @@ +#include +#include +#include +#include +#include +#include +#include + +#define check(statement) \ + ({ \ + int err = 0; \ + if ((err = (statement)) < 0) { \ + syslog(2, #statement " failed: %d", err); \ + _exit(1); \ + } \ + err; \ + }) + + +#define TTY_WIDTH 80 +#define TTY_HEIGHT 25 + +#define MAZE_WIDTH TTY_WIDTH-1 +#define MAZE_HEIGHT TTY_HEIGHT + +#define BUFFER_SIZE 1024 + +#define WALL '#' +#define ROAD ' ' +#define PLAYER 'P' +#define FLAG 'F' +#define CUE '.' + +#define VISITED 1 + +#define UP 0 +#define RIGHT 1 +#define DOWN 2 +#define LEFT 3 + +int rand_fd; +int fd; + +void get_rand(unsigned int *r, unsigned int max) { + if (max==0) + { + *r = 0; + return; + } + read(rand_fd, r, 4); + *r = (*r)%max; +} + +typedef struct player_t { + int x; + int y; +}player_t; + +typedef struct write_cmd { + int x; + int y; + char data[MAZE_WIDTH+1]; +}write_cmd; + +typedef struct node_t { + int x; + int y; +}node_t; + +typedef struct stack_t { + node_t node[1000]; + unsigned int length; +}road_list_t, cue_list_t; + +road_list_t road_list; +cue_list_t cue_list; + +void init_list() { + for (size_t i = 0; i < 1000; i++) + { + road_list.node[i].x = 0; + road_list.node[i].y = 0; + } + road_list.length = 0; + + for (size_t i = 0; i < 1000; i++) + { + cue_list.node[i].x = 0; + cue_list.node[i].y = 0; + } + cue_list.length = 0; +} + +int insert_road_list(node_t n) { + if (road_list.length>=1000) + { + return 0; + } + + unsigned int r = 0; + get_rand(&r, road_list.length); + for (size_t i = road_list.length; i > r; i--) + { + road_list.node[i] = road_list.node[i-1]; + } + road_list.node[r] = n; + road_list.length+=1; + return 1; +} + +int push_road_list(node_t node) { + if (road_list.length>=1000) + { + return 0; + } + road_list.node[road_list.length] = node; + road_list.length+=1; + return 1; +} + +node_t pop_road_list() { + node_t n={.x = 0, .y = 0}; + if (road_list.length==0) + { + return n; + } + n = road_list.node[road_list.length-1]; + road_list.node[road_list.length-1].x = 0; + road_list.node[road_list.length-1].y = 0; + road_list.length-=1; + return n; +} + +int push_cue_list(node_t node) { + if (cue_list.length>=1000) + { + return 0; + } + cue_list.node[cue_list.length] = node; + cue_list.length+=1; + return 1; +} + +node_t pop_cue_list() { + node_t n={.x = 0, .y = 0}; + if (cue_list.length==0) + { + return n; + } + n = cue_list.node[cue_list.length-1]; + cue_list.node[cue_list.length-1].x = 0; + cue_list.node[cue_list.length-1].y = 0; + cue_list.length-=1; + return n; +} + +int iswall(node_t n) { + if (n.x<0 || n.y<0) + { + return 0; + } + + return n.x%2==0 || n.y%2==0; +} + +// is road position +// no matter it is marked as '-' +int isroad(node_t n) { + if (n.x<=0 || n.y<=0 || n.x>=MAZE_WIDTH-1 || n.y>=MAZE_HEIGHT-1) + { + return 0; + } + + return !iswall(n); +} + + +void generate_rand_road_node(node_t *n) +{ + get_rand((unsigned int *)&n->x, MAZE_WIDTH); + get_rand((unsigned int *)&n->y, MAZE_HEIGHT); + if (isroad(*n)) + { + return; + } + + if (n->x%2==0) + { + if (n->x<=(MAZE_WIDTH/2)) + { + n->x+=1; + } + else + { + n->x-=1; + } + } + + if (n->y%2==0) + { + if (n->y<=(MAZE_HEIGHT/2)) + { + n->y+=1; + } + else + { + n->y-=1; + } + } +} + +char maze[MAZE_HEIGHT][MAZE_WIDTH]; +char visited[MAZE_HEIGHT][MAZE_WIDTH]; + +void pick_near_road_to_list(node_t current_node) { + node_t picked_node; + + //UP + picked_node.x = current_node.x; + picked_node.y = current_node.y-2; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]!=ROAD) + { + insert_road_list(picked_node); + } + + //RIGHT + picked_node.x = current_node.x+2; + picked_node.y = current_node.y; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]!=ROAD) + { + insert_road_list(picked_node); + } + + //DOWN + picked_node.x = current_node.x; + picked_node.y = current_node.y+2; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]!=ROAD) + { + insert_road_list(picked_node); + } + + //LEFT + picked_node.x = current_node.x-2; + picked_node.y = current_node.y; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]!=ROAD) + { + insert_road_list(picked_node); + } +} + +node_t pick_near_road(node_t current_node) { + node_t picked_node; + + //UP + picked_node.x = current_node.x; + picked_node.y = current_node.y-2; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]==ROAD) + { + return picked_node; + } + + //LEFT + picked_node.x = current_node.x-2; + picked_node.y = current_node.y; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]==ROAD) + { + return picked_node; + } + + //DOWN + picked_node.x = current_node.x; + picked_node.y = current_node.y+2; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]==ROAD) + { + return picked_node; + } + + //RIGHT + picked_node.x = current_node.x+2; + picked_node.y = current_node.y; + if (isroad(picked_node) && maze[picked_node.y][picked_node.x]==ROAD) + { + return picked_node; + } + + return picked_node; +} + + +void break_wall(node_t n1, node_t n2) +{ + if (n1.x==n2.x) + { + maze[(n1.y+n2.y+1)/2][n1.x]=ROAD; + } + else if(n1.y==n2.y) + { + maze[n1.y][(n1.x+n2.x+1)/2]=ROAD; + } +} + +void print_maze(){ + write_cmd wcmd; + wcmd.x = 0; + wcmd.data[MAZE_WIDTH] = '\0'; + + for (size_t j = 0; j < MAZE_HEIGHT; j++) + { + for (size_t i = 0; i < MAZE_WIDTH; i++) + { + wcmd.data[i] = maze[j][i]; + } + wcmd.y = j; + write(fd, &wcmd, sizeof(write_cmd)); + } +} + +void refresh_line(int y){ + write_cmd wcmd; + wcmd.x = 0; + wcmd.y = y; + wcmd.data[MAZE_WIDTH] = '\0'; + + for (size_t i = 0; i < MAZE_WIDTH; i++) + { + wcmd.data[i] = maze[y][i]; + } + + write(fd, &wcmd, sizeof(write_cmd)); +} + +// Prim algorithm +void generate_maze() +{ + node_t start_node; + + for (size_t i = 0; i < MAZE_HEIGHT; i++) + { + for (size_t j = 0; j < MAZE_WIDTH; j++) + { + maze[i][j] = WALL; + } + } + + rand_fd = open("/dev/rand", O_RDONLY); + + generate_rand_road_node(&start_node); + + maze[start_node.y][start_node.x] = ROAD; + + pick_near_road_to_list(start_node); + + while (road_list.length>0) + { + node_t n = pop_road_list(); + maze[n.y][n.x] = ROAD; + node_t n2 = pick_near_road(n); + maze[n2.y][n2.x] = ROAD; + break_wall(n, n2); + pick_near_road_to_list(n); + print_maze(); + } + + maze[1][1] = PLAYER; + maze[MAZE_HEIGHT-2][MAZE_WIDTH-2] = FLAG; + refresh_line(1); + refresh_line(MAZE_HEIGHT-2); +} + + +int +set_termios(int fd) { + struct termios term; + + check(tcgetattr(fd, &term)); + + term.c_lflag = IEXTEN | ISIG | ECHO | ECHOE | ECHONL; + term.c_cc[VERASE] = 0x7f; + + check(tcsetattr(fd, 0, &term)); + + return 0; +} + +int +set_termios_end(int fd) { + struct termios term; + + check(tcgetattr(fd, &term)); + + term.c_lflag = ICANON | IEXTEN | ISIG | ECHO | ECHOE | ECHONL; + term.c_cc[VERASE] = 0x7f; + + check(tcsetattr(fd, 0, &term)); + + return 0; +} + +node_t player; + +void up(){ + if (maze[player.y-1][player.x]!='#') + { + player.y-=1; + maze[player.y][player.x] = 'P'; + maze[player.y+1][player.x] = ' '; + } + refresh_line(player.y); + refresh_line(player.y+1); +} + +void right(){ + if (maze[player.y][player.x+1]!='#') + { + player.x+=1; + maze[player.y][player.x] = 'P'; + maze[player.y][player.x-1] = ' '; + } + refresh_line(player.y); +} + +void down(){ + if (maze[player.y+1][player.x]!='#') + { + player.y+=1; + maze[player.y][player.x] = 'P'; + maze[player.y-1][player.x] = ' '; + } + refresh_line(player.y); + refresh_line(player.y-1); +} + +void left(){ + if (maze[player.y][player.x-1]!='#') + { + player.x-=1; + maze[player.y][player.x] = 'P'; + maze[player.y][player.x+1] = ' '; + } + refresh_line(player.y); +} + +void visit_node(node_t current_node){ + node_t picked_node; + + //UP + picked_node.x = current_node.x; + picked_node.y = current_node.y-1; + if (maze[picked_node.y][picked_node.x]!=PLAYER && maze[picked_node.y][picked_node.x]!=WALL && visited[picked_node.y][picked_node.x]!=VISITED) + { + push_road_list(picked_node); + } + + //RIGHT + picked_node.x = current_node.x+1; + picked_node.y = current_node.y; + if (maze[picked_node.y][picked_node.x]!=PLAYER && maze[picked_node.y][picked_node.x]!=WALL && visited[picked_node.y][picked_node.x]!=VISITED) + { + push_road_list(picked_node); + } + + //DOWN + picked_node.x = current_node.x; + picked_node.y = current_node.y+1; + if (maze[picked_node.y][picked_node.x]!=PLAYER && maze[picked_node.y][picked_node.x]!=WALL && visited[picked_node.y][picked_node.x]!=VISITED) + { + push_road_list(picked_node); + } + + //LEFT + picked_node.x = current_node.x-1; + picked_node.y = current_node.y; + if (maze[picked_node.y][picked_node.x]!=PLAYER && maze[picked_node.y][picked_node.x]!=WALL && visited[picked_node.y][picked_node.x]!=VISITED) + { + push_road_list(picked_node); + } +} + +int is_near_node(node_t n1, node_t n2){ + if (n1.x==n2.x && (n1.y-n2.y==1 || n1.y-n2.y==-1)) + { + return 1; + } + else if (n1.y==n2.y && (n1.x-n2.x==1 || n1.x-n2.x==-1)) + { + return 1; + } + else if (n1.x==n2.x && n1.y==n2.y) + { + return 1; + } + else + { + return 0; + } +} + +void dfs(){ + for (size_t i = 0; i < MAZE_HEIGHT; i++) + { + for (size_t j = 0; j < MAZE_WIDTH; j++) + { + visited[i][j] = 0; + } + } + + node_t node = player; + node_t cue_node = player; + push_road_list(node); + push_cue_list(cue_node); + while (road_list.length>0 && maze[node.y][node.x]!=FLAG) + { + node = pop_road_list(); + cue_node = pop_cue_list(); + while (!is_near_node(node, cue_node) && cue_node.x!=0) + { + if (maze[cue_node.y][cue_node.x] != PLAYER) + { + maze[cue_node.y][cue_node.x] = ROAD; + } + refresh_line(cue_node.y); + cue_node = pop_cue_list(); + } + if (cue_node.x!=0) + { + push_cue_list(cue_node); + } + + if (maze[node.y][node.x]!=FLAG && visited[node.y][node.x]!=VISITED) + { + if (maze[node.y][node.x]!=PLAYER) + { + maze[node.y][node.x] = CUE; + } + push_cue_list(node); + visited[node.y][node.x] = VISITED; + visit_node(node); + } + + refresh_line(node.y); + } +} + +int +main(int argc, char* argv[]) +{ + fd = open("/dev/ttyG0", FO_RDWR); + check(set_termios(fd)); + init_list(); + generate_maze(); + + char action = '\0'; + player.x=1; + player.y=1; + while (action!='Q') + { + read(fd, &action, 1); + switch (action) + { + case 'W': + up(); + break; + case 'D': + right(); + break; + case 'S': + down(); + break; + case 'A': + left(); + break; + case 'H': + dfs(); + break; + case 'N': + generate_maze(); + break; + default: + break; + } + } + + check(set_termios_end(fd)); + return 0; +} \ No newline at end of file -- 2.27.0 From 270869139db617e29a35bb9ded41087bb702f9ac Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Sat, 10 Aug 2024 14:52:28 +0100 Subject: [PATCH 11/16] Second Extended Filesystem (ext2) and other improvements (#33) * Add a wrapper layer to vfs interface with intension to serve an unified API for file-system driver implementation and thus makes the flow clear. * Localise iso9660's header file * Make qemu launch cmd out of makefile to enable better flexibility * remove an out-dated FIXME annotation * Add ext2 protocol definitions and v_superblock construction * Sparse Caching: cache framework for caching spatial data unit (#34) * Add framework for sparse caching facility Added a spinlock as a future-proof placeholder * * Add bcache_zone to allow more fine gran control over lru zone used when creating a bcache * Add lock on entire bcache instance to avoid concurrent modification on internal data structure * Add missing free function for lru zone, introduce async freeing when a zone is too busy to be freed onsite * Add interface for destroying spinlock when a guarded object is about to be freed, thus avoid locking-after-free. * Refactor inode pcache to use bcache for better centralisation * lru: add proper synchronisation ensurance to lru_zone * lru: add twimap integration to all lru_zones * lru: add extra statistical metrics * change naming to spinlock relatives for better clarity * twimap: go_next now called before read, to capture corner case of empty list * clean-up and compliation fixes * * blk_buf: buffering mechanism for blkio requests and their payloads * Replace locking on each bcache node with ref counter, thus avoid creating weired locking pattern and potentially dead lock. * add documentation on the stages that owloysius will recognise * housekeeping: remove not-needed file * * ext2: parsing superblock and block descriptor table * ext2: locating and reading inode * fsapi: add new helper method for initializing inode * fsapi: add support to block buffering, allowing read/write of file system block to be efficient. * * ext2: add support to read the inode data block and indirect link following. * ext2: introduce btlb to accelerate indirect link following. * blkbuf: add `refonce` to increament the internal ref counter for better cache management * housekeeping: make the inline function at hash.h static * housekeeping: rename fsapi_{get|put}blk to fsblock_{take|put} * * Change the directory iteration pattern in vfs This allow us to add support of lseek functionality to directory. i.e. seek to i-th directory entry, similar to seekdir(2) * * ext2: directory iteration and seeking. * ext2: add a generic iterator for ext2 objects * minor refactoring on some code snippet to reduce verbosity * * ext2: read and read_page support on data blocks * * proper error handling and reporting on blkbuf and ext2 * * vfs: fix an issue that async freeing of inode and dnode cachings could potentially result undefined access to an already freed v_superblock object. By tracking the no. of references of vsb and free it when and only when nobody is reference it. * vfs: remove the auto-handling of . and .. dir entry from the readdir interface. Thus makes it a file system driver's responsibility * ext2: fix the incorrect index usage when addressing an ext2 directory entry. * ext2: add strneq for bounded string comparision. * minor refactoring and tidying. * * make things compile * iso9660: fix the issue when selecting name for the 00/01 dir ident * inode::itype bit-field redefinition & bug fixes * rework the itype bit-fields for better clarity. * fix couple of bugs in ls that does not check errno of readdir syscall * add a new directive '?' to debug_sh (sh) to check exit status of previous invocation * ext2: properly functioned the directory listing * fix strncpy behavior to be smarter * corret the inode number offset * provide v_mount struct before mounting stuff, which makes more sense * fix issue of btrie tree retrive incorrect leaf when order is a odd number * fix the itype conversion from ext2 to lunaix * ext2: basic file read functioning * fix incorrect return size calculation in ext2::read and pcache::read * make ext2db_iter terminate when datablock reaching the size of file. * fix that cat does not check on negative return value of read upon read(2) error. * ext2: inode and data block allocation * ext2: add support to allocate inode and data block * ext2: add support to de-allocate inode and data block * ext2: managing the ext2_gdesc with bcache * ext2: rename some functions for conciseness * usr/sh: QoL improvement by faking the PATH env var * ext2: inode data blocks allocation * ext2: unifying the indirect block walking * ext2: data block allocation for inode * ext2: mkdir, rmdir, generic dir_ent insert/delete * ext2: add mkdir and rmdir * ext2: add inode creation * ext2: add ability to do file write * vfs: add the missing destruct callback for dnode * fix: add a null check on bbuf_t related operations * ext2: symlink, mknod, link/unlink, sync and fixes * ext2: add support to symlink get/set * ext2: add support to inode creation * ext2: add link and unlink * ext2: directory renaming * ext2: fsync and full fs syncing * ext2: add proper un-mounting * ext2: inode metadata update & sync * vfs : some refactoring * vfs : add O_TRUNC and fix O_APPEND behaviour * vfs : update the fsize after write * twimap: fix issue that it failed to read mapped content * update LBuild with new files, fix some issue with LConfig * fix issue where LConfigProvider::has_config does not capture the case of boolean typed config * fix issue where CHeaderConfigProvider:: try to appened non-str typed value to the header file generation flow * various fixes for merging the master branch * fix: the incorrect use of clz on unsigned long data type * ref: add 64bits variances of clzl, llog2. provide handy definition of most significant bit index of unsigned long and int. * ref: uncaptialize the ILOG2 * fix: the kernel stack creation and copy algorithm, make it less hacky * fix: assertion on thread state in pwake is too strong. if a thread is already awakened by other event or killed, just remove it from wait list * fix: change the tag calculation in ext2::bTLB, so it can contain validity bit * fix: a block buffer should ref again when inserting into bTLB * fix: mixed use of size_t and unsigned int, this is not working on 64bits platform * fix: boot disk prober automatically use last device it scaned when no blkdev can be identified as boot disk * feat: qemu.py (autoqemu) will now skip the block device if the image is not found, so it provides a less disastrous fallback behavior * fix: always vzalloc for the bltb buffer. * fix logic error in ext2 file write & inode creation flow * rename the fsblock_take to fsblock_get * add the fsblock_take for doing refonce function * add a test program that write random string into file * fix issues in dirent creation and file write * fix: incorrect calaculation of rec_len * fix: confusion on inode number (1 based) and inode index (0 based) * fix: racing when one process creating file while other try to read that directory structure. * fix: pcache write must not enforce the fpos to be page aligned all the time, otherwise, page cache paddings will be written. * fix: autoqemu: smp and memory parameter not set. * fix: rng generator failed to populate whole buffer other than the beginning few bytes * fix: failsafe unwrapper should follow x86_64 convention. * fix: blkbuf: define null bbuf as not initialized. * modified ext2 now passed the e2fsck validation * fix: sync the changes to superblock. * fix: correct the rec_len when the dirent is last of this data block * fix: (ahci) add sanity check on DMA buffer size * fix: i_blocks calculation should include the intermediate blocks allocated for building indrections. * ref: some minor changes to the fsapi_vsb_* * feat: add a printk emulation for quick log printing without the need of explicit specify the LOG_MODULE * fix: skip the sync if a blkio_req is pending. previous version only skip it after the access flag is changed, which may lead to undesired behaviour on some cases. * fix issue in ext2_mkdir * fix: break the pwait into two parts: add to wait list and the actual wait start. Thus to resolve the racing problem in the blkio by ensure that the active thread will always in the blkio_req's waiting list even when the completion interrupt arrive before we invoke the actual waiting. * fix: dirty page cache should be flushed before closing the file. * fix: make most system calls preemptive in kernel space, to increase the responsive time and avoid interrupt squashing for i/o intensive device. * feat: add mkdir program for testing * protect the blkio and blkbuf with mutex lock. * ref: separate the non-reentrant and reentrant (nested) mutex locking/unlocking. * Improvements patch: Kernel preemptions (#42) * added mechanism for checking any stalling in kernel space * ref: wrap the syscall dispatching into a proper C function. * ref: separate the sched_pass into two category: yield and preempt * ref: add a redirection checking stage prior to the context restore, which enable us to extend in the future (if needed) * feat: add the to more pwait variant, for indicate that the event waiting should be opted in stall checking * feat: add thread statistics on kernel/user transition * fix: outdated kernel stack reclaiming corrupt the other threads * feat: add an extra config term to detect dead loop in kernel space. * minor adjustment on formating * add comments on explaining switch_signposting * file deletion and add file resizing * fix: tested and fixed the file deletion. * feat: inode resizing for dynamically deallocate data block that is not used after shrinking. * fix: incorrect thread reference when checking stalled task * feat: test program for file deletion/unlinking. * update the inode attrs when setting symlink * ref: change some comments * fix: add FSTYPE_PSEUDO to distinguish filesystem that is capable of device-less mounting * feat: a wrapper for filesystem registration * ref: refactor currently avaliable file system registration code with aforementioned wrapper * update readme * * rewrite the ext2_rename, to make it right * feat: add support to FILE_TYPE and LARGE_FILE feature. * fix: feature check on mounting. * fix: memory leakage on ext2_dnode. * fix: double free on ext2_dnode when ext2_mkdir fail. * fix: properly release the ext2_dnode after ext2dr_remove. * ref: code clean up on ext2. --- README.md | 33 +- lunaix-os/LConfig | 37 +- lunaix-os/arch/x86/exceptions/interrupt32.S | 4 +- lunaix-os/arch/x86/exceptions/interrupt64.S | 4 +- lunaix-os/arch/x86/hal/rngx86.c | 12 +- lunaix-os/arch/x86/includes/sys/failsafe.h | 7 +- lunaix-os/arch/x86/includes/sys/muldiv64.h | 2 +- lunaix-os/arch/x86/syscall32.S | 5 +- lunaix-os/arch/x86/syscall64.S | 14 +- lunaix-os/arch/x86/trace.c | 7 +- lunaix-os/hal/ahci/ahci.c | 2 + lunaix-os/hal/ahci/io_event.c | 4 +- lunaix-os/hal/term/term_io.c | 4 +- lunaix-os/includes/klibc/hash.h | 2 +- lunaix-os/includes/klibc/string.h | 3 + lunaix-os/includes/lunaix/bcache.h | 179 +++ lunaix-os/includes/lunaix/blkbuf.h | 110 ++ lunaix-os/includes/lunaix/blkio.h | 159 ++- lunaix-os/includes/lunaix/block.h | 6 + lunaix-os/includes/lunaix/compiler.h | 16 +- lunaix-os/includes/lunaix/ds/btrie.h | 12 +- lunaix-os/includes/lunaix/ds/hstr.h | 4 + lunaix-os/includes/lunaix/ds/lru.h | 28 +- lunaix-os/includes/lunaix/ds/mutex.h | 6 + lunaix-os/includes/lunaix/ds/spinlock.h | 51 + lunaix-os/includes/lunaix/ds/waitq.h | 12 + lunaix-os/includes/lunaix/fs.h | 185 ++- lunaix-os/includes/lunaix/fs/api.h | 301 +++++ lunaix-os/includes/lunaix/fs/ramfs.h | 4 +- lunaix-os/includes/lunaix/fs/taskfs.h | 2 +- lunaix-os/includes/lunaix/kpreempt.h | 56 + lunaix-os/includes/lunaix/mm/page.h | 18 + lunaix-os/includes/lunaix/mm/pmm.h | 2 +- lunaix-os/includes/lunaix/owloysius.h | 16 + lunaix-os/includes/lunaix/process.h | 80 +- lunaix-os/includes/lunaix/sched.h | 3 - lunaix-os/includes/lunaix/spike.h | 25 +- lunaix-os/includes/lunaix/switch.h | 60 + lunaix-os/includes/lunaix/syslog.h | 5 + lunaix-os/includes/lunaix/time.h | 18 + lunaix-os/includes/lunaix/types.h | 5 +- lunaix-os/includes/usr/lunaix/fcntl_defs.h | 20 +- lunaix-os/includes/usr/lunaix/fstypes.h | 26 +- lunaix-os/includes/usr/lunaix/status.h | 1 + lunaix-os/kernel/LBuild | 5 +- lunaix-os/kernel/LConfig | 3 +- lunaix-os/kernel/bcache.c | 215 ++++ lunaix-os/kernel/block/LBuild | 3 +- lunaix-os/kernel/block/blkbuf.c | 245 ++++ lunaix-os/kernel/block/blkio.c | 85 +- lunaix-os/kernel/block/block.c | 16 +- lunaix-os/kernel/device/devfs.c | 25 +- lunaix-os/kernel/device/poll.c | 3 +- lunaix-os/kernel/ds/LBuild | 1 - lunaix-os/kernel/ds/btrie.c | 27 +- lunaix-os/kernel/ds/lru.c | 74 -- lunaix-os/kernel/ds/mutex.c | 51 +- lunaix-os/kernel/ds/semaphore.c | 4 +- lunaix-os/kernel/ds/waitq.c | 84 +- lunaix-os/kernel/fs/LBuild | 10 +- lunaix-os/kernel/fs/LConfig | 21 + lunaix-os/kernel/fs/defaults.c | 18 +- lunaix-os/kernel/fs/ext2/LBuild | 8 + lunaix-os/kernel/fs/ext2/alloc.c | 99 ++ lunaix-os/kernel/fs/ext2/dir.c | 691 +++++++++++ lunaix-os/kernel/fs/ext2/ext2.h | 707 +++++++++++ lunaix-os/kernel/fs/ext2/file.c | 313 +++++ lunaix-os/kernel/fs/ext2/group.c | 301 +++++ lunaix-os/kernel/fs/ext2/inode.c | 1088 +++++++++++++++++ lunaix-os/kernel/fs/ext2/mount.c | 230 ++++ lunaix-os/kernel/fs/iso9660/directory.c | 57 +- lunaix-os/kernel/fs/iso9660/file.c | 6 +- lunaix-os/kernel/fs/iso9660/inode.c | 2 +- .../lunaix/fs => kernel/fs/iso9660}/iso9660.h | 2 +- lunaix-os/kernel/fs/iso9660/mount.c | 36 +- lunaix-os/kernel/fs/iso9660/rockridge.c | 2 +- lunaix-os/kernel/fs/iso9660/utils.c | 2 +- lunaix-os/kernel/fs/mount.c | 71 +- lunaix-os/kernel/fs/path_walk.c | 16 +- lunaix-os/kernel/fs/pcache.c | 272 +++-- lunaix-os/kernel/fs/probe_boot.c | 7 +- lunaix-os/kernel/fs/ramfs/ramfs.c | 49 +- lunaix-os/kernel/fs/twifs/twifs.c | 38 +- lunaix-os/kernel/fs/twimap.c | 12 +- lunaix-os/kernel/fs/vfs.c | 520 +++++--- lunaix-os/kernel/kprint/kprintf.c | 9 + lunaix-os/kernel/lrud.c | 216 ++++ lunaix-os/kernel/lunad.c | 6 +- lunaix-os/kernel/mm/cake_export.c | 4 +- lunaix-os/kernel/mm/valloc.c | 34 +- lunaix-os/kernel/process/LBuild | 4 +- lunaix-os/kernel/process/fork.c | 10 +- lunaix-os/kernel/process/preemption.c | 82 ++ lunaix-os/kernel/process/sched.c | 30 +- lunaix-os/kernel/process/signal.c | 29 +- lunaix-os/kernel/process/switch.c | 37 + lunaix-os/kernel/process/taskfs.c | 26 +- lunaix-os/kernel/process/thread.c | 69 +- lunaix-os/kernel/syscall.c | 19 + lunaix-os/kernel/time/timer.c | 1 + lunaix-os/libs/klibc/string/strcmp.c | 14 + lunaix-os/libs/klibc/string/strcpy.c | 23 +- lunaix-os/live_debug.sh | 1 + lunaix-os/makefile | 5 +- lunaix-os/makeinc/toolchain.mkinc | 6 +- .../build-tools/integration/lbuild_bridge.py | 4 +- lunaix-os/scripts/qemu.py | 15 +- lunaix-os/scripts/qemus/qemu_x86_dev.json | 6 + lunaix-os/usr/LBuild | 5 +- lunaix-os/usr/cat.c | 6 +- lunaix-os/usr/file_test.c | 48 + lunaix-os/usr/init/init.c | 12 +- lunaix-os/usr/libc/src/readdir.c | 4 + lunaix-os/usr/ls.c | 7 +- lunaix-os/usr/mkdir.c | 21 + lunaix-os/usr/rm.c | 44 + lunaix-os/usr/sh/sh.c | 17 +- lunaix-os/usr/stat.c | 19 +- 118 files changed, 7006 insertions(+), 796 deletions(-) create mode 100644 lunaix-os/includes/lunaix/bcache.h create mode 100644 lunaix-os/includes/lunaix/blkbuf.h create mode 100644 lunaix-os/includes/lunaix/ds/spinlock.h create mode 100644 lunaix-os/includes/lunaix/fs/api.h create mode 100644 lunaix-os/includes/lunaix/switch.h create mode 100644 lunaix-os/kernel/bcache.c create mode 100644 lunaix-os/kernel/block/blkbuf.c delete mode 100644 lunaix-os/kernel/ds/lru.c create mode 100644 lunaix-os/kernel/fs/LConfig create mode 100644 lunaix-os/kernel/fs/ext2/LBuild create mode 100644 lunaix-os/kernel/fs/ext2/alloc.c create mode 100644 lunaix-os/kernel/fs/ext2/dir.c create mode 100644 lunaix-os/kernel/fs/ext2/ext2.h create mode 100644 lunaix-os/kernel/fs/ext2/file.c create mode 100644 lunaix-os/kernel/fs/ext2/group.c create mode 100644 lunaix-os/kernel/fs/ext2/inode.c create mode 100644 lunaix-os/kernel/fs/ext2/mount.c rename lunaix-os/{includes/lunaix/fs => kernel/fs/iso9660}/iso9660.h (99%) create mode 100644 lunaix-os/kernel/lrud.c create mode 100644 lunaix-os/kernel/process/preemption.c create mode 100644 lunaix-os/kernel/process/switch.c create mode 100644 lunaix-os/kernel/syscall.c create mode 100644 lunaix-os/usr/file_test.c create mode 100644 lunaix-os/usr/mkdir.c create mode 100644 lunaix-os/usr/rm.c diff --git a/README.md b/README.md index aaf28e3..86d0443 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ # The LunaixOS Project -LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有浓重个人风格的操作系统。开发过程以视频教程形式在Bilibili呈现:[《从零开始自制操作系统系列》](https://space.bilibili.com/12995787/channel/collectiondetail?sid=196337)。 +LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有浓重个人风格的操作系统,由 Lunaix 内核驱动。开发过程以视频教程形式在Bilibili呈现:[《从零开始自制操作系统系列》](https://space.bilibili.com/12995787/channel/collectiondetail?sid=196337)。 ## 1. 一些实用资源 -如果有意研读LunaixOS的内核代码和其中的设计,或欲开始属于自己的OS开发之道,以下资料可能会对此有用。 +如果有意研读 Lunaix 内核代码和其中的设计,或欲开始属于自己的OS开发之道,以下资料可能会对此有用。 -+ [最新的LunaixOS源代码分析教程](docs/tutorial/0-教程介绍和环境搭建.md) ++ [LunaixOS源代码分析教程](docs/tutorial/0-教程介绍和环境搭建.md) + 内核虚拟内存的详细布局 + [x86_32](docs/img/lunaix-mem-map/lunaix-mem-x86_32.png) + [x86_64](docs/img/lunaix-mem-map/lunaix-mem-x86_64.png) @@ -47,7 +47,8 @@ Lunaix全部特性一览: + 信号机制 + PCI 3.0 + PCIe 1.1 (WIP) -+ 块设备驱动 ++ 块设备IO与驱动 + + 块IO通用缓存池 + Serial ATA AHCI + ATA设备 + ATAPI封装的SCSI协议 @@ -59,6 +60,9 @@ Lunaix全部特性一览: + ISO9660 + ECMA-119 + IEEE P1282(Rock Ridge拓展) + + ext2 + + Revision 0 + + Revision 1 (额外特性不支持) + 远程GDB串口调试 (COM1@9600Bd) + 用户程序加载与执行 + 通用设备抽象层 @@ -77,7 +81,10 @@ Lunaix全部特性一览: + 参考:`lunaix-os/hal/term` + 线程模型 + 用户线程支持(pthread系列) - + 内核线程支持(抢占式内核设计) + + 内核线程支持 ++ 抢占式内核设计 + + 内核态上下文切换 + + 内核态异常挂起/死锁自动检测机制 ## 3. 目录结构 @@ -100,7 +107,7 @@ Lunaix全部特性一览: ### 4.1 使用 GNU CC 工具链 -正如同大多数OS一样,LunaixOS 是一个混合了 C 和汇编的产物。这就意味着你得要使用一些标准的C编译器来构建Lunaix。在这里,我推荐使用 GNU CC 工具链来进行构建。因为Lunaix 在编写时使用了大量的GNU CC 相关编译器属性修饰 (`__attribute__`) 。假若使用其他工具链,如LLVM,我对此就不能做出任何保证了。 +正如同大多数内核一样,Lunaix 是一个混合了 C 和汇编的产物。这就意味着你得要使用一些标准的C编译器来构建Lunaix。在这里,我推荐使用 GNU CC 工具链来进行构建。因为Lunaix 在编写时使用了大量的GNU CC 相关编译器属性修饰 (`__attribute__`) 。假若使用其他工具链,如LLVM,我对此就不能做出任何保证了。 如果你使用的是基于 x86 指令集的Linux系统,不论是64位还是32位,**其本机自带的gcc就足以编译Lunaix**。 当然了,如果说你的平台是其他非x86的,你也可以指定使用某个针对x86_32的gcc套件来进行交叉编译——在`make`时通过`CX_PREFIX`变量来指定gcc套件的前缀。如下例所示,我们可以在任意平台上,如risc-v,单独使用一个面向x86_32的gcc来进行交叉编译: @@ -177,6 +184,12 @@ console=/dev/ttyFB0 该脚本的运行需要设置 `ARCH=` 环境变量,其值需要与编译时制定的值一致。 +如: + +```sh +ARCH=x86_64 ./live_debug.sh +``` + ## 5. 运行,分支以及 Issue ### 5.1 代码稳定性 @@ -289,11 +302,11 @@ LunaixOS 提供对以下POSIX的系统接口的实现。内核定义的系统调 2. `readdir(2)` 2. `readlink(2)` 2. `readlinkat(2)` -2. `rmdir(2)`※ -2. `unlink(2)`※ -2. `unlinkat(2)`※ +2. `rmdir(2)` +2. `unlink(2)` +2. `unlinkat(2)` 2. `link(2)`※ -2. `fsync(2)`※ +2. `fsync(2)` 2. `dup(2)` 2. `dup2(2)` 2. `symlink(2)` diff --git a/lunaix-os/LConfig b/lunaix-os/LConfig index fef1221..36be155 100644 --- a/lunaix-os/LConfig +++ b/lunaix-os/LConfig @@ -30,4 +30,39 @@ def debug_and_testing(): extermly unstable """ type(bool) - default(False) \ No newline at end of file + default(False) + + @Term("Report on stalled thread") + def check_stall(): + """ + Check and report on any thread that spend too much time in kernel. + """ + + type(bool) + default(True) + + @Term("Max kernel time allowance") + def stall_timeout(): + """ + Set the maximum time (in seconds) spent in kernel before considered + to be stalled. + """ + + type(int) + default(10) + + return v(check_stall) + + @Term("Max number of preemptions") + def stall_max_preempts(): + """ + Set the maximum number of preemptions that a task can take + before it is considered to be stucked in some loops. + + Setting it to 0 disable this check + """ + + type(int) + default(0) + + return v(check_stall) \ No newline at end of file diff --git a/lunaix-os/arch/x86/exceptions/interrupt32.S b/lunaix-os/arch/x86/exceptions/interrupt32.S index 58c033a..52f3ad4 100644 --- a/lunaix-os/arch/x86/exceptions/interrupt32.S +++ b/lunaix-os/arch/x86/exceptions/interrupt32.S @@ -20,7 +20,7 @@ .section .bss .align 16 lo_tmp_stack: - .skip 256 + .skip 1024 tmp_stack: /* @@ -182,7 +182,7 @@ # is required to prevent corrupt existing stack movl $tmp_stack, %esp - call signal_dispatch # kernel/signal.c + call switch_signposting # kernel/process/switch.c movl current_thread, %ebx test %eax, %eax # do we have signal to handle? diff --git a/lunaix-os/arch/x86/exceptions/interrupt64.S b/lunaix-os/arch/x86/exceptions/interrupt64.S index 2b15410..356de95 100644 --- a/lunaix-os/arch/x86/exceptions/interrupt64.S +++ b/lunaix-os/arch/x86/exceptions/interrupt64.S @@ -10,7 +10,7 @@ tmp_store: .skip 8 lo_tmp_stack: - .skip 256 + .skip 1024 tmp_stack: @@ -160,7 +160,7 @@ # is required to prevent corrupt existing stack movq $tmp_stack, %rsp - call signal_dispatch # kernel/signal.c + call switch_signposting # kernel/process/switch.c movq current_thread, %rbx test %rax, %rax # do we have signal to handle? diff --git a/lunaix-os/arch/x86/hal/rngx86.c b/lunaix-os/arch/x86/hal/rngx86.c index 0d48d44..c444196 100644 --- a/lunaix-os/arch/x86/hal/rngx86.c +++ b/lunaix-os/arch/x86/hal/rngx86.c @@ -8,22 +8,24 @@ rng_fill(void* data, size_t len) { #ifdef CONFIG_ARCH_X86_64 asm volatile("1:\n" + "subq $8, %1\n" "rdrand %%rax\n" - "movq %%rax, (%0)\n" + "movq %%rax, (%0, %1, 1)\n" "addq $8, %%rax\n" - "subq $8, %1\n" + "testq %1, %1\n" "jnz 1b" :: "r"((ptr_t)data), "r"((len & ~0x7)) : - "%eax"); + "rax"); #else asm volatile("1:\n" + "subl $4, %1\n" "rdrand %%eax\n" - "movl %%eax, (%0)\n" + "movl %%eax, (%0, %1, 1)\n" "addl $4, %%eax\n" - "subl $4, %1\n" + "testl %1, %1\n" "jnz 1b" :: "r"((ptr_t)data), diff --git a/lunaix-os/arch/x86/includes/sys/failsafe.h b/lunaix-os/arch/x86/includes/sys/failsafe.h index bf96d35..42ebf9c 100644 --- a/lunaix-os/arch/x86/includes/sys/failsafe.h +++ b/lunaix-os/arch/x86/includes/sys/failsafe.h @@ -24,13 +24,10 @@ failsafe_diagnostic() { extern int failsafe_stack_top[]; #ifdef CONFIG_ARCH_X86_64 asm ( - "movq %%rsp, %%rax\n" - "movq %%rbp, %%rbx\n" + "movq %%rsp, %%rdi\n" + "movq %%rbp, %%rsi\n" "movq %0, %%rsp\n" - - "pushq %%rax\n" - "pushq %%rbx\n" "call do_failsafe_unrecoverable\n" ::"r"(failsafe_stack_top) diff --git a/lunaix-os/arch/x86/includes/sys/muldiv64.h b/lunaix-os/arch/x86/includes/sys/muldiv64.h index 5905990..c575685 100644 --- a/lunaix-os/arch/x86/includes/sys/muldiv64.h +++ b/lunaix-os/arch/x86/includes/sys/muldiv64.h @@ -11,7 +11,7 @@ __base = (base); \ if (__builtin_constant_p(__base) && is_pot(__base)) { \ __mod = n & (__base - 1); \ - n >>= ILOG2(__base); \ + n >>= ilog2(__base); \ } else { \ asm("" : "=a"(__low), "=d"(__high) : "A"(n)); \ __upper = __high; \ diff --git a/lunaix-os/arch/x86/syscall32.S b/lunaix-os/arch/x86/syscall32.S index 4f0ad5b..650dc17 100644 --- a/lunaix-os/arch/x86/syscall32.S +++ b/lunaix-os/arch/x86/syscall32.S @@ -107,10 +107,11 @@ pushl 12(%ebx) /* edx - #3 arg */ pushl 8(%ebx) /* ecx - #2 arg */ pushl 4(%ebx) /* ebx - #1 arg */ + pushl (%eax) - call *(%eax) + call dispatch_syscall - addl $20, %esp /* remove the parameters from stack */ + addl $24, %esp /* remove the parameters from stack */ popl %ebx movl %eax, (%ebx) /* save the return value */ diff --git a/lunaix-os/arch/x86/syscall64.S b/lunaix-os/arch/x86/syscall64.S index f4ea8bd..624002f 100644 --- a/lunaix-os/arch/x86/syscall64.S +++ b/lunaix-os/arch/x86/syscall64.S @@ -106,14 +106,14 @@ ret 1: - - movq irbx(%rbx), %rdi /* rbx -> rdi #1 arg */ - movq ircx(%rbx), %rsi /* rcx -> rsi #2 arg */ - movq irdx(%rbx), %rdx /* rdx -> rdx #3 arg */ - movq irdi(%rbx), %rcx /* rdi -> rcx #4 arg */ - movq irsi(%rbx), %r8 /* rsi -> r8 #5 arg */ + movq (%rax), %rdi + movq irbx(%rbx), %rsi /* rbx -> rsi #1 arg */ + movq ircx(%rbx), %rdx /* rcx -> rdx #2 arg */ + movq irdx(%rbx), %rcx /* rdx -> rcx #3 arg */ + movq irdi(%rbx), %r8 /* rdi -> r8 #4 arg */ + movq irsi(%rbx), %r9 /* rsi -> r9 #5 arg */ - call *(%rax) + call dispatch_syscall movq %rax, irax(%rbx) /* save the return value */ diff --git a/lunaix-os/arch/x86/trace.c b/lunaix-os/arch/x86/trace.c index 3e06895..6a087a9 100644 --- a/lunaix-os/arch/x86/trace.c +++ b/lunaix-os/arch/x86/trace.c @@ -3,9 +3,10 @@ void trace_print_transistion_short(struct hart_state* hstate) { - trace_log(" trigger: iv=%d, ecause=%p", + trace_log(" trigger: iv=%d, ecause=%p, frame=%p", hart_vector_stamp(hstate), - hart_ecause(hstate)); + hart_ecause(hstate), + hart_stack_frame(hstate)); } #ifdef CONFIG_ARCH_X86_64 @@ -40,7 +41,7 @@ trace_dump_state(struct hart_state* hstate) trace_log(" rdi=0x%016lx, rsi=0x%016lx", rh->rdi, rh->rsi); - trace_log(" r8=0x%016lx, r9=0x%016lx", + trace_log(" r08=0x%016lx, r09=0x%016lx", rh->r8, rh->r9); trace_log(" r10=0x%016lx, r11=0x%016lx", rh->r10, rh->r11); diff --git a/lunaix-os/hal/ahci/ahci.c b/lunaix-os/hal/ahci/ahci.c index 82375e0..f4e0dd4 100644 --- a/lunaix-os/hal/ahci/ahci.c +++ b/lunaix-os/hal/ahci/ahci.c @@ -255,6 +255,7 @@ hba_bind_vbuf(struct hba_cmdh* cmdh, struct hba_cmdt* cmdt, struct vecbuf* vbuf) do { assert_msg(i < HBA_MAX_PRDTE, "HBA: Too many PRDTEs"); assert_msg(pos->buf.size <= 0x400000U, "HBA: Buffer too big"); + assert_msg(pos->buf.size, "HBA: expect a non-zero buffer size"); cmdt->entries[i++] = (struct hba_prdte){ .data_base = vmm_v2p((ptr_t)pos->buf.buffer), @@ -410,6 +411,7 @@ int ahci_identify_device(struct hba_device* device) { // 用于重新识别设备(比如在热插拔的情况下) + // FIXME this is not right... vfree(device); return ahci_init_device(device->port); } diff --git a/lunaix-os/hal/ahci/io_event.c b/lunaix-os/hal/ahci/io_event.c index 4e77f98..d37f8e6 100644 --- a/lunaix-os/hal/ahci/io_event.c +++ b/lunaix-os/hal/ahci/io_event.c @@ -28,7 +28,7 @@ proceed: if (!hba->base[HBA_RIS]) return; - u32_t port_num = 31 - clz(hba->base[HBA_RIS]); + u32_t port_num = msbiti - clz(hba->base[HBA_RIS]); struct hba_port* port = hba->ports[port_num]; struct hba_cmd_context* cmdctx = &port->cmdctx; u32_t processed = port->regs[HBA_RPxCI] ^ cmdctx->tracked_ci; @@ -48,7 +48,7 @@ proceed: goto done; } - u32_t slot = 31 - clz(processed); + u32_t slot = msbiti - clz(processed); struct hba_cmd_state* cmdstate = cmdctx->issued[slot]; if (!cmdstate) { diff --git a/lunaix-os/hal/term/term_io.c b/lunaix-os/hal/term/term_io.c index cb93c4e..2c0fb66 100644 --- a/lunaix-os/hal/term/term_io.c +++ b/lunaix-os/hal/term/term_io.c @@ -1,7 +1,7 @@ #include #include -#include +#include #include @@ -26,7 +26,7 @@ do_read_raw(struct term* tdev) min = MIN(min, (size_t)line_in->sz_hlf); while (sz <= min && dt <= expr) { // XXX should we held the device lock while we are waiting? - sched_pass(); + yield_current(); dt = clock_systime() - t; t += dt; diff --git a/lunaix-os/includes/klibc/hash.h b/lunaix-os/includes/klibc/hash.h index 69df34b..715f4a5 100644 --- a/lunaix-os/includes/klibc/hash.h +++ b/lunaix-os/includes/klibc/hash.h @@ -17,7 +17,7 @@ strhash_32(const char* str, u32_t truncate_to); * @param val * @return u32_t */ -inline u32_t +static inline u32_t hash_32(const u32_t val, u32_t truncate_to) { return (val * 0x61C88647u) >> (HASH_SIZE_BITS - truncate_to); diff --git a/lunaix-os/includes/klibc/string.h b/lunaix-os/includes/klibc/string.h index 04272ed..415d481 100644 --- a/lunaix-os/includes/klibc/string.h +++ b/lunaix-os/includes/klibc/string.h @@ -31,6 +31,9 @@ strchr(const char* str, int character); int streq(const char* a, const char* b); +int +strneq(const char* a, const char* b, unsigned long n); + void strrtrim(char* str); diff --git a/lunaix-os/includes/lunaix/bcache.h b/lunaix-os/includes/lunaix/bcache.h new file mode 100644 index 0000000..ce03494 --- /dev/null +++ b/lunaix-os/includes/lunaix/bcache.h @@ -0,0 +1,179 @@ +#ifndef __LUNAIX_BCACHE_H +#define __LUNAIX_BCACHE_H + +#include +#include +#include +#include +#include + +/* + Block cache. A cache built on top of + sparse array (trie tree) allow caching + any blocks that have spatial structure + attach to them. With intention to unify + all the existing caching construct, as + well as potential future use case. + + NB. block is not necessarily + equivalence to disk sector nor filesystem + logical block. block can be anything + discrete. + + NB2. not to be confused with page cahce + (pcache), which is a special case of + bcache. +*/ + +struct bcache; +struct bcache_ops +{ + void (*release_on_evict)(struct bcache*, void* data); + void (*sync_cached)(struct bcache*, unsigned long tag, void* data); +}; + +struct bcache +{ + struct { + unsigned int blksz; + }; + + struct btrie root; + struct lru_zone* lru; + struct bcache_ops ops; + struct llist_header objs; + struct spinlock lock; +}; // block cache + +struct bcache_node +{ + void* data; + + unsigned long tag; + + struct bcache* holder; + unsigned int refs; + struct lru_node lru_node; + struct llist_header objs; +}; + +typedef void * bcobj_t; +typedef struct lru_zone* bcache_zone_t; + +static inline void* +bcached_data(bcobj_t obj) +{ + return ((struct bcache_node*)obj)->data; +} + +#define to_bcache_node(cobj) \ + ((struct bcache_node*)(cobj)) + +#define bcache_holder_embed(cobj, type, member) \ + container_of(to_bcache_node(cobj)->holder, type, member) + +/** + * @brief Create a block cache with shared bcache zone + * + * @param cache to be initialized + * @param name name of this cache + * @param log_ways ways-associative of this cache + * @param cap capacity of this cache, -1 for 'infinity' cache + * @param blk_size size of each cached object + * @param ops block cache operation + */ +void +bcache_init_zone(struct bcache* cache, bcache_zone_t zone, + unsigned int log_ways, int cap, + unsigned int blk_size, struct bcache_ops* ops); + +bcache_zone_t +bcache_create_zone(char* name); + +bcobj_t +bcache_put_and_ref(struct bcache* cache, unsigned long tag, void* block); + +/** + * @brief Try look for a hit and return the reference to block. + * Now, this create a unmanaged pointer that could end up in + * everywhere and unsafe to evict. One should called `bcache_tryhit_unref` + * when the reference is no longer needed. + * + * @param cache + * @param tag + * @param block_out + * @return true + * @return false + */ +bool +bcache_tryget(struct bcache* cache, unsigned long tag, bcobj_t* result); + +/** + * @brief Unreference a cached block that is returned + * by `bcache_tryhit_ref` + * + * @param cache + * @param tag + * @param block_out + * @return true + * @return false + */ +void +bcache_return(bcobj_t obj); + +static inline void +bcache_refonce(bcobj_t obj) +{ + struct bcache_node* b_node; + b_node = to_bcache_node(obj); + + assert(b_node->refs); + b_node->refs++; +} + +void +bcache_promote(bcobj_t obj); + +void +bcache_evict(struct bcache* cache, unsigned long tag); + +static inline void +bcache_evict_one(struct bcache* cache) +{ + lru_evict_one(cache->lru); +} + +void +bcache_flush(struct bcache* cache); + +void +bcache_free(struct bcache* cache); + +void +bcache_zone_free(bcache_zone_t zone); + +/** + * @brief Create a block cache + * + * @param cache to be initialized + * @param name name of this cache + * @param log_ways ways-associative of this cache + * @param cap capacity of this cache, -1 for 'infinity' cache + * @param blk_size size of each cached object + * @param ops block cache operation + */ +static inline void +bcache_init(struct bcache* cache, char* name, unsigned int log_ways, + int cap, unsigned int blk_size, struct bcache_ops* ops) +{ + bcache_init_zone(cache, bcache_create_zone(name), + log_ways, cap, blk_size, ops); +} + +static inline void +bcache_put(struct bcache* cache, unsigned long tag, void* block) +{ + bcache_return(bcache_put_and_ref(cache, tag, block)); +} + +#endif /* __LUNAIX_BCACHE_H */ diff --git a/lunaix-os/includes/lunaix/blkbuf.h b/lunaix-os/includes/lunaix/blkbuf.h new file mode 100644 index 0000000..decf92f --- /dev/null +++ b/lunaix-os/includes/lunaix/blkbuf.h @@ -0,0 +1,110 @@ +#ifndef __LUNAIX_BLKBUF_H +#define __LUNAIX_BLKBUF_H + +#include +#include +#include +#include + +struct blkbuf_cache +{ + union { + struct { + unsigned int blksize; + }; + struct bcache cached; + }; + struct llist_header dirty; + struct block_dev* blkdev; + mutex_t lock; +}; + +struct blk_buf { + void* raw; + bcobj_t cobj; + struct llist_header dirty; + struct blkio_req* breq; +}; + +typedef void* bbuf_t; + +#define BLOCK_BUFFER(type, name) \ + union { \ + type* name; \ + bbuf_t bb_##name; \ + } + +#define INVL_BUFFER 0xdeadc0de + +#define bbuf_null ((bbuf_t)0) + +static inline bool +blkbuf_errbuf(bbuf_t buf) { + return (ptr_t)buf == INVL_BUFFER; +} + +static inline bool +blkbuf_nullbuf(bbuf_t buf) { + return buf == bbuf_null; +} + +static inline unsigned int +blkbuf_id(bbuf_t buf) +{ + return to_bcache_node(((struct blk_buf*)buf)->cobj)->tag; +} + +static inline unsigned int +blkbuf_refcounts(bbuf_t buf) +{ + return to_bcache_node(((struct blk_buf*)buf)->cobj)->refs; +} + +static inline bool +blkbuf_not_shared(bbuf_t buf) +{ + return blkbuf_refcounts(buf) == 1; +} + + +struct blkbuf_cache* +blkbuf_create(struct block_dev* blkdev, unsigned int blk_size); + +bbuf_t +blkbuf_take(struct blkbuf_cache* bc, unsigned int block_id); + +static inline bbuf_t +blkbuf_refonce(bbuf_t buf) +{ + if (likely(buf && !blkbuf_errbuf(buf))) { + bcache_refonce(((struct blk_buf*)buf)->cobj); + } + + return buf; +} + +static inline void* +blkbuf_data(bbuf_t buf) +{ + assert(!blkbuf_errbuf(buf)); + return ((struct blk_buf*)buf)->raw; +} +#define block_buffer(buf, type) \ + ((type*)blkbuf_data(buf)) + +void +blkbuf_dirty(bbuf_t buf); + +void +blkbuf_schedule_sync(bbuf_t buf); + +void +blkbuf_release(struct blkbuf_cache* bc); + +void +blkbuf_put(bbuf_t buf); + +bool +blkbuf_syncall(struct blkbuf_cache* bc, bool async); + +#endif /* __LUNAIX_BLKBUF_H */ diff --git a/lunaix-os/includes/lunaix/blkio.h b/lunaix-os/includes/lunaix/blkio.h index a869c8c..6470194 100644 --- a/lunaix-os/includes/lunaix/blkio.h +++ b/lunaix-os/includes/lunaix/blkio.h @@ -1,9 +1,11 @@ #ifndef __LUNAIX_BLKIO_H #define __LUNAIX_BLKIO_H +#include #include #include #include +#include #include #define BLKIO_WRITE 0x1 @@ -11,13 +13,14 @@ #define BLKIO_BUSY 0x4 #define BLKIO_PENDING 0x8 +// Free on complete +#define BLKIO_FOC 0x10 +#define BLKIO_SHOULD_WAIT 0x20 #define BLKIO_WAIT 0x1 +#define BLKIO_NOWAIT 0 #define BLKIO_NOASYNC 0x2 -// Free on complete -#define BLKIO_FOC 0x10 - #define BLKIO_SCHED_IDEL 0x1 struct blkio_req; @@ -41,17 +44,39 @@ struct blkio_req struct blkio_context { struct llist_header queue; + struct { u32_t seektime; u32_t rotdelay; } metrics; + req_handler handle_one; u32_t state; u32_t busy; void* driver; + + mutex_t lock; }; +static inline void +blkio_lock(struct blkio_context* contex) +{ + mutex_lock(&contex->lock); +} + +static inline void +blkio_unlock(struct blkio_context* contex) +{ + mutex_unlock(&contex->lock); +} + +static inline bool +blkio_stalled(struct blkio_context* contex) +{ + return !contex->busy; +} + void blkio_init(); @@ -89,6 +114,131 @@ blkio_vwr(struct vecbuf* vbuf, void* evt_args, u32_t options); +/** + * @brief Vectorized request (no write/read preference) + * + * @param vbuf + * @param start_lba + * @param completed + * @param evt_args + * @param options + * @return struct blkio_req* + */ +static inline struct blkio_req* +blkio_vreq(struct vecbuf* buffer, + u64_t start_lba, + blkio_cb completed, + void* evt_args, + u32_t options) { + /* + This is currently aliased to blkio_vrd. Although `no preference` + does essentially mean `default read`, the blkio_vreq just used + to enhance readability + */ + return blkio_vrd(buffer, start_lba, completed, evt_args, options); +} + + +/** + * @brief Bind a block IO context to request + * + * @param ctx + * @param req + */ +static inline void +blkio_bindctx(struct blkio_req* req, struct blkio_context* ctx) +{ + req->io_ctx = ctx; +} + +/** + * @brief Set block IO request to read + * + * @param ctx + * @param req + */ +static inline void +blkio_setread(struct blkio_req* req) +{ + if ((req->flags & BLKIO_PENDING)) { + return; + } + + req->flags &= ~BLKIO_WRITE; +} + +/** + * @brief Set block IO request to write + * + * @param ctx + * @param req + */ +static inline void +blkio_setwrite(struct blkio_req* req) +{ + if ((req->flags & BLKIO_PENDING)) { + return; + } + + req->flags |= BLKIO_WRITE; +} + +/** + * @brief Set callback when request complete + * + * @param req + * @param on_completed + */ +static inline void +blkio_when_completed(struct blkio_req* req, blkio_cb on_completed) +{ + req->completed = on_completed; +} + +static inline bool +blkio_is_pending(struct blkio_req* req) +{ + return (req->flags & BLKIO_PENDING); +} + +/** + * @brief Mark request to be freed-on-completion (FOC) + * + * @param req + */ +static inline void +blkio_mark_foc(struct blkio_req* req) +{ + req->flags |= BLKIO_FOC; +} + +/** + * @brief Mark request to be not-freed-on-completion (nFOC) + * + * @param req + */ +static inline void +blkio_mark_nfoc(struct blkio_req* req) +{ + req->flags &= ~BLKIO_FOC; +} + +int +blkio_read_aligned(struct blkio_context* ctx, + unsigned long lba, void* block, size_t n_blk); + +int +blkio_read(struct blkio_context* ctx, + unsigned long offset, void* block, size_t len); + +int +blkio_write_aligned(struct blkio_context* ctx, + unsigned long lba, void* block, size_t n_blk); + +int +blkio_read(struct blkio_context* ctx, + unsigned long offset, void* block, size_t len); + void blkio_free_req(struct blkio_req* req); @@ -99,7 +249,8 @@ blkio_free_req(struct blkio_req* req); * @param req */ void -blkio_commit(struct blkio_context* ctx, struct blkio_req* req, int options); +blkio_commit(struct blkio_req* req, int options); + /** * @brief Schedule an IO request to be handled. diff --git a/lunaix-os/includes/lunaix/block.h b/lunaix-os/includes/lunaix/block.h index 0eb7775..075ed37 100644 --- a/lunaix-os/includes/lunaix/block.h +++ b/lunaix-os/includes/lunaix/block.h @@ -48,6 +48,12 @@ typedef u64_t partition_t; typedef u32_t bdev_t; typedef void (*devfs_exporter)(struct block_dev* bdev, void* fsnode); +static inline struct block_dev* +block_dev(struct device* dev) +{ + return (struct block_dev*)dev->underlay; +} + void block_init(); diff --git a/lunaix-os/includes/lunaix/compiler.h b/lunaix-os/includes/lunaix/compiler.h index 28ae36e..570fdcf 100644 --- a/lunaix-os/includes/lunaix/compiler.h +++ b/lunaix-os/includes/lunaix/compiler.h @@ -16,9 +16,21 @@ #define defualt weak +#define msbiti (sizeof(int) * 8 - 1) #define clz(bits) __builtin_clz(bits) -#define sadd_overflow(a, b, of) __builtin_sadd_overflow(a, b, of) -#define umul_overflow(a, b, of) __builtin_umul_overflow(a, b, of) + +#ifdef CONFIG_ARCH_BITS_64 +#define msbitl (sizeof(long) * 8 - 1) +#define clzl(bits) __builtin_clzl(bits) +#else +#define msbitl msbiti +#define clzl(bits) clz(bits) +#endif + +#define sadd_of(a, b, of) __builtin_sadd_overflow(a, b, of) +#define saddl_of(a, b, of) __builtin_saddl_overflow(a, b, of) +#define umul_of(a, b, of) __builtin_umul_overflow(a, b, of) +#define umull_of(a, b, of) __builtin_umull_overflow(a, b, of) #define offsetof(f, m) __builtin_offsetof(f, m) #define prefetch_rd(ptr, ll) __builtin_prefetch((ptr), 0, ll) diff --git a/lunaix-os/includes/lunaix/ds/btrie.h b/lunaix-os/includes/lunaix/ds/btrie.h index c902c48..bcf0250 100644 --- a/lunaix-os/includes/lunaix/ds/btrie.h +++ b/lunaix-os/includes/lunaix/ds/btrie.h @@ -9,7 +9,7 @@ struct btrie { struct btrie_node* btrie_root; - int truncated; + unsigned int order; }; struct btrie_node @@ -18,21 +18,21 @@ struct btrie_node struct llist_header siblings; struct llist_header nodes; struct btrie_node* parent; - u32_t index; + unsigned long index; void* data; }; void -btrie_init(struct btrie* btrie, u32_t trunc_bits); +btrie_init(struct btrie* btrie, unsigned int order); void* -btrie_get(struct btrie* root, u32_t index); +btrie_get(struct btrie* root, unsigned long index); void -btrie_set(struct btrie* root, u32_t index, void* data); +btrie_set(struct btrie* root, unsigned long index, void* data); void* -btrie_remove(struct btrie* root, u32_t index); +btrie_remove(struct btrie* root, unsigned long index); void btrie_release(struct btrie* tree); diff --git a/lunaix-os/includes/lunaix/ds/hstr.h b/lunaix-os/includes/lunaix/ds/hstr.h index 6d2b0ee..f43a6db 100644 --- a/lunaix-os/includes/lunaix/ds/hstr.h +++ b/lunaix-os/includes/lunaix/ds/hstr.h @@ -26,6 +26,10 @@ struct hstr #define HSTR_EQ(str1, str2) ((str1)->hash == (str2)->hash) +#define HSTR_VAL(hstr) ((hstr).value) +#define HSTR_LEN(hstr) ((hstr).len) +#define HSTR_HASH(hstr) ((hstr).hash) + inline void hstr_rehash(struct hstr* hash_str, u32_t truncate_to) { diff --git a/lunaix-os/includes/lunaix/ds/lru.h b/lunaix-os/includes/lunaix/ds/lru.h index 76c9597..622d6e4 100644 --- a/lunaix-os/includes/lunaix/ds/lru.h +++ b/lunaix-os/includes/lunaix/ds/lru.h @@ -2,6 +2,7 @@ #define __LUNAIX_LRU_H #include +#include #include struct lru_node @@ -15,12 +16,29 @@ struct lru_zone { struct llist_header lead_node; struct llist_header zones; - u32_t objects; + char name[32]; evict_cb try_evict; + spinlock_t lock; + + unsigned int objects; + unsigned int hotness; + struct { + unsigned int n_single; + unsigned int n_half; + unsigned int n_full; + } evict_stats; + + union { + struct { + bool delayed_free:1; + unsigned char attempts; + }; + unsigned int flags; + }; }; struct lru_zone* -lru_new_zone(evict_cb try_evict_cb); +lru_new_zone(const char* name, evict_cb try_evict_cb); void lru_use_one(struct lru_zone* zone, struct lru_node* node); @@ -34,4 +52,10 @@ lru_remove(struct lru_zone* zone, struct lru_node* node); void lru_evict_half(struct lru_zone* zone); +void +lru_evict_all(struct lru_zone* zone); + +void +lru_free_zone(struct lru_zone* zone); + #endif /* __LUNAIX_LRU_H */ diff --git a/lunaix-os/includes/lunaix/ds/mutex.h b/lunaix-os/includes/lunaix/ds/mutex.h index 162186c..510f1fb 100644 --- a/lunaix-os/includes/lunaix/ds/mutex.h +++ b/lunaix-os/includes/lunaix/ds/mutex.h @@ -28,6 +28,12 @@ mutex_lock(mutex_t* mutex); void mutex_unlock(mutex_t* mutex); +void +mutex_lock_nested(mutex_t* mutex); + +void +mutex_unlock_nested(mutex_t* mutex); + void mutex_unlock_for(mutex_t* mutex, pid_t pid); diff --git a/lunaix-os/includes/lunaix/ds/spinlock.h b/lunaix-os/includes/lunaix/ds/spinlock.h new file mode 100644 index 0000000..557f310 --- /dev/null +++ b/lunaix-os/includes/lunaix/ds/spinlock.h @@ -0,0 +1,51 @@ +#ifndef __LUNAIX_SPIN_H +#define __LUNAIX_SPIN_H + +#include + +struct spinlock +{ + volatile bool flag; +}; + +typedef struct spinlock spinlock_t; + +/* + TODO we might use our own construct for atomic ops + But we will do itlater, currently this whole + kernel is on a single long thread of fate, + there won't be any hardware concurrent access + happened here. +*/ + +static inline void +spinlock_init(spinlock_t* lock) +{ + lock->flag = false; +} + +static inline bool spinlock_try_acquire(spinlock_t* lock) +{ + if (lock->flag){ + return false; + } + + return (lock->flag = true); +} + +static inline void spinlock_acquire(spinlock_t* lock) +{ + while (lock->flag); + lock->flag = true; +} + +static inline void spinlock_release(spinlock_t* lock) +{ + lock->flag = false; +} + +#define DEFINE_SPINLOCK_OPS(type, lock_accessor) \ + static inline void lock(type obj) { spinlock_acquire(&obj->lock_accessor); } \ + static inline void unlock(type obj) { spinlock_release(&obj->lock_accessor); } + +#endif /* __LUNAIX_SPIN_H */ diff --git a/lunaix-os/includes/lunaix/ds/waitq.h b/lunaix-os/includes/lunaix/ds/waitq.h index efcc808..4469e1c 100644 --- a/lunaix-os/includes/lunaix/ds/waitq.h +++ b/lunaix-os/includes/lunaix/ds/waitq.h @@ -26,9 +26,21 @@ waitq_cancel_wait(waitq_t* waitq) llist_delete(&waitq->waiters); } +void +prepare_to_wait(waitq_t* waitq); + +void +try_wait(); + +void +try_wait_check_stall(); + void pwait(waitq_t* queue); +void +pwait_check_stall(waitq_t* queue); + void pwake_one(waitq_t* queue); diff --git a/lunaix-os/includes/lunaix/fs.h b/lunaix-os/includes/lunaix/fs.h index 0240c96..94cf42c 100644 --- a/lunaix-os/includes/lunaix/fs.h +++ b/lunaix-os/includes/lunaix/fs.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include @@ -19,15 +21,12 @@ #define VFS_NAME_MAXLEN 128 #define VFS_MAX_FD 32 -#define VFS_IFDIR F_DIR -#define VFS_IFFILE F_FILE -#define VFS_IFDEV (F_DEV | F_FILE) -#define VFS_IFSEQDEV (F_SEQDEV | F_FILE) -#define VFS_IFVOLDEV (F_VOLDEV | F_FILE) -#define VFS_IFSYMLINK (F_SYMLINK | F_FILE) - -#define VFS_DEVFILE(type) ((type) & F_DEV) -#define VFS_DEVTYPE(type) ((type) & ((F_SEQDEV | F_VOLDEV) ^ F_DEV)) +#define VFS_IFFILE F_FILE +#define VFS_IFDIR (F_FILE | F_DIR ) +#define VFS_IFDEV (F_FILE | F_DEV ) +#define VFS_IFSYMLINK (F_FILE | F_SYMLINK) +#define VFS_IFVOLDEV (F_FILE | F_SVDEV ) +#define VFS_IFSEQDEV VFS_IFDEV // Walk, mkdir if component encountered is non-exists. #define VFS_WALK_MKPARENT 0x1 @@ -51,7 +50,8 @@ #define VFS_PATH_DELIM '/' -#define FSTYPE_ROFS 0x1 +#define FSTYPE_ROFS 0b00000001 +#define FSTYPE_PSEUDO 0x00000010 #define TEST_FD(fd) (fd >= 0 && fd < VFS_MAX_FD) @@ -77,6 +77,9 @@ lru_use_one(dnode_lru, &dnode->lru); \ }) +#define assert_fs(cond) assert_p(cond, "FS") +#define fail_fs(msg) fail_p(msg, "FS") + typedef u32_t inode_t; struct v_dnode; @@ -96,6 +99,9 @@ extern struct hstr vfs_ddot; extern struct hstr vfs_dot; extern struct v_dnode* vfs_sysroot; +typedef int (*mntops_mnt)(struct v_superblock* vsb, struct v_dnode* mount_point); +typedef int (*mntops_umnt)(struct v_superblock* vsb); + struct filesystem { struct llist_header fs_flat; @@ -103,8 +109,8 @@ struct filesystem struct hstr fs_name; u32_t types; int fs_id; - int (*mount)(struct v_superblock* vsb, struct v_dnode* mount_point); - int (*unmount)(struct v_superblock* vsb); + mntops_mnt mount; + mntops_umnt unmount; }; struct v_superblock @@ -113,20 +119,22 @@ struct v_superblock struct device* dev; struct v_dnode* root; struct filesystem* fs; + struct blkbuf_cache* blks; struct hbucket* i_cache; void* data; + unsigned int ref_count; size_t blksize; struct { - u32_t (*read_capacity)(struct v_superblock* vsb); - u32_t (*read_usage)(struct v_superblock* vsb); + size_t (*read_capacity)(struct v_superblock* vsb); + size_t (*read_usage)(struct v_superblock* vsb); void (*init_inode)(struct v_superblock* vsb, struct v_inode* inode); + void (*release)(struct v_superblock* vsb); } ops; }; struct dir_context { - int index; void* cb_data; void (*read_complete_callback)(struct dir_context* dctx, const char* name, @@ -149,26 +157,33 @@ struct v_file_ops int (*read_page)(struct v_inode* inode, void* pg, size_t fpos); int (*readdir)(struct v_file* file, struct dir_context* dctx); - int (*seek)(struct v_inode* inode, size_t offset); // optional + int (*seek)(struct v_file* file, size_t offset); int (*close)(struct v_file* file); int (*sync)(struct v_file* file); }; struct v_inode_ops { - int (*create)(struct v_inode* this, struct v_dnode* dnode); + int (*create)(struct v_inode* this, struct v_dnode* dnode, + unsigned int itype); + int (*open)(struct v_inode* this, struct v_file* file); int (*sync)(struct v_inode* this); + int (*mkdir)(struct v_inode* this, struct v_dnode* dnode); - int (*rmdir)(struct v_inode* this, struct v_dnode* dir); - int (*unlink)(struct v_inode* this); + int (*rmdir)(struct v_inode* this, struct v_dnode* dnode); + int (*unlink)(struct v_inode* this, struct v_dnode* name); int (*link)(struct v_inode* this, struct v_dnode* new_name); + int (*read_symlink)(struct v_inode* this, const char** path_out); int (*set_symlink)(struct v_inode* this, const char* target); + int (*dir_lookup)(struct v_inode* this, struct v_dnode* dnode); + int (*rename)(struct v_inode* from_inode, struct v_dnode* from_dnode, struct v_dnode* to_dnode); + int (*getxattr)(struct v_inode* this, struct v_xattr_entry* entry); // optional int (*setxattr)(struct v_inode* this, @@ -192,6 +207,7 @@ struct v_file struct llist_header* f_list; u32_t f_pos; atomic_ulong ref_count; + void* data; struct v_file_ops* ops; // for caching }; @@ -269,6 +285,8 @@ struct v_dnode atomic_ulong ref_count; void* data; + + void (*destruct)(struct v_dnode* dnode); }; struct v_fdtable @@ -279,8 +297,7 @@ struct v_fdtable struct pcache { struct v_inode* master; - struct btrie tree; - struct llist_header pages; + struct bcache cache; struct llist_header dirty; u32_t n_dirty; u32_t n_pages; @@ -288,14 +305,17 @@ struct pcache struct pcache_pg { - struct llist_header pg_list; struct llist_header dirty_list; - struct lru_node lru; - struct pcache* holder; - void* pg; - u32_t flags; - u32_t fpos; - u32_t len; + + union { + struct { + bool dirty:1; + }; + u32_t flags; + }; + + void* data; + unsigned int index; }; static inline bool @@ -395,6 +415,35 @@ vfs_sb_alloc(); void vfs_sb_free(struct v_superblock* sb); +void +vfs_sb_ref(struct v_superblock* sb); + +#define vfs_assign_sb(sb_accessor, sb) \ + ({ \ + if (sb_accessor) { \ + vfs_sb_free(sb_accessor); \ + } \ + vfs_sb_ref(((sb_accessor) = (sb))); \ + }) + +static inline void +vfs_i_assign_sb(struct v_inode* inode, struct v_superblock* sb) +{ + vfs_assign_sb(inode->sb, sb); +} + +static inline void +vfs_d_assign_sb(struct v_dnode* dnode, struct v_superblock* sb) +{ + vfs_assign_sb(dnode->super_block, sb); +} + +static inline void +vfs_vmnt_assign_sb(struct v_mount* vmnt, struct v_superblock* sb) +{ + vfs_assign_sb(vmnt->super_block, sb); +} + struct v_dnode* vfs_d_alloc(); @@ -437,21 +486,6 @@ vfs_get_path(struct v_dnode* dnode, char* buf, size_t size, int depth); void pcache_init(struct pcache* pcache); -void -pcache_release_page(struct pcache* pcache, struct pcache_pg* page); - -struct pcache_pg* -pcache_new_page(struct pcache* pcache, u32_t index); - -void -pcache_set_dirty(struct pcache* pcache, struct pcache_pg* pg); - -int -pcache_get_page(struct pcache* pcache, - u32_t index, - u32_t* offset, - struct pcache_pg** page); - int pcache_write(struct v_inode* inode, void* data, u32_t len, u32_t fpos); @@ -525,7 +559,7 @@ int default_file_close(struct v_file* file); int -default_file_seek(struct v_inode* inode, size_t offset); +default_file_seek(struct v_file* file, size_t offset); int default_inode_open(struct v_inode* this, struct v_file* file); @@ -545,4 +579,67 @@ xattr_getcache(struct v_inode* inode, struct hstr* name); void xattr_addcache(struct v_inode* inode, struct v_xattr_entry* xattr); + +/* --- misc stuff --- */ + +#define check_itype(to_check, itype) \ + (((to_check) & (itype)) == (itype)) + +/** + * @brief Check if node represent a regular file (nothing but a file) + * + * @param inode + * @return true + * @return false + */ +static inline bool +check_regfile_node(struct v_inode* inode) +{ + return inode->itype == VFS_IFFILE; +} + +/** + * @brief Check if node represent a file. + * This is basically everything within file system (dir, dev, etc.) + * + * @param inode + * @return true + * @return false + */ +static inline bool +check_file_node(struct v_inode* inode) +{ + return check_itype(inode->itype, VFS_IFFILE); +} + +static inline bool +check_directory_node(struct v_inode* inode) +{ + return check_itype(inode->itype, VFS_IFDIR); +} + +static inline bool +check_device_node(struct v_inode* inode) +{ + return check_itype(inode->itype, VFS_IFDEV); +} + +static inline bool +check_seqdev_node(struct v_inode* inode) +{ + return check_device_node(inode); +} + +static inline bool +check_voldev_node(struct v_inode* inode) +{ + return check_itype(inode->itype, VFS_IFVOLDEV); +} + +static inline bool +check_symlink_node(struct v_inode* inode) +{ + return check_itype(inode->itype, VFS_IFSYMLINK); +} + #endif /* __LUNAIX_VFS_H */ diff --git a/lunaix-os/includes/lunaix/fs/api.h b/lunaix-os/includes/lunaix/fs/api.h new file mode 100644 index 0000000..d002774 --- /dev/null +++ b/lunaix-os/includes/lunaix/fs/api.h @@ -0,0 +1,301 @@ +#ifndef __LUNAIX_FSAPI_H +#define __LUNAIX_FSAPI_H + +#include +#include +#include +#include + +#include + +struct fsapi_vsb_ops +{ + size_t (*read_capacity)(struct v_superblock* vsb); + size_t (*read_usage)(struct v_superblock* vsb); + void (*init_inode)(struct v_superblock* vsb, struct v_inode* inode); + void (*release)(struct v_superblock* vsb); +}; + +static inline struct device* +fsapi_blockdev(struct v_superblock* vsb) +{ + if (!(vsb->fs->types & FSTYPE_PSEUDO)) { + assert_fs(vsb->dev); + } + + return vsb->dev; +} + +typedef void (*inode_init)(struct v_superblock* vsb, struct v_inode* inode) ; +typedef void (*inode_free)(struct v_inode* inode) ; +typedef void (*dnode_free)(struct v_dnode* dnode) ; + +static inline void +fsapi_set_inode_initiator(struct v_superblock* vsb, inode_init inode_initiator) +{ + vsb->ops.init_inode = inode_initiator; +} + +static inline size_t +fsapi_block_size(struct v_superblock* vsb) +{ + return vsb->blksize; +} + +static inline void +fsapi_set_vsb_ops(struct v_superblock* vsb, struct fsapi_vsb_ops* basic_ops) +{ + vsb->ops.read_capacity = basic_ops->read_capacity; + vsb->ops.read_usage = basic_ops->read_usage; + vsb->ops.release = basic_ops->release; + vsb->ops.init_inode = basic_ops->init_inode; +} + +static inline void +fsapi_complete_vsb_setup(struct v_superblock* vsb, void* cfs_sb) +{ + assert_fs(vsb->ops.init_inode); + assert_fs(vsb->ops.read_capacity); + assert_fs(vsb->blksize); + assert_fs(vsb->blks); + + vsb->data = cfs_sb; +} + +static inline void +fsapi_begin_vsb_setup(struct v_superblock* vsb, size_t blksz) +{ + assert(!vsb->blks); + assert(blksz); + + vsb->blksize = blksz; + vsb->blks = blkbuf_create(block_dev(vsb->dev), blksz); +} + +static inline void +fsapi_reset_vsb(struct v_superblock* vsb) +{ + assert(vsb->blks); + blkbuf_release(vsb->blks); + + vsb->blks = NULL; + vsb->data = NULL; + vsb->blksize = 0; + vsb->root->mnt->flags = 0; + memset(&vsb->ops, 0, sizeof(vsb->ops)); +} + +static inline bool +fsapi_readonly_mount(struct v_superblock* vsb) +{ + return (vsb->root->mnt->flags & MNT_RO); +} + +static inline void +fsapi_set_readonly_mount(struct v_superblock* vsb) +{ + vsb->root->mnt->flags |= MNT_RO; +} + +#define fsapi_impl_data(vfs_obj, type) (type*)((vfs_obj)->data) + +static inline void +fsapi_inode_setid(struct v_inode* inode, + inode_t i_id, unsigned int blk_addr) +{ + inode->id = i_id; + inode->lb_addr = blk_addr; +} + +static inline void +fsapi_inode_settype(struct v_inode* inode, unsigned int type) +{ + inode->itype = type; +} + +static inline void +fsapi_inode_setsize(struct v_inode* inode, unsigned int fsize) +{ + inode->lb_usage = ICEIL(fsize, inode->sb->blksize); + inode->fsize = fsize; +} + +static inline void +fsapi_inode_setops(struct v_inode* inode, + struct v_inode_ops* ops) +{ + inode->ops = ops; +} + +static inline void +fsapi_inode_setfops(struct v_inode* inode, + struct v_file_ops* fops) +{ + inode->default_fops = fops; +} + +static inline void +fsapi_inode_setdector(struct v_inode* inode, + inode_free free_cb) +{ + inode->destruct = free_cb; +} + +static inline void +fsapi_inode_complete(struct v_inode* inode, void* data) +{ + assert_fs(inode->ops); + assert_fs(inode->default_fops); + assert_fs(inode->default_fops); + + inode->data = data; +} + +static inline void +fsapi_inode_settime(struct v_inode* inode, + time_t ctime, time_t mtime, time_t atime) +{ + inode->ctime = ctime; + inode->mtime = mtime; + inode->atime = atime; +} + +static inline void +fsapi_dnode_setdector(struct v_dnode* dnode, + dnode_free free_cb) +{ + dnode->destruct = free_cb; +} + +static inline struct v_inode* +fsapi_dnode_parent(struct v_dnode* dnode) +{ + assert(dnode->parent); + return dnode->parent->inode; +} + +static inline void +fsapi_dir_report(struct dir_context *dctx, + const char *name, const int len, const int dtype) +{ + dctx->read_complete_callback(dctx, name, len, dtype); +} + +/** + * @brief Get a block with file-system defined block size + * from underlying storage medium at given block id + * (block address). Depending on the device attribute, + * it may or may not go through the block cache layer. + * + * @param vsb super-block + * @param block_id block address + * @return bbuf_t + */ +static inline bbuf_t +fsblock_get(struct v_superblock* vsb, unsigned int block_id) +{ + return blkbuf_take(vsb->blks, block_id); +} + +/** + * @brief put the block back into cache, must to pair with + * fsblock_get. Otherwise memory leakage will occur. + * + * @param blkbuf + */ +static inline void +fsblock_put(bbuf_t blkbuf) +{ + return blkbuf_put(blkbuf); +} + + +static inline bbuf_t +fsblock_take(bbuf_t blk) +{ + return blkbuf_refonce(blk); +} + +static inline unsigned int +fsblock_id(bbuf_t blkbuf) +{ + return blkbuf_id(blkbuf); +} + +/** + * @brief Mark the block dirty and require scheduling a device + * write request to sync it with underlying medium. Lunaix + * will do the scheduling when it sees fit. + * + * @param blkbuf + */ +static inline void +fsblock_dirty(bbuf_t blkbuf) +{ + return blkbuf_dirty(blkbuf); +} + +/** + * @brief Manually trigger a sync cycle, regardless the + * dirty property. + * + * @param blkbuf + */ +static inline void +fsblock_sync(bbuf_t blkbuf) +{ + /* + XXX delay the sync for better write aggregation + scheduled sync event may happened immediately (i.e., blkio queue is + empty or nearly empty), any susequent write to the same blkbuf must + schedule another write. Which could thrash the disk IO when intensive + workload + */ + return blkbuf_schedule_sync(blkbuf); +} + +static inline bool +fsapi_handle_pseudo_dirent(struct v_file* file, struct dir_context* dctx) +{ + if (file->f_pos == 0) { + fsapi_dir_report(dctx, ".", 1, vfs_get_dtype(VFS_IFDIR)); + return true; + } + + if (file->f_pos == 1) { + fsapi_dir_report(dctx, "..", 2, vfs_get_dtype(VFS_IFDIR)); + return true; + } + + return false; +} + +static inline struct filesystem* +fsapi_fs_declare(const char* name, unsigned int type) +{ + struct filesystem* fs; + + fs = fsm_new_fs(name, -1); + assert_fs(fs); + + fs->types = type; + return fs; +} + +static inline void +fsapi_fs_set_mntops(struct filesystem* fs, + mntops_mnt mnt, mntops_umnt umnt) +{ + fs->mount = mnt; + fs->unmount = umnt; +} + +static inline void +fsapi_fs_finalise(struct filesystem* fs) +{ + assert_fs(fs->mount); + assert_fs(fs->unmount); + fsm_register(fs); +} + +#endif /* __LUNAIX_FSAPI_H */ diff --git a/lunaix-os/includes/lunaix/fs/ramfs.h b/lunaix-os/includes/lunaix/fs/ramfs.h index 4c77906..e430df7 100644 --- a/lunaix-os/includes/lunaix/fs/ramfs.h +++ b/lunaix-os/includes/lunaix/fs/ramfs.h @@ -10,12 +10,10 @@ struct ram_inode { u32_t flags; + size_t size; char* symlink; }; #define RAM_INODE(data) ((struct ram_inode*)(data)) -void -ramfs_init(); - #endif /* __LUNAIX_RAMFS_H */ diff --git a/lunaix-os/includes/lunaix/fs/taskfs.h b/lunaix-os/includes/lunaix/fs/taskfs.h index 0d50be3..b96e0e5 100644 --- a/lunaix-os/includes/lunaix/fs/taskfs.h +++ b/lunaix-os/includes/lunaix/fs/taskfs.h @@ -1,7 +1,7 @@ #ifndef __LUNAIX_TASKFS_H #define __LUNAIX_TASKFS_H -#include +#include #include struct task_attribute diff --git a/lunaix-os/includes/lunaix/kpreempt.h b/lunaix-os/includes/lunaix/kpreempt.h index e622e05..400aaff 100644 --- a/lunaix-os/includes/lunaix/kpreempt.h +++ b/lunaix-os/includes/lunaix/kpreempt.h @@ -2,6 +2,8 @@ #define __LUNAIX_KPREEMPT_H #include +#include +#include #define _preemptible \ __attribute__((section(".kf.preempt"))) no_inline @@ -16,4 +18,58 @@ "caller must be kernel preemptible"); \ } while(0) +static inline void +set_preemption() +{ + cpu_enable_interrupt(); +} + +static inline void +no_preemption() +{ + cpu_disable_interrupt(); +} + +static inline void +__schedule_away() +{ + current_thread->stats.last_reentry = clock_systime(); + + cpu_trap_sched(); + set_preemption(); +} + +/** + * @brief preempt the current thread, and yield the remaining + * time slice to other threads. + * + * The current thread is marked as if it is being + * preempted involuntarily by kernel. + * + */ +static inline void +preempt_current() +{ + no_preemption(); + thread_stats_update_kpreempt(); + __schedule_away(); +} + +/** + * @brief yield the remaining time slice to other threads. + * + * The current thread is marked as if it is being + * preempted voluntarily by itself. + * + */ +static inline void +yield_current() +{ + no_preemption(); + __schedule_away(); +} + +bool +preempt_check_stalled(struct thread* th); + #endif /* __LUNAIX_KPREEMPT_H */ diff --git a/lunaix-os/includes/lunaix/mm/page.h b/lunaix-os/includes/lunaix/mm/page.h index 0f9fb0c..da7cab4 100644 --- a/lunaix-os/includes/lunaix/mm/page.h +++ b/lunaix-os/includes/lunaix/mm/page.h @@ -303,4 +303,22 @@ vunmap_range(pfn_t start, size_t npages) pte_t alloc_kpage_at(pte_t* ptep, pte_t pte, int order); +static inline void* +vmalloc_page(int order) +{ + struct leaflet* leaf = alloc_leaflet(0); + if (!leaf) { + return NULL; + } + + return (void*)vmap(leaf, KERNEL_DATA); +} + +static inline void +vmfree(void* ptr) +{ + struct leaflet* leaf = ppfn_leaflet(pfn((ptr_t)ptr)); + leaflet_return(leaf); +} + #endif /* __LUNAIX_PAGE_H */ diff --git a/lunaix-os/includes/lunaix/mm/pmm.h b/lunaix-os/includes/lunaix/mm/pmm.h index b5dcb97..bd55eb1 100644 --- a/lunaix-os/includes/lunaix/mm/pmm.h +++ b/lunaix-os/includes/lunaix/mm/pmm.h @@ -87,7 +87,7 @@ ppage_addr(struct ppage* page) { static inline unsigned int count_order(size_t page_count) { - unsigned int po = ILOG2(page_count); + unsigned int po = ilog2(page_count); assert(!(page_count % (1 << po))); return po; } diff --git a/lunaix-os/includes/lunaix/owloysius.h b/lunaix-os/includes/lunaix/owloysius.h index c1f8584..22311bb 100644 --- a/lunaix-os/includes/lunaix/owloysius.h +++ b/lunaix-os/includes/lunaix/owloysius.h @@ -3,8 +3,24 @@ #include +/** + * @brief stage where only basic memory management service + * is present + */ #define on_earlyboot c_earlyboot + +/** + * @brief stage where most kernel service is ready, non-preempt + * kernel. + * + * boot-stage initialization is about to conclude. + */ #define on_boot c_boot + +/** + * @brief stage where all services started, kernel is in preempt + * state + */ #define on_postboot c_postboot #define owloysius_fetch_init(func, call_stage) \ diff --git a/lunaix-os/includes/lunaix/process.h b/lunaix-os/includes/lunaix/process.h index 92b9407..faca284 100644 --- a/lunaix-os/includes/lunaix/process.h +++ b/lunaix-os/includes/lunaix/process.h @@ -50,11 +50,17 @@ #define proc_runnable(proc) (!(proc)->state || !(((proc)->state) & ~PS_Rn)) -#define TH_DETACHED 0b0001 +#define TH_DETACHED 0b00000001 +#define TH_STALLED 0b00000010 #define thread_detached(th) ((th)->flags & TH_DETACHED) #define detach_thread(th) ((th)->flags |= TH_DETACHED) +#define thread_flags_set(th, flag) ((th)->flags |= (flag)) +#define thread_flags_clear(th, flag) ((th)->flags &= ~(flag)) +#define thread_flags_test(th, flag) ((th)->flags & (flag)) +#define thread_flags_test_all(th, flag) (((th)->flags & (flag)) == (flag)) + struct proc_sig { int sig_num; @@ -72,6 +78,34 @@ struct haybed { time_t alarm_time; }; +struct thread_stats +{ + // number of times the thread entering kernel space involuntarily + unsigned long entry_count_invol; + // number of times the thread entering kernel space voluntarily + unsigned long entry_count_vol; + + // number of times the thread is preempted in kerenl space + unsigned long kpreempt_count; + + // timestamp of last time kernel entry + time_t last_entry; + // timestamp of last time kernel reentry + time_t last_reentry; + + // timestamp of last time kernel leave + time_t last_leave; + // timestamp of last time the thread is resumed + time_t last_resume; + + union { + struct { + bool at_user; + }; + int flags; + }; +}; + struct thread { /* @@ -99,6 +133,8 @@ struct thread struct mm_region* ustack; // process local user stack (NULL for kernel thread) }; + struct thread_stats stats; + struct haybed sleep; struct proc_info* process; @@ -381,5 +417,47 @@ proc_setsignal(struct proc_info* proc, signum_t signum); void thread_setsignal(struct thread* thread, signum_t signum); +void +thread_stats_update(bool inbound, bool voluntary); + +static inline void +thread_stats_update_entering(bool voluntary) +{ + thread_stats_update(true, voluntary); +} + +static inline void +thread_stats_update_leaving() +{ + thread_stats_update(false, true); +} + +static inline void +thread_stats_update_kpreempt() +{ + current_thread->stats.kpreempt_count++; +} + +static inline void +thread_stats_reset_kpreempt() +{ + current_thread->stats.kpreempt_count = 0; +} + +static inline ticks_t +thread_stats_kernel_elapse(struct thread* thread) +{ + return clock_systime() - thread->stats.last_reentry; +} + +static inline ticks_t +thread_stats_user_elapse(struct thread* thread) +{ + struct thread_stats* stats; + stats = &thread->stats; + + return stats->last_entry - stats->last_leave; +} + #endif /* __LUNAIX_PROCESS_H */ diff --git a/lunaix-os/includes/lunaix/sched.h b/lunaix-os/includes/lunaix/sched.h index c011adb..4264307 100644 --- a/lunaix-os/includes/lunaix/sched.h +++ b/lunaix-os/includes/lunaix/sched.h @@ -28,9 +28,6 @@ sched_init(); void noret schedule(); -void -sched_pass(); - void noret run(struct thread* thread); diff --git a/lunaix-os/includes/lunaix/spike.h b/lunaix-os/includes/lunaix/spike.h index e35cfdc..8abd8b0 100644 --- a/lunaix-os/includes/lunaix/spike.h +++ b/lunaix-os/includes/lunaix/spike.h @@ -30,7 +30,7 @@ * https://elixir.bootlin.com/linux/v4.4/source/include/linux/log2.h#L85 * */ -#define ILOG2(x) \ +#define ilog2(x) \ (__builtin_constant_p(x) ? ((x) == 0 ? 0 \ : ((x) & (1ul << 31)) ? 31 \ : ((x) & (1ul << 30)) ? 30 \ @@ -64,23 +64,39 @@ : ((x) & (1ul << 2)) ? 2 \ : ((x) & (1ul << 1)) ? 1 \ : 0) \ - : (31 - clz(x))) + : (msbiti - clz(x))) + +#define llog2(x) (msbitl - clzl(x)) #ifndef CONFIG_NO_ASSERT #define assert(cond) \ do { \ - if (unlikely(!(cond))) { \ + if (unlikely(!(cond))) { \ __assert_fail(#cond, __FILE__, __LINE__); \ } \ } while(0) +#define assert_p(cond, prefix) \ + do { \ + if (unlikely(!(cond))) { \ + __assert_fail(prefix ": " #cond, __FILE__, __LINE__); \ + } \ + } while(0) + #define assert_msg(cond, msg) \ do { \ - if (unlikely(!(cond))) { \ + if (unlikely(!(cond))) { \ __assert_fail(msg, __FILE__, __LINE__); \ } \ } while(0) +#define assert_msg_p(cond, prefix, msg) \ + do { \ + if (unlikely(!(cond))) { \ + __assert_fail(prefix ":" msg, __FILE__, __LINE__); \ + } \ + } while(0) + #define must_success(statement) \ do { \ int err = (statement); \ @@ -88,6 +104,7 @@ } while(0) #define fail(msg) __assert_fail(msg, __FILE__, __LINE__); +#define fail_p(msg, prefix) fail(prefix msg); void __assert_fail(const char* expr, const char* file, unsigned int line) diff --git a/lunaix-os/includes/lunaix/switch.h b/lunaix-os/includes/lunaix/switch.h new file mode 100644 index 0000000..4daca4d --- /dev/null +++ b/lunaix-os/includes/lunaix/switch.h @@ -0,0 +1,60 @@ +#ifndef __LUNAIX_SWITCH_H +#define __LUNAIX_SWITCH_H + +#define SWITCH_MODE_NORMAL 0 +#define SWITCH_MODE_FAST 1 +#define SWITCH_MODE_GIVEUP 2 + +#ifndef __ASM__ + +#include + +struct signpost_result +{ + int mode; + ptr_t stack; +}; + +/** + * @brief Decide how current thread should perform the context switch + * back to it's previously saved context. + * + * Return a user stack pointer perform a temporary fast redirected + * context switch. + * No redirection is required if such pointer is null. + * + * This function might never return if the decision is made to give up + * this switching. + * + * NOTE: This function might have side effects, it can only be + * called within the twilight zone of context restore. (after entering + * do_switch and before returning from exception) + * + * @return ptr_t + */ +ptr_t +switch_signposting(); + +static inline void +redirect_switch(struct signpost_result* res, ptr_t stack) +{ + res->mode = SWITCH_MODE_FAST; + res->stack = stack; +} + +static inline void +continue_switch(struct signpost_result* res) +{ + res->mode = SWITCH_MODE_NORMAL; + res->stack = 0; +} + +static inline void +giveup_switch(struct signpost_result* res) +{ + res->mode = SWITCH_MODE_GIVEUP; + res->stack = 0; +} + +#endif +#endif /* __LUNAIX_SWITCH_H */ diff --git a/lunaix-os/includes/lunaix/syslog.h b/lunaix-os/includes/lunaix/syslog.h index 621c81a..3b827f6 100644 --- a/lunaix-os/includes/lunaix/syslog.h +++ b/lunaix-os/includes/lunaix/syslog.h @@ -29,6 +29,8 @@ va_end(args); \ } +#define printk(fmt, ...) kprintf_v(__FILE__, fmt, ##__VA_ARGS__) + #define DEBUG(fmt, ...) kprintf(KDEBUG fmt, ##__VA_ARGS__) #define INFO(fmt, ...) kprintf(KINFO fmt, ##__VA_ARGS__) #define WARN(fmt, ...) kprintf(KWARN fmt, ##__VA_ARGS__) @@ -41,4 +43,7 @@ void kprintf_m(const char* component, const char* fmt, va_list args); + +void +kprintf_v(const char* component, const char* fmt, ...); #endif /* __LUNAIX_SYSLOG_H */ diff --git a/lunaix-os/includes/lunaix/time.h b/lunaix-os/includes/lunaix/time.h index 2b2c70a..04d9f53 100644 --- a/lunaix-os/includes/lunaix/time.h +++ b/lunaix-os/includes/lunaix/time.h @@ -22,6 +22,24 @@ typedef struct u8_t second; } datetime_t; +static inline ticks_t +ticks_seconds(unsigned int seconds) +{ + return seconds * 1000; +} + +static inline ticks_t +ticks_minutes(unsigned int min) +{ + return ticks_seconds(min * 60); +} + +static inline ticks_t +ticks_msecs(unsigned int ms) +{ + return ms; +} + static inline time_t datetime_tounix(datetime_t* dt) { diff --git a/lunaix-os/includes/lunaix/types.h b/lunaix-os/includes/lunaix/types.h index 52faac3..f74c9b8 100644 --- a/lunaix-os/includes/lunaix/types.h +++ b/lunaix-os/includes/lunaix/types.h @@ -43,9 +43,12 @@ typedef int bool; #define container_of(ptr, type, member) \ ({ \ const typeof(((type*)0)->member)* __mptr = (ptr); \ - (ptr) ? (type*)((char*)__mptr - offsetof(type, member)) : 0; \ + ((ptr_t)ptr != 0UL) ? (type*)((char*)__mptr - offsetof(type, member)) : 0; \ }) +#define offset(data, off) \ + ((void*)(__ptr(data) + (off))) + #define __ptr(val) ((ptr_t)(val)) typedef va_list* sc_va_list; diff --git a/lunaix-os/includes/usr/lunaix/fcntl_defs.h b/lunaix-os/includes/usr/lunaix/fcntl_defs.h index 7659a32..186f5f4 100644 --- a/lunaix-os/includes/usr/lunaix/fcntl_defs.h +++ b/lunaix-os/includes/usr/lunaix/fcntl_defs.h @@ -4,12 +4,13 @@ #include "fstypes.h" #include "types.h" -#define FO_CREATE 0x1 -#define FO_APPEND 0x2 -#define FO_DIRECT 0x4 -#define FO_WRONLY 0x8 -#define FO_RDONLY 0x10 -#define FO_RDWR 0x20 +#define FO_CREATE 0x1 +#define FO_APPEND 0x2 +#define FO_DIRECT 0x4 +#define FO_WRONLY 0x8 +#define FO_RDONLY 0x10 +#define FO_RDWR 0x20 +#define FO_TRUNC 0x40 #define FO_NOFOLLOW 0x10000 @@ -23,8 +24,13 @@ #define O_WRONLY FO_WRONLY #define O_RDONLY FO_RDONLY #define O_RDWR FO_RDWR +#define O_TRUNC FO_TRUNC -#define MNT_RO 0x1 +/* Mount with read-only flag */ +#define MNT_RO (1 << 0) + +/* Mount with block-cache-disabled flag */ +#define MNT_NC (1 << 1) struct file_stat { diff --git a/lunaix-os/includes/usr/lunaix/fstypes.h b/lunaix-os/includes/usr/lunaix/fstypes.h index 957ede0..680a822 100644 --- a/lunaix-os/includes/usr/lunaix/fstypes.h +++ b/lunaix-os/includes/usr/lunaix/fstypes.h @@ -1,15 +1,23 @@ #ifndef __LUNAIX_FSTYPES_H #define __LUNAIX_FSTYPES_H -#define F_DIR 0x0 -#define F_FILE 0x1 -#define F_DEV 0x2 -#define F_SEQDEV 0x6 -#define F_VOLDEV 0xa -#define F_SYMLINK 0x10 +/* + 7 6 5 4 3 2 1 0 + * * s P SV D d f + | | | | | |_ file + | | | | |___ directory + | | | |_____ Device + | | |_________ Seq/Vol (0: Seq; 1: Vol) + | |___________ Pipe + |_____________ symlink + +*/ -#define F_MFILE 0b00001 -#define F_MDEV 0b01110 -#define F_MSLNK 0b10000 +#define F_FILE 0b00000001 +#define F_DIR 0b00000010 +#define F_DEV 0b00000100 +#define F_SVDEV 0b00001000 +#define F_PIPE 0b00010000 +#define F_SYMLINK 0b00100000 #endif /* __LUNAIX_FSTYPES_H */ diff --git a/lunaix-os/includes/usr/lunaix/status.h b/lunaix-os/includes/usr/lunaix/status.h index 479d844..df37b61 100644 --- a/lunaix-os/includes/usr/lunaix/status.h +++ b/lunaix-os/includes/usr/lunaix/status.h @@ -35,5 +35,6 @@ #define ELIBBAD -29 #define EAGAIN -30 #define EDEADLK -31 +#define EDQUOT -32 #endif /* __LUNAIX_STATUS_H */ diff --git a/lunaix-os/kernel/LBuild b/lunaix-os/kernel/LBuild index 600b512..a71dd8b 100644 --- a/lunaix-os/kernel/LBuild +++ b/lunaix-os/kernel/LBuild @@ -13,8 +13,11 @@ sources([ "kinit.c", "lunad.c", "spike.c", + "lrud.c", + "bcache.c", + "syscall.c", "kprint/kp_records.c", "kprint/kprintf.c", "time/clock.c", - "time/timer.c" + "time/timer.c", ]) \ No newline at end of file diff --git a/lunaix-os/kernel/LConfig b/lunaix-os/kernel/LConfig index 37b9005..a46e85f 100644 --- a/lunaix-os/kernel/LConfig +++ b/lunaix-os/kernel/LConfig @@ -3,4 +3,5 @@ def kernel_feature(): """ Config kernel features """ pass -include("mm") \ No newline at end of file +include("fs") +include("mm") diff --git a/lunaix-os/kernel/bcache.c b/lunaix-os/kernel/bcache.c new file mode 100644 index 0000000..3190374 --- /dev/null +++ b/lunaix-os/kernel/bcache.c @@ -0,0 +1,215 @@ +#include +#include +#include + +static struct lru_zone* bcache_global_lru; + +#define lock(bc) spinlock_acquire(&((bc)->lock)) +#define unlock(bc) spinlock_release(&((bc)->lock)) + +static void +__evict_internal_locked(struct bcache_node* node) +{ + struct bcache* cache; + + cache = node->holder; + cache->ops.sync_cached(cache, node->tag, node->data); + + cache->ops.release_on_evict(cache, node->data); +} + +static int +__try_evict_bcache(struct lru_node* node) +{ + struct bcache_node* bnode; + struct bcache* cache; + + bnode = container_of(node, struct bcache_node, lru_node); + cache = bnode->holder; + + lock(cache); + + if (bnode->refs) { + unlock(cache); + return false; + } + + __evict_internal_locked(bnode); + btrie_remove(&cache->root, bnode->tag); + llist_delete(&bnode->objs); + + vfree(bnode); + + unlock(cache); + + return true; +} + +bcache_zone_t +bcache_create_zone(char* name) +{ + return lru_new_zone(name, __try_evict_bcache); +} + +void +bcache_init_zone(struct bcache* cache, bcache_zone_t lru, unsigned int log_ways, + int cap, unsigned int blk_size, struct bcache_ops* ops) +{ + // TODO handle cap + + *cache = (struct bcache) { + .lru = lru, + .ops = *ops, + .blksz = blk_size + }; + + btrie_init(&cache->root, log_ways); + llist_init_head(&cache->objs); + spinlock_init(&cache->lock); +} + +bcobj_t +bcache_put_and_ref(struct bcache* cache, unsigned long tag, void* block) +{ + struct bcache_node* node; + + lock(cache); + + node = (struct bcache_node*)btrie_get(&cache->root, tag); + + if (node != NULL) { + assert(!node->refs); + __evict_internal_locked(node); + // Now the node is ready to be reused. + } + else { + node = vzalloc(sizeof(*node)); + btrie_set(&cache->root, tag, node); + } + + *node = (struct bcache_node) { + .data = block, + .holder = cache, + .tag = tag, + .refs = 1 + }; + + lru_use_one(cache->lru, &node->lru_node); + llist_append(&cache->objs, &node->objs); + + unlock(cache); + + return (bcobj_t)node; +} + +bool +bcache_tryget(struct bcache* cache, unsigned long tag, bcobj_t* result) +{ + struct bcache_node* node; + + lock(cache); + + node = (struct bcache_node*)btrie_get(&cache->root, tag); + if (!node) { + unlock(cache); + *result = NULL; + + return false; + } + + node->refs++; + + *result = (bcobj_t)node; + + unlock(cache); + + return true; +} + +void +bcache_return(bcobj_t obj) +{ + struct bcache_node* node = (struct bcache_node*) obj; + + assert(node->refs); + + // non bisogno bloccare il cache, perche il lru ha la serratura propria. + lru_use_one(node->holder->lru, &node->lru_node); + node->refs--; +} + +void +bcache_promote(bcobj_t obj) +{ + struct bcache_node* node; + + node = (struct bcache_node*) obj; + assert(node->refs); + lru_use_one(node->holder->lru, &node->lru_node); +} + +void +bcache_evict(struct bcache* cache, unsigned long tag) +{ + struct bcache_node* node; + + lock(cache); + + node = (struct bcache_node*)btrie_get(&cache->root, tag); + + if (!node || node->refs) { + unlock(cache); + return; + } + + __evict_internal_locked(node); + + btrie_remove(&cache->root, tag); + lru_remove(cache->lru, &node->lru_node); + llist_delete(&node->objs); + + vfree(node); + + unlock(cache); +} + +static void +bcache_flush_locked(struct bcache* cache) +{ + struct bcache_node *pos, *n; + llist_for_each(pos, n, &cache->objs, objs) { + __evict_internal_locked(pos); + btrie_remove(&cache->root, pos->tag); + lru_remove(cache->lru, &pos->lru_node); + llist_delete(&pos->objs); + } +} + +void +bcache_flush(struct bcache* cache) +{ + lock(cache); + + bcache_flush_locked(cache); + + unlock(cache); +} + +void +bcache_free(struct bcache* cache) +{ + lock(cache); + + bcache_flush_locked(cache); + btrie_release(&cache->root); + + unlock(cache); + + vfree(cache); +} + +void +bcache_zone_free(bcache_zone_t zone) +{ + lru_free_zone(zone); +} \ No newline at end of file diff --git a/lunaix-os/kernel/block/LBuild b/lunaix-os/kernel/block/LBuild index 252a19e..10586c5 100644 --- a/lunaix-os/kernel/block/LBuild +++ b/lunaix-os/kernel/block/LBuild @@ -2,5 +2,6 @@ sources([ "blkpart_gpt.c", "blk_mapping.c", "blkio.c", - "block.c" + "block.c", + "blkbuf.c" ]) \ No newline at end of file diff --git a/lunaix-os/kernel/block/blkbuf.c b/lunaix-os/kernel/block/blkbuf.c new file mode 100644 index 0000000..4f255c9 --- /dev/null +++ b/lunaix-os/kernel/block/blkbuf.c @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include + +LOG_MODULE("blkbuf") + +#define bb_cache_obj(bcache) \ + container_of(bcache, struct blkbuf_cache, cached) + +#define to_blkbuf(bbuf) ((struct blk_buf*)(bbuf)) + +static bcache_zone_t bb_zone; +static struct cake_pile* bb_pile; + +static inline u64_t +__tolba(struct blkbuf_cache* cache, unsigned int blk_id) +{ + return ((u64_t)cache->blksize * (u64_t)blk_id) / cache->blkdev->blk_size; +} + +static void +__blkbuf_do_sync(struct bcache* bc, unsigned long tag, void* data) +{ + return; +} + +static void +__blkbuf_sync_callback(struct blkio_req* req) +{ + struct blk_buf* buf; + + buf = (struct blk_buf*)req->evt_args; + + if (req->errcode) { + ERROR("sync failed: io error, 0x%x", req->errcode); + return; + } +} + +static void +__blkbuf_evict_callback(struct blkio_req* req) +{ + struct blk_buf* buf; + + buf = (struct blk_buf*)req->evt_args; + + if (req->errcode) { + ERROR("sync on evict failed (io error, 0x%x)", req->errcode); + } + + vfree(buf->raw); + vbuf_free(req->vbuf); + cake_release(bb_pile, buf); +} + +static void +__blkbuf_do_try_release(struct bcache* bc, void* data) +{ + struct blkio_req* req; + struct blk_buf* buf; + + buf = (struct blk_buf*)data; + req = buf->breq; + + if (llist_empty(&buf->dirty)) { + __blkbuf_evict_callback(req); + blkio_free_req(req); + return; + } + + // since we are evicting, don't care if the sync is failed + llist_delete(&buf->dirty); + + blkio_when_completed(req, __blkbuf_evict_callback); + blkio_mark_foc(req); + blkio_commit(req, 0); +} + +static struct bcache_ops cache_ops = { + .release_on_evict = __blkbuf_do_try_release, + .sync_cached = __blkbuf_do_sync +}; + +static bbuf_t +__blkbuf_take_slow_lockness(struct blkbuf_cache* bc, unsigned int block_id) +{ + struct blk_buf* buf; + struct blkio_req* req; + struct vecbuf* vbuf; + void* data; + u64_t lba; + + data = valloc(bc->blksize); + + vbuf = NULL; + vbuf_alloc(&vbuf, data, bc->blksize); + + lba = __tolba(bc, block_id); + buf = (struct blk_buf*)cake_grab(bb_pile); + req = blkio_vreq(vbuf, lba, __blkbuf_sync_callback, buf, 0); + + // give dirty a know state + llist_init_head(&buf->dirty); + + blkio_setread(req); + blkio_bindctx(req, bc->blkdev->blkio); + blkio_commit(req, BLKIO_WAIT); + + if (req->errcode) { + ERROR("block io error (0x%x)", req->errcode); + cake_release(bb_pile, buf); + return (bbuf_t)INVL_BUFFER; + } + + buf->raw = data; + buf->cobj = bcache_put_and_ref(&bc->cached, block_id, buf); + buf->breq = req; + + return buf; +} + +struct blkbuf_cache* +blkbuf_create(struct block_dev* blkdev, unsigned int blk_size) +{ + struct blkbuf_cache* bb_cache; + + assert(is_pot(blk_size)); + + bb_cache = valloc(sizeof(*bb_cache)); + bb_cache->blkdev = blkdev; + + bcache_init_zone(&bb_cache->cached, bb_zone, 3, -1, blk_size, &cache_ops); + llist_init_head(&bb_cache->dirty); + mutex_init(&bb_cache->lock); + + return bb_cache; +} + +bbuf_t +blkbuf_take(struct blkbuf_cache* bc, unsigned int block_id) +{ + bcobj_t cobj; + mutex_lock(&bc->lock); + if (bcache_tryget(&bc->cached, block_id, &cobj)) { + mutex_unlock(&bc->lock); + return (bbuf_t)bcached_data(cobj); + } + + bbuf_t buf = __blkbuf_take_slow_lockness(bc, block_id); + + mutex_unlock(&bc->lock); + return buf; +} + +void +blkbuf_put(bbuf_t buf) +{ + if (unlikely(!buf || blkbuf_errbuf(buf))) { + return; + } + + struct blk_buf* bbuf; + bbuf = to_blkbuf(buf); + + bcache_return(bbuf->cobj); +} + +void +blkbuf_dirty(bbuf_t buf) +{ + assert(buf && !blkbuf_errbuf(buf)); + + struct blk_buf* bbuf; + struct blkbuf_cache* bc; + + bbuf = ((struct blk_buf*)buf); + bc = bcache_holder_embed(bbuf->cobj, struct blkbuf_cache, cached); + + mutex_lock(&bc->lock); + + if (llist_empty(&bbuf->dirty)) { + llist_append(&bc->dirty, &bbuf->dirty); + } + + mutex_unlock(&bc->lock); +} + +static inline void +__schedule_sync_event(struct blk_buf* bbuf, bool wait) +{ + struct blkio_req* blkio; + + blkio = bbuf->breq; + + blkio_setwrite(blkio); + blkio_commit(blkio, wait ? BLKIO_WAIT : BLKIO_NOWAIT); + + llist_delete(&bbuf->dirty); +} + +void +blkbuf_schedule_sync(bbuf_t buf) +{ + struct blk_buf* bbuf; + bbuf = to_blkbuf(buf); + + __schedule_sync_event(bbuf, false); +} + +bool +blkbuf_syncall(struct blkbuf_cache* bc, bool async) +{ + struct blk_buf *pos, *n; + + mutex_lock(&bc->lock); + + llist_for_each(pos, n, &bc->dirty, dirty) { + __schedule_sync_event(pos, !async); + } + + mutex_unlock(&bc->lock); + + if (async) { + return true; + } + + return llist_empty(&bc->dirty); +} + +void +blkbuf_release(struct blkbuf_cache* bc) +{ + bcache_free(&bc->cached); + vfree(bc); +} + +static void +__init_blkbuf() +{ + bb_zone = bcache_create_zone("blk_buf"); + bb_pile = cake_new_pile("blk_buf", sizeof(struct blk_buf), 1, 0); +} +owloysius_fetch_init(__init_blkbuf, on_earlyboot) \ No newline at end of file diff --git a/lunaix-os/kernel/block/blkio.c b/lunaix-os/kernel/block/blkio.c index 9fcd670..4810925 100644 --- a/lunaix-os/kernel/block/blkio.c +++ b/lunaix-os/kernel/block/blkio.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -6,6 +7,8 @@ static struct cake_pile* blkio_reqpile; +LOG_MODULE("blkio") + void blkio_init() { @@ -67,17 +70,40 @@ blkio_newctx(req_handler handler) ctx->handle_one = handler; llist_init_head(&ctx->queue); + mutex_init(&ctx->lock); return ctx; } void -blkio_commit(struct blkio_context* ctx, struct blkio_req* req, int options) +blkio_commit(struct blkio_req* req, int options) { + struct blkio_context* ctx; + + if (blkio_is_pending(req)) { + // prevent double submition + return; + } + + assert(req->io_ctx); + req->flags |= BLKIO_PENDING; - req->io_ctx = ctx; - llist_append(&ctx->queue, &req->reqs); + + if ((options & BLKIO_WAIT)) { + req->flags |= BLKIO_SHOULD_WAIT; + prepare_to_wait(&req->wait); + } + else { + req->flags &= ~BLKIO_SHOULD_WAIT; + } + ctx = req->io_ctx; + + blkio_lock(ctx); + + llist_append(&ctx->queue, &req->reqs); + + blkio_unlock(ctx); // if the pipeline is not running (e.g., stalling). Then we should schedule // one immediately and kick it started. // NOTE: Possible race condition between blkio_commit and pwait. @@ -89,23 +115,43 @@ blkio_commit(struct blkio_context* ctx, struct blkio_req* req, int options) // As we don't want to overwhelming the interrupt context and also keep the // request RTT as small as possible, hence #1 is preferred. - if (!ctx->busy) { + /* + FIXME + Potential racing here. + happened when blkio is committed at high volumn, while the + block device has very little latency. + This is particular serious for non-async blkio, it could + completed before we do pwait, causing the thread hanged indefinitely + */ + + if (blkio_stalled(ctx)) { if ((options & BLKIO_WAIT)) { - cpu_disable_interrupt(); blkio_schedule(ctx); - pwait(&req->wait); + try_wait_check_stall(); return; } blkio_schedule(ctx); } else if ((options & BLKIO_WAIT)) { - pwait(&req->wait); + try_wait_check_stall(); } } void blkio_schedule(struct blkio_context* ctx) { + // stall the pipeline if ctx is locked by others. + // we must not try to hold the lock in this case, as + // blkio_schedule will be in irq context most of the + // time, we can't afford the waiting there. + if (mutex_on_hold(&ctx->lock)) { + return; + } + + // will always successed when in irq context + blkio_lock(ctx); + if (llist_empty(&ctx->queue)) { + blkio_unlock(ctx); return; } @@ -113,7 +159,9 @@ blkio_schedule(struct blkio_context* ctx) llist_delete(&head->reqs); head->flags |= BLKIO_BUSY; - head->io_ctx->busy++; + ctx->busy++; + + blkio_unlock(ctx); ctx->handle_one(head); } @@ -121,19 +169,30 @@ blkio_schedule(struct blkio_context* ctx) void blkio_complete(struct blkio_req* req) { + struct blkio_context* ctx; + + ctx = req->io_ctx; req->flags &= ~(BLKIO_BUSY | BLKIO_PENDING); + // Wake all blocked processes on completion, + // albeit should be no more than one process in everycase (by design) + if ((req->flags & BLKIO_SHOULD_WAIT)) { + assert(!waitq_empty(&req->wait)); + pwake_all(&req->wait); + } + + if (req->errcode) { + WARN("request completed with error. (errno=0x%x, ctx=%p)", + req->errcode, (ptr_t)ctx); + } + if (req->completed) { req->completed(req); } - // Wake all blocked processes on completion, - // albeit should be no more than one process in everycase (by design) - pwake_all(&req->wait); - if ((req->flags & BLKIO_FOC)) { blkio_free_req(req); } - req->io_ctx->busy--; + ctx->busy--; } \ No newline at end of file diff --git a/lunaix-os/kernel/block/block.c b/lunaix-os/kernel/block/block.c index b620b2f..73fc9db 100644 --- a/lunaix-os/kernel/block/block.c +++ b/lunaix-os/kernel/block/block.c @@ -51,7 +51,10 @@ static int __block_commit(struct blkio_context* blkio, struct blkio_req* req, int flags) { int errno; - blkio_commit(blkio, req, flags); + + blkio_bindctx(req, blkio); + blkio_mark_nfoc(req); + blkio_commit(req, flags); if ((errno = req->errcode)) { errno = -errno; @@ -66,8 +69,9 @@ int __block_read(struct device* dev, void* buf, size_t offset, size_t len) { int errno; - struct block_dev* bdev = (struct block_dev*)dev->underlay; - size_t bsize = bdev->blk_size, rd_block = offset / bsize + bdev->start_lba, + struct block_dev* bdev = block_dev(dev); + size_t bsize = bdev->blk_size, + rd_block = offset / bsize + bdev->start_lba, r = offset % bsize, rd_size = 0; if (!(len = MIN(len, ((size_t)bdev->end_lba - rd_block + 1) * bsize))) { @@ -109,7 +113,7 @@ __block_read(struct device* dev, void* buf, size_t offset, size_t len) int __block_write(struct device* dev, void* buf, size_t offset, size_t len) { - struct block_dev* bdev = (struct block_dev*)dev->underlay; + struct block_dev* bdev = block_dev(dev); size_t bsize = bdev->blk_size, wr_block = offset / bsize + bdev->start_lba, r = offset % bsize, wr_size = 0; @@ -153,7 +157,7 @@ int __block_read_page(struct device* dev, void* buf, size_t offset) { struct vecbuf* vbuf = NULL; - struct block_dev* bdev = (struct block_dev*)dev->underlay; + struct block_dev* bdev = block_dev(dev); u32_t lba = offset / bdev->blk_size + bdev->start_lba; u32_t rd_lba = MIN(lba + PAGE_SIZE / bdev->blk_size, bdev->end_lba); @@ -180,7 +184,7 @@ int __block_write_page(struct device* dev, void* buf, size_t offset) { struct vecbuf* vbuf = NULL; - struct block_dev* bdev = (struct block_dev*)dev->underlay; + struct block_dev* bdev = block_dev(dev); u32_t lba = offset / bdev->blk_size + bdev->start_lba; u32_t wr_lba = MIN(lba + PAGE_SIZE / bdev->blk_size, bdev->end_lba); diff --git a/lunaix-os/kernel/device/devfs.c b/lunaix-os/kernel/device/devfs.c index 350bbd6..28f923a 100644 --- a/lunaix-os/kernel/device/devfs.c +++ b/lunaix-os/kernel/device/devfs.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -67,7 +67,7 @@ devfs_write_page(struct v_inode* inode, void* buffer, size_t fpos) int devfs_get_itype(struct device_meta* dm) { - int itype = VFS_IFFILE; + int itype = VFS_IFDEV; if (valid_device_subtype_ref(dm, DEV_CAT)) { return VFS_IFDIR; @@ -78,11 +78,9 @@ devfs_get_itype(struct device_meta* dm) if (dev_if == DEV_IFVOL) { itype |= VFS_IFVOLDEV; - } else if (dev_if == DEV_IFSEQ) { - itype |= VFS_IFSEQDEV; - } else { - itype |= VFS_IFDEV; } + + // otherwise, the mapping is considered to be generic seq dev. return itype; } @@ -147,8 +145,12 @@ devfs_readdir(struct v_file* file, struct dir_context* dctx) return ENOTDIR; } + if (fsapi_handle_pseudo_dirent(file, dctx)) { + return 1; + } + struct device_meta* dev = - device_getbyoffset(rootdev, dctx->index); + device_getbyoffset(rootdev, file->f_pos - 2); if (!dev) { return 0; @@ -195,10 +197,11 @@ devfs_unmount(struct v_superblock* vsb) void devfs_init() { - struct filesystem* fs = fsm_new_fs("devfs", 5); - fsm_register(fs); - fs->mount = devfs_mount; - fs->unmount = devfs_unmount; + struct filesystem* fs; + fs = fsapi_fs_declare("devfs", FSTYPE_PSEUDO); + + fsapi_fs_set_mntops(fs, devfs_mount, devfs_unmount); + fsapi_fs_finalise(fs); } EXPORT_FILE_SYSTEM(devfs, devfs_init); diff --git a/lunaix-os/kernel/device/poll.c b/lunaix-os/kernel/device/poll.c index 493d5d3..159c2bb 100644 --- a/lunaix-os/kernel/device/poll.c +++ b/lunaix-os/kernel/device/poll.c @@ -7,6 +7,7 @@ #include #include #include +#include #define MAX_POLLER_COUNT 16 @@ -153,7 +154,7 @@ static void __wait_until_event() { block_current_thread(); - sched_pass(); + yield_current(); } void diff --git a/lunaix-os/kernel/ds/LBuild b/lunaix-os/kernel/ds/LBuild index e87f3d1..a5c082f 100644 --- a/lunaix-os/kernel/ds/LBuild +++ b/lunaix-os/kernel/ds/LBuild @@ -1,7 +1,6 @@ sources([ "waitq.c", "buffer.c", - "lru.c", "rbuffer.c", "btrie.c", "semaphore.c", diff --git a/lunaix-os/kernel/ds/btrie.c b/lunaix-os/kernel/ds/btrie.c index 781def4..72758fb 100644 --- a/lunaix-os/kernel/ds/btrie.c +++ b/lunaix-os/kernel/ds/btrie.c @@ -16,14 +16,17 @@ #define BTRIE_INSERT 1 struct btrie_node* -__btrie_traversal(struct btrie* root, u32_t index, int options) +__btrie_traversal(struct btrie* root, unsigned long index, int options) { - index = index >> root->truncated; - u32_t lz = index ? ROUNDDOWN(31 - clz(index), BTRIE_BITS) : 0; - u32_t bitmask = ((1 << BTRIE_BITS) - 1) << lz; - u32_t i = 0; + unsigned long lz; + unsigned long bitmask; + unsigned long i = 0; struct btrie_node* tree = root->btrie_root; + lz = index ? ICEIL(msbitl - clzl(index), root->order) : 0; + lz = lz * root->order; + bitmask = ((1 << root->order) - 1) << lz; + // Time complexity: O(log_2(log_2(N))) where N is the index to lookup while (bitmask && tree) { i = (index & bitmask) >> lz; @@ -49,24 +52,24 @@ __btrie_traversal(struct btrie* root, u32_t index, int options) } else { tree = subtree; } - bitmask = bitmask >> BTRIE_BITS; - lz -= BTRIE_BITS; + bitmask = bitmask >> root->order; + lz -= root->order; } return tree; } void -btrie_init(struct btrie* btrie, u32_t trunc_bits) +btrie_init(struct btrie* btrie, unsigned int order) { btrie->btrie_root = vzalloc(sizeof(struct btrie_node)); llist_init_head(&btrie->btrie_root->nodes); llist_init_head(&btrie->btrie_root->children); - btrie->truncated = trunc_bits; + btrie->order = order; } void* -btrie_get(struct btrie* root, u32_t index) +btrie_get(struct btrie* root, unsigned long index) { struct btrie_node* node = __btrie_traversal(root, index, 0); if (!node) { @@ -76,7 +79,7 @@ btrie_get(struct btrie* root, u32_t index) } void -btrie_set(struct btrie* root, u32_t index, void* data) +btrie_set(struct btrie* root, unsigned long index, void* data) { struct btrie_node* node = __btrie_traversal(root, index, BTRIE_INSERT); node->data = data; @@ -98,7 +101,7 @@ __btrie_rm_recursive(struct btrie_node* node) } void* -btrie_remove(struct btrie* root, u32_t index) +btrie_remove(struct btrie* root, unsigned long index) { struct btrie_node* node = __btrie_traversal(root, index, 0); if (!node) { diff --git a/lunaix-os/kernel/ds/lru.c b/lunaix-os/kernel/ds/lru.c deleted file mode 100644 index 08083b1..0000000 --- a/lunaix-os/kernel/ds/lru.c +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include - -struct llist_header zone_lead = { .next = &zone_lead, .prev = &zone_lead }; - -struct lru_zone* -lru_new_zone(evict_cb try_evict_cb) -{ - struct lru_zone* zone = vzalloc(sizeof(struct lru_zone)); - if (!zone) { - return NULL; - } - - zone->try_evict = try_evict_cb; - - llist_init_head(&zone->lead_node); - llist_append(&zone_lead, &zone->zones); - - return zone; -} - -void -lru_use_one(struct lru_zone* zone, struct lru_node* node) -{ - if (node->lru_nodes.next && node->lru_nodes.prev) { - llist_delete(&node->lru_nodes); - } - - llist_prepend(&zone->lead_node, &node->lru_nodes); - zone->objects++; -} - -static void -__do_evict(struct lru_zone* zone, struct llist_header* elem) -{ - llist_delete(elem); - if (!zone->try_evict(container_of(elem, struct lru_node, lru_nodes))) { - llist_append(&zone->lead_node, elem); - } else { - zone->objects--; - } -} - -void -lru_evict_one(struct lru_zone* zone) -{ - struct llist_header* tail = zone->lead_node.prev; - if (tail == &zone->lead_node) { - return; - } - - __do_evict(zone, tail); -} - -void -lru_evict_half(struct lru_zone* zone) -{ - int target = (int)(zone->objects / 2); - struct llist_header* tail = zone->lead_node.prev; - while (tail != &zone->lead_node && target > 0) { - __do_evict(zone, tail); - tail = tail->prev; - target--; - } -} - -void -lru_remove(struct lru_zone* zone, struct lru_node* node) -{ - if (node->lru_nodes.next && node->lru_nodes.prev) { - llist_delete(&node->lru_nodes); - } - zone->objects--; -} \ No newline at end of file diff --git a/lunaix-os/kernel/ds/mutex.c b/lunaix-os/kernel/ds/mutex.c index fd14df5..a27c8db 100644 --- a/lunaix-os/kernel/ds/mutex.c +++ b/lunaix-os/kernel/ds/mutex.c @@ -1,27 +1,41 @@ #include #include -#include +#include -void -mutex_lock(mutex_t* mutex) +static inline bool must_inline +__mutex_check_owner(mutex_t* mutex) { - if (atomic_load(&mutex->lk) && mutex->owner == __current->pid) { - atomic_fetch_add(&mutex->lk, 1); - return; - } + return mutex->owner == __current->pid; +} +static inline void must_inline +__mutext_lock(mutex_t* mutex) +{ while (atomic_load(&mutex->lk)) { - sched_pass(); + preempt_current(); } atomic_fetch_add(&mutex->lk, 1); mutex->owner = __current->pid; } +static inline void must_inline +__mutext_unlock(mutex_t* mutex) +{ + if (__mutex_check_owner(mutex)) + atomic_fetch_sub(&mutex->lk, 1); +} + +void +mutex_lock(mutex_t* mutex) +{ + __mutext_lock(mutex); +} + void mutex_unlock(mutex_t* mutex) { - mutex_unlock_for(mutex, __current->pid); + __mutext_unlock(mutex); } void @@ -30,5 +44,22 @@ mutex_unlock_for(mutex_t* mutex, pid_t pid) if (mutex->owner != pid || !atomic_load(&mutex->lk)) { return; } - atomic_fetch_sub(&mutex->lk, 1); + __mutext_unlock(mutex); +} + +void +mutex_lock_nested(mutex_t* mutex) +{ + if (atomic_load(&mutex->lk) && __mutex_check_owner(mutex)) { + atomic_fetch_add(&mutex->lk, 1); + return; + } + + __mutext_lock(mutex); +} + +void +mutex_unlock_nested(mutex_t* mutex) +{ + mutex_unlock_for(mutex, __current->pid); } \ No newline at end of file diff --git a/lunaix-os/kernel/ds/semaphore.c b/lunaix-os/kernel/ds/semaphore.c index b02266c..b4d0674 100644 --- a/lunaix-os/kernel/ds/semaphore.c +++ b/lunaix-os/kernel/ds/semaphore.c @@ -1,5 +1,5 @@ #include -#include +#include void sem_init(struct sem_t* sem, unsigned int initial) @@ -12,7 +12,7 @@ sem_wait(struct sem_t* sem) { while (!atomic_load(&sem->counter)) { // FIXME: better thing like wait queue - sched_pass(); + preempt_current(); } atomic_fetch_sub(&sem->counter, 1); } diff --git a/lunaix-os/kernel/ds/waitq.c b/lunaix-os/kernel/ds/waitq.c index 522974f..c102882 100644 --- a/lunaix-os/kernel/ds/waitq.c +++ b/lunaix-os/kernel/ds/waitq.c @@ -2,25 +2,53 @@ #include #include #include +#include -void -pwait(waitq_t* queue) +static inline void must_inline +__try_wait(bool check_stall) { - assert(current_thread); - // prevent race condition. - cpu_disable_interrupt(); - + unsigned int nstall; waitq_t* current_wq = ¤t_thread->waitqueue; - assert(llist_empty(¤t_wq->waiters)); - - llist_append(&queue->waiters, ¤t_wq->waiters); - + if (waitq_empty(current_wq)) { + return; + } + block_current_thread(); - sched_pass(); + + if (!check_stall) { + // if we are not checking stall, we give up voluntarily + yield_current(); + } else { + // otherwise, treat it as being preempted by kernel + preempt_current(); + } // In case of SIGINT-forced awaken llist_delete(¤t_wq->waiters); - cpu_enable_interrupt(); +} + +static inline void must_inline +__pwait(waitq_t* queue, bool check_stall) +{ + // prevent race condition. + no_preemption(); + + prepare_to_wait(queue); + __try_wait(check_stall); + + set_preemption(); +} + +void +pwait(waitq_t* queue) +{ + __pwait(queue, false); +} + +void +pwait_check_stall(waitq_t* queue) +{ + __pwait(queue, true); } void @@ -51,8 +79,34 @@ pwake_all(waitq_t* queue) { thread = container_of(pos, struct thread, waitqueue); - assert(thread->state == PS_BLOCKED); - thread->state = PS_READY; + if (thread->state == PS_BLOCKED) { + thread->state = PS_READY; + } + + // already awaken or killed by other event, just remove it llist_delete(&pos->waiters); } -} \ No newline at end of file +} + +void +prepare_to_wait(waitq_t* queue) +{ + assert(current_thread); + + waitq_t* current_wq = ¤t_thread->waitqueue; + assert(llist_empty(¤t_wq->waiters)); + + llist_append(&queue->waiters, ¤t_wq->waiters); +} + +void +try_wait() +{ + __try_wait(false); +} + +void +try_wait_check_stall() +{ + __try_wait(true); +} diff --git a/lunaix-os/kernel/fs/LBuild b/lunaix-os/kernel/fs/LBuild index 25e8446..b092f54 100644 --- a/lunaix-os/kernel/fs/LBuild +++ b/lunaix-os/kernel/fs/LBuild @@ -1,6 +1,11 @@ use("twifs") use("ramfs") -use("iso9660") + +if configured("fs_iso9660"): + use("iso9660") + +if configured("fs_ext2"): + use("ext2") sources([ "twimap.c", @@ -13,4 +18,5 @@ sources([ "fsm.c", "fs_export.c", "probe_boot.c" -]) \ No newline at end of file +]) + diff --git a/lunaix-os/kernel/fs/LConfig b/lunaix-os/kernel/fs/LConfig new file mode 100644 index 0000000..37a9695 --- /dev/null +++ b/lunaix-os/kernel/fs/LConfig @@ -0,0 +1,21 @@ + +@Collection +def file_system(): + """ Config feature related to file system supports """ + + add_to_collection(kernel_feature) + + @Term + def fs_ext2(): + """ Enable ext2 file system support """ + + type(bool) + default(True) + + @Term + def fs_iso9660(): + """ Enable iso9660 file system support """ + + type(bool) + default(True) + diff --git a/lunaix-os/kernel/fs/defaults.c b/lunaix-os/kernel/fs/defaults.c index 8512d8e..55b9dcd 100644 --- a/lunaix-os/kernel/fs/defaults.c +++ b/lunaix-os/kernel/fs/defaults.c @@ -1,4 +1,4 @@ -#include +#include int default_file_close(struct v_file* file) @@ -7,8 +7,9 @@ default_file_close(struct v_file* file) } int -default_file_seek(struct v_inode* inode, size_t offset) +default_file_seek(struct v_file* file, size_t offset) { + file->f_pos = offset; return 0; } @@ -45,19 +46,24 @@ default_file_write_page(struct v_inode* inode, void* buffer, size_t fpos) int default_file_readdir(struct v_file* file, struct dir_context* dctx) { - int i = 0; + unsigned int i = 0; struct v_dnode *pos, *n; + + if (fsapi_handle_pseudo_dirent(file, dctx)) { + return 1; + } + llist_for_each(pos, n, &file->dnode->children, siblings) { - if (i < dctx->index) { + if (i < file->f_pos) { i++; continue; } dctx->read_complete_callback(dctx, pos->name.value, pos->name.len, 0); - break; + return 1; } - return i; + return 0; } int diff --git a/lunaix-os/kernel/fs/ext2/LBuild b/lunaix-os/kernel/fs/ext2/LBuild new file mode 100644 index 0000000..a9700fe --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/LBuild @@ -0,0 +1,8 @@ +sources([ + "alloc.c", + "dir.c", + "file.c", + "group.c", + "inode.c", + "mount.c" +]) \ No newline at end of file diff --git a/lunaix-os/kernel/fs/ext2/alloc.c b/lunaix-os/kernel/fs/ext2/alloc.c new file mode 100644 index 0000000..2996141 --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/alloc.c @@ -0,0 +1,99 @@ +#include "ext2.h" + +static inline unsigned int +__ext2_global_slot_alloc(struct v_superblock* vsb, int type_sel, + struct ext2_gdesc** gd_out) +{ + struct ext2_sbinfo* sb; + struct ext2_gdesc *pos; + struct llist_header *header; + + sb = EXT2_SB(vsb); + header = &sb->free_list_sel[type_sel]; + + if (type_sel == GDESC_INO_SEL) { + pos = list_entry(header->next, struct ext2_gdesc, free_grps_ino); + } + else { + pos = list_entry(header->next, struct ext2_gdesc, free_grps_blk); + } + + int alloc = ext2gd_alloc_slot(pos, type_sel); + + if (valid_bmp_slot(alloc)) { + *gd_out = pos; + } + + return alloc; +} + +int +ext2ino_alloc_slot(struct v_superblock* vsb, struct ext2_gdesc** gd_out) +{ + return __ext2_global_slot_alloc(vsb, GDESC_INO_SEL, gd_out); +} + +int +ext2db_alloc_slot(struct v_superblock* vsb, struct ext2_gdesc** gd_out) +{ + return __ext2_global_slot_alloc(vsb, GDESC_BLK_SEL, gd_out); +} + +int +ext2gd_alloc_slot(struct ext2_gdesc* gd, int type_sel) +{ + struct ext2_bmp* bmp; + struct ext2_sbinfo *sb; + int alloc; + + sb = gd->sb; + bmp = &gd->bmps[type_sel]; + alloc = ext2bmp_alloc_one(bmp); + + if (alloc < 0) { + return alloc; + } + + if (!ext2bmp_check_free(bmp)) { + llist_delete(&gd->free_list_sel[type_sel]); + } + + if (type_sel == GDESC_INO_SEL) { + gd->info->bg_free_ino_cnt--; + sb->raw->s_free_ino_cnt--; + } else { + gd->info->bg_free_blk_cnt--; + sb->raw->s_free_blk_cnt--; + } + + fsblock_dirty(gd->buf); + fsblock_dirty(sb->buf); + return alloc; +} + +void +ext2gd_free_slot(struct ext2_gdesc* gd, int type_sel, int slot) +{ + struct llist_header *free_ent, *free_list; + struct ext2_sbinfo *sb; + + ext2bmp_free_one(&gd->bmps[type_sel], slot); + + sb = gd->sb; + free_ent = &gd->free_list_sel[slot]; + free_list = &gd->sb->free_list_sel[slot]; + if (llist_empty(free_ent)) { + llist_append(free_list, free_ent); + } + + if (type_sel == GDESC_INO_SEL) { + gd->info->bg_free_ino_cnt++; + sb->raw->s_free_ino_cnt++; + } else { + gd->info->bg_free_blk_cnt++; + sb->raw->s_free_blk_cnt++; + } + + fsblock_dirty(gd->buf); + fsblock_dirty(sb->buf); +} \ No newline at end of file diff --git a/lunaix-os/kernel/fs/ext2/dir.c b/lunaix-os/kernel/fs/ext2/dir.c new file mode 100644 index 0000000..a6af51e --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/dir.c @@ -0,0 +1,691 @@ +#include +#include +#include + +#include "ext2.h" + +static inline bool +aligned_reclen(struct ext2b_dirent* dirent) +{ + return !(dirent->rec_len % 4); +} + +static int +__find_dirent_byname(struct v_inode* inode, struct hstr* name, + struct ext2_dnode* e_dnode_out) +{ + int errno = 0; + struct ext2_iterator iter; + struct ext2b_dirent *dir = NULL, *prev = NULL; + bbuf_t prev_buf = NULL; + + ext2dr_itbegin(&iter, inode); + + while (ext2dr_itnext(&iter)) { + dir = iter.dirent; + + if (dir->name_len != name->len) { + goto cont; + } + + if (strneq(dir->name, name->value, name->len)) { + goto done; + } + +cont: + prev = dir; + if (prev_buf) { + fsblock_put(prev_buf); + } + prev_buf = fsblock_take(iter.sel_buf); + } + + errno = ENOENT; + goto _ret; + +done: + e_dnode_out->self = (struct ext2_dnode_sub) { + .buf = fsblock_take(iter.sel_buf), + .dirent = dir + }; + + e_dnode_out->prev = (struct ext2_dnode_sub) { + .buf = fsblock_take(prev_buf), + .dirent = prev + }; + +_ret: + fsblock_put(prev_buf); + ext2dr_itend(&iter); + return itstate_sel(&iter, errno); +} + +static size_t +__dirent_realsize(struct ext2b_dirent* dirent) +{ + return sizeof(*dirent) - sizeof(dirent->name) + dirent->name_len; +} + +#define DIRENT_SLOT_MID 0 +#define DIRENT_SLOT_LAST 1 +#define DIRENT_SLOT_EMPTY 2 + +static int +__find_free_dirent_slot(struct v_inode* inode, size_t size, + struct ext2_dnode* e_dnode_out, size_t *reclen) +{ + struct ext2_iterator iter; + struct ext2b_dirent *dir = NULL; + bbuf_t prev_buf = bbuf_null; + bool found = false; + + ext2db_itbegin(&iter, inode); + + size_t sz = 0; + unsigned int rec = 0, total_rec = 0; + + while (!found && ext2db_itnext(&iter)) + { + rec = 0; + do { + dir = (struct ext2b_dirent*)offset(iter.data, rec); + + sz = dir->rec_len - __dirent_realsize(dir); + sz = ROUNDDOWN(sz, 4); + if (sz >= size) { + found = true; + break; + } + + rec += dir->rec_len; + total_rec += dir->rec_len; + } while(rec < iter.blksz); + + if (likely(prev_buf)) { + fsblock_put(prev_buf); + } + + prev_buf = fsblock_take(iter.sel_buf); + } + + if (blkbuf_nullbuf(prev_buf)) { + // this dir is brand new + return DIRENT_SLOT_EMPTY; + } + + e_dnode_out->prev = (struct ext2_dnode_sub) { + .buf = fsblock_take(prev_buf), + .dirent = dir + }; + + if (!found) { + // if prev is the last, and no more space left behind. + assert_fs(rec == iter.blksz); + + e_dnode_out->self.buf = bbuf_null; + ext2db_itend(&iter); + return itstate_sel(&iter, DIRENT_SLOT_LAST); + } + + unsigned int dir_size; + + dir_size = ROUNDUP(__dirent_realsize(dir), 4); + *reclen = dir_size; + + rec = total_rec + dir_size; + dir = (struct ext2b_dirent*)offset(iter.data, rec); + + e_dnode_out->self = (struct ext2_dnode_sub) { + .buf = fsblock_take(iter.sel_buf), + .dirent = dir + }; + + ext2db_itend(&iter); + return DIRENT_SLOT_MID; +} + +static inline void +__destruct_ext2_dnode(struct ext2_dnode* e_dno) +{ + fsblock_put(e_dno->prev.buf); + fsblock_put(e_dno->self.buf); + vfree(e_dno); +} + +static inline bool +__check_special(struct v_dnode* dnode) +{ + return HSTR_EQ(&dnode->name, &vfs_dot) + || HSTR_EQ(&dnode->name, &vfs_ddot); +} + +static bool +__check_empty_dir(struct v_inode* dir_ino) +{ + struct ext2_iterator iter; + struct ext2b_dirent* dir; + + ext2dr_itbegin(&iter, dir_ino); + while (ext2dr_itnext(&iter)) + { + dir = iter.dirent; + if (strneq(dir->name, vfs_dot.value, 1)) { + continue; + } + + if (strneq(dir->name, vfs_ddot.value, 2)) { + continue; + } + + ext2dr_itend(&iter); + return false; + } + + ext2dr_itend(&iter); + return true; +} + +void +ext2dr_itbegin(struct ext2_iterator* iter, struct v_inode* inode) +{ + *iter = (struct ext2_iterator){ + .pos = 0, + .inode = inode, + .blksz = inode->sb->blksize + }; + + iter->sel_buf = ext2db_get(inode, 0); + ext2_itcheckbuf(iter); +} + +void +ext2dr_itreset(struct ext2_iterator* iter) +{ + fsblock_put(iter->sel_buf); + iter->sel_buf = ext2db_get(iter->inode, 0); + ext2_itcheckbuf(iter); + + iter->pos = 0; +} + +int +ext2dr_itffw(struct ext2_iterator* iter, int count) +{ + int i = 0; + while (i < count && ext2dr_itnext(iter)) { + i++; + } + + return i; +} + +void +ext2dr_itend(struct ext2_iterator* iter) +{ + if (iter->sel_buf) { + fsblock_put(iter->sel_buf); + } +} + +bool +ext2dr_itnext(struct ext2_iterator* iter) +{ + struct ext2b_dirent* d; + unsigned int blkpos, db_index; + bbuf_t buf; + + buf = iter->sel_buf; + + if (iter->has_error) { + return false; + } + + if (likely(iter->dirent)) { + d = iter->dirent; + + assert_fs(!(d->rec_len % 4)); + iter->pos += d->rec_len; + + if (!d->rec_len || !d->inode) { + return false; + } + } + + blkpos = iter->pos % iter->blksz; + db_index = iter->pos / iter->blksz; + + if (unlikely(iter->pos >= iter->blksz)) { + fsblock_put(buf); + + buf = ext2db_get(iter->inode, db_index); + iter->sel_buf = buf; + + if (!buf || !ext2_itcheckbuf(iter)) { + return false; + } + } + + d = (struct ext2b_dirent*)offset(blkbuf_data(buf), blkpos); + iter->dirent = d; + + return true; +} + +int +ext2dr_open(struct v_inode* this, struct v_file* file) +{ + struct ext2_file* e_file; + + e_file = EXT2_FILE(file); + + ext2dr_itbegin(&e_file->iter, this); + + return itstate_sel(&e_file->iter, 0); +} + +int +ext2dr_close(struct v_inode* this, struct v_file* file) +{ + struct ext2_file* e_file; + + e_file = EXT2_FILE(file); + + ext2dr_itend(&e_file->iter); + + return 0; +} + +int +ext2dr_lookup(struct v_inode* inode, struct v_dnode* dnode) +{ + int errno; + struct ext2b_dirent* dir; + struct ext2_dnode* e_dnode; + struct v_inode* dir_inode; + + e_dnode = valloc(sizeof(struct ext2_dnode)); + errno = __find_dirent_byname(inode, &dnode->name, e_dnode); + if (errno) { + vfree(e_dnode); + return errno; + } + + dir = e_dnode->self.dirent; + if (!(dir_inode = vfs_i_find(inode->sb, dir->inode))) { + dir_inode = vfs_i_alloc(inode->sb); + ext2ino_fill(dir_inode, dir->inode); + } + + dnode->data = e_dnode; + vfs_assign_inode(dnode, dir_inode); + + return 0; +} + +#define FT_NUL 0 +#define FT_REG 1 +#define FT_DIR 2 +#define FT_CHR 3 +#define FT_BLK 4 +#define FT_SYM 7 +#define check_imode(val, imode) (((val) & (imode)) == (imode)) + +static inline unsigned int +__imode_to_filetype(unsigned int imode) +{ + if (check_imode(imode, IMODE_IFLNK)) { + return FT_SYM; + } + + if (check_imode(imode, IMODE_IFBLK)) { + return FT_BLK; + } + + if (check_imode(imode, IMODE_IFCHR)) { + return FT_CHR; + } + + if (check_imode(imode, IMODE_IFDIR)) { + return FT_DIR; + } + + if (check_imode(imode, IMODE_IFREG)) { + return FT_REG; + } + + return FT_NUL; +} + +static int +__dir_filetype(struct v_superblock* vsb, struct ext2b_dirent* dir) +{ + int errno; + unsigned int type; + + if (ext2_feature(vsb, FEAT_FILETYPE)) { + type = dir->file_type; + } + else { + struct ext2_fast_inode e_fino; + + errno = ext2ino_get_fast(vsb, dir->inode, &e_fino); + if (errno) { + return errno; + } + + type = __imode_to_filetype(e_fino.ino->i_mode); + + fsblock_put(e_fino.buf); + } + + if (type == FT_DIR) { + return DT_DIR; + } + + if (type == FT_SYM) { + return DT_SYMLINK; + } + + return DT_FILE; +} + +int +ext2dr_read(struct v_file *file, struct dir_context *dctx) +{ + struct ext2_file* e_file; + struct ext2b_dirent* dir; + struct ext2_iterator* iter; + struct v_superblock* vsb; + int dirtype; + + e_file = EXT2_FILE(file); + vsb = file->inode->sb; + iter = &e_file->iter; + + if (!ext2dr_itnext(&e_file->iter)) { + return itstate_sel(iter, 0); + } + + dir = e_file->iter.dirent; + dirtype = __dir_filetype(vsb, dir); + if (dirtype < 0) { + return dirtype; + } + + fsapi_dir_report(dctx, dir->name, dir->name_len, dirtype); + + return 1; +} + +int +ext2dr_seek(struct v_file* file, size_t offset) +{ + struct ext2_file* e_file; + struct ext2_iterator* iter; + unsigned int fpos; + + e_file = EXT2_FILE(file); + iter = &e_file->iter; + fpos = file->f_pos; + + if (offset == fpos) { + return 0; + } + + if (offset > fpos) { + fpos = ext2dr_itffw(iter, fpos - offset); + return 0; + } + + if (!offset || offset < fpos) { + ext2dr_itreset(iter); + } + + fpos = ext2dr_itffw(iter, offset); + + return itstate_sel(iter, 0); +} + +int +ext2dr_insert(struct v_inode* this, struct ext2b_dirent* dirent, + struct ext2_dnode** e_dno_out) +{ + int errno; + size_t size, new_reclen, old_reclen; + struct ext2_inode* e_self; + struct ext2_dnode* e_dno; + struct ext2b_dirent* prev_dirent; + bbuf_t buf; + + e_self = EXT2_INO(this); + e_dno = vzalloc(sizeof(*e_dno)); + + size = __dirent_realsize(dirent); + errno = __find_free_dirent_slot(this, size, e_dno, &new_reclen); + if (errno < 0) { + goto failed; + } + + if (errno == DIRENT_SLOT_EMPTY) { + if ((errno = ext2db_acquire(this, 0, &buf))) { + goto failed; + } + + this->fsize += fsapi_block_size(this->sb); + ext2ino_update(this); + + old_reclen = fsapi_block_size(this->sb); + e_dno->self.buf = buf; + e_dno->self.dirent = blkbuf_data(buf); + + goto place_dir; + } + + prev_dirent = e_dno->prev.dirent; + old_reclen = prev_dirent->rec_len; + + if (errno == DIRENT_SLOT_LAST) { + // prev is last record + if ((errno = ext2db_alloc(this, &buf))) { + goto failed; + } + + this->fsize += fsapi_block_size(this->sb); + ext2ino_update(this); + + new_reclen = __dirent_realsize(prev_dirent); + new_reclen = ROUNDUP(new_reclen, sizeof(int)); + e_dno->self = (struct ext2_dnode_sub) { + .buf = buf, + .dirent = block_buffer(buf, struct ext2b_dirent) + }; + } + + /* + --- +--------+ --- + ^ | prev | | + | +--------+ | + | | new_reclen + | | + | v + | +--------+ --- - + | | dirent | | | size + old_reclen | +--------+ | - + | | dirent.reclen + | | + v v + --- +--------+ --- + | next | + +--------+ + */ + + old_reclen -= new_reclen; + prev_dirent->rec_len = new_reclen; + fsblock_dirty(e_dno->prev.buf); + +place_dir: + dirent->rec_len = ROUNDUP(old_reclen, sizeof(int)); + memcpy(e_dno->self.dirent, dirent, size); + fsblock_dirty(e_dno->self.buf); + + if (!e_dno_out) { + __destruct_ext2_dnode(e_dno); + } + else { + *e_dno_out = e_dno; + } + + return errno; + +failed: + __destruct_ext2_dnode(e_dno); + return errno; +} + +int +ext2dr_remove(struct ext2_dnode* e_dno) +{ + struct ext2_dnode_sub *dir_prev, *dir; + assert(e_dno->prev.dirent); + + dir_prev = &e_dno->prev; + dir = &e_dno->self; + + dir_prev->dirent->rec_len += dir->dirent->rec_len; + dir->dirent->rec_len = 0; + dir->dirent->inode = 0; + + fsblock_dirty(dir_prev->buf); + fsblock_dirty(dir->buf); + + __destruct_ext2_dnode(e_dno); + + return 0; +} + +int +ext2_rmdir(struct v_inode* this, struct v_dnode* dnode) +{ + int errno; + struct v_inode* self; + struct ext2_dnode* e_dno; + + self = dnode->inode; + e_dno = EXT2_DNO(dnode); + + if (__check_special(dnode)) { + return EINVAL; + } + + if (!__check_empty_dir(self)) { + return ENOTEMPTY; + } + + if ((errno = ext2ino_free(self))) { + return errno; + } + + return ext2dr_remove(e_dno); +} + +static int +__d_insert(struct v_inode* parent, struct v_inode* self, + struct ext2b_dirent* dirent, + struct hstr* name, struct ext2_dnode** e_dno_out) +{ + ext2dr_setup_dirent(dirent, self, name); + + dirent->inode = self->id; + return ext2dr_insert(parent, dirent, e_dno_out); +} + +int +ext2_mkdir(struct v_inode* this, struct v_dnode* dnode) +{ + int errno; + struct ext2_inode *e_contain, *e_created; + struct v_inode* i_created; + struct ext2_dnode* e_dno = NULL; + struct ext2b_dirent dirent; + + e_contain = EXT2_INO(this); + + errno = ext2ino_make(this->sb, VFS_IFDIR, e_contain, &i_created); + if (errno) { + return errno; + } + + e_created = EXT2_INO(i_created); + + if ((errno = __d_insert(this, i_created, &dirent, &dnode->name, &e_dno))) { + goto cleanup1; + } + + // link the created dir inode to dirent + ext2ino_linkto(e_created, &dirent); + dnode->data = e_dno; + + // insert . and .. + // we don't need ext2ino_linkto here. + + if ((errno = __d_insert(i_created, i_created, &dirent, &vfs_dot, NULL))) { + goto cleanup; + } + + if ((errno = __d_insert(i_created, this, &dirent, &vfs_ddot, NULL))) { + goto cleanup; + } + + vfs_assign_inode(dnode, i_created); + return 0; + +cleanup: + __destruct_ext2_dnode(e_dno); + +cleanup1: + dnode->data = NULL; + ext2ino_free(i_created); + vfs_i_free(i_created); + + return errno; +} + +void +ext2dr_setup_dirent(struct ext2b_dirent* dirent, + struct v_inode* inode, struct hstr* name) +{ + unsigned int imode; + + imode = EXT2_INO(inode)->ino->i_mode; + *dirent = (struct ext2b_dirent){ + .name_len = name->len + }; + + strncpy(dirent->name, name->value, name->len); + + if (ext2_feature(inode->sb, FEAT_FILETYPE)) { + dirent->file_type = __imode_to_filetype(imode); + } +} + +int +ext2_rename(struct v_inode* from_inode, struct v_dnode* from_dnode, + struct v_dnode* to_dnode) +{ + int errno; + struct v_inode* to_parent; + + if (EXT2_DNO(to_dnode)) { + errno = ext2_unlink(to_dnode->inode, to_dnode); + if (errno) { + return errno; + } + } + + errno = ext2_link(from_inode, to_dnode); + if (errno) { + return errno; + } + + return ext2_unlink(from_inode, from_dnode); +} \ No newline at end of file diff --git a/lunaix-os/kernel/fs/ext2/ext2.h b/lunaix-os/kernel/fs/ext2/ext2.h new file mode 100644 index 0000000..14b2218 --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/ext2.h @@ -0,0 +1,707 @@ +#ifndef __LUNAIX_EXT2_H +#define __LUNAIX_EXT2_H + +#include +#include +#include +#include +#include + +#define FEAT_COMPRESSION 0b00000001 +#define FEAT_RESIZE_INO 0b00000010 +#define FEAT_FILETYPE 0b00000100 +#define FEAT_SPARSE_SB 0b00001000 +#define FEAT_LARGE_FILE 0b00010000 + +#define IMODE_IFSOCK 0xC000 +#define IMODE_IFLNK 0xA000 +#define IMODE_IFREG 0x8000 +#define IMODE_IFBLK 0x6000 +#define IMODE_IFDIR 0x4000 +#define IMODE_IFCHR 0x2000 +#define IMODE_IFFIFO 0x1000 + +#define IMODE_URD 0x0100 +#define IMODE_UWR 0x0080 +#define IMODE_UEX 0x0040 +#define IMODE_GRD 0x0020 +#define IMODE_GWR 0x0010 +#define IMODE_GEX 0x0008 +#define IMODE_ORD 0x0004 +#define IMODE_OWR 0x0002 +#define IMODE_OEX 0x0001 + +#define ext2_aligned compact align(4) +#define to_ext2ino_id(fsblock_id) ((fsblock_id) + 1) +#define to_fsblock_id(ext2_ino) ((ext2_ino) - 1) + +extern bcache_zone_t gdesc_bcache_zone; + +struct ext2b_super { + u32_t s_ino_cnt; + u32_t s_blk_cnt; + u32_t s_r_blk_cnt; + u32_t s_free_blk_cnt; + u32_t s_free_ino_cnt; + u32_t s_first_data_cnt; + + u32_t s_log_blk_size; + u32_t s_log_frg_size; + + u32_t s_blk_per_grp; + u32_t s_frg_per_grp; + u32_t s_ino_per_grp; + + u32_t s_mtime; + u32_t s_wtime; + + u16_t s_mnt_cnt; + u16_t s_max_mnt_cnt; + u16_t s_magic; + u16_t s_state; + u16_t s_error; + u16_t s_minor_rev; + + u32_t s_last_check; + u32_t s_checkinterval; + u32_t s_creator_os; + u32_t s_rev; + + u16_t s_def_resuid; + u16_t s_def_resgid; + + // EXT2_DYNAMIC_REV + + struct { + u32_t s_first_ino; + u16_t s_ino_size; + u16_t s_blkgrp_nr; + u32_t s_optional_feat; + u32_t s_required_feat; + u32_t s_ro_feat; + u8_t s_uuid[16]; + u8_t s_volname[16]; + u8_t s_last_mnt[64]; + u32_t s_algo_bmp; + } compact; + +} ext2_aligned; + +struct ext2b_gdesc +{ + u32_t bg_blk_map; + u32_t bg_ino_map; + u32_t bg_ino_tab; + + u16_t bg_free_blk_cnt; + u16_t bg_free_ino_cnt; + u16_t bg_used_dir_cnt; + u16_t bg_pad; +} align(32) compact; + +struct ext2b_inode +{ + u16_t i_mode; + u16_t i_uid; + union { + u32_t i_size; + u32_t i_size_l32; + }; + + u32_t i_atime; + u32_t i_ctime; + u32_t i_mtime; + u32_t i_dtime; + + u16_t i_gid; + u16_t i_lnk_cnt; + + u32_t i_blocks; + u32_t i_flags; + u32_t i_osd1; + + union { + struct + { + u32_t directs[12]; // directum + union { + struct { + u32_t ind1; // prima indirecta + u32_t ind23[2]; // secunda et tertia indirecta + } ext2_aligned; + u32_t inds[3]; + }; + } ext2_aligned i_block; + u32_t i_block_arr[15]; + }; + + u32_t i_gen; + u32_t i_file_acl; + union { + u32_t i_dir_acl; + u32_t i_size_h32; + }; + u32_t i_faddr; + + u8_t i_osd2[12]; +} ext2_aligned; + +struct ext2b_dirent +{ + u32_t inode; + u16_t rec_len; + u8_t name_len; + u8_t file_type; + char name[256]; +} ext2_aligned; +#define EXT2_DRE(v_dnode) (fsapi_impl_data(v_dnode, struct ext2b_dirent)) + + +#define GDESC_INO_SEL 0 +#define GDESC_BLK_SEL 1 + +#define GDESC_FREE_LISTS \ + union { \ + struct { \ + struct llist_header free_grps_ino; \ + struct llist_header free_grps_blk; \ + }; \ + struct llist_header free_list_sel[2]; \ + } + +#define check_gdesc_type_sel(sel) \ + assert_msg(sel == GDESC_INO_SEL || sel == GDESC_BLK_SEL, \ + "invalid type_sel"); + +struct ext2_sbinfo +{ + /** + * @brief + * offset to inode table (in terms of blocks) within each block group. + * to account the difference of backup presence between rev 0/1 + */ + int ino_tab_len; + + bool read_only; + unsigned int block_size; + unsigned int nr_gdesc_pb; + unsigned int nr_gdesc; + unsigned int all_feature; + + struct device* bdev; + struct v_superblock* vsb; + + struct ext2b_super* raw; + bbuf_t* gdt_frag; + struct bcache gd_caches; + + bbuf_t buf; + + struct { + struct llist_header gds; + GDESC_FREE_LISTS; + }; +}; +#define EXT2_SB(vsb) (fsapi_impl_data(vsb, struct ext2_sbinfo)) + + +struct ext2_bmp +{ + bbuf_t raw; + u8_t* bmp; + unsigned int nr_bytes; + int next_free; +}; + +struct ext2_gdesc +{ + struct llist_header groups; + GDESC_FREE_LISTS; + + union { + struct { + struct ext2_bmp ino_bmp; + struct ext2_bmp blk_bmp; + }; + struct ext2_bmp bmps[2]; + }; + + unsigned int base; + unsigned int ino_base; + + struct ext2b_gdesc* info; + struct ext2_sbinfo* sb; + bbuf_t buf; + bcobj_t cache_ref; +}; + +/* + Indriection Block Translation Look-aside Buffer + + Provide a look-aside buffer for all last-level indirect block + that is at least two indirection away. + + For 4KiB block size: + 16 sets, 256 ways, capacity 4096 blocks +*/ + +struct ext2_btlb_entry +{ + unsigned int tag; + bbuf_t block; +}; + +#define BTLB_SETS 16 +struct ext2_btlb +{ + struct ext2_btlb_entry buffer[BTLB_SETS]; +}; + +struct ext2_fast_inode +{ + struct ext2b_inode* ino; + bbuf_t buf; +}; + +struct ext2_inode +{ + bbuf_t buf; // partial inotab that holds this inode + unsigned int inds_lgents; // log2(# of block in an indirection level) + unsigned int ino_id; + size_t indirect_blocks; + size_t isize; + + struct ext2b_inode* ino; // raw ext2 inode + struct ext2_btlb* btlb; // block-TLB + struct ext2_gdesc* blk_grp; // block group + + union { + struct { + /* + (future) + dirent fragmentation degree, we will perform + full reconstruction on dirent table when this goes too high. + */ + unsigned int dir_fragdeg; + }; + }; + + // prefetched block for 1st order of indirection + bbuf_t ind_ord1; + char* symlink; +}; +#define EXT2_INO(v_inode) (fsapi_impl_data(v_inode, struct ext2_inode)) + +struct ext2_dnode_sub +{ + bbuf_t buf; + struct ext2b_dirent* dirent; +}; + +struct ext2_dnode +{ + struct ext2_dnode_sub self; + struct ext2_dnode_sub prev; +}; +#define EXT2_DNO(v_dnode) (fsapi_impl_data(v_dnode, struct ext2_dnode)) + + +/** + * @brief General purpose iterator for ext2 objects + * + */ +struct ext2_iterator +{ + struct v_inode* inode; + + union { + struct ext2b_dirent* dirent; + void* data; + }; + + union { + struct { + bool has_error:1; + }; + unsigned int flags; + }; + + size_t pos; + unsigned int blksz; + size_t end_pos; + bbuf_t sel_buf; +}; + +struct ext2_file +{ + struct ext2_iterator iter; + struct ext2_inode* b_ino; +}; +#define EXT2_FILE(v_file) (fsapi_impl_data(v_file, struct ext2_file)) + + +#define MAX_INDS_DEPTH 4 + +struct walk_stack +{ + unsigned int tables[MAX_INDS_DEPTH]; + unsigned int indices[MAX_INDS_DEPTH]; +}; + +struct walk_state +{ + unsigned int* slot_ref; + bbuf_t table; + int indirections; + int level; + + struct walk_stack stack; +}; + +static inline unsigned int +ext2_datablock(struct v_superblock* vsb, unsigned int id) +{ + return EXT2_SB(vsb)->raw->s_first_data_cnt + id; +} + +static inline bool +ext2_feature(struct v_superblock* vsb, unsigned int feat) +{ + return !!(EXT2_SB(vsb)->all_feature & feat); +} + +/* ************ Inodes ************ */ + +void +ext2ino_init(struct v_superblock* vsb, struct v_inode* inode); + +int +ext2ino_get(struct v_superblock* vsb, + unsigned int ino, struct ext2_inode** out); + +int +ext2ino_get_fast(struct v_superblock* vsb, + unsigned int ino, struct ext2_fast_inode* fast_ino); + +int +ext2ino_fill(struct v_inode* inode, ino_t ino_id); + +int +ext2ino_make(struct v_superblock* vsb, unsigned int itype, + struct ext2_inode* hint, struct v_inode** out); + +void +ext2ino_update(struct v_inode* inode); + +int +ext2ino_resizing(struct v_inode* inode, size_t new_size); + +static inline void +ext2ino_linkto(struct ext2_inode* e_ino, struct ext2b_dirent* dirent) +{ + dirent->inode = e_ino->ino_id; + e_ino->ino->i_lnk_cnt++; + fsblock_dirty(e_ino->buf); +} + +void +ext2db_itbegin(struct ext2_iterator* iter, struct v_inode* inode); + +void +ext2db_itend(struct ext2_iterator* iter); + +bool +ext2db_itnext(struct ext2_iterator* iter); + +int +ext2db_itffw(struct ext2_iterator* iter, int count); + +void +ext2db_itreset(struct ext2_iterator* iter); + + +/** + * @brief Get the data block at given data pos associated with the + * inode, return NULL if not present + * + * @param inode + * @param data_pos + * @return bbuf_t + */ +bbuf_t +ext2db_get(struct v_inode* inode, unsigned int data_pos); + +/** + * @brief Get the data block at given data pos associated with the + * inode, allocate one if not present. + * + * @param inode + * @param data_pos + * @param out + * @return int + */ +int +ext2db_acquire(struct v_inode* inode, unsigned int data_pos, bbuf_t* out); + +void +ext2db_free_pos(struct v_inode* inode, unsigned int block_pos); + +/* ************* Walker ************* */ + +static inline void +ext2walk_init_state(struct walk_state* state) +{ + *state = (struct walk_state) { }; +} + +static inline void +ext2walk_free_state(struct walk_state* state) +{ + fsblock_put(state->table); +} + +/* ************* Iterator ************* */ + +static inline bool +ext2_iterror(struct ext2_iterator* iter) { + return iter->has_error; +} + +static inline bool +ext2_itcheckbuf(struct ext2_iterator* iter) { + return !(iter->has_error = blkbuf_errbuf(iter->sel_buf)); +} + +#define itstate_sel(iter, value) \ + (ext2_iterror(iter) ? EIO : (int)(value)) + + +/* ************ Block Group ************ */ + +void +ext2gd_prepare_gdt(struct v_superblock* vsb); + +void +ext2gd_release_gdt(struct v_superblock* vsb); + +int +ext2gd_take(struct v_superblock* vsb, + unsigned int index, struct ext2_gdesc** out); + +static inline void +ext2gd_put(struct ext2_gdesc* gd) { + bcache_return(gd->cache_ref); +} + + +/* ************ Directory ************ */ + +int +ext2dr_lookup(struct v_inode* this, struct v_dnode* dnode); + +int +ext2dr_read(struct v_file *file, struct dir_context *dctx); + +void +ext2dr_itbegin(struct ext2_iterator* iter, struct v_inode* inode); + +void +ext2dr_itend(struct ext2_iterator* iter); + +static inline bool +ext2dr_itdrain(struct ext2_iterator* iter) +{ + return iter->pos > iter->end_pos; +} + +bool +ext2dr_itnext(struct ext2_iterator* iter); + +int +ext2dr_itffw(struct ext2_iterator* iter, int count); + +void +ext2dr_itreset(struct ext2_iterator* iter); + +int +ext2dr_open(struct v_inode* this, struct v_file* file); + +int +ext2dr_close(struct v_inode* this, struct v_file* file); + +int +ext2dr_seek(struct v_file* file, size_t offset); + +int +ext2dr_insert(struct v_inode* this, struct ext2b_dirent* dirent, + struct ext2_dnode** e_dno_out); + +int +ext2dr_remove(struct ext2_dnode* e_dno); + +int +ext2_rmdir(struct v_inode* parent, struct v_dnode* dnode); + +int +ext2_mkdir(struct v_inode* parent, struct v_dnode* dnode); + +int +ext2_rename(struct v_inode* from_inode, struct v_dnode* from_dnode, + struct v_dnode* to_dnode); + +void +ext2dr_setup_dirent(struct ext2b_dirent* dirent, + struct v_inode* inode, struct hstr* name); + + +/* ************ Files ************ */ + +int +ext2_open_inode(struct v_inode* this, struct v_file* file); + +int +ext2_close_inode(struct v_file* file); + +int +ext2_sync_inode(struct v_inode* inode); + +int +ext2_file_sync(struct v_file* file); + +int +ext2_inode_read(struct v_inode *inode, void *buffer, size_t len, size_t fpos); + +int +ext2_inode_read_page(struct v_inode *inode, void *buffer, size_t fpos); + +int +ext2_inode_write(struct v_inode *inode, void *buffer, size_t len, size_t fpos); + +int +ext2_inode_write_page(struct v_inode *inode, void *buffer, size_t fpos); + +int +ext2_seek_inode(struct v_file* file, size_t offset); + +int +ext2_create(struct v_inode* this, struct v_dnode* dnode, unsigned int itype); + +int +ext2_link(struct v_inode* this, struct v_dnode* new_name); + +int +ext2_unlink(struct v_inode* this, struct v_dnode* name); + +int +ext2_get_symlink(struct v_inode *this, const char **path_out); + +int +ext2_set_symlink(struct v_inode *this, const char *target); + +/* *********** Bitmap *********** */ + +void +ext2bmp_init(struct ext2_bmp* e_bmp, bbuf_t bmp_buf, unsigned int nr_bits); + +bool +ext2bmp_check_free(struct ext2_bmp* e_bmp); + +int +ext2bmp_alloc_one(struct ext2_bmp* e_bmp); + +void +ext2bmp_free_one(struct ext2_bmp* e_bmp, unsigned int pos); + +void +ext2bmp_discard(struct ext2_bmp* e_bmp); + +/* *********** Allocations *********** */ + +#define ALLOC_FAIL -1 + +static inline bool +valid_bmp_slot(int slot) +{ + return slot != ALLOC_FAIL; +} + +int +ext2gd_alloc_slot(struct ext2_gdesc* gd, int type_sel); + +void +ext2gd_free_slot(struct ext2_gdesc* gd, int type_sel, int slot); + +static inline int +ext2gd_alloc_inode(struct ext2_gdesc* gd) +{ + return ext2gd_alloc_slot(gd, GDESC_INO_SEL); +} + +static inline int +ext2gd_alloc_block(struct ext2_gdesc* gd) +{ + return ext2gd_alloc_slot(gd, GDESC_BLK_SEL); +} + +static inline void +ext2gd_free_inode(struct ext2_gdesc* gd, int slot) +{ + ext2gd_free_slot(gd, GDESC_INO_SEL, slot); +} + +static inline void +ext2gd_free_block(struct ext2_gdesc* gd, int slot) +{ + ext2gd_free_slot(gd, GDESC_BLK_SEL, slot); +} + + +/** + * @brief Allocate a free inode + * + * @param vsb + * @param hint locality hint + * @param out + * @return int + */ +int +ext2ino_alloc(struct v_superblock* vsb, + struct ext2_inode* hint, struct ext2_inode** out); + +/** + * @brief Allocate a free data block + * + * @param inode inode where the data block goes, also used as locality hint + * @return bbuf_t + */ +int +ext2db_alloc(struct v_inode* inode, bbuf_t* out); + +/** + * @brief free an inode + * + * @param vsb + * @param hint locality hint + * @param out + * @return int + */ +int +ext2ino_free(struct v_inode* inode); + +/** + * @brief Free a data block + * + * @param inode inode where the data block goes, also used as locality hint + * @return bbuf_t + */ +int +ext2db_free(struct v_inode* inode, bbuf_t buf); + +int +ext2ino_alloc_slot(struct v_superblock* vsb, struct ext2_gdesc** gd_out); + +int +ext2db_alloc_slot(struct v_superblock* vsb, struct ext2_gdesc** gd_out); + + +#endif /* __LUNAIX_EXT2_H */ diff --git a/lunaix-os/kernel/fs/ext2/file.c b/lunaix-os/kernel/fs/ext2/file.c new file mode 100644 index 0000000..0fe4d3d --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/file.c @@ -0,0 +1,313 @@ +#include +#include +#include "ext2.h" + +#define blkpos(e_sb, fpos) ((fpos) / (e_sb)->block_size) +#define blkoff(e_sb, fpos) ((fpos) % (e_sb)->block_size) + +int +ext2_open_inode(struct v_inode* inode, struct v_file* file) +{ + int errno = 0; + struct ext2_file* e_file; + + e_file = valloc(sizeof(*e_file)); + e_file->b_ino = EXT2_INO(inode); + + file->data = e_file; + + if (check_directory_node(inode)) { + errno = ext2dr_open(inode, file); + goto done; + } + + // XXX anything for regular file? + +done: + if (!errno) { + return 0; + } + + vfree(e_file); + file->data = NULL; + return errno; +} + +int +ext2_close_inode(struct v_file* file) +{ + ext2ino_update(file->inode); + + if (check_directory_node(file->inode)) { + ext2dr_close(file->inode, file); + } + + vfree(file->data); + file->data = NULL; + return 0; +} + +int +ext2_sync_inode(struct v_inode* inode) +{ + // TODO + // a modification to an inode may involves multiple + // blkbuf scattering among different groups. + // For now, we just sync everything, until we figure out + // a way to track each dirtied blkbuf w.r.t inode + ext2ino_resizing(inode, inode->fsize); + blkbuf_syncall(inode->sb->blks, false); + + return 0; +} + +int +ext2_file_sync(struct v_file* file) +{ + return ext2_sync_inode(file->inode); +} + +int +ext2_seek_inode(struct v_file* file, size_t offset) +{ + if (check_directory_node(file->inode)) { + return ext2dr_seek(file, offset); + } + + // nothing to do, seek on file pos handled by vfs + return 0; +} + +int +ext2_inode_read(struct v_inode *inode, + void *buffer, size_t len, size_t fpos) +{ + struct ext2_sbinfo* e_sb; + struct ext2_iterator iter; + struct ext2b_inode* b_ino; + struct ext2_inode* e_ino; + unsigned int off; + unsigned int end; + unsigned int sz = 0, blksz, movsz; + + e_sb = EXT2_SB(inode->sb); + e_ino = EXT2_INO(inode); + b_ino = e_ino->ino; + blksz = e_sb->block_size; + end = fpos + len; + + ext2db_itbegin(&iter, inode); + ext2db_itffw(&iter, fpos / blksz); + + while (fpos < end && ext2db_itnext(&iter)) { + off = fpos % blksz; + movsz = MIN(end - fpos, blksz - off); + + memcpy(buffer, offset(iter.data, off), movsz); + + buffer = offset(buffer, movsz); + fpos += movsz; + sz += movsz; + } + + ext2db_itend(&iter); + return itstate_sel(&iter, MIN(sz, e_ino->isize)); +} + +int +ext2_inode_read_page(struct v_inode *inode, void *buffer, size_t fpos) +{ + struct ext2_sbinfo* e_sb; + struct ext2_iterator iter; + struct ext2_inode* e_ino; + struct ext2b_inode* b_ino; + unsigned int blk_start, n, + transfer_sz, total_sz = 0; + + assert(!va_offset(fpos)); + + e_sb = EXT2_SB(inode->sb); + e_ino = EXT2_INO(inode); + b_ino = e_ino->ino; + + blk_start = fpos / e_sb->block_size; + n = PAGE_SIZE / e_sb->block_size; + transfer_sz = MIN(PAGE_SIZE, e_sb->block_size); + + ext2db_itbegin(&iter, inode); + ext2db_itffw(&iter, blk_start); + + while (n-- && ext2db_itnext(&iter)) + { + memcpy(buffer, iter.data, transfer_sz); + buffer = offset(buffer, transfer_sz); + total_sz += transfer_sz; + } + + ext2db_itend(&iter); + return itstate_sel(&iter, MIN(total_sz, e_ino->isize)); +} + +int +ext2_inode_write(struct v_inode *inode, + void *buffer, size_t len, size_t fpos) +{ + int errno; + unsigned int acc, blk_off, end, size; + struct ext2_sbinfo* e_sb; + bbuf_t buf; + + e_sb = EXT2_SB(inode->sb); + + acc = 0; + end = fpos + len; + while (fpos < end) { + errno = ext2db_acquire(inode, blkpos(e_sb, fpos), &buf); + if (errno) { + return errno; + } + + blk_off = blkoff(e_sb, fpos); + size = e_sb->block_size - blk_off; + + memcpy(offset(blkbuf_data(buf), blk_off), buffer, size); + buffer = offset(buffer, size); + + fsblock_dirty(buf); + fsblock_put(buf); + + fpos += size; + acc += size; + } + + return (int)acc; +} + +int +ext2_inode_write_page(struct v_inode *inode, void *buffer, size_t fpos) +{ + return ext2_inode_write(inode, buffer, PAGE_SIZE, fpos); +} + +#define SYMLNK_INPLACE \ + sizeof(((struct ext2b_inode*)0)->i_block_arr) + +static inline int +__readlink_symlink(struct v_inode *this, char* path) +{ + size_t size; + char* link = NULL; + int errno; + bbuf_t buf; + struct ext2_inode* e_ino; + + e_ino = EXT2_INO(this); + size = e_ino->isize; + if (size <= SYMLNK_INPLACE) { + link = (char*) e_ino->ino->i_block_arr; + strncpy(path, link, size); + } + else { + buf = ext2db_get(this, 0); + if (blkbuf_errbuf(buf)) { + return EIO; + } + + link = blkbuf_data(buf); + strncpy(path, link, size); + + fsblock_put(buf); + } + + return 0; +} + +int +ext2_get_symlink(struct v_inode *this, const char **path_out) +{ + int errno; + size_t size; + char* symlink; + struct ext2_inode* e_ino; + + e_ino = EXT2_INO(this); + size = e_ino->isize; + + if (!size) { + return ENOENT; + } + + if (!e_ino->symlink) { + symlink = valloc(size); + if ((errno = __readlink_symlink(this, symlink))) { + vfree(symlink); + return errno; + } + + e_ino->symlink = symlink; + } + + *path_out = e_ino->symlink; + + return size; +} + +int +ext2_set_symlink(struct v_inode *this, const char *target) +{ + int errno = 0; + bbuf_t buf = NULL; + char* link; + size_t size, new_len; + struct ext2_inode* e_ino; + + e_ino = EXT2_INO(this); + size = e_ino->isize; + new_len = strlen(target); + + if (new_len > this->sb->blksize) { + return ENAMETOOLONG; + } + + if (size != new_len) { + vfree_safe(e_ino->symlink); + e_ino->symlink = valloc(new_len); + } + + link = (char*) e_ino->ino->i_block_arr; + + // if new size is shrinked to inplace range + if (size > SYMLNK_INPLACE && new_len <= SYMLNK_INPLACE) + { + ext2db_free_pos(this, 0); + } + + // if new size is too big to fit inpalce + if (new_len > SYMLNK_INPLACE) { + + // repurpose the i_block array back to normal + if (size <= SYMLNK_INPLACE) { + memset(link, 0, SYMLNK_INPLACE); + } + + errno = ext2db_acquire(this, 0, &buf); + if (errno) { + goto done; + } + + link = blkbuf_data(buf); + } + + strncpy(e_ino->symlink, target, new_len); + strncpy(link, target, new_len); + + ext2ino_update(this); + ext2ino_resizing(this, new_len); + + if (buf) { + fsblock_put(buf); + } + +done: + return errno; +} \ No newline at end of file diff --git a/lunaix-os/kernel/fs/ext2/group.c b/lunaix-os/kernel/fs/ext2/group.c new file mode 100644 index 0000000..b77d6b7 --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/group.c @@ -0,0 +1,301 @@ +#include + +#include "ext2.h" + +bcache_zone_t gdesc_bcache_zone; + +static void +__cached_gdesc_evict(struct bcache* bc, void* data) +{ + struct ext2_gdesc* gd; + gd = (struct ext2_gdesc*)data; + + llist_delete(&gd->groups); + llist_delete(&gd->free_grps_blk); + llist_delete(&gd->free_grps_ino); + + fsblock_put(gd->buf); + + vfree(gd); +} + +static void +__cached_gdesc_sync(struct bcache*, unsigned long tag, void* data) +{ + // since all mods to gdesc goes directly into fs buffer, + // we just need to invoke the sync on the underlying. + + struct ext2_gdesc* gd; + gd = (struct ext2_gdesc*)data; + + fsblock_sync(gd->buf); +} + +static struct bcache_ops gdesc_bc_ops = { + .release_on_evict = __cached_gdesc_evict, + .sync_cached = __cached_gdesc_sync +}; + +void +ext2gd_prepare_gdt(struct v_superblock* vsb) +{ + struct ext2b_super* sb; + unsigned int nr_parts; + unsigned int nr_gd_pb, nr_gd; + struct ext2_sbinfo* ext2sb; + + ext2sb = EXT2_SB(vsb); + sb = ext2sb->raw; + + nr_gd_pb = ext2sb->block_size / sizeof(struct ext2b_gdesc); + nr_gd = ICEIL(sb->s_blk_cnt, sb->s_blk_per_grp); + nr_parts = ICEIL(nr_gd, nr_gd_pb); + + ext2sb->gdt_frag = (bbuf_t*)vcalloc(sizeof(bbuf_t), nr_parts); + ext2sb->nr_gdesc_pb = nr_gd_pb; + ext2sb->nr_gdesc = nr_gd; + + bcache_init_zone(&ext2sb->gd_caches, gdesc_bcache_zone, + ilog2(64), 0, sizeof(struct ext2b_gdesc), &gdesc_bc_ops); + + llist_init_head(&ext2sb->gds); + llist_init_head(&ext2sb->free_grps_blk); + llist_init_head(&ext2sb->free_grps_ino); +} + +void +ext2gd_release_gdt(struct v_superblock* vsb) +{ + unsigned int parts_cnt; + struct ext2_sbinfo* ext2sb; + + ext2sb = EXT2_SB(vsb); + parts_cnt = ICEIL(ext2sb->nr_gdesc, ext2sb->nr_gdesc_pb); + for (size_t i = 0; i < parts_cnt; i++) + { + if (!ext2sb->gdt_frag[i]) { + continue; + } + + fsblock_put(ext2sb->gdt_frag[i]); + ext2sb->gdt_frag[i] = NULL; + } +} + +static inline bool +__try_load_bitmap(struct v_superblock* vsb, + struct ext2_gdesc* gd, int type) +{ + struct ext2_sbinfo* ext2sb; + struct ext2_bmp* bmp; + struct llist_header* flist, *flist_entry; + bbuf_t buf; + unsigned int blk_id, bmp_blk_id, bmp_size; + + ext2sb = EXT2_SB(vsb); + + if (type == GDESC_INO_SEL) { + bmp_blk_id = gd->info->bg_ino_map; + bmp_size = ext2sb->raw->s_ino_per_grp; + bmp = &gd->ino_bmp; + } + else if (type == GDESC_BLK_SEL) { + bmp_blk_id = gd->info->bg_blk_map; + bmp_size = ext2sb->raw->s_blk_per_grp; + bmp = &gd->blk_bmp; + } + else { + fail_fs("unknown bitmap type"); + } + + flist = &ext2sb->free_list_sel[type]; + flist_entry = &gd->free_list_sel[type]; + + blk_id = ext2_datablock(vsb, bmp_blk_id); + buf = fsblock_get(vsb, blk_id); + if (blkbuf_errbuf(buf)) { + return false; + } + + ext2bmp_init(bmp, buf, bmp_size); + + if (ext2bmp_check_free(bmp)) { + llist_append(flist, flist_entry); + } + + return true; +} + +int +ext2gd_take(struct v_superblock* vsb, + unsigned int index, struct ext2_gdesc** out) +{ + bbuf_t part, buf; + struct ext2_sbinfo* ext2sb; + unsigned int blk_id, blk_off; + + ext2sb = EXT2_SB(vsb); + + if (index >= ext2sb->nr_gdesc) { + return ENOENT; + } + + bcobj_t cached; + if (bcache_tryget(&ext2sb->gd_caches, index, &cached)) { + *out = (struct ext2_gdesc*)bcached_data(cached); + return 0; + } + + blk_id = index / ext2sb->nr_gdesc_pb; + blk_off = index % ext2sb->nr_gdesc_pb; + + part = ext2sb->gdt_frag[blk_id]; + if (!part) { + blk_id = ext2_datablock(vsb, blk_id + 1); + part = fsblock_get(vsb, blk_id); + if (!part) { + return EIO; + } + + ext2sb->gdt_frag[blk_id] = part; + } + + struct ext2_gdesc* gd; + + gd = valloc(sizeof(struct ext2_gdesc)); + *gd = (struct ext2_gdesc) { + .info = &block_buffer(part, struct ext2b_gdesc)[blk_off], + .buf = part, + .base = index * ext2sb->raw->s_blk_per_grp, + .ino_base = index * ext2sb->raw->s_ino_per_grp + }; + + *out = gd; + + if (!ext2sb->read_only) { + if (!__try_load_bitmap(vsb, gd, GDESC_INO_SEL)) { + goto cleanup; + } + + if (!__try_load_bitmap(vsb, gd, GDESC_BLK_SEL)) { + llist_delete(&gd->free_grps_ino); + goto cleanup; + } + } + + llist_append(&ext2sb->gds, &gd->groups); + + cached = bcache_put_and_ref(&ext2sb->gd_caches, index, gd); + gd->cache_ref = cached; + gd->sb = ext2sb; + + return 0; + +cleanup: + *out = NULL; + + vfree(gd); + return EIO; +} + +static void +__ext2bmp_update_next_free_cell(struct ext2_bmp* e_bmp) +{ + unsigned int i; + unsigned int end; + + i = valid_bmp_slot(e_bmp->next_free) ? e_bmp->next_free : 0; + end = i; + + // next fit, try to maximize our locality without going after + // some crazy algorithm + do { + if (e_bmp->bmp[i] != 0xff) { + e_bmp->next_free = i; + return; + } + + if (++i == e_bmp->nr_bytes) { + i = 0; + } + } + while (i != end); + + e_bmp->next_free = ALLOC_FAIL; +} + +void +ext2bmp_init(struct ext2_bmp* e_bmp, bbuf_t bmp_buf, unsigned int nr_bits) +{ + assert(nr_bits % 8 == 0); + + e_bmp->bmp = blkbuf_data(bmp_buf); + e_bmp->raw = bmp_buf; + e_bmp->nr_bytes = nr_bits / 8; + + __ext2bmp_update_next_free_cell(e_bmp); +} + +bool +ext2bmp_check_free(struct ext2_bmp* e_bmp) +{ + assert(e_bmp->raw); + + return valid_bmp_slot(e_bmp->next_free); +} + +int +ext2bmp_alloc_one(struct ext2_bmp* e_bmp) +{ + assert(e_bmp->raw); + + u8_t cell; + int slot, next_free; + + if (!valid_bmp_slot(e_bmp->next_free)) { + return ALLOC_FAIL; + } + + slot = 0; + next_free = e_bmp->next_free; + cell = e_bmp->bmp[next_free]; + assert(cell != 0xff); + + while ((cell & (1 << slot++))); + + cell |= (1 << --slot); + slot += (next_free * 8); + e_bmp->bmp[next_free] = cell; + + if (cell == 0xff) { + __ext2bmp_update_next_free_cell(e_bmp); + } + + fsblock_dirty(e_bmp->raw); + return slot; +} + +void +ext2bmp_free_one(struct ext2_bmp* e_bmp, unsigned int pos) +{ + assert(e_bmp->raw); + + int cell_idx = pos / 8; + u8_t cell_mask = 1 << (pos % 8); + e_bmp->bmp[cell_idx] &= ~cell_mask; + + if (!valid_bmp_slot(e_bmp->next_free)) { + e_bmp->next_free = cell_idx; + } + + fsblock_dirty(e_bmp->raw); +} + +void +ext2bmp_discard(struct ext2_bmp* e_bmp) +{ + assert(e_bmp->raw); + + fsblock_put(e_bmp->raw); + e_bmp->raw = NULL; +} \ No newline at end of file diff --git a/lunaix-os/kernel/fs/ext2/inode.c b/lunaix-os/kernel/fs/ext2/inode.c new file mode 100644 index 0000000..389bdea --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/inode.c @@ -0,0 +1,1088 @@ +#include +#include + +#include + +#include "ext2.h" + +static struct v_inode_ops ext2_inode_ops = { + .dir_lookup = ext2dr_lookup, + .open = ext2_open_inode, + .mkdir = ext2_mkdir, + .rmdir = ext2_rmdir, + .read_symlink = ext2_get_symlink, + .set_symlink = ext2_set_symlink, + .rename = ext2_rename, + .link = ext2_link, + .unlink = ext2_unlink, + .create = ext2_create, + .sync = ext2_sync_inode +}; + +static struct v_file_ops ext2_file_ops = { + .close = ext2_close_inode, + + .read = ext2_inode_read, + .read_page = ext2_inode_read_page, + + .write = ext2_inode_write, + .write_page = ext2_inode_write_page, + + .readdir = ext2dr_read, + .seek = ext2_seek_inode, + .sync = ext2_file_sync +}; + +#define to_tag(e_ino, val) \ + (((val) >> (e_ino)->inds_lgents) | (1 << msbiti)) +#define valid_tag(tag) ((tag) & (1 << msbiti)) + +static void +__btlb_insert(struct ext2_inode* e_inode, unsigned int blkid, bbuf_t buf) +{ + struct ext2_btlb* btlb; + struct ext2_btlb_entry* btlbe = NULL; + unsigned int cap_sel; + + if (unlikely(!blkid)) { + return; + } + + btlb = e_inode->btlb; + + for (int i = 0; i < BTLB_SETS; i++) + { + if (valid_tag(btlb->buffer[i].tag)) { + continue; + } + + btlbe = &btlb->buffer[i]; + goto found; + } + + /* + we have triggered the capacity miss. + since most file operation is heavily linear and strong + locality, we place our bet on it and avoid go through + the whole overhead of LRU eviction stuff. Just a trival + random eviction will do the fine job + */ + cap_sel = hash_32(blkid, ilog2(BTLB_SETS)); + btlbe = &btlb->buffer[cap_sel]; + + fsblock_put(btlbe->block); + +found: + btlbe->tag = to_tag(e_inode, blkid); + btlbe->block = fsblock_take(buf); +} + +static bbuf_t +__btlb_hit(struct ext2_inode* e_inode, unsigned int blkid) +{ + struct ext2_btlb* btlb; + struct ext2_btlb_entry* btlbe = NULL; + unsigned int in_tag, ref_cnts; + + btlb = e_inode->btlb; + in_tag = to_tag(e_inode, blkid); + + for (int i = 0; i < BTLB_SETS; i++) + { + btlbe = &btlb->buffer[i]; + + if (btlbe->tag != in_tag) { + continue; + } + + ref_cnts = blkbuf_refcounts(btlbe->block); + if (!ref_cnts) { + btlbe->tag = 0; + btlbe->block = bbuf_null; + break; + } + + return fsblock_take(btlbe->block); + } + + return NULL; +} + +static void +__btlb_flushall(struct ext2_inode* e_inode) +{ + struct ext2_btlb* btlb; + struct ext2_btlb_entry* btlbe = NULL; + + btlb = e_inode->btlb; + + for (int i = 0; i < BTLB_SETS; i++) + { + btlbe = &btlb->buffer[i]; + if (!valid_tag(btlbe->tag)) { + continue; + } + + btlbe->tag = 0; + fsblock_put(btlbe->block); + } +} + +void +ext2db_itbegin(struct ext2_iterator* iter, struct v_inode* inode) +{ + struct ext2_inode* e_ino; + + e_ino = EXT2_INO(inode); + *iter = (struct ext2_iterator){ + .pos = 0, + .inode = inode, + .blksz = inode->sb->blksize, + .end_pos = ICEIL(e_ino->isize, inode->sb->blksize) + }; +} + +void +ext2db_itreset(struct ext2_iterator* iter) +{ + if (likely(iter->sel_buf)) { + fsblock_put(iter->sel_buf); + iter->sel_buf = NULL; + } + + iter->pos = 0; +} + +int +ext2db_itffw(struct ext2_iterator* iter, int count) +{ + iter->pos += count; + return count; +} + +void +ext2db_itend(struct ext2_iterator* iter) +{ + if (likely(iter->sel_buf)) { + fsblock_put(iter->sel_buf); + iter->sel_buf = NULL; + } +} + +bool +ext2db_itnext(struct ext2_iterator* iter) +{ + bbuf_t buf; + + if (unlikely(iter->has_error)) { + return false; + } + + if (unlikely(iter->pos > iter->end_pos)) { + return false; + } + + if (likely(iter->sel_buf)) { + fsblock_put(iter->sel_buf); + } + + buf = ext2db_get(iter->inode, iter->pos); + iter->sel_buf = buf; + + if (!buf || !ext2_itcheckbuf(iter)) { + return false; + } + + iter->pos++; + iter->data = blkbuf_data(buf); + + return true; +} + +void +ext2ino_init(struct v_superblock* vsb, struct v_inode* inode) +{ + // Placeholder, to make vsb happy +} + +static void +__destruct_ext2_inode(struct ext2_inode* e_inode) +{ + __btlb_flushall(e_inode); + + fsblock_put(e_inode->ind_ord1); + fsblock_put(e_inode->buf); + + ext2gd_put(e_inode->blk_grp); + + vfree_safe(e_inode->symlink); + vfree(e_inode->btlb); + vfree(e_inode); +} + +static void +ext2_destruct_inode(struct v_inode* inode) +{ + struct ext2_inode* e_inode; + + e_inode = EXT2_INO(inode); + + assert(e_inode); + __destruct_ext2_inode(e_inode); +} + +static inline void +__ext2ino_fill_common(struct v_inode* inode, ino_t ino_id) +{ + fsapi_inode_setid(inode, ino_id, ino_id); + fsapi_inode_setfops(inode, &ext2_file_ops); + fsapi_inode_setops(inode, &ext2_inode_ops); + fsapi_inode_setdector(inode, ext2_destruct_inode); +} + + +static unsigned int +__translate_vfs_itype(unsigned int v_itype) +{ + unsigned int e_itype = IMODE_IFREG; + + if (v_itype == VFS_IFFILE) { + e_itype = IMODE_IFREG; + } + else if (check_itype(v_itype, VFS_IFDIR)) { + e_itype = IMODE_IFDIR; + e_itype |= IMODE_UEX; + } + else if (check_itype(v_itype, VFS_IFSEQDEV)) { + e_itype = IMODE_IFCHR; + } + else if (check_itype(v_itype, VFS_IFVOLDEV)) { + e_itype = IMODE_IFBLK; + } + + if (check_itype(v_itype, VFS_IFSYMLINK)) { + e_itype |= IMODE_IFLNK; + } + + // FIXME we keep this until we have our own user manager + e_itype |= (IMODE_URD | IMODE_GRD | IMODE_ORD); + return e_itype; +} + +int +ext2ino_fill(struct v_inode* inode, ino_t ino_id) +{ + struct ext2_sbinfo* sb; + struct ext2_inode* e_ino; + struct v_superblock* vsb; + struct ext2b_inode* b_ino; + unsigned int type = VFS_IFFILE; + int errno = 0; + + vsb = inode->sb; + sb = EXT2_SB(vsb); + + if ((errno = ext2ino_get(vsb, ino_id, &e_ino))) { + return errno; + } + + b_ino = e_ino->ino; + ino_id = e_ino->ino_id; + + fsapi_inode_setsize(inode, e_ino->isize); + + fsapi_inode_settime(inode, b_ino->i_ctime, + b_ino->i_mtime, + b_ino->i_atime); + + __ext2ino_fill_common(inode, ino_id); + + if (check_itype(b_ino->i_mode, IMODE_IFLNK)) { + type = VFS_IFSYMLINK; + } + else if (check_itype(b_ino->i_mode, IMODE_IFDIR)) { + type = VFS_IFDIR; + } + else if (check_itype(b_ino->i_mode, IMODE_IFCHR)) { + type = VFS_IFSEQDEV; + } + else if (check_itype(b_ino->i_mode, IMODE_IFBLK)) { + type = VFS_IFVOLDEV; + } + + fsapi_inode_settype(inode, type); + + fsapi_inode_complete(inode, e_ino); + + return 0; +} + +static int +__get_group_desc(struct v_superblock* vsb, int ino, + struct ext2_gdesc** gd_out) +{ + unsigned int blkgrp_id; + struct ext2_sbinfo* sb; + + sb = EXT2_SB(vsb); + + blkgrp_id = to_fsblock_id(ino) / sb->raw->s_ino_per_grp; + return ext2gd_take(vsb, blkgrp_id, gd_out); +} + +static struct ext2b_inode* +__get_raw_inode(struct v_superblock* vsb, struct ext2_gdesc* gd, + bbuf_t* buf_out, int ino_index) +{ + bbuf_t ino_tab; + struct ext2_sbinfo* sb; + struct ext2b_inode* b_inode; + unsigned int ino_tab_sel, ino_tab_off, tab_partlen; + + assert(buf_out); + + sb = gd->sb; + tab_partlen = sb->block_size / sb->raw->s_ino_size; + ino_tab_sel = ino_index / tab_partlen; + ino_tab_off = ino_index % tab_partlen; + + ino_tab = fsblock_get(vsb, gd->info->bg_ino_tab + ino_tab_sel); + if (blkbuf_errbuf(ino_tab)) { + return NULL; + } + + b_inode = (struct ext2b_inode*)blkbuf_data(ino_tab); + b_inode = &b_inode[ino_tab_off]; + + *buf_out = ino_tab; + + return b_inode; +} + +static struct ext2_inode* +__create_inode(struct v_superblock* vsb, struct ext2_gdesc* gd, int ino_index) +{ + bbuf_t ino_tab; + struct ext2_sbinfo* sb; + struct ext2b_inode* b_inode; + struct ext2_inode* inode; + unsigned int ind_ents; + size_t inds_blks; + + sb = gd->sb; + b_inode = __get_raw_inode(vsb, gd, &ino_tab, ino_index); + if (!b_inode) { + return NULL; + } + + inode = vzalloc(sizeof(*inode)); + inode->btlb = vzalloc(sizeof(struct ext2_btlb)); + inode->buf = ino_tab; + inode->ino = b_inode; + inode->blk_grp = gd; + inode->isize = b_inode->i_size; + + if (ext2_feature(vsb, FEAT_LARGE_FILE)) { + inode->isize |= (size_t)((u64_t)(b_inode->i_size_h32) << 32); + } + + if (b_inode->i_blocks) { + inds_blks = (size_t)b_inode->i_blocks; + inds_blks -= ICEIL(inode->isize, 512); + inds_blks /= (sb->block_size / 512); + + inode->indirect_blocks = inds_blks; + } + + ind_ents = sb->block_size / sizeof(int); + assert(is_pot(ind_ents)); + + inode->inds_lgents = ilog2(ind_ents); + inode->ino_id = gd->ino_base + to_ext2ino_id(ino_index); + + return inode; +} + +int +ext2ino_get_fast(struct v_superblock* vsb, + unsigned int ino, struct ext2_fast_inode* fast_ino) +{ + int errno; + bbuf_t ino_tab; + struct ext2_gdesc* gd; + struct ext2_sbinfo* sb; + struct ext2b_inode* b_inode; + unsigned int ino_rel_id; + + sb = EXT2_SB(vsb); + errno = __get_group_desc(vsb, ino, &gd); + if (errno) { + return errno; + } + + ino_rel_id = to_fsblock_id(ino) % sb->raw->s_ino_per_grp; + b_inode = __get_raw_inode(vsb, gd, &ino_tab, ino_rel_id); + + fast_ino->buf = ino_tab; + fast_ino->ino = b_inode; + + return 0; +} + +int +ext2ino_get(struct v_superblock* vsb, + unsigned int ino, struct ext2_inode** out) +{ + struct ext2_sbinfo* sb; + struct ext2_inode* inode; + struct ext2_gdesc* gd; + struct ext2b_inode* b_inode; + unsigned int ino_rel_id; + unsigned int tab_partlen; + unsigned int ind_ents, prima_ind; + int errno = 0; + + sb = EXT2_SB(vsb); + + if ((errno = __get_group_desc(vsb, ino, &gd))) { + return errno; + } + + ino_rel_id = to_fsblock_id(ino) % sb->raw->s_ino_per_grp; + inode = __create_inode(vsb, gd, ino_rel_id); + if (!inode) { + return EIO; + } + + b_inode = inode->ino; + prima_ind = b_inode->i_block.ind1; + *out = inode; + + if (!prima_ind) { + return errno; + } + + inode->ind_ord1 = fsblock_get(vsb, prima_ind); + if (blkbuf_errbuf(inode->ind_ord1)) { + vfree(inode->btlb); + vfree(inode); + *out = NULL; + return EIO; + } + + return errno; +} + +int +ext2ino_alloc(struct v_superblock* vsb, + struct ext2_inode* hint, struct ext2_inode** out) +{ + int free_ino_idx; + struct ext2_gdesc* gd; + struct ext2_inode* inode; + + free_ino_idx = ALLOC_FAIL; + if (hint) { + gd = hint->blk_grp; + free_ino_idx = ext2gd_alloc_inode(gd); + } + + // locality hinted alloc failed, try entire fs + if (!valid_bmp_slot(free_ino_idx)) { + free_ino_idx = ext2ino_alloc_slot(vsb, &gd); + } + + if (!valid_bmp_slot(free_ino_idx)) { + return EDQUOT; + } + + inode = __create_inode(vsb, gd, free_ino_idx); + if (!inode) { + // what a shame! + ext2gd_free_inode(gd, free_ino_idx); + return EIO; + } + + memset(inode->ino, 0, sizeof(*inode->ino)); + fsblock_dirty(inode->buf); + + *out = inode; + return 0; +} + +static inline int +__free_block_at(struct v_superblock *vsb, unsigned int block_pos) +{ + int errno, gd_index; + struct ext2_gdesc* gd; + struct ext2_sbinfo * sb; + + if (!block_pos) { + return 0; + } + + block_pos = ext2_datablock(vsb, block_pos); + + sb = EXT2_SB(vsb); + gd_index = block_pos / sb->raw->s_blk_per_grp; + + if ((errno = ext2gd_take(vsb, gd_index, &gd))) { + return errno; + } + + assert(block_pos >= gd->base); + ext2gd_free_block(gd, block_pos - gd->base); + + ext2gd_put(gd); + return 0; +} + +static int +__free_recurisve_from(struct v_superblock *vsb, struct ext2_inode* inode, + struct walk_stack* stack, int depth) +{ + bbuf_t tab; + int idx, len, errno; + u32_t* db_tab; + + int ind_entries = 1 << inode->inds_lgents; + int max_len[] = { 15, ind_entries, ind_entries, ind_entries }; + + u32_t* tables = stack->tables; + u32_t* indices = stack->indices; + + if (depth > MAX_INDS_DEPTH || !tables[depth]) { + return 0; + } + + idx = indices[depth]; + len = max_len[depth]; + tab = fsblock_get(vsb, ext2_datablock(vsb, tables[depth])); + + if (blkbuf_errbuf(tab)) { + return EIO; + } + + db_tab = blkbuf_data(tab); + if (depth == 0) { + int offset = offsetof(struct ext2b_inode, i_block_arr); + db_tab = offset(db_tab, offset); + } + + errno = 0; + indices[depth] = 0; + + for (; idx < len; idx++) + { + u32_t db_id = db_tab[idx]; + + if (!db_id) { + continue; + } + + if (depth >= MAX_INDS_DEPTH) { + goto cont; + } + + tables[depth] = db_id; + errno = __free_recurisve_from(vsb, inode, stack, depth + 1); + if (errno) { + break; + } + +cont: + __free_block_at(vsb, db_id); + db_tab[idx] = 0; + } + + fsblock_dirty(tab); + fsblock_put(tab); + return errno; +} + +int +ext2ino_free(struct v_inode* inode) +{ + int errno = 0; + unsigned int ino_slot; + struct ext2_inode* e_ino; + struct ext2_gdesc* e_gd; + struct ext2b_inode* b_ino; + struct ext2_sbinfo* sb; + + sb = EXT2_SB(inode->sb); + e_ino = EXT2_INO(inode); + b_ino = e_ino->ino; + e_gd = e_ino->blk_grp; + + assert_fs(b_ino->i_lnk_cnt > 0); + fsblock_dirty(e_ino->buf); + + b_ino->i_lnk_cnt--; + if (b_ino->i_lnk_cnt >= 1) { + return 0; + } + + ext2ino_resizing(inode, 0); + + ino_slot = e_ino->ino_id; + ino_slot = to_fsblock_id(ino_slot - e_gd->base); + ext2gd_free_inode(e_ino->blk_grp, ino_slot); + + __destruct_ext2_inode(e_ino); + + inode->data = NULL; + + return errno; +} + +static void +__update_inode_access_metadata(struct ext2b_inode* b_ino, + struct v_inode* inode) +{ + b_ino->i_ctime = inode->ctime; + b_ino->i_atime = inode->atime; + b_ino->i_mtime = inode->mtime; +} + +static inline void +__update_inode_size(struct v_inode* inode, size_t size) +{ + struct ext2b_inode* b_ino; + struct ext2_inode* e_ino; + + e_ino = EXT2_INO(inode); + b_ino = e_ino->ino; + + e_ino->isize = size; + + if (ext2_feature(inode->sb, FEAT_LARGE_FILE)) { + b_ino->i_size_l32 = (unsigned int)size; + b_ino->i_size_h32 = (unsigned int)((u64_t)size >> 32); + } + else { + b_ino->i_size = size; + } + + b_ino->i_blocks = ICEIL(size, 512); + b_ino->i_blocks += e_ino->indirect_blocks; +} + +int +ext2ino_make(struct v_superblock* vsb, unsigned int itype, + struct ext2_inode* hint, struct v_inode** out) +{ + int errno = 0; + struct ext2_inode* e_ino; + struct ext2b_inode* b_ino; + struct v_inode* inode; + + errno = ext2ino_alloc(vsb, hint, &e_ino); + if (errno) { + return errno; + } + + b_ino = e_ino->ino; + inode = vfs_i_alloc(vsb); + + __ext2ino_fill_common(inode, e_ino->ino_id); + + __update_inode_access_metadata(b_ino, inode); + b_ino->i_mode = __translate_vfs_itype(itype); + + fsapi_inode_settype(inode, itype); + fsapi_inode_complete(inode, e_ino); + + *out = inode; + return errno; +} + +int +ext2_create(struct v_inode* this, struct v_dnode* dnode, unsigned int itype) +{ + int errno; + struct v_inode* created; + + errno = ext2ino_make(this->sb, itype, EXT2_INO(this), &created); + if (errno) { + return errno; + } + + return ext2_link(created, dnode); +} + +int +ext2_link(struct v_inode* this, struct v_dnode* new_name) +{ + int errno = 0; + struct v_inode* parent; + struct ext2_inode* e_ino; + struct ext2_dnode* e_dno; + struct ext2b_dirent dirent; + + e_ino = EXT2_INO(this); + parent = fsapi_dnode_parent(new_name); + + ext2dr_setup_dirent(&dirent, this, &new_name->name); + ext2ino_linkto(e_ino, &dirent); + + errno = ext2dr_insert(parent, &dirent, &e_dno); + if (errno) { + goto done; + } + + new_name->data = e_dno; + vfs_assign_inode(new_name, this); + +done: + return errno; +} + +int +ext2_unlink(struct v_inode* this, struct v_dnode* name) +{ + int errno = 0; + struct ext2_inode* e_ino; + struct ext2_dnode* e_dno; + + e_ino = EXT2_INO(this); + e_dno = EXT2_DNO(name); + + assert_fs(e_dno); + assert_fs(e_dno->self.dirent->inode == e_ino->ino_id); + + errno = ext2dr_remove(e_dno); + if (errno) { + return errno; + } + + return ext2ino_free(this); +} + +void +ext2ino_update(struct v_inode* inode) +{ + struct ext2_inode* e_ino; + + e_ino = EXT2_INO(inode); + __update_inode_access_metadata(e_ino->ino, inode); + + fsblock_dirty(e_ino->buf); +} + +/* ******************* Data Blocks ******************* */ + +static inline void +__walkstate_set_stack(struct walk_state* state, int depth, + bbuf_t tab, unsigned int index) +{ + state->stack.tables[depth] = fsblock_id(tab); + state->stack.indices[depth] = index; +} + +/** + * @brief Walk the indrection chain given the position of data block + * relative to the inode. Upon completed, walk_state will be + * populated with result. On error, walk_state is untouched. + * + * Note, the result will always be one above the stopping level. + * That means, if your pos is pointed directly to file-content block + * (i.e., a leaf block), then the state is the indirect block that + * containing the ID of that leaf block. + * + * If `resolve` is set, it will resolve any absence encountered + * during the walk by allocating and chaining indirect block. + * It require the file system is mounted writable. + * + * @param inode inode to walk + * @param pos flattened data block position to be located + * @param state contain the walk result + * @param resolve whether to auto allocate the indirection structure during + * walk if `pos` is not exist. + * @return int + */ +static int +__walk_indirects(struct v_inode* inode, unsigned int pos, + struct walk_state* state, bool resolve, bool full_walk) +{ + int errno; + int inds, stride, shifts, level; + unsigned int *slotref, index, next, mask; + struct ext2_inode* e_inode; + struct ext2b_inode* b_inode; + struct v_superblock* vsb; + bbuf_t table, next_table; + + e_inode = EXT2_INO(inode); + b_inode = e_inode->ino; + vsb = inode->sb; + level = 0; + resolve = resolve && !EXT2_SB(vsb)->read_only; + + if (pos < 12) { + index = pos; + slotref = &b_inode->i_block_arr[pos]; + table = fsblock_take(e_inode->buf); + inds = 0; + goto _return; + } + + pos -= 12; + stride = e_inode->inds_lgents; + if (!(pos >> stride)) { + inds = 1; + } + else if (!(pos >> (stride * 2))) { + inds = 2; + } + else if (!(pos >> (stride * 3))) { + inds = 3; + } + else { + fail("unrealistic block pos"); + } + + // bTLB cache the last level indirect block + if (!full_walk && (table = __btlb_hit(e_inode, pos))) { + level = inds; + index = pos & ((1 << stride) - 1); + slotref = &block_buffer(table, u32_t)[index]; + goto _return; + } + + shifts = stride * (inds - 1); + mask = ((1 << stride) - 1) << shifts; + + index = 12 + inds - 1; + slotref = &b_inode->i_block.inds[inds - 1]; + table = fsblock_take(e_inode->buf); + + for (; level < inds; level++) + { + __walkstate_set_stack(state, level, table, index); + + next = *slotref; + if (!next) { + if (!resolve) { + goto _return; + } + + if ((errno = ext2db_alloc(inode, &next_table))) { + fsblock_put(table); + return errno; + } + + e_inode->indirect_blocks++; + *slotref = fsblock_id(next_table); + fsblock_dirty(table); + } + else { + next_table = fsblock_get(vsb, next); + } + + fsblock_put(table); + table = next_table; + + if (blkbuf_errbuf(table)) { + return EIO; + } + + assert(shifts >= 0); + + index = (pos & mask) >> shifts; + + slotref = &block_buffer(table, u32_t)[index]; + + shifts -= stride; + mask = mask >> stride; + } + + __btlb_insert(e_inode, pos, table); + +_return: + assert(blkbuf_refcounts(table) >= 1); + assert_fs(table); + assert_fs(slotref); + + state->slot_ref = slotref; + state->table = table; + state->level = level; + state->indirections = inds; + + __walkstate_set_stack(state, level, table, index); + + return 0; +} + +bbuf_t +ext2db_get(struct v_inode* inode, unsigned int data_pos) +{ + int errno; + unsigned int blkid; + struct walk_state state; + + ext2walk_init_state(&state); + + errno = __walk_indirects(inode, data_pos, &state, false, false); + if (errno) { + return (bbuf_t)INVL_BUFFER; + } + + blkid = *state.slot_ref; + + ext2walk_free_state(&state); + + if (!blkid) { + return NULL; + } + + return fsblock_get(inode->sb, blkid); +} + +int +ext2db_acquire(struct v_inode* inode, unsigned int data_pos, bbuf_t* out) +{ + int errno = 0; + bbuf_t buf; + unsigned int block_id; + struct walk_state state; + + ext2walk_init_state(&state); + + errno = __walk_indirects(inode, data_pos, &state, true, false); + if (errno) { + return errno; + } + + block_id = *state.slot_ref; + if (block_id) { + buf = fsblock_get(inode->sb, block_id); + goto done; + } + + errno = ext2db_alloc(inode, &buf); + if (errno) { + ext2walk_free_state(&state); + return errno; + } + + *state.slot_ref = fsblock_id(buf); + fsblock_dirty(state.table); + +done: + ext2walk_free_state(&state); + + if (blkbuf_errbuf(buf)) { + return EIO; + } + + *out = buf; + return 0; +} + +int +ext2db_alloc(struct v_inode* inode, bbuf_t* out) +{ + int free_ino_idx; + struct ext2_gdesc* gd; + struct ext2_inode* e_inode; + struct v_superblock* vsb; + + free_ino_idx = ALLOC_FAIL; + e_inode = EXT2_INO(inode); + vsb = inode->sb; + + gd = e_inode->blk_grp; + free_ino_idx = ext2gd_alloc_block(gd); + + // locality alloc failed, try entire fs + if (!valid_bmp_slot(free_ino_idx)) { + free_ino_idx = ext2db_alloc_slot(vsb, &gd); + } + + if (!valid_bmp_slot(free_ino_idx)) { + return EDQUOT; + } + + free_ino_idx += gd->base; + free_ino_idx = ext2_datablock(vsb, free_ino_idx); + free_ino_idx = to_ext2ino_id(free_ino_idx); + + bbuf_t buf = fsblock_get(vsb, free_ino_idx); + if (blkbuf_errbuf(buf)) { + return EIO; + } + + *out = buf; + return 0; +} + +void +ext2db_free_pos(struct v_inode* inode, unsigned int block_pos) +{ + struct ext2_inode* e_inode; + struct ext2_gdesc* gd; + + e_inode = EXT2_INO(inode); + gd = e_inode->blk_grp; + + assert(block_pos >= gd->base); + + block_pos -= gd->base; + + ext2gd_free_block(gd, block_pos); +} + +int +ext2db_free(struct v_inode* inode, bbuf_t buf) +{ + assert(blkbuf_not_shared(buf)); + + ext2db_free_pos(inode, blkbuf_id(buf)); + fsblock_put(buf); + + return 0; +} + +int +ext2ino_resizing(struct v_inode* inode, size_t new_size) +{ + int errno; + unsigned int pos; + size_t oldsize; + struct walk_state state; + struct ext2_inode* e_ino; + struct ext2b_inode* b_ino; + + e_ino = EXT2_INO(inode); + b_ino = e_ino->ino; + oldsize = e_ino->isize; + + if (oldsize == new_size) { + return 0; + } + + __update_inode_size(inode, new_size); + fsblock_dirty(e_ino->buf); + + if (check_symlink_node(inode)) { + return 0; + } + + if (oldsize < new_size) { + return 0; + } + + ext2walk_init_state(&state); + + pos = new_size / fsapi_block_size(inode->sb); + errno = __walk_indirects(inode, pos, &state, false, true); + if (errno) { + return errno; + } + + errno = __free_recurisve_from(inode->sb, e_ino, &state.stack, 0); + + ext2walk_free_state(&state); + return errno; +} \ No newline at end of file diff --git a/lunaix-os/kernel/fs/ext2/mount.c b/lunaix-os/kernel/fs/ext2/mount.c new file mode 100644 index 0000000..05fbdce --- /dev/null +++ b/lunaix-os/kernel/fs/ext2/mount.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include + +#include "ext2.h" + +LOG_MODULE("EXT2") + +#define EXT2_COMPRESSION 0x0001 +#define EXT2_FILETYPE 0x0002 +#define EXT2_JOURNAL 0x0004 +#define EXT2_METABG 0x0008 + +#define EXT2_SPARSESB 0x0001 +#define EXT2_LARGEFLE 0x0002 +#define EXT2_BTREEDIR 0x0004 + +#define EXT2_SUPER_MAGIC 0xef53 +#define EXT2_BASE_BLKSZ 1024 +#define EXT2_PRIME_SB_OFF EXT2_BASE_BLKSZ + +// current support for incompatible features +#define EXT2_IMPL_REQFEAT (EXT2_FILETYPE) + +// current support for readonly feature +#define EXT2_IMPL_ROFEAT (EXT2_SPARSESB) + +#define EXT2_ROOT_INO to_ext2ino_id(1) + +#define check_compat_mnt(feat) \ + (!((feat) & ~EXT2_IMPL_REQFEAT)) + +#define check_compat_mnt_ro_fallback(feat) \ + (((feat) & ~EXT2_IMPL_ROFEAT)) + +static size_t +ext2_rd_capacity(struct v_superblock* vsb) +{ + struct ext2_sbinfo* sb = fsapi_impl_data(vsb, struct ext2_sbinfo); + return sb->raw->s_blk_cnt * fsapi_block_size(vsb); +} + +static void +__vsb_release(struct v_superblock* vsb) +{ + ext2gd_release_gdt(vsb); + vfree(vsb->data); +} + +static size_t +ext2_rd_usage(struct v_superblock* vsb) +{ + struct ext2_sbinfo* sb = fsapi_impl_data(vsb, struct ext2_sbinfo); + size_t used = sb->raw->s_free_blk_cnt - sb->raw->s_blk_cnt; + return used * fsapi_block_size(vsb); +} + +struct fsapi_vsb_ops vsb_ops = { + .read_capacity = ext2_rd_capacity, + .read_usage = ext2_rd_usage, + .init_inode = ext2ino_init, + .release = __vsb_release +}; + +static inline unsigned int +__translate_feature(struct ext2b_super* sb) +{ + unsigned int feature = 0; + unsigned int req, opt, ro; + + req = sb->s_required_feat; + opt = sb->s_optional_feat; + ro = sb->s_ro_feat; + + if ((req & EXT2_COMPRESSION)) { + feature |= FEAT_COMPRESSION; + } + + if ((req & EXT2_FILETYPE)) { + feature |= FEAT_FILETYPE; + } + + if ((ro & EXT2_SPARSESB)) { + feature |= FEAT_SPARSE_SB; + } + + if ((ro & EXT2_LARGEFLE)) { + feature |= FEAT_LARGE_FILE; + } + + return feature; +} + +static bool +__check_mount(struct v_superblock* vsb, struct ext2b_super* sb) +{ + unsigned int req, opt, ro; + + req = sb->s_required_feat; + opt = sb->s_optional_feat; + ro = sb->s_ro_feat; + + if (sb->s_magic != EXT2_SUPER_MAGIC) { + ERROR("invalid magic: 0x%x", sb->s_magic); + return false; + } + + if (!check_compat_mnt(req)) + { + ERROR("unsupported feature: 0x%x, mount refused", req); + return false; + } + + if (check_compat_mnt_ro_fallback(ro)) + { + WARN("unsupported feature: 0x%x, mounted as readonly", ro); + fsapi_set_readonly_mount(vsb); + } + +#ifndef CONFIG_ARCH_BITS_64 + if ((ro & EXT2_LARGEFLE)) { + WARN("large file not supported on 32bits machine"); + fsapi_set_readonly_mount(vsb); + } +#endif + + return true; +} + +static int +ext2_mount(struct v_superblock* vsb, struct v_dnode* mnt) +{ + struct device* bdev; + struct ext2_sbinfo* ext2sb; + struct ext2b_super* rawsb; + struct v_inode* root_inode; + bbuf_t buf; + size_t block_size; + int errno = 0; + unsigned int req_feat; + + bdev = fsapi_blockdev(vsb); + ext2sb = vzalloc(sizeof(*ext2sb)); + rawsb = vzalloc(sizeof(*rawsb)); + + errno = bdev->ops.read(bdev, rawsb, EXT2_PRIME_SB_OFF, sizeof(*rawsb)); + if (errno < 0) { + goto failed; + } + + block_size = EXT2_BASE_BLKSZ << rawsb->s_log_blk_size; + fsapi_begin_vsb_setup(vsb, block_size); + + if (!__check_mount(vsb, rawsb)) { + goto unsupported; + } + + if (block_size > PAGE_SIZE) { + ERROR("block size must not greater than page size"); + errno = EINVAL; + goto failed; + } + + ext2sb->bdev = bdev; + ext2sb->block_size = block_size; + ext2sb->vsb = vsb; + ext2sb->read_only = fsapi_readonly_mount(vsb); + ext2sb->raw = rawsb; + ext2sb->all_feature = __translate_feature(rawsb); + + fsapi_set_vsb_ops(vsb, &vsb_ops); + fsapi_complete_vsb_setup(vsb, ext2sb); + + ext2gd_prepare_gdt(vsb); + + root_inode = vfs_i_alloc(vsb); + ext2ino_fill(root_inode, EXT2_ROOT_INO); + vfs_assign_inode(mnt, root_inode); + + // replace the superblock raw buffer with bcache managed one + buf = fsblock_get(vsb, ext2_datablock(vsb, 0)); + if (block_size == EXT2_BASE_BLKSZ) { + ext2sb->raw = blkbuf_data(buf); + } + else { + ext2sb->raw = offset(blkbuf_data(buf), EXT2_BASE_BLKSZ); + } + + ext2sb->buf = buf; + vfree(rawsb); + return 0; + +unsupported: + errno = ENOTSUP; + +failed: + vfree(ext2sb); + vfree(rawsb); + fsapi_reset_vsb(vsb); + return errno; +} + +static int +ext2_umount(struct v_superblock* vsb) +{ + // sync all dirty buffers + if (!blkbuf_syncall(vsb->blks, false)) { + return EAGAIN; + } + + ext2gd_release_gdt(vsb); + + blkbuf_release(vsb->blks); + return 0; +} + +static void +ext2_init() +{ + struct filesystem* fs; + fs = fsapi_fs_declare("ext2", 0); + + fsapi_fs_set_mntops(fs, ext2_mount, ext2_umount); + fsapi_fs_finalise(fs); + + gdesc_bcache_zone = bcache_create_zone("ext2_gdesc"); +} +EXPORT_FILE_SYSTEM(ext2fs, ext2_init); \ No newline at end of file diff --git a/lunaix-os/kernel/fs/iso9660/directory.c b/lunaix-os/kernel/fs/iso9660/directory.c index 9f4d988..b022d42 100644 --- a/lunaix-os/kernel/fs/iso9660/directory.c +++ b/lunaix-os/kernel/fs/iso9660/directory.c @@ -1,5 +1,5 @@ #include -#include +#include "iso9660.h" #include #include #include @@ -47,26 +47,41 @@ iso9660_fill_drecache(struct iso_drecache* cache, } done: - if (!cache->name.len) { - // Load ISO9660 file id if no NM found. - u32_t l = drec->name.len; - while (l < (u32_t)-1 && drec->name.content[l--] != ';') - ; + if (cache->name.len) { + return; + } - l = (l + 1) ? l : drec->name.len; - l = MIN(l, ISO9660_IDLEN - 1); + // Load ISO9660 file id if no NM found. - strncpy(cache->name_val, (const char*)drec->name.content, l); + ; + char name_val = drec->name.content[0]; + u32_t l = drec->name.len; - cache->name = HSTR(cache->name_val, l); - hstr_rehash(&cache->name, HSTR_FULL_HASH); + if (l == 1 && !name_val) { + cache->name = vfs_dot; + return; + } + + if(l == 1 && name_val == 1) { + cache->name = vfs_ddot; + return; } + + while (l < (u32_t)-1 && drec->name.content[l--] != ';') + ; + + l = (l + 1) ? l : drec->name.len; + l = MIN(l, ISO9660_IDLEN - 1); + + strncpy(cache->name_val, (const char*)drec->name.content, l); + cache->name = HSTR(cache->name_val, l); + hstr_rehash(&cache->name, HSTR_FULL_HASH); } int iso9660_setup_dnode(struct v_dnode* dnode, struct v_inode* inode) { - if ((inode->itype & F_FILE)) { + if (!check_directory_node(inode)) { vfs_assign_inode(dnode, inode); return 0; } @@ -104,16 +119,11 @@ iso9660_setup_dnode(struct v_dnode* dnode, struct v_inode* inode) break; } - // ignore the '.', '..' as we have built-in support - if (drec->name.len == 1) { - goto cont; - } - struct iso_drecache* cache = cake_grab(drec_cache_pile); iso9660_fill_drecache(cache, drec, mdu->len); llist_append(&isoino->drecaches, &cache->caches); - cont: + blk_offset += mdu->len; } while (current_pos + blk_offset < max_pos); @@ -166,19 +176,20 @@ __get_dtype(struct iso_drecache* pos) int iso9660_readdir(struct v_file* file, struct dir_context* dctx) -{ +{ struct llist_header* lead = file->dnode->data; struct iso_drecache *pos, *n; - u32_t counter = dctx->index - 1; + u32_t counter = 0; llist_for_each(pos, n, lead, caches) { - if (counter == (u32_t)-1 && !(pos->flags & ISO_FHIDDEN)) { + if (counter == file->f_pos && !(pos->flags & ISO_FHIDDEN)) { dctx->read_complete_callback( - dctx, pos->name_val, pos->name.len, __get_dtype(pos)); + dctx, HSTR_VAL(pos->name), HSTR_LEN(pos->name), __get_dtype(pos)); return 1; } - counter--; + counter++; } + return 0; } \ No newline at end of file diff --git a/lunaix-os/kernel/fs/iso9660/file.c b/lunaix-os/kernel/fs/iso9660/file.c index 23c4066..b1ac030 100644 --- a/lunaix-os/kernel/fs/iso9660/file.c +++ b/lunaix-os/kernel/fs/iso9660/file.c @@ -1,5 +1,5 @@ #include -#include +#include "iso9660.h" #include #include @@ -101,8 +101,8 @@ iso9660_write_page(struct v_inode* inode, void* buffer, size_t fpos) } int -iso9660_seek(struct v_inode* inode, size_t offset) +iso9660_seek(struct v_file* file, size_t offset) { - // TODO + file->f_pos = offset; return 0; } \ No newline at end of file diff --git a/lunaix-os/kernel/fs/iso9660/inode.c b/lunaix-os/kernel/fs/iso9660/inode.c index 58d1b8f..ab66ca4 100644 --- a/lunaix-os/kernel/fs/iso9660/inode.c +++ b/lunaix-os/kernel/fs/iso9660/inode.c @@ -1,6 +1,6 @@ #include #include -#include +#include "iso9660.h" #include #include #include diff --git a/lunaix-os/includes/lunaix/fs/iso9660.h b/lunaix-os/kernel/fs/iso9660/iso9660.h similarity index 99% rename from lunaix-os/includes/lunaix/fs/iso9660.h rename to lunaix-os/kernel/fs/iso9660/iso9660.h index 99fc484..708a28a 100644 --- a/lunaix-os/includes/lunaix/fs/iso9660.h +++ b/lunaix-os/kernel/fs/iso9660/iso9660.h @@ -347,7 +347,7 @@ int iso9660_write_page(struct v_inode* inode, void* buffer, size_t fpos); int -iso9660_seek(struct v_inode* inode, size_t offset); +iso9660_seek(struct v_file* file, size_t offset); int isorr_parse_px(struct iso_drecache* cache, void* px_start); diff --git a/lunaix-os/kernel/fs/iso9660/mount.c b/lunaix-os/kernel/fs/iso9660/mount.c index 25150e5..aeb6e3b 100644 --- a/lunaix-os/kernel/fs/iso9660/mount.c +++ b/lunaix-os/kernel/fs/iso9660/mount.c @@ -1,23 +1,29 @@ #include -#include -#include +#include #include +#include #include -#include +#include "iso9660.h" struct cake_pile* drec_cache_pile; extern void iso9660_init_inode(struct v_superblock* vsb, struct v_inode* inode); -u32_t -iso9660_rd_capacity(struct v_superblock* vsb) +static size_t +__iso9660_rd_capacity(struct v_superblock* vsb) { struct iso_superblock* isovsb = (struct iso_superblock*)vsb->data; return isovsb->volume_size; } +static void +__vsb_release(struct v_superblock* vsb) +{ + vfree(vsb->data); +} + int iso9660_mount(struct v_superblock* vsb, struct v_dnode* mount_point) { @@ -54,7 +60,8 @@ iso9660_mount(struct v_superblock* vsb, struct v_dnode* mount_point) vsb->data = isovsb; vsb->ops.init_inode = iso9660_init_inode; - vsb->ops.read_capacity = iso9660_rd_capacity; + vsb->ops.read_capacity = __iso9660_rd_capacity; + vsb->ops.release = __vsb_release; vsb->blksize = ISO9660_BLKSZ; struct v_inode* rootino = vfs_i_alloc(vsb); @@ -90,25 +97,24 @@ done: return errno; } + + int iso9660_unmount(struct v_superblock* vsb) { - vfree(vsb->data); - return 0; } void iso9660_init() { + struct filesystem* fs; + fs = fsapi_fs_declare("iso9660", FSTYPE_ROFS); + + fsapi_fs_set_mntops(fs, iso9660_mount, iso9660_unmount); + fsapi_fs_finalise(fs); + drec_cache_pile = cake_new_pile("iso_drec", sizeof(struct iso_drecache), 1, 0); - - struct filesystem* fs = fsm_new_fs("iso9660", -1); - fs->types |= FSTYPE_ROFS; - fs->mount = iso9660_mount; - fs->unmount = iso9660_unmount; - - fsm_register(fs); } EXPORT_FILE_SYSTEM(iso9660, iso9660_init); \ No newline at end of file diff --git a/lunaix-os/kernel/fs/iso9660/rockridge.c b/lunaix-os/kernel/fs/iso9660/rockridge.c index 6dcb610..d0295d0 100644 --- a/lunaix-os/kernel/fs/iso9660/rockridge.c +++ b/lunaix-os/kernel/fs/iso9660/rockridge.c @@ -1,5 +1,5 @@ #include -#include +#include "iso9660.h" int isorr_parse_px(struct iso_drecache* cache, void* px_start) diff --git a/lunaix-os/kernel/fs/iso9660/utils.c b/lunaix-os/kernel/fs/iso9660/utils.c index 3b19c95..36d8847 100644 --- a/lunaix-os/kernel/fs/iso9660/utils.c +++ b/lunaix-os/kernel/fs/iso9660/utils.c @@ -1,4 +1,4 @@ -#include +#include "iso9660.h" struct iso_drecord* iso9660_get_drecord(struct iso_var_mdu* drecord_mdu) diff --git a/lunaix-os/kernel/fs/mount.c b/lunaix-os/kernel/fs/mount.c index 2b996d4..c5b7344 100644 --- a/lunaix-os/kernel/fs/mount.c +++ b/lunaix-os/kernel/fs/mount.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -25,7 +25,7 @@ vfs_create_mount(struct v_mount* parent, struct v_dnode* mnt_point) mnt->parent = parent; mnt->mnt_point = mnt_point; - mnt->super_block = mnt_point->super_block; + vfs_vmnt_assign_sb(mnt, mnt_point->super_block); if (parent) { mnt_mkbusy(parent); @@ -39,6 +39,21 @@ vfs_create_mount(struct v_mount* parent, struct v_dnode* mnt_point) return mnt; } +void +__vfs_release_vmnt(struct v_mount* mnt) +{ + assert(llist_empty(&mnt->submnts)); + + if (mnt->parent) { + mnt_chillax(mnt->parent); + } + + llist_delete(&mnt->sibmnts); + llist_delete(&mnt->list); + atomic_fetch_sub(&mnt->mnt_point->ref_count, 1); + vfree(mnt); +} + int __vfs_do_unmount(struct v_mount* mnt) { @@ -49,9 +64,6 @@ __vfs_do_unmount(struct v_mount* mnt) return errno; } - llist_delete(&mnt->list); - llist_delete(&mnt->sibmnts); - // detached the inodes from cache, and let lru policy to recycle them for (size_t i = 0; i < VFS_HASHTABLE_SIZE; i++) { struct hbucket* bucket = &sb->i_cache[i]; @@ -61,13 +73,10 @@ __vfs_do_unmount(struct v_mount* mnt) bucket->head->pprev = 0; } - mnt_chillax(mnt->parent); - mnt->mnt_point->mnt = mnt->parent; vfs_sb_free(sb); - atomic_fetch_sub(&mnt->mnt_point->ref_count, 1); - vfree(mnt); + __vfs_release_vmnt(mnt); return errno; } @@ -139,7 +148,7 @@ vfs_mount_at(const char* fs_name, return ENOTBLK; } - if (mnt_point->inode && (mnt_point->inode->itype & F_MFILE)) { + if (mnt_point->inode && !check_directory_node(mnt_point->inode)) { return ENOTDIR; } @@ -148,47 +157,55 @@ vfs_mount_at(const char* fs_name, return ENODEV; } - if (fs->types == FSTYPE_ROFS) { + if ((fs->types & FSTYPE_ROFS)) { options |= MNT_RO; } + if (!(fs->types & FSTYPE_PSEUDO) && !device) { + return ENODEV; + } + + int errno = 0; char* dev_name = "sys"; struct v_mount* parent_mnt = mnt_point->mnt; - struct v_superblock *sb = vfs_sb_alloc(), *old_sb = mnt_point->super_block; - sb->dev = device; - mnt_point->super_block = sb; + struct v_superblock *sb = vfs_sb_alloc(), + *old_sb = mnt_point->super_block; if (device) { dev_name = device->name_val; } - int errno = 0; - if (!(errno = fs->mount(sb, mnt_point))) { - sb->fs = fs; - sb->root = mnt_point; + // prepare v_superblock for fs::mount invoke + sb->dev = device; + sb->fs = fs; + sb->root = mnt_point; + vfs_d_assign_sb(mnt_point, sb); - if (!(mnt_point->mnt = vfs_create_mount(parent_mnt, mnt_point))) { - errno = ENOMEM; - goto cleanup; - } + if (!(mnt_point->mnt = vfs_create_mount(parent_mnt, mnt_point))) { + errno = ENOMEM; + goto cleanup; + } + mnt_point->mnt->flags = options; + if (!(errno = fs->mount(sb, mnt_point))) { kprintf("mount: dev=%s, fs=%s, mode=%d", dev_name, fs_name, options); - - mnt_point->mnt->flags = options; } else { goto cleanup; } + vfs_sb_free(old_sb); return errno; cleanup: - ERROR("mount: dev=%s, fs=%s, mode=%d, err=%d", + ERROR("failed mount: dev=%s, fs=%s, mode=%d, err=%d", dev_name, fs_name, options, errno); - mnt_point->super_block = old_sb; + vfs_d_assign_sb(mnt_point, old_sb); vfs_sb_free(sb); + __vfs_release_vmnt(mnt_point->mnt); + return errno; } @@ -261,7 +278,7 @@ __DEFINE_LXSYSCALL4(int, struct device* device = NULL; if (dev) { - if (!(dev->inode->itype & VFS_IFVOLDEV)) { + if (!check_voldev_node(dev->inode)) { errno = ENOTDEV; goto done; } diff --git a/lunaix-os/kernel/fs/path_walk.c b/lunaix-os/kernel/fs/path_walk.c index e1b1157..783d9b4 100644 --- a/lunaix-os/kernel/fs/path_walk.c +++ b/lunaix-os/kernel/fs/path_walk.c @@ -6,6 +6,7 @@ #include #define VFS_SYMLINK_DEPTH 16 +#define VFS_SYMLINK_MAXLEN 512 extern struct lru_zone *dnode_lru, *inode_lru; @@ -119,21 +120,28 @@ __vfs_walk(struct v_dnode* start, current_level = dnode; current_inode = current_level->inode; - if ((current_inode->itype & F_MSLNK) && + assert(current_inode); + + if (check_symlink_node(current_inode) && !(walk_options & VFS_WALK_NOFOLLOW)) { const char* link; + struct v_inode_ops* iops; - if (!current_inode->ops->read_symlink) { + iops = current_inode->ops; + + if (!iops->read_symlink) { errno = ENOTSUP; goto error; } lock_inode(current_inode); - if ((errno = - current_inode->ops->read_symlink(current_inode, &link))) { + + errno = iops->read_symlink(current_inode, &link); + if ((errno < 0)) { unlock_inode(current_inode); goto error; } + unlock_inode(current_inode); errno = __vfs_walk(current_level->parent, diff --git a/lunaix-os/kernel/fs/pcache.c b/lunaix-os/kernel/fs/pcache.c index 26e7ac4..4a280db 100644 --- a/lunaix-os/kernel/fs/pcache.c +++ b/lunaix-os/kernel/fs/pcache.c @@ -4,27 +4,41 @@ #include #include #include +#include +#include -#define PCACHE_DIRTY 0x1 +LOG_MODULE("pcache") -static struct lru_zone* pcache_zone; +#define pcache_obj(bcache) container_of(bcache, struct pcache, cache) -static int -__pcache_try_evict(struct lru_node* obj) +void pcache_release_page(struct pcache* pcache, struct pcache_pg* page); +void pcache_set_dirty(struct pcache* pcache, struct pcache_pg* pg); + +static bcache_zone_t pagecached_zone = NULL; + +static void +__pcache_sync(struct bcache* bc, unsigned long tag, void* data) { - struct pcache_pg* page = container_of(obj, struct pcache_pg, lru); - pcache_invalidate(page->holder, page); - return 1; + struct pcache* cache; + + cache = pcache_obj(bc); + pcache_commit(cache->master, (struct pcache_pg*)data); } static void -pcache_free_page(void* va) +__pcache_try_release(struct bcache* bc, void* data) { - pte_t* ptep = mkptep_va(VMS_SELF, (ptr_t)va); - pte_t pte = pte_at(ptep); - leaflet_return(pte_leaflet(pte)); + struct pcache_pg* page; + + page = (struct pcache_pg*)data; + pcache_release_page(pcache_obj(bc), page); } +static struct bcache_ops cache_ops = { + .release_on_evict = __pcache_try_release, + .sync_cached = __pcache_sync +}; + static void* pcache_alloc_page() { @@ -44,24 +58,31 @@ pcache_alloc_page() return (void*)va; } +static void +pcache_free_page(void* va) +{ + pte_t* ptep = mkptep_va(VMS_SELF, (ptr_t)va); + pte_t pte = pte_at(ptep); + leaflet_return(pte_leaflet(pte)); +} + void pcache_init(struct pcache* pcache) { - btrie_init(&pcache->tree, PAGE_SHIFT); + if (unlikely(!pagecached_zone)) { + pagecached_zone = bcache_create_zone("pcache"); + } + llist_init_head(&pcache->dirty); - llist_init_head(&pcache->pages); - pcache_zone = lru_new_zone(__pcache_try_evict); + bcache_init_zone(&pcache->cache, pagecached_zone, 4, -1, + sizeof(struct pcache_pg), &cache_ops); } void pcache_release_page(struct pcache* pcache, struct pcache_pg* page) { - pcache_free_page(page->pg); - - llist_delete(&page->pg_list); - - btrie_remove(&pcache->tree, page->fpos); + pcache_free_page(page->data); vfree(page); @@ -69,27 +90,18 @@ pcache_release_page(struct pcache* pcache, struct pcache_pg* page) } struct pcache_pg* -pcache_new_page(struct pcache* pcache, u32_t index) +pcache_new_page(struct pcache* pcache) { - struct pcache_pg* ppg = vzalloc(sizeof(struct pcache_pg)); - void* pg = pcache_alloc_page(); - - if (!ppg || !pg) { - lru_evict_one(pcache_zone); - if (!ppg && !(ppg = vzalloc(sizeof(struct pcache_pg)))) { - return NULL; - } - - if (!pg && !(pg = pcache_alloc_page())) { - return NULL; - } + struct pcache_pg* ppg; + void* data_page; + + data_page = pcache_alloc_page(); + if (!data_page) { + return NULL; } - ppg->pg = pg; - ppg->holder = pcache; - - llist_append(&pcache->pages, &ppg->pg_list); - btrie_set(&pcache->tree, index, ppg); + ppg = vzalloc(sizeof(struct pcache_pg)); + ppg->data = data_page; return ppg; } @@ -97,142 +109,156 @@ pcache_new_page(struct pcache* pcache, u32_t index) void pcache_set_dirty(struct pcache* pcache, struct pcache_pg* pg) { - if (!(pg->flags & PCACHE_DIRTY)) { - pg->flags |= PCACHE_DIRTY; - pcache->n_dirty++; - llist_append(&pcache->dirty, &pg->dirty_list); + if (pg->dirty) { + return; } + + pg->dirty = true; + pcache->n_dirty++; + llist_append(&pcache->dirty, &pg->dirty_list); } -int -pcache_get_page(struct pcache* pcache, - u32_t index, - u32_t* offset, - struct pcache_pg** page) +static bcobj_t +__getpage_and_lock(struct pcache* pcache, unsigned int tag, + struct pcache_pg** page) { - struct pcache_pg* pg = btrie_get(&pcache->tree, index); - int is_new = 0; - u32_t mask = ((1 << pcache->tree.truncated) - 1); - *offset = index & mask; - if (!pg && (pg = pcache_new_page(pcache, index))) { - pg->fpos = index & ~mask; - pcache->n_pages++; - is_new = 1; + bcobj_t cobj; + struct pcache_pg* pg; + + if (bcache_tryget(&pcache->cache, tag, &cobj)) + { + *page = (struct pcache_pg*)bcached_data(cobj); + return cobj; + } + + pg = pcache_new_page(pcache); + if (pg) { + pg->index = tag; } - if (pg) - lru_use_one(pcache_zone, &pg->lru); + *page = pg; - return is_new; + + return NULL; +} + +static inline int +__fill_page(struct v_inode* inode, struct pcache_pg* pg, unsigned int index) +{ + return inode->default_fops->read_page(inode, pg->data, page_addr(index)); } int pcache_write(struct v_inode* inode, void* data, u32_t len, u32_t fpos) { int errno = 0; - u32_t pg_off, buf_off = 0; - struct pcache* pcache = inode->pg_cache; + unsigned int tag, off, wr_cnt; + unsigned int end = fpos + len; + struct pcache* pcache; struct pcache_pg* pg; + bcobj_t obj; - while (buf_off < len && errno >= 0) { - u32_t wr_bytes = MIN(PAGE_SIZE - pg_off, len - buf_off); + pcache = inode->pg_cache; + + while (fpos < end && errno >= 0) { + tag = pfn(fpos); + off = va_offset(fpos); + wr_cnt = MIN(end - fpos, PAGE_SIZE - off); - int new_page = pcache_get_page(pcache, fpos, &pg_off, &pg); + obj = __getpage_and_lock(pcache, tag, &pg); - if (new_page) { - // Filling up the page - errno = inode->default_fops->read_page(inode, pg->pg, pg->fpos); + if (!obj && !pg) { + errno = inode->default_fops->write(inode, data, fpos, wr_cnt); + goto cont; + } + // new page and unaligned write, then prepare for partial override + if (!obj && wr_cnt != PAGE_SIZE) { + errno = __fill_page(inode, pg, tag); if (errno < 0) { - break; + return errno; } - if (errno < (int)PAGE_SIZE) { - // EOF - len = MIN(len, buf_off + errno); - } - } else if (!pg) { - errno = inode->default_fops->write(inode, data, wr_bytes, fpos); - continue; } - - memcpy(pg->pg + pg_off, (data + buf_off), wr_bytes); + + memcpy(offset(pg->data, off), data, wr_cnt); pcache_set_dirty(pcache, pg); - pg->len = pg_off + wr_bytes; - buf_off += wr_bytes; - fpos += wr_bytes; + if (obj) { + bcache_return(obj); + } else { + bcache_put(&pcache->cache, tag, pg); + } + +cont: + data = offset(data, wr_cnt); + fpos += wr_cnt; } - return errno < 0 ? errno : (int)buf_off; + return errno < 0 ? errno : (int)(len - (end - fpos)); } int pcache_read(struct v_inode* inode, void* data, u32_t len, u32_t fpos) { - u32_t pg_off, buf_off = 0, new_pg = 0; int errno = 0; - struct pcache* pcache = inode->pg_cache; + unsigned int tag, off, rd_cnt; + unsigned int end = fpos + len, size = 0; + struct pcache* pcache; struct pcache_pg* pg; + bcobj_t obj; + + pcache = inode->pg_cache; + + while (fpos < page_upaligned(end)) { + tag = pfn(fpos); + off = va_offset(fpos); - while (buf_off < len) { - int new_page = pcache_get_page(pcache, fpos, &pg_off, &pg); - if (new_page) { - // Filling up the page - errno = inode->default_fops->read_page(inode, pg->pg, pg->fpos); + obj = __getpage_and_lock(pcache, tag, &pg); + if (!obj) { + errno = __fill_page(inode, pg, tag); if (errno < 0) { - break; - } - if (errno < (int)PAGE_SIZE) { - // EOF - len = MIN(len, buf_off + errno); + return errno; } - pg->len = errno; - } else if (!pg) { - errno = inode->default_fops->read( - inode, (data + buf_off), len - buf_off, pg->fpos); - buf_off = len; - break; + end -= (PAGE_SIZE - errno); } - u32_t rd_bytes = MIN(pg->len - pg_off, len - buf_off); - - if (!rd_bytes) - break; + rd_cnt = MIN(end - fpos, PAGE_SIZE - off); + memcpy(data, pg->data + off, rd_cnt); - memcpy((data + buf_off), pg->pg + pg_off, rd_bytes); + if (obj) { + bcache_return(obj); + } else { + bcache_put(&pcache->cache, tag, pg); + } - buf_off += rd_bytes; - fpos += rd_bytes; + data += rd_cnt; + size += rd_cnt; + fpos = page_aligned(fpos + PAGE_SIZE); } - return errno < 0 ? errno : (int)buf_off; + return errno < 0 ? errno : (int)size; } void pcache_release(struct pcache* pcache) { - struct pcache_pg *pos, *n; - llist_for_each(pos, n, &pcache->pages, pg_list) - { - lru_remove(pcache_zone, &pos->lru); - vfree(pos); - } - - btrie_release(&pcache->tree); + bcache_free(&pcache->cache); } int pcache_commit(struct v_inode* inode, struct pcache_pg* page) { - if (!(page->flags & PCACHE_DIRTY)) { + if (!page->dirty) { return 0; } - int errno = inode->default_fops->write_page(inode, page->pg, page->fpos); - + int errno; + unsigned int fpos = page_addr(page->index); + + errno = inode->default_fops->write_page(inode, page->data, fpos); if (!errno) { - page->flags &= ~PCACHE_DIRTY; + page->dirty = false; llist_delete(&page->dirty_list); inode->pg_cache->n_dirty--; } @@ -243,22 +269,14 @@ pcache_commit(struct v_inode* inode, struct pcache_pg* page) void pcache_commit_all(struct v_inode* inode) { - if (!inode->pg_cache) { + struct pcache* cache = inode->pg_cache; + if (!cache) { return; } - struct pcache* cache = inode->pg_cache; struct pcache_pg *pos, *n; - llist_for_each(pos, n, &cache->dirty, dirty_list) { pcache_commit(inode, pos); } -} - -void -pcache_invalidate(struct pcache* pcache, struct pcache_pg* page) -{ - pcache_commit(pcache->master, page); - pcache_release_page(pcache, page); } \ No newline at end of file diff --git a/lunaix-os/kernel/fs/probe_boot.c b/lunaix-os/kernel/fs/probe_boot.c index 3cb4049..fe384f7 100644 --- a/lunaix-os/kernel/fs/probe_boot.c +++ b/lunaix-os/kernel/fs/probe_boot.c @@ -1,8 +1,9 @@ -#include #include #include #include +#include "iso9660/iso9660.h" + LOG_MODULE("PROBE") #define LUNAIX_ID 0x414e554cUL // "LUNA" @@ -47,10 +48,12 @@ probe_boot_medium() dev->ident.unique, dev->name.value, (char*)volp->vol_id); - break; + goto done; } } + return NULL; + done: vfree(volp); return dev; diff --git a/lunaix-os/kernel/fs/ramfs/ramfs.c b/lunaix-os/kernel/fs/ramfs/ramfs.c index ca40efb..b7512e5 100644 --- a/lunaix-os/kernel/fs/ramfs/ramfs.c +++ b/lunaix-os/kernel/fs/ramfs/ramfs.c @@ -9,7 +9,7 @@ * */ #include -#include +#include #include #include #include @@ -64,9 +64,10 @@ __ramfs_mknod(struct v_dnode* dnode, struct v_inode** nod_out, u32_t flags) rinode->flags = flags; inode->data = rinode; + inode->itype = VFS_IFFILE; - if (!(flags & RAMF_DIR)) { - inode->itype = VFS_IFFILE; + if ((flags & RAMF_DIR)) { + inode->itype |= VFS_IFDIR; } if ((flags & RAMF_SYMLINK)) { @@ -86,11 +87,16 @@ __ramfs_mknod(struct v_dnode* dnode, struct v_inode** nod_out, u32_t flags) int ramfs_readdir(struct v_file* file, struct dir_context* dctx) { - int i = 0; + unsigned int i = 2; struct v_dnode *pos, *n; + + if (fsapi_handle_pseudo_dirent(file, dctx)) { + return 1; + } + llist_for_each(pos, n, &file->dnode->children, siblings) { - if (i++ >= dctx->index) { + if (i++ >= file->f_pos) { dctx->read_complete_callback(dctx, pos->name.value, pos->name.len, @@ -108,7 +114,7 @@ ramfs_mkdir(struct v_inode* this, struct v_dnode* dnode) } int -ramfs_create(struct v_inode* this, struct v_dnode* dnode) +ramfs_create(struct v_inode* this, struct v_dnode* dnode, unsigned int itype) { return __ramfs_mknod(dnode, NULL, RAMF_FILE); } @@ -135,17 +141,6 @@ ramfs_unmount(struct v_superblock* vsb) return 0; } -void -ramfs_init() -{ - struct filesystem* ramfs = fsm_new_fs("ramfs", -1); - ramfs->mount = ramfs_mount; - ramfs->unmount = ramfs_unmount; - - fsm_register(ramfs); -} -EXPORT_FILE_SYSTEM(ramfs, ramfs_init); - int ramfs_mksymlink(struct v_inode* this, const char* target) { @@ -162,9 +157,10 @@ ramfs_mksymlink(struct v_inode* this, const char* target) memcpy(symlink, target, len); - this->itype |= VFS_IFSYMLINK; + this->itype = VFS_IFSYMLINK; rinode->flags |= RAMF_SYMLINK; rinode->symlink = symlink; + rinode->size = len; return 0; } @@ -180,17 +176,17 @@ ramfs_read_symlink(struct v_inode* this, const char** path_out) *path_out = rinode->symlink; - return 0; + return rinode->size; } int -ramfs_unlink(struct v_inode* this) +ramfs_unlink(struct v_inode* this, struct v_dnode* name) { struct ram_inode* rinode = RAM_INODE(this->data); if ((rinode->flags & RAMF_SYMLINK)) { rinode->flags &= ~RAMF_SYMLINK; - this->itype &= ~VFS_IFSYMLINK; + this->itype &= ~F_SYMLINK; vfree(rinode->symlink); @@ -202,6 +198,17 @@ ramfs_unlink(struct v_inode* this) return 0; } +static void +ramfs_init() +{ + struct filesystem* fs; + fs = fsapi_fs_declare("ramfs", FSTYPE_PSEUDO); + + fsapi_fs_set_mntops(fs, ramfs_mount, ramfs_unmount); + fsapi_fs_finalise(fs); +} +EXPORT_FILE_SYSTEM(ramfs, ramfs_init); + const struct v_inode_ops ramfs_inode_ops = { .mkdir = ramfs_mkdir, .rmdir = default_inode_rmdir, .dir_lookup = diff --git a/lunaix-os/kernel/fs/twifs/twifs.c b/lunaix-os/kernel/fs/twifs/twifs.c index 2894f96..8f4ed8d 100644 --- a/lunaix-os/kernel/fs/twifs/twifs.c +++ b/lunaix-os/kernel/fs/twifs/twifs.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -79,6 +79,12 @@ __twifs_mount(struct v_superblock* vsb, struct v_dnode* mount_point) return 0; } +int +__twifs_unmount(struct v_superblock* vsb) +{ + return 0; +} + int __twifs_fwrite(struct v_inode* inode, void* buffer, size_t len, size_t fpos) { @@ -132,7 +138,7 @@ __twifs_dirlookup(struct v_inode* inode, struct v_dnode* dnode) { struct twifs_node* twi_node = (struct twifs_node*)inode->data; - if ((twi_node->itype & F_FILE)) { + if (!check_directory_node(inode)) { return ENOTDIR; } @@ -165,14 +171,17 @@ int __twifs_iterate_dir(struct v_file* file, struct dir_context* dctx) { struct twifs_node* twi_node = (struct twifs_node*)(file->inode->data); - int counter = 0; + unsigned int counter = 2; struct twifs_node *pos, *n; + if (fsapi_handle_pseudo_dirent(file, dctx)) { + return 1; + } + llist_for_each(pos, n, &twi_node->children, siblings) { - if (counter++ >= dctx->index) { - dctx->index = counter; - dctx->read_complete_callback( + if (counter++ >= file->f_pos) { + fsapi_dir_report( dctx, pos->name.value, pos->name.len, vfs_get_dtype(pos->itype)); return 1; } @@ -194,7 +203,7 @@ __twifs_openfile(struct v_inode* inode, struct v_file* file) int twifs_rm_node(struct twifs_node* node) { - if (!(node->itype & F_FILE) && !llist_empty(&node->children)) { + if (check_itype(node->itype, VFS_IFDIR) && !llist_empty(&node->children)) { return ENOTEMPTY; } llist_delete(&node->siblings); @@ -243,16 +252,13 @@ twifs_dir_node(struct twifs_node* parent, const char* fmt, ...) void twifs_init() { - twi_pile = cake_new_pile("twifs_node", sizeof(struct twifs_node), 1, 0); - - struct filesystem* twifs = vzalloc(sizeof(struct filesystem)); - twifs->fs_name = HSTR("twifs", 5); - twifs->mount = __twifs_mount; - twifs->types = FSTYPE_ROFS; - twifs->fs_id = 0; - - fsm_register(twifs); + struct filesystem* fs; + fs = fsapi_fs_declare("twifs", FSTYPE_PSEUDO | FSTYPE_ROFS); + + fsapi_fs_set_mntops(fs, __twifs_mount, __twifs_unmount); + fsapi_fs_finalise(fs); + twi_pile = cake_new_pile("twifs_node", sizeof(struct twifs_node), 1, 0); fs_root = twifs_dir_node(NULL, NULL, 0, 0); } EXPORT_FILE_SYSTEM(twifs, twifs_init); diff --git a/lunaix-os/kernel/fs/twimap.c b/lunaix-os/kernel/fs/twimap.c index e72f6c2..dff2de3 100644 --- a/lunaix-os/kernel/fs/twimap.c +++ b/lunaix-os/kernel/fs/twimap.c @@ -39,16 +39,22 @@ int twimap_read(struct twimap* map, void* buffer, size_t len, size_t fpos) { map->buffer = valloc(TWIMAP_BUFFER_SIZE); + map->size_acc = 0; + map->reset(map); // FIXME what if TWIMAP_BUFFER_SIZE is not big enough? - size_t pos = 0; - do { + size_t pos = map->size_acc; + while (pos <= fpos) { map->size_acc = 0; map->read(map); pos += map->size_acc; - } while (pos <= fpos && map->go_next(map)); + + if (!map->go_next(map)) { + break; + } + } if (pos <= fpos) { vfree(map->buffer); diff --git a/lunaix-os/kernel/fs/vfs.c b/lunaix-os/kernel/fs/vfs.c index d4bc56a..415e36b 100644 --- a/lunaix-os/kernel/fs/vfs.c +++ b/lunaix-os/kernel/fs/vfs.c @@ -72,12 +72,6 @@ struct hstr vfs_ddot = HSTR("..", 2); struct hstr vfs_dot = HSTR(".", 1); struct hstr vfs_empty = HSTR("", 0); -struct v_superblock* -vfs_sb_alloc(); - -void -vfs_sb_free(struct v_superblock* sb); - static int __vfs_try_evict_dnode(struct lru_node* obj); @@ -97,8 +91,8 @@ vfs_init() dnode_cache = vzalloc(VFS_HASHTABLE_SIZE * sizeof(struct hbucket)); - dnode_lru = lru_new_zone(__vfs_try_evict_dnode); - inode_lru = lru_new_zone(__vfs_try_evict_inode); + dnode_lru = lru_new_zone("vfs_dnode", __vfs_try_evict_dnode); + inode_lru = lru_new_zone("vfs_inode", __vfs_try_evict_inode); hstr_rehash(&vfs_ddot, HSTR_FULL_HASH); hstr_rehash(&vfs_dot, HSTR_FULL_HASH); @@ -121,6 +115,19 @@ __dcache_hash(struct v_dnode* parent, u32_t* hash) return &dnode_cache[_hash & VFS_HASH_MASK]; } +static inline int +__sync_inode_nolock(struct v_inode* inode) +{ + pcache_commit_all(inode); + + int errno = ENOTSUP; + if (inode->ops->sync) { + errno = inode->ops->sync(inode); + } + + return errno; +} + struct v_dnode* vfs_dcache_lookup(struct v_dnode* parent, struct hstr* str) { @@ -137,7 +144,7 @@ vfs_dcache_lookup(struct v_dnode* parent, struct hstr* str) struct v_dnode *pos, *n; hashtable_bucket_foreach(slot, pos, n, hash_list) { - if (pos->name.hash == hash) { + if (pos->name.hash == hash && pos->parent == parent) { return pos; } } @@ -200,7 +207,7 @@ vfs_open(struct v_dnode* dnode, struct v_file** file) vfile->ref_count = ATOMIC_VAR_INIT(1); vfile->ops = inode->default_fops; - if ((inode->itype & F_MFILE) && !inode->pg_cache) { + if (check_file_node(inode) && !inode->pg_cache) { struct pcache* pcache = vzalloc(sizeof(struct pcache)); pcache_init(pcache); pcache->master = inode; @@ -230,6 +237,7 @@ vfs_assign_inode(struct v_dnode* assign_to, struct v_inode* inode) llist_delete(&assign_to->aka_list); assign_to->inode->link_count--; } + llist_append(&inode->aka_dnodes, &assign_to->aka_list); assign_to->inode = inode; inode->link_count++; @@ -260,38 +268,57 @@ vfs_link(struct v_dnode* to_link, struct v_dnode* name) int vfs_pclose(struct v_file* file, pid_t pid) { + struct v_inode* inode; int errno = 0; + if (file->ref_count > 1) { atomic_fetch_sub(&file->ref_count, 1); - } else if (!(errno = file->ops->close(file))) { - atomic_fetch_sub(&file->dnode->ref_count, 1); - file->inode->open_count--; - - /* - * Prevent dead lock. - * This happened when process is terminated while blocking on read. - * In that case, the process is still holding the inode lock and it - will never get released. - * The unlocking should also include ownership check. - * - * To see why, consider two process both open the same file both with - * fd=x. - * Process A: busy on reading x - * Process B: do nothing with x - * Assuming that, after a very short time, process B get terminated - * while process A is still busy in it's reading business. By this - * design, the inode lock of this file x is get released by B rather - * than A. And this will cause a probable race condition on A if other - * process is writing to this file later after B exit. - */ - if (mutex_on_hold(&file->inode->lock)) { - mutex_unlock_for(&file->inode->lock, pid); - } - mnt_chillax(file->dnode->mnt); + return 0; + } + + inode = file->inode; + + /* + * Prevent dead lock. + * This happened when process is terminated while blocking on read. + * In that case, the process is still holding the inode lock and it + will never get released. + * The unlocking should also include ownership check. + * + * To see why, consider two process both open the same file both with + * fd=x. + * Process A: busy on reading x + * Process B: do nothing with x + * Assuming that, after a very short time, process B get terminated + * while process A is still busy in it's reading business. By this + * design, the inode lock of this file x is get released by B rather + * than A. And this will cause a probable race condition on A if other + * process is writing to this file later after B exit. + */ + + if (mutex_on_hold(&inode->lock)) { + mutex_unlock_for(&inode->lock, pid); + } + + lock_inode(inode); + + pcache_commit_all(inode); + if ((errno = file->ops->close(file))) { + goto unlock; + } + + atomic_fetch_sub(&file->dnode->ref_count, 1); + inode->open_count--; - pcache_commit_all(file->inode); - cake_release(file_pile, file); + if (!inode->open_count) { + __sync_inode_nolock(inode); } + + mnt_chillax(file->dnode->mnt); + cake_release(file_pile, file); + +unlock: + unlock_inode(inode); return errno; } @@ -307,6 +334,18 @@ vfs_free_fd(struct v_fd* fd) cake_release(fd_pile, fd); } +int +vfs_isync(struct v_inode* inode) +{ + lock_inode(inode); + + int errno = __sync_inode_nolock(inode); + + unlock_inode(inode); + + return errno; +} + int vfs_fsync(struct v_file* file) { @@ -315,18 +354,7 @@ vfs_fsync(struct v_file* file) return errno; } - lock_inode(file->inode); - - pcache_commit_all(file->inode); - - errno = ENOTSUP; - if (file->ops->sync) { - errno = file->ops->sync(file); - } - - unlock_inode(file->inode); - - return errno; + return vfs_isync(file->inode); } int @@ -348,12 +376,30 @@ vfs_sb_alloc() memset(sb, 0, sizeof(*sb)); llist_init_head(&sb->sb_list); sb->i_cache = vzalloc(VFS_HASHTABLE_SIZE * sizeof(struct hbucket)); + sb->ref_count = 1; return sb; } +void +vfs_sb_ref(struct v_superblock* sb) +{ + sb->ref_count++; +} + void vfs_sb_free(struct v_superblock* sb) { + assert(sb->ref_count); + + sb->ref_count--; + if (sb->ref_count) { + return; + } + + if (sb->ops.release) { + sb->ops.release(sb); + } + vfree(sb->i_cache); cake_release(superblock_pile, sb); } @@ -406,7 +452,7 @@ vfs_d_alloc(struct v_dnode* parent, struct hstr* name) hstrcpy(&dnode->name, name); if (parent) { - dnode->super_block = parent->super_block; + vfs_d_assign_sb(dnode, parent->super_block); dnode->mnt = parent->mnt; } @@ -435,6 +481,11 @@ vfs_d_free(struct v_dnode* dnode) vfs_dcache_remove(pos); } + if (dnode->destruct) { + dnode->destruct(dnode); + } + + vfs_sb_free(dnode->super_block); vfree((void*)dnode->name.value); cake_release(dnode_pile, dnode); } @@ -484,11 +535,11 @@ vfs_i_alloc(struct v_superblock* sb) sb->ops.init_inode(sb, inode); - inode->sb = sb; inode->ctime = clock_unixtime(); inode->atime = inode->ctime; inode->mtime = inode->ctime; + vfs_i_assign_sb(inode, sb); lru_use_one(inode_lru, &inode->lru); return inode; } @@ -506,15 +557,22 @@ vfs_i_free(struct v_inode* inode) if (inode->destruct) { inode->destruct(inode); } + + vfs_sb_free(inode->sb); hlist_delete(&inode->hash_list); cake_release(inode_pile, inode); } /* ---- System call definition and support ---- */ -#define FLOCATE_CREATE_EMPTY 1 -#define FLOCATE_CREATE_ONLY 2 -#define FLOCATE_NOFOLLOW 4 +// make a new name when not exists +#define FLOC_MAYBE_MKNAME 1 + +// name must be non-exist and made. +#define FLOC_MKNAME 2 + +// no follow symlink +#define FLOC_NOFOLLOW 4 int vfs_getfd(int fd, struct v_fd** fd_s) @@ -525,53 +583,104 @@ vfs_getfd(int fd, struct v_fd** fd_s) return EBADF; } -int +static int +__vfs_mknod(struct v_inode* parent, struct v_dnode* dnode, + unsigned int itype, dev_t* dev) +{ + int errno; + + errno = parent->ops->create(parent, dnode, itype); + if (errno) { + return errno; + } + + return 0; +} + +struct file_locator { + struct v_dnode* dir; + struct v_dnode* file; + bool fresh; +}; + +/** + * @brief unlock the file locator (floc) if possible. + * If the file to be located if not exists, and + * any FLOC_*MKNAME flag is set, then the parent + * dnode will be locked until the file has been properly + * finalised by subsequent logic. + * + * @param floc + */ +static inline void +__floc_try_unlock(struct file_locator* floc) +{ + if (floc->fresh) { + assert(floc->dir); + unlock_dnode(floc->dir); + } +} + +static int __vfs_try_locate_file(const char* path, - struct v_dnode** fdir, - struct v_dnode** file, + struct file_locator* floc, int options) { char name_str[VFS_NAME_MAXLEN]; + struct v_dnode *fdir, *file; struct hstr name = HSTR(name_str, 0); int errno, woption = 0; - if ((options & FLOCATE_NOFOLLOW)) { + if ((options & FLOC_NOFOLLOW)) { woption |= VFS_WALK_NOFOLLOW; + options &= ~FLOC_NOFOLLOW; } + floc->fresh = false; name_str[0] = 0; - if ((errno = vfs_walk_proc(path, fdir, &name, woption | VFS_WALK_PARENT))) { + errno = vfs_walk_proc(path, &fdir, &name, woption | VFS_WALK_PARENT); + if (errno) { return errno; } - errno = vfs_walk(*fdir, name.value, file, NULL, woption); + errno = vfs_walk(fdir, name.value, &file, NULL, woption); - if (errno != ENOENT && (options & FLOCATE_CREATE_ONLY)) { - return EEXIST; + if (errno && errno != ENOENT) { + goto done; + } + + if (!errno) { + if ((options & FLOC_MKNAME)) { + errno = EEXIST; + } + goto done; } - if (errno != ENOENT || - !(options & (FLOCATE_CREATE_EMPTY | FLOCATE_CREATE_ONLY))) { - return errno; + // errno == ENOENT + if (!options) { + goto done; } - struct v_dnode* parent = *fdir; - struct v_dnode* file_new = vfs_d_alloc(parent, &name); + errno = vfs_check_writable(fdir); + if (errno) { + goto done; + } + + floc->fresh = true; + + file = vfs_d_alloc(fdir, &name); - if (!file_new) { + if (!file) { return ENOMEM; } - lock_dnode(parent); + lock_dnode(fdir); - if (!(errno = parent->inode->ops->create(parent->inode, file_new))) { - vfs_dcache_add(parent, file_new); - *file = file_new; - } else { - vfs_d_free(file_new); - } + vfs_dcache_add(fdir, file); - unlock_dnode(parent); +done: + floc->dir = fdir; + floc->file = file; return errno; } @@ -582,32 +691,61 @@ vfs_do_open(const char* path, int options) int errno, fd, loptions = 0; struct v_dnode *dentry, *file; struct v_file* ofile = NULL; + struct file_locator floc; + struct v_inode* inode; if ((options & FO_CREATE)) { - loptions |= FLOCATE_CREATE_EMPTY; + loptions |= FLOC_MAYBE_MKNAME; } else if ((options & FO_NOFOLLOW)) { - loptions |= FLOCATE_NOFOLLOW; + loptions |= FLOC_NOFOLLOW; } - errno = __vfs_try_locate_file(path, &dentry, &file, loptions); + errno = __vfs_try_locate_file(path, &floc, loptions); - if (!errno && !(errno = vfs_alloc_fdslot(&fd))) { + if (errno || (errno = vfs_alloc_fdslot(&fd))) { + return errno; + } + + file = floc.file; + dentry = floc.dir; - if (errno || (errno = vfs_open(file, &ofile))) { + if (floc.fresh) { + errno = __vfs_mknod(dentry->inode, file, VFS_IFFILE, NULL); + if (errno) { + vfs_d_free(file); + __floc_try_unlock(&floc); return errno; } - struct v_fd* fd_s = cake_grab(fd_pile); - memset(fd_s, 0, sizeof(*fd_s)); + __floc_try_unlock(&floc); + } + - ofile->f_pos = ofile->inode->fsize & -((options & FO_APPEND) != 0); - fd_s->file = ofile; - fd_s->flags = options; - __current->fdtable->fds[fd] = fd_s; - return fd; + if ((errno = vfs_open(file, &ofile))) { + return errno; } - return errno; + inode = ofile->inode; + lock_inode(inode); + + struct v_fd* fd_s = cake_grab(fd_pile); + memset(fd_s, 0, sizeof(*fd_s)); + + if ((options & O_TRUNC)) { + file->inode->fsize = 0; + } + + if (vfs_get_dtype(inode->itype) == DT_DIR) { + ofile->f_pos = 0; + } + + fd_s->file = ofile; + fd_s->flags = options; + __current->fdtable->fds[fd] = fd_s; + + unlock_inode(inode); + + return fd; } __DEFINE_LXSYSCALL2(int, open, const char*, path, int, options) @@ -642,7 +780,7 @@ __vfs_readdir_callback(struct dir_context* dctx, const int dtype) { struct lx_dirent* dent = (struct lx_dirent*)dctx->cb_data; - strncpy(dent->d_name, name, DIRENT_NAME_MAX_LEN); + strncpy(dent->d_name, name, MIN(len, DIRENT_NAME_MAX_LEN)); dent->d_nlen = len; dent->d_type = dtype; } @@ -660,28 +798,23 @@ __DEFINE_LXSYSCALL2(int, sys_readdir, int, fd, struct lx_dirent*, dent) lock_inode(inode); - if ((inode->itype & F_FILE)) { + if (!check_directory_node(inode)) { errno = ENOTDIR; - } else { - struct dir_context dctx = (struct dir_context){ - .cb_data = dent, - .index = dent->d_offset, - .read_complete_callback = __vfs_readdir_callback}; - errno = 1; - if (dent->d_offset == 0) { - __vfs_readdir_callback(&dctx, vfs_dot.value, vfs_dot.len, DT_DIR); - } else if (dent->d_offset == 1) { - __vfs_readdir_callback(&dctx, vfs_ddot.value, vfs_ddot.len, DT_DIR); - } else { - dctx.index -= 2; - if ((errno = fd_s->file->ops->readdir(fd_s->file, &dctx)) != 1) { - unlock_inode(inode); - goto done; - } - } - dent->d_offset++; + goto unlock; + } + + struct dir_context dctx = (struct dir_context) { + .cb_data = dent, + .read_complete_callback = __vfs_readdir_callback + }; + + if ((errno = fd_s->file->ops->readdir(fd_s->file, &dctx)) != 1) { + goto unlock; } + dent->d_offset++; + fd_s->file->f_pos++; +unlock: unlock_inode(inode); done: @@ -697,7 +830,7 @@ __DEFINE_LXSYSCALL3(int, read, int, fd, void*, buf, size_t, count) } struct v_file* file = fd_s->file; - if (!(file->inode->itype & F_FILE)) { + if (check_directory_node(file->inode)) { errno = EISDIR; goto done; } @@ -706,7 +839,7 @@ __DEFINE_LXSYSCALL3(int, read, int, fd, void*, buf, size_t, count) file->inode->atime = clock_unixtime(); - if ((file->inode->itype & VFS_IFSEQDEV) || (fd_s->flags & FO_DIRECT)) { + if (check_seqdev_node(file->inode) || (fd_s->flags & FO_DIRECT)) { errno = file->ops->read(file->inode, buf, count, file->f_pos); } else { errno = pcache_read(file->inode, buf, count, file->f_pos); @@ -732,34 +865,41 @@ __DEFINE_LXSYSCALL3(int, write, int, fd, void*, buf, size_t, count) goto done; } + struct v_inode* inode; struct v_file* file = fd_s->file; if ((errno = vfs_check_writable(file->dnode))) { goto done; } - if (!(file->inode->itype & F_FILE)) { + if (check_directory_node(file->inode)) { errno = EISDIR; goto done; } - lock_inode(file->inode); + inode = file->inode; + lock_inode(inode); - file->inode->mtime = clock_unixtime(); + inode->mtime = clock_unixtime(); + if ((fd_s->flags & O_APPEND)) { + file->f_pos = inode->fsize; + } - if ((file->inode->itype & VFS_IFSEQDEV) || (fd_s->flags & FO_DIRECT)) { - errno = file->ops->write(file->inode, buf, count, file->f_pos); + if (check_seqdev_node(inode) || (fd_s->flags & FO_DIRECT)) { + errno = file->ops->write(inode, buf, count, file->f_pos); } else { - errno = pcache_write(file->inode, buf, count, file->f_pos); + errno = pcache_write(inode, buf, count, file->f_pos); } if (errno > 0) { file->f_pos += errno; - unlock_inode(file->inode); + inode->fsize = MAX(inode->fsize, file->f_pos); + + unlock_inode(inode); return errno; } - unlock_inode(file->inode); + unlock_inode(inode); done: return DO_STATUS(errno); @@ -774,34 +914,42 @@ __DEFINE_LXSYSCALL3(int, lseek, int, fd, int, offset, int, options) } struct v_file* file = fd_s->file; + struct v_inode* inode = file->inode; if (!file->ops->seek) { errno = ENOTSUP; goto done; } - lock_inode(file->inode); + lock_inode(inode); int overflow = 0; int fpos = file->f_pos; + + if (vfs_get_dtype(inode->itype) == DT_DIR) { + options = (options != FSEEK_END) ? options : FSEEK_SET; + } + switch (options) { case FSEEK_CUR: - overflow = sadd_overflow((int)file->f_pos, offset, &fpos); + overflow = sadd_of((int)file->f_pos, offset, &fpos); break; case FSEEK_END: - overflow = sadd_overflow((int)file->inode->fsize, offset, &fpos); + overflow = sadd_of((int)inode->fsize, offset, &fpos); break; case FSEEK_SET: fpos = offset; break; } + if (overflow) { errno = EOVERFLOW; - } else if (!(errno = file->ops->seek(file->inode, fpos))) { - file->f_pos = fpos; + } + else { + errno = file->ops->seek(file, fpos); } - unlock_inode(file->inode); + unlock_inode(inode); done: return DO_STATUS(errno); @@ -844,28 +992,42 @@ vfs_readlink(struct v_dnode* dnode, char* buf, size_t size) { const char* link; struct v_inode* inode = dnode->inode; - if (inode->ops->read_symlink) { - lock_inode(inode); - int errno = inode->ops->read_symlink(inode, &link); - strncpy(buf, link, size); + if (!check_symlink_node(inode)) { + return EINVAL; + } - unlock_inode(inode); - return errno; + if (!inode->ops->read_symlink) { + return ENOTSUP; } - return 0; + + lock_inode(inode); + + int errno = inode->ops->read_symlink(inode, &link); + if (errno >= 0) { + strncpy(buf, link, MIN(size, (size_t)errno)); + } + + unlock_inode(inode); + return errno; } int vfs_get_dtype(int itype) { - if ((itype & VFS_IFSYMLINK) == VFS_IFSYMLINK) { - return DT_SYMLINK; - } else if (!(itype & VFS_IFFILE)) { - return DT_DIR; - } else { - return DT_FILE; + int dtype = DT_FILE; + if (check_itype(itype, VFS_IFSYMLINK)) { + dtype |= DT_SYMLINK; + } + + if (check_itype(itype, VFS_IFDIR)) { + dtype |= DT_DIR; + return dtype; } + + // TODO other types + + return dtype; } __DEFINE_LXSYSCALL3(int, realpathat, int, fd, char*, buf, size_t, size) @@ -974,7 +1136,7 @@ __DEFINE_LXSYSCALL1(int, rmdir, const char*, pathname) lock_dnode(parent); lock_inode(parent->inode); - if (!(dnode->inode->itype & F_MFILE)) { + if (check_directory_node(dnode->inode)) { errno = parent->inode->ops->rmdir(parent->inode, dnode); if (!errno) { vfs_dcache_remove(dnode); @@ -1016,16 +1178,18 @@ __DEFINE_LXSYSCALL1(int, mkdir, const char*, path) goto done; } + struct v_inode* inode = parent->inode; + lock_dnode(parent); - lock_inode(parent->inode); + lock_inode(inode); if ((parent->super_block->fs->types & FSTYPE_ROFS)) { errno = ENOTSUP; - } else if (!parent->inode->ops->mkdir) { + } else if (!inode->ops->mkdir) { errno = ENOTSUP; - } else if ((parent->inode->itype & F_FILE)) { + } else if (!check_directory_node(inode)) { errno = ENOTDIR; - } else if (!(errno = parent->inode->ops->mkdir(parent->inode, dir))) { + } else if (!(errno = inode->ops->mkdir(inode, dir))) { vfs_dcache_add(parent, dir); goto cleanup; } @@ -1033,7 +1197,7 @@ __DEFINE_LXSYSCALL1(int, mkdir, const char*, path) vfs_d_free(dir); cleanup: - unlock_inode(parent->inode); + unlock_inode(inode); unlock_dnode(parent); done: return DO_STATUS(errno); @@ -1057,8 +1221,8 @@ __vfs_do_unlink(struct v_dnode* dnode) if (inode->open_count) { errno = EBUSY; - } else if ((inode->itype & F_MFILE)) { - errno = inode->ops->unlink(inode); + } else if (!check_directory_node(inode)) { + errno = inode->ops->unlink(inode, dnode); if (!errno) { vfs_d_free(dnode); } @@ -1105,16 +1269,30 @@ done: __DEFINE_LXSYSCALL2(int, link, const char*, oldpath, const char*, newpath) { int errno; - struct v_dnode *dentry, *to_link, *name_dentry, *name_file; + struct file_locator floc; + struct v_dnode *to_link, *name_file; - errno = __vfs_try_locate_file(oldpath, &dentry, &to_link, 0); + errno = __vfs_try_locate_file(oldpath, &floc, 0); + if (errno) { + goto done; + } + + __floc_try_unlock(&floc); + + to_link = floc.file; + errno = __vfs_try_locate_file(newpath, &floc, FLOC_MKNAME); if (!errno) { - errno = __vfs_try_locate_file( - newpath, &name_dentry, &name_file, FLOCATE_CREATE_ONLY); - if (!errno) { - errno = vfs_link(to_link, name_file); - } + goto done; + } + + name_file = floc.file; + errno = vfs_link(to_link, name_file); + if (errno) { + vfs_d_free(name_file); } + +done: + __floc_try_unlock(&floc); return DO_STATUS(errno); } @@ -1204,28 +1382,44 @@ __DEFINE_LXSYSCALL2( int, symlink, const char*, pathname, const char*, link_target) { int errno; - struct v_dnode *dnode, *file; - if ((errno = __vfs_try_locate_file( - pathname, &dnode, &file, FLOCATE_CREATE_ONLY))) { + struct file_locator floc; + struct v_dnode *file; + struct v_inode *f_ino; + + errno = __vfs_try_locate_file(pathname, &floc, FLOC_MKNAME); + if (errno) { goto done; } - if ((errno = vfs_check_writable(file))) { + file = floc.file; + errno = __vfs_mknod(floc.dir->inode, file, VFS_IFSYMLINK, NULL); + if (errno) { + vfs_d_free(file); goto done; } - if (!file->inode->ops->set_symlink) { + f_ino = file->inode; + + assert(f_ino); + + errno = vfs_check_writable(file); + if (errno) { + goto done; + } + + if (!f_ino->ops->set_symlink) { errno = ENOTSUP; goto done; } - lock_inode(file->inode); + lock_inode(f_ino); - errno = file->inode->ops->set_symlink(file->inode, link_target); + errno = f_ino->ops->set_symlink(f_ino, link_target); - unlock_inode(file->inode); + unlock_inode(f_ino); done: + __floc_try_unlock(&floc); return DO_STATUS(errno); } @@ -1261,7 +1455,7 @@ vfs_do_chdir(struct proc_info* proc, struct v_dnode* dnode) lock_dnode(dnode); - if ((dnode->inode->itype & F_FILE)) { + if (!check_directory_node(dnode->inode)) { errno = ENOTDIR; goto done; } @@ -1456,7 +1650,7 @@ __DEFINE_LXSYSCALL2(int, fstat, int, fd, struct file_stat*, stat) .st_ioblksize = PAGE_SIZE, .st_blksize = vino->sb->blksize}; - if (VFS_DEVFILE(vino->itype)) { + if (check_device_node(vino)) { struct device* rdev = resolve_device(vino->data); if (!rdev || rdev->magic != DEV_STRUCT_MAGIC) { errno = EINVAL; diff --git a/lunaix-os/kernel/kprint/kprintf.c b/lunaix-os/kernel/kprint/kprintf.c index 4c1ee40..02bc0ee 100644 --- a/lunaix-os/kernel/kprint/kprintf.c +++ b/lunaix-os/kernel/kprint/kprintf.c @@ -68,6 +68,15 @@ kprintf_m(const char* component, const char* fmt, va_list args) kprintf_ml(component, level, fmt, args); } +void +kprintf_v(const char* component, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + kprintf_m(component, fmt, args); + va_end(args); +} + static void __twimap_kprintf_read(struct twimap* map) { diff --git a/lunaix-os/kernel/lrud.c b/lunaix-os/kernel/lrud.c new file mode 100644 index 0000000..dc9ee34 --- /dev/null +++ b/lunaix-os/kernel/lrud.c @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include + +#include + +static struct llist_header zone_lead = { .next = &zone_lead, .prev = &zone_lead }; + +DEFINE_SPINLOCK_OPS(struct lru_zone*, lock); + + +static void +__do_evict_lockless(struct lru_zone* zone, struct llist_header* elem) +{ + llist_delete(elem); + if (!zone->try_evict(container_of(elem, struct lru_node, lru_nodes))) { + // if the node is unable to evict, raise it's rank by one, so + // others can have chance in the next round + struct llist_header* new_tail = zone->lead_node.prev; + llist_prepend(new_tail, elem); + } else { + zone->objects--; + } + + zone->evict_stats.n_single++; +} + +static void +__lru_evict_all_lockness(struct lru_zone* zone) +{ + struct llist_header* tail = zone->lead_node.prev; + while (tail != &zone->lead_node) { + __do_evict_lockless(zone, tail); + tail = tail->prev; + } +} + +struct lru_zone* +lru_new_zone(const char* name, evict_cb try_evict_cb) +{ + struct lru_zone* zone = vzalloc(sizeof(struct lru_zone)); + if (!zone) { + return NULL; + } + + zone->try_evict = try_evict_cb; + + strncpy(zone->name, name, sizeof(zone->name) - 1); + llist_init_head(&zone->lead_node); + llist_append(&zone_lead, &zone->zones); + spinlock_init(&zone->lock); + + return zone; +} + +void +lru_free_zone(struct lru_zone* zone) +{ + lock(zone); + + __lru_evict_all_lockness(zone); + + if (llist_empty(&zone->lead_node)) { + llist_delete(&zone->zones); + vfree(zone); + return; + } + + /* + We are unable to free it at this moment, + (probably due to tricky things happened + to some cached object). Thus mark it and + let the daemon try to free it asynchronously + */ + zone->delayed_free = true; + zone->attempts++; + + unlock(zone); +} + +void +lru_use_one(struct lru_zone* zone, struct lru_node* node) +{ + lock(zone); + + assert(!zone->delayed_free); + + if (node->lru_nodes.next && node->lru_nodes.prev) { + llist_delete(&node->lru_nodes); + } + else { + zone->objects++; + } + + llist_prepend(&zone->lead_node, &node->lru_nodes); + zone->hotness++; + + unlock(zone); +} + +void +lru_evict_one(struct lru_zone* zone) +{ + lock(zone); + + struct llist_header* tail = zone->lead_node.prev; + if (tail == &zone->lead_node) { + return; + } + + __do_evict_lockless(zone, tail); + + unlock(zone); +} + +void +lru_evict_half(struct lru_zone* zone) +{ + lock(zone); + + int target = (int)(zone->objects / 2); + struct llist_header* tail = zone->lead_node.prev; + while (tail != &zone->lead_node && target > 0) { + __do_evict_lockless(zone, tail); + tail = tail->prev; + target--; + } + + zone->evict_stats.n_half++; + + unlock(zone); +} + +void +lru_evict_all(struct lru_zone* zone) +{ + lock(zone); + + __lru_evict_all_lockness(zone); + + zone->evict_stats.n_full++; + + unlock(zone); +} + +void +lru_remove(struct lru_zone* zone, struct lru_node* node) +{ + lock(zone); + + if (node->lru_nodes.next && node->lru_nodes.prev) { + llist_delete(&node->lru_nodes); + } + zone->objects--; + + unlock(zone); +} + +static void +read_lrulist_entry(struct twimap* map) +{ + struct lru_zone* zone; + + zone = twimap_index(map, struct lru_zone*); + twimap_printf(map, "%s, %d, %d, %d, %d, %d, ", + zone->name, + zone->objects, + zone->hotness, + zone->evict_stats.n_single, + zone->evict_stats.n_half, + zone->evict_stats.n_full); + + if (zone->delayed_free) { + twimap_printf(map, "freeing %d attempts\n", zone->attempts); + } + else { + twimap_printf(map, "active\n"); + } +} + +static void +read_lrulist_reset(struct twimap* map) +{ + map->index = container_of(&zone_lead, struct lru_zone, zones); + twimap_printf(map, "name, n_objs, hot, n_evt, n_half, n_full, status\n"); +} + +static int +read_lrulist_next(struct twimap* map) +{ + struct lru_zone* zone; + struct llist_header* next; + + zone = twimap_index(map, struct lru_zone*); + next = zone->zones.next; + if (next == &zone_lead) { + return false; + } + + map->index = container_of(next, struct lru_zone, zones); + return true; +} + +static void +lru_pool_twimappable() +{ + struct twimap* map; + + map = twifs_mapping(NULL, NULL, "lru_pool"); + map->read = read_lrulist_entry; + map->reset = read_lrulist_reset; + map->go_next = read_lrulist_next; +} +EXPORT_TWIFS_PLUGIN(__lru_twimap, lru_pool_twimappable); \ No newline at end of file diff --git a/lunaix-os/kernel/lunad.c b/lunaix-os/kernel/lunad.c index ff52e71..d9e138f 100644 --- a/lunaix-os/kernel/lunad.c +++ b/lunaix-os/kernel/lunad.c @@ -58,7 +58,7 @@ fail: static void lunad_do_usr() { // No, these are not preemptive - cpu_disable_interrupt(); + no_preemption(); if (!mount_bootmedium() || !exec_initd()) { fail("failed to initd"); @@ -89,11 +89,11 @@ lunad_main() thread (which is preemptive!) */ - cpu_enable_interrupt(); + set_preemption(); while (1) { cleanup_detached_threads(); - sched_pass(); + yield_current(); } } diff --git a/lunaix-os/kernel/mm/cake_export.c b/lunaix-os/kernel/mm/cake_export.c index 956423b..377a9ad 100644 --- a/lunaix-os/kernel/mm/cake_export.c +++ b/lunaix-os/kernel/mm/cake_export.c @@ -17,7 +17,8 @@ __cake_stat_gonext(struct twimap* map) void __cake_stat_reset(struct twimap* map) { - map->index = container_of(piles.next, struct cake_pile, piles); + map->index = container_of(&piles, struct cake_pile, piles); + twimap_printf(map, "name, n_cakes, pg/cake, slices/cake, n_slices\n"); } void @@ -98,7 +99,6 @@ cake_export() map->reset = __cake_stat_reset; map->go_next = __cake_stat_gonext; map->read = __cake_rd_stat; - __cake_stat_reset(map); struct cake_pile *pos, *n; llist_for_each(pos, n, &piles, piles) diff --git a/lunaix-os/kernel/mm/valloc.c b/lunaix-os/kernel/mm/valloc.c index 113b7c6..67c8eac 100644 --- a/lunaix-os/kernel/mm/valloc.c +++ b/lunaix-os/kernel/mm/valloc.c @@ -5,24 +5,18 @@ #define CLASS_LEN(class) (sizeof(class) / sizeof(class[0])) -static char piles_names[][PILE_NAME_MAXLEN] = {"valloc_8", - "valloc_16", - "valloc_32", - "valloc_64", - "valloc_128", - "valloc_256", - "valloc_512", - "valloc_1k", - "valloc_2k", - "valloc_4k", - "valloc_8k"}; - -static char piles_names_dma[][PILE_NAME_MAXLEN] = {"valloc_dma_128", - "valloc_dma_256", - "valloc_dma_512", - "valloc_dma_1k", - "valloc_dma_2k", - "valloc_dma_4k"}; +static char piles_names[][PILE_NAME_MAXLEN] = +{ + "valloc_8", "valloc_16", "valloc_32", "valloc_64", + "valloc_128", "valloc_256", "valloc_512", "valloc_1k", + "valloc_2k", "valloc_4k", "valloc_8k" +}; + +static char piles_names_dma[][PILE_NAME_MAXLEN] = +{ + "valloc_dma_128", "valloc_dma_256", "valloc_dma_512", + "valloc_dma_1k", "valloc_dma_2k", "valloc_dma_4k" +}; static struct cake_pile* piles[CLASS_LEN(piles_names)]; static struct cake_pile* piles_dma[CLASS_LEN(piles_names_dma)]; @@ -49,7 +43,7 @@ __valloc(unsigned int size, size_t len, size_t boffset) { - size_t i = ILOG2(size); + size_t i = ilog2(size); i += (size - (1 << i) != 0); i -= boffset; @@ -88,7 +82,7 @@ void* vcalloc(unsigned int size, unsigned int count) { unsigned int alloc_size; - if (umul_overflow(size, count, &alloc_size)) { + if (umul_of(size, count, &alloc_size)) { return 0; } diff --git a/lunaix-os/kernel/process/LBuild b/lunaix-os/kernel/process/LBuild index 3bb42be..3fc72ec 100644 --- a/lunaix-os/kernel/process/LBuild +++ b/lunaix-os/kernel/process/LBuild @@ -5,5 +5,7 @@ sources([ "process.c", "taskfs.c", "task_attr.c", - "thread.c" + "thread.c", + "preemption.c", + "switch.c", ]) \ No newline at end of file diff --git a/lunaix-os/kernel/process/fork.c b/lunaix-os/kernel/process/fork.c index 6dbe737..9f257aa 100644 --- a/lunaix-os/kernel/process/fork.c +++ b/lunaix-os/kernel/process/fork.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -62,12 +63,12 @@ __dup_kernel_stack(struct thread* thread, ptr_t vm_mnt) struct leaflet* leaflet; ptr_t kstack_pn = pfn(current_thread->kstack); - kstack_pn -= pfn(KSTACK_SIZE) - 1; + kstack_pn -= pfn(KSTACK_SIZE); // copy the kernel stack pte_t* src_ptep = mkptep_pn(VMS_SELF, kstack_pn); pte_t* dest_ptep = mkptep_pn(vm_mnt, kstack_pn); - for (size_t i = 0; i < pfn(KSTACK_SIZE); i++) { + for (size_t i = 0; i <= pfn(KSTACK_SIZE); i++) { pte_t p = *src_ptep; if (pte_isguardian(p)) { @@ -149,9 +150,8 @@ done: pid_t dup_proc() { - // FIXME need investigate: issue with fork, as well as pthread - // especially when involving frequent alloc and dealloc ops - // (could be issue in allocator's segregated free list) + no_preemption(); + struct proc_info* pcb = alloc_process(); if (!pcb) { syscall_result(ENOMEM); diff --git a/lunaix-os/kernel/process/preemption.c b/lunaix-os/kernel/process/preemption.c new file mode 100644 index 0000000..e89a593 --- /dev/null +++ b/lunaix-os/kernel/process/preemption.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +LOG_MODULE("preempt"); + +#ifdef CONFIG_CHECK_STALL +bool +preempt_check_stalled(struct thread* th) +{ + // we can't access the hart state here + // as th might be in other address space + + if (thread_flags_test(th, TH_STALLED)) + { + // alrady stalled, no need to concern + return false; + } + + struct thread_stats* stats; + stats = &th->stats; + + if (!stats->kpreempt_count) { + return false; + } + + if (stats->at_user) { + return false; + } + +#if defined(CONFIG_STALL_MAX_PREEMPTS) && CONFIG_STALL_MAX_PREEMPTS + if (stats->kpreempt_count > CONFIG_STALL_MAX_PREEMPTS) { + return true; + } +#endif + + ticks_t total_elapsed; + total_elapsed = thread_stats_kernel_elapse(th); + + return total_elapsed > ticks_seconds(CONFIG_STALL_TIMEOUT); +} + +#else +bool +preempt_check_stalled(struct thread* th) +{ + return false; +} + +#endif + +void +preempt_handle_stalled(struct signpost_result* result) +{ + if (!thread_flags_test(current_thread, TH_STALLED)) + { + continue_switch(result); + return; + } + + kprintf("+++++ "); + kprintf(" stalling detected (pid: %d, tid: %d)", + __current->pid, current_thread->tid); + kprintf(" (preempted: %d, elapsed: %dms)", + current_thread->stats.kpreempt_count, + thread_stats_kernel_elapse(current_thread)); + kprintf(" are you keeping Luna too busy?"); + kprintf("+++++ "); + + kprintf("last known state:"); + trace_dump_state(current_thread->hstate); + + kprintf("trace from the point of stalling:"); + trace_printstack_isr(current_thread->hstate); + + kprintf("thread is blocked"); + + block_current_thread(); + giveup_switch(result); +} \ No newline at end of file diff --git a/lunaix-os/kernel/process/sched.c b/lunaix-os/kernel/process/sched.c index 82a2aa9..574d593 100644 --- a/lunaix-os/kernel/process/sched.c +++ b/lunaix-os/kernel/process/sched.c @@ -62,6 +62,7 @@ run(struct thread* thread) set_current_executing(thread); switch_context(); + fail("unexpected return from switching"); } @@ -102,13 +103,22 @@ cleanup_detached_threads() { cpu_enable_interrupt(); } -int +bool can_schedule(struct thread* thread) { if (!thread) { return 0; } + if (proc_terminated(thread)) { + return false; + } + + if (preempt_check_stalled(thread)) { + thread_flags_set(thread, TH_STALLED); + return true; + } + if (unlikely(kernel_process(thread->process))) { // a kernel process is always runnable return thread->state == PS_READY; @@ -119,6 +129,7 @@ can_schedule(struct thread* thread) if ((thread->state & PS_PAUSED)) { return !!(sh->sig_pending & ~1); } + if ((thread->state & PS_BLOCKED)) { return sigset_test(sh->sig_pending, _SIGINT); } @@ -128,8 +139,9 @@ can_schedule(struct thread* thread) // all other threads are also SIGSTOP (as per POSIX-2008.1) // In which case, the entire process is stopped. thread->state = PS_STOPPED; - return 0; + return false; } + if (sigset_test(sh->sig_pending, _SIGCONT)) { thread->state = PS_READY; } @@ -176,7 +188,7 @@ schedule() assert(sched_ctx.ptable_len && sched_ctx.ttable_len); // 上下文切换相当的敏感!我们不希望任何的中断打乱栈的顺序…… - cpu_disable_interrupt(); + no_preemption(); if (!(current_thread->state & ~PS_RUNNING)) { current_thread->state = PS_READY; @@ -216,13 +228,6 @@ done: fail("unexpected return from scheduler"); } -void -sched_pass() -{ - cpu_enable_interrupt(); - cpu_trap_sched(); -} - __DEFINE_LXSYSCALL1(unsigned int, sleep, unsigned int, seconds) { if (!seconds) { @@ -305,6 +310,7 @@ _wait(pid_t wpid, int* status, int options) } wpid = wpid ? wpid : -__current->pgid; + repeat: llist_for_each(proc, n, &__current->children, siblings) { @@ -323,12 +329,12 @@ repeat: return 0; } // 放弃当前的运行机会 - sched_pass(); + yield_current(); goto repeat; done: if (status) { - *status = proc->exit_code | status_flags; + *status = PEXITNUM(status_flags, proc->exit_code); } return destroy_process(proc->pid); } diff --git a/lunaix-os/kernel/process/signal.c b/lunaix-os/kernel/process/signal.c index eef5c7b..2f84098 100644 --- a/lunaix-os/kernel/process/signal.c +++ b/lunaix-os/kernel/process/signal.c @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include @@ -28,17 +30,19 @@ signal_terminate(int caused_by) } // Referenced in kernel/asm/x86/interrupt.S -void* -signal_dispatch() +void +signal_dispatch(struct signpost_result* result) { + continue_switch(result); + if (kernel_process(__current)) { // signal is undefined under 'kernel process' - return 0; + return; } if (!pending_sigs(current_thread)) { // 没有待处理信号 - return 0; + return; } struct sigregistry* sigreg = __current->sigreg; @@ -46,29 +50,28 @@ signal_dispatch() struct sigact* prev_working = active_signal(current_thread); sigset_t mask = psig->sig_mask | (prev_working ? prev_working->sa_mask : 0); - int sig_selected = 31 - clz(psig->sig_pending & ~mask); + int sig_selected = msbiti - clz(psig->sig_pending & ~mask); sigset_clear(psig->sig_pending, sig_selected); if (!sig_selected) { // SIG0 is reserved - return 0; + return; } struct sigact* action = sigreg->signals[sig_selected]; if (!action || !action->sa_actor) { if (sigset_test(TERMSIG, sig_selected)) { signal_terminate(sig_selected); - schedule(); - // never return + giveup_switch(result); } - return 0; + return; } ptr_t ustack = current_thread->ustack_top; ptr_t ustack_start = current_thread->ustack->start; if ((int)(ustack - ustack_start) < (int)sizeof(struct proc_sig)) { // 用户栈没有空间存放信号上下文 - return 0; + return; } struct proc_sig* sigframe = @@ -82,7 +85,7 @@ signal_dispatch() sigactive_push(current_thread, sig_selected); - return sigframe; + redirect_switch(result, __ptr(sigframe)); } static inline void must_inline @@ -347,7 +350,7 @@ __DEFINE_LXSYSCALL2(int, sys_sigaction, int, signum, struct sigaction*, action) __DEFINE_LXSYSCALL(int, pause) { pause_current_thread(); - sched_pass(); + yield_current(); syscall_result(EINTR); return -1; @@ -371,7 +374,7 @@ __DEFINE_LXSYSCALL1(int, sigsuspend, sigset_t, *mask) sigctx->sig_mask = (*mask) & ~UNMASKABLE; pause_current_thread(); - sched_pass(); + yield_current(); sigctx->sig_mask = tmp; return -1; diff --git a/lunaix-os/kernel/process/switch.c b/lunaix-os/kernel/process/switch.c new file mode 100644 index 0000000..c5d9f27 --- /dev/null +++ b/lunaix-os/kernel/process/switch.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +extern void +signal_dispatch(struct signpost_result* result); + +extern void +preempt_handle_stalled(struct signpost_result* result); + +#define do_signpost(fn, result) \ + do { \ + fn((result)); \ + if ((result)->mode == SWITCH_MODE_FAST) { \ + thread_stats_update_leaving(); \ + return (result)->stack; \ + } \ + if ((result)->mode == SWITCH_MODE_GIVEUP) { \ + schedule(); \ + fail("unexpected return"); \ + } \ + } while (0) + +ptr_t +switch_signposting() +{ + struct signpost_result result; + + do_signpost(preempt_handle_stalled, &result); + + do_signpost(signal_dispatch, &result); + + thread_stats_update_leaving(); + + return 0; +} \ No newline at end of file diff --git a/lunaix-os/kernel/process/taskfs.c b/lunaix-os/kernel/process/taskfs.c index bc29921..2015aec 100644 --- a/lunaix-os/kernel/process/taskfs.c +++ b/lunaix-os/kernel/process/taskfs.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -52,17 +53,21 @@ taskfs_readdir(struct v_file* file, struct dir_context* dctx) { struct v_inode* inode = file->inode; pid_t pid = inode->id >> 16; - int counter = 0; + unsigned int counter = 0; if ((inode->id & COUNTER_MASK)) { return ENOTDIR; } + if (fsapi_handle_pseudo_dirent(file, dctx)) { + return 1; + } + if (pid) { struct task_attribute *pos, *n; llist_for_each(pos, n, &attributes, siblings) { - if (counter == dctx->index) { + if (counter == file->f_pos) { dctx->read_complete_callback( dctx, pos->key_val, VFS_NAME_MAXLEN, DT_FILE); return 1; @@ -76,7 +81,7 @@ taskfs_readdir(struct v_file* file, struct dir_context* dctx) struct proc_info *root = get_process(pid), *pos, *n; llist_for_each(pos, n, &root->tasks, tasks) { - if (counter == dctx->index) { + if (counter == file->f_pos) { ksnprintf(name, VFS_NAME_MAXLEN, "%d", pos->pid); dctx->read_complete_callback(dctx, name, VFS_NAME_MAXLEN, DT_DIR); return 1; @@ -165,6 +170,12 @@ taskfs_mount(struct v_superblock* vsb, struct v_dnode* mount_point) return taskfs_mknod(mount_point, 0, 0, VFS_IFDIR); } +int +taskfs_unmount(struct v_superblock* vsb) +{ + return 0; +} + void taskfs_invalidate(pid_t pid) { @@ -220,10 +231,11 @@ export_task_attr(); void taskfs_init() { - struct filesystem* taskfs = fsm_new_fs("taskfs", 5); - taskfs->mount = taskfs_mount; - - fsm_register(taskfs); + struct filesystem* fs; + fs = fsapi_fs_declare("taskfs", FSTYPE_PSEUDO); + + fsapi_fs_set_mntops(fs, taskfs_mount, taskfs_unmount); + fsapi_fs_finalise(fs); attr_export_table = vcalloc(ATTR_TABLE_LEN, sizeof(struct hbucket)); diff --git a/lunaix-os/kernel/process/thread.c b/lunaix-os/kernel/process/thread.c index ef207aa..493ed99 100644 --- a/lunaix-os/kernel/process/thread.c +++ b/lunaix-os/kernel/process/thread.c @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -49,16 +50,18 @@ __alloc_user_thread_stack(struct proc_info* proc, static ptr_t __alloc_kernel_thread_stack(struct proc_info* proc, ptr_t vm_mnt) { - pfn_t kstack_top = leaf_count(KSTACK_AREA_END); + pfn_t kstack_top = pfn(KSTACK_AREA_END); pfn_t kstack_end = pfn(KSTACK_AREA); pte_t* ptep = mkptep_pn(vm_mnt, kstack_top); while (ptep_pfn(ptep) > kstack_end) { - ptep -= KSTACK_PAGES + 1; + ptep -= KSTACK_PAGES; - pte_t pte = pte_at(ptep + 1); + pte_t pte = pte_at(ptep); if (pte_isnull(pte)) { goto found; } + + ptep--; } WARN("failed to create kernel stack: max stack num reach\n"); @@ -73,8 +76,8 @@ found:; return 0; } - set_pte(ptep, guard_pte); - ptep_map_leaflet(ptep + 1, mkpte_prot(KERNEL_DATA), leaflet); + set_pte(ptep++, guard_pte); + ptep_map_leaflet(ptep, mkpte_prot(KERNEL_DATA), leaflet); ptep += KSTACK_PAGES; return align_stack(ptep_va(ptep, LFT_SIZE) - 1); @@ -93,7 +96,7 @@ thread_release_mem(struct thread* thread) pte_t* ptep = mkptep_va(vm_mnt, thread->kstack); leaflet = pte_leaflet(*ptep); - ptep -= KSTACK_PAGES - 1; + ptep -= KSTACK_PAGES; set_pte(ptep, null_pte); ptep_unmap_leaflet(ptep + 1, leaflet); @@ -185,9 +188,60 @@ thread_find(struct proc_info* proc, tid_t tid) return NULL; } +void +thread_stats_update(bool inbound, bool voluntary) +{ + struct thread_stats* stats; + time_t now; + + now = clock_systime(); + stats = ¤t_thread->stats; + + stats->at_user = !kernel_context(current_thread->hstate); + + if (!inbound) { + if (kernel_process(current_thread->process) || + stats->at_user) + { + // exiting to user or kernel (kernel thread only), how graceful + stats->last_leave = now; + } + else { + // exiting to kernel, effectively reentry + stats->last_reentry = now; + } + + stats->last_resume = now; + return; + } + + stats->last_reentry = now; + + if (!stats->at_user) + { + // entering from kernel, it is a kernel preempt + thread_stats_update_kpreempt(); + return; + } + + // entering from user space, a clean entrance. + + if (!voluntary) { + stats->entry_count_invol++; + } + else { + stats->entry_count_vol++; + } + + thread_stats_reset_kpreempt(); + stats->last_entry = now; +} + __DEFINE_LXSYSCALL3(int, th_create, tid_t*, tid, struct uthread_param*, thparam, void*, entry) { + no_preemption(); + struct thread* th = create_thread(__current, true); if (!th) { return EAGAIN; @@ -232,13 +286,14 @@ __DEFINE_LXSYSCALL2(int, th_join, tid_t, tid, void**, val_ptr) } while (!proc_terminated(th)) { - sched_pass(); + yield_current(); } if (val_ptr) { *val_ptr = (void*)th->exit_val; } + no_preemption(); destory_thread(th); return 0; diff --git a/lunaix-os/kernel/syscall.c b/lunaix-os/kernel/syscall.c new file mode 100644 index 0000000..cd3b80d --- /dev/null +++ b/lunaix-os/kernel/syscall.c @@ -0,0 +1,19 @@ +#include +#include + +typedef reg_t (*syscall_fn)(reg_t p1, reg_t p2, reg_t p3, reg_t p4, reg_t p5); + +reg_t +dispatch_syscall(void* syscall_fnptr, + reg_t p1, reg_t p2, reg_t p3, reg_t p4, reg_t p5) +{ + reg_t ret_val; + + thread_stats_update_entering(true); + + set_preemption(); + ret_val = ((syscall_fn)syscall_fnptr)(p1, p2, p3, p4, p5); + no_preemption(); + + return ret_val; +} \ No newline at end of file diff --git a/lunaix-os/kernel/time/timer.c b/lunaix-os/kernel/time/timer.c index fe8f5d3..b9d98af 100644 --- a/lunaix-os/kernel/time/timer.c +++ b/lunaix-os/kernel/time/timer.c @@ -123,6 +123,7 @@ timer_update() if (sched_ticks_counter >= sched_ticks) { sched_ticks_counter = 0; + thread_stats_update_entering(false); schedule(); } } diff --git a/lunaix-os/libs/klibc/string/strcmp.c b/lunaix-os/libs/klibc/string/strcmp.c index 88759ce..072912b 100644 --- a/lunaix-os/libs/klibc/string/strcmp.c +++ b/lunaix-os/libs/klibc/string/strcmp.c @@ -12,4 +12,18 @@ streq(const char* a, const char* b) b++; } return 0; +} + +int +strneq(const char* a, const char* b, unsigned long n) +{ + while (n-- && *a == *b) { + if (!(*a)) { + return 1; + } + + a++; + b++; + } + return !(n + 1); } \ No newline at end of file diff --git a/lunaix-os/libs/klibc/string/strcpy.c b/lunaix-os/libs/klibc/string/strcpy.c index 04c85c9..4002d5b 100644 --- a/lunaix-os/libs/klibc/string/strcpy.c +++ b/lunaix-os/libs/klibc/string/strcpy.c @@ -14,14 +14,31 @@ strcpy(char* dest, const char* src) return &dest[i]; } +/** + * @brief strcpy with constrain on numbers of character. + * this version is smarter than stdc, it will automatically + * handle the null-terminator. + * + * @param dest + * @param src + * @param n + * @return char* + */ char* weak strncpy(char* dest, const char* src, unsigned long n) { char c; unsigned int i = 0; - while ((c = src[i]) && i <= n) + while (i <= n && (c = src[i])) dest[i++] = c; - while (i <= n) - dest[i++] = 0; + + if (!(n < i && src[i - 1])) { + while (i <= n) + dest[i++] = 0; + } + else { + dest[i - 1] = 0; + } + return dest; } \ No newline at end of file diff --git a/lunaix-os/live_debug.sh b/lunaix-os/live_debug.sh index 6462f5d..d189329 100755 --- a/lunaix-os/live_debug.sh +++ b/lunaix-os/live_debug.sh @@ -12,6 +12,7 @@ make CMDLINE=${default_cmd} ARCH=${ARCH} MODE=${MODE:-debug} image -j5 || exit - -v KIMG=build/lunaix.iso \ -v QMPORT=${hmp_port} \ -v GDB_PORT=${gdb_port} \ + -v EXT2_TEST_DISC=machine/test_part.ext2 \ -v ARCH=${ARCH} & QMPORT=${hmp_port} gdb build/bin/kernel.bin -ex "target remote localhost:${gdb_port}" \ No newline at end of file diff --git a/lunaix-os/makefile b/lunaix-os/makefile index 5427275..f53d62e 100644 --- a/lunaix-os/makefile +++ b/lunaix-os/makefile @@ -4,6 +4,7 @@ include $(mkinc_dir)/toolchain.mkinc include $(mkinc_dir)/utils.mkinc include $(mkinc_dir)/lunabuild.mkinc +QEMU_HMP ?= 45454 ARCH ?= i386 MODE ?= debug export ARCH @@ -54,7 +55,7 @@ image: $(kbuild_dir) kernel usr/build @cp -r usr/build/* $(os_img_dir)/usr @cp -r $(kbin_dir)/* $(os_img_dir)/boot @grub-mkrescue -o $(kimg) $(os_img_dir)\ - -- -volid "$(OS_ID) $(OS_VER)" -system_id "$(OS_NAME)" \ + -- -volid "LUNA" -system_id "Lunaix" \ -report_about FAILURE -abort_on FAILURE usr/build: user @@ -70,4 +71,4 @@ clean: @$(MAKE) -C usr clean -I $(mkinc_dir) @$(MAKE) -f kernel.mk clean -I $(mkinc_dir) @rm -rf $(kbuild_dir) || exit 1 - @rm -rf .builder || exit 1 \ No newline at end of file + @rm -rf .builder || exit 1 diff --git a/lunaix-os/makeinc/toolchain.mkinc b/lunaix-os/makeinc/toolchain.mkinc index 486e9ce..96dd3c4 100644 --- a/lunaix-os/makeinc/toolchain.mkinc +++ b/lunaix-os/makeinc/toolchain.mkinc @@ -4,7 +4,7 @@ AS := $(CX_PREFIX)as AR := $(CX_PREFIX)ar LBUILD ?= $(shell realpath ./scripts/build-tools/luna_build.py) -O := -O2 + W := -Wall -Wextra -Werror \ -Wno-unknown-pragmas \ -Wno-unused-function \ @@ -19,7 +19,9 @@ OFLAGS := -fno-omit-frame-pointer CFLAGS := -std=gnu99 $(OFLAGS) $(W) -g ifeq ($(MODE),debug) - O = -Og + O := -Og +else + O := -O2 endif CFLAGS += $(O) diff --git a/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py b/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py index 7566670..2d755d7 100644 --- a/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py +++ b/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py @@ -11,7 +11,7 @@ class LConfigProvider(ConfigProvider): def has_config(self, name): try: - self.__env.lookup_value(name) - return True + v = self.__env.lookup_value(name) + return not not v except: return False \ No newline at end of file diff --git a/lunaix-os/scripts/qemu.py b/lunaix-os/scripts/qemu.py index ce843da..f2808e5 100755 --- a/lunaix-os/scripts/qemu.py +++ b/lunaix-os/scripts/qemu.py @@ -2,6 +2,9 @@ import subprocess, time, os, re, argparse, json from pathlib import PurePosixPath +import logging + +logger = logging.getLogger("auto_qemu") g_lookup = {} @@ -109,6 +112,10 @@ class AHCIBus(QEMUPeripherals): d_ro = get_config(disk, "ro", default=False) d_fmt = get_config(disk, "format", default="raw") d_id = f"disk_{i}" + + if not os.path.exists(d_img): + logger.warning(f"AHCI bus: {d_img} not exists, disk skipped") + continue cmds += [ "-drive", join_attrs([ @@ -204,7 +211,7 @@ class QEMUExec: def get_qemu_general_opts(self): return [ "-m", get_config(self._opt, "memory", required=True), - "-smp", get_config(self._opt, "ncpu", default=1) + "-smp", str(get_config(self._opt, "ncpu", default=1)) ] def add_peripheral(self, peripheral): @@ -215,6 +222,7 @@ class QEMUExec: qemu_path = os.path.join(qemu_dir_override, qemu_path) cmds = [ qemu_path, + *self.get_qemu_general_opts(), *self.get_qemu_arch_opts(), *self.get_qemu_debug_opts() ] @@ -272,7 +280,4 @@ def main(): q.start(arg_opt.qemu_dir) if __name__ == "__main__": - try: - main() - except Exception as e: - print(e) \ No newline at end of file + main() \ No newline at end of file diff --git a/lunaix-os/scripts/qemus/qemu_x86_dev.json b/lunaix-os/scripts/qemus/qemu_x86_dev.json index e006691..023dfec 100644 --- a/lunaix-os/scripts/qemus/qemu_x86_dev.json +++ b/lunaix-os/scripts/qemus/qemu_x86_dev.json @@ -1,6 +1,7 @@ { "arch": "$ARCH", "memory": "1G", + "ncpu": 1, "machine": "q35", "cpu": { "type": "base", @@ -53,6 +54,11 @@ "img": "$KIMG", "ro": true, "format": "raw" + }, + { + "type": "ide-hd", + "img": "$EXT2_TEST_DISC", + "format": "raw" } ] }, diff --git a/lunaix-os/usr/LBuild b/lunaix-os/usr/LBuild index a637308..4b0617c 100644 --- a/lunaix-os/usr/LBuild +++ b/lunaix-os/usr/LBuild @@ -5,7 +5,10 @@ sources([ "cat", "stat", "test_pthread", - "maze" + "file_test", + "maze", + "mkdir", + "rm", ]) compile_opts([ diff --git a/lunaix-os/usr/cat.c b/lunaix-os/usr/cat.c index 9f0d35b..cd8cad0 100644 --- a/lunaix-os/usr/cat.c +++ b/lunaix-os/usr/cat.c @@ -26,13 +26,17 @@ main(int argc, const char* argv[]) return 1; } - if (!(stat.mode & F_MFILE)) { + if ((stat.mode & F_DIR)) { printf("%s is a directory", argv[i]); return 1; } do { size = read(fd, buffer, BUFSIZE); + if (size < 0) { + printf("error while reading: %d\n", size); + break; + } write(stdout, buffer, size); } while (size == BUFSIZE); diff --git a/lunaix-os/usr/file_test.c b/lunaix-os/usr/file_test.c new file mode 100644 index 0000000..dfd960d --- /dev/null +++ b/lunaix-os/usr/file_test.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +#define BUFSIZE 4096 + +static char buffer[BUFSIZE]; + +#define _open(f, o) \ + ({ \ + int __fd = open(f, o); \ + if (__fd < 0) { \ + printf("open failed: %s (error: %d)", f, errno); \ + _exit(__fd); \ + } \ + __fd; \ + }) + +int +main(int argc, const char* argv[]) +{ + int fd = 0, fdrand = 0; + int size = 0, sz2 = 0; + + fd = _open(argv[1], O_RDWR | O_CREAT); + fdrand = _open("/dev/rand", O_RDONLY); + + for (int i = 0; i < 100; i++) + { + printf("write to file: (round) {%d}/100\n", i + 1); + + size = read(fdrand, buffer, BUFSIZE); + printf(">>> read random chars: %d\n", size); + for (int i = 0; i < size; i++) + { + buffer[i] = (char)((unsigned char)(buffer[i] % 94U) + 33U); + } + + sz2 += write(fd, buffer, size); + sz2 += write(fd, "\n\n", 2); + } + + close(fd); + close(fdrand); + + return 0; +} \ No newline at end of file diff --git a/lunaix-os/usr/init/init.c b/lunaix-os/usr/init/init.c index 47fcf68..c402a4f 100644 --- a/lunaix-os/usr/init/init.c +++ b/lunaix-os/usr/init/init.c @@ -15,6 +15,14 @@ } \ } while (0) +#define maybe_mount(src, target, fs, opts) \ + do { \ + int err = 0; \ + if ((err = mount(src, target, fs, opts))) { \ + syslog(2, "mount fs %s to %s failed (%d)\n", fs, target, errno); \ + } \ + } while (0) + #define check(statement) \ ({ \ int err = 0; \ @@ -54,10 +62,12 @@ main(int argc, const char** argv) mkdir("/dev"); mkdir("/sys"); mkdir("/task"); + mkdir("/mnt/disk"); must_mount(NULL, "/dev", "devfs", 0); must_mount(NULL, "/sys", "twifs", MNT_RO); must_mount(NULL, "/task", "taskfs", MNT_RO); + maybe_mount("/dev/block/sdb", "/mnt/disk", "ext2", 0); int fd = check(open("/dev/tty", 0)); @@ -70,8 +80,6 @@ main(int argc, const char** argv) pid_t pid; int err = 0; if (!(pid = fork())) { - - err = execve(sh_argv[0], sh_argv, sh_envp); printf("fail to execute (%d)\n", errno); _exit(err); diff --git a/lunaix-os/usr/libc/src/readdir.c b/lunaix-os/usr/libc/src/readdir.c index d70f225..eaf4b71 100644 --- a/lunaix-os/usr/libc/src/readdir.c +++ b/lunaix-os/usr/libc/src/readdir.c @@ -45,6 +45,10 @@ readdir(DIR* dir) struct lx_dirent* _lxd = &dir->_lxd; int more = sys_readdir(dir->dirfd, _lxd); + + if (more < 0) { + return NULL; + } _dirent.d_type = _lxd->d_type; strncpy(_dirent.d_name, _lxd->d_name, 256); diff --git a/lunaix-os/usr/ls.c b/lunaix-os/usr/ls.c index 698d025..caea32d 100644 --- a/lunaix-os/usr/ls.c +++ b/lunaix-os/usr/ls.c @@ -28,7 +28,12 @@ main(int argc, const char* argv[]) } } + int err = errno; + if (err) { + printf("failed: %d\n",err); + } + closedir(dir); - return 0; + return err; } \ No newline at end of file diff --git a/lunaix-os/usr/mkdir.c b/lunaix-os/usr/mkdir.c new file mode 100644 index 0000000..f8bc4ad --- /dev/null +++ b/lunaix-os/usr/mkdir.c @@ -0,0 +1,21 @@ +#include +#include +#include + +int +main(int argc, const char* argv[]) +{ + if (argc != 2) { + printf("expect a directory name\n"); + return 1; + } + + int err; + + err = mkdir(argv[1]); + if (err) { + printf("unable to mkdir: %d\n", errno); + } + + return err; +} \ No newline at end of file diff --git a/lunaix-os/usr/rm.c b/lunaix-os/usr/rm.c new file mode 100644 index 0000000..7fea833 --- /dev/null +++ b/lunaix-os/usr/rm.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +int +main(int argc, const char* argv[]) +{ + if (argc != 2) { + printf("expect a file name\n"); + return 1; + } + + int err, fd; + char* path = argv[1]; + struct file_stat stat; + + fd = open(path, O_RDONLY); + + if (fd < 0) { + printf("open failed: %s (error: %d)", path, fd); + return 1; + } + + if (fstat(fd, &stat) < 0) { + printf("fail to get stat %d\n", errno); + return 1; + } + + close(fd); + + if ((stat.mode & F_DIR)) { + err = rmdir(path); + } + else { + err = unlink(path); + } + + if (err) { + printf("fail to delete: %s (%d)", path, errno); + } + + return err; +} \ No newline at end of file diff --git a/lunaix-os/usr/sh/sh.c b/lunaix-os/usr/sh/sh.c index c2c8347..33f0b03 100644 --- a/lunaix-os/usr/sh/sh.c +++ b/lunaix-os/usr/sh/sh.c @@ -113,6 +113,7 @@ sigint_handle(int signum) void sh_exec(const char** argv) { + static int prev_exit; const char* envp[] = { 0 }; char* name = argv[0]; if (!strcmp(name, "cd")) { @@ -121,15 +122,27 @@ sh_exec(const char** argv) return; } + if (!strcmp(name, "?")) { + printf("%d\n", prev_exit); + return; + } + + char buffer[1024]; + strcpy(buffer, "/usr/bin/"); + strcpy(&buffer[9], name); + pid_t p; + int res; if (!(p = fork())) { - if (execve(name, argv, envp)) { + if (execve(buffer, argv, envp)) { sh_printerr(); } _exit(1); } setpgid(p, getpgid()); - waitpid(p, NULL, 0); + waitpid(p, &res, 0); + + prev_exit = WEXITSTATUS(res); } static char* diff --git a/lunaix-os/usr/stat.c b/lunaix-os/usr/stat.c index 108f462..ea40200 100644 --- a/lunaix-os/usr/stat.c +++ b/lunaix-os/usr/stat.c @@ -15,7 +15,7 @@ main(int argc, char* argv[]) int fd = open(argv[1], FO_RDONLY | FO_NOFOLLOW); if (fd < 0) { - printf("fail to open %d\n", fd); + printf("fail to open %d\n", errno); return 1; } @@ -29,22 +29,21 @@ main(int argc, char* argv[]) char* ftype = "directory"; int mode = stat.mode; - if ((mode & F_MDEV)) { - if (!((mode & F_SEQDEV) ^ F_SEQDEV)) { - ftype = "sequential device"; - } else if (!((mode & F_VOLDEV) ^ F_VOLDEV)) { + if ((mode & F_DEV)) { + ftype = "mappable (sequential) device"; + + if (!((mode & F_SVDEV) ^ F_SVDEV)) { ftype = "volumetric device"; - } else { - ftype = "regular device"; } - } else if ((mode & F_MSLNK)) { + + } else if ((mode & F_SYMLINK)) { if (readlinkat(fd, NULL, buf, 256) < 0) { printf("fail to readlink %d\n", errno); } else { printf("-> %s", buf); } ftype = "symbolic link"; - } else if ((mode & F_MFILE)) { + } else if (mode == F_FILE) { ftype = "regular file"; } @@ -57,7 +56,7 @@ main(int argc, char* argv[]) printf("Inode: %d; ", stat.st_ino); dev_t* dev; - if (!(stat.mode & F_MDEV)) { + if (!(stat.mode & F_DEV)) { dev = &stat.st_dev; } else { dev = &stat.st_rdev; -- 2.27.0 From dbfc095e6e2db3fd17d5406c1ec30a478194ad4d Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Wed, 14 Aug 2024 14:31:43 +0100 Subject: [PATCH 12/16] Refinement on documentation (#38) * start a docs series on lunaix-internal * update readme, include a list for auto-loaded symbols * tweak the gitignore file * add a script to dump all .lga symbols * fix the image link to mem map * update readme, fix gitignore --- README.md | 12 ++-- docs/img/boot_sequence.jpeg | Bin 92837 -> 0 bytes docs/img/boot_sequence.png | Bin 0 -> 208121 bytes docs/lunaix-boot.md | 117 ++++++++++++++++++++++++++++++++ docs/lunaix-internal.md | 11 +++ docs/lunaix-mem-map.md | 36 ++++++++++ docs/lunaix-syscall-table.md | 2 +- lunaix-os/.gitignore | 45 ++++++------ lunaix-os/kernel.mk | 6 +- lunaix-os/scripts/gather_lga.sh | 70 +++++++++++++++++++ 10 files changed, 266 insertions(+), 33 deletions(-) delete mode 100644 docs/img/boot_sequence.jpeg create mode 100644 docs/img/boot_sequence.png create mode 100644 docs/lunaix-boot.md create mode 100644 docs/lunaix-internal.md create mode 100644 docs/lunaix-mem-map.md create mode 100755 lunaix-os/scripts/gather_lga.sh diff --git a/README.md b/README.md index 86d0443..43695ee 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,8 @@ LunaixOS - 一个简单的,详细的,POSIX兼容的(但愿!),带有 如果有意研读 Lunaix 内核代码和其中的设计,或欲开始属于自己的OS开发之道,以下资料可能会对此有用。 ++ [内核文档(Luna's Tour)](docs/lunaix-internal.md) + [LunaixOS源代码分析教程](docs/tutorial/0-教程介绍和环境搭建.md) -+ 内核虚拟内存的详细布局 - + [x86_32](docs/img/lunaix-mem-map/lunaix-mem-x86_32.png) - + [x86_64](docs/img/lunaix-mem-map/lunaix-mem-x86_64.png) -+ [LunaixOS启动流程概览](docs/img/boot_sequence.jpeg) -+ LunaixOS总体架构概览(WIP) + [作者修改的QEMU](https://github.com/Minep/qemu) (添加了一些额外用于调试的功能) ## 2. 当前进度以及支持的功能 @@ -194,9 +190,9 @@ ARCH=x86_64 ./live_debug.sh ### 5.1 代码稳定性 -主分支一般是稳定的。因为在大多数情况下,我都会尽量保证本机运行无误后,push到该分支中。至于其他的分支,则是作为标记或者是开发中的功能。前者标记用分支一般会很快删掉;后者开发分支不能保证稳定性,这些分支的代码有可能没有经过测试,但可以作为Lunaix当前开发进度的参考。 +主分支一般是稳定的。因为在大多数情况下,我都会尽量保证本机运行无误后,push到该分支中。所有正在开发的功能请参考当前活跃的Pull Request。 -该系统是经过虚拟机和真机测试。如果发现在使用`make all`之后,虚拟机中运行报错,则一般是编译器优化问题。这个问题笔者一般很快就会修复,如果你使用别的版本的gcc(笔者版本11.2),出现了此问题,欢迎提issue。请参考[附录3:Issue的提交](#appendix3) +如果主分支的运行出现了此问题,欢迎提issue。请参考[附录3:Issue的提交](#appendix3) ## 6. 调试 Lunaix 内核 @@ -263,7 +259,7 @@ ARCH=x86_64 ./live_debug.sh #### 网站 -+ [OSDev](https://wiki.osdev.org/Main_Page) - 杂七杂八的参考,很多过来人的经验。作者主要用于上古资料查询以及收集;技术文献,手册,标准的粗略总结;以及开发环境/工具链的搭建。当然,上面的内容假设了x86_32架构的生态,对于其他的ISA支持,该网站便失去了其价值了。 ++ [OSDev](https://wiki.osdev.org/Main_Page) - 适合快速入门,和一些文档手册的总结。 + [FreeVGA](http://www.osdever.net/FreeVGA/home.htm) - 98年的资源!关于VGA编程技术的宝藏网站。 + GNU CC 和 GNU LD 的官方文档。 + [PCI Lookup](https://www.pcilookup.com/) - PCI设备编号查询 diff --git a/docs/img/boot_sequence.jpeg b/docs/img/boot_sequence.jpeg deleted file mode 100644 index 0d57d3186556eb91118c151d6af6a54257383eb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92837 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%0DE=N*ZS2e|A9^CI5uo`F1Qe=HIO&o8 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 L{&NPHe^34ofi{$n diff --git a/docs/img/boot_sequence.png b/docs/img/boot_sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..3d580c41899ce3e3d9e2a101a6857685e05e0add GIT binary patch literal 208121 zcmeEuWn5L=x-TNa0%=4MBo-_{KtYj^?k;I*=?3ZU?gj-3=@JC#MnFnHx$~^e+kMVG_w&79bpCX%F~@k~|KxarUP+5!pb?@WARu6fi3-XgAl$)4KtOhcAcKF| zqT;{V33^fIx;ICdjAYsJWSp;;0~Wd@aLXADD(1JXZU<*?a7>Hj3;I zWuZ-s1ALd3qSw6l+Jb|F(Ta71+R$iUm!y%?(h4a!ONpLe_x-B1iZP4s*`BVqS#e)> za)flozDRw+@!i$NfXZ|sjFtWYE)p5SEeJ0HBDN>Ozdm}YLiC_8M(+Re|7##*uo0oa zRDu8Sz4od zzGq|y!T}_tVNL9X~08TmRIfzp0NAaq2?OGyN1zxK)i z^B*X~>yJtXGaz``!$%eamVY6ZRPpbMj(})^$17?(*?2%dj}(oAdlwCDv20K}h9ZJf zj@@>ZOhrY7(dEeO{WERv&_`@?N=51_K_*`edNQ>d-L1#81H>jP%qe+zwA|0PSe@3g z627|yP*PIH?U^PkYdU3Zmb8;Z$HY+ZTpq+sIn25Z0^g28pP}7Cy0L}96|C_qsBIh)Z;>yO8SG5Z5!wO*VD62h{3fTlqjC!*f}t?shngf66-RYU6Y`tSrSu!LRdc0r~Eq+}Z`( zVWcPf`ONDKEoh-VB7xX`tXYuQ#8%sE!hX^`Y>ZX+8#>!!q`>zGmI)(@af2jty^%td zEjOX8-7F1xqFUCm*uUBqBhB6^Naakf zW2P-5YSy3b=L!hPJW_lNo3ZKRf&{t0Okw|%oW^9Hk?)Xm6;N-fL0}XxdX>^v&O%&< zH7i56XF`6$_>cNtE}-utehChi;Xcg_GCI(-8-cW39?v$ClgsW%4Yezp;L^GrZPGZ* zI8pPbKlMT3`12!*aGt?#cm@e_W>WEMnwnC#v8}GEs=|G^V`S~*RDD`aA~gaL!iOC8 zw&pvKnAm<4e&~!7q*_d3KC-7~QLUd9pk4-b^A#f1oh!i)K!N?qqgQYqm7hNYJi>$X zh&haiwO5dc#9_)x7#1p(%x)L`K+9FO;dGg3|FQVOx3Hz!orah@ckg#t)NU!!6q8t` zFd0V|R?ax%^QRm9Ncg~EyNZ2W5+_3HHYE6r9LAUr{Trr zi*8O<7EihE_w3CDkcv0DU(%8Y_8SS|pD3&jqR0YuHs3rynzYQ)Zt*WY->y}jdY>B= zq8hMlK3!9;?tZ$oH)YfJ{=x?ZD?Ubsd;CJJafhT)ScgycCt4D}CeMz~cA*gmm@Rp{kejKm2oDH2y~ zayg!MkgB-2aHUvX+tugtFI*ed@?hql%azTbolYK0{@i>-E9Tjq~~m8U_u;QQ@@wChqtg;9Vz*$0kD*|Ls=( z!MT4SrDq(_xKFlsh57!or`bdgyg~cZ+opfY_RTtPC;@iUO?%7!%c}qU*5?oe{&bD2 zCc-~-{jYakivbHJUis$Uf8lLzl6yuPCGdPk{tr}!FR26D|E>I1G%qoctsB)D4F@1oDjDrD-*qL6{Kv?3Qn}YfRaRM@C@QJ+L3??LUA@H``cJ`M) zKISD3KJgG6+mrVzc)$}D68=LjJ#E1MB-_vsd-I+_9Qo(RN_6#qr17np1}6TAg4mzK zfqdK1HQY}MlW z4KY$7T)pBrp8cs_k+OiA58I@4u_3@cYkhl;wejci^RU4u{7Cen*w4Y+I5sb*{r>n^ zD|komC15Ekuu_r)_z(YFrWZ(_ZEuz>1aXZ57-jB?A@o1g`ExQL^cKliZpdrFG>HWI zz0EIuUB7vAU&Wv1Pf9>ClMwiIYEb2QkRE|!u13CCL;Hg_X(@0@qK0l_2Qq?VK6XiM z`1hEbcL>f9H>4jPkRcziBG;QJe83vsKvX?_d{KK%iOw!#s&Te3!BQeoB_;bn7vHsB zyvGa)dwa}snl*A=;>w~bojUu>|3GICxaI1OnIQYg1jHd{T4Dt*4qzp^&Q43}>N{W= zX}myQvNEKu?%>Qaqmbq-w6E^3;fFCrQBU4ay}psxE9$lU^hspdMhk3AG|T6 zN8jp36<$Da)4I2DA16eAo$X02oxihfACvNX1x7*eGl4W?WJG$v8{6iPgInYtB&AT{2K1Oojn)-qV zs?gw2IO_|kB9kZO_7XQAvpGF7teB7`NVt!-*XK zWJchL&&BfyzHFv6nQ{aZ>&6pul{{r(3~b1AulcxH@!kAX{;AuN0e`84UJ8(%<@bZ+ zNc;dPo|hH1yoD1E9|slk()Vc?f;jIY{Ki3CN}v>vY4*TI z|4#d0TOf{C_hh9xDR*|!=Z-}6@l**BvV2dl2*8ZLb2lNNHiP*!k5Lnj^?J}W?WOJF z0^ZUL3f0NLY{X0hpDL3j429S(0ATTa z+Bc6BE+fvUtM!4-JUVsv?m%}p2Qg!3xQ?GqYZcqs>{oX4&s)@*(Pogm6v@7RDdL>U zIW*cF*IVPpTo&FXu5?XqswCO17gP4zR7047Aib)^T7X8X09w9Zk}`!e@`5UI)|R@{ z{Ii&zU+Y-7ma99~n#Z0yBuEUCQQ5b(G}=$D>f{ne^9mj82T&H93;ei$pN-3(cK|M4 z!XkV(egU@84;`(}UQ@rw$K=2)V2FVq@9p8zsGlUR&cgmi{dHhXUb9kI-$*b5H0mSV z&n25eA}q4`thdCJmk>F&@H2TaoJc0tc=n2(`iN9hmW7T^BAmw_i*5TI{eaa1OR0fW zkSp7k^24Jhr1#HlG=&;kFxO!M7VLTRv!k=@N)1;>4%ORxDWyxx=K5E&5=(RKvr)Un z9u|9LlW&NfOfTXXw~}TT*NIYNnw1aZ%_~h9{Dj)}mN~S#iI3~d8p5T*i10g9F0o>L zu*Uiysc4qkX4Ubmxq$Bxl{$8IRb5KuL^@7&9(SA?5|h{N)aAv-#&;@f^+lfxHyDQ# zC`;9Dmj!)r`Nmf|%|awDZC`Qm(k6wIbDdb%V>r{{_4@9n0M9Wf2Ac9WxKYX z>2XKQj{Cc($5YoeG-}GK^{W^(43`O78mFghe*pwp7Z8JtO}8^lFK1u@3#p%#!e#n% zC~u2WfS8hfOO?{Ftw=g`RhXM`zPdyAU3~rGx*#hO_sEZ5r&d$zw#e(2W z%j-E|vH=A}(TmycRPh{QcF_%`o=~ze=V=2ftsQy)htE6la1Af7BEFy7L~Rc1OHe#s zT3&wVnvt6Hq;lp^&X0IvTOKRf(D$Vjozi{rm~R;0M;ot>5*|71y`p(i_=I!4Z-55Y za5cijR(2lFcwL+|P^zy>VP>o)!@Y1NuA?)`RT+K0!`}0jjsX}H>!{7mC;NwSKJ6Xd z#U8T#GWZWkU4EL$&dJ80xvEMJ%J5v%xSdU_K|galU#kdD9d3IKae7>SDT|&Zb+t|B zyl#~Feq6RqP0B69Za&+5p*E?UT0}$7tTVqT;~pDQ?AUct*kGp&QXM=VnW0s+jZai+ z#0iKR)^4Pt26d;hqvxba+r zW$nke1Fa2t`jW5qSzoV@BZlwJM(#2CwmO=fko?7@G+LnifuTX!PAb;@6=931UrPfI z-dSY(G}%u_#JA8l2yqj-HM$-gT&@{;vqK}Fc?nmmSdTi)&7SD%FNtX`Y-QFJKAUTZ z!9ncA?JzB?7FE)mnr8;P;NI67*ZG2>_k&rcd&AOQzstn;=e`VPY+OO`VyS6r6c&{S z!>LoXb(=>-yrzf)i%uWg11^)j`->~$gV`8avts9o4`Qeru9FLCXyfTD2`DHj^%TFB$+9A@RDzJ8)k)lO1fi{7yJl7^l>Zu+>kOOV%Ofc=np zS*7vv>1_rNIrl5O2Am)MGw#8gpQnYbM}L7VkJ@moG*niobU}ug%4V6KiF`hpH5GQg z_2tuA-@^x-dXlouzrwCHpj(qg6nL}eN=5ajcPKEgj=f2!ye-G~hD&F)Srr0(+8>o9 zP1*MB3g_0Jbmc5A(C~C(PFliR0uEy0pAvRYAJsSx`A&IIc(&hY=J=j*f7( z1isajla*;}8;doXroCrUwh-uYmSmNoyINrB5o4Hg-4Plx+OA2pQD#P-ld|vWdep`+ zd@)pLEWnnv!) zhb5!-)^%)3Q(tuPk$plcwa#)}sx&tD?t)v*Az@%x_!k!D>J!CQMyhevpKBZ@@y=|E zi287kO7#0il`i*`6G3s2yvct#Xup5-Qd{g*&-sJ}Ur14O%;>jK_B?dNLrQEJ(anm{ z5n%+m=C{`*tcgZEQVHsgRXE6pqFX)@4I2Hi!Txw!)v5{?fa!Uy}&%cItbHN@<% z?>|wuXyC4vQLf%h&k&@*FRorY73ZIyYoDeke7~A?tXZoxQD#9EaNN)W2}n%w(D5{@ zJ@mKUdw<;hV9-EuaoV83e(!6{gHA)OD?p`7Ugk$@_izh>ot+Pq?l3HsOg5NS8zC*( zWFQkXOWzULF)9^)-Eh9nEc}S`c)mSUv|!E4<*b|A#oSy_7GI>^R(#gNdaa3KzTx|a z+_6#f9do_b>6}h24~DoF=NYA~RmZv#z%5M`ViEQfCiB{JN_@q9>>7_s^QGX8)KB-k zRLXls+coHkS=5y=IrpeeZLS(^8mVF2=U+vZ1IrRvW^&8xSdJL+!t%d&chTPcczEd= zNs3s3{g^@RX~BaAix9RkkewEc1a6A&2ktrv`Lzo7i^skY?X&_KxSxNEM97W$XKdt{ zt4wF-Y?Fhq0>tYK`6BEWAEoDxA=kliYR+0iVzP4Gu+|v)u%`^8nyXr}IVA~#DKt4TB)we#F ztL^L=C1IGAz7~*Pb+Dw|Kd9Po(5TG2r@xA7V2I@^Nac2+E95ToOp#A+hcr|wDAt_dI5X;KmV}3;+Bk+ufx|UCU3o@_zDMz3K`I zX5@^_dA}z1JErwBjyA+Y?uUhSM<0cK>5Ek}_4Wk6+F8pd1^;-NCQ6)#{rJh#1Y|bT z6?$bq;JN7ZFZ9A7dWoLYHLa!&?OY5m_)`-O2ntC8K0PI#Ug-^X!DUXRhFvjkFtF{N zK$-6!`U-N9)F~gE(k&Ix!tx=fJ=6|ECL(ptWxVWutv=y3Ld#dxxzQS0u8sw4^R+8p zqlDUpmOJ%}`4ZvJ)6;@|K-&1(hU{`wgiNdp>AN@X2mO<*q9My+p{u#`83`<}8~r3* zPfhmwh>xnu_(MbVi?rH%d-(1w%;7?2`C5?;v5rc+ zipmf~)n9aZ;*+*dw8FSI22Evb1ZQfmOH71!=%sefQz9KDXKTM}M3t;1D;LEdj+)BS zcry>C(oOhyG9YzH2$hb^;Dto4-j${GLO_y(r*RSMKI4bs)(ED?jqJQt4~71lPcK1YXaH z1s@QVO=*>uj&cjiF}I-wId3{nD@jS+$B#UyGTlseN_m`l+;4oKT4%eVv7kSfUf!O- z?DRE8IL!FqwNxHAfBMMW_~karV3v{8q06>{XehP1hV6{D^%>2%GVX@lVG#9^B0lb+ zXc0+|q9!fO1W8|*9M@q+L72-iJm}}3XoFxhw?*k>Z<2VzG)s)n(X6aO?oh6L7Ri|%qhQA$jL=H-(jnolFfv^xzoO7|3Z_mYnrUqqA|+2&CbjKW}=&OL*B6W7yo zYl_=JmD3S5D$AGERo}KB(7PwNnBYBpw1^tWTfN9ST|K{u$gjg|k){ISP4k8;KC(bz zH{p9PJPYG}DSTT-(lh}-xSo8OB(d`D;Q&`Ok zpb$mL#xz71I>2a;9UWlYXG1R5$G=8tRe7p`T^etqSeS`gE&u4f%^iSVXbsLa0Pl*1DYcKO!J83VGV`XNwME zf@~NBxHf{1*`nKrF{&uGQH5BsaRuh6a>8uwNOXOE9(*p(x4}@gPuQF?urd!m9t$RU zQGYg|tADu8JdiQEg@62gO`_#2(r?E6c=1cjcOSYLUFg|YOLtA_EQqlAGY$TEoPcfe zN{nq?65r~L8RexmWu5}Z{&5ZxA{jyU{PrU2ZaeCCqUG|vB-&Ra=VYU%gLU)JgfsTO zT9p0}(IF8-ruCBR&hv*P^)e(TnsFnUAE@f8j)s>jXHJESYW24YdLC%~XbB8g)G|LJ zCn!&SptT$8%6LdU?mmg3f0BF|x=OG!Y~c3%6!`A%biZP8?p5SM@*2K(_TCTUJRs@o zufLGM^Yph$~-<>+h|cy(Fr%)xb#VNW&fg@s_5#n z-$obem@B4rJj{X4b3Xro?eN2TF(>u>D+Uwk{dFJOiH5!Y8iv{<8TpsKeab%xnIt9aN%E*02d$6El*5HMCS2u?XB~JTGnmT?oo{yd1umE z5usXI*!lv?j#Q#1&f)0qU{$^Qqv(!tPkt4(Y@_m_kX~`A;JVUB25Q#Y#Osw_4cOUZ zDYJpvhg$9yk!6;nU4cQgH5MaQT;nF)_KYS6=Wquvd-jFLdj>9l7xkG9i&O$ z1Srna*}{t>qb22GV;qzPXR;JHcONu9O?UVaMfQf~)(C1mJMtGYY*`SM<5<@f5dDHw zKl@eW%s-#T=cBhd<`IE)$ASP^(BHFQpMc1>1AVTEBXQJGt<$tjQJrI62xjul!o`w; zVw<}smTmXoTyKK4Q89Mbsut6_E9ktVm&2Y(){5557&iT`gtYrJ%FMIVI^_=w%r)0@ ztc3jxER+;56DRx@XOdx+ikZ@6Bf-RA?X&q`KPSI+x{yjoOs;HIPIuHv)-T;R58I+O zaesE|QoYoh5WS~Z7@6yx%w4s&JM^B>`Fo~GM6n}=(a==2p>4ue#muYHFJG40-|sKZ z$?kNV$21fh_z2^q3^vv}HCIV(7?c|AiaV`bTI!#4u+ow97u=WQ`!p*yo!Q>Z(e1yb z_C8~fPU&9PqKtt8q-FYemhYffH0+zgbD6f+Vb3m?MjIEyDswvTGDtn2mB`+<%%0LF zCo9O#FJ^V&*%|v-j!yX2ceCp9oA6ljbDNYYo9m4M`}%n6?nCN#X5Gs7#1k$@XR=mN zgJ{K%x2pqeXDwYQV%@J>9#tO$JRVPI99r)lVBXI%S`!gFP2$JyxChwuo?MCpBy`Au z=HPu?6eUq+{TW_GeQz3668(}sunY`4g<^OQwv6QWF5NGSxiHWuEJf8Ni$nvpw!$-U z$8@{<#UJR~3fv``I8Mp1Az($XH#xFE7m?wdCnPMgXrfsB+Eg1HNl~Q zb#(P6crTxe+HW_uhrSqjiNQyPfh32E+bxmxb}c|osqQgGyeuWs1w|8kGf(^w$R1|G z6W@0JAM=`rF&vE^{q+0@3Br8&d{N^q98$6OydE;{n{v!NDlK|Lk3<{JLX8*oTJ9vE zQ0403J?I-Qu(y#~$ZaS#&5cl2{wR1UNDfm#eQ{w3eg4K)+n-H9me}NX=j~52&3Bq4{ zOgYxpe|rUxAg@}oJ>)9(h!VcQiyxTiy!O)aAoCly$TIf$8IchI;(PK}HOrPXDwi1` zb4@KTHGqla3I*JJ5UPN93vvR|&;8Vq+PU8VHwngSbtw4L=~B=wd2UYY>mXV4#!1Ou z0Hgqe7FjkQtBl{Fw+-UvO~dut+he48dZr<+)E8YE-S)$acNOwk8j{@oi~vy@p4OeH zkK5T@H6jxj}o9!PTQhR^*(-7Lp1sXe$rvhgXK z(NW7TEoLpzLwU(6i zja#wt3>ZCBkg|*|EEJB8OAbj}rBKcnNAa?b+LNQ9Q=?qUDiCzNx~3++FHenxOWDK@ zm6clr-5!ka8UkMIQ{xuku24EsXbb<~*HN*!O=D8(b&uJCqAg6->Y#T|HlfkPYq9u3 zVo&*kl8v&!tz_f|AsAYG#D5pFq=5+j%v4*u_jiK3pm7dp#)gbHZYM58QuKRQ^l%SL z?APqAtW520y`CI(UZg!GhG_34o2=&2>S)E3iCxAUlHS6QPW)X+^7RB(9{VLK9uymT zqrDY>YcDbs7Qe_GJ^YkiTstJ}_6`qnrf^zw^lWDTyq0{h^o_R_IWd;b^jF5INgt41 z{fOSphxY`Cw11zWnA#?g6=Sk{g{)qZcY`QTrSyl{lf+~PD-J8-5bbdvzuwJZQa$)m zE%5yP;tfW;@8^B^SLT?P2}oX`f9e_~(pOLhdHy72uUQ%h3wPSbU{HyX6MfQS{+Le9 z(tKMGRxOnF=9JU8^e0LuJx!rQ`;C|nxjcULcTwRX*cthP2|FASZs7Og`2b)Bk8Lg` z4&wWJxXOjK<%Jg2V&n!<)V@fB^jE`yL5ET|+Puzz+ky30c_k1NNN@ixGnXGP0sMf? z65Qjf00F2u6=DzoYHtqaL`c48r1Zk=)bG#u-aR@g+x4Y1rodC~BiRqLE2e#jgj@8D zQ9cVr-1}YcO^5rnWfxMG&^H{Q>NLrLw_tXoub58~HjunYw#)2WCoG*-RRa z*P(Tqko)ePqEhr*84Wmg!8nmRAiwwb2<$J)hK<*M^FUe^tX)l1ETrH&aza*2~o#v)@> zF#BmM8zkqtLI{+62Z78qj25e=ijFOU9=*LhOa-c1qxjw z@P>sE<}0FQu)c@f&}%&S`fXt&ag0NX4X76#ZnO%u zI#u$oDyf$!P*rTP%0XkuYk1XKb@asYT>;VCYcW~fSN!w&b}TV*pDH9nZcDT0hHyG| ziZL#Zv+D1$@^niXK6;u|X-RQ6;Zy9&%&DY>)4Cmvy1d!McKHrH<9e4nzPO*>7&Y^qG-~M@%V44>|ZT$Morp2XehGxII2{ zKA+RAww!X2Q&m!WdTkuAMQQk`I$|y3$aDbg79*ljXX4j@A(j@Un!{L{(u^|KMLN36 zTyoh-YeCuW*M|c}{e7tAb2rKjJ&yTZp~V)1E99CzJH{?N{=y<#4@~Ho#yKMvSvSJ%%cU#G@J(6 z&eUWSHcH#5)Ek7Istt0l%tE!U4gyZkR*N_~W0X^=##L10u)O;)~c@aiW5e2G6RnN-+d=;ysF6mky&=OP7jJUKt=zSqUXBxug>e>fur;G%(0eN3taToF!Q$Z+q*Lec zhLl1*|K-UV))&c{aP!f)V$#N5*Xs$9_U^z3vk7)dtWo`=DfG1#<9st$n=Wk2`8aXv zj}~?5Od$N}5F(^yjlau>Cge@*UL_!FItKS?s4O$bLAzSi(bi-Vk9_t85%EZt#Dlf* zFNVx#KFApPNA>W|)s@~j(%bJ}7>1)0u@tIm;!l@-8e;;@We+**&C2Tp{L%A{>a7;O zDVEB=FmhA19M0S-ATFUHzTER|&3F4%pvB;}o3xs03u7Rc-LP=)@QVw-&EwvdXFcU! zOE@PLLGEJQWQEevWB5q-s(KAoxl1b~{(W&DhdodA51vFlJ=fHT;sFKEhKF{^wo`93*cPL; z8Bdem3uAvc9>6vp`0RC1cXrEZe{c=|w7zDC_&i+w^he0@`RxdfACD%lbiVBjYV`1q1zoo56hm5ZkOGv#WN(~5cP^|V-ZJmMOy0yIkzSREF`VDMi$MiKok zbyEUdN?$ZTL%VejaA{(PV)jXBdL#i|UEa%F4}}jQVPpsC_jLJOoC>~1;8WD6W+k#} zo~t@2;>b28n9L}cgFqf1lNqgq1-WyKT~|@y)cA111@TfE93Ct6pnfh&d^pwh7YXBh zBXy#Pv~=}pNpPwY|9nJaUxG$oRi1E+!efQz&lLx==US0b3p~$MOkB#HrleLnnrkgh z%@ous$E!ZMp8c56?WKi%qXKQg?I}5u*NT{RtJ~ss1%bIbI%a3voz11YXP*`Wy92si zw2HW-Kjl$14_T59DUgaJu}yr)D-i~9DZFLE>lrYW=ozD*A0lppC)?4=Vdv%2$W>x! zk#n6gH236zOl(NxhTBFyHSAkR{6R*Pp0<+b#mB@*f*t+E zv69by;c6Wl0`&LrX&iX=m&My77Q)^q`nKxUZatCFv`bvw-S{M3_if6Mz(r)fL^&+7 zlKZ?Ny=w(Im6GddOmluZ>f|(ZR)6*#2d=|ZVZAHUytTYxtuAs3;pyL$Zat{}nQuL=-BPjDin51V%V%uv}3Y{jv?aH96w z90vaF9#Q4Nr?4W6aapCpS}D~t-1D(64c(dO4N?!GuI}!o+@j=A$Ap+!&{ITPujL{B*t;r4!tqU?A;n28LgKI@unHKLl@p&CZsH|2f+ zbCYla7qJW^W;?Fy@ZixTU5sht{jkNj$-ad9QX@0Fxy9rR>NRqzOy)42tE(K_#*>ub zO?eSbnn`;z(<`o()V6QP_;}2D9Zu7_49Yq`ET-=>u3dF!N_Cf4#K*=z?3a<}jM}ep z7wJgjMp5oAkPSS?s*-R&x$H9u4ZwKGcXm8hJAV%9KnQ0O)U)EL*9+12+XF?voDOEb zIi2#&cQ7Ui*?LCg>pLYw^*h9Yp@I-6b8?LdsSq?E8GSOnVEtZ>Q5CET8ps!}1g-)NSvx6y`OfNgtvSV(Y9-Tr8h7*_u+2B7N$=Y9LW64W zRZPR##b~GTYY!-T74sy8mxB2j8#<1$#aM+ z5eHU*GQotV z``^bi4@NMmxU6hlIhC(qxxP@6 zxxU?g7#=g-M^{J?s&=9W-va}U39!W!-qS!!s?w?p)h%868!yGd{f?-EoRq2btbxUy9l;&Z zLK(D4&Um9)5PgaVyZ*K%t2iLy;YuCUg^a=POd0H)n5m7i{3Hy@#R(D!z>EVI6HX<4 z)}x`3G*DS{I09-8d>+@l+0ABn-Ap4ox&n;;}` zg1|e_JbT~-ISTKq@H4($)E`KzYCK>h7)^}CNCPv8P%fWR01T{-HKd@Lc#bGnNiNZq z)$a|0kX;5b#rxS#h1_sTld3NM&V2}m0D&_)O`73vvIhcJ;nE>;{p^RYY1B8^&G?SFBEkmJO$#eFJ6P?bjmjQ!ZOtv26bR*p1leP#?ADVWOC6-P zuh~SR#+nOfiPBe|ki~n&(a7B^VppZ((Q0+H`gDJ-%E^@bJ0s(}H3RSHx75Dw2ON8V$#*S;2@z(B3;mT4j))eN10b68&^Mm;pyt#R*%BnD6+S**aUOPDw zmm3Xqvi;Szn%grx-f%0G*)+a+v_w}V={dWlnt9a6KJN2E9G6=At*-fzfvBG~5oW3F z(Zu)7u%js-BwOD`M$e@p$^V&#BRRt`3$CJdPd!S8^2rdB{k&x4%?PUR(9tk->cqnb zmuQxGE!X&IfiIrPA5jXrb7uh`E~!kc+*mA|m`nLHrVm&@6Uqvqg?payJt*3B$J0y_ z&AR*MX~7jmI@8PRYIh;ep8eOfnub1+Xk3-u&Nc_j5brfr5T_i@ESC0WaEW;in{!1)jbgz_LErlGd>=;vkhD-V@! zuHgP%4>K7RYQe#s146_Kcp7H-h4JTZ0E^V>C1T<}gE55zcK18{y>BQB=`SvI8Uw{@59as1 zcg{l8R#cs}<6-DF9_?X= zy@Qzr4~KYu+J9Zojzj%|$X5ZiMv*uO>$YLFMSzv@B!Ytc7+ zZ~F3o5(3(da;i#r07Vw*@O!vrVy2)&j)52U4nnz?Dc*FGj>@NK`Ls845fZo>ueAm$ zE|fo~X?kf=6vX_CIX8TNouY?8e}W!(!GwCXEX%*jH=*{-nhH8n$Y5`>qp+cOmE&)v z0{==6{P-L$NVOySV&4QdB}EN=Wr;Y$Kv=bxHnYmF0i3AZ>407AxA4mmHuRsW1%5B* zz}%lJn2z&eJCU8}pcUfV$}X!C<%QvmZ}ItSAh=}p-sOj_CMDb%%N1{O@f2$8W+Y9bt5H^he@O>{uNOx~nm&lnpS=WO-;lAB6Sb8nw)_NP7a zAdT}WgG7ogz9u`wqN~y$c8ysBJR_)iKQf*mk^O{UIc*G5RZk5A3awBR5dU{C0LKAq zf%9mH@SDg@idV`C3+Joax42W=67!9m{(=8h@3JbBqs z9UGr47PZ>SRTNl^px)e)7Now~PYp zyoueL&>*~U$F>y`hD5~rGlkcWO#hfS^g&#Q!Kg)QwMlZ={yWmbak3M>=Tml$=}n+y z`vLd5WedMBz_T7nz5edF=6tj2H+Jajt1{pXD1hc^f{!@b3&>M=xNP6_uXzgz;{s1i z8~fDDetdKKj@4_1eUV16&0bf)t?pvXHKp0?{P#7P%ofHG)U`D6WKc&d1<(>h`QWQH zAeG%h=3VHvLx~4(n=s{GdhX?w^)Nr2@~^+}{qoVN+W6V~^G4<)jS-7x{5}|mYa2({ z@xSKuOU4dF21heG89xBob4Oow0!QT_GK{G^QLKN-|6)MJ+hG)4xNw0R6s>1J&^$Cp zQ0I=JT%Mj1U<(?12E=ag3S9AIP1OLT#(rz!{evyhE9_VCXNFy|G*=3uPyL&Q^5pe< zKd?2}9DZvU*%JS&`qBlFY9NiFl49JaQ+9*(MMh%>65euK)3}aU(Tg}wF--p15U~bD z`U(%RBxl6?l|atAu3J5aNLxr6|F`nZV zeglWY_7l*>!01zSbe-0Kuh@Dr4ICOao|hai0BjL9gMDg#O3)d30F-a;-ezmD*9f*hzS z%6(lC)F#JGeoK+;$t#Td%u?^3Lt0pgG~^yn7_eFJYp}vbqGOLi$5XtpPTom^*#NtO ztL*M)aJ{TkNq`OVTA(SUXt-lSftv`p)>UQF0%K%DK^>>)5X9cvcbNQ54v6208Ys#u z_(3tZ2Dya{Bx&It2DM@QI{Ewo$2!S1vO57{hmVbX{cmfOzvDI2OAlV?6dubyJ1xbD zf(!PCvA+bH4B|WK@fypE4D!qpiI3|YiwZSTOR^Fa!RzFPn2csqo;kF(j!|la*DW5PYS@5;-f1L~NriacbJ7LC@8s86 zZ1)T!`la9}$Td(Xk`yEydhyF17a<{6*`WZwBcBKiZ_;}$8YtepRhI#W*br}oePQe& zALAH1{%2Fh;Fb}uXy^sOXRF&O`H>xnw0kWR#t1Vjw?AHJu};2>IDHQJt*gJTRsIhP z6yf+e`@R?#ku&H?Y@?uyA|2mG{1v{PV=a?zYI}no-~U37+k9kGS1ryEBtB0k3dnkM zStmP`V7mM8KXt+Y^H0*&Wxsj4);xCwqA*E1heTC%j5M1bGjf%p5@4 zmrK2>94-HZZW1+Ap9I&^{x*!4{OK}2J|=~*$o$CMh)+hX#S&OJ)h{EUFxAuD?kZUb z;zJQ>A8ZKMt>v&Ewa?}-|GwtKiwrhlfKd8W>Sh~d`nK94?2t@aSWQ*)TPqR)QOWik zYFE3O6DN$+2cHQL=g6DtaOgdlR#3u@2sn{Srs_eWReksm?(=WhiV(|{~v zVfc?WCv@oZrHEiWd@{((Ibrr&UZJ>eW;z~k^AZV&qW_B+|3!lz2;py(ad2^PDPC?; zOzIKDITg#etHI~n`lA}%2m1Stms+(A3tG^b$(Nahk#f_~Oir)JclTJ( z{CpAMX&6y|T7xrQ1imJeK!sagfjDnw0>fKya5EN6ozc9W!Ha@7q!WfdAHIYAH}yOt z10#)=np$(qutg8ATgnK8vVa8{24YxfA5r`b3Ofsq=>loAdmxO;hCGvO!@+G?d}`+B zDVoop-Y`~Z3}CGHXPFhr{%!gjt5%DN#^q$zbTck~&)GFiEEbA5RXWbm&GX<_L&n5Pkd-NAOqV~SO=<85a z*3kE$Ml8jKmasd*&4543|M*K7L6Ktx$_&P%Madx5{)9#bi4wMc`+1FH61!dghqqr* zc z(|j<+gCj8f;P=GYu1*$~&UYFUXymgyz^DQ0jZc*+I1jX(*C?y^dpXRX4T=+C0}1)` z=p8}aeZ&;9w1|jmTVg)dH?lOqoX@hAvGL$i;H|+c3|smEY4RX?kCz5Bu5~~S5<(||5(b)Nr{$X z85gvuUb-K{s#$Y9g{Yo7PH6T(oGSEE-#xLKCXP&2}bT@Im^a8XEw2c&zl9K?n0^Gm&4wAA?*9# z)s!s6Dg=pkL3+@o@d5z$iYrUsv2Ezdo+2#vngPl3q`^zqmCcE!EP$#S1t|?$L`oU|^ zm+};Jdc;wuIHs$@^dd;OLXE0>SSH`qU0>`+l*pW?$wq0ek9Q`mob}539s%gWFQ!m)>YFlA%D3qf076VHV6#u^!cMc%P~PcxwIa zp`qx`Og)A~xy-rKuVxhG_1U$YRCwCo8EQ!6eT|o$oVfet7IWyO#wZ(s8j9 zj&c2!5t6xd{MeA2c@bXNZ)PA!E@WY#7s@+X7}!9<8&horFs@QcbSRe(e}}|*6>5X> zhEdF~1dLgky68VgQXCsV5ktlzF%zs49ME*xqKx1=ddH^uiT|B2V*bw@@5M>W#-|`1 zRKaHPX|3`4viO6?)v2(8%jr^rerGhL@T(yg62+<^-C>^Vi!Yr88In+pn^#WjwFTn~`wl|d;D?nvP?xe>UR>-zA$5JT{_<=!LrNi)>hcO)=Q3~Q z!e642d`G5cULJ?Db9HrY2?8kmzPe1DVAiR(K9Y${V$$5Fdi&-(qLuES((_2Fj|M&l zkkC>Hhd$%~h!&?A>`l`djw(+>Fq%VK55_59egsVfKByV#CS&-F*`)^RM$WE<5JiuZAf4XkDGe z?l6UYALQ66OPPok{;`JhCah|3wP`G~{lnq55I&0H&w{PT6=)8G_+xcWHsfF(MkoR( zS<+r?gKD6hrlzKBJzBy95B@4sV!uQXT98SxFqu8j7jndb0s*I0;*dN@&}|gVd-%&Y zvw2oq#ukO5^G^!eD|BN>T9hy+Mw9I)I!~D7QGwwxh8v!5xButIefz*qkXX*S?<0xd zG{B)SNX{{)i5$*4KkC=>JeacYExOJG) z?6O@ga?l??5sem3%egMARbF5+01>V)ezik>u^1ym3mS40hd7XRVg%(hSMNhdM zPlvrLi2C?m99VMa59)1NZaw{RdJCM(C~7Pa_gCl+#1O=}BhnE8G6-;M_+DXUOO2p5 zzs*qu*vkzp1SAX{7;{kVDruo*WgX?5`RLuN9YAcbjiSIOyCVm-SfA-|JO6b~dnhww ze{IVvk0QI(T?01+!3Zh_t%ka9Q3b~|@R26y$`K1A@nvpYGll$1U@DTr(*kbBbdi)k zjvY=Lq=^vG|3%t+M@6|jU89l*5FC=^G(-VGBqJbU2!bFWL6Rt-3{kS8k`ZReK}5hn z63IvuB&tYIP=+ju%Q;(?poiy|DEH~o~OI3yQ=oyRb9#P0x!uF+Fp-5%jR7!TiqRWH<*Ec|K8VW@V$P9{`Yc4Yut$ip`bPF>P(6FkPsRK&d_t} z+C^OX@j%U79_@r?nLhEn3i*;>MMny7U;L;E950I!>jnh{UHzW&ocf`qx5ZOQn9EJ< zSri}HIk^>1LFhp5G_k0y}Ts$o5U4HW$T zQGb>PIbkPnPBApC>(6UrY47xdytHW75 zS(t0%ZaQU}N4~9Cd+)rrtQ`Ykto(~%p=L49@}pDNJ%wy8-k72wMmsnNFhbm5y@}Hw z8p*58ZrS);?R;)P+`37%Iqhrf##$1a8iQLm8`a5jgd|K98Y(OM{W+HxkIfacv=V z4B$_z>Ok$D+03ZxaA}5;L+Q2)ko3mOuhxmWylJJS5`i$$D^suT2)EKiL)WOgG9Of` zr>{6knVws9=QVu1WDbUG8j?2t!XlLuM zp5fk~pNW5}{FOz*0WvsVp;mJIt=z*OPxCxk7o;QG{o?Mf4H1&q)E-}!hJvHESp7JU z52wmYPY+Z$Qeb;^WgQBdQZoXOh&aOn^1gP6e z%FCsW9f>^g=9LC_nin43GNPfS6{of}7g%=X@Pi80yshbGH7*++`%QD{b8Mx-bL2eLd=Qg#5v--q<6G0_dmrd=muXpU zpszm*E?K74ZgsRWFOE$**A$yOKXRKR0|})qm{`%B#U8Q-sARsb!?2+aHaC?&NuOvCtMowbG3 zZB+)YS#LZfnOg7j#nF0XVWWlZ*kTb5X$JkNZU7=wT3(r=uwf9bF*ogo1o?TB@!SRc z^0K40<+k*F&TCCXeB}D~6E&~8WZA-!E^rseZn9}QaHy&9mooB8M;g6$LOCU=Hx;&L zX2PHm3Ejr$p+gR-p$r(NCKx8ut63dkxPV`X_Mg7FZ+nuOUz*)D;2tq=f`j@O1jOA^ zt-Ge(dd;k+tJuM42WU^|>`c{vi3Lf=IHFk0t@YueGwr-|lw1Nc*~{dgk#CtP*jQIy z;u*|y7@X~Y%ftcq(v@Oh9A|Ia>5EY-&Xv+%cI%m&n=A0;^sLWmf<}e?&{T}bTC@*Q z<8@^_-E&APDj+lZwXmTh2dJibqx0m4D9ItYPgKX45U>I-l)!k#x5S&n)h_^M5H;j` zW;8zOKDlg+Q(P#`otxM5zufw@@_Ztb3bz$}rrE7ZJND>7N@96G?=B>7wN0MNAdsIu zERO_ClU4r!wg6rnv=4-$0D#k5szMM81AGw;U_T=3SASuB4Sseg`J-au(5FgonrHv* zQw5FBBglWomgdz@4iA&8zSKbXn?a-sMN^=?0{|>2l;pE3Lr8?k_M1mIXd>^y58&*g zr_f!BVKGM8Ix5P{2wwtC zW!Q`Fe+7O6%gbj_WWwHBN2YSW-hjd=g8|8Fg9Y7!&CZx$GP@q>wH@4&!Am^?b z<;%a;6ZvG`?DXp8uOb1U4f@M4$%_74S<@^$OkH;i^@k6A9NDss=dqk#7{H!G1xAO= z?%Xk3<*Q|Ah~2y}3c+bO@$|z}*X2V}clon(TQ>%~FexGA1x62D%g<#%1G11b)AP+p zu*GSmNpZL2J$DZ$^_0G1YWyapro*f60-(xHB7q-a4|O+Sg4>IM(SzM?G?^aRvm$^T z?`Z1{(v`5zWhSf}Jw)q?kgzbByR*x?Ur^4<`7Qu-vR?iQ*a1mE= zlWUH0*S~+x@3?ZKDg22fdM($v8Gnwu6}VlDrZn06D_^<}Ic4h6l5j52eHx*5@g`4$ zHTz#8@2h!9_lx^-=C)7Yip1`B(#lbuy9D~gtGBOz8Xb6`{3wHRMcqK~# z^@#1#d8a7eo+1HjazDwHkJV41>ZA`fi6?yBKE0`P^S9}(tEf@gYokp@U2zIH3g~M> zfVmld(W^cVb34zXT<@gZ8xgfcCX}>9+%pphCR@2p7mt9&FT027G{2Q(chigoujSCA zerc-WtH0*E2AMGpuGCR1-nzYX;2rY+;1;y9NNrKO$ z3aZCg8HN@%Y}LbZsM;0{(UWSt2R12`CVM1InYwVXta~-@x1{IdKf~551~2WNNZ~(89?nc| z8`aY^RdxNVP13=t>L9Md?%?b!eeJTS3j_A-$_?b3Rk~TG~}VQsWTqe#c&Z>UHNjRSIcK4ph=b~ z(&kF+B|7aod?|)C@{1XeR6aSee}QAG_KujG{zW|^!sa$`hEGp}@8KTJ3(xuZY*qC? zUj_eJQ2vLdMWB>@23_BB$N#PmDT6QG(bHde|4@51QsCpcP`dCvwV2vI?GWU+NsGZM z!gP1nSp4fXV8}MQ@bz0S|92Ax&vEp@6i4VU+7JrEHiZ8XmiI*eYWg!}we&0vCKg(D zcK2Cq;OiqXgO|ns-GaIu>PFatEjUGIPdmC<1CjGSd?0fwIf?(5IRDvUrwtx1pRe0K zhCP)a>D{jIXc-vW(9l)5vT5|NUi7`T}sFNuPe<_W$XlK*WxS;0@q5D4mMr zmt(pV(YA=n3`IDvv#{)Ifj?hYA1o6EiA~0@%dDGGyv#+fE&jC!_id&C&;Wb|*~Il5>r%C?6J&+YDiw*{PzWdrZB5>s5t+K7n}Lu zMM+C>4CVAJi~X8dTHp*I&&dc*T{j+JV^!;F-r-SE@^ZM$=H1iB!D4`pmvI5u?tdly z>RCx`ssCsJdNaGBN$c^}zdI#dUTq})^NH3`CpM@LHUnKNZ4d%@DdKi@aI*C;ND`B1 z$yQy(I3wHQ6SGNPOb+2EfY?_axCLA;rvgec=u6`1zc(@zt~~bV2FeD?kc zjo|&g>q_hGOhLljf0uSAQ@=K-b_Krf@YnpM3xxI|@)YDWNi?AELFEdMre);tEF7nt z6+b-Pb)r4gj)9HXvY$qJe0z+^27Yf?m<7oa^}2}ig@NavJWnxAjY7BRK){tEE~2R*GB7&yqrXd&;yW<8m@*j>a=bQKyF{&)@55U zx)Ooz_!~8Tm343K;9h*0wqA|o9$D8~jvYC@>-A96;dK-pbUqlmyubML&0pyKYXzus zT+;ocFTR^)!mS>YbOExKH*emUT38&dcTCL~aIb2W58clAw7iVZtmhXTP8a+Q&4esr z7hWuVRNVVdCFKyzHjI;3gUog-9g4^_9d4>+11OrLbwstsbPj=hZM|Mn_<+5({-$W<`uwnsX!!X>KhoOQZlXv zzkVm#c=o;8ANPTRI=M@2hkTDb3|0-=@oNC?21%DnaMiBd2sT{f{;?{>(9%ArR%LH% z;LIm>zn6RzVcCBkz5l`VJ4APNP33ETYR?d-!95A-$49s$q?)%(fnnH|$$k70MB!RU z=kxPdLA-GC_AjsB0OXv#t`cJOP}$r_aVpZ)t_RAl{?j(SaiuOx!tO7Ay zb1NtsoB!eGLwj&0lioK(z5r`z1Zx;u;k7TT_9iQ@kY5gY3)RmY$g4pEymqtnt)phAV`);DP=T$(KMD6^{WP@$9t0Cd28NO|YSiW~L=0M=(Z z;@BPKw|RrMlA(9DfRzlH6l*>o`o_WlPTg)ZiY6SofLBUfc&7gMXX`$DGVXEE59x5L z*rUhu5vGgB#zfdJN9Y}N|F?mfirbm?`-{#7ypAU8-#R$2$p%5^ zr)_@r_!nmD1Ap#lmvTQs-*`=@&;_GkTy4lLS2}+2v0HV>+}kuAv|(hc<@0iW^AqL_ zrgx@Zz0#a1flk*}2%On)i;i zy(`-f9hYAp|L0!hc*SM~#hw05*cr~jO@BQUJB+XZE7g}Cu#xo)J> z;H{0$gYRN=!`r%uOinMnL;St&SiOeVmEA)8jSs3n%}$MdBe=bo8VZEI4g+*0G>KxU z#VG&Z8*?4KViqc!Ju<@KCJK+mB5|f+>J&8NTT5=`@9rI!nHafgwu}4H)Nm|sI*Nut z%%WH{hZy^1x#sup-#RLzz#-28@g+Bxat7L}Nipi*VLjKjwKN>xaNXTXVKH-h$Qyud zf|`39H=+B6kn$DREWpb8@oI)t+4{aDh;@kuqGiKm6OY zGoj98;PWFRPw3{E@oo|Labu`B28IC*QBJwLnH`6Y7|l(oELXo6$qrSG%HmcP5))R7 zOh1|O&Un25wDCU9=a~K=vQyUt1a~x~gSq8A5^Qg89uekRcmgr35+H-WiybwAAG@(EBkMnay438dp6KJ|RFxhudYMYV%;=HI z$C`?w7GG@-Uu(m>YWY9Q@9gvBR~uK=e0MJh)HtT<*e*-yJM3{LCMF!=7P&bTsld~8 zbL++nk#9>sT{Axobkp%C2O`gm9Mm>2a8N@w3CJv0D2H8P=V;R`b1xF|3>=j zS6G7^oSS$4uccm~w&MsY{K;uyc=PauH$vz1wy+8;>9V=cTsJUl4y(&%3?_LyW_N#L zBII_?Q3SJmrK007E6LkRf6AfCb#8IwLurgt<#oZEuS}cdj8$?iNYy@(5UoM)#cJk z%ea_3g_WHbBEl&e!*l?>>sxRjJ()5UBB!~)!Hrdy|caA+hm`XX*wAF8LoHf?%R=s9M z!IGN{h5^fudX1}Yn9&ogGowA#uAcp|fu5lP&s@s75}m`q9p~0_52rj8$>g*)`SQ54 zDjXcc9nzY-L^W%AcvvJgn&FN?-?#LMbVN-JoAn(K_VlY`r_7_%pVtMvmeV$s=6_LuCDoj@z^6Vp@l;sh)Bi z!E4I);;~y=Jcf(tv{a&(HpeY8%dWg1wg0ZraeLhWK2mHp29=~&iqEY)8&9v;64?H{ zx-3^iEMHj8pe$bBU6?J8VW{x<4Gq~bY1ZsiJ-;xAJ!xwO!uEqNLl!n=5ng1{;i6h* zffSWgx{TzGsu%`hOfO9mUll_d5JR}w3R!EVV5!_m^?n_UG>^j-M$&b`t!hgd3~_$O z9V>__qiN}?!KNNp3x6zNU1_2fa|rE?gD|PILD?g=c+y&CBYFC*^ae>?F+O2htZUA4 z)qsT4@Is;<_A&!jPsc_8!=U{jH0*eBIH@3V?RJykKZoL6-0ui6axw&NuT`&VsQyl! zzP9}d_ZNAi!_3E1TTs*JQnZv3`fL31vgjb`A6lP*`PNeBPn#WOXB%_YA51PY)86JQit7O-1%6NG5)Lp%baHgt9NgvU_QJ7$xx?S5vy7&I}`ihwn{ z*TMR%>l0pluqy-a)?Q0-C1s3e1nXeB>wTOPi>uLZe#5D45z6Uq&xMgL7MZOhuZ$I6 z#}9W5`7*b;I(^z~|7Ws&OF`2MFxkl1LxgrO|4&!0&U?!DaJ(jQM@6X<&N2h!01%7?>(nDM#4S5qV%l8_BGik5}zo3h0Hg#MAj3C zmR5;zK%SAXo+O@%#*5@UZqEEjXRtdcUgwav@yyjgHtdAv7Q2{gUf&rHtwD}*l97T# ziqm+3vwo8#R9t*rBr(n&8uu&;$BJKW=L$>n)BJ$w!BOu6 zPC7Zi?v-5fedds?iDwrj^|36uibiIw+ZcIZYdo3aapG#a+Z3c6w@^dRMb6&uOVaCM zdVX>xs>4^a#mVAWx^6YB3(f7<$v`#c{HSjKYk(a;c8L?sx?7C9`P7t>jjaTUkM z9MET+`bVFi)l?VYDR*+cs^F!{Vy`w`K!)j&u!_XVG4H;Vv%m^lV;GWgg_jbV2!##_ zA)hF#sD}bIny%G-yvlvYe66AE5M34TS4xPyZ8Tn{DfW)h=8LLp``VygAfAvLNrY(d zvIsU8{+dmw6h{3%X6NMCf2=5su0=wlh7a`_p7d##pc;u!j$j#D&SJ6AU=T~{n7F+e zUzsOb@4)HVkzFiD^96ww{%$_rzhAh1vrhHg=eISSf{ZgwFTZJ#zhLNCgq1cRE z3Kb8P$1!AT3!UUJdA_8@AD$~3?b<1;jI8bKDY9+_DDP+b9f50w)XpYTX?$4Yj=(L@ z9=!!km-9x+O9nM~wdt(6xnd*VpUKa!ifU)3*v&Y!$=C^cd7uIsM+@Qj^pQ8QvOHfD zH9^#+BaaprFRL+)jyU{k1MXb?E0;Dfj6Xm7o=Y8|djsvCZF$L)XWYO-*I23*eV$iw z7Ct*{ieBaSA)Ow#zSg|mv#)V-N>zOd(`1js_jAfzjVtm?2~%4-a3BxhGiUaM+Y7fRrKS#%bQ$YZ_XSVZYjIuVRIAFEQdJYpyC$a?a_s`|_zL ze`3H2IgwYQ7?1zE=p4H_i5d}KK=2VYjv&6FH_0@O8b@` z=}xYzi4v9Vj2%Cw-yN>e`FFKBK2UqRlm0=2Eej*IJm@xV?&8$8QqeDs#@w zY1!Q{(0EC8yD}W7CAb^CN2b&Oq+=#ZNY?f-*4%pbG?tt$H!~@Vj^4i}tW;f1+m;yN zd$^Nz=9M>PH%dg=<6W#C)>Fpzc%)Y1a(WUIQN5+^BsGb>bgaO2@y4*`L_PZ?L(z*4 zgr73+6<>clW_D61;T7`Mm!q=5Fel?PIeXk$a zY5R>lo?a9Gr}Iv^q7EDC?io~M*rh%5KF`R~ad$T@Wh(SbJqnYok&~ag-l*CYjQ8Cz z5~UA(k0r;4%ER!k19}M?IKQ^Q$sUs_`si|~Kfz(64=u0Uo z7X5d2AASm<0yGzc#*G9t+CI2N7^fm zZDc2<1|YinJVk8ELNRht@i{fQC}<9QZr0`~zH>}ey;@#6)AS26G=CK@Kn1dDju2gc zs$tCk`C*gjyBR1ni2_^}QqOd!VVL*8P*~frg&h_qZO){3{b{SAO&ppEpJI!JvJi;Uv+ zzUuM>V#hvxGGw{>*d`-DYewm%vz=)GEOZsWI!2l^_@ZhVk>t7M6}UsLI$sE1)D$|q zTh+-ckI7~yF_U;6(viq3B{@OSyKE$$rE}_Dz;WUN+>nCgwzzwaIPGlk3gW_&v$^A5 zVVKUo81;jn&?tE%%F4h;`da{gA|!682VG3&Nm55X4G-=qpLk9l&mm-t#u!tDiB83< z#^TTI_71az9mFqTk%uPQk{Co&(I|GyvG}CgceQCQ3bK!w>ILwJ7zz>3D4zevnA@wc z@2^8=t47=84!J<1SWSbfy_lZY?pJuuEELtr)A9F>+WY?CpN$h4*9G!R7IVg=^HgJTsvzi)wU zWT*;gs5=;&^g1~#$B;IRe8-O$7UM{nAcJ^K2AO@aY9Zz%B5EzNO72`-CF0x#46oReb-umADm8U*FuHA;0wYuV&{azuiP>U)`)JCtoPdNBmi=nZuad z4!^P=m~O0L>A^SF_px-M$ig7-JAp23rTKpu_r@^UQz095Oo5Pqcnw9pSt9ySx0>f? z#p;TIlv%3@sdz6J#4`I2KG$|J#xXJs))jh~rRW{DO0=~JBLC-t`YZwgof#_$BY~yA zS%*rcB#$PSBGSOd#^#P}_Y(^Wk|y5K36}~5feFkbT{A~f!#r-^uri2bOM(KzXgm2x z1NlSo*KV_C2rZqG`gofsJ>a$TipdWP)ZGKSu%3GJfpCZch5W$OQr)|^vdWWIM;?e) zrN|}P-l;p3zQj)YWA9S?%04sVWJWDGp!AAZhE#p4Y4JBPTd%1^-jEAM{HP1T{w z=@GY;JTb?H&1NUPhgNS_Xi0MNjX1Ot#mS`(;W;`{}>Q~%}oeWKL%s` zl0wcw4U;S*LD4JsNjxRQ!CSKXfH)=1opyBD42ZV$KUpJ)%v%0 z^y0w$^>5v}^%3J8j7j1MTMQOfdc@Ij??}IAO(dRMMN>T%23#Z-

O{KGU4DwLUEiN?yKQnlIXCPGaggN~!(}vLW8=jeHm>rk+&?H@F6F`uCDQs#Fq<4NLV5hnc`-HK!_8Jr8X&5H}H#F5hA?Yxm z4we~HP);T2kS?;(Nba!JDWJ{E;{zr%QB;;s%GwO($LW$(ns>&BX{~GW@k3=lh5`Oh7F}|Z6PQqQb<5G9$rbb>#y!$F++Bs&xo5`|X!Vmv?VuyH?&M?% zFSB+>vZzGA=0M%C3Y-G5+k8}~)G~Y_+N?IWr1V=hI%Y@p2K&9v>rWI_bApQ&y0_oMNw&pK_e}&ruPEEYuLLw+s&9d>>Pc}} z>dz_m*s&e~+tDe(f}(u>>&NH0e$0Y+t(Fa$f^>v8kF?B)1lRe^q4%TJlFb`@>HU3~ zJL1k>)*sL3;b&0NgG8g&;p}8jdpO;*-T+9BC?0tH;^P~ ze5@Vx2eS~|dggav{sWUK1F2B?BMK)W*)fm@bh^xFC<{t$Ke;NZDy1Su&2JN{J09z{ zXf0)sk-cXR5~Mlp`jg!R-P_r8=2I2qe5U=Y&8P^~b=&^zaNWRc|7DQL&Vm-)Y)h0- zJ4j4rgq)X5Pd@;s=m02+nYYsLgHDe21kPJ5gX-k2^$C7@P>M&J<194s=yzf{>Z|02 zxu(j3>^e5$)n7p7Esil!^f}sgD42ZFnI$@mHRVSBw#JCmD!G_v1>8mce7Rnl&;N<(7Sf?ZG`#gXg3i zPZIGE>R!}Z7H?ta|o7EA)I;e1oWP#K261+1tF|c;vE(0Mg=Tmqo3k+tl3Rm1=SN!jB)lXDUj~h4ACcalWJIWO`z?8+C#QL zRx63Io4?gKxPSkI#t^OAqZo+*{MN=0a-RMlTzH!b4IYJwqvaq97_H-`0R!~}pSJou zodTOQ(Oh%`>Kn*p)P=|ObXia7C*`+)wyzYi!X~BrpXg6ov2eVQWa(cd2XiQZ>BQPT z9mPhrU-^F8FC^I!&M0J12xZ-y8(d#XoK1`;D88%eKawhiVotaZ zq@a7c2Q2;?W8w_?rH;tHO!;NOMfF65 z50xhLl(qSiFs9a0Fp?Ov{h4nQfGxd zU{`D!)BE{d2eGnNM{^F__m3byUq}!lD6y@}GAQ@ua8-aT9%iio+HDRWk5q#SuuPv^en;#micgwgowSsVc%=~wuelTC zfcaz%b^ux$`1!+}(GJW2+<(49gBGv^n4ovh4T)6FHbuljGB=L~o)=IJTd0oUdHUaD z=gwDSwmo87MHmR;yRvaJT7`HXb;-LSTyPqEte2sF4!&ELXF68})VgzlEc$s-7;XY>2 z#{a6owD|fP&~j2U2TZ2-adGrNKqf3X8TEk(Tdo@hEEUU`bl*`h_DBtRAf4(P+NpTu zS2)=GccGn+u~)hkK>95m>LWv5VLbfUHtT2#FY&d;Jc^n9lAPPAkA2hz^JrAx^)T>g zY6sW?Rvxegn43f$RFE87uKJ_^UYkSitP~JX)AUNpM#P&iUj_izJu*$^x!AM1i>a0^ zF9r`{7=yE$g2mv`|3|;~$mmM4F=kVN2HirMF^?v8Q$2x6uI_9e&<(`up1xVLe}+l_UcwO&H#;&5Jke{TWqFoQ?z^t(-{dluR8P^-L|9&U zg-bxnyGKgN+fo~8F+%}9v4$AfsT8mjPOT_GtewvM7Fq2mSZTaS?EEXl-`E(DUEfFX zijr1OJ6X(ICv2fD)Lz6A$cbHzumbWg5g;Gu#LB^(!?}r(W+a>sXwKIR3DFpMpvGz7 zpA98b5n4=VB+_E=SZ)Ffpd}hdfx6)Zj6z9|uFGlY!bAa6eGXH*@?ZCqNj;7i?`V0t0BKJfk~_)buW`|s4?-_2{$ z0cX%eXi`K6tr?Opy*&YyKrGb8b2H$9BKFMfe|;IoBhZ?Foa4=+*VYqTEv911o`gbN ziBy5*w*1S#s?Wg^PEPUf`Tns8|2N>Zcrv#J0&@Q6hrvkT$#Zkq{sIKJz!-mXs`L1i z`THH#@_>Q;Z@io~nRdl*%-@>?I8GSMJjgE@eB_C~_Z;?$rpf|dS!m;6>1Xcmw?JcZ zl8WW_mCL{Kz~79Z-8ER-Tml|d$Ks(8pQ<|sey%m}c_C>(6f;8i1I!natk+6%$NG+b z5Ne9LUe;ODKL7o!kq21cq1c=BKQNpuajir_?6bo_<=^sBS|Mf8?{_#d1AUY}VR3Xg zJ{Y{)WFU?QYxe9ohGYAtbTj+zFV- z#Sffz$w&DFtmV{Q{-uY=p8Q3H$-%j@KE5eydmZIi=ay(7+WH3(&47yWsXA@a%Q~qi z*MZ_L3>=)1hzx^2dw;(Bk{he6q6i@)&BGhr^&*P*pg5GwORjg2_iA7jcDjpM{9zEr zLFo`raKE<&KW&$HV;UQtTBtDD5+zCz=y_E-5Xw%}7!or+I`5tAxwB+eJ}(SNe;^UP zFvA5(_n6tsH5_CCSSovq1?HzeFn;K^ziHAg`QWSMcxEBgHt}LJm|&lCs~a58I&j41 zJOgjg{8{%06Bpg+WJMO!BNcz7vP@Y2_!8)+jh7!)euz|IDZdXAj9DdPYEVxB=t|4c z-*=oK3}6b1SMRU0jGOCRq<)kle;!_D3$AdE92jD*gOKDo|63pdo;w%E@n2?e9rN&Ss z@rXOIzuqMs*j}Q}a|MLXGWT;ZG_4X7Mx>gIv#}d{!1N=ER!uW;KStl!<#2)^_S%Dk=GV9;7Mu~MV ze-7k0I4%p(@`5~F5z+~Aa*@00y`Q3Za8rif;NcW;v+9W&Eg|Ef7nZe6`|-)l0NN8< zbz22{|JVnbUp@1j=lZ(A#jLT0Gn@hUP2jTJ^Ab|^vC@FX06Lj(53-!E6UOAf_kf-v z4)acPJxxkgF@^;6e8B4calNgWfN+Iy28=K__8Dpp>5;lb)jE0&rXV+6uI!|e%zp`w zk%J`ql#L|1v$)DEK2I6k6 z+tf2_dYA%zd?*#x&0m%U-D=zGRNy#d=q(7tLelGQ%_GDR;8E{P`kvJugPLP;x7g9J5V+XD1mQN|lJ7dmw{taT=&@EQ zT4u^$)ra_%nyEpG`n3)%hx5 za5LK*G=?VOlTbcB2$M`4`VUJRiO-)t06@wNK=Gp0(xh8Ki32X_2@sK4fB~Wxl7JB@ z%gf8pykdBZzyOcs6CQ9YP;|TtD^~p9*Hni_4n^ovmJ27~J`8vz7(?<5(a=_Zc0a zC-{clp!?&89wcGN>>M3*!MEtsWZv;l9)hapxZ=GGw2Q!`mm|5P)s4(Es|%hO(Y7j? z09KO8xvoU`TF*J9uLTKqC^-O0!i5GAYd?c&`0EpREZ!2a?Fa#hs}4FQN>yw0U=)}Z zn<{mQ2j74$d-@DWV|Cn6zW)<1PQ<u995a=~dlcDd~;|;&`8r_2uTfb z^*|yoQ+Il}O!-<}6EU2rMCHEGbUougtj(R@ZfrO|$Od55B0v$z0f2_urFnFz6O-w3 zIFU(znhaTvc3~1RC9f=KJJfkz?~$!=_ZrXn!okM@Jz%lBw=*2)K1KEW##A!! z_ycDby`zM-*nM^_NoX-;eD?jL-S@YJ%cGPM_#!+4FED2t)fk7_xE}7PQ?j02TgsNO zXj=?kWp6gv@^f1IE`7MS!hwtzh@PC|j3zLQrB|st(d}JuSs0eie1{r5w(+)#>wwif zWbWKoTcbWJxT}Nu`6K+%-msxzU2VOhn{Uu|tx5ie=Wg2WGr2q+j$x$rkA=$Y*^NYf z0y>f^bX*oaBM%PVsf^!+mD z=4b#ua%M2~wgXSgwmjiIuU+KGSAKEl_5+rd*;+EBWm#pqVyJ>V6QERD={`PnZu4dH zvA7VTQp=;{K|L%bcIuMJYpY|lyEfKyOha%+EvEdf=0L9LWZY#$I*H2RDRff2)Uo-8O;pC*J6BFZryP;awxuQP$gY1azNSR zGC|4zfZ6vT^43{0K_`zEV*k4WO$!qXHKSR0_SKi|^@0cE_FJZtuh+*c2Z}qLtOSNk zKWnR(noEp!Z#=LDVYgp;?C-|vD2!V+B8JN97n_DyKFiMQg%rLMKh*(D04Qq589=$_ zc_jre`)HN5o0$wRT)3dFRsJ(U8#Rkj-n&YYUbjE>I^7f0-CaxAl(jpoWHYo;fXy1w z|6Nu4crkje5x)QHr^E3^Gs}gL!kW#{k#b}nY5i}w)phtO0eR}8%Ttn7YgFzfNSTts zh2ac^>$!?})f1t^U6;?;UgQhTn1U*)&Fu?fOfJhBIT!84BMGUhm#OoJ(mzkB5=RdVkS z+t2N;2^6m?#14YqcM&#SthfYmkUys@rhQy=&8JZI;@@cuQy;Iopon$vU0adngB~SAE{P{+V zGGvsjG~SWN>#|gf+}5GATny!UqLK(5D6<_Fx_s~ibBFG?4+J=mwa^K*;jBUh>++z{ z$=W375K|(|%G-_RG_uobUUV@t_^EZ!d)9LTj&r=GKZ-&p;8UZ*Bbk$vPztExaJDuO zKC#mn{H{_6bdPPX`dNR^LBBy#gm~2ZFxom_UYxb3Q0uToQi`aM06 zABtOc+khw=O5^Oas2>HciF;1aD*PhK7OlE|Wn7r(v6ObGR^6AMpB7mAgzRe#Jr0AFqv&XWHtpt`y^3WD#=Ntnu+VH6;e7v{baE&*PjYcZ zB@=~H{Pl=xj%0E=4AtrkhFcYID*_AO`}#VszqX5^@kutCeg*+zT@E$|pC3H%@tC`2 zRV~LuzT~~J^}!lFQe&nOC%DF6vg6iVateDs`9*s@YG653>9U|*OCUFVjlM*^*kt`o zc0FJZQGK`*u}tHs8>A9znpE_nQLWJHj<(qSkEw}VLmy1mh8olYnt#)=wMhV9uFiL^%b%qgG6X()EGSJJoMIUWEibr3}@q1DG<9V=ehux14(fe!6A1zfqrNJ`7$+^6Hc89DVDTHpi?M0=>drbeWq8=ywk zS*y!W1yC9KoF}BIgDe{l;@Y}(!D+4Q|6YRHMIu+g*aVGQmg9+P9E)*&X~W}BUa-2p zp_!lBEfa}nW_3^Ze-72hi$|ItGm3YO+<#mO+Bq`4=;p@{Onbi@RqhxI8W-N~=-$~I z%s?l3Qh%2kt+1opxpb)`x2ca54I8{#7}NQ-K6k3FuzBnb%x+L!Z~MlbWHGNWV((qs zD&HqGf*Yg3Gs7n?;ZS8kMdf<4(i-lvb&>S|TgeosW~Cm`5eaUyysvZ6 ziyTKa!75zf3)wL~Tk7yMC_i6A|5uHCTvI2$9O`tF&K{w%YLF$a4{2tDUC<4WJzUS# zUT=w^2w6g>$~`*hz9}QOzUC?0o*Bm0;*8#$-4kc&^VvsES%B3Uctk!E9wV;E)@>5d z9U&IHrdb40J$o0}qn*r@bxXhQ5Vcw6;+<0}wiX&nQ!ltO>CnKDm~PPhd9$xiAOYD5 zi1KU<%!>TLMCmw>3>>n&c;E%@-!4M-P``H5zem#cXVr2Zt9Fj%4J6U}4WgKAK$#pMyA9g+hOpnO8oksKJgSOiX71wv3MB5OA zM5#n5pUe%OynS9x(YmJ8R^ipHPZdec1DJI~wFG+EVGruO7`UG2e|)04J8Y-0Sci6T z*cKC(i&2KPEIBWm%{$t0=MnA{zi2$G-HOw9=;o2r@ZKm_nE53t+%EMsDZ*kRfunh~ z;h3+Ot_^T zC{I&p>p!iVjd9bRq$zzNd6HTnPZ*Jh6b3Pv5L8!11K4jp^=k0T} zS}})+jxSgOpI)RJP@P+T53ZW^meOO;NfU^lz3RtR=Y2I$q3KcPn#i$(F$ujEvVjg31jp1B<*B+*==jH zxa)tM{)0h`(uDS90yYHwjX}?3K%>Q6yNGznzz3@vmVSFPy%;--`pq_I=N|Q+ zeHkpB&<#x}{U_Dh^9p`zYufc9V=f`kgM#8^=TbdOq43a$Ij_d3ud|Ws=5dou_#w6S zx0vjekCeAlqR<>7Y8=bXpLE&x3pGv|n9QzJE%kp@x|}`9+P_Y*l#xfBTvJf^;k&H> zIisD+aTJBG5vS1UgQ~2vR_>oCH#ZCU6WaV^NFqz#jmanrS(5R5b61E~s5pHDHD7O| zeSS*Js#pE}+r{4Z|BHes$QFO*gASv6BtN!I%}GA;KI@GNq%2MZlxhlee7DG!B8E-e zd68dq#^mRhz~0K|CM74%dC2mJMtk<9vwJjr*6(j&NT%S@p1@vl$Sz4Tp8rX$;?TMw zGX{*QZPHV58nowG8k{A)E%Yj>As@QZB{+X;%aMd;$GR^wOpsAhTh_WECpc$e@60JM zHOQaRXK_h{gsk)I8CjsBY!2Fe2NoE~ti&zer|CuM=L2%<%y;qSd~o3bq-<#&iy*k! zPG_U5cML`Gq`nG|TO(QS4LNC2Fy_84i0(h@=UNtV=F1AIrlenrb`ZVV96t>>ddz71 z`q~;GMr>a8F@AwfA@S%M%;xD*ZWpCj-QiMRrkEZJi56e1J|GwoF%C$s_Z@YMpLvl- zjC$*4`2oPuPIs@g;@>jFTe4vx30(qp$`E;7MmCaV3avK^6%;hj7F9zeP3Azj%6>Z8 zI=+VpY}b-YJCsVz5?_w0JbZS`;+ut3^1HuP7}&ejz@e_M z&a8s*q#F%e6tIsmg@!DV^~Z8uRVPG?%m^ggONeR2#%$=6 zYk1xI?3tG;@OIvtj3`@q`M#*bkxesbt376Hs=Cx%pz+19S zzF$_*rd_(>1SZR^4j_9&pv2CKLin}5;KIcFF{_}hPgRLb!2R@KUMVFzj~JKPR$BeI691ykDD&X2da+S$yqo2%meNml>-j8 zC*Qq(iWk zRb}YmQId2KSwFPehYEeH{e~Kd*Uu$2YE)~~YnIgq&=&1#8P+;_N4gZFb~xUusA!*x zI!+(-KHrM&O)+%8QE5KzabWuy;=Z>};yLeeL> zUWeM}0Qcs}VY8fL?<4gB;k;nW?Ci6*$G<=&qVp?f3!lX;CJUgLIF`r4vE173X;Yoy z_3VsxtK{kQM0i4yn^L~*_rns;WKR`4uj8oD-SKOT_zXC$XPEzr;Ku->suBz`YbuAcg*dl0EEpT^7N~S+W!{LV2Me4t?*#b4pnJv1OK=OrmcVKhJCCoHsnFrJ|wC&)1bn#tuw@^*) zoy=%CfQWEV34T>#`u=Q@v-inJ{^N=bPb#oxwhz*YuvIEwP|7?kS?d@T1wX%*qgfzL z$!c@AiM>2x=VeG0eYdr^zs}?B>Q?|z>97{(tpkcm;T7lqLp1@T=6?cIoXIay$Uoom zjT1-B#Cy;$!UuBC>1nq=dJOk^{ZV4SLT2ie2Fl)cVKakKIv?IlP9d5A=K z9Q~iSdEkOEt7-W>!hg9a;-evfp^WPNI>r}BBrC+L-O*d1Zp)D<>>3SZC0jrni9W31 zi3r*S$9gWJ`ihs zTh(H$79gTrj)R-@nn;J(kHuwg;V9P~b#*6QF$I7XR|2Z^FAT`)|HM=CI8@qMpUK4X z)CloH1X;4af>DK%hQT)A1eq&DyI@UDg!5mVbk`gdlJX!`n9jz&b$*Fd?FmNLt^nGe zL}$mZ*?@+n%Jl6o>>>u!5J{m03KRwaCctzSbSS{_10SGp20d5%%e?`Z1^ss7f+5%B z$iYH@QPhzAb@ol~VBpdUpQUT6C$#lJAzo0fuh(BRZ%Q{@RO7udyBa}>|Zs%d28?g;;n7%n7o)E2fQ`htAFv<)=wPz-Vj2@AG5oLMbY*c z>=>6~U&Ed8M+U8$F5k|7;ZAO%vK?hCCv-oUz}|9d_IW$f$vPQzj&U2mkgRwd`Wy?{&H$c}@kG?i z^K=LSuoLbY7mY|k+hB}YcWe6pg+@ReInFKs)kmjgp>Y_anfs=ZgURWso?{z3&*5l- z{oOPMh`%#tgQ=9kKZSDESL=u%wFXA!Sh)-Yp!_u6o$BSq;Q$jFo!rkGhFiz19Gb=;B#@oic9~Z47!7_-5nE4ME@_l zMZ_`yP))<;b7}D9ldq>-Y-f?zt!3$Ad>J#|$YyI-Tf*2Tx;lF#J1^!mjrZCNlF;}Vo zmAQg9n^)+-yYep^h`JP`4T&!qpREpD@b&*CrtO6h=n9~p-?$1|M=S4h&`?UGwqI)b zg+06!2YD4aJ4*nZX-}?h2!?zT9@n38P>%H+U(bhY2#~er*na8Fp;I7RRdcq0`d)1|J3fB(CV?j0nWLK=_^Ja^53_#oi*ii= z5BA>qvv_g zxu5UP`}6%r%r)1TbB;Ok8e`p$1d+2mSapW~wqEoA{DanoXe|?Rzvn@xlVX#@5>Wx% z7@{TQ@^Uf!R(pT>=otv?wdLxvDGnU4Hm4?PI{5^Sxe4TNO!}y)>&k3&@F7qnos-_q zqTP)$UZ>yZa3#$y$*@itCAbWLtN%XTy%)fmWa(^umH*e`R#*C&nFr({&jXa*Swtmo zoz=7}EP`uY@yLQh6ks6&cUD_>-+(7u#Vbk3fx=q3zV_c+k_qJ5hnt~Wfc>b*LT0mn zL+eZBaI?=!z{j=Tcm)DCR+?oKz7WYT9!%3r1bVxaJH7b*op-3m`sG&|6^dvuPTOu0 zA5EzgUyaI-8!C+$;3Zo7(IwjJxupX@!?xuqpA4Y4e5-ml-v$8WRAfv;`gH|_V*$oz zgE?w5*MfI3l5>9Lb?;+!X4u%%Fsu#$q+Qe-__=^D`HdyB+tUD|kL#Tg`R_fn1Grbd z&4dvJn$@aU^h<-|i@UIL&wQil6iSJo?`+zkDCtw?XDd0M@;!ABxYCF z+7=U{tels@F4!@~>t0LyoO3CnQC0X^R7AH zGcfu4L#J=H0lKG{f2?H$e+wYlzvDCkI`r%W--Gz`+PathMDsVo)cLRiyR9stp^#h_ z^aI7?M{WMX0^E?&cC^G|mab@-90@T`(1sO0s7#+k0DG2<_YgaKVz+G%FmEfoA1M@o zUMat}>Ecm2} z+sG-o?su~v>-WF6q%7M0Y2APRSF>#@jHt;ue*f!YtL+FcbYS<{u8|yM-o6Ajz4q<= zCob{yPf5ySLmyiyf8205e!tqhKBL)NU4o53U!ST4q&Vo`C2aSZ-0iZ#PqU%x^XyqY zeF*-(S9I_-TJq)lDr%>_*OW0CavgU$g~Tr3ik3kScizs1>$9&)SEK{O&f;t za`|IuiI;Ix&lgY*eLn>=wKlEi6qtskwu)`vYC<2>C0?GiQJ*gqfNs7eqnRSYg6Hp^ zB9Tt(LT9)?jYyKcO<|_K?nI1u_$fE31#`eFb2*zb_2z0Gk55s%^tx(efKsHy(bGR0 zL>Bx=;usDi0&Z9y@;wOvxAeEpIYlWZ|C)h((!xk&!5{Gd@zDlt{-nH`iXQ~ zZodl&aN237tvTuvT976_~=Cm+%_{OpX=;s=UVcn@V z8c=GIQO}+x2X)U+<57Q@sRUVmE^W{}6OsVamGE!yVt1#N7ixj^o`H}f-W$@!>)uHF z91cfT!+riL*9mjT{9u80a%Rw?!I<0+NmTftOxAx1#ycwjB;D z=86Hr?smcfTctGl=`jFbDoO)75T8BVwPT+H7k|M*M($7e)vvfHq8T96QUw?5mUiU? zyxlo9iNL$N0{T68zGli-xQoH`OI;dg6aD(W)b~>^&Y~_JDgMxL)>K@#;AsU-Le)khsYhbOM5;pQzHODu%d?}gzOKGB|(C6I+Xtt zXxok(0EDxhqc&75op)Xhb?=S$7fC=>dy1GOio>rvaXX5(u(w!x!q z3V}B{&#LSCWL<8@(QLVv#E~U^l7LT&$o7p(xW9XEwcpUhg={v`?DVNt`$k6(gy(K! zgR4!1^L;+|);6bslSbfGl06*I4HyrwUK*6oEy&&jEw7i-IDX0OH)xNa0=LD_XHGA< zlw5CzkuYIFa^n#fEk?&Joy#@(X=onqq6zL@zRMBMX{pIiTg?uWD|v7@jp^(cz@y>F zc?!;+^6WZt8TweM6D>YmhuNhmXr=K?(vC-cy{1jUii+PO8kW~Rxqn7V)@LmTrwzrX zr5yHxSEbOL2>Jc%3hGuf9iCeOushb~&3r&X%hBxY_cSapuRhfE?`jr@#~Km8q)}v0 zIibQAPreM>p5&^)c-4{DU7$D_?Uj=wL=9JJ($AobGx;9IS|rfMsfFCP1=Dg{KA(dr z(9ZNb$?pE2eJTF5ESM00wbG8*H9V7_0)e&LZRC}c1lR?8`gmQXl*{)Ia$aDc8*7kd zO)`BQ=XM`a%Kfwdly*fl0=H{+B(As|Fm%5i{Pt;qin0J3*a%ZMrUcd$9~dTV0Ez|Q zbGWo11EiU$>>48o(n$sL!}7F>QD|eiJVgB`)FX&&?<&|!8(|}=;|!S|1Wh{@0S z=Rdk7cehquvwz=VtIOz83v42Lo;M^Z>+?l4LeQ_G+yAE0r^(G&fX?HL-dR}==*89L zfn-x#BYxPVwZ7t$${sjK4>R#T7{xx9jA&&AW67BQ0} zL3Ky#+@4pUR`)?k5tEV?Wh6HNNt3FWgdG$r&c&7H+TbJamD?I34Bap0R@G0NSY*zw z4Q;kk9;-h*H9=#j02Ghyof+kC6QcpJkmL<-zuNsQG#Fpr(BK3>3{N0p*U~$~MDp#p z4O^BG%!aA#^X;Qc!*qjQ5Sg=%0MN}jRlKFzqXb5cOseF%uwau$wOLT?bZ?~tAxB-o zZ_r~zz1PVKCn;pAYQQdzP07FS{Sp2j*RCJ@F2tK+^Y#+u^jXp{!mZrTlSH zAPrn=qu$DL5~>S5<<`K7%mp9Kt2-t%kb9O6!ydpn4^e&Ct$;yvra$1!X&l&{`Em;D z_m!Ok{EDfn9U||i1Dt3F`UyaF#j`v}u)VJYbjE|6$&^9L4pmShLU~);6*bbQrN-Os z>Jn+~Qt6kz5j5|wxt`fh$88Ms)gyew1gbb&g;SAsqm)~0p4g<&zva)vkzuD`LRil1iNWWA|quZqg zk-xEc zECqY2>jzZ0#jnP1OFLvRz2f^En!D8$q1r2V7P5vDxAT4QRWqSqFD2;ayK{tqckvfe zl6|-J&?ola-ffQU+D_;c1qL?#tK1k2Txbj1?Q$fJxX4i~6gAK)C&}-s-#N;YW1i2AiS4 zGqP_;D=7RdT$Jv^Os6d{PY|aifiNYf>|$aBAk=8A_-Mvz?t92fG*4cS0^=s5WwXBa>{@|OzjXENJkk^;KH^cP@^Di zH(&IkCd_j%1^VLFfTrL+`qJr8o-jZjU{ z+_!9z3(bwmPbEDjPoeknDY-@&5;gIF!kN>aSAlDj{vQ`8c zf*yL1MqO3U&ZnrhlLlb?F)6*828Wr5fQP;)zf%B;&Atiz4;c=45~yqUp7L{)2D5p> zOiQeKb)g;$iO>ZPk^Oi6;)UFUZ(k|lrplN<;xEdZTkSH*C+0RSPJR>c$Jz|8Y6xkv zzcMlSehqtv^EGo1eK!DC<86=m@M8FtD(JL5I29Ti9oRwMH;0dn9DP+Ri+&fU8nprO z1mB`1UHHvuPPoYyiSbs+*8{H5ISg?FmsjQ|qacv@98Mh6cJdn>@cAF9>$jSwrY{Zw zf#~9XhyU1c;SZj869laFeBE9JA0nm;|IT{|a@Dm-77&-xXEvA+E^k1X2uE#Wey+5c z_WCn$ur<_jO~8i2{Pj4%SN|~^DfA-y0-@rU2ocQdibTG&zEobALy^v)RCFJWbvx8W zgY6Cuah~hjEaqUj0$8n8hai#lq-zl`h*aP)y8303R}d&X?2~JM^84a1KDLmW9b|cI zozwmmAal90DiLT3_D5OFUreP3L@;)CPgp!2NB!^D&8~uY;}PTC{P@VhH7^ezP#OF4 zJJGKIc>4_SmP#hFphug6|Njvd@&9)0KkWGbct7&46->hk? zv6T)0^7-Ai)-j>KpT_K`3K#@hjiJTl{YL;@YOOA|)IsieKST0VUkxxv&0cB+mVRYa z{%lvvzF>ob1S?kbI#9eRdVRCo_fdTORJgu}gpZ15paJ zq`M}oO(Fhdia@}6?|ZdY)pNzk-@Abopllp}%lW#Q;odLZmTjFV;`7T%%wJRd(zOts zH(eSkM2=ws&>aqW-IVXn*=Z<&D(n9?n4F~+%l%vNqZ2b5^740i>|df<3Rr-Ejux3b zkQ_mfcoq3KA^gV>h!7yZu2fMZ6S9FxybKn9=ZSt<0MkH0ceSDq|9&o@LxL_{{yF6S z(GE2nX<;=D6t7ZC@ci@jzn1mp4LzU<^k34zYli@l_A0o4S@~o83}Tb1VZtp|lTwENouV;pzP+8I=Iy-o^Y2{wKiNIU)Q95l^6bfC0l4ocdn?Jz+^t8}pA1 zdjib?mTd!E0RqY|FL1Kk2rtLw=p6$wQ25uX_UWn>!U|$*YbTt5a`zN7&Q6E z3~z*iDY}!y7a_&K-h1lchlc##s6T&N1_(WSrGNK=9w=Q^OXK;SnfY^7!pnfB-%_B% zmre&Jc^&BZyT{M3$&~Sc_cZSOc%KLi`5%dhzlK}_^>GvK)EC3SK;&3emgaAf_&zjdtmYgxkRz;QT-D`@^@KK=ENzup1ldPW|frLY5RBNyvU{NMSI zzlPKUDUFF_bJKB3QAoe_FTVSIfZvn>+bC?_#r&~ExFVDNcMrs0gQtNAX+P2WV|Ro) zjabLOd7ytv#V1FG4*Z@duA#?bpC0MM%8||FU2Dz-8IX z@2mMEJEpYqk0F1yPFQ5arjb6Ar%OZp2O;&3*&;M`lW27yx^QCfdn$3|EG~1u=scwvl+|#*hUihzHdMM4W#iGe-#3^NnWNK&GWGf zP;rQ{);~8?0pJ_^*%^!)01zOSoc8yk(?14(1KO0DEGGp96h1naG^r$YeH)YkZ%7y? z{+>4cV{AR}MZF1I9Mk5<=6kU#A>q?0935_xU|!w80IgW>2mkYB82OJMU2|y+`>;$;T&JC3Ck@ikf1CRa5`#E27~gMCccl}FHdFcJyuU#j znhF71EBtZweX2k5s7iQoet4irPTo+&@WXKok8GfO(EZP2`bDWA67jw)!)MMWtSGGY zISg7TT+nuWD+;#pIvD>IBQX~Y-B0dma)CI$)pvh8<-Z;W&OhJ^{9;R1ug;DHwJX)_ zwHwvw_Q0{uCZ-WZHL;a>tYaa5bUGVm#4MGC#VV>G?8qLoGbd^9e_0tk^t(pTmza8d z#na6eDS(t8w)Sj#^R7ic!t7t+fU0=EWH(F?vBkALp!cTplHyiLg@>-Zqnkj>t zNR4iBYz!LQ3QsuuKO*QD*H(4{TbrXS!l$BO(>C6JQxf$49?7qC`lTgB;?=yDd13q!U zN@(WIEvqviBoWbYv9hpM3gPo=A@?pthpXX8MevChhmvGlPnD6In99!0S$aVEl~B)AO`%rU<>@^t(M(%6FUx(%uEED#NgF ztRUik%ji!9bXUkOp-j9mL}@RCcM6S)xN>ESTjY*?Gz&%OKSryK1%-$DI-fl|{&$W= z$Fxk)%+uD6fqzXcPd93r+MZAT0)Jo3zaAy)9v3w%E6QXn>UE-2CghnMp@}3)LUnTP z=s+W3*z>;J{zgo+yY|pMi`6Th%RGRuJDslMYrR*msn)lR<8^`~&2o7^BgYJ2V{s71 zj~)at63ycXAXCZO+}>)+-c(;rR8gVs=n^5xI#Cm*WCjzqU`d)0c$s0hbeJXUU9gsn z+ChEuDc&MKX2~RxRR4qwvlK_Qb)+LdhIf?w58c=fi%Sqtq84L5D5F)G$sp_L_#&W3 znjGicsO|#Q4PlJ@3BdsObCK0>5Xe-L4Y(qKmEu^zt(O_WUF6{ESiLZnd*(HjY2kdT zJ8yiib&GUDk)gKqy}z&knDC-%^14m&HQsA*xSiv8hmvA)`MiyU5&bGGv=I=nVB^P# zr7c!e>zNG;N+|AXjb5XRnqvWkRtsb96cO(koy&>Md5Ky~iG`DIKydVxH(uys^C={_ zI|&n%h`;X*UsQv`&V}$x)&E@y$YO^}t7{cxhH04lzAN6kN^Li$_%P6f@by*-eP}^y zJzpvqB2Cv`TkWd#_1q0L)&@7W>q{^!PN)W&YI$f`H@QqCCOQdM$onbk?t$uKGm<7M zdX_?(Vtp0KaCmP_4oMhJrGnr&5z(L;6d1+2)G-YHc}4Uh(jKFgnipA&4B*i%2Tz87 zA?+#ngQNgPc@kSHCN{STqShKBr-#3&#fPi#hT<+yDuAr{Bz~MBBIb_O{vx`m1Tkqu zX;kooF}~x+X4RLwBII`#s=JafPjAy0#>b|R|Qm}p0nwEde-cUH=kwCkbU z#TkFszsTH6|02K>JWgcOw{ggrxvmGb6=fi;mi%m%p#i=!g;!i06_A8;`Gys6cRWcY zpT(YCUqT=y!K5awa8&)GC=4Y52n2p2FByVSoydzm5u%pO^p_xAQqM;1Kosf`#|gOM z;wltXsu#(6m-k=67Pu02y%1-bLYpa(99zDL7v@q^$BEI};*)lBEd)EM2(TC6Hj;)N zeLa@?Ob%}c&9<`LvB&ZcIrCh`D8`FPgU&+3Nj5tQi2)xvu7Rt7cK)-`d5sp=N8u20 zq{#P?gkjU>o{ixd6yOe?W3E$SNxV~aG56{qxW8kIO^>#n~^t6;$`u)&VD!v^=-(Mgj7L< zuP^6q?k)Sa=xH5S>JIUT33M2A(fw15X$@i`F-p_gLZgPKxk5Ei+CnheGvoCqW()#r zCi`E{ENuKQXn;!t0`g~g4Al+Za6H=PCH4;-X@G)5NwK1zt05MG4b zXM*W*9LR9no|QB!|GICCS}7Fc9RvSMv4Blj#Lh2GPh}WvG%4qI6mS=;N=Qvo#oXPn zZ^tEGq<1QOA1)uQC+5q(r#A3538$}Yg|6k8slU^_SeweZ2PGK^QN~}<`N$nuPVP>G z_q}bOjuF#Rx$9MH#D00xAagquI2!aXM)m3kkP*d?59`}K+o_M%8C0`TaQE)cORX=r zl7-HE7Oobpg`M_Vkui#y(9caL$Mq=$Fx+0+fLj$u@}q;(M#Ui^DdhLX3y6<;-1uf+yFk4dN zXW~RzXJHj)iu9hFT3|-o1QR$EBscJ(WT*W@sl5<-wCl)5n+Mk@+C@#w>zVN>sw>X< zrvZtRw3!x6blLC4)K7x9qmB=J?HSKLbTfH*CY6w!mh!pEmuBJOeh_72G*udNHE3#aG-NW)w~XFZUcqS(sQ4UfLWTbiLb~-5`3n z)6aBVhO7S>#g!*Z6o^;!D$r7LE+Jzo-g=FQBwUIpw_ml1SAR@`w*`g8p4JRPl@#=k zB>;Mv4e)#LPQU+Hj=};Q7yP_Q1H%`h>yx2#-&46t<0?3n|HSrko zzDy8b_{cbfzwasQ!pf-*!vro|#0;1~EO284{~TXH2j4SVM;j^SQ1d>IlNeTmq>&Tj zVk1SZQCA=Tl+x^&fp=V?f%}eRJz>j`s4o6qAK#^v6fADV<|-^`r;l3IumxBkUo0HD zX6AC4aQk?+v!jyj>!HUM8!@cTwWGE8A{h;5_efC-lix{qryK1|tC?G|%wqVQp5fvAJa>&FZZ7F^!b}aN zoplJg=gVQc(k7Eb-`)}pn`6;vh{%z`$-GMsMA&*p1A3kiJY&mZv%uyGgOx7x5_~Br z;~y3Zk7U1N*;&3^x>2prbiCd&%O@O(0s=E0}? z$%FM$98E$fm4RA?d6O;O`ZyV}AC{I`AKp_5+VJXr%H#ETPi_v-GK`wQH6c(( zD8$dYX=na@dnbnfImYE+&SCc)vp4o`{!5>2zLk!Y*6SlnzN@AFv+v=IcC;bgE5 zK4h=E$u+BjOlCDh^#ZhapQ0IhUClp#{pU<>~DUW+oEw6BnsaA?>;^qW6;mmP zgwKj;s`Y=2UfW0P+>Z(mVJo-iDMsi>{1?BcJ^C?jL!}4D?6=XU*azqD%)q%=N3?R+ zPWGIQZj99$8n77&^HPlfRhLfK=SV^(`n*o=sVI1Rv9ezlb;mXFP)!xAEYOuFZ{Tw( zc`i)gL&y=||0xjPRgiSJXceDv+&V6xu$Np$w$)TN03tQSD*KVWG}|O^?{!guD}iDC zArAMEI=$oOn0;q5$&kaCf>4jTrhZ7y%023#FZifF^SPo{8*K5z>AU*1@`u>%Hg6av z?js)teYdBFev~Kz3&GKbAvzZlh47@@X52H;F7LpsvM8cFG~$9{>Unn$okc}u5b6`z zw|L+7_lXEXiR;5tk-!BJGC#4U-ZR|B$ApKrY%;bN02=05+LL4|#2GMj4Nd?k%*0*H zt&tqA;d_%%ax=7H|KE*+V<+l%(e-^L12%P^AFbAy3WS$c7fTT_82f2XlN(HWq2+BZ zOm0Fz0~^0%jN>Jv^C&WQv93w*{dwqIWc0{U^O54&L3UGbg{hqXR%N;J%FB?P+o=|h zGfmy+eo8fI5Woi?=X>D>?0Te)(MUo$7A({4i|wtY{Y@^|!0B9Lz|* z^Fc($W}m5EO9qSCHIiE_h1*gnmy4^uRc3*b2`Eg3JS9megklc^QxVMMN(<|&^v(Jb zQ1Hrr!jmDn+x}Kt()I({0<%`(?($;HAV!J*;tA>9HP^)5aX~#$4*g?z3+q{g>4|A^ z_{LoA>P(IwjNM%ahQnN&pL%orbZu5d<7}d_H)$@j7`rPoYpDfI%;$Gj_17w?uNWyC zwbN)#U2wg+1{ywNK0{T=VZCc$o(ZczB~F@fF5iLoU@0CaGdFMH6+Aq6>@?4YId0rj?Cl}r zeR+F!vEm1E_q!PgwDvh}s_M8tzoqhEX(i2OcU@q0#i>-X+94aH9Bd%}hrKdVmEpXsdy7Zy=}U;J?CIUO!Ghx&~N!5qHFXAFE< z(U%HmMeyUw?$AkxfSgo#z>UU~!qgA*C@jhiPjBnH-CkCiOtm;Uf8R0bW*a2xj$E6S{{H8OFgiLnsUOL1t)|mSB2U$i5W3LnNdBk z^+R@0XeX)%5kxf+>m!$tsOw0MCm|(f5?WXG$rjF1n{n?R&R@ppU$ZKZ*uKGy^hWJ6 zQd2d@s`Y8*Px#ZgXzv7!q4oW-^^GRp zrUvcXn#t<(_nbaE-ruBQTTho)HhRJ?aP$I0x@D(?oVImeh*DD6mGDB145%7;2W@m; zNNVIu*+27u?nGq0e*TU}4n`@DlQ1jEix004FPP$^2D+FVy46n|{Mx=;Mb2?+3}GTa zbXP2r_~+O&awPnhi4U;sXnV_-Tn-oFtJ{l#x{aIXaU1^LUdO9-N1g38H{B!gJM}^v z#=alny7alIw$CDdoEdYzBa6B=8%jEbGbZ1~oAeNyS-lnny?UBnrVfiV!k2j{-s#y& zdHMqkcv12SZ~W%6wzxVf1Wi>Cu7j%H-m(w-3Wc(+4Z*+SWrNPsf#dRHXx^Q|Odq+j zhlFf?a3F}jMK(3$pLp{matY4-5~-xLnBpc)Vp{_>2_M=NDe!XQI7TB_5(fI6)!9%9 z%)@5#B89Y~(9>cB6+67A-ppWk`~=JzZN1DdQ+=9$sRQoYN7HnP0MZ4?40W6BDIRS> zYWT!<5G)sGEB>(jR1*FOG=<*V#3*H=wb;dOo^O$((&r1=e3dKkjExGXjT0+RO|3Bp3%tpy)OpsLJD=?K z!yA1fP|%J#CZ}!fiyrP^Sn}ZOO13dIzIGluNVHw0aYs=3O(^tafX*0Og%xKg#IFYqP30$yg zR-TP8w|;{3W&EjsU5dp=VdfUSa_4UtgKgLZjd5#3Wp}@5XuvD^<3~R_aP+OpJ4YSY;8)tY+mKh0-(93m|W- zb`#+w2_=|B`&_YD2CLplQ?4t&dXip?LlY{3WWOoy)Kkw3quzyYb->^V z%i=8~%)h)9&=h-V?(^NXO?*xcft9gO(&y(7cZvk=X|Tke0uI^z+B@#+XpP&|YoRDo zsHLOm0_PKAiGhgzVDHi+(x5d!6WMV%T)bcLX?PinA_H=PcX$(s5J8-CDAtGW@IQ4QvzIh z8bNxex*;B+rHhBo*W)j3Mev;Ln4>EVMRCz@PgKxsZ@%~IIi^Mr(*&L=KHtom&mS_w8RPc*{9E_jRV(!?A(u_|f&p_rJFq3! zW~UQ|{;gPTm-}YhrW6#8WlV#c$Ps&E2kkd)&MaoTihLBG1wWHUPl>Qs(X~~al!&k3zf9o1Q_bb{2*1>tNrk9;&Gyq5lXzVi-&yU#51zENOC-K zAS_pPY!lr)a+c9^J#jSb-IA#BpL%E4j^b}K(HLE@e$*VZWeMAHQBIV}*oiq&EmR6T zr;&Bwcft$}PmlOPEwB=jD3iDJUL-lbJyc*N=Tp`b!o_~VZj*`)QObiM6rb*BpVO3T z%+6Ps;d9cKGaR^imrUWnqsm4o#dk4+zS zM_9uipR&`{L%{a$JUT9X0F{U|xOg$l-=Xx$^(r&x+JvEPhq60-X$KS51B_ zg~X@9_DJlE@sEwTDR+0J>lPTdE>xk7k`50_T=|uZ0egP8UTfgygCUHKY%-DssyBM! zIc*nKYBN$SFAMoE)e2-_K4#*rl!%?wKDcFHIYonl^Eq5#q%=XyKzCRi3jK@NK+Cr+w6>z-3ITBX^N3HNfvDUB+ z7#N2O@`~du1}+E1M~%p6Bp!sI`HTrlrkjkW9hLh&{MO;3bmOpX)P?Y8#ddzrfmkOAX8zI#>%2L4^4B!- z6vvw@?ET3*H)p*PB)N3vyvVW#MJzmgFiWlzHejr|{Q2b@KCJ>Yjo$NBu`n|IILr6i z!_uNO4R8Z8JS@G35shl}k;nz8SS)RZ2%j_H`UN)7I#J=tWon!g0Bag(3ASP225W}q zc1P`CJ({lZq+nXx`9bLoAsH% zvRyN(-#QNByH3N@kxf!dyRt0H!+U$_0z>@KpI-bzzhWE8gpW*8nDnjA@0@|c=Watr zh=B(xf3rl#=cFzLMOo?gkoxvWXi)(DV9BuU39Z4$k46VwiH3E^N4IgBNOkMmOt%Ek zZI>3CrlWh9>m(dzSMzj2=_hY`RWiQ!?9Hyc5M)MrW9Ye`)tPsB;J)(m15pju%rTi# zi|+7UGS^aMOU5;dAmY1OX&YV5xVV+`S1e;(l?^0cv-b&eob#I!L%JvZwsAMqPieq| z34v2_*9?;lOCJ_RD1fh|ly7{?{j^v!cZ<*%MoOx*F*3-~{L~P`{*zl`-T-1CX6#=3 zkthC)liTcwSg)JNC?5!WV1`TGIXR+f#NBRGR8=OI4KivzZZey@mix&hY%)NfXem3S z%mYnrbG&i@OrG#zK4n)#boK-5@|%SnR-5^ot_h}*#FcRV0dagj_z7t-1D`^d*E8IO zepCl3;^I~x^PeT1_*gD=r}5XTkxg9Kq86U#(JVrF;>IWg)+3T<>-nZ4xpKL&tIr1` z{gwbqDcdzB#EG}5gDoIttP7|A&Nks+olB?caX zLCRkylPLI6V5_M@V7ViUA3l&M-JVn1`1%Mrm43HoS9%I6B_-!M?KPR9W7Gn~!P3{T zqNfLf*HK)^Tx2{~_by#*)FcP;!QJ{}KUak+Z+{kg3Y``MZn;4Sng6;0|M?!HY9$Yz z==y7W9mdS0ZzqML(+GY3c7AP_SSS(H0`O5M`JULI48Z=78%f(BNu#U%a*8o;-?zg- zt0bj+@I_i1VKR$%#C(T6y$>o8vU5YxOdf3iv+Kwkx>n~IY>O;x!=`%qinOC5v_sT&b=?eYa6uN{$o%)_Qo<4Bb0==F@tna<%WLxa(cjR-03jnu_Ktn5qQI7gg?4Sc z)IUbrB0Jsz7akHw40cD1Y{A<-R#pD^wMa?eE%;KBsQDWmBISXo9%?CZA8JO*4$Ot{ zUK3H$x!wc>Twhxixa2S}T=b4GRBbB$d}Aub|308GbbT2-aYYE8d`{D;99Uu^s33j# zWcZHDQRj46F|g{@p8%>C%&@Vr6!F6GR6mmK^||ibsEsjAEPK1VuO`;3xY8X7-@9IZ#ycE;)g2@v?%HCh z<3zVDQJ$zpGa38yJ0G!XiW#z1=lTNYTPdQKc-BE1@ABxdxR()$nW-zQ`7v~}u)rG} zh_^m5@UlEbpSG%{i*Lx^-6Qib+`@kwP=|o}x~5GX_%&VLGEEzW2qH9Q!!V;pE)UI< zK6BOGSI{VWs?6BeDQ!hmCJF)#VKV_3pG}!>)~W4g&`Gg`-5On7Ur#BM?L`!X5WbKY zsbM1q>yYe+&UKda`ZX7dzZ5op(o;0rshb;Z10o3>LP2W%0?+Ko-<&>$H8}Oa^=*}7 zdRle-<{t4XHvxC^fLbV0rLp338=WWfw^r~EwU6gw=b^c;41&L@YaNWqKtMq%B^Iup1E34CxBR^14hF&9B@@xf3MnzjVHBWO2%j$&13HGG%0DoaYE4}7ipS7s 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 literal 0 HcmV?d00001 diff --git a/docs/lunaix-boot.md b/docs/lunaix-boot.md new file mode 100644 index 0000000..9477ab2 --- /dev/null +++ b/docs/lunaix-boot.md @@ -0,0 +1,117 @@ +# Lunaix内核初始化流程 + +虽然不同的架构对启动与初始化有着不同的要求,但这些区分只是针对指令集架构或其微架构的特性。对于架构中性的内核而言,其初始化流程是保持不变的。 + +下图展示了从内核被引导,及至第一个用户进程的创建,这之间的流程。 + +![boot](img/boot_sequence.png) + +## 图例与解释 + +### 1. 架构相关 ++ **ISA Config** + 对CPU状态进行而外的配置,如进行模式切换,开启或关闭CPU特性。该部分代码为架构相关,如果某些架构没有这个需求,那么该步骤将只是一个单纯的占位符,以满足Lunaix的规范。 ++ **collect boot info** + 收集引导信息,以及平台信息。比如内存地图,内核启动参数的存放地址。 ++ **high-mem** + 配置内核的虚拟地址资源,包括:按照[虚拟内存地址映射图](./lunaix-mem-map.md)进行页表的构建,以及将内核重映射到高内存区。 ++ **exception model** + 初始化CPU的异常模型(或者也叫中断模型),包括:安装异常处理函数,配置CPU使得其按照Lunaix预定的方式进行异常的接受和分派。 ++ **hw_irq manager** + 初始化硬件中断资源管理器。该组件用于管理外部中断号的分配,比如:当一个驱动程序想获得一个中断号以便配置与相应该设备的中断。 ++ **install syscall** + 安装系统调用接口 + +### 2. 架构中性 + ++ **pmm_init** + 初始化物理管理器,主要包括:内存页池创建,分配器初始化。 ++ **vmm_init** + 初始化虚拟内存管理器(该步骤目前没有任何行为) ++ **kmem_init** + 初始化内核内存管理器,主要包括:蛋糕分配器初始化(Lunaix内核的动态内存分配器,是slab分配器的一个变体) ++ **cmdline parse** + 解析内核启动参数,方便其他组件随时取阅。 ++ **trace_init** + 初始化内核跟踪器,用于在内核出现无法恢复的致命错误时,打印出详尽的调试信息,并跟踪堆栈。 ++ **dev driver** + 扫描并注册所有的编译进内核的设备驱动程序 ++ **device: (stage)** + 加载驱动程序。注意,该步骤是参数化的。驱动程序可能对加载的时序有要求,不同的驱动需要在内核启动过程中不同的时刻加载。该时刻由参数`stage`定义,每一次调用该步骤只会加载注册在指定时刻下的驱动程序。 + 这里需要明确指出一点:该步骤只是负责运行驱动程序的安装函数。如果一个驱动程序在该平台上找不到兼容的设备进行认领,则可以选择忽略本次运行。安装函数可以在内核进入正常的工作序列后,由用户通过`twifs`暴露的接口进行手动执行。 + Lunaix目前规定了以下几个时刻 + + **sysconf** + 序章时刻。驱动程序所能使用的内核组件只有最基本的内存管理服务,以及中断资源管理服务。其他组件/服务处于未定义阶段。像ACPI,芯片组这类的驱动程序一般会在这个时候进行加载。 + + **onboot** + 正文时刻。在这个时刻,基本上所有的内核服务都可以使用,比如:时钟/计时器服务,虚拟文件系统,调度服务,块儿设备IO。大部分驱动程序将会在此时加载。 + + **postboot** + 终章时刻。在这个时刻与正文时刻几乎一样。但不同于正文时——其运行在同步模式,该时刻的驱动运行在抢占模式,也就是内核线程已经开始进入正常的调度序列。 ++ **init_fn: (stage)** + 加载内核组件初始化程序。注意,该步骤是参数化的。初始化程序可能对加载的时序有要求,不同的初始化程序需要在内核启动过程中不同的时刻加载。该时刻由参数`stage`定义,每一次调用该步骤只会加载注册在指定时刻下的初始化程序。Lunaix目前规定了以下几个时刻 + + **earlyboot** + 序章时刻。在驱动程序的序章时刻后进行。 + + **onboot** + 正文时刻。在驱动程序的正文时刻后进行。 + + **postboot** + 终章时刻。在驱动程序的终章时刻后进行。 ++ **timer/clock init** + 计数器与系统时钟的初始化。前者负责控制整个系统的时序,提供精确到毫秒或以下(取决于架构)的计时服务;后者构建与前者与RTC设备之上,提供基本的时间播报与统计服务(如当前的时间戳,系统运行时间,等等) ++ **vfs init** + 虚拟文件系统初始化,同时加载文件系统驱动。 ++ **pseudo fs load** + 伪文件系统加载(如`twifs`,`devfs`) ++ **kernel daemon** + 内核守护线程,用于重复执行一些具有维护性的例程(如LRU检查,僵尸线程自动回收) ++ **lock pmem** + 冻结物理内存。用于“锁住”包含启动时所需信息的物理页,这类页面一般由固件提供,以便OS能够更好的进行适配。如ACPI的系统描述表们。该类页面将在 **unlock pmem** 时分释放。 ++ **unlock pmem** + 解冻物理内存。 + +### 自加载符号 + +上文提到的“安装函数”,如 init_fn,均属于Lunaix的自加载函数。这类函数通常由内核在初始化过程中自动调用。当然,可以被自加载的东西并不局限于函数,而是可以为任意符号,比如一个全局变量。Lunaix的自加载框架只负责统筹所有导出的符号,具体的释义和用法由具体使用此框架的子系统决定,而不同的释义与用法也就构成了不同的自加载类型。 + +目前定义的自加载类型由如下几大类: + ++ **devdefs** 设备驱动声明(`device`阶段) ++ **lunainit** 通用初始化函数(`init_fn`阶段) ++ **twiplugin** (twifs) 内核态映射构建函数 ++ **fs** 文件系统驱动声明 + +#### 所有自加载符号 + +| 类型 | 符号 | 源文件 | +| --- | --- | --- | +| fs | `iso9660` | kernel/fs/iso9660/mount.c | +| devdefs | `randdev` | arch/x86/hal/rngx86.c | +| fs | `ext2fs` | kernel/fs/ext2/mount.c | +| twiplugin | `sys_clock` | kernel/time/clock.c | +| devdefs | `lxconsole` | hal/char/lxconsole.c | +| devdefs | `zerodev` | hal/char/devzero.c | +| lunainit | `lxconsole_init` | hal/char/lxconsole.c | +| devdefs | `vga_pci` | hal/gfxa/vga/vga_pci.c | +| twiplugin | `fstab` | kernel/fs/fsm.c | +| devdefs | `ahci` | hal/ahci/ahci_pci.c | +| twiplugin | `rtc_fsexport` | hal/rtc/rtc_device.c | +| lunainit | `vga_rawtty_init` | hal/gfxa/vga/vga_rawtty.c | +| twiplugin | `cake_alloc` | kernel/mm/cake_export.c | +| twiplugin | `pci_devs` | hal/bus/pci.c | +| lunainit | `init_serial_dev` | hal/char/serial.c | +| twiplugin | `vfs_general` | kernel/fs/fs_export.c | +| devdefs | `uart16550_pci` | hal/char/uart/16x50_pci.c | +| twiplugin | `devdb` | kernel/device/devdb.c | +| fs | `twifs` | kernel/fs/twifs/twifs.c | +| lunainit | `setup_default_tty` | hal/term/console.c | +| twiplugin | `__lru_twimap` | kernel/lrud.c | +| devdefs | `uart16550_pmio` | hal/char/uart/16x50_isa.c | +| fs | `ramfs` | kernel/fs/ramfs/ramfs.c | +| lunainit | `__intc_init` | arch/x86/exceptions/isrm.c | +| devdefs | `acpi` | hal/acpi/acpi.c | +| fs | `devfs` | kernel/device/devfs.c | +| devdefs | `mc146818` | arch/x86/hal/mc146818a.c | +| lunainit | `__init_blkbuf` | kernel/block/blkbuf.c | +| twiplugin | `kprintf` | kernel/kprint/kprintf.c | +| devdefs | `nulldev` | hal/char/devnull.c | +| devdefs | `pci3hba` | hal/bus/pci.c | +| fs | `taskfs` | kernel/process/taskfs.c | +| devdefs | `i8042_kbd` | arch/x86/hal/ps2kbd.c | diff --git a/docs/lunaix-internal.md b/docs/lunaix-internal.md new file mode 100644 index 0000000..21c7ef6 --- /dev/null +++ b/docs/lunaix-internal.md @@ -0,0 +1,11 @@ +# Luna's Tour + +这里是一个目录,收集了关于 Lunaix 内核设计的种种文档。 + +需要注意的是,这里的文档并不是教程,而是为读者提供一个 +针对Lunaix内核架构的高层次描述和概览,帮助读者快速的, +清晰的,掌握整个架构的设计;以便更容易进行源代码的阅读。 + ++ [虚拟内存资源划分](./lunaix-mem-map.md) ++ [启动流程概览](./lunaix-boot.md) + diff --git a/docs/lunaix-mem-map.md b/docs/lunaix-mem-map.md new file mode 100644 index 0000000..3b47c67 --- /dev/null +++ b/docs/lunaix-mem-map.md @@ -0,0 +1,36 @@ +# Lunaix 虚拟内存地址映射图简要 + +该说明将会展示 Lunaix 内核在不同架构下的虚拟内存地址资源的划分。 +请参看下表: + ++ [x86_32](img/lunaix-mem-map/lunaix-mem-x86_32.png) ++ [x86_64](img/lunaix-mem-map/lunaix-mem-x86_64.png) + +## 图例 + +| 名称 | 类型 | 默认权限 | 说明 | +| --- | --- | --- | --- | +| `reserved` | `PcLc` | `R` | 保留区域(存在映射,但暂无用途,或为过时硬件/功能预留) | +| <阴影区域> | `PcLc` | N/A | 空洞区,没有任何用处,没有任何映射 | +| `32 bits legacy` | `PcLc` | `RW` | 存在于 64位系统上,为32位4GiB地址资源的对等映射| +| `kstask_area` | `PcLc` | `RW` | 内核栈聚集地,所有内核线程的栈都来自于此 | +| `ks@threadN` | `PcLc` | `RW` | 第N个内核线程的栈 | +| `usr_space` | `PcLc` | `URW` | 用户空间 | +| `usr_exec` | `PcLv` | `URX` | 用户程序镜像映射加载区 | +| `heap` | `PvLv` | `URW` | 用户堆(起始地址取决于`usr_exec`) | +| `mmaps` | `PcLc` | `URW` | 通用内存映射区 | +| `ustack_area` | `PcLc` | `URW` | 用户栈聚集地,所有用户线程的栈都来自于此 | +| `vmap` | `PcLc`* | `RW` | 内核通用内存映射区,一个内存地址池,为内核所有组件/子系统,提供地址资源管理服务 | +| `vms_mnt` | `PcLc` | `RW` | 虚拟内存空间挂载点,用于远程编辑其他虚拟内存空间的映射关系(PTE们) | +| `vms_self` | `PcLc` | `RW` | 环回挂载点,用于编辑当前虚拟内存空间的映射关系。 | +| `pg_mnts` | `PcLc` | `RW` | 页挂载点,用于临时挂载页面,以便对其内容进行读写,最多支持挂载4个标准页以及一个大小不超过512M的巨页/复合页 | +| `pmap` | `PvLc` | `RW` | 用于存放物理页管理结构(一个巨型数组,长度取决于实装内存大小) | +| `kernel_exec` | `PcLc` | `RX` | 内核镜像 | + +类型注释: + ++ `Pv`: 动态起始地址(Positione varia) ++ `Pc`: 固定起始地址(Positione certa) ++ `Lv`: 动态长度(Longitudinis variae) ++ `Lc`: 固定长度(Longitudinem certam) ++ `*`: vmap的起始地址在x86_32中为动态的,取决于`pmap`的长度。 \ No newline at end of file diff --git a/docs/lunaix-syscall-table.md b/docs/lunaix-syscall-table.md index 29b76c8..43633d8 100644 --- a/docs/lunaix-syscall-table.md +++ b/docs/lunaix-syscall-table.md @@ -1,4 +1,4 @@ -| Name | Id | +| Name | ID | | ----- | ---- | | __SYSCALL_fork | 1 | | __SYSCALL_yield | 2 | diff --git a/lunaix-os/.gitignore b/lunaix-os/.gitignore index a8dbb20..f167317 100644 --- a/lunaix-os/.gitignore +++ b/lunaix-os/.gitignore @@ -1,26 +1,29 @@ -build/ -playground/ -.vscode/settings.json -.vscode/*.log -.VSCodeCounter/ -.idea -bx_enh_dbg.ini -machine/ -draft/ -iso_inspect/ -unused/ -__pycache__/ +/* -.builder/ -.config.json - -link/lunaix.ld - -.gdb_history +!arch +!hal +!kernel +!libs +!makeinc +!includes +!scripts +!usr +!link -**.o -**.d +!.gitignore +!*.md +!.pre-commit-config.yaml +!LBuild +!LConfig +!makefile +!live_debug.sh +!kernel.mk +*.[od] +*.ld *.log -.lunaix_ksymtable.S \ No newline at end of file +__pycache__ + +.config.json +.builder \ No newline at end of file diff --git a/lunaix-os/kernel.mk b/lunaix-os/kernel.mk index be10da2..eb5276d 100644 --- a/lunaix-os/kernel.mk +++ b/lunaix-os/kernel.mk @@ -45,9 +45,9 @@ $(tmp_kbin): $(klinking) $(ksrc_objs) $(ksymtable): $(tmp_kbin) $(call status_,KSYM,$@) - @ARCH=$(ARCH) scripts/gen_ksymtable.sh DdRrTtAGg $< > .lunaix_ksymtable.S + @ARCH=$(ARCH) scripts/gen_ksymtable.sh DdRrTtAGg $< > lunaix_ksymtable.S - @$(CC) $(CFLAGS) -c .lunaix_ksymtable.S -o $@ + @$(CC) $(CFLAGS) -c lunaix_ksymtable.S -o $@ .PHONY: __do_relink @@ -68,4 +68,4 @@ clean: @rm -f $(ksrc_objs) @rm -f $(ksrc_deps) @rm -f $(klinking) - @rm -f .lunaix_ksymtable.S $(ksymtable) \ No newline at end of file + @rm -f lunaix_ksymtable.S $(ksymtable) \ No newline at end of file diff --git a/lunaix-os/scripts/gather_lga.sh b/lunaix-os/scripts/gather_lga.sh new file mode 100755 index 0000000..12134e0 --- /dev/null +++ b/lunaix-os/scripts/gather_lga.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +declare -A nm_lookup + +kbin="$1" + +nm_out=($(nm -l "${kbin}" | awk '$3 ~ /^__lga*/ {print $3 "," $4}')) + +for line in "${nm_out[@]}" +do + parts=(${line//,/ }) + if [ "${parts[1]}" != "" ]; then + path=(${parts[1]//:/ }) + relative=$(realpath -s --relative-to="${SCRIPT_DIR}/.." "${path[0]}") + nm_lookup["${parts[0]}"]="${relative}" + fi +done + +objdump_out=($(objdump -t -j .lga "${kbin}" | grep .lga | awk '!($NF ~ /_start$|_end$|_ldorder$/) {print $NF}')) + +list=() +dev_list=() +init_list=() +sysmap_list=() +init_prefix="lunainit_" +sysmap_prefix="twiplugin_inits_" +devdefs_prefix="devdefs_" +fs_prefix="fs_" + +for line in "${objdump_out[@]}" +do + loc=${nm_lookup["$line"]} + if [ "$loc" == "" ]; then + continue + fi + + type="unknown" + line=$(echo "$line" | awk '{ sub(/^__lga_/, ""); print }') + + if [[ $line == $init_prefix* ]]; then + line=$(echo "$line" | awk "{ sub(/^${init_prefix}/, \"\"); print }") + init_list+=("$line $loc") + type="lunainit" + fi + + if [[ $line == $sysmap_prefix* ]]; then + line=$(echo "$line" | awk "{ sub(/^${sysmap_prefix}/, \"\"); print }") + sysmap_list+=("$line $loc") + type="twiplugin" + fi + + if [[ $line == $fs_prefix* ]]; then + line=$(echo "$line" | awk "{ sub(/^${fs_prefix}/, \"\"); print }") + sysmap_list+=("$line $loc") + type="fs" + fi + + if [[ $line == $devdefs_prefix* ]]; then + line=$(echo "$line" | awk "{ sub(/^${devdefs_prefix}/, \"\"); print }") + type="devdefs" + fi + + line=$(echo "$line" | awk "{ sub(/_on_[a-z]+$/, \"\"); print }") + list+=("$line $loc") + + echo "| $type | \`$line\` | $loc |" +done + -- 2.27.0 From a136ca38d83fae60994a54f5da88120e545895e1 Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Wed, 14 Aug 2024 22:54:37 +0100 Subject: [PATCH 13/16] Improve cake allocator's memory utilisation (#43) * allow cake metadata to be separated and improve the utilisation * add method to reclaim the freed cakes * adjust threshold to make all valloc > 128 use non-embed pile --- lunaix-os/includes/lunaix/mm/cake.h | 29 +++- lunaix-os/kernel/mm/cake.c | 249 ++++++++++++++++++++++++---- lunaix-os/kernel/mm/cake_export.c | 5 +- lunaix-os/kernel/mm/valloc.c | 33 +++- 4 files changed, 278 insertions(+), 38 deletions(-) diff --git a/lunaix-os/includes/lunaix/mm/cake.h b/lunaix-os/includes/lunaix/mm/cake.h index cf1c16e..d880531 100644 --- a/lunaix-os/includes/lunaix/mm/cake.h +++ b/lunaix-os/includes/lunaix/mm/cake.h @@ -6,7 +6,8 @@ #define PILE_NAME_MAXLEN 20 -#define PILE_ALIGN_CACHE 1 +#define PILE_ALIGN_CACHE 0b0001 +#define PILE_FL_EXTERN 0b0010 struct cake_pile; @@ -24,22 +25,39 @@ struct cake_pile u32_t alloced_pieces; u32_t pieces_per_cake; u32_t pg_per_cake; + u32_t options; char pile_name[PILE_NAME_MAXLEN+1]; pile_cb ctor; }; -typedef unsigned int piece_index_t; +typedef unsigned short piece_t; -#define EO_FREE_PIECE ((u32_t)-1) +#define EO_FREE_PIECE ((piece_t)-1) + +#define CAKE_FL_SIZE 128 +#define CAKE_FL_MAXLEN \ + ((unsigned int)((CAKE_FL_SIZE - sizeof(ptr_t)) / sizeof(piece_t))) +struct cake_fl +{ + piece_t indices[CAKE_FL_MAXLEN]; + struct cake_fl* next; +} align(CAKE_FL_SIZE); struct cake_s { struct llist_header cakes; + struct cake_pile* owner; void* first_piece; unsigned int used_pieces; unsigned int next_free; - piece_index_t free_list[0]; + union { + struct cake_fl* fl; + struct { + void* rsvd; + piece_t free_list[0]; + }; + }; }; /** @@ -83,6 +101,9 @@ cake_init(); void cake_export(); +void +cake_reclaim_freed(); + /********** some handy constructor ***********/ void diff --git a/lunaix-os/kernel/mm/cake.c b/lunaix-os/kernel/mm/cake.c index d9ef381..b1e699f 100644 --- a/lunaix-os/kernel/mm/cake.c +++ b/lunaix-os/kernel/mm/cake.c @@ -2,7 +2,7 @@ * @file cake.c * @author Lunaixsky (zelong56@gmail.com) * @brief A simplified cake(slab) allocator. - * P.s. I call it cake as slab sounds more 'ridge' to me. :) + * P.s. I call it cake as slab sounds quite 'rigid' to me :) * @version 0.1 * @date 2022-07-02 * @@ -20,12 +20,18 @@ LOG_MODULE("CAKE") #define CACHE_LINE_SIZE 128 -struct cake_pile master_pile; +static struct cake_pile master_pile, fl_pile, cakes; struct llist_header piles = { .next = &piles, .prev = &piles }; -void* -__alloc_cake(unsigned int cake_pg) +static inline bool +embedded_pile(struct cake_pile* pile) +{ + return !(pile->options & PILE_FL_EXTERN); +} + +static void* +__alloc_cake_pages(unsigned int cake_pg) { struct leaflet* leaflet = alloc_leaflet(count_order(cake_pg)); if (!leaflet) { @@ -35,35 +41,135 @@ __alloc_cake(unsigned int cake_pg) return (void*)vmap(leaflet, KERNEL_DATA); } -struct cake_s* -__new_cake(struct cake_pile* pile) +static struct cake_fl* +__alloc_fl(piece_t prev_id, piece_t max_len) { + unsigned int i, j; + struct cake_fl* cake_fl; + + cake_fl = cake_grab(&fl_pile); + for (i = 0, j=prev_id; i < CAKE_FL_MAXLEN && j < max_len; i++, j++) + { + cake_fl->indices[i] = j + 1; + } + + for (i -= 1; j == max_len && i < CAKE_FL_SIZE; i++) { + cake_fl->indices[i] = EO_FREE_PIECE; + } + + cake_fl->next = NULL; + return cake_fl; +} + +static void +__free_fls(struct cake_s* cake) { + unsigned int i, j; + struct cake_fl *cake_fl, *next; + + cake_fl = cake->fl; + while (cake_fl) + { + next = cake_fl->next; + cake_release(&fl_pile, cake_fl); + cake_fl = next; + } + + cake->fl = NULL; +} + +static piece_t* +__fl_slot(struct cake_s* cake, piece_t index) +{ + int id_acc; + struct cake_fl* cake_fl; + struct cake_pile* pile; + + pile = cake->owner; + if (embedded_pile(pile)) { + return &cake->free_list[index]; + } + + id_acc = 0; + cake_fl = cake->fl; + while (index >= CAKE_FL_MAXLEN) + { + index -= CAKE_FL_MAXLEN; + id_acc += CAKE_FL_MAXLEN; + + if (cake_fl->next) { + cake_fl = cake_fl->next; + continue; + } + + cake_fl = __alloc_fl(id_acc, pile->pieces_per_cake); + cake_fl->next = cake_fl; + } + + return &cake_fl->indices[index]; +} + +static inline struct cake_s* +__create_cake_extern(struct cake_pile* pile) { - struct cake_s* cake = __alloc_cake(pile->pg_per_cake); + struct cake_s* cake; + + cake = cake_grab(&cakes); + if (unlikely(!cake)) { + return NULL; + } - if (!cake) { + cake->first_piece = __alloc_cake_pages(pile->pg_per_cake); + cake->fl = __alloc_fl(0, pile->pieces_per_cake); + cake->next_free = 0; + + return cake; +} + +static inline struct cake_s* +__create_cake_embed(struct cake_pile* pile) +{ + struct cake_s* cake; + + cake = __alloc_cake_pages(pile->pg_per_cake); + if (unlikely(!cake)) { return NULL; } u32_t max_piece = pile->pieces_per_cake; assert(max_piece); - + cake->first_piece = (void*)((ptr_t)cake + pile->offset); cake->next_free = 0; - pile->cakes_count++; - piece_index_t* free_list = cake->free_list; + piece_t* free_list = cake->free_list; for (size_t i = 0; i < max_piece - 1; i++) { free_list[i] = i + 1; } free_list[max_piece - 1] = EO_FREE_PIECE; + return cake; +} + +static struct cake_s* +__new_cake(struct cake_pile* pile) +{ + struct cake_s* cake; + + if (embedded_pile(pile)) { + cake = __create_cake_embed(pile); + } + else { + cake = __create_cake_extern(pile); + } + + cake->owner = pile; + pile->cakes_count++; llist_append(&pile->free, &cake->cakes); return cake; } -void +static void __init_pile(struct cake_pile* pile, char* name, unsigned int piece_size, @@ -85,15 +191,28 @@ __init_pile(struct cake_pile* pile, piece_size = ROUNDUP(piece_size, offset); *pile = (struct cake_pile){ .piece_size = piece_size, .cakes_count = 0, - .pieces_per_cake = - (pg_per_cake * PAGE_SIZE) / - (piece_size + sizeof(piece_index_t)), + .options = options, .pg_per_cake = pg_per_cake }; - unsigned int free_list_size = pile->pieces_per_cake * sizeof(piece_index_t); - - pile->offset = ROUNDUP(sizeof(struct cake_s) + free_list_size, offset); - pile->pieces_per_cake -= ICEIL((pile->offset - free_list_size), piece_size); + if (!embedded_pile(pile)) { + pile->offset = 0; + pile->pieces_per_cake = (pg_per_cake * PAGE_SIZE) / piece_size; + } + else { + unsigned int free_list_size; + + pile->pieces_per_cake + = (pg_per_cake * PAGE_SIZE) / + (piece_size + sizeof(piece_t)); + + free_list_size + = pile->pieces_per_cake * sizeof(piece_t); + + pile->offset + = ROUNDUP(sizeof(struct cake_s) + free_list_size, offset); + pile->pieces_per_cake + -= ICEIL((pile->offset - free_list_size), piece_size); + } strncpy(pile->pile_name, name, PILE_NAME_MAXLEN); @@ -103,10 +222,49 @@ __init_pile(struct cake_pile* pile, llist_append(&piles, &pile->piles); } +static void +__destory_cake(struct cake_s* cake) +{ + // Pinkie Pie is going to MAD! + + struct leaflet* leaflet; + struct cake_pile* owner; + pfn_t _pfn; + ptr_t page_va; + + owner = cake->owner; + assert(!cake->used_pieces); + + llist_delete(&cake->cakes); + owner->cakes_count--; + + if (!embedded_pile(owner)) { + page_va = __ptr(cake->first_piece); + __free_fls(cake); + cake_release(&cakes, cake); + } + else { + page_va = __ptr(cake); + } + + _pfn = pfn(vmm_v2p(page_va)); + leaflet = ppfn_leaflet(_pfn); + vunmap(page_va, leaflet); + + leaflet_return(leaflet); +} + void cake_init() { - __init_pile(&master_pile, "pinkamina", sizeof(master_pile), 1, 0); + // pinkamina is our master, no one shall precede her. + __init_pile(&master_pile, "pinkamina", + sizeof(master_pile), 1, 0); + + __init_pile(&fl_pile, "gummy", \ + sizeof(struct cake_fl), 1, 0); + __init_pile(&cakes, "cakes", \ + sizeof(struct cake_s), 1, 0); } struct cake_pile* @@ -145,33 +303,40 @@ cake_grab(struct cake_pile* pile) if (!pos) return NULL; + + void* piece; + piece_t found_index, *fl_slot; + + found_index = pos->next_free; + fl_slot = __fl_slot(pos, found_index); - piece_index_t found_index = pos->next_free; - pos->next_free = pos->free_list[found_index]; + pos->next_free = *fl_slot; pos->used_pieces++; pile->alloced_pieces++; llist_delete(&pos->cakes); - if (pos->free_list[pos->next_free] == EO_FREE_PIECE) { + + fl_slot = __fl_slot(pos, pos->next_free); + if (*fl_slot == EO_FREE_PIECE) { llist_append(&pile->full, &pos->cakes); } else { llist_append(&pile->partial, &pos->cakes); } - void* ptr = - (void*)((ptr_t)pos->first_piece + found_index * pile->piece_size); + piece = + (void*)(__ptr(pos->first_piece) + found_index * pile->piece_size); if (pile->ctor) { - pile->ctor(pile, ptr); + pile->ctor(pile, piece); } - return ptr; + return piece; } int cake_release(struct cake_pile* pile, void* area) { - piece_index_t piece_index; + piece_t piece_index; size_t dsize = 0; struct cake_s *pos, *n; struct llist_header* hdrs[2] = { &pile->full, &pile->partial }; @@ -194,10 +359,14 @@ cake_release(struct cake_pile* pile, void* area) found: assert(!(dsize % pile->piece_size)); - pos->free_list[piece_index] = pos->next_free; + + piece_t *fl_slot; + + fl_slot = __fl_slot(pos, piece_index); + *fl_slot = pos->next_free; pos->next_free = piece_index; - assert_msg(pos->free_list[piece_index] != pos->next_free, "double free"); + assert_msg(*fl_slot != pos->next_free, "double free"); pos->used_pieces--; pile->alloced_pieces--; @@ -218,4 +387,24 @@ void cake_ctor_zeroing(struct cake_pile* pile, void* piece) { memset(piece, 0, pile->piece_size); +} + +static inline void +__reclaim(struct cake_pile *pile) +{ + struct cake_s *pos, *n; + llist_for_each(pos, n, &pile->free, cakes) + { + __destory_cake(pos); + } +} + +void +cake_reclaim_freed() +{ + struct cake_pile *pos, *n; + llist_for_each(pos, n, &master_pile.piles, piles) + { + __reclaim(pos); + } } \ No newline at end of file diff --git a/lunaix-os/kernel/mm/cake_export.c b/lunaix-os/kernel/mm/cake_export.c index 377a9ad..e2ecb3a 100644 --- a/lunaix-os/kernel/mm/cake_export.c +++ b/lunaix-os/kernel/mm/cake_export.c @@ -18,7 +18,7 @@ void __cake_stat_reset(struct twimap* map) { map->index = container_of(&piles, struct cake_pile, piles); - twimap_printf(map, "name, n_cakes, pg/cake, slices/cake, n_slices\n"); + twimap_printf(map, "name cakes pages size slices actives\n"); } void @@ -26,10 +26,11 @@ __cake_rd_stat(struct twimap* map) { struct cake_pile* pos = twimap_index(map, struct cake_pile*); twimap_printf(map, - "%s %d %d %d %d\n", + "%s %d %d %d %d %d\n", pos->pile_name, pos->cakes_count, pos->pg_per_cake, + pos->piece_size, pos->pieces_per_cake, pos->alloced_pieces); } diff --git a/lunaix-os/kernel/mm/valloc.c b/lunaix-os/kernel/mm/valloc.c index 67c8eac..d2baa35 100644 --- a/lunaix-os/kernel/mm/valloc.c +++ b/lunaix-os/kernel/mm/valloc.c @@ -5,6 +5,9 @@ #define CLASS_LEN(class) (sizeof(class) / sizeof(class[0])) +// threshold to use external cake metadata +#define EXTERN_THRESHOLD 128 + static char piles_names[][PILE_NAME_MAXLEN] = { "valloc_8", "valloc_16", "valloc_32", "valloc_64", @@ -12,6 +15,24 @@ static char piles_names[][PILE_NAME_MAXLEN] = "valloc_2k", "valloc_4k", "valloc_8k" }; +#define M128 (4) +#define M1K (M128 + 3) + +static int page_counts[] = +{ + [0] = 1, + [1] = 1, + [2] = 1, + [3] = 1, + [M128 ] = 1, + [M128 + 1] = 2, + [M128 + 2] = 2, + [M1K ] = 4, + [M1K + 1 ] = 4, + [M1K + 2 ] = 8, + [M1K + 3 ] = 8 +}; + static char piles_names_dma[][PILE_NAME_MAXLEN] = { "valloc_dma_128", "valloc_dma_256", "valloc_dma_512", @@ -24,16 +45,24 @@ static struct cake_pile* piles_dma[CLASS_LEN(piles_names_dma)]; void valloc_init() { + int opts = 0; for (size_t i = 0; i < CLASS_LEN(piles_names); i++) { int size = 1 << (i + 3); - piles[i] = cake_new_pile(piles_names[i], size, size > 1024 ? 8 : 1, 0); + if (size >= EXTERN_THRESHOLD) { + opts |= PILE_FL_EXTERN; + } + piles[i] = cake_new_pile(piles_names[i], size, page_counts[i], opts); } + opts = PILE_ALIGN_CACHE; // DMA 内存保证128字节对齐 for (size_t i = 0; i < CLASS_LEN(piles_names_dma); i++) { int size = 1 << (i + 7); + if (size >= EXTERN_THRESHOLD) { + opts |= PILE_FL_EXTERN; + } piles_dma[i] = cake_new_pile( - piles_names_dma[i], size, size > 1024 ? 4 : 1, PILE_ALIGN_CACHE); + piles_names_dma[i], size, page_counts[M128 + i], opts); } } -- 2.27.0 From 50b4ecfb1b28e9b1dfc57b6a876fcdf938092152 Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Sun, 25 Aug 2024 01:55:17 +0100 Subject: [PATCH 14/16] Menuconfig Implementation and auto-qemu refactoring (#44) * unify the chardev backend definitions * add --dry option for dry-running * allow supply extra raw qemu options * implement ncurses based tui framework * basic lunamenu framework built on top libtui * (libtui) add scrollable, list, textbox * (menu) add listview, dialogue * interface LunaConfig into menuconfig * remove flickering when new context being added and redrawn * add ability to navigate, edit the config node * adjust the layout parameters * some refactors * add help dialogue * layout refinement * add as hot key to navigate up level * add confirmation dialogue for exiting * refactors * add user friendly alias to LConfig terms * fix the focusing problem with textbox * bypass the configuration for user program generation * bypass the showing of config view during usr program generation, use default value instead * house keeping stuff * add terminal dimension check and fallback to prompt based * replenish the help messages * fix: udiv64 is not used when doing u64 division --- lunaix-os/LConfig | 4 +- lunaix-os/arch/LConfig | 22 +- lunaix-os/arch/x86/LConfig | 8 +- lunaix-os/hal/LConfig | 2 +- lunaix-os/hal/ahci/LConfig | 4 +- lunaix-os/hal/bus/LConfig | 8 +- lunaix-os/hal/char/LConfig | 12 +- lunaix-os/kernel/block/blkbuf.c | 4 +- lunaix-os/kernel/fs/LConfig | 6 +- lunaix-os/kernel/mm/LConfig | 28 +- lunaix-os/makeinc/lunabuild.mkinc | 9 +- .../build-tools/integration/libmenu.py | 261 ++++ .../scripts/build-tools/integration/libtui.py | 1267 +++++++++++++++++ .../build-tools/integration/lunamenu.py | 457 ++++++ lunaix-os/scripts/build-tools/lcfg/types.py | 4 +- lunaix-os/scripts/build-tools/luna_build.py | 21 +- lunaix-os/scripts/qemu.py | 231 ++- lunaix-os/scripts/qemus/qemu_x86_dev.json | 28 +- lunaix-os/usr/LConfig | 2 +- lunaix-os/usr/makefile | 3 + 20 files changed, 2259 insertions(+), 122 deletions(-) create mode 100644 lunaix-os/scripts/build-tools/integration/libmenu.py create mode 100644 lunaix-os/scripts/build-tools/integration/libtui.py create mode 100644 lunaix-os/scripts/build-tools/integration/lunamenu.py diff --git a/lunaix-os/LConfig b/lunaix-os/LConfig index 36be155..1c151e3 100644 --- a/lunaix-os/LConfig +++ b/lunaix-os/LConfig @@ -4,7 +4,7 @@ include("kernel") include("arch") include("hal") -@Term("Version") +@Term("Kernel Version") @ReadOnly def lunaix_ver(): """ @@ -16,7 +16,7 @@ def lunaix_ver(): seq_num = int(time.time() / 3600) default("%s dev-2024_%d"%(v(arch), seq_num)) -@Collection +@Collection("Kernel Debug and Testing") def debug_and_testing(): """ General settings for kernel debugging feature diff --git a/lunaix-os/arch/LConfig b/lunaix-os/arch/LConfig index 95403e3..7afea85 100644 --- a/lunaix-os/arch/LConfig +++ b/lunaix-os/arch/LConfig @@ -1,24 +1,34 @@ include("x86/LConfig") -@Collection +@Collection("Platform") def architecture_support(): """ Config ISA related features """ - @Term + @Term("Architecture") def arch(): - """ Config ISA support """ - type(["i386", "x86_64", "aarch64", "rv64"]) - default("i386") + """ + Config ISA support + """ + # type(["i386", "x86_64", "aarch64", "rv64"]) + type(["i386", "x86_64"]) + default("x86_64") env_val = env("ARCH") if env_val: set_value(env_val) - @Term + @Term("Base operand size") @ReadOnly def arch_bits(): + """ + Defines the base size of a general register of the + current selected ISA. + + This the 'bits' part when we are talking about a CPU + """ + type(["64", "32"]) match v(arch): case "i386": diff --git a/lunaix-os/arch/x86/LConfig b/lunaix-os/arch/x86/LConfig index 7580a36..2b34524 100644 --- a/lunaix-os/arch/x86/LConfig +++ b/lunaix-os/arch/x86/LConfig @@ -4,7 +4,7 @@ def x86_configurations(): add_to_collection(architecture_support) - @Term + @Term("Use SSE2/3/4 extension") def x86_enable_sse_feature(): """ Config whether to allow using SSE feature for certain @@ -15,14 +15,14 @@ def x86_configurations(): default(False) - @Term + @Term("Bootloader Model") def x86_bl(): """ Select the bootloader interface Supported interface - mb: multiboot compliant - mb2: multiboot2 compliant + mb: multiboot compliance + mb2: multiboot2 compliance none: do not use any interface """ diff --git a/lunaix-os/hal/LConfig b/lunaix-os/hal/LConfig index 35385c9..63598db 100644 --- a/lunaix-os/hal/LConfig +++ b/lunaix-os/hal/LConfig @@ -2,7 +2,7 @@ include("char") include("bus") include("ahci") -@Collection +@Collection("Devices & Peripherials") def hal(): """ Lunaix hardware asbtraction layer """ diff --git a/lunaix-os/hal/ahci/LConfig b/lunaix-os/hal/ahci/LConfig index b957881..a199abf 100644 --- a/lunaix-os/hal/ahci/LConfig +++ b/lunaix-os/hal/ahci/LConfig @@ -1,10 +1,10 @@ -@Collection +@Collection("AHCI") def sata_ahci(): add_to_collection(hal) - @Term + @Term("Enable AHCI support") def ahci_enable(): """ Enable the support of SATA AHCI. Must require PCI at current stage """ diff --git a/lunaix-os/hal/bus/LConfig b/lunaix-os/hal/bus/LConfig index 74e3dbf..e4281d2 100644 --- a/lunaix-os/hal/bus/LConfig +++ b/lunaix-os/hal/bus/LConfig @@ -1,17 +1,17 @@ -@Collection +@Collection("Buses & Interconnects") def bus_if(): """ System/platform bus interface """ add_to_collection(hal) - @Term + @Term("PCI") def pci_enable(): """ Peripheral Component Interconnect (PCI) Bus """ type(bool) default(True) - @Term + @Term("PCI Express") def pcie_ext(): """ Enable support of PCI-Express extension """ type(bool) @@ -19,7 +19,7 @@ def bus_if(): return v(pci_enable) - @Term + @Term("Use PMIO for PCI") def pci_pmio(): """ Use port-mapped I/O interface for controlling PCI """ type(bool) diff --git a/lunaix-os/hal/char/LConfig b/lunaix-os/hal/char/LConfig index 788bfb4..8e662fa 100644 --- a/lunaix-os/hal/char/LConfig +++ b/lunaix-os/hal/char/LConfig @@ -1,21 +1,25 @@ include("uart") -@Collection +@Collection("Character Devices") def char_device(): """ Controlling support of character devices """ add_to_collection(hal) - @Term + @Term("VGA 80x25 text-mode console") def vga_console(): """ Enable VGA console device (text mode only) """ type(bool) default(True) - @Term + @Term("VGA character game device") def chargame_console(): - """ Enable VGA Charactor Game console device (text mode only) """ + """ + Enable VGA Charactor Game console device (text mode only) + + You normally don't need to include this, unless you want some user space fun ;) + """ type(bool) default(False) \ No newline at end of file diff --git a/lunaix-os/kernel/block/blkbuf.c b/lunaix-os/kernel/block/blkbuf.c index 4f255c9..302cd96 100644 --- a/lunaix-os/kernel/block/blkbuf.c +++ b/lunaix-os/kernel/block/blkbuf.c @@ -3,6 +3,7 @@ #include #include #include +#include LOG_MODULE("blkbuf") @@ -17,7 +18,8 @@ static struct cake_pile* bb_pile; static inline u64_t __tolba(struct blkbuf_cache* cache, unsigned int blk_id) { - return ((u64_t)cache->blksize * (u64_t)blk_id) / cache->blkdev->blk_size; + return udiv64(((u64_t)cache->blksize * (u64_t)blk_id), + cache->blkdev->blk_size); } static void diff --git a/lunaix-os/kernel/fs/LConfig b/lunaix-os/kernel/fs/LConfig index 37a9695..194e186 100644 --- a/lunaix-os/kernel/fs/LConfig +++ b/lunaix-os/kernel/fs/LConfig @@ -1,18 +1,18 @@ -@Collection +@Collection("File Systems") def file_system(): """ Config feature related to file system supports """ add_to_collection(kernel_feature) - @Term + @Term("ext2 support") def fs_ext2(): """ Enable ext2 file system support """ type(bool) default(True) - @Term + @Term("iso9660 support") def fs_iso9660(): """ Enable iso9660 file system support """ diff --git a/lunaix-os/kernel/mm/LConfig b/lunaix-os/kernel/mm/LConfig index 7db89f5..17247b0 100644 --- a/lunaix-os/kernel/mm/LConfig +++ b/lunaix-os/kernel/mm/LConfig @@ -1,86 +1,86 @@ -@Collection +@Collection("Memory Management") def memory_subsystem(): """ Config the memory subsystem """ - @Collection + @Collection("Physical Memory") def physical_mm(): """ Physical memory manager """ - @Term + @Term("Allocation policy") def pmalloc_method(): """ Allocation policy for phiscal memory """ type(["simple", "buddy", "ncontig"]) default("simple") - @Group + @Group("Simple") def pmalloc_simple_po_thresholds(): - @Term + @Term("Maximum cached order-0 free pages") def pmalloc_simple_max_po0(): """ free list capacity for order-0 pages """ type(int) default(4096) - @Term + @Term("Maximum cached order-1 free pages") def pmalloc_simple_max_po1(): """ free list capacity for order-1 pages """ type(int) default(2048) - @Term + @Term("Maximum cached order-2 free pages") def pmalloc_simple_max_po2(): """ free list capacity for order-2 pages """ type(int) default(2048) - @Term + @Term("Maximum cached order-3 free pages") def pmalloc_simple_max_po3(): """ free list capacity for order-3 pages """ type(int) default(2048) - @Term + @Term("Maximum cached order-4 free pages") def pmalloc_simple_max_po4(): """ free list capacity for order-4 pages """ type(int) default(512) - @Term + @Term("Maximum cached order-5 free pages") def pmalloc_simple_max_po5(): """ free list capacity for order-5 pages """ type(int) default(512) - @Term + @Term("Maximum cached order-6 free pages") def pmalloc_simple_max_po6(): """ free list capacity for order-6 pages """ type(int) default(128) - @Term + @Term("Maximum cached order-7 free pages") def pmalloc_simple_max_po7(): """ free list capacity for order-7 pages """ type(int) default(128) - @Term + @Term("Maximum cached order-8 free pages") def pmalloc_simple_max_po8(): """ free list capacity for order-8 pages """ type(int) default(64) - @Term + @Term("Maximum cached order-9 free pages") def pmalloc_simple_max_po9(): """ free list capacity for order-9 pages """ diff --git a/lunaix-os/makeinc/lunabuild.mkinc b/lunaix-os/makeinc/lunabuild.mkinc index d246a6e..aee8e49 100644 --- a/lunaix-os/makeinc/lunabuild.mkinc +++ b/lunaix-os/makeinc/lunabuild.mkinc @@ -7,15 +7,16 @@ lbuild_opts := --lconfig-file LConfig all_lconfigs = $(shell find $(CURDIR) -name "LConfig") +LCONFIG_FLAGS += --config $(lbuild_opts) --config-save $(lconfig_save) + export $(lconfig_save): $(all_lconfigs) @echo restarting configuration... - @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) --force\ - -o $(lbuild_dir)/ + @$(LBUILD) $(LCONFIG_FLAGS) --force -o $(lbuild_dir)/ export $(lbuild_config_h): $(lconfig_save) - @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) -o $(@D) + @$(LBUILD) $(LCONFIG_FLAGS) -o $(@D) export $(lbuild_mkinc): $(lbuild_config_h) @@ -24,5 +25,5 @@ $(lbuild_mkinc): $(lbuild_config_h) .PHONY: config export config: $(all_lconfigs) - @$(LBUILD) --config $(lbuild_opts) --config-save $(lconfig_save) --force\ + @$(LBUILD) $(LCONFIG_FLAGS) --force\ -o $(lbuild_dir)/ diff --git a/lunaix-os/scripts/build-tools/integration/libmenu.py b/lunaix-os/scripts/build-tools/integration/libmenu.py new file mode 100644 index 0000000..3b0492e --- /dev/null +++ b/lunaix-os/scripts/build-tools/integration/libmenu.py @@ -0,0 +1,261 @@ +import curses +import integration.libtui as tui +from integration.libtui import ColorScope, TuiColor, Alignment, EventType + +def create_buttons(main_ctx, btn_defs, sizes = "*,*"): + size_defs = ",".join(['*'] * len(btn_defs)) + + layout = tui.FlexLinearLayout(main_ctx, "buttons", size_defs) + layout.orientation(tui.FlexLinearLayout.LANDSCAPE) + layout.set_size(*(sizes.split(',')[:2])) + layout.set_padding(1, 1, 1, 1) + layout.set_alignment(Alignment.CENTER | Alignment.BOT) + + for i, btn_def in enumerate(btn_defs): + but1 = tui.TuiButton(main_ctx, "b1") + but1.set_text(btn_def["text"]) + but1.set_click_callback(btn_def["onclick"]) + but1.set_alignment(Alignment.CENTER) + + layout.set_cell(i, but1) + + return layout + +def create_title(ctx, title): + _t = tui.TuiLabel(ctx, "label") + _t.set_text(title) + _t.set_local_pos(1, 0) + _t.set_alignment(Alignment.TOP | Alignment.CENTER) + _t.hightlight(True) + _t.pad_around(True) + return _t + +class ListView(tui.TuiObject): + def __init__(self, context, id): + super().__init__(context, id) + + self.__create_layout() + + self.__sel_changed = None + self.__sel = None + + def __create_layout(self): + hint_moveup = tui.TuiLabel(self._context, "movup") + hint_moveup.override_color(ColorScope.HINT) + hint_moveup.set_text("^^^ - MORE") + hint_moveup.set_visbility(False) + hint_moveup.set_alignment(Alignment.TOP) + + hint_movedown = tui.TuiLabel(self._context, "movdown") + hint_movedown.override_color(ColorScope.HINT) + hint_movedown.set_text("vvv - MORE") + hint_movedown.set_visbility(False) + hint_movedown.set_alignment(Alignment.BOT) + + list_ = tui.SimpleList(self._context, "list") + list_.set_size("*", "*") + list_.set_alignment(Alignment.CENTER | Alignment.TOP) + + list_.set_onselected_cb(self._on_selected) + list_.set_onselection_change_cb(self._on_sel_changed) + + scroll = tui.TuiScrollable(self._context, "scroll") + scroll.set_size("*", "*") + scroll.set_alignment(Alignment.CENTER) + scroll.set_content(list_) + + layout = tui.FlexLinearLayout( + self._context, f"main_layout", "2,*,2") + layout.set_size("*", "*") + layout.set_alignment(Alignment.CENTER) + layout.orientation(tui.FlexLinearLayout.PORTRAIT) + layout.set_parent(self) + + layout.set_cell(0, hint_moveup) + layout.set_cell(1, scroll) + layout.set_cell(2, hint_movedown) + + self.__hint_up = hint_moveup + self.__hint_down = hint_movedown + self.__list = list_ + self.__scroll = scroll + self.__layout = layout + + def add_item(self, item): + self.__list.add_item(item) + + def clear(self): + self.__list.clear() + + def on_draw(self): + super().on_draw() + + more_above = not self.__scroll.reached_top() + more_below = not self.__scroll.reached_last() + self.__hint_up.set_visbility(more_above) + self.__hint_down.set_visbility(more_below) + + self.__layout.on_draw() + + def on_layout(self): + super().on_layout() + self.__layout.on_layout() + + def _on_sel_changed(self, listv, prev, new): + h = self.__scroll._size.y() + self.__scroll.set_scrollY((new + 1) // h * h) + + if self.__sel_changed: + self.__sel_changed(listv, prev, new) + + def _on_selected(self, listv, index, item): + if self.__sel: + self.__sel(listv, index, item) + + def set_onselected_cb(self, cb): + self.__sel = cb + + def set_onselect_changed_cb(self, cb): + self.__sel_changed = cb + +class Dialogue(tui.TuiContext): + Pending = 0 + Yes = 1 + No = 2 + Abort = 3 + def __init__(self, session, title = "", content = "", input=False, + ok_btn = "OK", no_btn = "No", abort_btn = None): + super().__init__(session) + + self.__btns = [ + { "text": ok_btn, "onclick": lambda x: self._ok_onclick() } + ] + + if no_btn: + self.__btns.append({ + "text": no_btn, + "onclick": lambda x: self._no_onclick() + }) + if abort_btn: + self.__btns.append({ + "text": abort_btn, + "onclick": lambda x: self._abort_onclick() + }) + + self.__title_txt = title + self.__status = Dialogue.Pending + self.__content = content + self.__input_dialog = input + self._textbox = None + + self.set_size("70", "0.5*") + self.set_alignment(Alignment.CENTER) + + def set_content(self, content): + self.__content = content + + def set_input_dialogue(self, yes): + self.__input_dialog = yes + + def prepare(self): + self.__create_layout(self.__title_txt) + + def _handle_key_event(self, key): + if key == 27: + self.__close() + return + super()._handle_key_event(key) + + + def _ok_onclick(self): + self.__status = Dialogue.Yes + self.__close() + + def _no_onclick(self): + self.__status = Dialogue.No + self.__close() + + def _abort_onclick(self): + self.__status = Dialogue.Abort + self.__close() + + def __create_layout(self, title): + panel = tui.TuiPanel(self, "panel") + layout = tui.FlexLinearLayout(self, "layout", "*,3") + btn_grp = create_buttons(self, self.__btns) + t = create_title(self, title) + content = self.__create_content() + + self.__title = t + self.__layout = layout + self.__panel = panel + + panel._dyn_size.set(self._dyn_size) + panel._local_pos.set(self._local_pos) + panel.set_alignment(self._align) + panel.drop_shadow(1, 2) + panel.border(True) + + layout.orientation(tui.FlexLinearLayout.PORTRAIT) + layout.set_size("*", "*") + layout.set_padding(4, 1, 1, 1) + + t.set_alignment(Alignment.CENTER | Alignment.TOP) + + layout.set_cell(0, content) + layout.set_cell(1, btn_grp) + + panel.add(t) + panel.add(layout) + + self.set_root(panel) + + def __create_content(self): + text = None + if isinstance(self.__content, str): + text = tui.TuiTextBlock(self, "tb") + text.set_size("0.6*", "0.5*") + text.set_alignment(Alignment.CENTER) + text.set_text(self.__content) + elif self.__content is not None: + return self.__content + + if not self.__input_dialog: + self.set_size(h = "20") + return text + + tb = tui.TuiTextBox(self, "input") + tb.set_size("0.5*", "3") + tb.set_alignment(Alignment.CENTER) + + if text: + layout = tui.FlexLinearLayout(self, "layout", "*,5") + layout.orientation(tui.FlexLinearLayout.PORTRAIT) + layout.set_size("*", "*") + layout.set_cell(0, text) + layout.set_cell(1, tb) + else: + layout = tb + self.set_size(h = "10") + + self.set_curser_mode(1) + + self._textbox = tb + + return layout + + def __close(self): + self.session().pop_context() + + def status(self): + return self.__status + + def show(self, title=None): + if title: + self.__title.set_text(title) + self.session().push_context(self) + + +def show_dialog(session, title, text): + dia = Dialogue(session, title=title, content=text, no_btn=None) + dia.show() \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/integration/libtui.py b/lunaix-os/scripts/build-tools/integration/libtui.py new file mode 100644 index 0000000..30e28f6 --- /dev/null +++ b/lunaix-os/scripts/build-tools/integration/libtui.py @@ -0,0 +1,1267 @@ +# +# libtui - TUI framework using ncurses +# (c) 2024 Lunaixsky +# +# I sware, this is the last time I ever touch +# any sort of the GUI messes. +# + +import curses +import re +import curses.panel as cpanel +import curses.textpad as textpad +import textwrap + +def __invoke_fn(obj, fn, *args): + try: + fn(*args) + except: + _id = obj._id if obj else "" + raise Exception(_id, str(fn), args) + +def resize_safe(obj, co, y, x): + __invoke_fn(obj, co.resize, y, x) + +def move_safe(obj, co, y, x): + __invoke_fn(obj, co.move, y, x) + +def addstr_safe(obj, co, y, x, str, *args): + __invoke_fn(obj, co.addstr, y, x, str, *args) + +class _TuiColor: + def __init__(self, v) -> None: + self.__v = v + def __int__(self): + return self.__v + def bright(self): + return self.__v + 8 + +class TuiColor: + black = _TuiColor(curses.COLOR_BLACK) + red = _TuiColor(curses.COLOR_RED) + green = _TuiColor(curses.COLOR_GREEN) + yellow = _TuiColor(curses.COLOR_YELLOW) + blue = _TuiColor(curses.COLOR_BLUE) + magenta = _TuiColor(curses.COLOR_MAGENTA) + cyan = _TuiColor(curses.COLOR_CYAN) + white = _TuiColor(curses.COLOR_WHITE) + +class Alignment: + LEFT = 0b000001 + RIGHT = 0b000010 + CENTER = 0b000100 + TOP = 0b001000 + BOT = 0b010000 + ABS = 0b000000 + REL = 0b100000 + +class ColorScope: + WIN = 1 + PANEL = 2 + TEXT = 3 + TEXT_HI = 4 + SHADOW = 5 + SELECT = 6 + HINT = 7 + BOX = 8 + +class EventType: + E_KEY = 0 + E_REDRAW = 1 + E_QUIT = 2 + E_TASK = 3 + E_CHFOCUS = 4 + E_M_FOCUS = 0b10000000 + + def focused_only(t): + return (t & EventType.E_M_FOCUS) + + def value(t): + return t & ~EventType.E_M_FOCUS + + def key_press(t): + return (t & ~EventType.E_M_FOCUS) == EventType.E_KEY + +class Matchers: + RelSize = re.compile(r"(?P[0-9]+(?:\.[0-9]+)?)?\*(?P[+-][0-9]+)?") + +class BoundExpression: + def __init__(self, expr = None): + self._mult = 0 + self._add = 0 + + if expr: + self.update(expr) + + def set_pair(self, mult, add): + self._mult = mult + self._add = add + + def set(self, expr): + self._mult = expr._mult + self._add = expr._add + + def update(self, expr): + if isinstance(expr, int): + m = None + else: + m = Matchers.RelSize.match(expr) + + if m: + g = m.groupdict() + mult = 1 if not g["mult"] else float(g["mult"]) + add = 0 if not g["add"] else int(g["add"]) + self._mult = mult + self._add = add + else: + self.set_pair(0, int(expr)) + + def calc(self, ref_val): + return int(self._mult * ref_val + self._add) + + def absolute(self): + return self._mult == 0 + + def nullity(self): + return self._mult == 0 and self._add == 0 + + def scale_mult(self, scalar): + self._mult *= scalar + return self + + @staticmethod + def normalise(*exprs): + v = BoundExpression() + for e in exprs: + v += e + return [e.scale_mult(1 / v._mult) for e in exprs] + + def __add__(self, b): + v = BoundExpression() + v.set(self) + v._mult += b._mult + v._add += b._add + return v + + def __sub__(self, b): + v = BoundExpression() + v.set(self) + v._mult -= b._mult + v._add -= b._add + return v + + def __iadd__(self, b): + self._mult += b._mult + self._add += b._add + return self + + def __isub__(self, b): + self._mult -= b._mult + self._add -= b._add + return self + + def __rmul__(self, scalar): + v = BoundExpression() + v.set(self) + v._mult *= scalar + v._add *= scalar + return v + + def __truediv__(self, scalar): + v = BoundExpression() + v.set(self) + v._mult /= float(scalar) + v._add /= scalar + return v + +class DynamicBound: + def __init__(self): + self.__x = BoundExpression() + self.__y = BoundExpression() + + def dyn_x(self): + return self.__x + + def dyn_y(self): + return self.__y + + def resolve(self, ref_w, ref_h): + return (self.__y.calc(ref_h), self.__x.calc(ref_w)) + + def set(self, dyn_bound): + self.__x.set(dyn_bound.dyn_x()) + self.__y.set(dyn_bound.dyn_y()) + +class Bound: + def __init__(self) -> None: + self.__x = 0 + self.__y = 0 + + def shrinkX(self, dx): + self.__x -= dx + def shrinkY(self, dy): + self.__y -= dy + + def growX(self, dx): + self.__x += dx + def growY(self, dy): + self.__y += dy + + def resetX(self, x): + self.__x = x + def resetY(self, y): + self.__y = y + + def update(self, dynsz, ref_bound): + y, x = dynsz.resolve(ref_bound.x(), ref_bound.y()) + self.__x = x + self.__y = y + + def reset(self, x, y): + self.__x, self.__y = x, y + + def x(self): + return self.__x + + def y(self): + return self.__y + + def yx(self, scale = 1): + return int(self.__y * scale), int(self.__x * scale) + +class TuiStackWindow: + def __init__(self, obj) -> None: + self.__obj = obj + self.__win = curses.newwin(0, 0) + self.__pan = cpanel.new_panel(self.__win) + self.__pan.hide() + + def resize(self, h, w): + resize_safe(self.__obj, self.__win, h, w) + + def relocate(self, y, x): + move_safe(self.__obj, self.__pan, y, x) + + def set_geometric(self, h, w, y, x): + resize_safe(self.__obj, self.__win, h, w) + move_safe(self.__obj, self.__pan, y, x) + + def set_background(self, color_scope): + self.__win.bkgd(' ', curses.color_pair(color_scope)) + + def show(self): + self.__pan.show() + + def hide(self): + self.__pan.hide() + + def send_back(self): + self.__pan.bottom() + + def send_front(self): + self.__pan.top() + + def window(self): + return self.__win + +class SpatialObject: + def __init__(self) -> None: + self._local_pos = DynamicBound() + self._pos = Bound() + self._dyn_size = DynamicBound() + self._size = Bound() + self._margin = (0, 0, 0, 0) + self._padding = (0, 0, 0, 0) + self._align = Alignment.TOP | Alignment.LEFT + + def set_local_pos(self, x, y): + self._local_pos.dyn_x().update(x) + self._local_pos.dyn_y().update(y) + + def set_alignment(self, align): + self._align = align + + def set_size(self, w = None, h = None): + if w: + self._dyn_size.dyn_x().update(w) + if h: + self._dyn_size.dyn_y().update(h) + + def set_margin(self, top, right, bottom, left): + self._margin = (top, right, bottom, left) + + def set_padding(self, top, right, bottom, left): + self._padding = (top, right, bottom, left) + + def reset(self): + self._pos.reset(0, 0) + self._size.reset(0, 0) + + def deduce_spatial(self, constrain): + self.reset() + self.__satisfy_bound(constrain) + self.__satisfy_alignment(constrain) + self.__satisfy_margin(constrain) + self.__satisfy_padding(constrain) + + self.__to_corner_pos(constrain) + + def __satisfy_alignment(self, constrain): + local_pos = self._local_pos + cbound = constrain._size + size = self._size + + cy, cx = cbound.yx() + ry, rx = local_pos.resolve(cx, cy) + ay, ax = size.yx(0.5) + + if self._align & Alignment.CENTER: + ax = cx // 2 + ay = cy // 2 + + if self._align & Alignment.BOT: + ay = min(cy - ay, cy - 1) + ry = -ry + elif self._align & Alignment.TOP: + ay = size.y() // 2 + + if self._align & Alignment.RIGHT: + ax = cx - ax + rx = -rx + elif self._align & Alignment.LEFT: + ax = size.x() // 2 + + self._pos.reset(ax + rx, ay + ry) + + def __satisfy_margin(self, constrain): + tm, lm, bm, rm = self._margin + + self._pos.growX(rm - lm) + self._pos.growY(bm - tm) + + def __satisfy_padding(self, constrain): + csize = constrain._size + ch, cw = csize.yx() + h, w = self._size.yx(0.5) + y, x = self._pos.yx() + + tp, lp, bp, rp = self._padding + + if not (tp or lp or bp or rp): + return + + dtp = min(y - h, tp) - tp + dbp = min(ch - (y + h), bp) - bp + + dlp = min(x - w, lp) - lp + drp = min(cw - (x + w), rp) - rp + + self._size.growX(drp + dlp) + self._size.growY(dtp + dbp) + + def __satisfy_bound(self, constrain): + self._size.update(self._dyn_size, constrain._size) + + def __to_corner_pos(self, constrain): + h, w = self._size.yx(0.5) + g_pos = constrain._pos + + self._pos.shrinkX(w) + self._pos.shrinkY(h) + + self._pos.growX(g_pos.x()) + self._pos.growY(g_pos.y()) + + +class TuiObject(SpatialObject): + def __init__(self, context, id): + super().__init__() + self._id = id + self._context = context + self._parent = None + self._visible = True + self._focused = False + + def set_parent(self, parent): + self._parent = parent + + def canvas(self): + if self._parent: + return self._parent.canvas() + return (self, self._context.window()) + + def context(self): + return self._context + + def session(self): + return self._context.session() + + def on_create(self): + pass + + def on_destory(self): + pass + + def on_quit(self): + pass + + def on_layout(self): + if self._parent: + self.deduce_spatial(self._parent) + + def on_draw(self): + pass + + def on_event(self, ev_type, ev_arg): + pass + + def on_focused(self): + self._focused = True + + def on_focus_lost(self): + self._focused = False + + def set_visbility(self, visible): + self._visible = visible + + def do_draw(self): + if self._visible: + self.on_draw() + + def do_layout(self): + self.on_layout() + +class TuiWidget(TuiObject): + def __init__(self, context, id): + super().__init__(context, id) + + def on_layout(self): + super().on_layout() + + co, _ = self.canvas() + + y, x = co._pos.yx() + self._pos.shrinkX(x) + self._pos.shrinkY(y) + +class TuiContainerObject(TuiObject): + def __init__(self, context, id): + super().__init__(context, id) + self._children = [] + + def add(self, child): + child.set_parent(self) + self._children.append(child) + + def children(self): + return self._children + + def on_create(self): + super().on_create() + for child in self._children: + child.on_create() + + def on_destory(self): + super().on_destory() + for child in self._children: + child.on_destory() + + def on_quit(self): + super().on_quit() + for child in self._children: + child.on_quit() + + def on_layout(self): + super().on_layout() + for child in self._children: + child.do_layout() + + def on_draw(self): + super().on_draw() + for child in self._children: + child.do_draw() + + def on_event(self, ev_type, ev_arg): + super().on_event(ev_type, ev_arg) + for child in self._children: + child.on_event(ev_type, ev_arg) + + +class TuiScrollable(TuiObject): + def __init__(self, context, id): + super().__init__(context, id) + self.__spos = Bound() + + self.__pad = curses.newpad(1, 1) + self.__pad.bkgd(' ', curses.color_pair(ColorScope.PANEL)) + self.__pad_panel = cpanel.new_panel(self.__pad) + self.__content = None + + def canvas(self): + return (self, self.__pad) + + def set_content(self, content): + self.__content = content + self.__content.set_parent(self) + + def set_scrollY(self, y): + self.__spos.resetY(y) + + def set_scrollX(self, x): + self.__spos.resetX(x) + + def reached_last(self): + off = self.__spos.y() + self._size.y() + return off >= self.__content._size.y() + + def reached_top(self): + return self.__spos.y() < self._size.y() + + def on_layout(self): + super().on_layout() + + if not self.__content: + return + + self.__content.on_layout() + + h, w = self._size.yx() + ch, cw = self.__content._size.yx() + sh, sw = max(ch, h), max(cw, w) + + self.__spos.resetX(min(self.__spos.x(), max(cw, w) - w)) + self.__spos.resetY(min(self.__spos.y(), max(ch, h) - h)) + + resize_safe(self, self.__pad, sh, sw) + + def on_draw(self): + if not self.__content: + return + + self.__pad.erase() + + self.__pad_panel.top() + self.__content.on_draw() + + wminy, wminx = self._pos.yx() + wmaxy, wmaxx = self._size.yx() + wmaxy, wmaxx = wmaxy + wminy, wmaxx + wminx + self.__pad.touchwin() + self.__pad.refresh(*self.__spos.yx(), + wminy, wminx, wmaxy - 1, wmaxx - 1) + + +class Layout(TuiContainerObject): + class Cell(TuiObject): + def __init__(self, context): + super().__init__(context, "cell") + self.__obj = None + + def set_obj(self, obj): + self.__obj = obj + self.__obj.set_parent(self) + + def on_create(self): + if self.__obj: + self.__obj.on_create() + + def on_destory(self): + if self.__obj: + self.__obj.on_destory() + + def on_quit(self): + if self.__obj: + self.__obj.on_quit() + + def on_layout(self): + super().on_layout() + if self.__obj: + self.__obj.do_layout() + + def on_draw(self): + if self.__obj: + self.__obj.do_draw() + + def on_event(self, ev_type, ev_arg): + if self.__obj: + self.__obj.on_event(ev_type, ev_arg) + + def __init__(self, context, id, ratios): + super().__init__(context, id) + + rs = [BoundExpression(r) for r in ratios.split(',')] + self._rs = BoundExpression.normalise(*rs) + + for _ in range(len(self._rs)): + cell = Layout.Cell(self._context) + super().add(cell) + + self._adjust_to_fit() + + def _adjust_to_fit(self): + pass + + def add(self, child): + raise RuntimeError("invalid operation") + + def set_cell(self, i, obj): + if i > len(self._children): + raise ValueError(f"cell #{i} out of bound") + + self._children[i].set_obj(obj) + + +class FlexLinearLayout(Layout): + LANDSCAPE = 0 + PORTRAIT = 1 + def __init__(self, context, id, ratios): + self.__horizontal = False + + super().__init__(context, id, ratios) + + def orientation(self, orient): + self.__horizontal = orient == FlexLinearLayout.LANDSCAPE + + def on_layout(self): + self.__apply_ratio() + super().on_layout() + + def _adjust_to_fit(self): + sum_abs = BoundExpression() + i = 0 + for r in self._rs: + if r.absolute(): + sum_abs += r + else: + i += 1 + + sum_abs /= i + for i, r in enumerate(self._rs): + if not r.absolute(): + self._rs[i] -= sum_abs + + def __apply_ratio(self): + if self.__horizontal: + self.__adjust_horizontal() + else: + self.__adjust_vertical() + + def __adjust_horizontal(self): + acc = BoundExpression() + for r, cell in zip(self._rs, self.children()): + cell._dyn_size.dyn_y().set_pair(1, 0) + cell._dyn_size.dyn_x().set(r) + + cell.set_alignment(Alignment.LEFT) + cell._local_pos.dyn_y().set_pair(0, 0) + cell._local_pos.dyn_x().set(acc) + + acc += r + + def __adjust_vertical(self): + acc = BoundExpression() + for r, cell in zip(self._rs, self.children()): + cell._dyn_size.dyn_x().set_pair(1, 0) + cell._dyn_size.dyn_y().set(r) + + cell.set_alignment(Alignment.TOP | Alignment.CENTER) + cell._local_pos.dyn_x().set_pair(0, 0) + cell._local_pos.dyn_y().set(acc) + + acc += r + + +class TuiPanel(TuiContainerObject): + def __init__(self, context, id): + super().__init__(context, id) + + self.__use_border = False + self.__use_shadow = False + self.__shadow_param = (0, 0) + + self.__swin = TuiStackWindow(self) + self.__shad = TuiStackWindow(self) + + self.__swin.set_background(ColorScope.PANEL) + self.__shad.set_background(ColorScope.SHADOW) + + def canvas(self): + return (self, self.__swin.window()) + + def drop_shadow(self, off_y, off_x): + self.__shadow_param = (off_y, off_x) + self.__use_shadow = not (off_y == off_x and off_y == 0) + + def border(self, _b): + self.__use_border = _b + + def bkgd_override(self, scope): + self.__swin.set_background(scope) + + def on_layout(self): + super().on_layout() + + self.__swin.hide() + + h, w = self._size.y(), self._size.x() + y, x = self._pos.y(), self._pos.x() + self.__swin.set_geometric(h, w, y, x) + + if self.__use_shadow: + sy, sx = self.__shadow_param + self.__shad.set_geometric(h, w, y + sy, x + sx) + + def on_destory(self): + super().on_destory() + self.__swin.hide() + self.__shad.hide() + + def on_draw(self): + win = self.__swin.window() + win.erase() + + if self.__use_border: + win.border() + + if self.__use_shadow: + self.__shad.show() + else: + self.__shad.hide() + + self.__swin.show() + self.__swin.send_front() + + super().on_draw() + + win.touchwin() + +class TuiLabel(TuiWidget): + def __init__(self, context, id): + super().__init__(context, id) + self._text = "TuiLabel" + self._wrapped = [] + + self.__auto_fit = True + self.__trunc = False + self.__dopad = False + self.__highlight = False + self.__color_scope = -1 + + def __try_fit_text(self, txt): + if self.__auto_fit: + self._dyn_size.dyn_x().set_pair(0, len(txt)) + self._dyn_size.dyn_y().set_pair(0, 1) + + def __pad_text(self): + for i, t in enumerate(self._wrapped): + self._wrapped[i] = str.rjust(t, self._size.x()) + + def set_text(self, text): + self._text = text + self.__try_fit_text(text) + + def override_color(self, color = -1): + self.__color_scope = color + + def auto_fit(self, _b): + self.__auto_fit = _b + + def truncate(self, _b): + self.__trunc = _b + + def hightlight(self, _b): + self.__highlight = _b + + def pad_around(self, _b): + self.__dopad = _b + + def on_layout(self): + txt = self._text + if self.__dopad: + txt = f" {txt} " + self.__try_fit_text(txt) + + super().on_layout() + + if len(txt) <= self._size.x(): + self._wrapped = [txt] + self.__pad_text() + return + + if not self.__trunc: + txt = txt[:self._size.x() - 1] + self._wrapped = [txt] + self.__pad_text() + return + + self._wrapped = textwrap.wrap(txt, self._size.x()) + self.__pad_text() + + def on_draw(self): + _, win = self.canvas() + y, x = self._pos.yx() + + if self.__color_scope != -1: + color = curses.color_pair(self.__color_scope) + elif self.__highlight: + color = curses.color_pair(ColorScope.TEXT_HI) + else: + color = curses.color_pair(ColorScope.TEXT) + + for i, t in enumerate(self._wrapped): + addstr_safe(self, win, y + i, x, t, color) + + +class TuiTextBlock(TuiWidget): + def __init__(self, context, id): + super().__init__(context, id) + self.__lines = [] + self.__wrapped = [] + self.__fit_to_height = False + + def set_text(self, text): + text = textwrap.dedent(text) + self.__lines = text.split('\n') + if self.__fit_to_height: + self._dyn_size.dyn_y().set_pair(0, 0) + + def height_auto_fit(self, yes): + self.__fit_to_height = yes + + def on_layout(self): + super().on_layout() + + self.__wrapped.clear() + for t in self.__lines: + if not t: + self.__wrapped.append(t) + continue + wrap = textwrap.wrap(t, self._size.x()) + self.__wrapped += wrap + + if self._dyn_size.dyn_y().nullity(): + h = len(self.__wrapped) + self._dyn_size.dyn_y().set_pair(0, h) + + # redo layouting + super().on_layout() + + def on_draw(self): + _, win = self.canvas() + y, x = self._pos.yx() + + color = curses.color_pair(ColorScope.TEXT) + for i, t in enumerate(self.__wrapped): + addstr_safe(self, win, y + i, x, t, color) + + +class TuiTextBox(TuiWidget): + def __init__(self, context, id): + super().__init__(context, id) + self.__box = TuiStackWindow(self) + self.__box.set_background(ColorScope.PANEL) + self.__textb = textpad.Textbox(self.__box.window(), True) + self.__textb.stripspaces = True + self.__str = "" + self.__scheduled_edit = False + + self._context.focus_group().register(self, 0) + + def __validate(self, x): + if x == 10 or x == 9: + return 7 + return x + + def on_layout(self): + super().on_layout() + + co, _ = self.canvas() + h, w = self._size.yx() + y, x = self._pos.yx() + cy, cx = co._pos.yx() + y, x = y + cy, x + cx + + self.__box.hide() + self.__box.set_geometric(1, w - 1, y + h // 2, x + 1) + + def on_draw(self): + self.__box.show() + self.__box.send_front() + + _, cwin = self.canvas() + + h, w = self._size.yx() + y, x = self._pos.yx() + textpad.rectangle(cwin, y, x, y + h - 1, x+w) + + win = self.__box.window() + win.touchwin() + + def __edit(self): + self.__str = self.__textb.edit(lambda x: self.__validate(x)) + self.session().schedule(EventType.E_CHFOCUS) + self.__scheduled_edit = False + + def get_text(self): + return self.__str + + def on_focused(self): + self.__box.set_background(ColorScope.BOX) + if not self.__scheduled_edit: + # edit will block, defer to next update cycle + self.session().schedule_task(self.__edit) + self.__scheduled_edit = True + + def on_focus_lost(self): + self.__box.set_background(ColorScope.PANEL) + + +class SimpleList(TuiWidget): + class Item: + def __init__(self) -> None: + pass + def get_text(self): + return "list_item" + def on_selected(self): + pass + def on_key_pressed(self, key): + pass + + def __init__(self, context, id): + super().__init__(context, id) + self.__items = [] + self.__selected = 0 + self.__on_sel_confirm_cb = None + self.__on_sel_change_cb = None + + self._context.focus_group().register(self) + + def set_onselected_cb(self, cb): + self.__on_sel_confirm_cb = cb + + def set_onselection_change_cb(self, cb): + self.__on_sel_change_cb = cb + + def count(self): + return len(self.__items) + + def index(self): + return self.__selected + + def add_item(self, item): + self.__items.append(item) + + def clear(self): + self.__items.clear() + + def on_layout(self): + super().on_layout() + self.__selected = min(self.__selected, len(self.__items)) + self._size.resetY(len(self.__items) + 1) + + def on_draw(self): + _, win = self.canvas() + w = self._size.x() + + for i, item in enumerate(self.__items): + color = curses.color_pair(ColorScope.TEXT) + if i == self.__selected: + if self._focused: + color = curses.color_pair(ColorScope.SELECT) + else: + color = curses.color_pair(ColorScope.BOX) + + txt = str.ljust(item.get_text(), w) + txt = txt[:w] + addstr_safe(self, win, i, 0, txt, color) + + def on_event(self, ev_type, ev_arg): + if not EventType.key_press(ev_type): + return + + if len(self.__items) == 0: + return + + sel = self.__items[self.__selected] + + if ev_arg == 10: + sel.on_selected() + + if self.__on_sel_confirm_cb: + self.__on_sel_confirm_cb(self, self.__selected, sel) + return + + sel.on_key_pressed(ev_arg) + + if (ev_arg != curses.KEY_DOWN and + ev_arg != curses.KEY_UP): + return + + prev = self.__selected + if ev_arg == curses.KEY_DOWN: + self.__selected += 1 + else: + self.__selected -= 1 + + self.__selected = max(self.__selected, 0) + self.__selected = self.__selected % len(self.__items) + + if self.__on_sel_change_cb: + self.__on_sel_change_cb(self, prev, self.__selected) + + +class TuiButton(TuiLabel): + def __init__(self, context, id): + super().__init__(context, id) + self.__onclick = None + + context.focus_group().register(self) + + def set_text(self, text): + return super().set_text(f"<{text}>") + + def set_click_callback(self, cb): + self.__onclick = cb + + def hightlight(self, _b): + raise NotImplemented() + + def on_draw(self): + _, win = self.canvas() + y, x = self._pos.yx() + + if self._focused: + color = curses.color_pair(ColorScope.SELECT) + else: + color = curses.color_pair(ColorScope.TEXT) + + addstr_safe(self, win, y, x, self._wrapped[0], color) + + def on_event(self, ev_type, ev_arg): + if not EventType.focused_only(ev_type): + return + if not EventType.key_press(ev_type): + return + + if ev_arg == ord('\n') and self.__onclick: + self.__onclick(self) + + +class TuiSession: + def __init__(self) -> None: + self.stdsc = curses.initscr() + curses.start_color() + + curses.noecho() + curses.cbreak() + + self.__context_stack = [] + self.__sched_events = [] + + ws = self.window_size() + self.__win = curses.newwin(*ws) + self.__winbg = curses.newwin(*ws) + self.__panbg = cpanel.new_panel(self.__winbg) + + self.__winbg.bkgd(' ', curses.color_pair(ColorScope.WIN)) + + self.__win.timeout(50) + self.__win.keypad(True) + + def window_size(self): + return self.stdsc.getmaxyx() + + def set_color(self, scope, fg, bg): + curses.init_pair(scope, int(fg), int(bg)) + + def schedule_redraw(self): + self.schedule(EventType.E_REDRAW) + + def schedule_task(self, task): + self.schedule(EventType.E_REDRAW) + self.schedule(EventType.E_TASK, task) + + def schedule(self, event, arg = None): + if len(self.__sched_events) > 0: + if self.__sched_events[-1] == event: + return + + self.__sched_events.append((event, arg)) + + def push_context(self, tuictx): + tuictx.prepare() + self.__context_stack.append(tuictx) + self.schedule(EventType.E_REDRAW) + + curses.curs_set(self.active().curser_mode()) + + def pop_context(self): + if len(self.__context_stack) == 1: + return + + ctx = self.__context_stack.pop() + ctx.on_destory() + self.schedule(EventType.E_REDRAW) + + curses.curs_set(self.active().curser_mode()) + + def active(self): + return self.__context_stack[-1] + + def event_loop(self): + if len(self.__context_stack) == 0: + raise RuntimeError("no tui context to display") + + while True: + key = self.__win.getch() + if key != -1: + self.schedule(EventType.E_KEY, key) + + if len(self.__sched_events) == 0: + continue + + evt, arg = self.__sched_events.pop(0) + if evt == EventType.E_REDRAW: + self.__redraw() + elif evt == EventType.E_QUIT: + self.__notify_quit() + break + + self.active().dispatch_event(evt, arg) + + def __notify_quit(self): + while len(self.__context_stack) == 0: + ctx = self.__context_stack.pop() + ctx.dispatch_event(EventType.E_QUIT, None) + + def __redraw(self): + self.stdsc.erase() + self.__win.erase() + + self.active().redraw(self.__win) + + self.__panbg.bottom() + self.__win.touchwin() + self.__winbg.touchwin() + + self.__win.refresh() + self.__winbg.refresh() + + cpanel.update_panels() + curses.doupdate() + +class TuiFocusGroup: + def __init__(self) -> None: + self.__grp = [] + self.__id = 0 + self.__sel = 0 + self.__focused = None + + def register(self, tui_obj, pos=-1): + if pos == -1: + self.__grp.append((self.__id, tui_obj)) + else: + self.__grp.insert(pos, (self.__id, tui_obj)) + self.__id += 1 + return self.__id - 1 + + def navigate_focus(self, dir = 1): + self.__sel = (self.__sel + dir) % len(self.__grp) + f = None if not len(self.__grp) else self.__grp[self.__sel][1] + if f and f != self.__focused: + if self.__focused: + self.__focused.on_focus_lost() + f.on_focused() + self.__focused = f + + def focused(self): + return self.__focused + +class TuiContext(TuiObject): + def __init__(self, session: TuiSession): + super().__init__(self, "context") + self.__root = None + self.__sobj = None + self.__session = session + + self.__win = None + + y, x = self.__session.window_size() + self._size.reset(x, y) + self.set_parent(None) + + self.__focus_group = TuiFocusGroup() + self.__curser_mode = 0 + + def set_curser_mode(self, mode): + self.__curser_mode = mode + + def curser_mode(self): + return self.__curser_mode + + def set_root(self, root): + self.__root = root + self.__root.set_parent(self) + + def set_state(self, obj): + self.__sobj = obj + + def state(self): + return self.__sobj + + def prepare(self): + self.__root.on_create() + + def on_destory(self): + self.__root.on_destory() + + def canvas(self): + return (self, self.__win) + + def session(self): + return self.__session + + def dispatch_event(self, evt, arg): + if evt == EventType.E_REDRAW: + self.__focus_group.navigate_focus(0) + elif evt == EventType.E_CHFOCUS: + self.__focus_group.navigate_focus(1) + self.__session.schedule(EventType.E_REDRAW) + return + elif evt == EventType.E_TASK: + arg() + elif evt == EventType.E_QUIT: + self.__root.on_quit() + elif evt == EventType.E_KEY: + self._handle_key_event(arg) + else: + self.__root.on_event(evt, arg) + + focused = self.__focus_group.focused() + if focused: + focused.on_event(evt | EventType.E_M_FOCUS, arg) + + def redraw(self, win): + self.__win = win + self.on_layout() + self.on_draw() + + def on_layout(self): + self.__root.on_layout() + + def on_draw(self): + self.__root.on_draw() + + def focus_group(self): + return self.__focus_group + + def _handle_key_event(self, key): + if key == ord('\t') or key == curses.KEY_RIGHT: + self.__focus_group.navigate_focus() + elif key == curses.KEY_LEFT: + self.__focus_group.navigate_focus(-1) + else: + self.__root.on_event(EventType.E_KEY, key) + + if self.__focus_group.focused(): + self.__session.schedule(EventType.E_REDRAW) \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/integration/lunamenu.py b/lunaix-os/scripts/build-tools/integration/lunamenu.py new file mode 100644 index 0000000..c8d0730 --- /dev/null +++ b/lunaix-os/scripts/build-tools/integration/lunamenu.py @@ -0,0 +1,457 @@ +from lcfg.api import RenderContext +from lcfg.types import ( + PrimitiveType, + MultipleChoiceType +) + +import subprocess +import curses +import textwrap +import integration.libtui as tui +import integration.libmenu as menu + +from integration.libtui import ColorScope, TuiColor, Alignment, EventType +from integration.libmenu import Dialogue, ListView, show_dialog + +__git_repo_info = None +__tainted = False + +def mark_tainted(): + global __tainted + __tainted = True + +def unmark_tainted(): + global __tainted + __tainted = False + +def get_git_hash(): + try: + hsh = subprocess.check_output([ + 'git', 'rev-parse', '--short', 'HEAD' + ]).decode('ascii').strip() + branch = subprocess.check_output([ + 'git', 'branch', '--show-current' + ]).decode('ascii').strip() + return f"{branch}@{hsh}" + except: + return None + +def get_git_info(): + global __git_repo_info + return __git_repo_info + +def do_save(session): + show_dialog(session, "Notice", "Configuration saved") + unmark_tainted() + +def do_exit(session): + global __tainted + if not __tainted: + session.schedule(EventType.E_QUIT) + return + + quit = QuitDialogue(session) + quit.show() + +class MainMenuContext(tui.TuiContext): + def __init__(self, session, view_title): + super().__init__(session) + + self.__title = view_title + + self.__prepare_layout() + + def __prepare_layout(self): + + root = tui.TuiPanel(self, "main_panel") + root.set_size("*-10", "*-5") + root.set_alignment(Alignment.CENTER) + root.drop_shadow(1, 2) + root.border(True) + + layout = tui.FlexLinearLayout(self, "layout", "6,*,5") + layout.orientation(tui.FlexLinearLayout.PORTRAIT) + layout.set_size("*", "*") + layout.set_padding(1, 1, 1, 1) + + listv = ListView(self, "list_view") + listv.set_size("70", "*") + listv.set_alignment(Alignment.CENTER) + + hint = tui.TuiTextBlock(self, "hint") + hint.set_size(w="*") + hint.set_local_pos("0.1*", 0) + hint.height_auto_fit(True) + hint.set_text( + "Use // to select from list\n" + "Use // to change focus\n" + ": show help (if applicable), : back previous level" + ) + hint.set_alignment(Alignment.CENTER | Alignment.LEFT) + + suffix = "" + btns_defs = [ + { + "text": "Save", + "onclick": lambda x: do_save(self.session()) + }, + { + "text": "Exit", + "onclick": lambda x: do_exit(self.session()) + } + ] + + repo_info = get_git_info() + + if self.__title: + suffix += f" - {self.__title}" + btns_defs.insert(1, { + "text": "Back", + "onclick": lambda x: self.session().pop_context() + }) + + btns = menu.create_buttons(self, btns_defs, sizes="50,*") + + layout.set_cell(0, hint) + layout.set_cell(1, listv) + layout.set_cell(2, btns) + + t = menu.create_title(self, "Lunaix Kernel Configuration" + suffix) + t2 = menu.create_title(self, repo_info) + t2.set_alignment(Alignment.BOT | Alignment.RIGHT) + + root.add(t) + root.add(t2) + root.add(layout) + + self.set_root(root) + self.__menu_list = listv + + def menu(self): + return self.__menu_list + + def _handle_key_event(self, key): + if key == curses.KEY_BACKSPACE or key == 8: + self.session().pop_context() + elif key == 27: + do_exit(self.session()) + return + + super()._handle_key_event(key) + +class ItemType: + Expandable = 0 + Switch = 1 + Choice = 2 + Other = 3 + def __init__(self, node, expandable) -> None: + self.__node = node + + if expandable: + self.__type = ItemType.Expandable + return + + self.__type = ItemType.Other + self.__primitive = False + type_provider = node.get_type() + + if isinstance(type_provider, PrimitiveType): + self.__primitive = True + + if isinstance(type_provider, MultipleChoiceType): + self.__type = ItemType.Choice + elif type_provider._type == bool: + self.__type = ItemType.Switch + + self.__provider = type_provider + + def get_formatter(self): + if self.__type == ItemType.Expandable: + return "%s ---->" + + v = self.__node.get_value() + + if self.is_switch(): + mark = "*" if v else " " + return f"[{mark}] %s" + + if self.is_choice() or isinstance(v, int): + return f"({v}) %s" + + return "%s" + + def expandable(self): + return self.__type == ItemType.Expandable + + def is_switch(self): + return self.__type == ItemType.Switch + + def is_choice(self): + return self.__type == ItemType.Choice + + def read_only(self): + return not self.expandable() and self.__node.read_only() + + def provider(self): + return self.__provider + +class MultiChoiceItem(tui.SimpleList.Item): + def __init__(self, value, get_val) -> None: + super().__init__() + self.__val = value + self.__getval = get_val + + def get_text(self): + marker = "*" if self.__getval() == self.__val else " " + return f" ({marker}) {self.__val}" + + def value(self): + return self.__val + +class LunaConfigItem(tui.SimpleList.Item): + def __init__(self, session, node, name, expand_cb = None): + super().__init__() + self.__node = node + self.__type = ItemType(node, expand_cb is not None) + self.__name = name + self.__expand_cb = expand_cb + self.__session = session + + def get_text(self): + fmt = self.__type.get_formatter() + if self.__type.read_only(): + fmt += "*" + return f" {fmt%(self.__name)}" + + def on_selected(self): + if self.__type.read_only(): + show_dialog( + self.__session, + f"Read-only: \"{self.__name}\"", + f"Value defined in this field:\n\n'{self.__node.get_value()}'") + return + + if self.__type.expandable(): + view = CollectionView(self.__session, self.__node, self.__name) + view.set_reloader(self.__expand_cb) + view.show() + return + + if self.__type.is_switch(): + v = self.__node.get_value() + self.change_value(not v) + else: + dia = ValueEditDialogue(self.__session, self) + dia.show() + + self.__session.schedule(EventType.E_REDRAW) + + def name(self): + return self.__name + + def node(self): + return self.__node + + def type(self): + return self.__type + + def change_value(self, val): + try: + self.__node.set_value(val) + except: + show_dialog( + self.__session, "Invalid value", + f"Value: '{val}' does not match the type") + return False + + mark_tainted() + CollectionView.reload_active(self.__session) + return True + + def on_key_pressed(self, key): + if (key & ~0b100000) != ord('H'): + return + + h = self.__node.help_prompt() + if not self.__type.expandable(): + h = "\n".join([ + h, "", "--------", + "Supported Values:", + textwrap.indent(str(self.__type.provider()), " ") + ]) + + dia = HelpDialogue(self.__session, f"Help: '{self.__name}'", h) + dia.show() + +class CollectionView(RenderContext): + def __init__(self, session, node, label = None) -> None: + super().__init__() + + ctx = MainMenuContext(session, label) + self.__node = node + self.__tui_ctx = ctx + self.__listv = ctx.menu() + self.__session = session + self.__reloader = lambda x: node.render(x) + + ctx.set_state(self) + + def set_reloader(self, cb): + self.__reloader = cb + + def add_expandable(self, label, node, on_expand_cb): + item = LunaConfigItem(self.__session, node, label, on_expand_cb) + self.__listv.add_item(item) + + def add_field(self, label, node): + item = LunaConfigItem(self.__session, node, label) + self.__listv.add_item(item) + + def show(self): + self.reload() + self.__session.push_context(self.__tui_ctx) + + def reload(self): + self.__listv.clear() + self.__reloader(self) + self.__session.schedule(EventType.E_REDRAW) + + @staticmethod + def reload_active(session): + state = session.active().state() + if isinstance(state, CollectionView): + state.reload() + +class ValueEditDialogue(menu.Dialogue): + def __init__(self, session, item: LunaConfigItem): + name = item.name() + title = f"Edit \"{name}\"" + super().__init__(session, title, None, False, + "Confirm", "Cancle") + + self.__item = item + self.__value = item.node().get_value() + + self.decide_content() + + def __get_val(self): + return self.__value + + def decide_content(self): + if not self.__item.type().is_choice(): + self.set_input_dialogue(True) + return + + listv = ListView(self.context(), "choices") + listv.set_size("0.8*", "*") + listv.set_alignment(Alignment.CENTER) + listv.set_onselected_cb(self.__on_selected) + + for t in self.__item.type().provider()._type: + listv.add_item(MultiChoiceItem(t, self.__get_val)) + + self.set_content(listv) + self.set_size() + + def __on_selected(self, listv, index, item): + self.__value = item.value() + + def _ok_onclick(self): + if self._textbox: + self.__value = self._textbox.get_text() + + if self.__item.change_value(self.__value): + super()._ok_onclick() + +class QuitDialogue(menu.Dialogue): + def __init__(self, session): + super().__init__(session, + "Quit ?", "Unsaved changes, sure to quit?", False, + "Quit Anyway", "No", "Save and Quit") + + def _ok_onclick(self): + self.session().schedule(EventType.E_QUIT) + + def _abort_onclick(self): + unmark_tainted() + self._ok_onclick() + + +class HelpDialogue(menu.Dialogue): + def __init__(self, session, title="", content=""): + super().__init__(session, title, None, no_btn=None) + + self.__content = content + self.__scroll_y = 0 + self.set_local_pos(0, -2) + + def prepare(self): + tb = tui.TuiTextBlock(self._context, "content") + tb.set_size(w="70") + tb.set_text(self.__content) + tb.height_auto_fit(True) + self.__tb = tb + + self.__scroll = tui.TuiScrollable(self._context, "scroll") + self.__scroll.set_size("65", "*") + self.__scroll.set_alignment(Alignment.CENTER) + self.__scroll.set_content(tb) + + self.set_size(w="75") + self.set_content(self.__scroll) + + super().prepare() + + def _handle_key_event(self, key): + if key == curses.KEY_UP: + self.__scroll_y = max(self.__scroll_y - 1, 0) + self.__scroll.set_scrollY(self.__scroll_y) + elif key == curses.KEY_DOWN: + y = self.__tb._size.y() + self.__scroll_y = min(self.__scroll_y + 1, y) + self.__scroll.set_scrollY(self.__scroll_y) + super()._handle_key_event(key) + +class TerminalSizeCheckFailed(Exception): + def __init__(self, *args: object) -> None: + super().__init__(*args) + +def main(_, root_node): + global __git_repo_info + + __git_repo_info = get_git_hash() + + session = tui.TuiSession() + + h, w = session.window_size() + if h < 30 or w < 85: + raise TerminalSizeCheckFailed((90, 40), (w, h)) + + base_background = TuiColor.white.bright() + session.set_color(ColorScope.WIN, + TuiColor.black, TuiColor.blue) + session.set_color(ColorScope.PANEL, + TuiColor.black, base_background) + session.set_color(ColorScope.TEXT, + TuiColor.black, base_background) + session.set_color(ColorScope.TEXT_HI, + TuiColor.magenta, base_background) + session.set_color(ColorScope.SHADOW, + TuiColor.black, TuiColor.black) + session.set_color(ColorScope.SELECT, + TuiColor.white, TuiColor.black.bright()) + session.set_color(ColorScope.HINT, + TuiColor.cyan, base_background) + session.set_color(ColorScope.BOX, + TuiColor.black, TuiColor.white) + + main_view = CollectionView(session, root_node) + main_view.show() + + session.event_loop() + +def menuconfig(root_node): + global __tainted + curses.wrapper(main, root_node) + + return not __tainted \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/lcfg/types.py b/lunaix-os/scripts/build-tools/lcfg/types.py index 123f6a9..8fc373d 100644 --- a/lunaix-os/scripts/build-tools/lcfg/types.py +++ b/lunaix-os/scripts/build-tools/lcfg/types.py @@ -70,8 +70,8 @@ class MultipleChoiceType(PrimitiveType): return None in self._type def __str__(self) -> str: - accepted = [f" {t}" for t in self._type] + accepted = [f" * {t}" for t in self._type] return "\n".join([ - "choose one: \n", + "choose one:", *accepted ]) diff --git a/lunaix-os/scripts/build-tools/luna_build.py b/lunaix-os/scripts/build-tools/luna_build.py index e3c6908..ce3be82 100755 --- a/lunaix-os/scripts/build-tools/luna_build.py +++ b/lunaix-os/scripts/build-tools/luna_build.py @@ -8,6 +8,7 @@ from integration.config_io import CHeaderConfigProvider from integration.lbuild_bridge import LConfigProvider from integration.render_ishell import InteractiveShell from integration.build_gen import MakefileBuildGen, install_lbuild_functions +from integration.lunamenu import menuconfig, TerminalSizeCheckFailed import lcfg.types as lcfg_type import lcfg.builtins as builtin @@ -37,12 +38,23 @@ def prepare_lconfig_env(out_dir): def do_config(opt, lcfg_env): redo_config = not exists(opt.config_save) or opt.force - if not redo_config: + if not redo_config or opt.quiet: return + + try: + clean_quit = menuconfig(lcfg_env) + except TerminalSizeCheckFailed as e: + least = e.args[0] + current = e.args[1] + print( + f"Your terminal size: {current} is less than minimum requirement of {least}.\n" + "menuconfig will not function properly, switch to prompt based.\n") + + shell = InteractiveShell(lcfg_env) + clean_quit = shell.render_loop() - shell = InteractiveShell(lcfg_env) - if not shell.render_loop(): - print("Configuration aborted.") + if not clean_quit: + print("Configuration aborted. Nothing has been saved.") exit(-1) def do_buildfile_gen(opts, lcfg_env): @@ -71,6 +83,7 @@ def do_buildfile_gen(opts, lcfg_env): def main(): parser = ArgumentParser() parser.add_argument("--config", action="store_true", default=False) + parser.add_argument("--quiet", action="store_true", default=False) parser.add_argument("--lconfig-file", default="LConfig") parser.add_argument("--config-save", default=".config.json") parser.add_argument("--force", action="store_true", default=False) diff --git a/lunaix-os/scripts/qemu.py b/lunaix-os/scripts/qemu.py index f2808e5..f12881a 100755 --- a/lunaix-os/scripts/qemu.py +++ b/lunaix-os/scripts/qemu.py @@ -3,8 +3,10 @@ import subprocess, time, os, re, argparse, json from pathlib import PurePosixPath import logging +import uuid logger = logging.getLogger("auto_qemu") +logging.basicConfig(level=logging.INFO) g_lookup = {} @@ -51,12 +53,89 @@ def get_config(opt, path, default=None, required=False): def join_attrs(attrs): return ",".join(attrs) -def parse_protocol(opt): - protocol = get_config(opt, "protocol", "telnet") - addr = get_config(opt, "addr", ":12345") - logfile = get_config(opt, "logfile") +def get_uniq(): + return uuid.uuid4().hex[:8] - return (f"{protocol}:{addr}", logfile) +def map_bool(b): + return "on" if b else "off" + + + +################################# +# IO Backend Definitions +# + +class IOBackend: + def __init__(self, opt, id_prefix="io") -> None: + self._type = get_config(opt, "type", required=True) + self._logfile = get_config(opt, "logfile") + self._id = f"{id_prefix}.{get_uniq()}" + + def get_options(self): + opts = [] + if self._logfile: + opts.append(f"logfile={self._logfile}") + return opts + + def to_cmdline(self): + return join_attrs([ + self._type, f"id={self._id}", *self.get_options() + ]) + + def name(self): + return self._id + +class FileIOBackend(IOBackend): + def __init__(self, opt) -> None: + super().__init__(opt) + self.__path = get_config(opt, "path", required=True) + + def get_options(self): + opts = [ + f"path={self.__path}" + ] + return opts + super().get_options() + +class SocketIOBackend(IOBackend): + def __init__(self, opt) -> None: + super().__init__(opt) + self.__protocol = self._type + self._type = "socket" + self.__host = get_config(opt, "host", default="localhost") + self.__port = get_config(opt, "port", required=True) + self.__server = bool(get_config(opt, "server", True)) + self.__wait = bool(get_config(opt, "wait", True)) + + def get_options(self): + opts = [ + f"host={self.__host}", + f"port={self.__port}", + f"server={map_bool(self.__server)}", + f"wait={map_bool(self.__wait)}", + ] + if self.__protocol == "telnet": + opts.append("telnet=on") + if self.__protocol == "ws": + opts.append("websocket=on") + return opts + super().get_options() + +def select_backend(opt): + bopt = get_config(opt, "io", required=True) + backend_type = get_config(bopt, "type", required=True) + + if backend_type in ["telnet", "ws", "tcp"]: + return SocketIOBackend(bopt) + + if backend_type in ["file", "pipe", "serial", "parallel"]: + return FileIOBackend(bopt) + + return IOBackend(bopt) + + + +################################# +# QEMU Emulated Device Definitions +# class QEMUPeripherals: def __init__(self, name, opt) -> None: @@ -66,39 +145,71 @@ class QEMUPeripherals: def get_qemu_opts(self) -> list: pass -class BasicSerialDevice(QEMUPeripherals): +class ISASerialDevice(QEMUPeripherals): def __init__(self, opt) -> None: - super().__init__("serial", opt) + super().__init__("isa-serial", opt) def get_qemu_opts(self): - link, logfile = parse_protocol(self._opt) + chardev = select_backend(self._opt) + + cmds = [ + "isa-serial", + f"id=com.{get_uniq()}", + f"chardev={chardev.name()}" + ] - cmds = [ link, "server", "nowait" ] - if logfile: - cmds.append(f"logfile={logfile}") - return [ "-serial", join_attrs(cmds) ] + return [ + "-chardev", chardev.to_cmdline(), + "-device", join_attrs(cmds) + ] class PCISerialDevice(QEMUPeripherals): def __init__(self, opt) -> None: super().__init__("pci-serial", opt) def get_qemu_opts(self): - uniq = hex(self.__hash__())[2:] - name = f"chrdev.{uniq}" - cmds = [ "pci-serial", f"id=uart.{uniq}", f"chardev={name}" ] - chrdev = [ "file", f"id={name}" ] - - logfile = get_config(self._opt, "logfile", required=True) - chrdev.append(f"path={logfile}") + chardev = select_backend(self._opt) + + cmds = [ + "pci-serial", + f"id=uart.{get_uniq()}", + f"chardev={chardev.name()}" + ] return [ - "-chardev", join_attrs(chrdev), + "-chardev", chardev.to_cmdline(), "-device", join_attrs(cmds) ] class AHCIBus(QEMUPeripherals): def __init__(self, opt) -> None: super().__init__("ahci", opt) + + def __create_disklet(self, index, bus, opt): + d_type = get_config(opt, "type", default="ide-hd") + d_img = get_config(opt, "img", required=True) + d_ro = get_config(opt, "ro", default=False) + d_fmt = get_config(opt, "format", default="raw") + d_id = f"disk_{index}" + + if not os.path.exists(d_img): + logger.warning(f"AHCI bus: {d_img} not exists, skipped") + return [] + + return [ + "-drive", join_attrs([ + f"id={d_id}", + f"file={d_img}", + f"readonly={'on' if d_ro else 'off'}", + f"if=none", + f"format={d_fmt}" + ]), + "-device", join_attrs([ + d_type, + f"drive={d_id}", + f"bus={bus}.{index}" + ]) + ] def get_qemu_opts(self): opt = self._opt @@ -106,31 +217,9 @@ class AHCIBus(QEMUPeripherals): name = name.strip().replace(" ", "_") cmds = [ "-device", f"ahci,id={name}" ] - for i, disk in enumerate(get_config(opt, "disks", default=[])): - d_type = get_config(disk, "type", default="ide-hd") - d_img = get_config(disk, "img", required=True) - d_ro = get_config(disk, "ro", default=False) - d_fmt = get_config(disk, "format", default="raw") - d_id = f"disk_{i}" - - if not os.path.exists(d_img): - logger.warning(f"AHCI bus: {d_img} not exists, disk skipped") - continue - - cmds += [ - "-drive", join_attrs([ - f"id={d_id}," - f"file={d_img}", - f"readonly={'on' if d_ro else 'off'}", - f"if=none", - f"format={d_fmt}" - ]), - "-device", join_attrs([ - d_type, - f"drive={d_id}", - f"bus={name}.{i}" - ]) - ] + disklets = get_config(opt, "disks", default=[]) + for i, disk in enumerate(disklets): + cmds += self.__create_disklet(i, name, disk) return cmds @@ -148,36 +237,49 @@ class QEMUMonitor(QEMUPeripherals): super().__init__("monitor", opt) def get_qemu_opts(self): - link, logfile = parse_protocol(self._opt) + + chardev = select_backend(self._opt) return [ - "-monitor", join_attrs([ - link, - "server", - "nowait", - f"logfile={logfile}" + "-chardev", chardev.to_cmdline(), + "-mon", join_attrs([ + chardev.name(), + "mode=readline", ]) ] -class QEMUExec: - devices = { - "basic_serial": BasicSerialDevice, +class QEMUDevices: + __devs = { + "isa-serial": ISASerialDevice, "ahci": AHCIBus, "rtc": RTCDevice, "hmp": QEMUMonitor, "pci-serial": PCISerialDevice } + @staticmethod + def get(name): + if name not in QEMUDevices.__devs: + raise Exception(f"device class: {name} is not defined") + return QEMUDevices.__devs[name] + + + +################################# +# QEMU Machine Definitions +# + +class QEMUExec: + + def __init__(self, options) -> None: self._opt = options self._devices = [] for dev in get_config(options, "devices", default=[]): dev_class = get_config(dev, "class") - if dev_class not in QEMUExec.devices: - raise Exception(f"device class: {dev_class} is not defined") - - self._devices.append(QEMUExec.devices[dev_class](dev)) + device = QEMUDevices.get(dev_class) + self._devices.append(device(dev)) def get_qemu_exec_name(self): pass @@ -217,7 +319,7 @@ class QEMUExec: def add_peripheral(self, peripheral): self._devices.append(peripheral) - def start(self, qemu_dir_override=""): + def start(self, qemu_dir_override="", dryrun=False, extras=[]): qemu_path = self.get_qemu_exec_name() qemu_path = os.path.join(qemu_dir_override, qemu_path) cmds = [ @@ -230,7 +332,12 @@ class QEMUExec: for dev in self._devices: cmds += dev.get_qemu_opts() + cmds += extras print(" ".join(cmds), "\n") + + if dryrun: + logger.info("[DRY RUN] QEMU not invoked") + return handle = subprocess.Popen(cmds) @@ -257,9 +364,10 @@ def main(): arg.add_argument("config_file") arg.add_argument("--qemu-dir", default="") + arg.add_argument("--dry", action='store_true') arg.add_argument("-v", "--values", action='append', default=[]) - arg_opt = arg.parse_args() + arg_opt, extras = arg.parse_known_args() opts = {} with open(arg_opt.config_file, 'r') as f: @@ -277,7 +385,8 @@ def main(): else: raise Exception(f"undefined arch: {arch}") - q.start(arg_opt.qemu_dir) + extras = [ x for x in extras if x != '--'] + q.start(arg_opt.qemu_dir, arg_opt.dry, extras) if __name__ == "__main__": main() \ No newline at end of file diff --git a/lunaix-os/scripts/qemus/qemu_x86_dev.json b/lunaix-os/scripts/qemus/qemu_x86_dev.json index 023dfec..60b292e 100644 --- a/lunaix-os/scripts/qemus/qemu_x86_dev.json +++ b/lunaix-os/scripts/qemus/qemu_x86_dev.json @@ -28,18 +28,26 @@ }, "devices": [ { - "class": "basic_serial", - "protocol": "telnet", - "addr": ":12345", - "logfile": "lunaix_ttyS0.log" + "class": "isa-serial", + "io": { + "type": "telnet", + "port": "12345", + "logfile": "lunaix_ttyS0.log" + } }, { "class": "pci-serial", - "logfile": "ttyPCI0.log" + "io": { + "type": "null", + "logfile": "ttypci1.log" + } }, { "class": "pci-serial", - "logfile": "ttyPCI1.log" + "io": { + "type": "null", + "logfile": "ttypci2.log" + } }, { "class": "rtc", @@ -64,9 +72,11 @@ }, { "class": "hmp", - "protocol": "telnet", - "addr": ":$QMPORT", - "logfile": "qm.log" + "io": { + "type": "telnet", + "port": "$QMPORT", + "logfile": "qm.log" + } } ] } \ No newline at end of file diff --git a/lunaix-os/usr/LConfig b/lunaix-os/usr/LConfig index ea38d57..3f44fe2 100644 --- a/lunaix-os/usr/LConfig +++ b/lunaix-os/usr/LConfig @@ -1,4 +1,4 @@ -@Term +@Term("Architecture") def arch(): """ set the ISA target diff --git a/lunaix-os/usr/makefile b/lunaix-os/usr/makefile index 54f7260..a69500f 100644 --- a/lunaix-os/usr/makefile +++ b/lunaix-os/usr/makefile @@ -1,5 +1,8 @@ include utils.mkinc include toolchain.mkinc + +LCONFIG_FLAGS := --quiet + include lunabuild.mkinc include $(lbuild_mkinc) -- 2.27.0 From 47c4e0c19ae8526b14ce4e0d7b243f7a4dc6fafd Mon Sep 17 00:00:00 2001 From: Minep Date: Sun, 25 Aug 2024 13:24:25 +0100 Subject: [PATCH 15/16] optimize the menuconfig redrawing --- .../scripts/build-tools/integration/libtui.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/lunaix-os/scripts/build-tools/integration/libtui.py b/lunaix-os/scripts/build-tools/integration/libtui.py index 30e28f6..1441836 100644 --- a/lunaix-os/scripts/build-tools/integration/libtui.py +++ b/lunaix-os/scripts/build-tools/integration/libtui.py @@ -538,15 +538,13 @@ class TuiScrollable(TuiObject): if not self.__content: return - self.__pad.erase() - self.__pad_panel.top() self.__content.on_draw() wminy, wminx = self._pos.yx() wmaxy, wmaxx = self._size.yx() wmaxy, wmaxx = wmaxy + wminy, wmaxx + wminx - self.__pad.touchwin() + self.__pad.refresh(*self.__spos.yx(), wminy, wminx, wmaxy - 1, wmaxx - 1) @@ -718,7 +716,7 @@ class TuiPanel(TuiContainerObject): def on_draw(self): win = self.__swin.window() - win.erase() + win.noutrefresh() if self.__use_border: win.border() @@ -733,8 +731,6 @@ class TuiPanel(TuiContainerObject): super().on_draw() - win.touchwin() - class TuiLabel(TuiWidget): def __init__(self, context, id): super().__init__(context, id) @@ -1135,17 +1131,11 @@ class TuiSession: ctx.dispatch_event(EventType.E_QUIT, None) def __redraw(self): - self.stdsc.erase() - self.__win.erase() + self.__win.noutrefresh() self.active().redraw(self.__win) self.__panbg.bottom() - self.__win.touchwin() - self.__winbg.touchwin() - - self.__win.refresh() - self.__winbg.refresh() cpanel.update_panels() curses.doupdate() -- 2.27.0 From 37c2b8f74c5a5733505851502282625f17435371 Mon Sep 17 00:00:00 2001 From: Minep Date: Mon, 26 Aug 2024 15:37:45 +0100 Subject: [PATCH 16/16] add vm probing for x86_64 --- lunaix-os/live_debug.sh | 3 +- .../scripts/gdb/lunadbg/arch/__init__.py | 2 +- .../scripts/gdb/lunadbg/arch/x86/__init__.py | 2 +- lunaix-os/scripts/gdb/lunadbg/arch/x86/pte.py | 81 ++++++++++++++++++- lunaix-os/scripts/gdb/lunadbg/commands.py | 15 ++-- lunaix-os/scripts/gdb/lunadbg/mem.py | 13 +-- .../scripts/gdb/lunadbg/structs/pagetable.py | 3 +- 7 files changed, 104 insertions(+), 15 deletions(-) diff --git a/lunaix-os/live_debug.sh b/lunaix-os/live_debug.sh index d189329..0ecc759 100755 --- a/lunaix-os/live_debug.sh +++ b/lunaix-os/live_debug.sh @@ -15,4 +15,5 @@ make CMDLINE=${default_cmd} ARCH=${ARCH} MODE=${MODE:-debug} image -j5 || exit - -v EXT2_TEST_DISC=machine/test_part.ext2 \ -v ARCH=${ARCH} & -QMPORT=${hmp_port} gdb build/bin/kernel.bin -ex "target remote localhost:${gdb_port}" \ No newline at end of file +QMPORT=${hmp_port} ARCH=${ARCH} \ + gdb build/bin/kernel.bin -ex "target remote localhost:${gdb_port}" \ No newline at end of file diff --git a/lunaix-os/scripts/gdb/lunadbg/arch/__init__.py b/lunaix-os/scripts/gdb/lunadbg/arch/__init__.py index dbb00ce..682d6d0 100644 --- a/lunaix-os/scripts/gdb/lunadbg/arch/__init__.py +++ b/lunaix-os/scripts/gdb/lunadbg/arch/__init__.py @@ -1,4 +1,4 @@ import os -if os.environ["LUNADBG_ARCH"].startswith("x86_"): +if os.environ["ARCH"].startswith("x86_"): from .x86 import * \ No newline at end of file diff --git a/lunaix-os/scripts/gdb/lunadbg/arch/x86/__init__.py b/lunaix-os/scripts/gdb/lunadbg/arch/x86/__init__.py index 2ee9e2d..6eede5b 100644 --- a/lunaix-os/scripts/gdb/lunadbg/arch/x86/__init__.py +++ b/lunaix-os/scripts/gdb/lunadbg/arch/x86/__init__.py @@ -1,6 +1,6 @@ import os -if os.environ["LUNADBG_ARCH"] == 'x86_64': +if os.environ["ARCH"] == 'x86_64': from .pte import PageTableHelper64 as PageTableHelper else: from .pte import PageTableHelper32 as PageTableHelper diff --git a/lunaix-os/scripts/gdb/lunadbg/arch/x86/pte.py b/lunaix-os/scripts/gdb/lunadbg/arch/x86/pte.py index 444cb3f..1bf626e 100644 --- a/lunaix-os/scripts/gdb/lunadbg/arch/x86/pte.py +++ b/lunaix-os/scripts/gdb/lunadbg/arch/x86/pte.py @@ -44,6 +44,10 @@ class PageTableHelperBase: raise NotImplementedError() class PageTableHelper32(PageTableHelperBase): + @staticmethod + def null_mapping(pte): + return pte == 0 + @staticmethod def translation_level(level = -1): return [0, 1][level] @@ -108,6 +112,81 @@ class PageTableHelper32(PageTableHelperBase): @staticmethod def pte_size(): return 4 + + @staticmethod + def vm_mnt(): + return 0xFFC00000 class PageTableHelper64(PageTableHelperBase): - pass \ No newline at end of file + @staticmethod + def null_mapping(pte): + return pte == 0 + + @staticmethod + def translation_level(level = -1): + return [0, 1, 2, 3][level] + + @staticmethod + def pgtable_len(): + return (1 << 9) + + @staticmethod + def translation_shift_bits(level): + return [9, 9, 9, 0][level] + 12 + + @staticmethod + def mapping_present(pte): + return bool(pte & 1) + + @staticmethod + def huge_page(pte, po): + return bool(pte & (1 << 7)) and po + + @staticmethod + def protections(pte): + prot = ['R'] # RWXUP + if (pte & (1 << 1)): + prot.append('W') + if (pte & -1): + prot.append('X') + if (pte & (1 << 2)): + prot.append('U') + if (pte & (1)): + prot.append('P') + return prot + + @staticmethod + def other_attributes(level, pte): + attrs = [] + if pte & (1 << 5): + attrs.append("A") + if pte & (1 << 6): + attrs.append("D") + if pte & (1 << 3): + attrs.append("PWT") + if pte & (1 << 4): + attrs.append("PCD") + if PageTableHelper32.translation_level(level) == 1 and pte & (1 << 8): + attrs.append("G") + return attrs + + @staticmethod + def same_kind(pte1, pte2): + attr_mask = 0x19f # P, R/W, U/S, PWT, PCD, PS, G + return (pte1 & attr_mask) == (pte2 & attr_mask) + + @staticmethod + def physical_pfn(pte): + return pte >> 12 + + @staticmethod + def vaddr_width(): + return 48 + + @staticmethod + def pte_size(): + return 8 + + @staticmethod + def vm_mnt(): + return 0xffffff0000000000 \ No newline at end of file diff --git a/lunaix-os/scripts/gdb/lunadbg/commands.py b/lunaix-os/scripts/gdb/lunadbg/commands.py index ec3fe82..1e4d425 100644 --- a/lunaix-os/scripts/gdb/lunadbg/commands.py +++ b/lunaix-os/scripts/gdb/lunadbg/commands.py @@ -2,6 +2,7 @@ from gdb import Command, COMMAND_USER import argparse import shlex +import traceback class LunadbgCommand(Command): def __init__(self, name: str) -> None: @@ -20,10 +21,14 @@ class LunadbgCommand(Command): return None def invoke(self, argument: str, from_tty: bool) -> None: - parsed = self._parse_args(argument) - if not parsed: - return - self.on_execute(parsed, argument, from_tty) - + try: + parsed = self._parse_args(argument) + if not parsed: + return + self.on_execute(parsed, argument, from_tty) + except Exception as e: + traceback.print_exception(e) + + def on_execute(self, parsed, gdb_args, from_tty): raise NotImplementedError() \ No newline at end of file diff --git a/lunaix-os/scripts/gdb/lunadbg/mem.py b/lunaix-os/scripts/gdb/lunadbg/mem.py index 558ad3a..a51560c 100644 --- a/lunaix-os/scripts/gdb/lunadbg/mem.py +++ b/lunaix-os/scripts/gdb/lunadbg/mem.py @@ -2,6 +2,7 @@ from .commands import LunadbgCommand from .pp import MyPrettyPrinter from .profiling.pmstat import PhysicalMemProfile from .structs.pagetable import PageTable +from .arch.x86 import PageTableHelper class MMStats(LunadbgCommand): def __init__(self) -> None: @@ -60,13 +61,15 @@ class MMStats(LunadbgCommand): def vm_lookup(self, pp, va, optn): to_addr = int(optn.to_addr, 0) + vmt = PageTableHelper.vm_mnt() if not optn.n and not to_addr: - pp.print(self.__ptw.get_pte(va, level=optn.level)) + pp.print(self.__ptw.get_pte(va, level=optn.level, mnt=vmt)) + return + + if to_addr: + self.__ptw.print_ptes_between(pp, va, to_addr, optn.level, mnt=vmt) else: - if to_addr: - self.__ptw.print_ptes_between(pp, va, to_addr, optn.level) - else: - self.__ptw.print_ptes(pp, va, optn.n, optn.level) + self.__ptw.print_ptes(pp, va, optn.n, optn.level, mnt=vmt) def __do_stats(self, pp, optn): if optn.state_type == "pmem": diff --git a/lunaix-os/scripts/gdb/lunadbg/structs/pagetable.py b/lunaix-os/scripts/gdb/lunadbg/structs/pagetable.py index 58ed0c8..c58eedd 100644 --- a/lunaix-os/scripts/gdb/lunadbg/structs/pagetable.py +++ b/lunaix-os/scripts/gdb/lunadbg/structs/pagetable.py @@ -248,7 +248,8 @@ class PageTable(): ptep += TLB.pte_size() - self.__print_pte_ranged(pp, head_pte, prev_pte) + if head_pte: + self.__print_pte_ranged(pp, head_pte, prev_pte) def print_ptes_between(self, pp, va, va_end, level=-1, mnt=0xFFC00000): ptep_start = PageTable.mkptep_for(mnt, va) -- 2.27.0

7FNs$aLG-U-o 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