#tabula imaginarium: osdev experiments in zig
1 messages · Page 7 of 1
Makes sense
though thats with a variety of callbacks
the function to get requester id specifically using it is only called by the arm interrupt controller hell
This is for finding who generated the irq?
this is for finding out what itll tell the interrupt controller generated the irq lmao
mapping backwards generally wont be needed because your irq abstraction will probably handle that
Oh ok
but generally this number is what indexes the tables in the iommu and/or this one arm hell interrupt controller in particular
so you need it to write to those tables correctly
idk its fuckin stupid
Yep
heres the relevant section of the spec btw
That probably means something to PCI people
yeah ikr
i copied the linux code i linked earlier instead of actually understanding this shit
i think what it is is if theres a possibility of the ids overlapping then the bridge is required to note who its from and take ownership
and then a reply goes to the bridge who looks up backwards and that way the things behind it cant conflict
the relevant part for MSI is the last bullet though
wait not thats reads im dumb
my logic diverges from linux and i just realised its wrong and specifically it wasnt working when i matched linux because the nvme was behind a pcie-pci bridge lmao
Yeah this is some cursed stuff
btw im also open to feedback/suggestions on my driver stuff not just questions
Oh im definitely not in that position, never written an nvme driver and dont understand zig concepts
i was thinking more if you happen to also look at the other drivers (esp the acpi one) you might have thoughts too, but fair
Oh ok
nw if you dont want to look elsewhere though lol
Im gonna sleep now anyways but yeah might give a look tmrw
sleep well
Thanks
god its so nice writing code that doesnt depend on architecture now
im still habitually checking on both arches but its still so nice
almost feels weird but its very nice
pushed the site to update the autodocs but havent updated the actual docs yet
well fortunately ive got a ton of old fat code still from before i started using uefi for bootloader filesystem stuff
so that fs will be relatively painless to implement i hope
at least for reading
i dont have writing at all yet 
(theres some stuff i need to fix with my build system to better support getting both initial and final disk images out that i can inspect before i want to add writing, and also ive got to wrap my head around how the fuck to change the size of a file in FAT)
ok time to see if i can read a bpb
and thats a nope
oh that was a super shrimple bug
ok i should make the two FAT systems different sizes so i can actually tell its reading the right partitions here
yup
part of me gets concerned when things just work 😅
holy shit that's so cool
the ultimate plan here
once i get fatfs working
is itll be told by the bootloader what the partition guid the kernel is on is
(and maybe the esp too i havent decided there)
itll load a fat filesystem on the kernel boot partition
and then using the alias i give that partition in the object namespace itll be able to load the stuff that something like linux would put in initramfs
most importantly: fstab
which will then let it assemble the actual vfs
nice
i love logging
ok time to absolutely hammer the nvme driver by reading the entire FAT into memory at once
wtf that worked???
Real hw test time
im still not set up to do a real hw test 😭
unless i pci passthrough the nvme controller of my actual hard drive with its actual efi system partition which is the only fat partition i have to test with on real hardware
which sounds uh dangerous
Until you see your motherboard manufacturer was a fucking asshole and shoved like 90% of your devices into IOMMU group 15 so all your SSDs your network card your USB and everything else except your GPU and one or two things is outside of it
(I have personal beef with IOMMU group 15 and my motherboard)
Why not
lazy 
also dont have any way to get log output other than port e9
Hack a quick flanterm integration
UART serial?
Took me 5 mins
i dont even have framebuffers id have to add those to the bootloader
Damn
havent written the driver yet, will soon™ but also i dont think any of my actual hardware has uart serial ports other than my main dev machine
I have one you can steal and port to zig 
my end goal is to run this thing on a serial terminal (ideally a teletypewriter if i can get my hands on one) anyway so framebuffer has been low priority
i have an old laptop i could absolutely turn into a real hardware test machine sometime and probably will
ill have to figure out what its serial situation is
Your loader is UEFI right?
yeah
You can just grab the GOP and pass it over in like 20 minutes
i could absolutely get that doing ive just been too lazy™
i even had a poll here and people voted for storage so im doing storage
one thing at a time lol
framebuffer and framebuffer-based logger/terminal is probably the next thing ill do after storage
My desktop has a windows NVMe
With a fat partition
I’m sure it would be fineee to test it there
And I have a serial port
Lowk been meaning to make it a game partition anyways
If you happened to break it

Soooooo just make sure it won’t touch my main nvme and I’m good to test ts
well rn it doesnt even issue a write command
so probs fine there
the only partitions itll touch rn are efi system partitions or ones with the special partition type guid i use for my imaginarium boot partition
but once i get a uart driver ill be happy to see it tested on real hardware
My Linux drive does have one of thoes
You can probably blacklist the pcie ID tho to be safe
Want me to cleanup my UART driver and send it over for portage so you can test it real quick
nah ill do my own when i get there
Fair
the goal with this project is learning not getting it working as fast as possible
so i wanna do it myself

I will say that the 16550 UART is fun but it can be picky
I have it do two seperate self tests with warnings if it fails the first due to how messy it wants to be
mhm good to know
anyway im hitting a wall now
where i gotta figure out how the fuck i want to abstract files
(Being loopback mode and the scratch register to prove it exists at all)
Do a Unix Style VFS with vnodes perhaps?
Where the vnode abstracts to the backend device
im already leaning very NT-like for things and i know i want files to be objects in the object manager
the issue is the details and the api design
That may be out of the realm of my knowledge sadly
i mean im not really copying windows fully here either
im just more NT-inspired than unix-inspired
mostly because theres some things about how *nix handles filesystems/devices in particular that i hate lmao
devices as part of the filesystem is the main thing i hate
and the resulting overreliance on ioctl
You could do an NT style device abstraction and a Unix Style VFS for standard files?
that and i find the *nix file permissions model overly restrictive
And you can still have NT style ACLs etc ontop
thats pretty much where im leaning, albeit with more nt-style acls
yeah
as i said, the problem rn is the details
That would be pretty based
honestly the first big question is do i make the fs data use containerof or do i make the file object carry an opaque pointer
containerof will always be my favorite
yeah i lean that way too
thats the biggest thing ive actually learned doing osdev
is that containerof is goated
intrusive data structures in general
i didnt even know containerof existed before starting this os and it took me a while to get it and now its one of my favorite things
ive seen people ask in other discords what the biggest thing to learn going into osdev is and honestly my answer has been to learn intrusive linked lists and containerof lmao
ok time to try iterating a directory
this is thoroughly not working lol
oh god a race condition again 💀
and/or deadlock i legit cant tell
ok i think i found the bug and its way stupider than that lmao
nope
that wasnt it
ok i dont have tha brain power to figure out what the fuck is wrong here rn
reverted my wrong fix and we'll stick with the weird edge case still existing
for now its working theres just something fucked up i dont understand here giving rise to an edge case
probably not going to get much work at all done this week, lot of irl stuff happening
somehow i have time today
so time to get back to work
kinda want to get open working for fatfs but also not sure on some nitpicky details there yet and the docs desparately need updating
ive got a whole docs page about my interrupt handling thats just completely incorrect now 😅
time to get out my frustration at how annoying the GITS was by writing it all out as a slightly-snarky devlog
wrote a devlog thats like 80% ranting about the GIC ITS and 20% talking about the new irq design for imaginarium lol
i can open files and read subdirs \o/
moving quite fast
ok now to see if i cant read from a file
oh i think i see the bug here
does this look like a reasonable elf header start to yall?
other than being cut off for whatever reason
opened the elf in imhex and got this and that looks the same?
so ig were working
🎉
the elf is like the only file i have here to try reading 😅
race conditions are going to kill me
aarch64 keeps having them
i mean its probably also happening on x86 tbh
but just cant reproduce it
so im gonna go to bed and come back if not saturday then fuckin probably thursday with how busy the coming week looks
are you testing on arm hardware?
arm has a much weaker memory model
im getting 100% success rate on x86 so far but like half the time arm deadlocks
idk if qemu emulates it properly
TCG, i dont have any arm hardware i can test on
qemu should be emulating it stronger than the real hardware would
in any case its not a memory order thing afaict its a straight up race condition deadlock in my io asynchrony
ah well deadlocks are like the easiest concurrency bugs at least
yeah the code is just complex
specifically whats happening is:
- thread starts io operation
- thread calls wait on event object associated with io
- event object is unset so the thread branches into enqueue_wait
- the io completion interrupt fires
- the io completion interrupt sets the event
- theres nothing waiting on the event yet so noone to free
- thread queues itself on already set event and sits there forever
and idk how to fix this
because if i mask all interrupts during the whole check and enqueue for wait process thats a) inefficient and b) sometimes causes the interrupt to be dropped so instead it hangs for that reason
the solution might actually be a thread pool/threaded dpcs tbh
because then the callback that releases the thread runs at passive whereas the wait runs at dispatch
wait no that wont work, because smp
fuck
this would be a lot easier to solve if i didnt want to support wait on multiple objects as a primitive
ok wont be working on anything for another like 5-6 hours probs but ive got a few goals coming up that are left turns from the filesystem/io ive been working on
going to add two things to the bootloader and then work on the mm some
bootloader things:
- actually parsing two (2) acpi tables
- DBG2 and as a fallback DBGP to let me pass along earlycon info to the kernel
- GOP and a framebuffer finally just so i can have something to map when im reworking mm things
mm things:
- my pmm and page allocator are ass (rn its a single free list and a virtual buddy allocator that allocates bunches of pages from the single page free list)
- i want to look into SRAT things and numa
- need better mapping api and pte abstraction
- map the framebuffer while im at it
- the newly parsed earlycon from the loader might need mapping (i may actually legit construct an acpi-style GAS for it in the cases where im not somehow getting one from the tables)
for the new pmm/page allocator im debating between:
- map all usable memory in what amounts to an hhdm with the holes removed and just use the buddy on that (i can cram buddy order into it ez)
- figure out what llfree is and how it works since i keep hearing about it
(if you have opinions please do tell but like give a justification for it, and note that while i do care about perf i will sacrifice small amounts of perf for readability and simplicity of the code since this is a learning project)
There is also SPCR
oh right that thing
thanks for the reminder to look at it when i get there lol
On arm, spcr is usually exposed though since linux relies on it for uart
good to know
qemu virt-acpi machine uses dbg2 for earlycon
so thats my initial focus
but def good to know about spcr
the idea with the earlycon thing btw, is that itll effectively be passing to the kernel a tagged union:
union(enum) {
e9,
uart16650: GAS,
pl011: GAS,
... // more as i encounter them
}
and also probably pass the namepath in addition to the GAS for the earlycon setup
(bootloader will map it)
too bad qemu is dumb and doesnt actually adhere to the standards correctly for the namepath field of dbg2 at least in the arm virt-acpi machine
(in that the namepath is relative to \_SB instead of being fully qualified like its supposed to be)
i was going to account for shit like that anyway because never trust the firmware but still annoys me
did the GOP stuff today so now i have framebuffers enumerated and set up ig
not mapped or using but yknow
it exists at least
so i can have it when i go to town on my mm
next thing im going to work on isnt quite the mm actually, im going to be working on enumeration stuff
i want to figure out cpu hierarchy stuff, generate my own cpu ids, and then parse the SRAT
new mm gonna support numa
(by which i mean each nume node gets its own instance of the page allocator)
(also updated to uacpi 5.0 preview branch which saves having to trim trailing whitespace every log message)
added quick and dirty srat parsing
and starting writing up some initial stuff reworking early init until i started getting sleepy lol
we goin numa compat this version
will need to rework a lot of stuff in the init especially in the mm which is daunting but i wanna do it semi-right this time lmao
new setup plans (roughly):
- all usable phys mem mapped always in a compacted single region
- segments in regions keep the same virtual alignment relative to 2mb blocks as physical and never share a 2mb block
- so the last page in a 1gb block will still be the last page in a 2mb block but may not be the last in a 1gb virtual block
- this means a gap will never exceed 4mb - 8k (segment ends on first page of 2mb block, next starts on last page of 2mb block)
- allows physical allocations up to 2mb if the physmem is arranged in a cooperative way
- pfms in the pfmdb point to their pte in this compacted region so phys->virt is doable with simple indirection, virt to phys is easy already because of recursive paging
- buddy order is stored in the pfmdb for free buddies instead of using the page itself
- each numa node gets its own page allocator
- pfm stores numa node id to allow freeing to the correct page allocator
- because there's no longer linked-list-of-pages bullshit going on i can free up 72(!) bits in the pfmdb from free pages to use for all this
- cant remove locks yet though, because im too stupid to figure out a lock-free buddy on my own
- if you got info on lock-free buddy alloc HMU
- devices associated with a node can allocate from that node (duh), otherwise default is to allocate from current-cpu
- memory pages not listed in SRAT get assigned to existing nodes round-robin in 2mb-minimum 2mb-aligned 3mb-max blocks
- other things not in SRAT get assigned to numa node 0 or round robin depending on what it is
- if theres no SRAT then a default numa node 0 gets created and then put in node 0 and round-robin work out to the same exact algorithm
- im open to other allocators like llfree but i dont get llfree yet so im sticking with my already working buddy allocator
- going to try and add limited resize to the buddy at some point maybe?
- parse cpu hierarchy on x86 (its literally just free info on arm too so no issues there) so i can set up cpu ids and masks too
is it really worth worrying about numa nodes for the page tables themselves?
you should have page tables for each NUMA node iirc
ugh
it's still a memory access
i hate mm bootstrapping lmao
makes sense that itd be good to have separate tables per node but it kinda sucks to bootstrap
should i even bother with numa tbh
i do wonder how you should keep them in sync tbh?
somthing like an IPI?
i mean you need smth like that for TLB flushes anyway?
but also the TLB id expect to absorb most of the issues with tables being on the wrong node
i also thought about that
an IPI to say map x to x for the kernel namespace?
if your kernel uses global mappings / pcid its prolly fine?
though for HHDM or other kinds it couldnt hurt if you make it simple to work in

i do use global mappings
except for the page tables themselves, which cant be because i do recursive page tables and amd has the global bit as reserved-must-be-0 on the higher level page tables
even if i had an hhdm id use recursive page tables
cool so an arch i have no intention of ever supporting doesnt support recursive tables, dont care lmao
oh btw did you ever wana get arround to me testing the nvme?
not ready yet because im still E9 only for logs
im closer than i was tho lol
the other numa thing im concern about is the PFMdb
because the boundary between numa segments dont have to align on the edge of pages-of-pfms
i am gonna have to use my other testing device for nvme tho so you will need FB logs and not jusr UART
fb is happening before uart anyway
let a little bit be on the edges as its not able to be avoided that easy and the performance will amortize
esp now that ive picked out a font and i even have the GOP stuff done in the loader now so i can do that eventually
yeah thats my thought, ill use the node of the first page i put in a pfmdb page
does mean i need to split my early mm init function tho
oh hm
i need the pfmdb to properly map the acpi stuff for uacpi
but i need the numa stuff from acpi to set up the pfmdb properly
only init the first NUMA node on the BSP then on AP init i would do NUMA crap then
and if you cant detect it right away just reinit things as NUMA aware later
most numa stuff will init on-demand
but as a bare minimum i need to allocate single pages for the pfmdb
(and for other early init stuff)
which means i need to know what memory regions are node 0 super early
maybe i just parse the SRAT in the bootloader for memory info tbh
you could use your loader to parse the ACPI/SMBios for NUMA
yeah
i can have the bootloader put the numa node id into the memmap yeah
yes
or prekernel or whatever
id prolly have a seperate map imo?
because the main map may have more fragmentation
and could make the parsing more annoying pre mem
not really
i mean the limine memory map was like 50 entries
it would be nice to have a simpler one for nodes as a total as id want to make my own table for that regardless
idk how your loader sorts it though
the node of a page will be in the pfmdb when im done setting it up
so i dont really care how fragmented the memmap is since i dont keep it long term
yeah
and thats okay
my loader does drop reserved entries and stuff like that
when i init my pdnfb it would be nice to check a seperate thing for the NUMA node as i think it would be an easier check to have it there when making it more aware
this is the final memmap its passing to kernel
each entry in the pfmdb has a field for node because i have enough spare bits to cram it in there without growing the struct, and i add pages to it by iterating over the memmap, so its easier for me to have node in the memmap
so as long as i can make the loader split entries properly to set node then its fine
shouldnt pfndb entries be 64 bytes anyways for cache lines & locks n crap
i was planning to keep them always padded to 64 to have them sit on thier own cache line

iirc linux uses 64
and the worlds most fucked up union
well @rigid python's union may take the cake but
i insist on mine being a power of 2 so if i have to grow my worlds most fucked up union itll go to 64
64 is better for optimization anyways
yeah but i want it smaller to save space in the pfmdb
and i like the challenge of cramming shit into fucked up unions
damn nice
with double linkage included btw 
yeah your a fucking wizard
its all very poorely designed and serves no purpose that its not a good reference really 😭
but yeah its here anyway xD
https://codeberg.org/heret0x0/lilkernel/src/branch/master/inc/pmm_def.h
https://codeberg.org/heret0x0/lilkernel/src/branch/master/inc/pmm_internal.h
im flattered 😭
ok dang, only reason i got down to 32 was pfns for double linkage included instead of pointers. if i used an hhdm i could drop to 24 bytes though and then might be worth trying to cram it down further
only reason i got down to 32 was pfns for double linkage included instead of pointers
yeah thats what i did too!
except that i had to anchor the pfns by some base to account for the way i store the pfndb (i wanted to have it virtually compact which was even more useless but was kinda fun)
also i dont store much into the structure at all which im sure will get more problematic as i go forward too :P
mhm
i prefer virtually sparse personally
oh god
i forgot how ass debugging the bootloader is
yeah lol pe stuff is undebuggable
fun fact that idk if its llvm or zigs fault
but the unwind info for the pdb
just doesnt exist
low level handover code i had to debug with bochs every now and again
fair
ok i found a spot that lets me get error return traces at least
ok theres some definitely wrong logic here
Have a compile option if you need to store more for added features
And then have an option to have a compact PFNdb or the more normal one
And you have to have the normal one for extended features
That way you can have the compact one for memory constrained systems
true yeah but im thinking more about essential fields that i would need for core features that i havent thought of how to implement them yet iykwim
swapping for example
or fields that points back to the structures maintaining the page cache and stuff
so i just dunno o_O
I think yours should be fine for a UNIX like kernel
And I’d probably not store page cache stuff in there but part of the page cache it’s self lol
Though maybe it could be useful?
Could also reuse the double linkage there for page cache
Etc etc
You gotta think with portals
ohh rightt!
well first things first i just need to get back to that piece of shit its been abandoned for well over a month already 🥀
right
apologies to the author if i did..
no worries
yallre good
testing with actually two nodes
seems to work
not sure how much im gonna do today tbh
next step is to make a proper memmap allocator that can request a node
instead of relying on single large free block
once i do that i can work on new mm
order of ops for new mm init will be:
- sysptes and pfmdb as now, but allocate pfmdb pages from best-guess nodes
- also map all pages that have pfms in a compacted way as i talked about [here](#1312232715193683988 message)
- return back to the main kmain for uacpi early tables
- parse MADT and allocate processor control regions as now (but from the right node mem for the cpu)
- parse SRAT
- use the memmap allocator to allocate one page per node to hold the node info struct
- give each percpu a pointer to its node struct
- instead of a single buddy, create a buddy tree per node here
- add all usable memory from the memmap to its node's buddy
result of all this is buddy alloc becomes phys-contiguous which means i can remove my kludge for the GIC ITS bullshit on aarch64
(oh and before creating the buddies and stuff the arch gets to reserve any page it wants for whatever, which is currently only used by x86 to reserve one (1) page for the ap trampoline)
aarch64 will need to have an ap trampoline but it doesnt need to manually alloc because theres no restriction to where it is in memory it just happens to run in no-mmu mode so all phys addresses
my brain is turning to mush so im calling it a night early i think, ill take having the numa node info at all for now lol
not sure if ill get anything done today but if i do itll be memblock
i dont think ill actually implement memblock free tho
since i dont really expect to need it
ok lets see if it works with this memblock setup
nvm first to find out why theres some weird program header page overlap perms shit with my new .init section stuff
oh lmao
it isnt even because of that
im just stupid
eyyyyy the memblock thingy works
next thing to do is probably mapping all normal-mem so i can get phys-contig allocation
idk if ill do that quite yet though, having a bit of mental struggle figuring out how to do it and got other stuff going on in life
(may also take a sidequest to write a BDF parser tool to process a font into a data structure i can include in the compilation using zon)
ok time for that mapping-all-normal-mem thing
ok lets see if this works lmao
thats a nope lmao
oh i unmapped a thing im stupid lmao
slowly inching towards having this working lmao
oh god
i hate debugging this thing
mostly because i cant get good stack traces without it lmao
The cycle of osDev in three messages
i love when the allocator page faults in the middle of trying to stack trace
so i just. dont get a trace
oh cool its page faulting in memset
even better
ok i found the assert thats panicking here
now to figure out why
oh
im stupid
yo progress
Wait page faults are considered unhandled?
well i dont have a handler for them yet
so yeah
reworking my mm
Ahhh cool cool
i fill the exception range of my interrupt table with pointers to the unhandled function and then replace them when i implement them
Yeah yeah that’s normal
this error makes no sense
because the page shouldnt have been able to be freed
so why is it trying to fiddle with it
time to add more printfs
OH
maybe i found it idk
scared to call it but this bug feels like an obvious reason
and it wouldnt have occurred in the old model
because in the old model the buddy alloc couldnt have holes
there was one page of acpi nvs in the middle of two usable sections
and the thing didnt detect the size mismatch so it merged the two on either side
and blobbed hte acpi nvs in
but the acpi nvs bit wasnt mapped in that virt area
so it page faulted later trying to split that again
and now suddenly i have an allocator
which means i can see the stupid stack trace for the outer error here lmao
and that looks like tiny fix
ok now its triple faulting later which is weirdge
somehow the percpu block is getting unmapped
and if i turn on the accel it spams this
not sure how long ill keep going tonight
oh that one is a different issue now solved
GOT IT
IT WORKS
the nonpaged pool is now physically contiguous and numa aware
aarch64 doesnt compile anymore because the GIC ITS code relies on now-removed apis but that shouldnt be too bad to update once i have the energy
one asterisk to numa-aware is the that kernel page tables are not per-node
but i expect TLBs to take up a lot of the downside there
(and i dont want to deal with syncing a bunch of page tables on top of syncing tlbs)
sadly i have to nest iterating the srat inside iterating the madt for this
at least to do it semi-sanely
but thats a small cost taken only once so whatevs
ill also be able to remove my fixed dma allocation on aarch64 now
because it only existed to force a physically contiguous pool to allocate ITS tables from
(which in practice multi-page tables really arent needed unless you have thousands of MSIs on a single device or the ITS doesnt support two-level device tables, both of which are unlikely)
(ig they are needed for the LPI tables but theres one of that total)
the its setup on the virt machine has enough device bits that for two-level device tables which take up a full page at the lower level the upper level needs 1024 bytes, aka a quarter of a page
so the only one that actually needs multiple physically contiguous pages is the LPI configuration table
of which there is one per system
which needs minimum two physically contiguous pages (and must be a power of 2 pages)
so that isnt bad at all even without physically contiguous pages
theres two (2) things that are a bit fiddly with the new mm setup tho
that being freeing the x86 ap trampoline and freeing the uacpi early tables block
and thats only because those start out as memblock allocations which are identity physical addresses in the uefi tables, but have to be freed as virtual addresses
the only thing id want to fix rn is the adding pages to the buddy
which rn is done one page at a time so i dont have to properly handle grouping up blocks to free, but thats slow af
lord that sucked to work on though
i hate debugging early init related things
i may also ditch mapping acpi tables in the bootloader
ill keep mapping the FACS tho since thats permanent
but if the acpi tables are marked reclaimable then the new mm setup will map them anyway
and because the loader is getting numa info from the srat i dont need the tables until after that mapping occurs
which would allow freeing the tables in theory
in theory i could probably ditch early tables now too but the code is cleaner if i dont
(early tables lets me map the percpu blocks which then lets the allocator use percpu to get the current core's numa node, instead of having to special case for before percpu is set up to have an allocator for uacpi init)
next thing to do after i get the mm up is probably reworking sync stuff
cause rn its in a bad spot (or rather an unreliable spot)
it works most of the time
but most is kinda not good enough for a sync primitive lol
@finite ether how do you prevent the race condition where a sync object gets signalled after the initial trylock check but before the thread is queued into the wait list?
what trylock check?
youd need to be mroe specific
but what I do is the windows 7 way
You've learned about many of the new features of the latest version of the Windows kernel in the Mark Russinovich Inside Windows 7 conversation here on Channel 9. One of Mark’s favorite kernel innovations is the way the Windows 7 kernel manages scheduling of threads and the underlying synchronization primitives that embody kernel thread manage...
like if you wait on a sync object presumably it checks if it its already signalled so you dont wait on an unlocked mutex for example, but im running into issues where after that check the thing gets signalled but before i can get into the queue to get the thread released
thanks, ill watch this later
well, with a mutex youd spin for a bit either way
so this wouldnt happen
it still takes time between stopping spinning and actually being on the list to wait though, no?
ahh ok I get it
you mean
A:
tries to acquire lock, fails
<B unlocks lock, signals waiters>
blocks until lock is signalled
exactly
one thing you could do is have a lock protecting the mutex state such that when you go to sleep that lock is released
I think this is a case where solaris' weird thread-lock-is-a-pointer thing could be useful
i actually tried this and instead it deadlocked on that for reasons i dont fully understand since it shouldnt be possible (since the locking runs at dispatch and the signalling is done with a dpc that also runs at dispatch)
but what I actually do to fix that issue is the windows thing, where you have "in-between" states and do an atomic cmpxchg before blocking
what does blocks until lock is signalled entail? how is signaling handled?
how you best handle this depends on how you implement signaling
yeah that's pretty much it
and i guess you add yourself to the list before doing the fast path checks? that way the state can be set back by signal to be seen by the cmpxchg
this is a generic blocking mechanism that would be used by the mutex in this case
what fast path checks?
i give each thread a list of wait blocks and each waithandle a list of wait blocks and the object provides a check_wait function for the waithandle it owns to use
taking an unlocked mutex without blocking
for example
if you have something like a wait queue, you can use sequence numbers to do a wait-only-if-no-wakes-happened
I would just spin on that
if you're using a semaphore, you get this "for free"
i use wait queues
what and make a mutex a spinlock?
no, the idea is that you spin optimistically for a bit
because blocking is expensive when you could just grab it
yeah but again if you dont add to a queue first the intermediate state solution wouldnt solve the race condition
with wait queues you always want to have a (potentially restricted to only certain predicates) wait_if primitive
and not a wait() primitive
this can still happen if you optimistically spin for a bit as long as the signalling happens after spin before add to queue
but you were talking about the "fast path"
the fast path is grabbing the lock uncontended
the wait handle has a vtable for the predicate for the object that provides it in my setup
when you do need to block, you need to avoid the race condition by doing a cmpxchg
yeah but i dont understand how the object knows to change the thread state and break the cmpxchg if the signal happens before the thread is in the queue
if it fails -> someone satisfied the wait while it was prepared, back out
if not -> block
but that should avoid waiting if the lock is free, right?
the issue is if the thing isnt free until in between the optimistic part and the adding-to-list operation
yeah sure but the predicate should be checked at add-to-the-list time
the problem im struggling with is the definition of "at ... time" in this
wait():
atomic_store(&curtd_status, in_progress);
setup waitblocks in the object ...
if cmpxchg(&curtd_status, in_progress, committed):
actually block
else:
undo the waitblocks we setup
ok cool, this makes more sense
then to wake up you do a cmpxchg too
sorta
it should be something like
if mutex.try_lock() {
return;
}
if waitq.wait_if(|| mutex.is_taken()) == predicate_fail {
goto retry;
}
// Three cases may occur here:
// 1. The wait was still preparing (status == .InProgress) and we interrupted it.
// 2. The wait was already committed (status == .Committed) and we satisfied it.
// 3. The wait was already satisfied by another object (status == .Satisfied).
// 1.
if (td.wait_status.cmpxchgStrong(.InProgress, .Satisfied, .acq_rel, .monotonic) == null) {
// ...
}
// 2.
else if (td.wait_status.cmpxchgStrong(.Committed, .Satisfied, .acq_rel, .monotonic) == null) {
// ...
}
// 3.
else if (td.wait_status.load(.acquire) == .Satisfied) {
// ...
}
and you only unblock in 2.
mhm
yeah true, that is an alternative to wait_if()
where in this do you double check the wait object's status?
between the setup and the CAS
basically this splits the wait into a prepare() + commit()
exactly
this is what allowed the windows devs to break the dispatcher lock
to me this seems more intuitive, I'm not sure how wait_if works
i assume wait_if is basically the same thing but with a callback
instead of splitting it
there are multiple possible implementation strategies, but that is one of them, yeah
others are sequence numbers or protecting both the queue and the predicate by a spinlock
mhm
ill work on fixing my code to actually do this right after i get aarch64 updated to my new mm then
sequence numbers in the sense of:
s = seqnum.load()
p = check_predicate()
if (!p)
bail out
atomically_check_if_s_did_not_increase_and_add_waiter(self)
mhm
the last step can be done by a DWCAS or a spinlock
i think ill go with the intermediate state version since thats the most intuitive to me
the windows video explains this really well
yeah ill def watch that one before actually writing the code
the prepare() + commit() impl has higher overhead on predicate failure (since you're doing + undoing the list manipulation)
but ig this doesn't matter in the mutex case since you can just check before calling into it
yeah ill always be checking the predicate before preparing as a fast path anyway
I don't think this is really important though, since for a mutex blocking is the slow path anyway, even if you don't end up actually blocking
yeah that's true
tbh thats true for most things
if youre going to try to block thats the slow path
well, there could be cases where checking the predicate is expensive
but yes, it mostly doesn't matter
for example because checking the predicate itself requires a spinlock or something like that
anyway this is all gonna have to wait because i got distracted by space things
did get aarch64 updated to the new mm things tho
but now its late and im gonna go do not-coding things for the rest of the evening
and removed the old dmamem reservation stuff since I don't need it anymore
the new mm being physically continuous and all
suddenly it freezes always and i was confused for a while until i realised i typed != instead of == somewhere 😭
turns out i was shockingly close to having the dispatcher already correct
just sucks ass to try and be certain here lmao
no its still fucked
damnit
actually hang on
dont need to do wait list cleanup in signal_wait_block
fuckin hell
ok the completion callback variant of IRPs is clearly thoroughly fucked
if i change the partition manager stuff to not use it suddenly its working every time
ok now to quickly make the memblock -> buddy conversion not slow af
(it was freeing one page at a time which is uh. not ideal)
actually hang on
i dont think thats the main culprit
i bet the culprit is me memsetting freed blocks to 0 lmao
memsetting all usable memory sounds slow to me
and sure enough
alright thats all fixed now too
so the dispatcher is more reliable, i killed a feature in IRP to fix a bug with it i couldnt isolate which i originally thought was the dispatchers fault, the mm init is way way way faster now and using the new mm that is set up a good bit better
next task: a font
going to be taking a diversion to write a tool that preprocesses a bdf font into something i can just include in the kernel, and then ill have to do framebuffer work to draw the font
and then ill make an fb terminal to actually log to
doing that should let me test on real hardware finally too!
ok time to see if the shit i transformed this font into actually compiles
maybe lmao
now its triple faulting when i actually map the fb to vmem lmao
oh that was just me being stupid lmao
ez fix
close
tiny fix
ok gonna try printing out the device tree to this fb now
🎉
next up ill hook this up to the log ring but thatll have to wait for morrow
(shouldnt be that bad though)
once this is hooked into logging i can theoretically try it on real hardware too
my hopes of it working on real hw arent that high tho lol
def liking this font too
yeah the font is very nice
im tempted to add padding as an option for the framebuffer logs
like half a character rounded down on each side or something
Yoooo you picked it!
picked what?
im debating if i want to actually make any changes to the font tbh
make the lowercase x connected
did you make the font yourself?
nope, its peep
MIT licensed
Isn't it judge and not hear?
I prefer the quick brown fox
oh youre right lol
quick brown fox is a classic but im bored of it lol
i think i might make a halfway abstraction for simple log outputs tbh
something that exposes functions to set fg and bg color and write text
and then therell be a thing to make a log output out of that that uses a standard output format and consumes the structured log records
ok time to try logging to framebuffer
Pack my box with five dozen liquor jugs
yknow i kinda like that one
anyway i forgot to fill with bg when advancing a line whoops
can't you port nanoprintf to zig?
why would you use printf with zig lmao
its a library which implements printf
but also yeah why the hell would you want that?
no i meant the actual connection between my logging and the framebuffer lmao
oh XD
and yeah zig has a really good fmt api anyway
huh?
lucky
isnt it the one with insane codegen
a little lmao
but its still super nice as an api
wait, can i see that?
and the impl lmao
zig comptime is goated for exactly this sort of thing
oh i meant pitust's api XD
oh yeah i cant link you that lmao
thx
ok these colors are a tad better for sure
yeah
im using the same palette as zig's default logger does as far as chocies go
im just fiddling with the exact rgb for them
for encoding the pixels on the framebuffer yeah
well my framebuffer text rendered has to convert terminal colors to rgb lol
i'm using flanterm
bruh
im debating if i want to wire in ansi code parsing lol
me when i dont google;
rn the fbterm has a function for setting color based on a terminal color enum
many many terminal things will epect it
it does more than color too
yeah i know but this is for my shrimple logging
not for an actual full scale terminal
whats your plan when you want that tho 
also some ANSI codes are useful for logging somtimes

like cursor possision etc
or clearing the screen
cursor on/off
ill do it eventually
zig has an abstraction over terminal code stuff anyway
because windows doesnt do ansi codes by default
ansi code parsing is really painful tbh
also that lol
its a giant state machine isnt it?
yeah its a giant really complicated state machine
the only downside with the zig abstraction is it doesnt let me plug in my own vtable atm
so im making my own copy of it
flanterm handles the linux standard escape codes but, no offence mint, its not a very good terminal emulator (i.e. me when no unicode)
and it doesnt handle the extra stuff you need to make a good terminal emulator in 2026 (like the kitty extension that lets you do actually reliable keyboard input lol)
uTerm when 
lol
its very much not something that can be small
tbf if you need more than flanterm your prolly in the spot where X11 and xterm/others can be ran
is ACPI small even?
i want to do reliable keyboard input in my tui app 🙃
relatively speaking: YES
i mean doing unicode requires doing font rendering so like
so you need to do most of the text rendering pipeline (you can mostly skip shaping and layout at least), you likely need a rasterizer, you need to do all of the crap relating to segmentation, and the state machine itself is not simple as well
- flanterm doesnt handle resizing which is also "fun"
idk which exact features flanterm has apart from that
It’s easy to drop into any project in a bare environment?
flanterm? yes
Yeah, idk what else FT has either
flanterm is codepage 437 in general which kinda sucks
and it seems like flanterm doesnt have alternate buffer support?
which also sucks
Took me 5 minutes
gave it a bit of padding
Add two c files and call one function
What's that
its like the vga character set
honestly if your terminal cant draw a firefighter (dark skin tone) emoji its a shit terminal
Flanterm did manage to print out a ralsei with the block characters so it can’t be that bad?
Tbh im glad it doesn't support Unicode, I dont need that shit in the kernel terminal emulator
ye its fine for a debug terminal
its not good for a proper terminal
that one emoji is 15 bytes btw
Is the Linux built-in text terminal better?
idk probably not lol
so this is a 32 byte password: 🧑🏿🚒.🧑🏿🚒!
XD
so is this: 🇵🇱🇵🇱🇵🇱🇵🇱
👨🟫🚒

8 code points btw

each flag is two code points
Oh I remember that fucking up emoji parsing
Because some emojis are multible charaters
Meaning I can’t properly track shit when I needed to
With regular JS stuff
flag emojis just seem to not render on my computer idk why
this is not just the case for emoji
Then what happened?
What I like is that it's not magic unlike certain languages
I needed some funky things iirc
its also the case for certain forms of the german letters ü, ö and ä among others; and for hangul (which is korean writing) and some indic scripts
Ahhh
same, being able to just. write code that looks normal. and have it work is so nice
I mean like it's completely implementable on your own, without compiler support
yeah
oh no lol
its the case for many emoji including all flags
and the firefighter emoji
and for some hangul
A lot of emojis are combining shi
and indic scripts which are mostly the same as emoji
you can also have prefixes and suffixes
libghostty
Yeah
It's completely freestanding afaik
my font rendering rn only renders single codepoints and only if theyre present in the font
zig std has a codepoint iterator for utf8 its great
sure, if by freestanding you mean it depends on harfbuzz, oniguruma, mesa, libpng, gtk4, fontconfig, and another png (among other things) parsing library
oh neat
no its not you should never use those kinds of APIs
It's a C library
they make it way too easy to write broken code that falls apart with emoji
or with you know. some languages that arent english
this is for debug terminal i wouldnt use it for the real thing
just iterate over the bytes for that tbh
You don't need emojis in a tty
and assume utf8 == ascii == latin-1 == code page 437
i use box drawing chars in a few places for printing device tree stuff and i need to handle other outputs that need the real unicode for that
i do have to convert to the bullshit encoding this font uses
which doesnt seem to match any of those normal ones for box drawing chars
this also lets me use the visible control variants ␀␌␍␊␉
But...! But, how will i print my "[✅] GDT Initialized"!!!!
so the way i handle this rn is the fbterm iterates codepoints (since i only handle single-codepoint chars) and then have a switch that converts \r into resetting the column, \n into advancing the line, \t into four spaces worth of bg, and the visible controls and box drawing unicode chars into the encoding the font uses
also i could always call next on the iterator in the loop to handle multi-codepoint things if needed later
yeah just call next its so easy
(hint: its not, in fact, that easy; the state machine is fucking pain)
Terminal emulator state machines are painful
mhm
this is why im using the most shrimple possible setup for my debug log output
wonder if ive got an unused usb stick i can put this disk image on tbh
since in theory im now set to at least make a first attempt at real hardware
oh yeah
zig 0.16.0 got tagged
so i should probs work on updating and pinning to that sometime
taking a break both for irl reasons and also because the language updated and all my deps are now temporarily broken and id rather be lazy and wait on others to update them instead of doing it myself
and by all my deps i mean my one dep and its sub-deps
Curse of Zig
ig
i could just not update
but i want to actually pin to a version that isnt a random nightly
in the meantime i might whip up a devlog about the silly trick i found for making the lookup of char to glyph at comptime for my basic fbterm rendering
zig has a type called EnumIndexer which lets you convert from values of a (possibly sparse) enum into a compact index space, and a type called EnumArray thats basically just an array mapped to by an EnumIndexer
so what i do is i generate an enum whose values are all the character encoding values the font uses, and construct at comptime an EnumArray from that enum. then i can use @enumFromInt (or the fallible std one that lets me have a fallback) to convert from codepoint to enum value and then the EnumArray to get the glyph
decided on a whim to look at FRED and holy shit this is amazing
reminds me a lot of how exception handling stuff works on aarch64 actually
in a positive way
its true ive been saying this lmao
There is a reason I shill it okay
yeah no it took one skim through for me to be converted lmao
Lmfao XD
It did the same to me
Mint wondered who would be the first hobby kernel to implement it
I skimmed over it and was convinced
Until I spent 70% of the time it took to finish it trying to get a working emulator
💀
lmao
That’s also why I got the cool role lol



