#uACPI - a portable and easy-to-integrate ACPI implementation
1 messages · Page 49 of 1
I heard that thin lto is the recommended kind for incremental builds but it costs me like 700k points
who needs incremental builds?
also for getter with unknown size like arrays try to use one interface without allocations (for example use a index approach), one with alloc and one with allocator_api
as an example look at the trait AcpiTable
fn get_ptr_to_table_payload(&self) -> *mut u8
points to the first byte after the table header
i do
did you publish it to cargo or should i clone the repo?
thin lto just supposedly makes faster than the full and fat ones
but takes more space and the performance is worse
clone the repo
its not ready to be published to cargo
branch "dev-1.0" btw
also ignore the ton of errors
__popcountdi2 is a libgcc (and FWIW probably clang too) function that count how many bits are set in an integer
So if you don't compile that into your kernel you may need to define some of them yourself.
- how can i ignore that many errors
- what do i do about it not compiling because of said errors
i was on a wrong branch
you can mask most of them by removing the aml_interpreter feature and probably replace the two errors in tables.rs with todo
"full_acpi_hardware"
still ton of errors
mostly types
error: could not compile uacpi (lib) due to 102 previous errors; 7 warnings emitted
line 393 in types.rs is inactive since no default features (aml_interpreter)
why do i get 102 errors?
mostly u32s instead of i32s
these 2 in tables
yeah its those 2 in tables and rest are u32s instead of i32s errors
replace those with todo!() temporarily
what about these?
on what system do you compile it?
aparently on linux c_int is a u32 while on windows its a i32
bruuuh
That doesn't make any sense
Why is c_int ever a u32?
It should be signed, like it implies with its name.
looks like bindgen chooses a c_int for me
LLM answer: Enum sign is not specified so it can be either.
Maybe you can tell bindgen you want a specific sign for your enums?
thats what im trying to figure out rn
i use clang
found it
.default_enum_style
i think
hold on
.default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: false })
.rustified_enum_variant_type(Some(bindgen::EnumVariantCustomType::Int32))
gpt halucinating as always
et voila
i fixed it
very horribly
@rustic compass is this it?
intellisense doesnt show core functionality
okay i will wait for uacpi-rs to be finished and meanwhile i will use the bindgen output
aww hell naw
kernel api isnt implemented yet
how can i edit includepaths for the c files?
doesnt work clearly
going to the c files gives me errors like this
int is always signed
It's possible that git didn't clean up when setting the submodule to a newer commit
And I forgot to check that and update the build script
its still a problem that bindgen chooses c_int for windows and c_uint for linux
this must do for now
what compiler do you use?
based on github bindgen issues linux isnt spec compliant here, enum should always be i32 on x86_64
In https://github.com/nbigaouette/onnxruntime-rs I use bindgen in onnxruntime-sys/build.rs to generate bindings on different platforms (macos, linux, windows). The original C header file is https:/...
im pretty sure its rust-lld
oh i read as linker
latest nightly
i mean the c compiler that cc/bindgen use
K so, this might be unrelated to uACPI but is it normal that there's no 'FACS' table in memory
because i made a QEMU memory dump, read all the RSDP,R/XSDT, found all addresses by myself, and in the FADT i can see there's a value in the FIRMWARE_CTRL field
even tho it brings nowhere
}
when in doubt you can dump ACPI tables from the UEFI shell and inspect those
nope
and im pretty sure qemu does have FACS
it does
isnt there an X_FIRMWARE_CTRL and if thats present you need to use that instead? idk though
and i dont know if the FACS region has a header since its not really a table
i could be wrong though
I mean, I'm pretty much leaving everything to uACPI, which does check for x_firmware_ctrl
But the problem is that it finds an address where there's no FACS apparently
Even tho I might try NVME shill's hint and see where this goes
can u explain the problem in more detail
what are you trying to do, what happens
if uacpi gets a bogus address that probably means you dont handle misaligned mappings correctly
read the comment above uacpi_kernel_map
So, to start, i was trying to initialize uACPI in my kernel, almost all tables get initlialized but when checking for FACS signature it simply doesn't find a signature (and there isn't any since i checked the memory dump)
so the problem is not anything related to address aligment since all the other tables worked fine 🤷♂️
Are you using early table access?
one sec
Is this invalid table thing logged by you or uacpi?
I meant this value btw
uacpi
OK
So, in the initialize_tables function, it tries to check for the table signature right here
i don't really think so
Yeah looks like you do
mapping it incorrectly could be the problem
There's an extra bogus 16 on top of its address
what's in your kernel api map function?
If u look at uacpis output
oh wait
about that
it's because it's not actually written correctly in the log function
nope
there's a literal 0x016 in front
yeah
Can u run something like Linux and see in dmesg what address it reports for facs
ok
void *uacpi_kernel_map(uacpi_phys_addr addr, uacpi_size len) {
uint64_t aligned = ROUND_DOWN(addr, PFRAME_SIZE);
size_t offset = (size_t)(addr - aligned);
size_t actual_len = len + offset; // this is BYTES!!!
size_t pages = ROUND_UP(actual_len, PFRAME_SIZE) / PFRAME_SIZE;
void *virt = vma_alloc(get_current_ctx(), pages, (void *)aligned);
// re-align the pointer to original addr offset
virt += offset;
return virt;
}
did you omit the actual mapping part or
or if you want the full code
https://github.com/purpleK2/purpleK2/blob/dev-unstable/src/kernel/acpi/uacpi_wrappers.c
Lol
that's part of vma_alloc
you're not passing it the physical address though
how does it know what to map
oh wait you are
nevermind
yup
the void * cast tripped me up
understandable
Btw how does your round down behave if the address is already aligned
Can you show it as well
oke
#define ROUND_DOWN(n, a) ((n) & ~((a) - 1))
#define ROUND_UP(n, a) (((n) + (a) - 1) & ~((a) - 1))
they're just some macros
Would be easier if you had an hhdm we could quickly replace this logic with but oh well
But this is fine I think
is it possible you're corrupting the facs memory itself?
👍
}}}}}}}}}}
technically an api break so uacpi 3.0 time? 
(jk i wont do that)
also as per shinribo's request, clarified the table api usage a bit
nice
add a new define and mark the old one as deprecated
then when enough deprecated defines accumulate make 3.0 that removes them?
i think i can go out on a limb and say that no one used this define
but a proper way would be that yeah
https://github.com/search?q=uacpi_poisitive_decode&type=code several people actually
most of these are just copied code or autogenerated bindings
but threre are two uses i think still
damn
okay then
ill do what qookie said
also why is there jai bindings damn lmfao
is there any way to make macros deprecated with a warning
#ifdef X
#warning Y
#endif
i guess
me when -werror:
your fault for using -Werror
i mean uacpi is what defines the warning
or rather, the macro itself
you could have a global function/object marked as deprecated and somehow reference it in the macro definition
#define UACPI_POISITIVE_DECODE (deprecated, 0)
interesting
could i make it a static inline function instead?
__attribute__((deprecated)) static inline int uacpi_poisitive_decode_value() { return X; }
#define UACPI_POISITIVE_DECODE uacpi_poisitive_decode_value()
maybe like this
the issue is that this is not a constant expression?
I think C allows to place deprecated onto variables? https://en.cppreference.com/w/c/language/attributes/deprecated
you can't make a global static inline tho
so i'd have to add it as an extern
which is fine i guess idk
yeah
this is c23, but the gcc attribute is pretty old yeah
but at that point maybe the function would be the best option
it was probably added after the compilers
like since everyone was using it so it became a language feature?
i'll just do #if __has_attribute(deprecated) etc
otherwise u get no warning and its your fault
This looked like it was just for bindings?
I recommend what I did last time I had a similar problem: Keep the old one around but deprecate it. Since it's a macro you can't do much better than put a warning comment before it. Then next major version (3.x) you can remove the misspelled version.
yeah see the discussion above
Alright I think I'm back from my Minecraft/gtnh semi hiatus because I kind of hate how AE2 works, I might focus on my os ™️ project
based
i can't wait to see ultraos form
nahh its gonna be way worse than keyronex
but i wanted it to be good 
Good need not be perfect
fair
how could a bad OS even come from you?
watch me 
like after you writing the best ACPI library 
well thanks
👀
lol
i did a bit of ultra work, mostly remembering whatever i was doing there and changing some things around
but its a start ig
you just need to rename this thread 
nah ill make a new one when im confident im actually gonna work on it
and it will generate less traffic since its not actually a useful project to anyone
to instantly become an operating system with a lot of stars
Yeah but you've built up quite some fame, I'm sure people would be interested
speaking of that, ill need to integrate uacpi at least for early table access mode very soon which is nice
ill probably get rid of my printf and just steal uacpi's symbols
watch me lose all of it because of how bad the os is 

can I shill a microkernel design? /j
nah im not doing that lol
basically my idea is very simple and not innovative, i want to get it running very quickly so that i can focus on stuff that i enjoy, which is drivers
and i want to get a basic drm up and running
anyway, you had some good drivers
if i were to make a microkernel id have to focus on the details which im not interested in
im considering dropping aarch64 and i686 atm as well
whereas a monolithic design just allows we to focus on getting stuff done
yeah thats the only half decent thing about that old os lol
basically i want it to be old proxima like which was very simple and very fast
although i do want to support numa as well from the start
we'll see
that's still a good goal
my long term goal is to be able to run a simple opengl 3 or 4 game with native accel on my 2010 igpu
with that game being a binary built for linux userspace and just cp'ed to my image
i might just get some game from my steam library
maybe factorio, idk which opengl it uses
or balatro would be cool lol
but thats obviously a giant amount of work so yeah
hopefully i get there eventually and dont abandon it lol
Run Minecraft 
Then while you are playing it instead of working you could at least do it as a stress test for your os
Today I did some vulkan thing and I got reminded about your (I think it was you) igpu triangle hello world and how much smaller it was compared to vulkan 
Spent a few hours figuring out how linuxes log ring works, I think I have a pretty good idea of the algorithm
Its pretty neat
Since I'm doing Linux compat might as well have a syslog ring
are you going linux binary compat?
that's pretty sick
definitely takes away a lot of the porting groundwork, you can just throw an alpine rootfs at it and work on the kernel instead :^)
Yeah basically
Ill probably have some of my own syscalls that live at some offset, sort of like xnu does
But Linux syscalls and vfs layout will be the native way to use it still
That's basically why I'm doing that lol
Also this will allow me to really understand how Linux is built in depth
the in-kernel implementation or /dev/kmsg?
sorta, the implementation of that device is just based on the internal prink_ringbuffer struct and all the helpers around that
this stuff is also reused for printing out to consoles etc
basically the in-kernel, yes
just copy managarm
Jokes aside you probably don't need a fancy strategy until you're using NMIs
its nothing fancy tbf
they just have a ring of log entries basically that have a pointer to text and its length + log level/facility etc
each log entry has a 64 bit sequence number
that u can use to extract its position in the ring with a simple modulo etc
the text buffer is also just a fixed length dynamic array
Well, at least in case of linux (and managarm) it needs to be NMI safe though
it does have all sorts of complicated locking stuff so that u can use it in every possible context yeah
which means it must be able to cope with the log function getting entered in an nmi while the context that was interrupted by the nmi is currently writing to the ringbuffer
yeah
for that it has multiple states for the log entries
like available, commited etc
What are you even supposed to do when you get an NMI and you're a microkernel?
Like your console driver would probably be running in userspace
buffer the message and send it to userspace after the NMI is done
But aren't you supposed to crash?
no
not in general
NMI can be used to deliver critical failures but the more modern mechanism for that is MCE
NMI is also used for watchdogs / profiling / etc
and you don't want to crash in these situations
But even in case of a critical failure, you should still somehow be able to get to userspace to show an error message, and I don't get what's the best way to do that
if your kernel is fucked to an extent where it gets a watchdog NMI it's not really an option
aka you cant rely on userspace working at all in that case probably
I was thinking about going to userspace and crossing fingers for it to work enough to print an error message to the screen
Since now when the kernel crashes, it only prints error to the debugcon
So like if you're running it on physical hardware without COM port and the kernel dies, it just freezes and you have no way of knowing what happened
why couldn't you also print to the fb in the kernel in case of a panic?
drm
well, linux has drm_panic i guess
but idk how that works
but if your framebuffer has been taken over by a driver then i don't think you can simply just print to the framebuffer anymore
well yeah if you have a proper gpu driver then you'd need to do it with it
I mean I do have debug branch which does that, but it doesn't have to be framebuffer
idk how feasible that is if you're in the "we're fucked" state :^)
Like it could be some cursed serial otput or something, and I want userspace drivers
Maybe I want too much though
imo if you're a microkernel your kernel shouldn't crash no matter what
you could still enter a user thread context from an nmi handler context, it's just that your system might be in less than ideal state at that point so you should probably do as little as possible
it's not like the cpu is dysfunctional and switching to another thread will blow things up
if you receive an NMI it probably is
the cpu? why lol
i dont know why you would receive an NMI other than hardware failure
hardware does not necessarily mean cpu
something like ram would be more likely yeah
exactly, as i said - do as little as possible
hopefully it's not all gone to shit yet
maybe you can get your last words out before dying
meanwhile windows
(it used to just clear the screen blue and say NMI - Hardware failure. Contact your hardware vendor)
it usually doesn't, but what if a bit gets flipped by a cosmic ray
ECC is a thing
but some fuckery could still happen in hardware
profiling
if you have some kind of special hardware that issues NMIs then maybe
i think infy meant actual architectural profiling
NMI is not used for error reporting on modern hardware
NMI is used for watch dogs and profiling
And yes, that is architectural
If you're talking about MCE: if you encounter a synchronous memory failure in the kernel, you're probably fucked
Real, it keeps bsoding on Meteor lake Arc iGPU
if it's in userspace you can poison the page, maybe kill the process, continue
It might be a intel problem but I blame windows
linux supports migrating pages so it wouldnt even have to kill the process
only if its nonmovable memory
can't migrate the page if you can't read it anymore
hm tru
if it's a kernel page, the most reasonable thing you can do is probably log a kernel panic to whatever devices the kernel can talk to
realistically ppl will run memtest upon random crashes / detect faulting memory during POST so that should be good enough anyway
another option is to write error info to some nvram
or even normal RAM
and then doing a soft reboot
linux can also do that
there is also ramoops https://docs.kernel.org/admin-guide/ramoops.html
essentially this does a kexec on panic
has anyone else ran into this
if I build with release meson doesnt like it
nothing else seems to really break
bro the linux printk_ringbuffer design is genius
you can reserve entries completely lockless
and they made it completely NMI safe
A fully reentrant ringbuffer is a very good exercise in concurrency and reentrancy, as I believe @flat badge (sorry for ping) has pointed out.
the log entries have 4 states: reusable, reserved, commited and finalized
Reusable just means free, reserved means someone allocated an entry, but is not yet done writing to it, so readers will be aborted when trying to continue reading the ring at this point, but writers can continue concurrently allocating more entries. Commited means someone has commited the data, but may want to append more later, this is only used during early boot for multiline logs. Then finalized is a read-to-read entry.
and this ring is actually 3 separate rings: descriptors, info, and actual text prepended by descriptor id
yeah im definitely not gonna bother making it NMI safe as thats insane amount of work and I simply dont care about NMIs at this point
but it can still be concurrent with a few atomic ops
btw re commited: AFAIK those multiline messages you're refering to are printk(KERN_CONT) messages
yeah the KERN_CONT flag makes printk code not finalize the record
but if a new record after is reserved the previous one is instantly finalized if its commited
so it cant really be used after early boot
I'm pretty sure I've seen code like
int arr[10];
// ...
printk(KERN_INFO "Some things:");
for(int i = 0; i < 10; i++)
printk(KERN_CONT " %d", arr[i]);
printk(KERN_CONT "\n");
note the lack of \n
ah.
what's cool is readers won't see the commited message unless a new one is added after or the commited message is finalized
so its very well thought out
hmm do i wanna waste a week making this log ring completely lockless like linux, I think I understand how to make that work entirely...
bro said he wants to focus on writing drivers and yet he's making one of the most sophisticated logging systems any hobby kernel has seen :^)
lmfao thats kinda true, but i already dumped way too much time into studying how it handles every edge case 
lockless stuff is always fun as long as it works correctly
it can be extremely tricky to get right too
the amount of barrier pairs and comments there do make me a bit scared tho
that maybe i wont make it in a week
oh shit 💀
like that log ring is truly cool, u can have an infinite number of cpus concurrently writing to it and then readers reading at the same time, and all u need for a reader's iterator is one u64 seqnum
i would probably care less about it if it wasnt exposed to userspace as /dev/kmesg etc
or maybe i should just say fuck it an do spin_lock() 
tbf that is probably good enough for any hobby os
and it will let you work on other important stuff
most hobby osses probably dont even have a log ring, they just pipe the input string to sinks directly
but yeah idk, i kinda want it to be better than your avg os
doesn't mean you can't have your kernel log ring be simple :^)
this design is also nice because u can do the actual output work in a separate thread
tru but it's tempting lmfao
so relatable
to start the ultra kernel with a 3k loc log ring
that doesnt even have smp
or malloc
if only i had the motivation to read about stuff like that
well i just browsed the sources for like 10 hours total probably
until i got a literal headache

ulog library when
lol
i'd have to make tons of tests for it anyway
so maybe
if it's lockless that makes it nmi safe as well which is nice
tbh the linux implementation looks way make complicated than the Managarm one
how does linux achieve NMI safety?
everything is reentrant, there isnt a single critical section there
they have id consistency checks and are able to detect concurrent modifications of a descriptor
and how do they prevent the messages themselves from being overwritten?
writing descriptors in an NMI safe way is relatively easy
but writing the messages is harder
an array?
so "text data" is fixed size?
rings are all fixed size
text data is dynamic ofc
but it's reserved when allocating the descriptor
so they have a sophisticated allocation algorithm for that?
yeah
sophisticated = not just circular reuse
its very sophisticated
ah
but it jumps over stuff that is still in use by another context? or how does it work
i can explain
so a new record is allocated via prb_reserve(), which accepts the needed text length.
The first thing it does is allocate a new descriptor by changing its state to reserved and advancing the descriptor ring tail pointer. It then allocates the text for this descriptor by advancing the tail text ring pointer, and then walking over it for the length of the needed text data and invalidating any descriptors currently referencing this text data (since text data has an inline header with descriptor ID that references this text, and text data is always aligned at 8 bytes to fit it)
so the older descriptors referencing text data in the new span are invalidated into reusable state
what if we prb_reserve a lot without ever finalizing, such that we wrap around?
the reader does the following
desc = get_descriptor(seqnum)
memcpy(caller_buffer, desc->text, desc->size)
return desc_still_alive_and_has_the_same_id(desc);
so concurrent modifications of the descriptor are safe
yeah thats a good question
basically, it isnt allowed to recycle reserved descriptors
so if you reserve but never commit, the ring will stall
aka all new writers will be aborted
ah, ok.
if it wraps around
and that voids messages?
yup
but it's very hard to trigger, as you need a buggy writer or a writer that literally does a sleep between reserve and commit
and the default implementation of reserve() disables interrupts
so its expected that u do commit very quickly
so if I have one hundred thousand concurrent printk's some of them may be voided 
yeah
but the default implementation also scales the log ring according to max number of cpus
so you wont run into this
ingenious design
yeah
what happens on commit that makes the log descriptors not lost when it wraps around?
wdym? thats the idea of the ring, if it wraps around the old entries are lost
there's klogd in userspace which journals kmsg entries
so they're always available
ah that's the part i was missing
that's why dmesg goes back all the way to the first message
no
nah dmesg reads kmgs directly
oh?
how large are we talking?
so it fits everything just fine usually
if you can somehow perform lots of printk's (e.g. via a kernel module) and then do dmesg, you will see that it does not go back to the beginning
the default one is 1 << 17 i think
- max_number_of_cpus
a megabyte or something
well 1 << 17 * AVG_MSG_SIZE (32)
that would be 4M
it's not that bad
but yeah that makes sense
so it's just a set of really big ring buffers
yup
they are still pretty big though
:^)
i wouldn't have thought to "waste" a few megabytes of memory for logging
actually I lied, the default is 1 << 14
so it can hold 16K "average sized messages"
whenever my monitor blanks (e.g. from inactivity) and then turns on again, if HDR is used the kernel will print a bunch of stuff about HDR. and if this happens enough, I actually see in dmesg that the first few messages are lost
/*
* Define the average message size. This only affects the number of
* descriptors that will be available. Underestimating is better than
* overestimating (too many available descriptors is better than not enough).
*/
#define PRB_AVGBITS 5 /* 32 character average length */
#if CONFIG_LOG_BUF_SHIFT <= PRB_AVGBITS
#error CONFIG_LOG_BUF_SHIFT value too small.
#endif
_DEFINE_PRINTKRB(printk_rb_static, CONFIG_LOG_BUF_SHIFT - PRB_AVGBITS,
PRB_AVGBITS, &__log_buf[0]);
basically this is the default
yeah or if u sleep and resume many times
yeah
interesting
config LOG_BUF_SHIFT
int "Kernel log buffer size (16 => 64KB, 17 => 128KB)"
range 12 25
default 17
depends on PRINTK
help
Select the minimal kernel log buffer size as a power of 2.
The final size is affected by LOG_CPU_MAX_BUF_SHIFT config
parameter, see below. Any higher size also might be forced
by "log_buf_len" boot parameter.
Examples:
17 => 128 KB
16 => 64 KB
15 => 32 KB
14 => 16 KB
13 => 8 KB
12 => 4 KB
so 128KB, but - 5 bits because of text
so its actually under a meg
or even under 512KiB for all
and then it's 12 bits per cpu
damn that's some crazy stuff lol
or well, extra 4K of log ring per cpu
the kernel has a static char printk_ringbuffer[128 kilobytes]; or something, and then it reallocates using vmalloc at a later stage I think
it reallocates using memblock
so the very early memory map allocator
before smp startup
initmem, which was an initmeme in my old kernel
i think x86 calls it here
however I did not have any printk ringbuffer
it's definitely a huge overkill for my kernel lmfao
but i might give it a shot
i'll need all of the atomic helpers and barriers anyway
yeah like whoever designed this algo is probably a literal genius
it evolved with time. Linux did not always have a printk ringbuffer, and it did not always have this printk ringbuffer.
it was actually added only in 2020
its very new
and this commit description is already outdated
evolution
Managarm achives nmi safety by cancelling in progress memcpys to the log ring when an nmi happens
in this case this isnt needed since a descriptor is either reserved or it isnt
and its guaranteed to be safe to memcpy if prb_reserve succeeded
Yeah but the cost of that is that you need to handle the case that the ring wraps around while the nmi happens
yeah they have two separate ID systems because of this
the seqnum, and the internal descriptor id which is limited to unsigned long bits - state bits
a lot of workarounds for 32 bit unsigned long
Managarm's ring can't ever stall
since stuff is committed atomically with no pre-reservation
well this one kinda cant as well unless every single CPU gets an NMI at the same time while its writing to the ring
and then writes in the NMI handler as well
then some entries might get dropped
since it scales for the maximum number of cpus at boot time
each cpu gets a 4K wiggle room by default
Managarm's ring as a single ring buffer and it works like this on write:
- it invalidates existing entries that'd overlap the new one
- it copies the new entry to the ring
- if the previous operation succeeded without being interrupted by reentrancy, it advances the head ptr
so it's not lockless right
It is
oh also managarms ring doesnt support many readers as far as i can see
since dequeue is global
Managarm is stateless at the reader side
linuxes ring has no such thing as dequeue
So it supports any number of readers
to me this seems like it is lockless but you are "cheating" wrt reentrancy
wdym
any new reader will only read the new contents
that the previous reader hasnt seen
A reader simply knows its position in the ring, checks if the entry at that position is still valid, copies out of the ring and checks that no invalidation happened in the meantime
No, readers are independent
oh ok
yeah i guess its lockless but not concurrent
many writers will just keep spinning in retries
but its definitely a simpler design
yes
i guess this is only bad because it it's hard to keep timestamp ordering
cpus push themselves?
the strategy is to defer the work of pushing to the centralized sink/logbuffer/whatever to a context that can e.g. block on mutexes?
The full story is this: logs go to per CPU ring buffer -> a per-CPU soft irq dumps them to a global sink -> a kernel thread serves the userspace visible /dev/kmsg
Yes
how does this ensure timestamp ordering?
Essentially this separates the reentrancy from concurrency
well because u want earlier logs to appear before later logs?
wdym
rdtsc?
it does
u have no guarantees that a cpu pushes its earlier logs before some other cpus later logs
basically
Linux messages are ordered by seqnum not by timestamp, aren't they?
both
alright.
thread level does sched_clock()
NMI
NMI handler does sched_clock(), prb_reserve() ..., returns
thread does prb_reserve
-> not ordered
I don't think that's true, the /dev/kmsg docs specifically state that there is no guaranteed ordering
dev kmsg docs generally kinda suck
lemme check the code if this is possible
Ah actually no, they state that it's ordered by seqnum
ok so prink grabs the timestamp on entry
so i guess its possible
but it would be an exceptional situation
more or less
Well in general you can't guarantee both seqnum and timestamp order, right?
yeah it is very unlikely to happen
Unless you do heavy synchronization around getting the timestamp
you can if you lie about the timestamps
but overall its nice if they come in order unless u get exceptional situation like NMIs
like managarm would just push in whichever order right
here its at least mostly in order i think
do u see any unordered ones in your /dev/kmsg?
I'm on my phone right now 
technically if two cpus log at the same time, but the other does it slightly later and then the first one gets preempted before prb_reserve this might happen
but strangely i dont see anything like that
but I've never seen unordered messages in dmesg. although dmesg might be doing some sorting of its own.
maybe
actually i think the x86 kernel is usually non preemptible
so thats why
oh, really?
yeah it can be overriden in the command line
but also it says CONFIG_PREEMPT=y, lemme check idk how these two cooperate
In Managarm it is almost always ordered except for NMI contexts or other situations where it can't push to the central sink
basically yeah, in dynamic mode preemption is off by default, unless the command line has preempt=none/voluntary/full/etc
so check you /proc/cmdline
or actually im wrong https://elixir.bootlin.com/linux/v6.14.4/source/kernel/sched/core.c#L7617
if i understand correctly with your config it will enter the last else
your /sys/kernel/debug/sched might contain the mode if u have that built
ah yeah
or just dmesg shows it
this is x86 right?
[iretq@raptor linux-mlibc-jinx]$ sudo cat /sys/kernel/debug/sched/preempt
none voluntary (full) lazy```
arch linux does preempt=full apparently?
btw are you planning to open source your kernel from the start or will your work on it privately first?
i am kinda curious to see how it develops 
I mean its already open source, i'm just not upstreaming stuff yet because there isnt much to upstream
last commit 2 years ago 
yeah i will be upstreaming that stuff ofc
i need to decide if im dropping 32 bit support
i probably should because im never getting to driver dev then
so why do you need this
what purpose does it provide
very robust logging
Lockless NMI safe logging
That scales to any number of readers and writers
Also works under panic or any other condition
just spinlock and if an nmi happens during logging too bad
True
dang now you're making me tempted to do the same
would take more stack space in my actual logging handler since I'll need to print out to a buffer though
rn my log handler just writes directly to the qemu debug port and I've been wanting to add other sink options for a hot minute now
nah
it takes 0 stack space
since you print out to a pre-reserved buffer
the buffer comes directly from the ring
id need to use a counting null-output writer and do the format in two passes then
they went to a lot of effort around kprintf in linux
zig logging handler happens before the formatting so you can do a formatting write directly to output to save on format buffer if possible
it was for decades the blocker to RTLinux™️ integration
yeah they also did non-blocking consoles specifically for rt
a lot of work has been put into this
also im def not doing a full kprintf type thing that soon, i may consider myself ok at lockless using interlockedxxx but zig atomics take an ordering like cpp ones and i dont understand those at all on a good day let alone while having been sick for a week lmao
also i dont need realtime anything if thats why its so thoroughly nonblocking i just want nmi correctness in theory
do the operation macros themselves take them? i think they are having separate barrier macros
nah macros dont, they abstract it away
and yeah they use barriers
they have e.g. atomic_read_acquire
which is an acquire barrier
the only atomics ive felt like i understood what i was doing using are the InterlockedXXX functions that microsoft has which iirc dont
microsoft's ones have suffixes i think
well ive never used a suffixed version lol
on x86 its not really that needed, only to protect from compile reorderings
because cpu stuff is mostly already serialized
yeah the docs for InterlockedCompareExchange say to use the aqcuire and release versions for itanium performance critical things
but thats it
interlocked is x86 semantics :^)
C# to this day doesn't have atomicswith proper memory ordering
yeah and thats how i learned atomics and lockless
so i never learned proper ordering and now ive gotten too dumb to understand it lmao
ill give that a read
do note, linux has a different memory model to the c memory model
because linux defined a memory model for themselves before the standard defined one
idk im using zig so i really dont know what would actually apply
its huge but its worth it
well clearly it works under gcc and clang lol
people have sent me memory order stuff before but its always gone over my head lol
yeah iirc the kernel memory model is more relaxed than the c memory model, but I don't remember the exact details
it was one of my three things that i cant wrap my head around no matter how much i try but maybe this changes that (the other two things are the rust borrow checker and being able to read lisps)
not like old gcc ports even listen to the standard memory model
yeah my struggle isnt so much in understanding the basically-legalese spec terms its in translating that to practice
its like ok i can understand this combination of specification words but i have no clue what to plug in when actually writing code
its probably the hardest part of concurrent programming
I have a friend who did his thesis on memory orderings, and from him the only way to ensure that what you do is safe and to ensure all the barriers actually interact correctly with each other is to describe the memory in relations like happens-before
like, in practice things just "happen to work" because of how cpus handle things in the real world, but it quickly breaks down under the correct conditions of the cpu
the part that sucks for me rn is i know and get x86 semantics and am targeting x86 only but because of compiler reordering i have to learn the rest of this anyway
The key idea is you make barrier pairs, so e.g. a release is paired with acquire somewhere
If you have multiple atomic accesses you don't want reordered you use a sequentially consistent ordering
the pairs ensure the relationships between the memory accesses
ok cool
the main goal is to not think about atomics on a timeline, but as a graph of accesses
oh its fuckin DAG ordering
there is no timeline when dealing with memory, and the only way to ensure a happens-before is with the atomic barriers
ok that makes way more sense suddenly just saying it that way
@fiery turtle do you already have a framework 13 dump or do you want it
i kinda wanna see what all the blobs are for
i dont think i have seen any framework dumps
Linux memory barriers and atomics have very stupid implied ordering and semantics, QEMUs seem a lot more sane so I'm stealing the idea
Official QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are ignored. Please only use release tarballs from the QEMU website. - qemu/qemu
Why don't you just use the c11 memory model?
yes
qemu uses the c model, just with their own wrappers using gcc's builtins
You can also just use stdatomic.h without weird wrappers
i hate it because it requires _Atomic prefix which is a special type which doesnt work with anything and changes the default behavior for operators
is stdatomic even a freestanding header?
yes
i think so
Just use the aliases like atomic_uint32_t etc
the __builtin_atomic intrinsics are basically once removed from it
ah
I'd use stdatomic.h just because it's standard and well established
Writing correct concurrent code is hard enough
So if you want to talk to others about it / get code reviews etc it's just an extra burden if you're using your own barriers
the main niggle people are having with the c11 atomics is that they want memory ordering parameters to the operations while most people think rather in terms of explicit barriers
is smp_Xmb() harder to read than atomic_thread_fence(...)?
use c23 
how does it help?
i note it doesn't work properly in objective-c which is part of what led me to go with gcc builtins instead
and nice as the type safety may be i found some subtle differences between clang and gcc's take on them and that put me off that stuff altogether
^
Why?
What exactly are the guarantees of smp_mb()?
No offense, but if you think that this question is easy to answer, you probably haven't done enough research yet
same as atomic_thread_fence(ACQ_REL)?
That fact alone is not obvious at all
why is it not obvious?
you know there's a read-modify barrier, you know there's a write-modify barrier
For example, why is it not a full memory barrier?
because linuxes model was invented before this stuff was added into the standard
Yeah but how many ppl know the intricacies of the linux model? Probably 20?
And how many ppl know the c11 model? Probably many thousands
Yeah but my point is that for a greenfield project you shouldn't go with something like the linux model at all
you're probably gonna hate qemu tho 
#define smp_mb() ({ barrier(); __atomic_thread_fence(__ATOMIC_SEQ_CST); })
#define smp_mb_release() ({ barrier(); __atomic_thread_fence(__ATOMIC_RELEASE); })
#define smp_mb_acquire() ({ barrier(); __atomic_thread_fence(__ATOMIC_ACQUIRE); })
remember to use the __atomic_* builtins instead of the __sync_* ones
why the barrier()?
the __sync primitives are old and outdated
one for compiler reorders one for cpu reorders
all the cool kids use __atomics
but __atomic_thread_fence() is a compiler intrinsic.
/* Manual memory barriers
*
*__atomic_thread_fence does not include a compiler barrier; instead,
* the barrier is part of __atomic_load/__atomic_store's "volatile-like"
* semantics. If smp_wmb() is a no-op, absence of the barrier means that
* the compiler is free to reorder stores on each side of the barrier.
* Add one here, and similarly in smp_rmb() and smp_read_barrier_depends().
*/
no compiler barrier there
-_-
which is kind of crazy
bloody hell
because it's easy to use correctly
as opposed to dealing with the intricacies of acquire/release
not to speak of consume
consume is only relevant for dead arches i think
apparently the lack of a total load/store order can be problematic for some lockless algorithms unless they are done using seq_cst
alpha
yeah i still dont understand entirely when u need seq_cst
yeah
when you need guarantees about the order in which two atomic vars are modified
For example this: #programming message
This one makes sense
Yeah the Linux macros are horrible, no one who does concurrency uses the Linux terms (which don't always align perfectly with what people usually use), but instead uses the C/C++ memory model terms
My friend made an example of a bakery lock that requires a seq_cst barrier on x86 
Consume is not only relevant for alpha btw
Alpha is the only arch where consume needs an explicit fence
But you can exploit that dependencies imply ordering on other archs as well
It's generally not worth it over acq/rel though except in niche cases
What's even the point when you can use __atomic_load/store stuff?
For example you do memcpy to a buffer and you don't want it to be reordered with some other store

Or you do multiple relaxed atomic stores followed by a stricter barrier
you can just make the last store release?
That works too
As far as I understand barriers and normal ordering flags for other operations are identical
what situation do you use this in?
maybe only copies from userspace
(at which point you don't need hardware barriers?)
memcpy(data)
atomic_fence(release)
data_is_set.store(true, relaxed)
this can also be replaced by release I think? 

Hm?
x.store(y, release) is the same as x.store(y, relaxed) fence(release)
A release barrier is not the same as a store-release
well the cppref says it is
No that's wrong
how?
The latter is stronger
unless i misremember, the abstract specification of the c11 atomics says something to the effect that only explicit barriers are specified to order anything other than explicit atomic ops
The barrier orders all proceeding stores vs all following stores
While the store release orders all proceeding stores vs. exactly the store done by the store-release
how can the barrier order stores that come after the barrier
unless im misunderstanding your point
That's the point of release semantics
Btw this is also wrong, the barrier needs to be before the store
release semantics means all stores before the barrier are not reordered with the following stores
im not sure what u mean
With the barrier after the store, it does nothing
true
No that's not correct
Acquire/release semantics mean that if an acquire operation reads the value written by a store-release, then all future reads will read newer (or just as new) values than the value that was read by the read acquire
yeah but still what exactly is the difference between a release barrier and just a store release
If i do
a.store_relaxed(1)
b.store_release(2)
c.store_relaxed(3)
And another thread read acquires c = 3 and then reads a, there is no guarantee that it'll read a=1
I think this has some answers
If you replace the store release by a barrier + relaxed store, this guarantee holds true
So you're saying relaxed stores can still be reordered with respect to each other
No I'm saying a release barrier acts on all following stores while a store release acts on one store
A release barrier is strictly stronger than a store release
I don't understand this part
How can a happen after b
Compare the snippet that i posted above to:
a.store_relaxed(1)
release_barrier()
b.store_relaxed(2)
c.store_relaxed(3)
Then the release barrier affects c as well
While the store release only ever affects b
If a read acquire now reads c=3, a following read is guaranteed to read a=1
Which is not true for the store-release
So tldr barrier affects all atomic stores and a store ordering only affects this store
yes
How is this even possible at CPU level
But if it acquires b is a guaranteed to be 1?
Yes
I have no idea how it works at the hw level but at least aarch64 and rv have explicit acq/rel load/store instructions, so it can apparently be optimized more aggressively in hw than barriers
interesting
the acq rel stuff on x86 is just a no-op other than the compiler barrier i think
a seq cst barrier might emit mfence
realising i dont think zig even has an equivalent to barrier()
Yeah i wouldn't be surprised
these are just sequentialyl consistent
Yeah qemu seems to not trust its developers
seems not worth using a wrapper
'wait for all stores to finish' vs 'wait for this one store to finish'
Well independent stores before release are sequenced before the atomic store, that's kind of the point, so you cant just wait for this specific store I think
hm true
is the question 'what's the difference between release store and release barrier in hw'?
Basically
<Insert banned word used by Marvin>
I don't think there's a difference on modern hw because stores pretty much have to retire in-order anyway: reordering stores A and B is risky because there may be a branch mispredict or exception that occurs between A and B, so this is only possible when you know that both instructions are non-speculative
(what I'm saying is that formally there is a difference in the ISA between store-release and fence-release, so you still need different instructions for those, but I don't think that a fence release would realistically be worse because store-store reordering is too difficult anyway)
nice, another couple hours of time wasted thinking about memory orderings
that's assuming that the architecture has release fences and release stores though
for example on risc-v you get acq/rel with no extra instructions (since it's a bit in the load-reserve/store-conditional isns)
but a fence needs an explicit instruction
and a fence may also degrade to a stronger fence than necessary
I was completely wrong as per usual
Why?
I just misremembered how store buffering works entirely
once a store retires, it's known to be non-speculative. at that point it gets entered into the store buffer
so entries in the store buffer can drain in whatever order the CPU likes. on x86, due to TSO, this must (at least appear to) be FIFO
but on aarch64 it's pretty plausible that a store could jump ahead of a prior entry in the store buffer if there's a cache miss or something
a store release effectively says 'this store cannot be moved ahead of an earlier store'
a fence-release says 'any later stores cannot be moved ahead of an earlier store'
so a cpu could in theory see the following sequence:
store 1
store-release
store 2
and reorder this to:
store 2
store 1
store-release
whereas a fence-release prohibits this
I do suspect this is a possible reordering that aarch64 stlr permits in practice but I'd need to cook up a litmus test to demonstrate that
interesting
I could be completely wrong again but who knows
the address is bogus as well
no. the address is the copy in the multiboot data
are you giving it the v2 version of the tag?
otherwise it cuts off the extended part
and uacpi gets garbage
managarm ran into this as well
this only happens with BIOS, not UEFI
do u handle misaligned mappings correctly
yeah, I'm very sure I do
yeah idk but im pretty sure this has something to do with multiboot crap
also here's the code that "finds" the RSDP:
multiboot_tag *rsdp1 = nullptr, *rsdp2 = nullptr;
for (multiboot_tag *tag : mb2_tags) {
switch (tag->type) {
case MB2_TAG_RSDPv1:
rsdp1 = tag;
break;
case MB2_TAG_RSDPv2:
rsdp2 = tag;
break;
default:
break;
}
}
if (rsdp2)
rsdp1 = rsdp2;
if (!rsdp1)
panic ("No ACPI RSD PTR was provided by the bootloader.");
acpi_set_rsdp (virt_to_phys ((uintptr_t) rsdp1) + 8);
it also says v00, which is "interesting"
00 is what qemu sets it to
can u just print out the rsdp fields yourself right here?
see if they make sense
also how does your virt_to_phys work
static inline uintptr_t
virt_to_phys (uintptr_t x)
{
return x - HHDM_OFFSET;
}
kernel direct map
are you sure you construct it correctly?
100%
anyway try this
i'm fairly sure this is a uacpi bug, it reads the length field from the rsdp unconditionally but that field is only present in revision >= 2 https://github.com/uACPI/uACPI/blob/22895b49e372303e1fb8338cead78534ed44b966/source/tables.c#L118
ah. that's it.
because everything seems to work, it loads all the other tables correctly etc.
thats not rsdp tho
it is ah the linked code
oh sorry yeah that's rsxdt
https://github.com/uACPI/uACPI/blob/22895b49e372303e1fb8338cead78534ed44b966/source/tables.c#L84 what i said's still correct, just linked the wrong line
but the RSDP handling in dump_table_header is incorrect
if (!uacpi_memcmp(hdr, ACPI_RSDP_SIGNATURE, sizeof(ACPI_RSDP_SIGNATURE) - 1)) {
struct acpi_rsdp *rsdp = hdr;
uacpi_info(
"RSDP 0x%016"UACPI_PRIX64" %08X v%02X (%6.6s)\n",
UACPI_FMT64(phys_addr), rsdp->length, rsdp->revision,
rsdp->oemid
);
return;
}
seems to be a purely cosmetic issue though
just out of curiosity, apart from faking a RSDP yourself, is it possible to initialize uACPI from a RSDT or XSDT?
afaict no
nope
then relaxed should be fine no?
yup, it is fine.
you probably don't even need an atomic there except for the initial load of the head, if there's no console_unregister the next field of a console in the list will never be written to again
unless you insert at tail instead of at head
it is fine for two reasons:
- seq_cst load in printk_emit synchronizes with seq_cst store in console_register, which happens after past seq_cst stores. thus the seq_cst load happens after all seq_cst stores it can see, and the relaxed load(s) cannot be moved before.
- I wasn't planning on having more than one console for the time being

good point, will remove
why not just acquire here and release in register instead?
you have one writer and one reader

idk, because I felt like it
anyway I will replace this with a 3000 LoC message ringbuffer sooner or later anyway 
or well, if you have multiple registers, you probably want an ACQ_REL cmpxchg loop in register
and just acquire here
L
(printk does not take console_lock. printk takes another lock instead.)
good catch btw, ill mention u in the release logs again
should i get rid of 32-bit support in the ultra kernel 
