#tabula imaginarium: osdev experiments in zig
1 messages ยท Page 6 of 1
So tldr if it was booted in xapic mode use xapic format
yeah i think so
thats what im going to do as a less hacky hardcoded fix than my initial testing version
alright time to try this shit for aarch64
alright got the irq rework set up on aarch64 too
may redo some of it a bit because i was being lazy in parts but otherwise its fine
aarch64 was easy because instead of having to write three separate irq domains i need one, total
at least for now
the lazy is making it one instead of two lmao
and the two would basically have the same code
so tbh its probably best not to do it as two just for that sake
(I may tweak somewhat and implement the logically separate second one on top of the first instead of entirely different, undecided)
actually hang on i already have that for the SGI stuff
just gonna reuse that lmao
and with that im done for today
next up based on the poll results is storage, so itll be time to put all this abstraction to the test and write an NVME driver
nvme because its the most shrimple
nvme also goated by putting specs up on their site for free and easy to find
oh god wait i need MSI for that dont i
which is easily done on x86 but going to be a whole can of worms on aarch64
not sure which arch's MSI stuff i should start with
aarch64 is gonna be way the fuck harder due to both being more complex and being less well documented
(the spec is pretty good but theres no implementation guidance or samples out there really apart from trying to parse the linux source which uh its the linux source and that part of it is a fuckin mess)
what do yall think? easy or hard first?
also @last pebble on a technicality i have found a take-any-gsi situation, insofar as MSIs on aarch64 pull from a range in the same pool of interrupt ids as everything else and the acpi spec defines a 1-to-1 correspondance between IntIDs and GSIs
its not actually really the same though, acpi shouldnt ever give you a GSI in that MSI range (arm calls em LPIs)
Hm
also stole @finite ether's rmb/wmb functions since we're sadly stuck in a world without a fence builtin in zig for now
i think before i do any of the actual MSI stuff im going to make a parser for the IORT table
cause ill need that for aarch64 MSIs and may as well do it now
let's form a cabal so they can add it back
if we can provide real benchmarks showing it would generate a significant speedup they might, the reason to remove was that the perf gains were negligible and 90% of uses the devs had found of the fence builtin were being used wrong because atomics are hard to reason about
it's not a performance thing it's more of a correctness thing
you cant really implement seqlocks without them
oh hey seqlocks are exactly what i was talking with the devs about about fences lmao
I asked in the zig server and people there seemed like idiots so I left
id asked in the zulip where they discuss actual dev on zig itself
this was the reply i got when talking about them
we'd have to ask @tiny summit can we implement seqlocks without fences?
you can make every load a load-acquire, then you don't need a fence
does that apply to the data?
yes
then I'd guess this does have considerable overhead
it probably depends on how much data you want to read. if it's only two words or so, the overhead won't be that big
everyone guessing that and never producing actual numbers except for some specific cases on m1/m2 that were since fixed by the hardware people is literally the argument the guy in the zig team gave me lol
everyone keeps throwing around probablies and guesses about the perf here and never numbers
but if you want to read, say, a page of memory, that'd be way more expensive than the fence
I think I'll take a look at where linux uses seqlocks, take that same amount of data and benchmark it
if the numbers look significant on actual hardware we can put an issue up asking to get fence back and maybe have it accepted
in the meantime im going to go back to my writing acpi table struct defs lol
on x86 it won't make a difference because every load is a load acquire
on risc-v you'll probably have a lot of overhead since there is no load acquire and it'll do a fence after each instruction ๐
on arm it'll probably depend a lot on the concrete implementation
yeah, that'd be interesting
m1? m2? m3? which silicon specifically counts, m1 and m2 iirc had some nasty hardware bugs that made atomics slower than they should
m3
on x86 the biggest diff that you'd see between fences and no fences is seq cst stores
for
static void WithStore(benchmark::State& state) {
volatile int x = 0;
for (auto _ : state) {
for(int i = 0; i < 10; ++i)
__atomic_store_n(&x, 42, __ATOMIC_SEQ_CST);
}
}
BENCHMARK(WithStore);
static void WithFence(benchmark::State& state) {
volatile int x = 0;
for (auto _ : state) {
for(int i = 0; i < 10; ++i)
__atomic_store_n(&x, 42, __ATOMIC_RELAXED);
__atomic_thread_fence(__ATOMIC_SEQ_CST);
}
}
BENCHMARK(WithFence);
im cooking up a test with seqlocks rn
and for write() in seqlock I can make the sequence increment acquire too?
pub fn store(self: *Self, data: T) void {
const seq = self.sequence.load(.monotonic);
self.sequence.store(seq + 1, .acquire);
self.raw = data;
self.sequence.store(seq + 2, .release);
}
like this?
Acquire store is ub lol
ah fuck you're right im stupid
it's load-acquire store-release
I'd need a store acquire for this work
I think I have to have a load-acquire here too for the data copying
no for the writer side, acquire -> relaxed (for data) -> release is enough
disregard that ^

you need store releases for all the data
if you can't use a fence
or a RMW acquire for the initial + 1 bump
actually why is it usually done this way (+ a fence) and not with a rmw?
a release fence is free on x86, for example
(i.e., a no-op at the instruction level)
ah I see
I know you don't know zig, but this should be correct, right?
fn memcpy_load_acquire(dest: *T, src: *const T, n: usize) void {
var i: usize = 0;
while (i < n) : (i += 1) {
dest[i] = @atomicLoad(u8, &src[i], .acquire);
}
}
pub fn load(self: *Self) T {
var ret: T = undefined;
while (true) {
const seq = self.sequence.load(.acquire);
if (seq & 1 != 0) {
std.atomic.spinLoopHint();
continue;
}
memcpy_load_acquire(&ret, &self.raw, @sizeOf(T));
if (self.sequence.load(.monotonic) == seq)
break;
std.atomic.spinLoopHint();
}
return ret;
}
pub fn store(self: *Self, data: T) void {
const seq = self.sequence.fetchAdd(1, .acquire);
self.raw = data;
self.sequence.store(seq + 2, .release);
}
monotonic is equivalent to c's relaxed fwiw
why is it named like this?
no idea
monotonic? thats the llvm name
yeah looks correct
ok good
on your m3? send it and open an issue with zig for sure
yeah thats on my m3
thats some data right there
well this is single-threaded, but it still shows the latency
oh fuckin hell i forgot a field no wonder the values coming from this iort parser are nonsense
looks like it would benefit from different per-arch impl
you gonna open an issue on the tracker for this? because this is clearly a big issue at those numbers lol
I'm writing better tests first
in any case i got iort now parsing nicely
mhm well this seems better
logs should be fairly self-explanatory but the tldr is that
- pci requestor ids 0-2ff get mapped to smmu input stream ids 0-2ff
- pci requestor ids 300-ffff get mapped to its device ids 300-ffff
- smmu output stream ids 0-ffff get mapped to its device ids 0-ffff
and that all seems fairly reasonable to me tbh
with no smmu it just maps pci requestor ids 0-ffff to its device ids 0-ffff
which will be useful once i learn what the fuck a pci requester id is lmao
if youre using zig try with an without llvm also (assuming the aarch64 self-hosted can handle whatever inline asm you need to make the fences happen)
I am using ReleaseFast so it should be llvm
yeah now I do multiple runs and get the mean and its even more conclusive
although to be fair, I am copying byte-by-byte while technically I could copy word-by-word but then the implementation would not be as generic
do you want to co-sign it or something
i think the more we are the better it will be heard
yeah ill throw my name in it
feel free to put "co-signed by khitiara" or something like that in there
how do I name it, "add @fence back"? lol
id call it "a performance argument for adding @fence back" or something like that
"Restore @fence / expose memory barrier primitives for efficient seqlock-style algorithms"
(also just to confirm, youve not used the clanker for any of this, have you? cause zig is very very strict about their no-llm policy. im assuming you didnt given you literally contribute to the made-by-humans repo docs but may as well ask)
yeah that works
I used it for the graph script
which is python
and unrelated
then dont include the graph script in the issue ig
(also if you got the raw data do put that in the issue, its always good to have)
well I cant create an issue for a language proposal lol
just make a blank one
the language proposal option doesnt let you to stop people from suggesting random shit with no effort
mhm ok
also fence used to exist which means this is arguably a regression and thus a bug
mhm do I attach the files or just copy paste
idk
my benchmark file is like 200 lines lol
at that point maybe attach
yeah its khitiara
thats my username basically everywhere
thankfully uncommon enough to not have issues
im stupid your project is literally on codeberg
lmao
im lucky in that khitiara is uncommon enough that if i have an account somewhere its almost always that
id make the image an embed instead of an attachment personally
but yeah cool, subscribed to notifs
idk how to do that
also I just realized my wmb is wrong so you should change that
make ishst on aarch64
i think if you drag drop it into the editor itll work
its faster
fun fact yours is what llvm does
i actually double checked
had to look up the fuckin instruction encoding
yeah but here a dmb ishst is fine
since it's a write barrier
llvm has to be more generic for its thread fences
i think
i ended up adapting for a proper user-code version of @fence instead of a write barrier
zig tries very hard to never have ub
its either defined or illegal
nope
so what is a data race defined to
so the way llvm does it (and i had to double check the encoding) on aarch64 is if the order is acquire it does ishld and otherwise it does ish
linux has their own imaginary memory model
idk I'll do your thing I think and just steal from llvm
 should work
what i couldnt figure out is what llvm does for barriers on x86, but yeah afaik they basically dont exist unless its a full barrier so whatever
thanks
you dont need any on x86
you just need a compiler barrier
which asm volatile with memory clobber ought to do
yea
what im doing rn is seq_cst on x86 gets mfence and the rest are compiler barriers, and aarch64 gets the same as llvm does
and then i have a separate function for dsb which is dsb sy on aarch64 and idk what to put in it for x86 rn its nothing
(actually rn on aarch64 its a dsb with two isbs around it because i was having issues)
yknow i might be able to get away with not reworking my buddy allocator if i only support 4096 different MSIs on aarch64
yknow only 4096 of them lmao
sadge, ill have to actually allocate collection tables smh
alright this looks reasonable too
and everything accepted 4k page size thank god
setting indirection took too
thats good
ok sick the math does add up here
i can get away with only discontinuous 4k pages here
wonder if ill be the first hobbyist os to have full gic its/msis
if theres others ive not been able to find any info about it or i would be referencing that to figure this shit out lmao
anyway i should be good to turn the ITS on now
but im done actually coding for tonight
so that can wait for morrow
once i enable it i can start sending it commands
like MAPC and MAPD and MAPTI
and SYNC
SYNC is probably the most important one lmao
ah fuck
i thought i could get away with this shit and nope
i cant tell this i only support 4096 LPIs, only the number of bits of total intids
which because the first LPI is 8192, means i can only support 8192 LPIs or 0
and the config table is one byte per LPI in required physically contiguous memory
ig youll be fine then, the architecture said fuck you to me and i have to support 8192 MSIs actually
well im allocating all the LPI and ITS stuff and enabling all the bits
almost scared to find out what i did wrong ๐
it aint erroring atm
ig i should hook up the interrupt dispatcher to the LPIs tbh
what are you using to test this? is it implemented on qemu?
it is implemented on qemu yeah
for the virt board
and tcg supports the ITS
which is what im implementing because im doing GICv3
the problem rn is i uh dont have any drivers that request MSIs yet
so i cant easily test this other than by if it throws an error on my config accesses
the actual config registers work though ive got enough debug printing to be sure of that
ok so now i can reserve LPIs
and the ITS and LPI tables are all allocated and the ITS is enabled
the next thing then is to write the ITS irqdomain
which will reserve LPIs and then map (device id, event id) pairs to (cpu id, LPI) pairs
(note that LPIs are global numbers but the mapping of LPI to target redistributor is on the ITS so the cpu picks happen there)
once ive got the ITS irqdomain i can make the MSI domain on top of it, which will convert to the address,data pair that you need for MSIs
i think i may make MSI domains their own type actually
either that or make the domain be per-deviceid
honestly might go with the latter
linux gives msi domains their own type tho
idk
anyone got thoughts there?
i think making it per-deviceid makes the most sense with my current irq abstraction setup tho
what i might actually do
thinking about it
is make an "msi domain" type
that has a single function to create an irqdomain for a given device (aka requester) id
(on x86 itll just have a singleton irqdomain that it returns)
and then each pci root complex or other arbiter of MSIs can have one of those
on aarch64 the msi domains will be each tied to a node in the IORT
and then creating a domain for a given device id will use the iort to figure out the msi target, and then wrap the relevant ITS domain
ok im now running the MAPC commands
so time to find out if i fucked up
well ok first i gotta fix a billion compiler errors
well
it isnt erroring yet
alright
MSI domains done
i might actually have the first hobby os implementation of GICv3 ITS MSIs now
i couldnt find any other examples anyway
(i wont say working implementation yet because i need a thing that generates MSIs first)
alright whats the simplest pci device i can put in qemu thatll let me generate MSIs on command
anyone got any ideas there?
One of the pci virtio devices
eh ig
i dont know anything about virtio stuff maybe i should learn sometime
pulled up the virtio spec this morning, started reading it, and then was like this shit is unreadable and closed it
maybe it was an old version though idk
i also did the same ๐
nvme was the next thing on my main todo list anyway
but still, virtio is probably overall easier
mhm
if i dont do some simple thing to test MSIs with the next step was going to be writing an nvme driver anyway, so if nvme is relatively simple then i might as well do that?
idk
if you want NVMe anyway, it makes sense to go for nvme
yeah
(strictly my next goal after the irq abstraction rework and adding msis to that abstraction was to do storage, and i was planning to start with nvme there because ive heard its one of the simpler options)
the cool thing about virtio is its the same driver for all devices
the only thing that changes is the request format that u put into the queue
ah, so i could make a virtio "bus" driver that then all of the individual device types sit on top of then?
the spec is a bit all over the place but thats because it tries to be platform abstract
thats how linux does it
virtio-blk is very close to nvme, u just have submissions and completion queues
but very much simplified
there isnt an admin queue or anything like that
but yeah nvme if u wanna test on real hw
virtio has nice things like net/blk/gpu/sound/rng/console etc etc
but just so i have it written down: the idea for virtio then would be to have a driver that detects based on the PCI vendor/devid stuff and then adds a HID of the form VIRTIO\{whatever} to the list and re-queues the device for discovery, and the individual virtio things would discover that hid and use a private dispatch IRP to put stuff in the virtio queue
i will probably still do nvme first though
not because im equipped for real hardware atm but because my goal with this project is learning and thus i have a lot more interest in emulated hardware than i do in stuff like virtio
false, it will boot with nvme in bios mode if and only if the total memory is 2G or less
above that and the nvme emulation puts its BAR in high physmem
oh ok
but also my uefi-only bootloader for this project wont boot in bios mode either so 
not exactly limiting myself there by using nvme atm, not any more than already
oh yeah i do also need to make the x86 msi domain too
rn ive only implemented MSIs on aarch64 (because its way fuckin harder there)
how does it boot with other pcie devices and >2G of RAM in bios mode then?
only some of them are put above 4G?
yup, I've only noticed it with nvme, and also most of the others aren't needed by the bios anyway
it's only a problem when your boot partition is affected
yeah i'm just surprised because i always boot from virtio-block and never noticed that
but ig it depends on the numbering on the pci bus or something like that
iirc this has to do with the bar being 64-bit
seabios is more eager to relocate those above 4g
ah that makes sense
they had numerous patches dealing with bar relocation blowing up old guests and then fixing blowing up new guests
its a constant back and forth they have
i even fixed one of those bugs myself i think
it was failing to properly map all the bars if u had like 8 nvidia cards there with giant bars
if you have that you probably want uefi anyway
u do true
its also useful so u can lock the guest down with secure boot to prevent it from messing with the drivers
since nvidia is really strict about guest driver matching host driver
or everything explodes
yup exactly, nvme defines it as a 64-bit memory bar
whereas the other storage options dont have to be 64-bit
anyone know a good reference on PRPs? really struggling to figure those out
PRPs?
an nvme thing
Ah
iirc in the simplest case you just have two pointers to data
there is a way to do an indirection and then you get another page (?) of pointers
yeah that indirection thing is what i dont understand
and it like depends on what the command is? maybe?
ok yeah
for some commands the first pointer is indirected
for others the first is just a pointer and the second depends on how many pages get crossed
for extra fun there is also a SGL (scatter gather list) extension but that's separate from PRPs :^)
yeah
iirc the SGL is only useful to read/write partial blocks to a buffer though
like, if you want the first half of a sector to go to a different buffer than the second half
also if its nvme over fabrics then youre required to use SGLs
nvme over memory (aka pcie) requires PRPs for admin commands but doesnt specify anything in particular for IO queues
yeah, that's also the only case of SGLs that we support in Managarm
i dont really plan to support nvme over fabrics at this point, at least until i find an actual case for it
our use case is netboot
ah
But like isn't it significantly harder to do virtio gpu
depends if u mean modeset or 3d
They should've made it where you can just send opengl ops directly 
commands themselves are easy, the hard part is the byte stream of commands and instructions etc
i started doing osdev partly because i was trying to learn modern 3d graphics programming and hit the point of desparately wanting to get away from it
id take digging through osdev hell specs and shitty firmware over 3d graphics programming any day personally
how does userpace OpenGL/Vulkan interact with the GPU anyway
ioctl?
3d is pretty fun if you do it yourself (entirely in software)
the actual opengl or vulkan is being loaded dynamically from the driver using a getprocaddress type setup
ah
how the driver actually does it internally is up to it
yeah its probably ioctl internally unless the driver is fully usermode
but the actual opengl/vulkan impl is part of the driver
(petitgl is my single-header ANSI C opengl 1.1 partial implementation
)
lol
id generally take vulkan over opengl at this point
just because its a lot closer to how the actual hardware is set up
well vulkan is way more modern
yeah
mesa has generic implementation for all of those
that eventually calls into gpu specific callbacks
yeah mesa is great
side note my bootloader protocol is inspired by vulkan
in what way lmao
in the way that a lot of vulkan functions support extensibility/adding features by using a singly linked list of structs (which are all basically tagged unions)
my bootloader passes a singly linked list of tagged protocol structures to the kernel and the kernel iterates it and handles any it knows about
yeah
thats the only sane way to do it i think
its a common and good design, it just happens that vulkan is the first place i personally encountered that pattern
(vulkan is slightly different in that you pass a list of empty tagged structs and it fills the ones you passed whereas this boot protocol just passes all the things)
Isn't multiboot2 more of an array of tags instead of a linked list?
i wouldnt know tbh, ive not used multiboot anything
As in, mb2 tags don't have a "next tag" pointer or whatever
dont think it matters
the idea is u have a tagged union + size
or just tagged union + next link
i dont actually have a size, just tagged union and next pointer
the size doesnt matter because its allocated from a fixed buffer by the loader anyway
I also do that for my prekernel
This specifically.
(technically what im doing wouldnt be called a tagged union in zig parlance because tagged unions are a zig feature, but they dont currently have a defined ABI which means i cant pass them across the compilation boundary safely so i use an untagged extern union and a separate tag field, and convert to the zig tagged union later)
i may actually rework some of my boot protocol stuff at some point soon tbh
mostly because i realised that some of the stuff im putting in high memory doesnt actually need to be there and could be left in low memory or even on the bootloader stack
since i dont kill the lower half page mappings until after i use most of the boot protocol stuff
im undecided if i want to keep the memory map in highmem tbh
im increasingly thinking i might make imaginarium a serial-terminal-first os tbh
dont have framebuffers even being fetched by the bootloader yet and not yet feeling like i miss that at all
plus if i ever get a teletypewriter itd be sick af to use my os from it
virtio-gpu transports Mesa IR to the host
so it only works if both the host and the guest run Mesa
there's also a vulkan variant, that might be more portable / less dependent on Mesa but I'm not sure
yeah
there's some feature bit thats set if 3d is supported
in virtio-gpu config space
but it is a pretty niche thing
@haughty cipher btw
as far as nvme goes
leo used to recommend that u start with an older version of the spec
newer versions assume u know stuff and leave out a lot of info
oh good to know, ill look for it
u can grep leos messages to check for which versions specifically
ok the real question is which leo
no92_leo
thanks
np
goddamnit discord
lol
I don't think it's that newer specs leave out stuff but they split it over 10 documents instead of 1
didnt expect it to defocus the search bar when i hit tab on the from bit
discord ux strikes again
yeah
also this, ive got three docs i need open to get everything for the spec if i use current versions
why the hell did they do that 
idk
theres the "base specification", the "nvm command set specification", and the pcie transport specification
thats cool and all but they could also you know... ship a full version as one pdf file
yep lmao
same as amd prms for example
yeah
they have one giant book or small ones
in this case the "nvm command set" is the stuff that actually deals with nonvolatile memory (yknow the main thing you use nvme for)
the base spec doesnt assume the controllers can actually do anything except basic ident stuff and io controllers can create and destroy io queues
yeah
well and cancel and flush, the base spec assumes the io controllers can cancel and flush
if you want read and write you gotta pull up the nvm command set spec
who needs that stuff 
and the pcie spec is self-explanatory except also you gotta reference a bunch of shit on the main spec too because a lot of the pcie transport spec assumes you already know what the base spec requires of memory transports (of which pcie is the one)
reminds me of ahci
where the command set lives in a completely different proprietary spec
that u can only find leaked copies of
but it's way worse there in general
mhm
at least with nvme theres just a bunch of specs on the site you can reference lmao
but like theres a bunch of cross referencing where the pcie spec says the vector is associated with a queue and then you gotta find the bit in the create io completion queue where theres a field for vector or the random one sentence deep in the config register description for the admin completion queue address that says "this queue is always associated with interrupt vector 0"
yeah ig
did u implement anything in code yet?
Fair
and likely to be busy the next three days too but less so lol
Ill be curious to see how zig drivers look
before i actually start it i do need to get x86 msi domains set up
since i got so distracted trying to piece together the hell that is the aarch64 gicv3 msis i forgot that
Msi on x86 is incredibly easy so thats good
yeah
im glad i started with the gic for it, because thats the main driver of how i abstract em
What's required for msis there
the abstraction for normal interrupts is mostly driven by x86, for msis its the other way around
ok so
each msi source has a device id
and then theres this thing called the ITS
and to trigger an msi you write an event id to an mmio register in the ITS
and it uses some implementation defined mechanism to get the device id
then it looks up the device id in one of its tables
(which is a two level table set up so the lower level is exactly one page and the upper level has to be physically contiguous and you have to allocate it yourself but also it might not support the two levels which would make you make a fuckin huge table)
the device's entry in that table is a pointer to another table for that device
the ITS looks up the event id in that second table
(which you also have to allocate yourself)
that second table lets the ITS translate the device id,event id pair into a combination interrupt collection id and intid
the interrupt collection id is looked up in yet another table you allocate yourself
which converts from the ICID to the mmio address of one of the per-cpu redistributors
What the actual fuck
and then the ITS sends the interrupt with that id to that redistributor
that redistributor then looks up the interrupt priority and mask bit for that interrupt id in another table you have to allocate yourself which has a minimum of 8192 bytes and must be physically contiguous
if its too low priority it sets a pending bit in another table you allocate yourself
if its high enough priority to get sent then the IRQ exception vector gets triggered, and you can read the interrupt id from the redistributor
and then you have to use your own data structures to convert the interrupt id back into a combination of device and event id
and then propogate that back
Me when irq controller needs 10k loc to send a damn irq
afaik mine is the first hobby os to implement this lmao
Msi for gic?
this is only for MSIs, the non-msi interrupts are super super ez
yeah. i couldnt find an example of it anywhere and the docs are kinda bad so the only way i figured this shit out was by cross-referencing the spec with the linux source which means having to decode the mess of duplicated abstractions for bitfields and some macro hell and whatever the linux abstractions are
when i say macro hell
Lol
What the hell
Did they really not find a way to deduplicate that code
so this takes a builder for the main command in the arg of the main function, and a builder for the relevant sync command in the arg of the macro, and then makes a routine which sends the main command and then the sync command if the main builder returned a sync object to associate with the command, and then waits for it to finish
What is its anyway?
interrupt translation service
Ah
(oh yeah the top level of the device table and the pending and config tables youre allowed to write to with your own code but everything else you cant and have to send commands to the ITS using its ring buffer which you also have to allocate yourself)
and if you ever modify the LPI tables (thats the one the redistributor checks) you have to send an INV command to the ITS to make it tell the redistributors to reload that from cache
its turning a builder that uses not the normal bitfield helpers even though the normal bitfields are used elsewhere in the same fuckin file into a thing that builds and sends the command to the ring buffer and waits on it
and to wait they have to use stall because the interrupt controller wont interrupt you when it finishes
there is actually use for the command ring in GICv4 fwiw, since that version adds full virtualization and you can use the commands to send virtual ipis around and reroute virtual msis to other virtual machines
so its got hypervisor use
but also you have to use the command ring to send an inv command if you mask or unmask the interrupts at the controller level
which is the main thing
https://codeberg.org/Khitiara/imaginarium/src/branch/rewrite/src/krnl/hal/arch/aarch64/gic/Its.zig#L448 anyway heres my code to reserve an interrupt from the ITS if youre curious
and the redistributor stuff is https://codeberg.org/Khitiara/imaginarium/src/branch/rewrite/src/krnl/hal/arch/aarch64/gic/lpi.zig here
dont have any 
Do u use atomics for mmio there?
What arm hw has gic3 even?
linux does that and i was like eh why not
no idea lmao, but its the best option ive got on qemu
also gic4 is back compatible with gic3 if theres hardware with that
gic5 looks a lot nicer than gic3/4 but the acpi spec support for gic5 is still in draft and also qemu doesnt support gic5
Never seen atomics being used for mmio on x86, I guess its an arm thing
yeah ig
i debated between that and just making it volatile but i still dont trust volatile due to past issues with it that probably arent relevant anymore in general let alone in zig lmao
Are asm stubs just not popular anymore
i use those on x86
because even though i probably dont need to worry about that one weird possible amd issue i already have them
Does Linux not use them on arm?
idk about in general
its linux nothing is consistent
but all the GIC code uses relaxed atomics for registers
They rolled ad hoc mmio helpers for this specific device?
Wtf 
Inconsistency and code duplication final boss
lemme check the spec actually
maybe it has something to say about accessing the registers
ah yep
there ya go @last pebble
That's so strange
But at least explains it
So they dont use the normal stuff like writel() etc
yeah
i think technically unordered would work here
doesnt have to be relaxed
just needs to be atomic (as in a single instruction)
actually no
it does need to be atomic properly
Well thats just talking about the standard mmio memory mappings
They should guarantee order
mhm
Usually arm does those in multiple or smth?
Can u send a link to where they use atomic stuff for that on Linux
Interesting, writel actually looks like an even stricter version
if i follow that chain as best i can
it is an asm helper
as it turns out
i may make an mmio helper thing at some point
got some ideas for that now that im thinkin bout it
The normal one also slaps some barrier on top
mhm
in that case im probably best leaving this as atomic for now
wish i had real hardware for this
just a basic sbc with an armv8 cpu a gic3 acpi and pci and im set
and a serial port ofc
Yep
so a thing im considering atm
i could theoretically whip up an extern or packed struct in zig that holds a single int and has read and write methods that act as mmio helpers
probably extern so it can't get out into a packed struct
there already is one for atomic values after all
could even use that to set up pci config space as a struct etc because its an extern struct and then just inherits size and alignment from its one (1) field
downside for pci config space would be support for legacy access
ARM does not support atomic access to arbitrary MMIO
By atomic, I mean ll/sc
Access to the same mmio region are strongly ordered on ARM but they are not strongly ordered with respect to other memory accesses
such as cached accesses or accesses to other mmio regions
Or are we talking about atomic access to tables in RAM?
older versions of the virtio spec are more concise.
i found it "not good, but implentable"
luckily they just ship the full inizialization routines for most stuff
ata acs? but that is not ahci (in that it's not a part of it and it could be used with something else, like ide). however it's definitely that t13 are greedy pvssies. worse than them are only t10.
then how would you interpret #1312232715193683988 message as far as requirements?
I think that's referring to normal loads/stores, not rmw
i think it just means regs must be written and read via their real type and not split up into multiple
especially since from looking at linux source it uses relaxed mmio helpers for it
yeah
I'm def gonna do my mmio value type at some point then
itll be an extern struct with a single field so itll inherit its representation fully from that field
and then have methods that take self-pointers and do the ops on that
alright did that change now
double checked and linux is using normal load stores except for one weirdly specific range of processors where the loads are acquire lmao
love fuckin hardware quirks
Yeah that's a funny quirk
https://codeberg.org/ziglang/zig/issues/31022 now if only this bug didnt exist so i could be slightly more general with my asm constraints
(fyi @finite ether on that one if you didnt know about it - "m" contraints in inline asm are currently broken in zig and cause a double-dereference if you pass something.* in)
realised i wasnt parsing pci capabilities yet so now im trying to add those but also got stuff going on later so probably wont get that done today
It's not even a thing anymore no?
Now it's a struct with memory = true
constraint not clobber
(where youd put register or whatever that you want that thing to be put in)
this
things tonight got canceled so ig i can keep working on pci cap iteration stuff
๐
alright my brain is slowly turning to mush so im gonna quit while im ahead
tomorrow: ima send a command to the controller and see what happens
realised a stupid mistake i made that ill have to fix tomorrow
which is that its currently throwing the virtual addresses for the completion and submission queues in there lmao
ive gotta make a generic virt_to_phys function tbh
i keep forgetting to use phys address when setting stuff up because i dont have an easy helper
alright the pci driver is now wrapping the msi irqdomain with a per-function one that handles masking
and made a phys_from_virt function to make it easier to set up device stuff
got shit going on in 15 mins but after im done all that ill try sending a command in the admin queue
i think you should have a dma memory allocation api
i do sorta now, had to make one for the GIC on arm
it just reserves a block of physically contiguous memory that you can pull from
rn on x86 it defaults to reserving nothing, on aarch64 it allocates 16mb
i was thinking about something that'd let you abstract iommu nicely from drivers
thats def something ill do eventually
i actually do sorta already
in that the io resource manager takes stuff like BARs or acpi memory resources, maps them, and then provides both a physical address and pointer to the driver
and it could easily just provide the address in the iommu instead of the physical address later
yeah but what about allocating buffers at runtime? for stuff like queues
ill provide a dedicated allocator for that
and an iommu version of phys_from_virt
ideally it should all be abstracted away from drivers, they shouldn't have to distinguish between memory in iommu and host address space if that makes sense
thats where the allocator interface in zig comes in handy
driver code written "with iommu in mind" should work without an iommu without changing anything
i think i'm stating obvious facts lol
so the plan is to provide two allocator instances to the driver, one for device-facing and one for host-only. if theres no iommu then the two will be the same but if theres an iommu the device-facing one will use it
if that makes sense
(technically the device one wont be a true zig allocator instance, itll be a wrapper that adds a virt-to-device function too, but the idea is the same and if no iommu then virt-to-device is just virt-to-phys)
(or actually more likely ill make the alloc function return both a pointer and the device-facing address)
that sounds nice
may actually make that abstraction after my appointment today tbh
just to not need to rework everything in the kitchen sink later when i add iommus
got that code up now, thanks for the prodding to actually go make that
maybe now ill go make the x86 msi stuff that ive been ignoring lmao
my brain: i should do the aarch64 MSI stuff first cause its harder
also my brain: alright aarch64 msi stuff is done lets jump right into nvme
in the process ignoring all of the x86 stuff lmao
alright we got x86 MSIs now
for the first time ive got something that uses blocking
so thatll be fun
soonโข
ok this is very broken lmao
x86 is just freezing and never getting the interrupt
and aarch64 is timing out in the ITS stuff
im hungry though so taking a break for dinner
oh i think i found the ITS issue
very dumb bug lmao
ok now both of them hang when waiting on the nvme controller to process the command
oh
im stupid
maybe enabling msi/msix is a good idea if i want to get interrupts from it 
ok the aarch64 its stuff is still at least part broken but i can try this on x86 now
its at least dispatching the interrupt but only on xapic not x2apic which is weird
pcie is hell actually
and i hate how hard it is to find the specs
turns out i need to walk upward to find possible alias conditions for the pci requester id
ok got that sorted
now to see if i cant get it to release the thread
oh hey it did pog
oh lmao no wonder the nvme controller is returning an error
i had the wrong opcode
popping off actually
now why the fuck does it break under whpx
gonna test kvm before drawing too many conclusions
current guess is x2apic related
actually hang on i have a way to turn off x2apic
lets test that
nope that changed nothin
oh damn thats a funky race condition
anyway calling it a night now
so to be specific, it now works perfectly on TCG
but not on any other accel even if i turn off x2apic
the MSI just never gets delivered
i even have qemu trace logging to tell me that it sent the msi
it just never actually interrupts in a way i can detect
tomorrow im going to try stalling instead of blocking and see if its just getting masked out for some reason
(im suspecting a race condition with the context switch stuff, which i need to redo my wait function to handle better)
anyone got experience with msis on qemu have ideas why this would break?
ok it is a race condition
scheduler code is going to kill me
ok i think its fixed?
im scared that ill get hit with some stupid race condition later and im just getting lucky rn
i smuggled the mcs lock token through to the new thread on a yield-for-wait so the context causing a thread to wake cant fuck with the thread wait list until after the context switch
and then because thats now synchronized more correctly i can make the context switch not raise irql above .dispatch
which means the MSI isnt getting lost due to that lol
id have expected the apic to buffer that msi but ig it doesnt?
i dont fuckin know
continuing to pop off
Letsgo
idk why the capacity thing is set to 0 tbh
if i multiply the size in blocks by the lba size i get the exact same number as my build script passes to the disk image tool i use in the "disk size" argument btw
so im very happy there
Cool
hmm its returning "invalid queue size" as an error when i try to make the queues
oh oops
i forgot to initialize the entry size in the config register
alright that works now hell yeah
poppin off again
now lets see if it works on aarch64
having good abstractions: still goated
didnt have to change anything it just worked
anyway now itll be time to figure out how i want to structure the IRPs for block io
initial plan is to have fully separate operations for streaming and positional io
block devices only support positional, stuff like serial only supports streaming
yknow what fuck this i should probably be using a DPC to signal sync objects anyway
well i think i fixed the race condition
but in the process i discovered a really annoying edge case and now im mad
not actually an edge case tbh
more something i didnt know about the type system
tbh maybe just a weird edge case on calling conventions idk
i think its all workin now tho
callin it a night my brain is turning to mush again lmao
and as usual the literal first thing that happens after i shut down the dev machine is i realise something i did wrong lmao
a bug in waiting as it were, as circumstantially it cant break rn but is def wrong
Quick question. For my kernel would I be able to make it with C and Zig? And have some parts in both?
For my arch abstractions I wanted to use weak symbols but Iโm not sure if that would work with zig?
Eg I want to make low level hardware stuff in C and other things since I may port them from my old kernel
(Doing a rewrite)
My kernel is a pile of dookie for a lot of things
So I lowkey need to rewrite
Whatโs the starters guide packet for zig
weak symbols do work in zig but why would you want to do it that way when zig's lazy eval is so good for this exact case
So unimplemented functions for an arch can trigger a panic?
mixing c and zig does work actually quite well but again why would you want to do that
Yeah Iโm considering just maybe not doing that?
Idk
Iโve been cleaning my desk and computer to get stated with this 
using zig lazy eval will make it compile error if a function gets used that isn't implemented but it's like 3 lines to make a panic stub for it?
they should trigger a linker error not a panic
Mainly just scared of not being able to use zig proper
Couldnโt that get annoying for arch ports depending on how much i need to have?
I could just have a template of stubs tho :p
Whatโs that do?
well it would panic with something like "panic: not implemented"
Ahh
there isn't a todo() in zig but you can just as easily do @unimplemented() or @panic("todo")
or rather @unreachable()
@unimplemented() is not a thing lol, mb
unreachable is a keyword not a builtin
oh it is? yeah my memory is a bit spotty
unreachable is also UB
but tbh unreachable might be bad
@breakpoint() if anything
or whatever it's called
so when are you joining zag 
and I wouldn't use it for todo stubs
Okay so. To get started I should get it loading with Limine and flantern and overwriting print functions to work right?
the compiler can assume unreachable is unreachable so it's UB to actually reach it
in debug it'll panic but in other modes it probably will just generate code that's ub
yes
sorta - if youre in a mode with runtime safety unreachable is a panic
so don't rely on that obviously
Also I did hear backtraces in zig are much easier?
std.dwarf
yeah i got a fully working setup in mine that wraps std.dwarf to just load from the kernel file memory mapped blob that limine or whatever gives you
I'm not skill issued so my panic is empty 
even supports loading split debug info from a module but the zig build system cant do the splitting without system objcopy so i cant be assed to actually use that yet
like seriously, do just steal https://codeberg.org/Khitiara/imaginarium/src/branch/rewrite/src/krnl/debug this folder and the debug.zig in the level above
itll give you stack tracing
So I could make the debug info seperate?
That sounds useful and somthing Iโd want
its not that useful unless your kernel is huge and you want to strip it to save space
it's a thing that linux distros do nowadays, they ship separate -debug packages that contain just the elf files with debug info and no code
True but itโs cool
windows historically did that
well, it has no other way 
all you have to do to use my debug thing is set SelfInfo.kernel_bin to the kernel file from limine and if you split debug info set SelfInfo.debug_bin to that loaded as an optional module from limine
i don't think you can embed debug info inside the PE itself
yeah its always PDBs separate
before pdbs they had dbg
windows nt 3.1 is a .dbg file
that's not a pdb
they probably switched to pdb because it's fancier
yeah
either way its always been separate debug info for windows
i think nowadays they do something even fancier for kernel debugging but idk how that works
from my experience there's also just plain pdbs for kernel itself and the drivers
windbg downloads them from msft servers and loads them automatically
ah cool
wasnt sure if it was still a plain pdb file
knew windbg downloaded them for you tho
this is melting my brain a little 
looking at the build scrips and the eld stuff
100% a skill issue tho
whats melting about the build scripts?
i get the elf stuff i barely understand that and i wrote it lmao
i dont see where the limine things get put into the right sections (looking at the zag kernel for that)
well i dont use limine
so thats probably why
ahh
wait why does zag have stuff for SDL??
zag has a user-mode emulated version
ahhh
true sorry
no worries
i dont mind offtopic here, youre just more likely to get an answer over there
i do sadly think i wont be able to do zig
though the more i read it the more its making a little sesne
rip
maybe my next reweite 
lol
I dont have limine support
yet
there's a limine zig template tho
yup
PE has means to embed debugging information inside the executable. There's a dedicated .debug section for this and a special Debug Data Directory entry in the Optional Header pointing to that. In fact debug info can be placed in .rdata or anything else. pdbs are just most commonly used to strip the executable size. Then internal debug section references this external file.
TIL
also just realised i never made a post in #introduce-yourself here lmao
fixed that now 
I can't write there somehow. Even though I've never introduced myself too. Not like I'm going to though.
well someone gave you the introduced role at some point is why
#introduce-yourself message
you wrote in the introductions channel in a reply and someone probably looked at it and gave you the role without reading lmao
I forgot about it. Well, if i forgot that i set a password to one of my laptops fw, then. 
its automatic
well a bot automating it def wont read the message to realise its a reply not an introduction so my point still stands 
also yeah i thought it was manual
it used to be automatic but the bot feature got lost in some rewrite of the bot
saw something about that when looking through random channels earlier
oh thats a mood lmao
anyway i cleaned up how drivers access device properties so its harder to accidentally fuck it up like i did last night
and moved the namespace identify command to the namespace driver
ok how the fuck do i want to handle read/write IRPs
the big questions:
- do i want to support scatter/gather stuff or only contig buffers
- do i want the nvme driver to allocate dmamem for transfers or rely on the caller provided buffer
- do i pass offset by bytes or make a separate block read/write that uses lba offsets
anyone have thoughts on those questions?
i think SG-first design would be nice, especially since you can easily just pass an SG list of 1 buffer and achieve the same behavior
and i think that the driver should take byte offsets + sizes that are multiples of the block size and then you can translate that to LBA + block count yourself
and iโd say rely on passed in buffers for zero copy IO
only possible risk there is if the nvme controller doesnt support SGLs i need to do temp buffers depending on the setup
i do agree about going SG-first with the design tho
esp because zig's reader and writer types are built on it (well built on being able to transparently use preadv and pwritev on posix platforms)
but in case NVMe does not support SG you can have the IO subsystem not do proper SG
idk how to explain it better
yeah itll just have to allocate a dma buffer in that case, and i dont really expect the partmgr to be asking for SG io anyway tbh
i mean you can still do IO to contiguous physical memory without SGL
(fwiw windows sidesteps this by requiring SG io to have each buffer be a full page which is just kinda dumb and makes it useless so i dont want to do that)
with NVMe
yeah
ig ill make an error code if SGLs arent supported to tell the caller to do contig buffers
if the buffer passed isnt aligned right to just use anyway
alright i wrote a read dispatch for nvme namespaces (only PRPs for now because lazy), not tested yet though ill do that after dinner when i start writing the partition manager driver and try to read the mbr/gpt
one thing ill definitely be working on at some point (probs as part of the partition manager driver actually, might make it a filter driver tho but then ill need to rig up the io manager to also do filter driver attachment) is a cache system
basically just cache page-size groups of blocks in some form of basically non-intrusive radix tree
๐
biiiig
nothing im doing actually sets that guid but it is the same one the bootloader logs at least
๐ฅ
https://codeberg.org/Khitiara/imaginarium/src/branch/rewrite/src/krnl/dev Nvme.zig and the nvme subfolder comprise the nvme driver for this if youre curious to look at it
and im happy to explain things
nvme read is also the first true async-capable driver call ive implemented
well nvme commands in general ig but read is the only one so far that operates truly async instead of just using a dpc to release a manual reset event that the caller blocks on on completion
thanks
(also the other stuff in that dir is other drivers fwiw)
ok time to test all this on aarch64
should? be fine?
this stuff isnt touching arch things
but who knows
did qemu actually generate some sort of an aml device for nvme?
for every pci slot actually, but i skip nodes unless theres a HID/CID or existing device object to attach the node to based on ADR (which the pci driver dispatches to the acpi driver to request)
oh ok
if i dont theres however many empty devices with only an ADR under the pcie-pci bridge
i think its to support pci hotplug using acpi hotplug?
not sure
yeah
i wanted to make sure my pci bridge stuff is fully fledged so i attach a pcie root port to the root complex and then a pcie-pci bridge to that and the nvme controller under that bridge
it would be more realistic if it was
pcie -> pcie bridge -> pcie root port -> nvme dev
nvme is pretty much always behind a pcie root port
i had to implement a painful edge case in the MSI requester id to make this work lol
good to know
i might do that then idk
unfortunately "pcie-pcie-bridge" isnt a device type qemu knows about
pcie-root-port is though, im using that
lemme find what its called
๐
this doc is really good https://gitlab.com/qemu-project/qemu/-/blob/v9.0.1/docs/pcie.txt
QEMU main repository: Please see https://www.qemu.org/docs/master/devel/submitting-a-patch.html for how to submit changes to QEMU. Pull Requests are ignored. Please only use release tarballs from...
2.1.2 To expose a new PCI Express Root Bus use:
-device pxb-pcie,id=pcie.1,bus_nr=x[,numa_node=y][,addr=z]
pcie-pcie bus is this basically
it also requires aml to detect i think
the pxb device is a device that actually exists on bus 0
it is a bridge
pxb -> pci express bridge
and on real hw it's like that also usually
oh hm ok
basically the root complex aka bus 0 almost never has actual pcie devices attached to it
only legacy endpoints like chipset devices or other pci bridges or pci express bridges
mhm
im gonna go with the pcie switch thing instead for plugging the nvme into
i knew the nvme usually isnt direct on the pcie.0 bus just didnt know that it wouldnt normally be behind a pcie-pci bridge
anyway plugging it into a downstream port of a switch it all still works
on both platforms
yeah because its a pci express device so it cant be on a pci bridge
well it can but you lose the extended part of extended configuration space
which im not using yet so it didnt impact me yet
yeah
whats your current topology?
- pcie.0 root complex
- pcie-pci bridge (nothing behind atm but there for later)
- x3130-upstream
- xio3130-downstream
- nvme controller
+ whatever the qemu machine does by default
ah u did the switch thing
q35 puts the legacy stuff behind a pci-isa bridge and a few things direct on pcie0, virt on aarch64 puts the hardwired stuff direct under \_SB on acpi with no pci intermediaries
the sad part about these switches is u run out of bus numbers very quickly, since every downstream port takes 1 extra bus number since its actually a bridge also
its kind of dumb
yeah
what threw me off at start is a lot of what im pulling from is what windows will give me as a device node tree
but windows calls all of the kinds of pci(e)-bridge/port/whatever "PCI-PCI bridge" in its ui
lol
well apparently upstream and downstream are labeled correctly too
but the naming is based on the HID and CID
which are based on the pci class code
which is the same for pcie-pci bridge, pci-pci bridge, and pcie root port
so it uses the original name for it, which is PCI-to-PCI Bridge because history
in any case i got the partitions enumerated by my partmgr driver and nvme is reading blocks for me successfully so rn im happy enough lol
Cool
well i have a limited number of devices here so less worried about running out of bus numbers and i want a complex enough topology to actually test my pci code
Yeah
That's why they added these segment group numbers
Which is only used by cursed arm devices for some reason
im glad i did, i found out about this bullshit that i have to do now
(which i do)
What does this even do
so do you know what requester id is in pci?
Nope
ok
its a 16-bit mask made from the bus number, device number, and function number
taking up 8, 5, and 3 bits respectively
and its the id that goes over the pci bus to say where a message came from
some interrupt controllers use that id as part of the lookup from msi to actual vector to send
but
the bridges and root ports and the like change the id when they forward a message
so in my old setup with the pcie-pci bridge shit, the nvme controller had requester id 0x0208 (bus 2, device 1, function 0)
but the message the interrupt controller got had requester id 0x200
because the bridge nuked the device and function bits
Is this requester thing only relevant for the insanely complex arm irq controllers?
which then caused the interrupt controller to look up in the wrong table, error, and not forward the irq
uh it might be relevant for iommu stuff too?
Ah ok
i dont have iommu yet so im not sure
Probably
Is this generic PCI code?
drivers/pci/search.c so yeah, though its got a pluggable callback
Interesting

