#tabula imaginarium: osdev experiments in zig

1 messages · Page 2 of 1

haughty cipher
#

i was making all the entries global so when the recursive mapping treats them as PTEs they are global

#

but evidently kvm and whpx dont like that

placid swallow
#

Which manual, there are slight differences between them and I wouldn't be surprised if this is an Intel vs and quirk

haughty cipher
#

I'll have to dig up the amd one to see what it says

#

been using the intel manual since I find it more readable for reference stuff

placid swallow
#

Yeah I default to the Intel manual

#

I just find it more pleasing to look at while reading lol, please don't judge me

#

But there are some subtle differences in a few places

haughty cipher
haughty cipher
#

it's def annoying because I can't make the recursive mapping global but whatever

#

at least now I know

haughty cipher
#

time to add a print thatll trigger an ungodly amount of times for debugging lmao

#

it just goes on and on and on and on lmao

last pebble
#

brings me back to my rbtree debugging sessions

#

of figuring out where it went wrong in the 20k lines of logs

haughty cipher
#

well im at 25k lines and counting

#

i can probably kill qemu and remove the print now

#

just wanted to make sure it was going through everything and ive got enough of a sample for that now

#

and the serial out to log this slows it down so much lmao

#

nvm it finished lol

#

26061 lines of that

#

replaced it with just a count in the kernel that it logs lol

#

the 0 unexpected state is nice

haughty cipher
#

and then i had to spend a hot minute figuring out a dumb bug where the compiler was like oh the limine requests never get modified and this is a static executable ill just inline the access to the response pointer to be always null and always panic lmao

#

got that figured eventually at least

#

thank goodness for std.mem.doNotOptimizeAway ig lol

#

anyway i think im actually at the point i might be able to start working on the actual page allocator bit

floral prawn
haughty cipher
haughty cipher
haughty cipher
#

at some point i gotta figure out how to swap both cr3 and the stack pointer at the same(ish) time safely

#

the new stack is in the new mappings but not mapped in the old mappings is the issue

#

if i can do that i can actually take my bsp kernel stack out of my actual kernel's data section too

#

ig my best option will be to load both addresses into gp registers and then load both properly from that

haughty cipher
#

ok the semi-new plan for memory management:

  • a simple allocator based on keeping an expandable free list of pages to be used for small object heap. this is to be implemented by having the first 8 bytes of each free page be a SinglyLinkedNode in an atomic linked list for the fast path, and the slow path allocates a new page directory of free pages from the block of virtual memory space reserved for the pool. this simple page allocator backs a GeneralPurposeAllocator which backs a number of MemoryPools
  • pool is used among other things to allocate virtual address tree nodes for the main page allocator, which is capable of multi-page allocation and both bottom-up and top-down allocations. the bottom-up version backs a GeneralPurposeAllocator, and the top-down version is used for stacks.
  • the virtual address tree is a struct itself so multiple address spaces can be managed - this to accomodate both paged and nonpaged allocations (nonpaged only for now as i dont have disk io yet)
#

the small object heap will probably be made available to general kernel things as well, not just for pools, but its not suitable for everything as any allocation larger than a page will always fail from that allocator

#

@last pebble btw, does uacpi_kernel_alloc need to handle large (> PAGE_SIZE) allocations?

last pebble
#

Yes

haughty cipher
#

then thatll have to run on the main address space thing

#

which is good tbh. most of the things im doing in my main kernel so far are pooled so uacpi will let me easily test the main alloc without having to swap things over that dont need it

#

my driver stack will probably use that too though for driver objects

#

though i can probably make driver objects globals tbh

#

this whole mm thing took me forever because it took me a while to mentally process how to allocate the tree nodes

#

i also have a syspte space, inspired by (aka like 75% a port of) the reactos one, which i use for temporary mappings of either io or other pages

#

e.g. if you request a zero page instead of a free page from the pfmdb and it only has undefined-content free pages, itll use a syspte to map it for the @memset and then unmap before returning the PFI

#

terminology:

  • pfi = page frame index aka index into the pfmdb aka PFN_NUMBER aka phys_addr >> 12.
  • pfm = page frame metadata aka struct page aka MMPFN.
  • pfmdb = a global array of pfms, where a pfm struct is guaranteed to exist in memory if the page is usable (theres a bitmap to check which pfms are real so the syspte stuff etc can properly refcount normal pages without having to create a full pfm for every possible physical page that might not actually exist)
haughty cipher
#

OH GOD THE COMPILER IS REORDERING THINGS

#

debugging page allocators is pain evidently

#

i added a log and the behavior changed

#

though it just changed from doing the second iteration of the loop first to doing the first iteration first

#

oh i was looking at this all wrong

#

the page fault error code was nonzero so i checked the other bits and thought it was a write to a nonwritable page

#

but the code is 2 not 3 so the page isnt present

#

oh god is this a TLB thing

#

idk why itd be caching that page as non present

#

IT ISSS

#

fuck

#

i should probably actually write an invlpg thing so i dont just reload cr3 every time

haughty cipher
#

nvm im stupid

#

non-present PTEs dont get cached

haughty cipher
#

OH FUCK THATS WHY

#

its not removing the pfm from the free list right

#

so it just grabs the same page twice

#

same physical page that is

#

for both the pdpt and the pd

#

now thats fixed at least

#

I CAN ALLOCATE THINGS \o/

haughty cipher
#
--- a/src/krnl/hal/mm/pfmdb.zig    (revision HEAD)
+++ b/src/krnl/hal/mm/pfmdb.zig    (revision Staged)
@@ -145,8 +145,8 @@
         pfm._1.alloc_end = false;
         pfm._1.status = self.associated_status;
         pfm._3.flink.next = terminator;
-        if (self.last != terminator) {
-            pfm._2.blink.prev = self.last;
+        pfm._2.blink.prev = self.last;
+        if (self.last != terminator) {
             pfm_db[self.last]._3.flink.next = pfi;
         } else {
             std.debug.assert(self.first == terminator);
#

this was the single most annoying bug to find lol

#

this... this is progress

#

well technically its not because this used to work on old mm

#

but it gets pretty far on new mm\

#

just gotta figure out why this is index out of bounds ing on the map_io

#

and also figure a way to make it so i can not leak mmio space lol

haughty cipher
#

the way ive got this set up now (which is the third iteration on mm design lmao):

  • a region of virtual memory is dedicated to nonpaged pool (name comes from winnt things but doesnt matter)
  • this is implemented as a buddy allocator of virtual memory.
  • the pfmdb's pfm._3.share_count is canniballized to be used as a free/not-free flag for buddies since that field is normally only used for swappable page refcounting
  • the smallest unit is one page and the biggest unit is 2M (one PDE, though its mapped using 4k pages since the pfmdb cant easily get contig. phys pages
  • if the allocator is out of top-order blocks it maps the next 2M from the address space until that runs out (which is a kernel panic) and then splits as normal for a buddy allocator
#

the nonpaged pool is used to implement the vtable of a zig std.mem.Allocator which is then used as the backing page_allocator for a std.heap.GeneralPurposeAllocator which is used for most things though the page allocator can still be used directly for large allocations if need be

#

and then i use a syspte type setup for mmio mappings which is currently broken lmao

#

i think zig's GPA is a slab allocator but im not smart enough to understand it and be certain

haughty cipher
#

ah fuck my syspte space is borked

#

time to printf spam ig

haughty cipher
#

really wish i wasnt always missing stack frames when tracing from an interrupt handler

haughty cipher
#

adding a log before i start tearing this apart

#

but hey maybe my hunch will be right

#

it isnt

#

fuck

polar pilot
#

rsp is pushed by cpu and rbp (hopefully) by you

haughty cipher
#

yeah ill just have to modify the zig stack iterator ig

polar pilot
#

no you won't

#

iirc you can create one from rsp+rbp

#

std.debug.StackIterator.init(@returnAddress(), @frameAddress())

#

close enough

haughty cipher
#

ah right

#

itd be null, rbp

polar pilot
#

why would it be?

#

are you building in one of the release modes?

haughty cipher
#

first_address being null means just get the first address it finds

#

which is the first param

polar pilot
#

but why would rbp be null is my question

#

you can just StackIterator.init(frame.rip, frame.rbp)

haughty cipher
#

no i mean the first param is null

polar pilot
#

ah

polar pilot
#

that should let you trace from the point where CPU left off

haughty cipher
#

ah cool

#

thanks!

polar pilot
#

np

#

i haven't personally tested that but

#

it should work

haughty cipher
#

i mean i can quickly check lol

polar pilot
#

it's really as if you did @returnAddress() and @frameAddress() right before you got interrupted

haughty cipher
#

yeah

#

i am running in releasesafe fwiw

#

passing rip for the first param didnt work btw

polar pilot
#

:(

#

it should

haughty cipher
#

i think its because rip is the next frame up from where rbp is

#

so it never gets to rip when iterating

#

the first_address means skip until you get there and that never happens because its starting at the return address of whatever rip is in

haughty cipher
# polar pilot it should

secret hack: preinitialize the trace with rip as the first addr and then iterate starting at offset 1 :)

#

sadly still getting the same set of frames

#

except instead of my iret being on top the interrupt rip is

haughty cipher
#

well i got it mostly working now at least

#

map_io/unmap_io works and the nonpaged pool allocator seems to mostly work

#

though it eventually panics in the allocator somewhere so ill need to debug that more later

#

but progress!

last pebble
haughty cipher
#

the big problem rn is the panic in the allocator is it tripping a debug assertion somewhere inside the std's generalpurposeallocator and thats annoying

haughty cipher
#

and idk which assert because its the debug asserts which are implemented as

if(!cond) unreachable;
#

which just triggers a panic with "unreachable code reached" in safe modes and is a noop in unsafe modes

#

and freestanding debug info loading is iffy sadly

#

i might be able to make limine provide the debug file as a module though and then load it somehow tbh

placid swallow
#

worst case you can always output the return address (if you implement unreachable() as a function), or the instruction pointer if its not

#

it'll give you something

haughty cipher
#

unreachable is a keyword

#

the rip helps a bit but the compiler decided wait all these panic calls look the same let's merge them so I have no idea which one tripped

alpine mortar
#

tbh I think it's best as a macro

#

oh wait nevermind, I thought this was one of the langdev projects xD

haughty cipher
#

oh no lol this is using zig

haughty cipher
#

turns out fixing my stack trace addresses is fairly easy thankfully

#

sadly the PRs im waiting for to be able to dump my own stack trace printing code are not yet merged

haughty cipher
#

oh cool the compiler segfaults now, fuck

haughty cipher
#

id just updated zig too and suddenly rip

#

i may start ripping up some of my code though and if that eventually fixes it thats great

#

thats what i get for using dev versions ig

haughty cipher
#

yknow what i got some energy time to compile my compiler in debug mode

#

the way building zig from source works is kinda wild tbh

#

theres a minimal version of the zig compiler in wasm in the repo

#

first thing it does is convert that wasm to c and then compile it with the host c compiler

#

then it uses that to compile a slightly more complete compiler

#

and then that to compile the full thing

haughty cipher
#

ok the compiler is panicking in my read_madt code now that ive got the compiler in debug mode

#

(the panic is an unreachable which gets optimized away in release modes which then led to UB and a segfault)

haughty cipher
#

yup commenting out the call to read_madt worked

#

well "worked"

haughty cipher
#

a bunch of other shit is unhappy because i dont parse the madt anymore

#

but at least it doesnt segfault the compiler now ig

#

found the error in my code that was triggering it (shouldve been a compiler error whoops)

#

IT WORKS

#

very slowly

#

but IT WORKS

lofty shoal
haughty cipher
#

there's a build command to update it from the source

lofty shoal
#

interesting

haughty cipher
#

it gets converted to c and then run (its wasm because that way it doesnt have to go to c in theory) and that has just enugh to compile a stripped version of the compiler which can only output c

#

that then gets used to compile the full compiler

#

which in theory should then be idempotent if it compiles itself

haughty cipher
#

ok time to test a basic ACPI processor object driver (it literally just attaches a pointer to the device object to the lcb for now)

haughty cipher
#

just realised im allocating my LCBs twice whoops

#

also really ought to make a decision if i want to stick with LCB or swap to calling them PRCBs

#

or some other name

haughty cipher
#

fuck

#

i put the bit size of a thing instead of the byte size and that was my pci issue

#

realised because comming out the ecam bit fixed that so i knew to look deeper there

#

there we go, nothing reporting no HID/CIDs anymore

#

(no HID/CID would mean that the ACPI driver reported it based on a device object that only has ADR and no other bus driver picked it up, which in this case is because my pci driver was a factor of 8 off on the numbering of things while using ECAM access and thus the addresses never matched)

haughty cipher
#

next project: slab time

haughty cipher
#

contender for least hinged line in my codebase btw

#

whatre you doing? oh just exporting a thing named kstart3 with the symbol name kstart2

haughty cipher
#

ah yes, the CRIME bit

#

offset 0x14, bit 24

#

the crime bit

#

oh yeah i should probably fix my pci helpers to use the a register always

dreamy sage
haughty cipher
#

yes

polar pilot
#

yeah, some sources say you should do that but i don't think any real machine enforces that

haughty cipher
haughty cipher
#

but i want to swap most of my mmio to use inline asm anyway at which point its easy to enforce that register

#

rn its all pointer access

#

which is like fine but might break if the compiler uses the wrong instructions

dreamy sage
#

@fair dragon

haughty cipher
#

ok i think i really need to implement BAR stuff in general before i can implement an nvme driver

#

so figuring that out will have to be the first step here

#

which means i ought to figure out device resource querying infrastructure

haughty cipher
#

will say the biggest benefit to nvme so far is just that the spec is free and open online

#

looking at you, pci

haughty cipher
#

wonder how good i can get this inline asm to generate

#

ideally i want this to generate as offset(%reg) if the offset is known as an immediate or as (%reg, %reg, 1) if not

haughty cipher
#

ok does anyone here understand the "m" constraint for inline asm?

polar pilot
#

yeah what's up?

#

well

#

"understand" meme

#

i can try and help though

haughty cipher
#

do i do asm volatile(... : [name] "m" (&thing)) or asm volatile(... : [name] "m" (thing))

polar pilot
#

i believe the latter

#

i think it wants a reference, so just thing should work as expected

haughty cipher
#

ok

#

im trying to pass an array indexing expression to it and hoping that works lol

polar pilot
#

yeah that should work

haughty cipher
#

ok

polar pilot
#

assuming zig's inline assembly tries to resembly C in any way

haughty cipher
#

oh right i should turn my logging to debug here lol

polar pilot
#

(as it should)

haughty cipher
polar pilot
#

yeah it should work exactly the same then

#

so any memory lvalue will do

#

or lvalue in general, im pretty sure passing a stack variable to an m constraint will allocate it on stack

haughty cipher
#
const value = asm volatile (
    \\ movl %[ptr], %[out]
    : [out] "={eax}" (-> u32),
    : [ptr] "m" (block[offset / 4]),
    : "memory"
);
// const value = block[offset / 4];

new uncommented code isnt working but the commented out bit is

#

block is a pointer to an array of u32s

polar pilot
#

huh that's weird

haughty cipher
#

(pointer to array because i know the length of ECAM so i can make it that instead of a slice or many pointer)

#

yknow what

#

gonna try [ptr] "r" (&block[offset / 4])

#

see if its just the m constraint being fucked in zig

polar pilot
#

that will work but you have to dereference it in the inline assembly template

#

by nooby of course

#

lmao

haughty cipher
#

oh lmao of course

#

i think i remember nooby telling me to avoid the m constraint too

polar pilot
#

frickin zigbugggg

haughty cipher
#

well i can do (%[base], %[offset], 4) ... : [base] "r" (block), [offset] "r" (offset / 4) now at least

#

the whole reason i wanted m constraint was to have that use offset(%reg) if reg was doable as an immediate tho

#

but fine ig

#

oh btw do i need to out to the addr register repeatedly for legacy pci access if the addr doesnt change?

polar pilot
#

i don't think so

#

but i can't tell you for sure

haughty cipher
#

alright

#

oh huh linux is cooking somethin weird with their legacy pci access code

#

say youre writing a u16 to offset 0x12

#

common advice is to outl port 0xCF8 to 0x10, do an inl at 0xCFC to get 0x10..0x13, do some bit math to replace the 16 bits you want, and then do an outl with the full 32 bits

#

linux says no, we're going to outl port 0xCF8 value 0x10, and then outw port 0xCFE the u16 they want to write

#

a 32 bit would be 0xCFC, 16 bits to either 0xCFC or 0xCFE, 8 bits to any of CFC..CFF

#

and same for reads

#

but with in instead of out

#

the hell are those nerds cookin

haughty cipher
#

sometimes i feel like going ham with partial staging and push 10 well named commits

#

other times i name the commit AAAA and push it all at once

#

today is of the former kind it seems

lost hawk
haughty cipher
#

same tbh

#

and scrolling down a bit we got idk shortly followed by idk2

#

anyway my brain is starting to get fried so its time to call it a night

#

i started work on nvme and changed a single bool to speed my uacpi init up by 500x

#

good enough for a days work

haughty cipher
#

alright its nvme day 2

#

which is to say reading a spec day 2 and/or device resources infrastructure day 1

haughty cipher
#

really not sure how i want to structure device resource querying in my io stack

haughty cipher
#

actually nvm i got idea

#

my big thing was not being sure how to store/cache the resource list

#

and then i realised that i can just make the driver do it

#

device driver can filter down what it actually cares about anyway

#

and with that the enumerating resources (BAR config figuring out, acpi _CRS method, etc) being a theoretically kinda expensive operation isnt a big deal

#

mostly the acpi bit is the expensive part but like

#

yeah

haughty cipher
#

i ended up adding a fourth io dispatch return status enum value

#

"partial"

#

might have to add partial_pending too

#

the possible non-error return codes from a driver's io dispatch routine before the addition were complete, pass, and pending

#

pending means its async from the driver's pov (which could just be signalling an event so blocking from the caller pov instead of callback based async io)

#

pass means i got nothin give it to the next lower driver

#

and complete is fairly self-explanatory

#

partial would be for enumerating lists of things, say i got this stuff but the next lower driver might have more

#

like a thing on the pci bus enumerating resources that also has an ACPI device object, the device driver says pass, the pci bus driver puts in the BARs and pci interrupt stuff and returns partial, and the acpi driver checks _CRS to fill out more of the list and also returns partial

#

io.execute_irp marks if it gets any partials when iterating but otherwise treats partial like pass

#

and if it gets to the end of the driver chain with all drivers passing thats an error, but if any of them said partial then its complete once its at the bottom of the driver stack

#

i may yet switch to recursion for passing the irp along though

#

so a driver can swap out the irp and then do post-processing to restore state after

#

whatever i do for resource enumeration will be only the second function code i add though

#

rn the only one is query property which takes a property uuid and an out pointer in the parameters

#

kinda funny that read and write will be so late on the list of io functions i add support for lmao

#

ok yeah def doing the recurse version i think

#

its a bit closer to how windows does it but in this case i dont think thats a bad thing per se

haughty cipher
#

looks like i wont actually get anything done today, rip

haughty cipher
#

tbh not getting stuff done until next week most likely

haughty cipher
#

turns out took longer than that even lol

#

also i should really rip apart my docs folder because theres some very no-longer-correct stuff in there lol

haughty cipher
#

update: migrated to split-out zuacpi and got everything working

#

also split up my acpi enumerating stuff a bit so that the _SB namespace gets its own device object

#

and lastly fixed a bug whereby uacpi's install_interrupt_handler was failing to back-update the cpu-side vector so it was trying to install the IDT handler always at vector 0 which is obviously wrong

timber moth
#

nice

haughty cipher
#

also (inspired somewhat by some stuff in #1326937732122935379) i may end up doing some absolute linker nonsense sometime soon to decouple percpu variables from a central prcb structure by just making the prcb structure itself a percpu variable on some particular scheme

#

the idea:

pub fn define_percpu(comptime T: type, comptime default: ?*T) type {
    return struct {
        var value: T linksection(".percpu") = if(default) |d| d.* else undefined;
        pub inline fn offset() usize {
            return @intFromPtr(value);
        }
        pub inline fn current() *addrspace(.gs) T {
            return @ptrFromInt(offset());
        }
        pub inline fn on(cpu: u32) *T {
            return @ptrCast(&prcbs[cpu][offset()..].ptr)
        }
    };
}

something like this on the zig side, and then use

percpu_start = .;
percpu 0 : AT(percpu_start) {
    *(.percpu)
    . = ALIGN(4096);
} : percpu =fill
. = percpu_start + SIZEOF(percpu);
#

and then at mm init i can use externs to get the percpu block initial value and load it in, or maybe get limine to provide the file for the executable and use that

#

i might also just be able to do a
var value: T linksection(".percpu") addrspace(.gs) for all i know tbh

haughty cipher
#

maybe time to resurrect this

#

updates since the last time i posted:

  • acpi device resource enumeration is a go
  • updated to the latest version of @hoary aurora's excellent disk-image-step for building the disk image for testing. it still cant do GPT disks yet though so no uefi yet
  • idk there might be other things im forgetting
#

also i blame infy that my next project on this os is liable to be a log ring lmao

hoary aurora
#

yay, progress!

#

i gotta do post some uodates on ashet os as well soon

haughty cipher
#

crosslinking #1367927268294397982 for the disk image building tool as well

haughty cipher
#

only warnings im logging now are "no driver found for device xxxx"

#

which is a nice place to be

#

of those most are for PNP0C0F

last pebble
#

now try real hardware trl

haughty cipher
#

I dont have an easy way to rig up a uefi capable image and my only loggins is 0xE9

#

so maybe someday

#

thats actually part of why a log ring is on my todo list, just so i can have backlogs saved to then dump to serial once my serial driver is up and going

#

and i should figure out a framebuffer too tbh

haughty cipher
#

also i should make a futex primitive if only because the zig std sync primitives can do all the work on top of a futex

haughty cipher
#

yknow if i wasnt feeling like making a lockless log ring a condition variable would be great for synchronizing it

haughty cipher
#

yknow if im going to start doing framebuffer stuff again i should probably actually map the framebuffer somewhere

#

i think i did something backwards

#

thats better, got a font rendering now

#

now to make a log ring and wire it in

polar pilot
#

why not flanterm

#

it's a relatively complete implementation and it's pretty well optimized

haughty cipher
#

my kernel is written in zig and the fact that i have a single full external dependency in uacpi already bothers me a bit

#

i dont want to make bindings for flanterm and even if i was willing to make bindings i want nothing to do with any more dependencies

polar pilot
#

bindings for 2 C functions is a bit of an overstatement

#

but yeah the "purity" argument is fair

haughty cipher
#

(also this kernel is meant largely as a learning exercise and i aint gonna learn anything if im just pulling in libraries for everything)

alpine mortar
#

also writing your own tty is just fun, with stuff like ansi csi etc

haughty cipher
#

yeah

#

the goal here is learning and fun, not trying to get a maximally featured thing as fast as possible

haughty cipher
#

wondering if theres a reason linux's printk ring stores info in a separate array/ring as the descriptors

last pebble
#

There's a commit not that long ago that changed that

#

Just less stack usage because most code doesn't care about this info

#

Also more cache friendly I guess

haughty cipher
#

MultiArrayList moment for me then probs

last pebble
#

Zig has that?

haughty cipher
#

yeah it rules

#

takes a struct and automatically generates separate lists for each field

#

though its not actually a great fit here since its only meant as an arraylist so for a static sized array its less usable

haughty cipher
#

you can get slices and access each field individually and theres a function that reassembles the struct from the fields at a given index too

#

its a great use of comptime

#

it also does stuff with tagged unions but that just splits out tag and the untagged union payload as separate arrays so ive not found it as useful

#

i made a bounded version for imaginarium that basically is the same but uses a statically allocated buffer instead of taking an allocator to use

last pebble
#

Interesting

haughty cipher
#

ill probably do things semi-manually instead of using multiarraylist for the log message info tbh

#

though i could reuse my MultiBoundedArray and ignore its resize-within-fixed-capacity ability

#

just init it at full size immediately

#

yknow what fuck it making a MultiArray type that doesnt do resizing for this purpose

haughty cipher
#

tfw i manage to make the compiler print out a 32768-entry array in an error lol

#

not kidding btw

hardy cloud
#

wtf is that XD

haughty cipher
hardy cloud
#

lol

haughty cipher
#

the types are different because the statically known array length is different and it goes and prints out the whole dang thing in the error

#

idk what it says above that mess because it overran the length of my terminal's buffer

#

the actual issue is the "entries - 1" should be "entries - 2"

storm wind
# haughty cipher not kidding btw

this is the zig equivalent of std::unordered_map<std::string, std::string>::const_iterator (std::unordered_map<std::basic_string<char>, std::basic_string<char>, std::hash<std::basic_string<char>>, std::allocator<std::pair<std::basic_string<char>, std::basic_string<char>>>>::const_iterator)

haughty cipher
#

lol

#

dear god atomics are so confusing sometimes

haughty cipher
#

can already tell this is going to be one of those bits where i end up writing a ton of comments on how it all works before i even touch actual implementation

last pebble
#

Yeah this thing feels like one of those things where I start hitting the limitations of my brain

#

Like when I was implementing rb trees

haughty cipher
#

god trees destroyed my brain yeah

#

i ended up doing AVL trees instead of RB trees because i understand them marginally better

#

if i figure out this log ring stuff ill be happy to help you figure it out for your end lol

last pebble
#

Yeah ill share my findings as well

#

Although ill probably start doing it a bit later, after doing mm bringup

haughty cipher
#

ah yeah mm bringup is important

last pebble
#

Currently I'm porting an early memory map allocator from my loader

#

So I can allocate struct pages etc

haughty cipher
#

mhm

#

i ended up taking a lot of inspo from reactos for my mm bringup

last pebble
#

But excited to see how your log ring turns out

haughty cipher
#

though i still kinda want to make a prekernel like 50% for mm bringup being cleaner there and better management of kernel pages

steep spear
haughty cipher
# last pebble What does it do?

starting from limine hhdm:

  1. in a static buffer in the data section, allocate copies of bootloader-provided information
  2. allocate new page table root and set up for recursive page tables
  3. using memmap allocation, map the kernel in the new page tables
    4 my bootstrap thread stack is part of the kernel data section even though i use limine so its now fine to switch to the new CR3
  4. allocate SYSPTE space and PRCBs (which i call LCBs), since those have static size and virtual address
  5. allocate the PFMDB (aka list of struct page) and map it and its bitmap (which is used so i dont need a struct page for every mmio or reserved page)
  6. put free pages except for the block im allocating from into the PFMDB
  7. put the remaining free pages from the allocation memmap entry into the PFMDB. this prevents allocating phys pages until the next step is done
  8. fill out pte back-pointers in the pfmdb and fix any incorrectly-free pfmdb entries to make the pfmdb free list usable
  9. give the nonpaged pool its first PPE worth of physical pages to run with, after which normal kernel memory allocation is usable
  10. not yet implemented: reclaim the .init section and any other bootloader reclaimable stuff
#

the reactos version starts with the PRCBs and the basic recursive page table (with kernel mappings) already provided by the loader

#

allocating the PRCBs is actually the only reason i need early table access from uacpi fun fact

haughty cipher
last pebble
#

What's a PRCB?

haughty cipher
#

processor control block

#

the percpu structure

last pebble
#

And why do your struct pages need a bitmap?

haughty cipher
#

at any given time theres an address that is an array of them indexed by LAPIC id and GSBASE points to the current cpu's one

haughty cipher
# last pebble And why do your struct pages need a bitmap?

the bitmap is set to 1 for normal memory pages and 0 otherwise. lets me avoid allocating pages worth of structs for reserved/unusable physical pages without having to also then do lazy mapping of them or whatever to allocate entries for mmio pages

#

windows and reactos both do that too

last pebble
#

I'm a bit confused, why cant that be sorted out at pfndb creation time?

#

Why would you have to allocate struct pages for them

haughty cipher
#

this mostly guards against accessing struct pages for pages that dont have them

last pebble
#

Would a page fault not suffice?

#

Like when would they ever be accessed

haughty cipher
#

the same code that does mmio mapping also does temporary mapping copies of other pages for stuff like io in case the process with an io target buffer page isnt running, and for additional mappings of normal memory it needs to refcount the phys pages so they dont get swapped out

last pebble
#

Is this all workarounds because no hhdm?

haughty cipher
#

kinda yeah

last pebble
#

Damn

#

Is it really worth it

haughty cipher
#

idk

#

but i feel like i learned something from it all so maybe

last pebble
#

Yeah

haughty cipher
#

also again this is how reactos does it and im pretty sure also how NT does it

last pebble
#

I believe you

#

Just didn't know about the bitmap

haughty cipher
#

yeah it took me a minute to figure out why they did it and then i ran into where it helps when implementing my map function

last pebble
#

Makes sense

haughty cipher
#

@last pebble btw do you happen to know how kprint sinks get notified when new entries are available? is that even a thing that happens or do serial log sinks run separate from the log ring? started to wonder and the only thing i could think of is some sort of blocking event object which would kinda hurt the whole lockless premise (though ig locks still not needed just notify)

last pebble
#

Goes through every console and prints to it from the last seqnum to current

#

This only happens if it managed to acquire the console lock

#

There's a separate work queue to do logging to consoles though

#

If u scroll prink.c you will see all of that stuff

last pebble
#

but in theory they should all be caught up

haughty cipher
#

alright, thanks

haughty cipher
#

ah i see, neat

haughty cipher
#

next thing, even before log ring, is adding a way for the bootloader to request the mm init to map chunks of memory

#

which will be used at first to map the kernel binary file and (if the file exists in the disk) the separate debug info file

#

which will then allow stack traces that arent just the address

#

also i might be able to use it for other stuff too

#

framebuffers etc

haughty cipher
#

oh neat found a bug in the zig std

#

in the stack trace unwinding code even

#

they handle signed eh pointers by casting the base addr to i64 which generates a panic when its a higher half address

#

im not 100% sure why this isnt getting source location info, might be some jank with how ive got the debug loading hacked in, but even getting symbol names is a nice improvement rn

#

oh thats why'

#

i had my page allocator doing one PDE at a time and thats only 2M

#

and it wanted more

#

ok so that last one is a bit off

#

but im not surprised it has no clue what to do about address 0

haughty cipher
#

as for how this works, i use the kernel file and internal modules features of limine to get the kernel binary and optionally the external debug symbols file

#

if it has both it uses the external debug file to get full info

#

if not it uses the symtab and stuff that are in the main elf file

#

and in all cases uses eh_frame stuff instead of just frame pointer now

haughty cipher
#

the only major updates to stuff outside of the debug/trace code to get that working:

  • add a function for the boot protocol handling code to request mapping large blocks of pages (todo: memory typing/cache stuff for that)
  • use that new function to map the kernel file and debug symbols file (latter from limine internal modules)
  • add some extra orders to my buddy allocator to handle single allocations bigger than 2M/one pde
#

notably, the zig std functionality for loading external debug symbols is used here, which means that if objcopy/whatever puts the expected crc of the external symbols in the main kernel binary then that will be verified

haughty cipher
#

so turns out its not actually using eh_frame tracing like id thought

#

it is still getting symbols now at least

#

theres more hacking i could try to integrate the zig std eh_frame support but meh

haughty cipher
#

at some point it aint worth the work lol

#

as best i can tell theres only two bits missing for me to use zig's std eh_frame unwinding

#

one type and one function, which the std has defined as void and returning an error respectively on freestanding

#

that being ThreadContext and getContext

#

ThreadContext is defined to ucontext_t on posix-y systems and to CONTEXT on windows, and getContext captures register state using a variety of methods (on windows it calls RtlCaptureContext, on no-libc linux it runs a custom routine, idk what it does on macos and linux-with-libc)

#

def something i could plug in since its basically the interrupt frame/saved registers type that i already have for interrupts but itd mean even more modifying std

haughty cipher
#

next zig version i may be able to rip out my intrusive collections stuff

#

@ornate oak idk if you noticed/knew this but on master they changed the std linked lists to be intrusive (mostly, they dont do the fieldParentPtr for you but thats realistically fine tbh)

ornate oak
haughty cipher
#

so the node struct is just the node, no item field, and you give the list pointers to it

ornate oak
#

oh okay that's good

haughty cipher
#

ive got an untypedqueue in my intrusive lists thing that the intrusive one is built on top of and that is basically the same as the new version of std SinglyLinkedList

#

the commit changing it over ends up adding a bunch of fieldparentptr usage in std wherever the linked lists get used

ornate oak
#

that's how it should work imo

haughty cipher
#

yeah

#

i may end up not fully adopting it because the version in std are missing some weird operations i use but its a good change

#

i have a clear operation that empties the list without changing the nodes so you can iterate what gets returned for batch removal/processing of a queue, and my singlylinkedlist tracks the tail in addition to head so i can do O(1) append which std cant without using the doubly linked list

#

also i have a fully atomic/lockless singly linked list that i need

ornate oak
haughty cipher
ornate oak
#

I don't think it has to be doubly linked to have a tail tbh 👀

haughty cipher
ornate oak
#

ah nice

#

yeah I have a tail for the list builder in the newton compiler too

haughty cipher
#

which is the primary reason i cant immediately switch because no append lol

ornate oak
#

exactly

haughty cipher
ornate oak
#

lmaooo

haughty cipher
#

the idea is you have a second int in the node that does some form of consistency tracking and then use a 128 bit cmpxchg

ornate oak
#

okay it requires cmpxchg128

haughty cipher
#

yeah

ornate oak
#

okay I don't want to depend on that

#

but it's a neat solution if you're okay with that

haughty cipher
#

i mean on 32-bit mode you need 64 bit cmpxchg

#

but same idea

ornate oak
#

yes

#

that's the same feature iirc

#

but less common on 32 bit cpus

haughty cipher
#

yeah

#

its supported on everything after core 2 for sure

#

and any cpu that can run 64-bit windows 8.1 or newer supports it because windows uses the instruction for this exact type of list starting that version

#

idk the implementation is the same though

#

(and actually thats why my impl is called what it is, its the same name as the windows one and i couldnt come up with a better name at the time)

#

whats funny is i dont even use the dang thing yet, i ended up doing a thing differently and not needing it lol

haughty cipher
#

had a talk with @hoary aurora and ended up making a wrapper for limine bios-install that has separate input and output file arguments because the way zig's build system interacts with run-a-program-to-make/change-file doesnt handle having both as the same argument at all without having to forgo the caching ability

#

and it also makes the optional gpt partition index arg a proper flag instead of positional since its transforming the args list anyway

haughty cipher
#

actually considering having my prekernel act like a dynamic library that the main kernel links to, and then having the prekernel export uacpi dynamically

#

would take some fuckery to get fully working the way id want though

#

because id have to figure a way to swap out a function in the middle of init

#

because ideally the prekernel swaps out its map function for the main kernel's once the main kernel is up, so it can happen on the main mm

#

so the linking situation would be messy

#

prekernel statically links to and re-exports uacpi for dynamic loading, while uacpi is dynamically linking to kernel_api functions in the prekernel, and the main kernel dynamically links to the prekernel

last pebble
#

Damn

storm wind
#

I'm guessing this is so you can initialize xyz thing in prekernel, and then use it in the kernel (where e.g. xyz=uacpi). At this point why even have a prekernel? It's possible to do kernel bootstrapping in the kernel itself.

haughty cipher
#

if i could make one kernel binary and split out the bootstrap sections as its own elf to have limine load thatd also probably work

storm wind
haughty cipher
#

yeah probably - that one is lowest priority

#

most likely outcome tbh is i end up just writing my own bootloader instead of a prekernel but idk

#

(well most likely outcome is i keep rattling this around in my head forever and never actually do any prekernel or loader stuff lmao)

storm wind
#

yeah, was going to say, this sounds like you'd be better off chainloading a 'dumb bootloader' instead

#

'i kinda want to' is a good reason though

haughty cipher
#

i really should refactor my kernel's init/bootstrap stuff in general tbh

#

rn its all a bit intertwined which isnt ideal

haughty cipher
#

now to figure out how to get ovmf into my zig build

#

if i could say get all artifacts of this release as a single zip thatd work

polar pilot
#

you could just call out to curl/wget with Build.addSystemCommand

haughty cipher
#

zig's dep management works great but it doesnt like having a single file, it wants it all in an archive

polar pilot
#

you could also just ask @pallid plaza to add a tarball with all files included to osdev0/edk2-ovmf-nightly :^)

haughty cipher
#

true

polar pilot
#

i would also appreciate that given i just rewrote my build system to be entirely zig

#

including launching qemu

haughty cipher
#

yeah zig build is super nice

polar pilot
#

so if i can just fetch ovmf as a zig dep it would be based

#

alternatively, if you (mint) aren't against merging that i can try doing it myself but i'm not very well versed in gha lol

haughty cipher
#

aye

#

apparently the rust-osdev people have a repo that does stable builds as a single archive

pallid plaza
#

you can download tarballs of releases

haughty cipher
pallid plaza
#

oh ok

#

i will do that

haughty cipher
#

much appreciated

polar pilot
#

thanks minttt :D

pallid plaza
#

done

#

wait for the workflow to trigger in like 1/2 hours

haughty cipher
#

thanks!

pallid plaza
#

np

polar pilot
#

thank youu

pallid plaza
#

np

haughty cipher
# pallid plaza np

took a look at the actions, and i dont think the tarball is actually included in the releases here, just generated (also having the root dir inside the tarball be named something consistent would be nice but not a huge deal there)

pallid plaza
#

hm

#

i guess i can make the name constant

haughty cipher
#

the bigger thing is the release create bit still has the /* so it wont select the tarball

haughty cipher
#

@pallid plaza unless im really misunderstanding globs this gh action still doesnt actually add the tarball as an artifact, since the release action is just selecting stuff inside the folder

#

can be solved by just adding a second line to files: with the tarball name

pallid plaza
#

nice catch, i actually missed that

haughty cipher
#

now just to wait on the cron job (which has me confused because midnight UTC was a half hour ago already and it hasnt started)

polar pilot
#

yeah gha is stupid, but the release should happen in about 45-50 minutes

haughty cipher
#

i probably wont get around to setting up imaginarium to pull it until tomorrow tbh but idk yet

haughty cipher
#

worked \o/

hoary aurora
#

@haughty cipher: i will probably vendor the tar ball in a github repo at Ashet-Technologies, so i can download the files easily.

where would i download them right now? i'm interested in getting an EFI build running as well

haughty cipher
#

the new bit is adding the tarball artifact in addition to the direct downloads

#

i think this is the basic template for invocation, the whole pflash bit anyway

#

just add it as a pflash drive in unit 0 (the vars fd can go on unit 1 and be readwrite if desired)

haughty cipher
#

as for actually efi booting, for that you also need the bootloader efi file at the right path (EFI/BOOT/BOOT<arch>.EFI)

#

i plan to have a build arg that makes it use gpt disk instead of mbr, and a build arg that makes it use ovmf instead of the default (and if both are passed then ovmf should detect the ESP and boot through efi)

hoary aurora
#

can you ping me when it works?

#

would be so awesome to have a bootable disk image

haughty cipher
#

yeah I'll let you know when I have it working

#

no idea on timing still tho since life has to have priority

haughty cipher
#

fuckin incredible

haughty cipher
#
gdisk -l zig-out/x86_64/img/drive.bin
GPT fdisk (gdisk) version 1.0.10

Caution! After loading partitions, the CRC doesn't check out!
Warning! Main and backup partition tables differ! Use the 'c' and 'e' options
on the recovery & transformation menu to examine the two tables.

Warning! One or more CRCs don't match. You should repair the disk!
Main header: OK
Backup header: OK
Main partition table: ERROR
Backup partition table: ERROR
#

ahh

#

ok im confused this is adding up

#

i think i found it

#
ERROR (interrupts): unhandled interrupt: 0xE  (page_fault) --- v=0e e=0000000000000009 cpl=0
     rax=fffffbfdfee00000 rbx=       fffffffff rcx=               1 rdx=ffffffff802950d0
     rsi=ffffffff80217fb8 rdi=fffffbfe00000000 rbp=ffffffff80217b48 rsp=ffffffff80217a08
     r08=               1 r09=       fffffffff r10=fffffb8000000000 r11=fffffb8000000000
     r12=fffffaffc0000000 r13=               1 r14=fffffa8000000000 r15=               0
     rip=ffffffff80102be8                                           flg=           10046
     cr0=80010011         cr2=fffffbfdfee00000 cr3=0000000001780010 cr4=000000a0
  fsbase=               0                   gsbase=               0
#

well

#

my os isnt happy

#

BUT it booted from efi

#
INFO (init): loading bootloader-provided system info
INFO (boot): LIMINE Boot Protocol, loaded by Limine v9.3.2 in uefi64 mode
#

yyup

#

alright @hoary aurora its working now

#

idk why my memory manager is dying but ive got it booting from efi with a gpt disk created by dimmer

haughty cipher
#

bios/gpt isnt working though so idk what ive got wrong there

haughty cipher
#

so bios/gpt isnt getting to limine somehow

#

bios/mbr still works the same

#

and efi/gpt results in my memory manager running off a cliff

#

(but after limine chainloads to my thing)

last pebble
#

i thought limine dropped support for bios + gpt

haughty cipher
#

if they did i didnt see anything about that

#

and the source suggests its still a thing

#

limine bios-install even has an optional arg to give it a gpt partition to stuff the stage2 into

#

which im using

#

and the stage2 location is showing up in the right offsets in the disk image too

#

but its def not getting to stage3

#

i literally tried compiling limine from scratch to get E9 output enabled and that doesnt seem to be working

haughty cipher
#

GOT IT

#

dimmer isnt setting the bootable flag on the protective mbr

#

so seabios wouldnt boot it

hoary aurora
haughty cipher
#

alright, with the latest push to the dimmer gpt branch imaginarium can now boot on bios/mbr, bios/gpt, and uefi/gpt

#

all from just flags passed to the build

#

zig build qemu -Dgpt for bios/gpt, zig build qemu -Defi for efi/gpt (efi implies gpt but passing both is ok; if you say gpt=false and pass efi it errors), neither flag for bios/mbr

#

both bios versions work perfectly but the efi version makes my memory manager shit itself for some reason so thatll be my next task lol

hoary aurora
#

sounds great. all of these run the same disk image, right?

haughty cipher
#

yup

#

well bios/mbr is different image from the other two

#

but bios/gpt and uefi/gpt are the same disk image

hoary aurora
haughty cipher
hoary aurora
#

i mean it should be possible to always have it, and MBR-only systems will ignore it

haughty cipher
#

yeah i just never took off the if from when i was still trying to get it working yesterday

haughty cipher
#

turns out i might be stupid

#

and i never bothered to zero out the new top level page table before filling it

#

which meant the bottom half of it was garbage random bits

#

that on bios were always 0 for some reason

#

now we workin on uefi lol

haughty cipher
#

alright @hoary aurora has convinced me, im going to at least start working on re-architecting imaginarium's internals to support a second arch

#

going with aarch64 for the second one because of the qemu virt board and ovmf and limine both already having aarch64 support so ill just use uefi on aarch64

#

and riscv puts me off for some reason lol

last pebble
#

aarch64 is so damn messy and difficult but good luck

#

At least its a real arch unlike riscv

haughty cipher
haughty cipher
#

and i aint touching anything 32-bit so ive got slightly limited other options lol

last pebble
#

They really fucked up the EL model especially with regards to 2-1

haughty cipher
#

mhm

#

as long as im only touching EL1 and EL0 i should be ok? i hope?

last pebble
#

Well CPU may not support disabling that bit that makes EL2 work like EL1

#

Like apple computers

haughty cipher
#

right

#

ill have to look further into it then ig

last pebble
#

But that mode also makes stores to el1 store to el2

#

So its sort of transparent but not quite

haughty cipher
#

(cant actually do much work because its a heat wave here and i am boiling alive)

haughty cipher
last pebble
#

Yeah otherwise they would have to rewrite every kernel to support it lol

haughty cipher
#

lol fair

last pebble
#

But most low end aarch64 stuff should support el1

haughty cipher
#

cool

#

i dont have a single arm machine except maybe my nintendo switch? idk offhand what that thing actually runs

#

so id be vm only anyway

#

mostly doing a second arch to force me to get arch-specific code cleanly separated and organized

last pebble
#

The most difficult part about aarch64 is the memory model, they have 99999 caching modes, cache maintenance instructions, barriers, tlb maintenance, configurations, bit granularity page tables etc etc

steep spear
#

A big difference between real hw and virt is that qemu has stronger memory model then most hw, so its a little risky not having any real hw to test

last pebble
#

Yeah

#

U want an arm device with kvm

haughty cipher
last pebble
#

Oh and also u don't get any firmware for power management so u must ship drivers for literally anything

haughty cipher
#

i already have a modded switch that i could run linux on and then kvm on that i think? if the processor does aarch64

last pebble
#

Also device dma addresses don't match the cpu address space so u must recursively walk the device tree to convert them to cpu address space

haughty cipher
#

oh the switch is a cortex a57, runs armv8-a with 64-bit support which is what i was planning on targeting as a minimum anyway

last pebble
#

So that's kinda annoying

steep spear
#

Yeah, you're probably fine for a while but once you start running on hw your stuff would just break, that's to be expected

last pebble
#

And no one is there to set up bars for u

haughty cipher
#

yeah thats fair

#

well if nothing else looks like i might actually have one (1) hardware with arm that i can put linux on to run kvm under

#

since i got that switch

last pebble
#

Do u have an android phone

haughty cipher
#

i do have an android phone yeah so ig that too

#

pixel 6a

last pebble
#

Yeah

haughty cipher
#

(rooted)

last pebble
#

That can run qemu ofc

#

And kvm

haughty cipher
#

cool

#

so ive got the same amount of hardware for aarch64 as x86_64 ig (and also i have a homebrewed wii u that runs powerpc stuff but i aint touching powerpc anytime soon)

last pebble
#

Anyway the aarch64 spec is really good, I find it easier to read than the x86 one

#

It's more structured in a technical way

haughty cipher
#

yeah

last pebble
#

So u should be fine

haughty cipher
#

im finding that certain aspects of each are easier for me so far

#

spec wise

last pebble
#

Ye

haughty cipher
#

i really do like the way special registers are used in arm

#

compared to x86 msrs etc

last pebble
#

Yeah

steep spear
#

There's quite a few system registers, a lot are quire irrelevant unless you're working on something related to them

haughty cipher
steep spear
#

Also the translation is a little more comlicated then x86 (enough to look at the qemu source to see the diff...)

haughty cipher
#

im still super unsure how i feel about the way exception vectors are handled on aarch64 though

steep spear
#

Wdym? I dont know much about x86 but they've split the exception vector nicely enough

#

Though sync exceptions is quite a wide term

haughty cipher
#

on x86 (except the super old school real mode stuff) the vector table is an array of structs that hold the pointers to the handlers

#

whereas on aarch64 its a couple of 128-byte blocks that you just put code in

#

(on aarch32 i think its 4-byte blocks so you can only fit like one instruction each)

steep spear
#

I mean in the first couple of instructions you'll branch somewhere, helps you set up the stack if you wish or disable some types of interrupt beforehand

#

Yeah aarch32 is quite terrible, luckily aarch64 learned a lot

haughty cipher
#

yeah

#

the aarch64 way isnt bad really i just need to get my head wrapped around it a bit

#

i will miss having builtin interrupt priority stuff for external interrupts on arm though

#

using cr8 to just mask off whole chunks of interrupts easily is so nice

steep spear
#

Also I'd keep esr.arm64.dev close by, arm encode a lot of info in that register and its a nice tool to parse

haughty cipher
#

ill have to dig into the gic stuff though, maybe theres a priority register in there i can fiddle with

#

ah ok yeah the gic has interrupt priorities

steep spear
#

My experience is that every "maybe theres a xxxx register" is "yes, there is."

haughty cipher
#

fair lol

hoary aurora
haughty cipher
#

yeah that's the main idea more than anything

#

idk if going aarch64 will actually work out but it'll improve my code quality for sure to try

hoary aurora
#

🙂 very cool

haughty cipher
#

got a branch for that now

#

it can try to compile on aarch64 now

#

it compile errors ofc

#

but the build system can attempt it at least

#

took some inspo from @hoary aurora and made up my own custom target name format for it, though i might make a proper parser for that instead at some point instead of just an enum of hardcoded options lmao

hoary aurora
#

nice!

haughty cipher
#

format im using is arch-machine-boot-part

#

so e.g. x86_64-q35-bios-mbr or aarch64-virt-efi-gpt

#

the only part that directly matters to the os/build is the arch though

#

the machine goes to qemu, the boot part determines if ovmf is needed for qemu, and the partitioning type goes into disk image creation

hoary aurora
#

i've split "run" and "kernel" target now

#

x86-pc-generic can use x86-pc-generic-bios or x86-pc-generic-uefi now

haughty cipher
#

if i ever have more to the kernel target than just the arch i might do something like that

#

idk

hoary aurora
#

🙂 i mean you basically do that already

haughty cipher
#

anyone know if theres an aarch64 equivalent to qemu's debugcon?

steep spear
#

Im not entirely sure what's debugcon but a quick google said it's to easily print to screen right? If so you can use semihosting

haughty cipher
#

any time you out a byte to that port it sends it to whatever the qemu command line has debugcon sending to

#

whether that be a file, stdio, or whatever

#

but that relies on x86 port io which aarch64 doesnt have

placid swallow
#

There's a uart

steep spear
#

Yeah semihosting works, you essentially gives some register parameters and then invoke a seemily illegal instruction sequence (iirc its like 3 times brk, better google it) and then qemu would handle your request

placid swallow
#

Its not zero setup, but its simple

last pebble
haughty cipher
# placid swallow There's a uart

id be doing that already but both of my x86 hardware options use nonstandard order for the uart ports so in theory to do it right i need to discover those with acpi

last pebble
#

u just write bytes to 0x9000000

haughty cipher
#

so i dont have uart for x86 yet and idk if i want nonparity

placid swallow
haughty cipher
haughty cipher
last pebble
#

yes

haughty cipher
#

well its a place to start thats zero-setup for logging

#

my logging isnt persistent in the kernel so i need something zero-setup for early boot logging

last pebble
#

my setup for testing that aarch64 boots at all was literally

volatile u8 *g_qemu_uart = (u8*)(0x9000000 + 0xFFFF000000000000);

static void qemu_uart_write(struct console *con, const char *str, size_t count)
{
    for (size_t i = 0; i < count; ++i) {
        *g_qemu_uart = (char)str[i];
    }
}
haughty cipher
#

cool

#

@last pebble oh also, are there any cpu features i need to toggle for kernel-mode aarch64? ive got a bunch of stuff i need to turn off for zig to not use sse on x86_64 since thats slow to save registers for in kernel-side but idk if i need anything like that for aarch64

last pebble
#

i use -mgeneral-regs-only, seems to work

haughty cipher
#

hm, ill have to see how to get zig to do that then

hoary aurora
#

(or the target)

polar pilot
#
        .aarch64 => {
            const Target = std.Target.aarch64;

            query.cpu_features_add = Target.featureSet(&.{});
            query.cpu_features_sub = Target.featureSet(&.{ .fp_armv8, .crypto, .neon });
        },
#

that's what i disable for aarch64 in my template

#

that seems to work but i'm not sure what more is required

haughty cipher
polar pilot
#

lol yeah it's awesome

#

np :^)

haughty cipher
#

well it still doesnt compile on aarch64 but thats mostly a bunch of "has no member" errors because the aarch64 arch files are stubs with not yet implemented comments

haughty cipher
#

ok down to two compiler errors

#

one of which gets repeated three times for some reason

#

error 1: I havent defined the saved register state struct yet and the thread struct depends on it

#

error 2: the memory manager is trying to set cr3 on aarch64 because i havent refactored that yet

#

time to double check i didnt break x86_64 with all this

#

x86_64 still works

#

there are also a whole lot of not implemented things on aarch64

#

stubs and the like

#

but still, down to two errors

haughty cipher
#

anyone know of an example of using efi runtime services when booted by limine?

#

trying to figure out what would be needed there

haughty cipher
#

still kinda neat to me that the drive letter followed by a colon is a command on windows

polar pilot
#

i believe you can just take the address of the table and use it as is

haughty cipher
haughty cipher
polar pilot
#

why do you need efi runtime services for that

#

ahh

#

can't you just write a simple driver for it or something

#

or use the boot time request

haughty cipher
polar pilot
#

that sounds really bad

#

can't you discover it by acpi then?

#

how the hell does uefi discover it

haughty cipher
haughty cipher
polar pilot
#

not by acpi tables but like dsdt

#

actual aml

haughty cipher
polar pilot
#

what the hell

#

no but like

#

WHAT

#

ok aarch64 virt is cooked

haughty cipher
#

"When booting the VM with UEFI, UEFI takes ownership of the RTC hardware. While UEFI can use libfdt to disable the RTC device node in the DTB that it passes to the OS, it cannot modify AML. Therefore, we won't generate the RTC ACPI device at all when using UEFI."

#

and uefi is basically the standard way to boot aarch64, or at least the only way limine supports

polar pilot
#

that is pretty dumb

haughty cipher
#

i think its probably a factor of efi firmware being decoupled from the actual device/machine generation in qemu

#

more than anything

haughty cipher
#

gods i kinda hate the arm manual

haughty cipher
#

its so unclear about the size of page tables

#

ive figured this out but its still hella unclear and i dislike it

haughty cipher
#

when i first read the arm manual some months ago i got thrown off by the flag bit being different for large page vs table-or-small-page

#

and now i get it

#

felt weird that the final stage would use a different bit depending on level

#

but turns out recursive page tables would be impossible if table and small page were different flags

#

anyway i think i can keep the virtual memory map the same on both x86_64 and aarch64 which is nice

#

the main thing i still need to do before aarch64 will compile and i can start filling in not-implemented stuff is just decouple out the PTE stuff from the arch-agnostic memory manager logic

#

turn "valid/present pte" into a thing provided by the arch with functions to set address and set memory attributes thats opaque-with-known-size to the kernel proper

#

i also want to make the page table stuff use proper access flags for parts of the kernel

hoary aurora
#

very cool!

placid swallow
haughty cipher
#

then you can use the runtime services pointer to get it, and use its setvirtualaddressmap thing to move efi to somewhere else in virtual memory

#

also nw about the necro, this project isnt so much dead as on hiatus atm and i do mean to get back to it soon

placid swallow
#

thanks, thats pretty much what I'm doing, I'm just having trouble getting it to work on real firmware and am wondering if there's some bs I missed in the spec.

haughty cipher
#

yeah idk its confusing and im not 100% sure since ive not done it myself

placid swallow
#

mm yeah, it seems like it should be straight-forward enough 😛

#

ty anyway, if I get it working I'll let you know

haughty cipher
#

the big thing is to make the mappings right

#

because efi can use internal pointers and the only thing that guarantees that is the efi memory map

#

anything thats listed with the runtime flag

#

but limine doesnt put those in paging

#

so you gotta do that yourself

#

if youre on an older limine that unconditionally identity maps a chunk of low memory then some firmware will be fine cause its usually identity mapped but thats not a requirement

placid swallow
#

limine can give you the efi memory map, which has all that info.

haughty cipher
#

yeah you just gotta use it

placid swallow
#

Im not having any trouble (I think lol) mapping the runtime stuff

#

from what I can tell its dying when calling SetVirtualAddressMap()

haughty cipher
#

after you do that you need to reconvert the system table pointer from physical to virtual

#

under the new map

#

and start from that root system table pointer

#

setvirtualaddressmap changes all the fields of the system table recursively but you need to start from the system table (which youll have the physical address for from limine, who gets it from the calling convention of efimain iirc)

placid swallow
#

yeah the issue is the call to SetVirtualAddressMap(), not after

haughty cipher
#

it returning anything? or just dying?

placid swallow
#

it doesnt return to the kernel: the system freezes for a few seconds and then resets

#

eh i'll just setup an idt see what comes out

haughty cipher
#

if you figure it out lmk, i want to know too

placid swallow
haughty cipher
#

looking at this refactoring plan and debating just ripping it all out and starting over tbh

#

well mostly out

#

theres some parts that are fine

lost hawk
#

What would you refactor and why? How much untouched code would be left?

haughty cipher
#

so id like to refactor out a HAL of some sort but its becoming too much of a tangle atm

lost hawk
#

Could you be more specific?

haughty cipher
#

the memory manager is tied into the specific layout of x86 PTEs still, the io system is calling directly into APIC stuff in a lot of places

lost hawk
#

That's an ouch in terms of separation of concerns

haughty cipher
#

and theres some other stuff that could really use redoing in general, the dispatcher/threading stuff is ugly rn and needs a hefty rework

haughty cipher
#

also im behind by a full major version on zig itself and would need to change at least some things to do that update

#

the entire startup sequence of the os is built assuming a different bootloader than im now using and had to get limine support bodged into it too

haughty cipher
#

starting to work on a redo of my build infrastructure in advance of major refactoring

#

and also updating the zig version

#

running into a funky issue where the one dependency legit makes zig build crash without a message and idk why yet lmao

haughty cipher
haughty cipher
#

love when ive finally got the energy to work on a project and the dependencies just say no

haughty cipher
#

as for the build infraastructure stuff, i took the setup that the zig std uses to store and manage cpu feature flags for build and duplicated the concept to use for my own kinds of feature flags

#

of which i have two sets, one for the build target which are passed through an options module to be checked by the kernel and one for the run target which are converted to qemu machine and cpu options for running the OS

#

the build target ones being passed through an options module makes them available at compile time, which means that I should be able to use them the same way as conditional compilation defines are used for feature flags in a C kernel

#

once i get it done ill be able to do stuff like:

# base x86_64 target features, enable huge pages on the vm
zig build ... -Dtarget=x86_64 \
  -Dmachine=q35.qemu64+pdpe1gb
# aarch64 with acpi support compiled in and run on the qemu virt machine, cortex a35 processor emulation, and acpi support for the machine turned on
zig build ... -Dtarget-aarch64+acpi \
  -Dmachine=virt.cortex_a35+acpi
#

or if im not running the qemu task and just building the kernel i can omit the machine option entirely

#

feature flags can also have dependencies too, though nothing ive got so far has them in any meaningful sense (i put rdrand as a dependency of the rdseed feature but idk if thats even true, idk if you can have rdseed without rdrand)

#

the target features can also imply cpu features for the compilation invocation

#

in case i want to forward a cpu feature through to the compiler from this system without putting it in the arch defaults for some reason

#

details on how it works to come once its done tested and pushed but so far so good and the code is mostly copied from how the std does similar things so hopes are high that itll work

haughty cipher
#

note that a target feature does not generally imply a machine feature - an aarch64 kernel compiled with acpi support would ideally still be able to run on an aarch64 machine without acpi using devicetree stuff

haughty cipher
#

one day of fixing the dang dependencies later, tomorrow i can start fixing the other dependencies lmao

haughty cipher
#

all this dependency bs is driving me to the conclusion that WSL sucks tbh

#

but at least the first level of dependency bs is done officially now

#

thanks, WSL in this one specific case behaving like windows in a way that diverges from posix lmao

polar pilot
#

just don’t store your projects on the windows drive

#

“problem” solved :^)

haughty cipher
#

some things i really wish i could customize/control about limine tbh

#

dont really need a stack provided, i want to control SMP startup myself but maybe still use the smp info from limine, stuff like that

polar pilot
#

regarding SMP you can just initialize the CPUs again yourself

#

but maybe that could just be a flag or something

hardy cloud
#

you should just have to ipi

polar pilot
#

well yeah that's what i'm saying

#

you can just re-initialize the other CPUs yourself regardless of what limine did

hardy cloud
#

i think?

polar pilot
#

i don't think, i know

hardy cloud
#

oh ok

haughty cipher
#

thats good to know at least

haughty cipher
haughty cipher
#

i do wish codeberg let you choose what events to subscribe to when watching a repo but otherwise i do prefer it to github just for the no microsoft connections and not pushing ai bs down my throat like github has been

haughty cipher
#

progress happening on the big rework

#

currently working on a custom bootloader for imaginarium, mostly to get access to uefi runtime services on aarch64 for the RTC but also the additional control is nice to have

#

current output from the new WIP bootloader

last pebble
#

nice

haughty cipher
#

next things for the bootloader:

  • filesystem stuff
  • load and map acpi tables and get the rsdp
haughty cipher
#

ok, to work withal

#

cc @ornate oak i feel like youd appreciate that nonsense

haughty cipher
#

time to pop off

#

ITS WORKING

#

oh shit hang on its returning early and not hitting the memory manager insert-this-into-the-mdl part

#

and now it doesnt compile 😓

#

see this makes more sense lmaooo

haughty cipher
#

there we go

#

before i keep going further on the ACPI part of the bootloader i need to get my act together wrt virtual memory allocation

#

though not actually that much of it tbh

#

shouldnt be that bad

#

just gotta actually do the work lmao

#

kinda hate that im doing this to myself with the bootloader but w/e its fiiine

#

i dont actually need full acpi tables here just enough to map em

#

am going to quickly change it so it overwrites reclaimable in the memmap for this

#

its now set up so when it maps acpi tables anything with the acpi NVS memory type gets left as is but anything with the reclaimable type gets turned into the acpi_tables type

#

that way the kernel knows not to nuke the acpi tables

#

though tbf im not actually sure thats something thatd be an issue

#

@last pebble is it allowed to overwrite the acpi tables after uacpi gets initialized if the tables are in acpi reclaimable memory?

last pebble
haughty cipher
polar pilot
#

i'm pretty sure uacpi copies all the tables into memory it allocates itself

haughty cipher
#

thats what i thought too but figured id check

#

im nowhere near the point of actually needing to reclaim that memory, its more about knowing what i can mark it as in the bootloader

haughty cipher
#

itd be an easy change to make later in the loader tho

#

so can leave it for now

haughty cipher
#

(i do assume the FACP needs to be preserved but i also expect that to be put in an nvs or reserved memmap entry)

last pebble
#

Aml bytecode is copied to heap, FACS ig but that's not gonna be marked reclaimable

haughty cipher
#

or whatever that block of mmio stuff is called if my memory is bad on the name yeah

last pebble
#

Yeah

last pebble
haughty cipher
#

nah the FACS is what i meant

last pebble
#

Yeah facs is needed later on

haughty cipher
#

the thing with the mmio stuff in it

last pebble
#

Its normal ram but touched by bios as well

#

Spinlock between fw and kernel

haughty cipher
#

👍

last pebble
#

Make sure u reclaim it after loading the namespace tho lol

haughty cipher
#

obv

#

reason im asking is my new bootloader is going to be mapping all the acpi tables and the facs etc ahead of time so the kernel mm doesnt need the memory-mapping stuff up in advance for the initial load of acpi tables, and every memory change the bootloader does it puts in the memory map

#

and not all of them need to stop being reclaimable if the firmware put them as such in the memorymap the bootloader gets from it

#

the memmap changes for acpi is the same idea as what limine protocol v4 does where if the tables arent marked as nvs or reclaimable already then it marks them as a new memory type acpi_tables so the kernel can handle that

last pebble
#

Fair

haughty cipher
#

but yeah if the tables are reclaimable then i can leave it alone if theyre already marked as such

haughty cipher
#
switch (switch (hdr.sig) {
    SdtHdr.convert_sig("RSDT".*) => false,
    SdtHdr.convert_sig("XSDT".*) => true,
    else => return error.WrongAcpiSignature,
}) {
    inline else => |is_64bit| {
        const Entry = if (is_64bit) u64 else u32;
        const entries: []align(1) Entry = @ptrCast(ptr[0..hdr.length][@sizeOf(SdtHdr)..]);
        for (entries) |e| {
            try map_table(e);
        }
    },
}
#

heres a fun dumb hack you can do in zig thats kinda fun

#

this ends up generating two separate switch cases for the two different tables, with the right bitwidth of entry automatically swapped in

haughty cipher
#

not sure if its normal for all the acpi tables to be page aligned but if the ovmf firmware wants to do that i aint complaining

#

actually its not all

#

the XSDT and RSDP arent page aligned

#

but the rest for some reason are

last pebble
#

on real hw 99% of the time all tables are page aligned

haughty cipher
#

cool

last pebble
#

in qemu they arent usually

haughty cipher
#

must be an ovmf thing then

last pebble
#

yeah

haughty cipher
#

the only thing im not sure my current bootloader mm can handle is if a table spans two pages and one of the pages on either side also has a table that was previously marked

last pebble
#

yeah thats possible

haughty cipher
#

yeah i figured it would be

#

but i dont have it in me to make sure that works rn lol

#

are the various gpe/pm/sleep/etc registers listed in the FADT also left mapped at all times? or just the FACS?

#

id rather not deal with those in the bootloader if i can help it lol

#

in any case, more progress \o/

#

because the virtual mappings for these get put in the memmap, the kernel can put them into the PFMdb (aka struct page stuff) and then get an easy back-reference to get the virtual address from the physical address until they maybe get reclaimed where applicable

#

you can see from the "to go at virtual" lines how the FACS gets a different location too, thats on purpose as the reclaimable stuff is put in the region where the kernel nonpaged pool goes but top-down instead of bottom up

#

that way theyll get reclaimed but only if the nonpaged pool runs out of space

#

the table ordering not being sorted by address does lead to this nonsense though lol

#

love how the stuff is just randomly out of order in the middle lmao

#

if i did the dsdt before the fadt itd be in order though but i dont really care tbh

#

this shit wont be set up as large pages anyway

#

also, octal slaps for virtual page numbers

#

every 3 octal digits is a level of the page table

last pebble
haughty cipher
last pebble
#

they're mapped/unmapped separately ofc

haughty cipher
#

yes but are they left mapped or unmapped after being accessed?

last pebble
#

some of the most common ones are left mapped yeah

haughty cipher
#

mhm

#

one of my goals here is to put stuff that gets left mapped in a different address area because the address range for temporary mappings has some limits (tbh probably not enough to warrant this but on principle i want stuff left mapped to be in its own spot)

#

so im trying to figure out what exactly i need to put there

last pebble
#

why would those registers get mapped in a bootloader tho?

#

like when you're accessing them you're hopefully at a very late point of init

haughty cipher
#

they wouldnt necessarily, just thinking ahead and trying to figure out how to tell theyre a long-term mapping

#

and the bootloader is already doing that sort of thing for the basic acpi table stuff since it has to get the rsdp anyway

last pebble
#

ah

haughty cipher
#

if uacpi could be configured to tell the kernel if its doing a long-term map or not thatd be nicer but ah well, not the biggest deal

last pebble
#

what would be classified as a long term map?

haughty cipher
#

something that wont be unmapped until the kernel makes a specific call to do so if ever

#

as opposed to something which is mapped, accessed, and then immediately unmapped

last pebble
#

i guess

#

sounds like a very niche thing though

haughty cipher
#

probably tbh

#

the real solution here is for me to just look through the uacpi code and figure out what i actually want lmao

last pebble
#

lol yeah

haughty cipher
#

tbh i think its basically the stuff in g_registers and the FACS that i care about keeping mapped here

#

the former im not really sure if its enough benefit to matter though

#

so not really worried about it atm

#

and the FACS is easy to wrap into my existing table stuff

last pebble
#

on x86 its basically never SystemMemory

haughty cipher
#

cool

haughty cipher
#

the more i read the spec the more i get the impression that uefi fully expects you to just. use two different page tables and swap back and forth a ton when making a bootloader

#

cursed shit

haughty cipher
#

and im not sure thats the worst option TBH

#

upsides of two parallel page tables:

  • dont need to fixup pointers before transferring control to kernel
    downsides of two parallel page tables:
  • have to swap back before every UEFI call
#

not sure which option ill actually go with

#

yall got thoughts there?

#

probably going to call it a night now and mull that over for a bit

haughty cipher
#

ok i think im going to just keep putting that off. might ask around elsewhere if anyone has suggestions for what the right channel for that is though - #virtual-memory maybe?

#

also, and maybe this is because im used to pre-uefi x86 crap, but it somehow surprised me a bit how little this bootloader actually needs to do

#

remaining things i can think of offhand for tabula (the new imaginarium bootloader):

  • figure out the ID for the BSP so the kernel bootstrap stack can get put in the right place for the per-core kernel stacks (used in interrupt handlers once the scheduler is up, each thread will get its own stack normally)
  • do filesystem stuff so it can load the kernel and its config info, though if i can figure it out i might use PCD or something like that instead of a normal config file idk
  • query video modes
  • actually set up the page tables
  • UEFI exitbootservices and setvirtualaddressmap
  • verify uefi runtimeservices work on the virtual map
#

note that i expect that 99% of the time the BSPID will be 0 but i want to actually handle if it isnt and my per-core stacks are indexed by processor id

haughty cipher
#

realised a benefit to doing paging later and fixing up pointers at the end actually