Vystem 0.2

This commit is contained in:
2026-05-27 19:34:54 +02:00
parent a43c08b893
commit d238606b75
372 changed files with 51320 additions and 83217 deletions

98
docs/shelter/irq/gdt.md Normal file
View File

@@ -0,0 +1,98 @@
# GDT
The GDT management is defined in `shelter/lib/include/irq/gdt.h` and implemented in `shelter/lib/include/irq/gdt.c`.
## 64 bits GDT
The 64 bits GDT can contains two types of entries:
- `sh_gdt_GDT_ENTRY_64`: standard GDT entry
- `sh_gdt_GDT_ENTRY_128`: special 128 bits GDT entry for TSS
The standard 64 bits GDT used during the boot process is contained in this structure:
``` C
#pragma pack(1)
typedef struct {
sh_gdt_GDT_ENTRY_64 null;
sh_gdt_GDT_ENTRY_64 kernel_code;
sh_gdt_GDT_ENTRY_64 kernel_data;
sh_gdt_GDT_ENTRY_64 user_code;
sh_gdt_GDT_ENTRY_64 user_data;
sh_gdt_GDT_ENTRY_128 tss;
} sh_gdt_GDT;
#pragma pack()
```
We also use this struct for generating the 10 bytes to use with `lgdt`:
``` C
#pragma pack(1)
typedef struct {
sh_uint16 limit;
sh_uint64 base;
} sh_gdt_GDTR;
#pragma pack()
```
Regarding the API, we provide the following functions:
- `sh_gdt_fill_access_byte(sh_bool present,sh_uint8 dpl,sh_bool descriptor_type,sh_uint8 segment_type)`: generate access byte
- `sh_gdt_fill_flags_byte(sh_bool g,sh_bool db,sh_bool l,sh_bool avl)`: generate flags byte
- `sh_gdt_fill_gdt_entry_64(sh_uint32 limit,sh_uint32 base,sh_uint8 access,sh_uint8 flags)`: fill a 64 bits GDT entry, expect flags as `sh_gdt_fill_flags_byte` would return it
- `sh_gdt_fill_gdt_entry_128(sh_uint32 limit,sh_uint64 base,sh_uint8 access,sh_uint8 flags)`: fill a 128 bits GDT entry, expect flags as `sh_gdt_fill_flags_byte` would return it
- `sh_gdt_fill_gdt(sh_tss_TSS *tss,sh_gdt_GDT *gdt)`: fill the standard 64 bits GDT with the provided TSS
- `sh_gdt_load_gdtr(sh_gdt_GDT *gdt)`: load the standard 64 bits GDT using `lgdt`
- `sh_gdt_reload_registers()`: an ASM stub to reload segment registers
All these functions are intended to be used with the standard 64 bits GDT defined with `sh_gdt_GDT`, which should only be used on the bootstrap CPU
## APs GDT
The APs use a special GDT containing sufficient spaces for the TSS entries of all APs.
For that we use a special shared 64 bits GDT between all APs, defined in this struct:
``` C
#pragma pack(1)
typedef struct {
sh_gdt_GDT_ENTRY_64 null;
sh_gdt_GDT_ENTRY_64 kernel_code;
sh_gdt_GDT_ENTRY_64 kernel_data;
sh_gdt_GDT_ENTRY_64 user_code;
sh_gdt_GDT_ENTRY_64 user_data;
sh_gdt_GDT_ENTRY_128 tss[256];
} sh_gdt_GDT_AP;
#pragma pack()
```
The APs GDT is generated using the function `sh_gdt_fill_gdt_ap(sh_tss_TSS *tss,sh_uint64 tss_count,sh_gdt_GDT_AP *gdt)`. This allows to fill a GDT with up to 256 TSS entries. The GDTR for this shared GDT is generated using `sh_gdt_make_gdtr_ap(sh_gdt_GDT_AP *gdt)`.
## 32 bits GDT
In order for the APs to exit 16 bits mode, they need a 32 bits GDT. Here is the structure for a 32 bits GDT entry:
``` C
#pragma pack(1)
typedef struct {
sh_uint16 limit_low;
sh_uint16 base_low;
sh_uint8 base_middle;
sh_uint8 access;
sh_uint8 granularity;
sh_uint8 base_high;
} sh_gdt_GDT_ENTRY_32;
#pragma pack()
```
The 32 bits GDT used to exit 16 bits mode is structured like this:
``` C
#pragma pack(1)
typedef struct {
sh_gdt_GDT_ENTRY_32 null;
sh_gdt_GDT_ENTRY_32 code_64;
sh_gdt_GDT_ENTRY_32 code;
sh_gdt_GDT_ENTRY_32 data;
} sh_gdt_GDT_32;
#pragma pack()
```
The 32 bits GDT implementation provides the following functions:
- `sh_gdt_fill_access_byte_32(sh_bool present,sh_uint8 dpl,sh_bool descriptor_type,sh_uint8 segment_type)`: generate access byte
- `sh_gdt_fill_granularity_byte_32(sh_bool granularity,sh_bool db,sh_bool l,sh_bool avl)`: generate granularity byte
- `sh_gdt_fill_gdt_entry_32(sh_uint32 limit,sh_uint32 base,sh_uint8 access,sh_uint8 granularity)`: fill a GDT entry for a 32 bits GDT, expect flags as `sh_gdt_fill_flags_byte_32` would return it
- `sh_gdt_fill_gdt_32(sh_gdt_GDT_32 *gdt_32)`: fill the 32 bits GDT structure

68
docs/shelter/irq/gsi.md Normal file
View File

@@ -0,0 +1,68 @@
# GSI subsystem
## Introduction
This subsystem is responsible for registering all the interrupts sources overrides (ISO) and providing an abstraction to manipulate all the GSI lines and legacy IRQs.
## ISOs registering
The subsystem can be initialized using `sh_gsi_iso_array_init()`. This allocate an array of 256 `sh_gsi_ISO` in which all the ISOs will be stored.
The ISO struct is the following:
``` C
typedef struct {
sh_uint32 gsi;
sh_bool valid;
sh_uint8 source_irq;
enum sh_gsi_POLARITY polarity;
enum sh_gsi_TRIGGER_MODE trigger_mode;
sh_uint8 bus;
} sh_gsi_ISO;
```
Any `sh_gsi_ISO` without the valid boolean to `SH_TRUE` should be considered as not occupied in the array.
The `sh_gsi_POLARITY` enum can take the following values:
``` C
enum sh_gsi_POLARITY {
SH_GSI_POLARITY_NOT_SET=0,
SH_GSI_POLARITY_BUS_DEFAULT,
SH_GSI_POLARITY_ACTIVE_HIGH,
SH_GSI_POLARITY_ACTIVE_LOW
};
```
The `sh_gsi_TRIGGER_MODE` enum can take the following values:
``` C
enum sh_gsi_TRIGGER_MODE {
SH_GSI_TRIGGER_MODE_NOT_SET=0,
SH_GSI_TRIGGER_MODE_BUS_DEFAULT,
SH_GSI_TRIGGER_MODE_EDGE,
SH_GSI_TRIGGER_MODE_LEVEL
};
```
Bus defaults are resolved when configuring the IOREDTBL of each IOAPIC.
During the MADT table parsing, ISOs are registered using the `sh_gsi_iso_register(sh_uint8 bus,sh_uint8 source,sh_uint32 gsi,sh_uint16 flags)` function, which expects the arguments as they are parsed in the MADT table.
Two functions can be used to search for ISOs:
- `sh_gsi_get_iso_by_irq(sh_uint8 irq)`: return a `*sh_gsi_ISO` with the provided legacy IRQ number. Return `SH_NULLPTR` if not found or error
- `sh_gsi_get_iso_by_gsi(sh_uint32 gsi)`: return a `*sh_gsi_ISO` with the provided GSI number. Return `SH_NULLPTR` if not found or error
## IOAPIC abstraction
The GSI subsystem provides an abstraction to manipulate IOREDTBL entries based on the GSI or legacy IRQ number without interacting directly with the IOAPIC subsystem. The following functions are provided:
- `sh_gsi_get(sh_uint32 gsi,sh_ioapic_IOREDTBL_ENTRY *entry)`: return the IOREDTBL entry corresponding to the provided GSI
- `sh_gsi_set(sh_uint32 gsi,sh_ioapic_IOREDTBL_ENTRY *entry)`: set the IOREDTBL entry corresponding to the provided GSI with the provided entry. Automatically mask the IOREDTBL entry before writing the entry
- `sh_gsi_irq_set(sh_uint8 irq,sh_ioapic_IOREDTBL_ENTRY *entry)`: do the same as `sh_gsi_set()` but retrieves the original GSI before hand, taking into account registered ISOs
- `sh_gsi_mask(sh_uint32 gsi)`: mask the IOREDTBL entry corresponding to the provided GSI
- `sh_gsi_unmask(sh_uint32 gsi)`: unmask the IOREDTBL entry corresponding to the provided GSI
- `sh_gsi_irq_mask(sh_uint8 irq)`: do the same as `sh_gsi_mask()` but retrieves the original GSI before hand, taking into account registered ISOs
- `sh_gsi_irq_unmask(sh_uint8 irq)`: do the same as `sh_gsi_unmask()` but retrieves the original GSI before hand, taking into account registered ISOs
## Legacy IRQs switch
What we call the legacy IRQs switch is the mapping of all the legacy IRQs to interrupt vectors 32 to 47, exactly like the PIT, and the transfer of legacy IRQs management from the PIT to the IOAPIC.
It's operated by `sh_gsi_irq_switch()`. This is the only function that is allowed to call `sh_irq_switch_irq_management()`. By default, all the IOREDTBL entries corresponding to the GSI lines used by all legacy IRQs will be mask.

49
docs/shelter/irq/idt.md Normal file
View File

@@ -0,0 +1,49 @@
# IDT generation and loading
The IDT management is defined inside `shelter/lib/include/irq/idt.h` and implemented inside `shelter/lib/src/irq/idt.c`. The current IDT generator generate the IDT according to boot process needs, and is therefore not definitive.
## IDT generation
The IDT entry is structured with this structure:
``` C
#pragma pack(1)
typedef struct {
sh_uint16 offset_low;
sh_uint16 selector;
sh_uint8 ist;
sh_uint8 type_attr;
sh_uint16 offset_mid;
sh_uint32 offset_high;
sh_uint32 reserved;
} sh_idt_IDT_ENTRY;
#pragma pack()
```
The full IDT is stored in this structure:
``` C
#pragma pack(1)
typedef struct {
sh_idt_IDT_ENTRY entries[256];
} sh_idt_IDT;
#pragma pack()
```
IDT entries are generated using the following functions:
- `sh_idt_fill_type_attr_byte(sh_bool present,sh_uint8 dpl,sh_uint8 type)`: generate the `type_attr` byte
- `sh_idt_fill_idt_entry(sh_uint64 offset,sh_uint16 selector,sh_uint8 ist,sh_uint8 type_attr)`: generate an IDT entry, expect the `type_attr` argument as `sh_idt_fill_type_attr_byte()` would generate it
The IDT is generated by `sh_idt_fill_idt(sh_idt_IDT *idt)`.
Using the following structure:
``` C
#pragma pack(1)
typedef struct {
sh_uint16 limit;
sh_uint64 base;
} sh_idt_IDTR;
#pragma pack()
```
`sh_idt_load_idtr(sh_idt_IDT *idt)` load the IDT.
The following vectors are filled for the moment: 0 to 21, 28 to 30, 32 to 47, 254 and 255. Please see the [IRQ docs](irq.md) to see how the handlers work.

19
docs/shelter/irq/index.md Normal file
View File

@@ -0,0 +1,19 @@
# IRQ subsystem
## Introduction
The IRQ subsystem contains all things related to interrupts management.
## Overview
The IRQ subsystem manages the following elements:
- [TSS structure generation and loading](tss.md)
- [GDT generation for bootstrap and applications processors, and GDT loading](gdt.md)
- [IDT generation and loading](idt.md)
- [IRQ handlers](irq.md)
- [ISO registering and GSI abstractions](gsi.md)
Please note the following:
- this subsystem is only targeting the x86-64 architecture
- this subsystem should only be used after the full memory subsystem initialization
- all the assets generated by this subsystem is, for the moment, only useful for the task performed during the boot process

126
docs/shelter/irq/irq.md Normal file
View File

@@ -0,0 +1,126 @@
# IRQ handlers
The current IRQ handlers are defined in `shelter/lib/include/irq/irq.h` and implemented in `shelter/lib/src/irq/irq.c`. The current handlers are tailored to fit the needs of the boot process and are therefore not complete at all.
## Overview
All IRQ handling is done through the macros defined inside `shelter/lib/src/irq/irq_handler.asm`.
The current IRQ handlers are currently capable of managing three categories:
- CPU faults
- Legacy IRQs
- LAPIC interrupts
The main dispatching is done inside `sh_irq_dispatch(sh_irq_INTERRUPT_FRAME *frame)`. The `frame` argument is built by pushing registers on the stack in the ASM stub and look like this:
``` C
typedef struct {
sh_uint64 r11,r10,r9,r8,rdx,rcx,rax;
sh_uint64 vector;
sh_uint64 error_code;
sh_uint64 rip;
sh_uint64 cs;
sh_uint64 rflags;
} sh_irq_INTERRUPT_FRAME;
#pragma pack()
```
This frame will probably be modified/expanded in futures updates.
When we say "block", we mean a simple infinite loop.
## CPU faults
The following table provide the informations related to CPU faults handling:
Name | Behaviour
-----|----------
Division by zero | Log and block
Debug | Log and block
NMI | Log and block
Breakpoints | Log and continue, usage of breakpoints isn't supported but it won't block the kernel
Overflow | Log and block
Bound range | Log and block
Device not available | Log and block
Double fault | Log and block
Co-processor segment overrun | Log and block
Invalid TSS | Log and block
Segment not present | Log and block
Stack segment fault | Log and block
General protection fault | Log and block
Page fault | Log and block
Alignement check | Log and block
Machine check | Log and block
SIMD floating point exception | Log and block
Virtualization exception | Log and block
Control protection exception | Log and block
Hypervisor exception exception | Log and block
VMM communication exception | Log and block
Security exception | Log and block
CPU faults are the only exception where the frame will be presented.
## Legacy IRQs
In Shelter, legacy IRQs are remapped on vectors 32 to 47. Depending on vector, two behavious are possibles:
- If vector is 32, the IRQ come from the PIT, starting a specific procedure depending on the context
- If vector is between 33 and 47, the IRQ is treated by registered IRQ handlers
Legacy IRQs have two main state:
- Managed by PIC: in this case, they are just logged and then ignored, except for the PIT IRQ0 depending on the context
- Managed by IOAPIC: in this case, PIT IRQ0 are logged and then ignored, and others IRQ are delegated to drivers handlers
The switch between PIC and IOAPIC is effectuated by `sh_irq_switch_irq_management()`. Only after this switch is correctly done, drivers handles can be registered using `sh_irq_legacy_register_handler(sh_uint8 legacy_irq,sh_irq_HANDLER_PTR handler)`. Handlers can't be registered for IRQ 0. Once a handler is set, it can't be removed or updated. The `sh_irq_legacy_register_handler()` function automatically unmask the GSI corresponding to the legacy IRQ.
The type `sh_irq_HANDLER_PTR` is defined like this:
``` C
typedef void (*sh_irq_HANDLER_PTR)();
```
No arguments are provided.
### PIT IRQ
The PIT is mainly used to estimate the CPU frequency. For this, each PIT IRQ is handled differently depending on the context.
The `sh_irq_start_tsc()` function signal to the IRQ handler that the CPU frequency estimation procedure has been triggered, setting the internal state of the procedure to 0, and reseting the TSC values (`tsc_start`, `tsc_end` and `tsc_delta`).
When a PIT IRQ arrives:
- If the legacy IRQs are managed by IOAPIC, simply log and continue
- If not, do the following:
- If the state of the estimation procedure is 0, set `tsc_start` to the current TSC register value and set the state of the procedure to 1
- Else if the state of the estimation procedure is 1, set `tsc_end` to the current TSC register value, compute `tsc_delta = tsc_end - tsc_start` and set the state of the procedure to 2
- Else, log and continue
The `sh_irq_get_tsc_delta()` return `tsc_delta`. A value of 0 indicating that the procedure isn't finished yet.
### Others legacy IRQs
Others legacy IRQs (vectors 33 to 47) are handled through a common loader that work like this:
- If the legacy IRQs are managed by IOAPIC:
- If a handler pointer is defined for this legacy IRQ, call it and continue once finished
- Else, just continue without logging
- Else, log and block
## LAPIC interrupts
LAPIC interrupts can be of two sorts:
- one shot timer: mapped on vector 254
- spurious vector: mapped on vector 255
### One shot timer
The purpose of this handler is to know when the one shot timer generated an interrupt.
Each CPU maintain in its per-CPU struct a boolean named `timer_state.`
The `sh_irq_start_timer()` function is executed by the function to arm the `timer_state` flag: it set this flag to `SH_TRUE`.
When an one shot timer interrupt arrives:
- If `timer_state` was `SH_FALSE`, log the interrupt for debug purposes, it doesn't block anything
- If `timer_state` was `SH_TRUE`, set it to `SH_FALSE` and then resume
The `sh_irq_get_timer_state()` function is a wrapper that return the local `timer_state` of any CPU.
### Spurious vector
In case of interrupts on the spurious vector, we just log it for debug purposes and continue.

11
docs/shelter/irq/tss.md Normal file
View File

@@ -0,0 +1,11 @@
# TSS structure generation and loading
The TSS management is defined in `shelter/lib/include/irq/tss.h` and implemented in `shelter/lib/include/irq/tss.c`.
This implementation provides the following types:
- `sh_tss_TSS`: a full structure of a 64 bits TSS
- `sh_tss_TR`: a structure used to load the TSS
The following functions are provided:
- `sh_tss_fill_tss(sh_tss_TSS *tss)`: fill a TSS structure with 4 stacks dedicated for double faults, NMI, page faults and stack-related faults. Heap must be available for using this function. All stacks are 4 pages long
- `sh_tss_load_tr(sh_tss_TSS* tss)`: load the provided TSS into the task register. Always assume that the TSS selector in the GDT is `0x28`