diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9132231 --- /dev/null +++ b/.clang-format @@ -0,0 +1,237 @@ + +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 128 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/.cmake-format.yaml b/.cmake-format.yaml new file mode 100644 index 0000000..82476a4 --- /dev/null +++ b/.cmake-format.yaml @@ -0,0 +1,244 @@ +_help_parse: Options affecting listfile parsing +parse: + _help_additional_commands: + - Specify structure for custom cmake functions + additional_commands: + foo: + flags: + - BAR + - BAZ + kwargs: + HEADERS: "*" + SOURCES: "*" + DEPENDS: "*" + _help_override_spec: + - Override configurations per-command where available + override_spec: {} + _help_vartags: + - Specify variable tags. + vartags: [] + _help_proptags: + - Specify property tags. + proptags: [] +_help_format: Options affecting formatting. +format: + _help_disable: + - Disable formatting entirely, making cmake-format a no-op + disable: false + _help_line_width: + - How wide to allow formatted cmake files + line_width: 128 + _help_tab_size: + - How many spaces to tab for indent + tab_size: 2 + _help_use_tabchars: + - If true, lines are indented using tab characters (utf-8 + - 0x09) instead of space characters (utf-8 0x20). + - In cases where the layout would require a fractional tab + - character, the behavior of the fractional indentation is + - governed by + use_tabchars: false + _help_fractional_tab_policy: + - If is True, then the value of this variable + - indicates how fractional indentions are handled during + - whitespace replacement. If set to 'use-space', fractional + - indentation is left as spaces (utf-8 0x20). If set to + - "`round-up` fractional indentation is replaced with a single" + - tab character (utf-8 0x09) effectively shifting the column + - to the next tabstop + fractional_tab_policy: use-space + _help_max_subgroups_hwrap: + - If an argument group contains more than this many sub-groups + - (parg or kwarg groups) then force it to a vertical layout. + max_subgroups_hwrap: 3 + _help_max_pargs_hwrap: + - If a positional argument group contains more than this many + - arguments, then force it to a vertical layout. + max_pargs_hwrap: 6 + _help_max_rows_cmdline: + - If a cmdline positional group consumes more than this many + - lines without nesting, then invalidate the layout (and nest) + max_rows_cmdline: 2 + _help_separate_ctrl_name_with_space: + - If true, separate flow control names from their parentheses + - with a space + separate_ctrl_name_with_space: false + _help_separate_fn_name_with_space: + - If true, separate function names from parentheses with a + - space + separate_fn_name_with_space: false + _help_dangle_parens: + - If a statement is wrapped to more than one line, than dangle + dangle_parens: false + _help_dangle_align: + - If the trailing parenthesis must be 'dangled' on its on + - "line, then align it to this reference: `prefix`: the start" + - "of the statement, `prefix-indent`: the start of the" + - "statement, plus one indentation level, `child`: align to" + - the column of the arguments + dangle_align: prefix + _help_min_prefix_chars: + - If the statement spelling length (including space and + - parenthesis) is smaller than this amount, then force reject + - nested layouts. + min_prefix_chars: 4 + _help_max_prefix_chars: + - If the statement spelling length (including space and + - parenthesis) is larger than the tab width by more than this + - amount, then force reject un-nested layouts. + max_prefix_chars: 10 + _help_max_lines_hwrap: + - If a candidate layout is wrapped horizontally but it exceeds + - this many lines, then reject the layout. + max_lines_hwrap: 3 + _help_line_ending: + - What style line endings to use in the output. + line_ending: unix + _help_command_case: + - Format command names consistently as 'lower' or 'upper' case + command_case: canonical + _help_keyword_case: + - Format keywords consistently as 'lower' or 'upper' case + keyword_case: unchanged + _help_always_wrap: + - A list of command names which should always be wrapped + always_wrap: [] + _help_enable_sort: + - If true, the argument lists which are known to be sortable + - will be sorted lexicographicall + enable_sort: true + _help_autosort: + - If true, the parsers may infer whether or not an argument + - list is sortable (without annotation). + autosort: false + _help_require_valid_layout: + - By default, if cmake-format cannot successfully fit + - everything into the desired linewidth it will apply the + - last, most aggressive attempt that it made. If this flag is + - True, however, cmake-format will print error, exit with non- + - zero status code, and write-out nothing + require_valid_layout: false + _help_layout_passes: + - A dictionary mapping layout nodes to a list of wrap + - decisions. See the documentation for more information. + layout_passes: {} +_help_markup: Options affecting comment reflow and formatting. +markup: + _help_bullet_char: + - What character to use for bulleted lists + bullet_char: "*" + _help_enum_char: + - What character to use as punctuation after numerals in an + - enumerated list + enum_char: . + _help_first_comment_is_literal: + - If comment markup is enabled, don't reflow the first comment + - block in each listfile. Use this to preserve formatting of + - your copyright/license statements. + first_comment_is_literal: false + _help_literal_comment_pattern: + - If comment markup is enabled, don't reflow any comment block + - which matches this (regex) pattern. Default is `None` + - (disabled). + literal_comment_pattern: null + _help_fence_pattern: + - Regular expression to match preformat fences in comments + - default= ``r'^\s*([`~]{3}[`~]*)(.*)$'`` + fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$ + _help_ruler_pattern: + - Regular expression to match rulers in comments default= + - '``r''^\s*[^\w\s]{3}.*[^\w\s]{3}$''``' + ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$ + _help_explicit_trailing_pattern: + - If a comment line matches starts with this pattern then it + - is explicitly a trailing comment for the preceeding + - argument. Default is '#<' + explicit_trailing_pattern: "#<" + _help_hashruler_min_length: + - If a comment line starts with at least this many consecutive + - hash characters, then don't lstrip() them off. This allows + - for lazy hash rulers where the first hash char is not + - separated by space + hashruler_min_length: 10 + _help_canonicalize_hashrulers: + - If true, then insert a space between the first hash char and + - remaining hash chars in a hash ruler, and normalize its + - length to fill the column + canonicalize_hashrulers: true + _help_enable_markup: + - enable comment markup parsing and reflow + enable_markup: false +_help_lint: Options affecting the linter +lint: + _help_disabled_codes: + - a list of lint codes to disable + disabled_codes: [] + _help_function_pattern: + - regular expression pattern describing valid function names + function_pattern: "[0-9a-z_]+" + _help_macro_pattern: + - regular expression pattern describing valid macro names + macro_pattern: "[0-9A-Z_]+" + _help_global_var_pattern: + - regular expression pattern describing valid names for + - variables with global (cache) scope + global_var_pattern: "[A-Z][0-9A-Z_]+" + _help_internal_var_pattern: + - regular expression pattern describing valid names for + - variables with global scope (but internal semantic) + internal_var_pattern: _[A-Z][0-9A-Z_]+ + _help_local_var_pattern: + - regular expression pattern describing valid names for + - variables with local scope + local_var_pattern: "[a-z][a-z0-9_]+" + _help_private_var_pattern: + - regular expression pattern describing valid names for + - privatedirectory variables + private_var_pattern: _[0-9a-z_]+ + _help_public_var_pattern: + - regular expression pattern describing valid names for public + - directory variables + public_var_pattern: "[A-Z][0-9A-Z_]+" + _help_argument_var_pattern: + - regular expression pattern describing valid names for + - function/macro arguments and loop variables. + argument_var_pattern: "[a-z][a-z0-9_]+" + _help_keyword_pattern: + - regular expression pattern describing valid names for + - keywords used in functions or macros + keyword_pattern: "[A-Z][0-9A-Z_]+" + _help_max_conditionals_custom_parser: + - In the heuristic for C0201, how many conditionals to match + - within a loop in before considering the loop a parser. + max_conditionals_custom_parser: 2 + _help_min_statement_spacing: + - Require at least this many newlines between statements + min_statement_spacing: 1 + _help_max_statement_spacing: + - Require no more than this many newlines between statements + max_statement_spacing: 2 + max_returns: 6 + max_branches: 12 + max_arguments: 5 + max_localvars: 15 + max_statements: 50 +_help_encode: Options affecting file encoding +encode: + _help_emit_byteorder_mark: + - If true, emit the unicode byte-order mark (BOM) at the start + - of the file + emit_byteorder_mark: false + _help_input_encoding: + - Specify the encoding of the input file. Defaults to utf-8 + input_encoding: utf-8 + _help_output_encoding: + - Specify the encoding of the output file. Defaults to utf-8. + - Note that cmake only claims to support utf-8 so be careful + - when using anything else + output_encoding: utf-8 +_help_misc: Miscellaneous configurations options. +misc: + _help_per_command: + - A dictionary containing any per-command configuration + - overrides. Currently only `command_case` is supported. + per_command: {} diff --git a/.gitignore b/.gitignore index e628b6e..cf4608a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,20 @@ # Project ignores + +# Kernel Build +build/ +compile_commands.json + +# Tools build tools/build/ toolchain/ +*.bin + +# GRUB iso building +isodir/ +*.iso + +# clangd cache +.cache/ # ---> C # Prerequisites diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fd8f0d0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.30 FATAL_ERROR) +cmake_policy(VERSION 3.30) + +project(appa-os LANGUAGES C ASM) + +enable_language(ASM-ATT) +set(CMAKE_C_STANDARD 99) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +option(BUILD_DOCS "Build documentation" ON) + +if(BUILD_DOCS) + message(STATUS "Building documentation") + add_subdirectory(docs) +endif() + +add_subdirectory(src) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 0000000..4746851 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,43 @@ +if(NOT BUILD_DOCS) + return() +endif() + +find_package(Doxygen REQUIRED doxygen OPTIONAL_COMPONENTS dot mscgen dia) + +if(NOT DOXYGEN_FOUND) + message(ERROR "Doxygen is needed to build documentation, please install it") +endif() + +set(DOXYGEN_ALPHABETICAL_INDEX NO) +set(DOXYGEN_BUILTIN_STL_SUPPORT YES) +set(DOXYGEN_CASE_SENSE_NAMES NO) +set(DOXYGEN_CLASS_DIAGRAMS YES) +set(DOXYGEN_DISTRIBUTE_GROUP_DOC YES) +set(DOXYGEN_EXCLUDE build) +set(DOXYGEN_EXTRACT_ALL YES) +set(DOXYGEN_EXTRACT_LOCAL_CLASSES NO) +set(DOXYGEN_FILE_PATTERNS *.hpp) +set(DOXYGEN_GENERATE_TREEVIEW YES) +set(DOXYGEN_HIDE_FRIEND_COMPOUNDS YES) +set(DOXYGEN_HIDE_IN_BODY_DOCS YES) +set(DOXYGEN_HIDE_UNDOC_CLASSES YES) +set(DOXYGEN_HIDE_UNDOC_MEMBERS YES) +set(DOXYGEN_JAVADOC_AUTOBRIEF YES) +set(DOXYGEN_QT_AUTOBRIEF YES) +set(DOXYGEN_QUIET YES) +set(DOXYGEN_RECURSIVE YES) +set(DOXYGEN_REFERENCED_BY_RELATION YES) +set(DOXYGEN_REFERENCES_RELATION YES) +set(DOXYGEN_SORT_BY_SCOPE_NAME YES) +set(DOXYGEN_SORT_MEMBER_DOCS NO) +set(DOXYGEN_SOURCE_BROWSER YES) +set(DOXYGEN_STRIP_CODE_COMMENTS NO) +set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md") + +doxygen_add_docs(doc "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_SOURCE_DIR}/src/" ALL COMMENT "Generate HTML documentation") + +add_custom_target( + open-docs + COMMAND ${CMAKE_COMMAND} -E env xdg-open ${CMAKE_SOURCE_DIR}/build/docs/html/index.html + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/build/docs/html + COMMENT "Opening Doxygen documentation") diff --git a/scripts/versioning.sh b/scripts/versioning.sh new file mode 100755 index 0000000..5ec0944 --- /dev/null +++ b/scripts/versioning.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +# Script to Bump Version Numbers and Update File metadata +# This script updates the version numbers of a C/C++ project, specifically in `version.hpp` or `version.h`, +#and also updates information about compilation commits and build timestamps. +VERSION_FILE="$(find "$(pwd)" \( -iname "version.hpp" -o -iname "version.h" \))" + +MAJOR=$(grep -oP '([a-zA-Z]*)_VERSION_MAJOR (\K[0-9]+)' "$VERSION_FILE") +MINOR=$(grep -oP '([a-zA-Z]*)_VERSION_MINOR (\K[0-9]+)' "$VERSION_FILE") +PATCH=$(grep -oP '([a-zA-Z]*)_VERSION_PATCH (\K[0-9]+)' "$VERSION_FILE") + +_bump_version() { + case "$1" in + major) + MAJOR=$((MAJOR + 1)) + ;; + minor) + MINOR=$((MINOR + 1)) + ;; + patch) + PATCH=$((PATCH + 1)) + ;; + *) + echo "Unknown version semantic $1" >&2 + exit 124 + ;; + esac +} + +# Atualizar versões +[ "$#" -gt 0 ] && _bump_version "$1" +sed -i "s/\([a-zA-Z]\+\)_VERSION_MAJOR \([0-9]\+\)$/\1_VERSION_MAJOR $MAJOR/" "$VERSION_FILE" +sed -i "s/\([a-zA-Z]\+\)_VERSION_MINOR \([0-9]\+\)$/\1_VERSION_MINOR $MINOR/" "$VERSION_FILE" +sed -i "s/\([a-zA-Z]\+\)_VERSION_PATCH \([0-9]\+\)$/\1_VERSION_PATCH $PATCH/" "$VERSION_FILE" + +# Atualizar informações de compilação +COMMIT_HASH=$(git rev-parse HEAD) +BUILD_TIMESTAMP=$(date +%s) +sed -i "s/\([a-zA-Z]\+\)_COMMIT_HASH \"[a-zA-Z0-9]\+\"/\1_COMMIT_HASH \"$COMMIT_HASH\"/" "$VERSION_FILE" +sed -i "s/\([a-zA-Z]\+\)_BUILD_TIMESTAMP [0-9]\+/\1_BUILD_TIMESTAMP $BUILD_TIMESTAMP/" "$VERSION_FILE" + +# Exibir versão atualizada +printf "%d.%d.%d" "$MAJOR" "$MINOR" "$PATCH" + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..73cd581 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,24 @@ +add_executable(appa-os kernel.c) +execute_process(COMMAND ${PROJECT_SOURCE_DIR}/scripts/versioning.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE appa-os_VERSION) + +message(STATUS "Building [appa-os] version ${appa-os_VERSION}") +target_compile_options(appa-os PRIVATE $<$:-ffreestanding -Wall -Wextra>) +target_link_options( + appa-os + PRIVATE + -T + ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld + -ffreestanding + -O2 + -nostdlib) +set_target_properties(appa-os PROPERTIES PREFIX "") +set_target_properties(appa-os PROPERTIES OUTPUT_NAME "appa-os") +set_target_properties(appa-os PROPERTIES SUFFIX ".bin") + +file(GLOB ASM_FILES CONFIGURE_DEPENDS *.s) +file(GLOB C_FILES CONFIGURE_DEPENDS *.c) +file(GLOB H_FILES CONFIGURE_DEPENDS *.h) + +target_sources(appa-os PRIVATE ${C_FILES} ${ASM_FILES} PUBLIC ${H_FILES}) +target_include_directories(appa-os PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/boot.s b/src/boot.s new file mode 100644 index 0000000..1850cfc --- /dev/null +++ b/src/boot.s @@ -0,0 +1,113 @@ +// Declare constants for the multiboot header. +.set ALIGN, 1<<0 // align loaded modules on page boundaries +.set MEMINFO, 1<<1 // provide memory map +.set FLAGS, ALIGN | MEMINFO // this is the Multiboot 'flag' field +.set MAGIC, 0x1BADB002 // 'magic number' lets bootloader find the header +.set CHECKSUM, -(MAGIC + FLAGS) // checksum of above, to prove we are multiboot + +/* +Declare a multiboot header that marks the program as a kernel. These are magic +values that are documented in the multiboot standard. The bootloader will +search for this signature in the first 8 KiB of the kernel file, aligned at a +32-bit boundary. The signature is in its own section so the header can be +forced to be within the first 8 KiB of the kernel file. +*/ +.section .multiboot +.align 4 +.long MAGIC +.long FLAGS +.long CHECKSUM + +/* +The multiboot standard does not define the value of the stack pointer register +(esp) and it is up to the kernel to provide a stack. This allocates room for a +small stack by creating a symbol at the bottom of it, then allocating 16384 +bytes for it, and finally creating a symbol at the top. The stack grows +downwards on x86. The stack is in its own section so it can be marked nobits, +which means the kernel file is smaller because it does not contain an +uninitialized stack. The stack on x86 must be 16-byte aligned according to the +System V ABI standard and de-facto extensions. The compiler will assume the +stack is properly aligned and failure to align the stack will result in +undefined behavior. +*/ +.section .bss +.align 16 + +stack_bottom: + .skip 16384 # 16 KiB + +stack_top: + +/* +The linker script specifies _start as the entry point to the kernel and the +bootloader will jump to this position once the kernel has been loaded. It +doesn't make sense to return from this function as the bootloader is gone. +*/ + .section .text + .global _start + .type _start, @function + +_start: +/* + The bootloader has loaded us into 32-bit protected mode on a x86 + machine. Interrupts are disabled. Paging is disabled. The processor + state is as defined in the multiboot standard. The kernel has full + control of the CPU. The kernel can only make use of hardware features + and any code it provides as part of itself. There's no printf + function, unless the kernel provides its own header and a + printf implementation. There are no security restrictions, no + safeguards, no debugging mechanisms, only what the kernel provides + itself. It has absolute and complete power over the + machine. + */ + +/* + To set up a stack, we set the esp register to point to the top of the + stack (as it grows downwards on x86 systems). This is necessarily done + in assembly as languages such as C cannot function without a stack. + */ + mov $stack_top, %esp + +/* + This is a good place to initialize crucial processor state before the + high-level kernel is entered. It's best to minimize the early + environment where crucial features are offline. Note that the + processor is not fully initialized yet: Features such as floating + point instructions and instruction set extensions are not initialized + yet. The GDT should be loaded here. Paging should be enabled here. + C++ features such as global constructors and exceptions will require + runtime support to work as well. + */ +/* + Enter the high-level kernel. The ABI requires the stack is 16-byte + aligned at the time of the call instruction (which afterwards pushes + the return pointer of size 4 bytes). The stack was originally 16-byte + aligned above and we've pushed a multiple of 16 bytes to the + stack since (pushed 0 bytes so far), so the alignment has thus been + preserved and the call is well defined. + */ + call kernel_main + +/* + If the system has nothing more to do, put the computer into an + infinite loop. To do that: + 1) Disable interrupts with cli (clear interrupt enable in eflags). + They are already disabled by the bootloader, so this is not needed. + Mind that you might later enable interrupts and return from + kernel_main (which is sort of nonsensical to do). + 2) Wait for the next interrupt to arrive with hlt (halt instruction). + Since they are disabled, this will lock up the computer. + 3) Jump to the hlt instruction if it ever wakes up due to a + non-maskable interrupt occurring or due to system management mode. + */ + cli + +1: + hlt + jmp 1b + +/* +Set the size of the _start symbol to the current location '.' minus its start. +This is useful when debugging or when you implement call tracing. +*/ +.size _start, . - _start diff --git a/src/grub.cfg b/src/grub.cfg new file mode 100644 index 0000000..b1cad62 --- /dev/null +++ b/src/grub.cfg @@ -0,0 +1,4 @@ +menuentry "appa-os" { + multiboot /boot/appa-os.bin +} + diff --git a/src/kernel.c b/src/kernel.c new file mode 100644 index 0000000..c8bb7c8 --- /dev/null +++ b/src/kernel.c @@ -0,0 +1,219 @@ + +#include +#include +#include + +/* Check if the compiler thinks you are targeting the wrong operating system. */ +#if defined(__linux__) +#error \ + "You are not using a cross-compiler, you will most certainly run into trouble" +#endif + +/* This tutorial will only work for the 32-bit ix86 targets. */ +#if !defined(__i386__) +#error "This tutorial needs to be compiled with a ix86-elf compiler" +#endif + +/* Hardware text mode color constants. */ +enum vga_color { + VGA_COLOR_BLACK = 0, + VGA_COLOR_BLUE = 1, + VGA_COLOR_GREEN = 2, + VGA_COLOR_CYAN = 3, + VGA_COLOR_RED = 4, + VGA_COLOR_MAGENTA = 5, + VGA_COLOR_BROWN = 6, + VGA_COLOR_LIGHT_GREY = 7, + VGA_COLOR_DARK_GREY = 8, + VGA_COLOR_LIGHT_BLUE = 9, + VGA_COLOR_LIGHT_GREEN = 10, + VGA_COLOR_LIGHT_CYAN = 11, + VGA_COLOR_LIGHT_RED = 12, + VGA_COLOR_LIGHT_MAGENTA = 13, + VGA_COLOR_LIGHT_BROWN = 14, + VGA_COLOR_WHITE = 15, +}; + +/** + * @brief Converts foreground and background VGA colors into a single byte. + * + * This function takes two `enum vga_color` values representing the foreground + * (fg) and background (bg) colors. It combines these by placing the foreground + * color in the lower 4 bits, and the background color in the upper 4 bits, + * returning the result as an unsigned char. + * + * @param fg The foreground color value from enum vga_color + * @param bg The background color value from enum vga_color + * @return uint8_t A byte representing the combined VGA colors + */ +static inline uint8_t vga_entry_color(enum vga_color fg, enum vga_color bg) { + return fg | bg << 4; +} + +/** + * @brief Combines a single byte character with an 8-bit VGA color to form a + * 16-bit VGA entry. + * + * This function takes a `unsigned char` representing the character and an + * `uint8_t` representing the VGA color. It combines these by placing the + * character in the lower 8 bits, and the color value in the upper 8 bits, + * returning the result as a `uint16_t`. + * + * @param uc The single byte character + * @param color The VGA color value from enum vga_color + * @return uint16_t A 16-bit number representing the combined VGA entry + */ +static inline uint16_t vga_entry(unsigned char uc, uint8_t color) { + return (uint16_t)uc | (uint16_t)color << 8; +} + +/** + * @brief Calculates the length of a given string. + * + * This function iterates through the provided null-terminated character array, + * counting characters until it encounters a terminator, which is represented by + * '\0'. The count of characters up to (but not including) this terminator is + * returned as size_t len. + * + * @param str A pointer to the null-terminated string whose length needs to be + * calculated + * @return size_t The length of the provided string, excluding the terminating + * null character. + */ +size_t strlen(const char *str) { + size_t len = 0; + while (str[len]) + len++; + return len; +} + +static const size_t VGA_WIDTH = 80; +static const size_t VGA_HEIGHT = 25; + +size_t terminal_row; +size_t terminal_column; +uint8_t terminal_color; +uint16_t *terminal_buffer; + +/** + * @brief Initializes the terminal interface. + * + * This function sets up the initial state of the terminal, including its + * dimensions, color settings, and initializes all buffer entries to a default + * character with black foreground and white background colors. The + * initialization process involves iterating over every cell in the terminal's + * grid and setting each entry to the default VGA entry. + */ +void terminal_initialize(void) { + terminal_row = 0; + terminal_column = 0; + terminal_color = vga_entry_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK); + terminal_buffer = (uint16_t *)0xB8000; + for (size_t y = 0; y < VGA_HEIGHT; y++) { + for (size_t x = 0; x < VGA_WIDTH; x++) { + const size_t index = y * VGA_WIDTH + x; + terminal_buffer[index] = vga_entry(' ', terminal_color); + } + } +} + +/** + * @brief Sets the color of all entries in the terminal buffer to a new color. + * + * This function updates the color value stored at each entry in the terminal's + * character-color grid. The new color is combined using bitwise OR and left + * shift operations. It assumes that the terminal_color variable holds the + * desired VGA color code, which is used both as foreground (lower 4 bits) and + * background (upper 4 bits) for each cell. + * + * @param color The new color value to be applied to all entries in the buffer. + * This value should be a combination of foreground and background colors using + * vga_entry_color(). + */ +void terminal_setcolor(uint8_t color) { terminal_color = color; } + +/** + * @brief Writes an entry at specified coordinates with a specific character and + * color. + * + * This function places a single character entry into the terminal's buffer, + * combining both the character value (provided via 'c') and its associated + * color (stored in 'color'). The position of this entry is determined by + * passing the x and y coordinates as parameters. The resulting 16-bit VGA entry + * is stored at the corresponding index within the terminal buffer. + * + * @param c The character to be displayed + * @param color The VGA color value for both foreground and background, combined + * using vga_entry_color() + * @param x The x-coordinate (column) where the entry should appear in the grid + * @param y The y-coordinate (row) where the entry should appear in the grid + */ +void terminal_putentryat(char c, uint8_t color, size_t x, size_t y) { + const size_t index = y * VGA_WIDTH + x; + terminal_buffer[index] = vga_entry(c, color); +} + +/** + * @brief Outputs a character to the terminal. + * + * This function writes a single character to the terminal at its current cursor + * position. The cursor is moved one cell right after each write; if it reaches + * the end of the row (VGA_WIDTH), it resets to the beginning of the next line + * (row) and increments the row counter (terminal_row). + * + * @param c The character to be displayed on the terminal + */ +void terminal_putchar(char c) { + terminal_putentryat(c, terminal_color, terminal_column, terminal_row); + if (++terminal_column == VGA_WIDTH) { + terminal_column = 0; + if (++terminal_row == VGA_HEIGHT) + terminal_row = 0; + } +} + +/** + * @brief Writes a string to the terminal. + * + * This function iterates over each character in the provided string and calls + * 'terminal_putchar' for each. The string is assumed to be null-terminated, + * meaning that 'strlen' can be used to determine its length before writing it + * out. + * + * @param data A pointer to a null-terminated string containing characters to + * display on the terminal + */ +void terminal_write(const char *data, size_t size) { + for (size_t i = 0; i < size; i++) + terminal_putchar(data[i]); +} + +/** + * @brief Writes a string to the terminal. + * + * This function is an alias for 'terminal_write', providing a simpler interface + * for printing strings by directly passing in a string literal. The length of + * the string is determined automatically using 'strlen'. + * + * @param data A pointer to a null-terminated string containing characters to + * display on the terminal + */ +void terminal_writestring(const char *data) { + terminal_write(data, strlen(data)); +} + +/** + * @brief Main function for the kernel's execution. + * + * This function initializes the terminal interface and then writes out a simple + * message ("Hello, kernel World!\n") using 'terminal_writestring', which + * automatically handles null-termination. The main loop of the kernel would + * typically include more complex operations but is left as an exercise for now. + */ +void kernel_main(void) { + /* Initialize terminal interface */ + terminal_initialize(); + + /* Newline support is left as an exercise. */ + terminal_writestring("Hello, kernel World!\n"); +} diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..f00fa2d --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,52 @@ +/* The bootloader will look at this image and start execution at the symbol + designated as the entry point. */ +ENTRY(_start) + +/* Tell where the various sections of the object files will be put in the final + kernel image. */ +SECTIONS +{ + /* It used to be universally recommended to use 1M as a start offset, + as it was effectively guaranteed to be available under BIOS systems. + However, UEFI has made things more complicated, and experimental data + strongly suggests that 2M is a safer place to load. In 2016, a new + feature was introduced to the multiboot2 spec to inform bootloaders + that a kernel can be loaded anywhere within a range of addresses and + will be able to relocate itself to run from such a loader-selected + address, in order to give the loader freedom in selecting a span of + memory which is verified to be available by the firmware, in order to + work around this issue. This does not use that feature, so 2M was + chosen as a safer option than the traditional 1M. */ + . = 2M; + + /* First put the multiboot header, as it is required to be put very early + in the image or the bootloader won't recognize the file format. + Next we'll put the .text section. */ + .text BLOCK(4K) : ALIGN(4K) + { + *(.multiboot) + *(.text) + } + + /* Read-only data. */ + .rodata BLOCK(4K) : ALIGN(4K) + { + *(.rodata) + } + + /* Read-write data (initialized) */ + .data BLOCK(4K) : ALIGN(4K) + { + *(.data) + } + + /* Read-write data (uninitialized) and stack */ + .bss BLOCK(4K) : ALIGN(4K) + { + *(COMMON) + *(.bss) + } + + /* The compiler may produce other sections, by default it will put them in + a segment with the same name. Simply add stuff here as needed. */ +} diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..54c56c2 --- /dev/null +++ b/src/version.h @@ -0,0 +1,16 @@ +#ifndef INCLUDE_APPA_OS_VERSION_H_ +#define INCLUDE_APPA_OS_VERSION_H_ + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#define VERSION TOSTRING(APPA_OS_VERSION_MAJOR) "." TOSTRING(APPA_OS_VERSION_MINOR) "." TOSTRING(APPA_OS_VERSION_PATCH) + +#define APPA_OS_VERSION_MAJOR 0 +#define APPA_OS_VERSION_MINOR 0 +#define APPA_OS_VERSION_PATCH 0 +#define APPA_OS_COMMIT_HASH cd01d7d8d36862bc00ae88f7cd185c352a7b7eda +#define APPA_OS_BUILD_TIMESTAMP 1730160636 + +#endif // INCLUDE_APPA_OS_VERSION_H_ + diff --git a/toolchain.cmake b/toolchain.cmake new file mode 100644 index 0000000..a48c330 --- /dev/null +++ b/toolchain.cmake @@ -0,0 +1,17 @@ +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR "i686") + +set(CMAKE_STAGING_PREFIX ${CMAKE_SOURCE_DIR}/toolchain/) +set(CMAKE_SYSROOT ${CMAKE_SOURCE_DIR}/toolchain/i686-elf) + +set(tools ${CMAKE_SOURCE_DIR}/toolchain/bin) +set(CMAKE_ASM_COMPILER ${tools}/i686-elf-as) +set(CMAKE_C_COMPILER ${tools}/i686-elf-gcc) +set(CMAKE_CXX_COMPILER ${tools}/i686-elf-g++) +set(CMAKE_C_COMPILER_WORKS TRUE) +set(CMAKE_CXX_COMPILER_WORKS TRUE) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)