#Nyaux
1 messages · Page 57 of 1
it could be related to some kind of race condition
this seems to be a one in a million issue
making it even harder to trigger
fuck
i cannot get it to trigger
guess we will have to live with this bug
pretty much undebuggable
without a way to actually trigger it
(at least for my case)
i love heisenbugs
copypaste from me 
no
is nyaux now have games ???
yea
waiting for nyaux youtube showcase day 1
send my ur youtube
ill send in dms
Does that still not exist damn
yep
I tried doing that yesterday but I ran into some fucked up mlibc build failure and gave up
I can try again today after I do eberything I need to do
i can help with that
whats up
with the build?
fcntl.cpp doesnt build cuz its complaining about a type mismatch with struct file_handle
Or something like that
ill look into it
just after i implement this syscall for doom
so it can actually get the fucking time
for CLOCK_REALTIME with clock_gettime do i HAVE to use the cmos or can i use something more accurate
like what would be BEST to use
the best (imo) would be maintain a "real time when boot time was 0" value
which would be initialized from cmos (or limine's boot time request)
that's how i do it
may i see it?
apparently i misremembered: my implementation uses a 128-bit timestamp instead of a 64-bit one and so i had to use a seqlock
https://github.com/proxima-os/hydrogen/blob/a5704b19bf2507ac70acdfc6e5b252444a25f040/kernel/util/time.c#L16 this is where it's initialized
i dont think ive asked you but what is seqlock exactly? i know youve told me that atomics basically force the cpu to load things/store things a certain order which makes sense
but what does seqlock exactly MEAN
seqlock is a way to have low-read-overhead shared variables larger than what the isa can handle atomically
a seqlock uses a sequence number to check if data was modified while you read it
essentially on read you read a version number (which also encodes whether an update is currently in progress) before and after reading the data
if it was odd (update in progress) or not equal (updated in the middle of the read) you retry the read
on write you first store version+1 (to indicate the update being in progress), then do the writes, then store version+2 (to indicate the update is done)
note that you can't have concurrent writers with seqlocks, you need a separate lock for that
ah okay so bascially ur waiting til whatever code is done with it so u can modify it?
(you can reuse the update-in-progress bit as a 1-bit spinlock)
yeah
they cant tho? cause they have to wait til the update is done before they can modify it right
that's one way to have a separate lock for it
if the variable is rarely written this results in very low read-side overhead
and very high read-side scalability
huh?
by waiting until the update is done (=version is even), you've essentially turned the lowest bit of the version number into a spinlock
but dont u already do that for a seqlock????
on the read side, yes, but that's different since there you don't set the bit
on the write side you don't necessarily check if an update was in progress
for readers the LSB is a indication that what they are reading is the up-to-date stuff
for writers it could be used as a spinlock
basically if bit 0 is set then you just re-read until it's not set
so anyone that wants to write to a seqlock can just do it unwillingly?
but anyone that wants to READ it
read side: ```
do {
do {
version = atomically get version;
} while (version is odd);
do read;
memory fence to prevent reads from being reordered past the loop condition;
} while (version matches (atomically get version));
write side: ```
do {
version = atomically get version;
} while (version is odd or cmpxchg (version + 1) failed);
do write;
atomically set version to version + 2;
note that due to the cmpxchg on the write side, only one writer can be active at a time: the loop is essentially a spinlock acquire
don't you have to set bit 0 to 1 first?
whereas on the read side any number of readers can be active at a time as long as no writers are
see or cmpxchg (version + 1) failed
now when you know only one writer is going to be active at a time regardless, you don't need the cmpxchg and can instead use a store, which can be faster in some cases
and how would a write that uses a seperate lock look like
just so i know i get it
lock(&separate_lock);
atomic_add(&version, 1);
// do the update
atomic_add(&version, 1);
unlock(&separate_lock);
acquire lock;
version = atomically get version;
atomically set version to version + 1;
memory fence to prevent writes from being reordered before the atomic set;
do write;
atomically set version to version + 2;
release lock;
lol we both missed the release 
note in my pseudocode atomic ops implicitly use acquire/release semantics
the memory fence is important on the read side because release loads don't exist and acquire loads don't prevent prior accesses from being reordered past them
the memory fence is important on the write side because acquire stores don't exist and release stores don't prevent future accesses from being reordered before them
so what a memory fenece essentially does is force all prior accesses to be ordered??
if im reading this right?
with explicit orderings: ```c
static size_t seq_version;
void seq_read_example(void) {
for (;;) {
size_t version = __atomic_load_n(&seq_version, __ATOMIC_ACQUIRE); // acquire prevents do_read() from being reordered before it
if (version & 1) continue;
do_read();
__atomic_thread_fence(__ATOMIC_ACQUIRE); // this turns do_read() into one giant acquire op, preventing the version load from being reordered before it
if (__atomic_load_n(&seq_version, __ATOMIC_RELAXED) == version) break; // relaxed is fine here, we don't care about stuff after this being reordered before it
}
}
void seq_write_example(void) {
size_t version = __atomic_load_n(&seq_version, __ATOMIC_RELAXED); // relaxed is fine here, we don't care about stuff before this being reordered after it
__atomic_store_n(&seq_version, version + 1, __ATOMIC_RELAXED); // ditto
__atomic_thread_fence(__ATOMIC_RELEASE); // this turns do_write() into one giant release op, preventing the version store from being reordered after
do_write();
__atomic_store_n(&seq_version, version + 2, __ATOMIC_RELEASE); // release prevents do_write() from being reordered after it
}
holy hell
think of atomics like a dependency tree
- an release store depends on all (both atomic and non-atomic) accesses in the thread that happened before it
- an acquire load depends on some prior release store, and transitively all (atomic and non-atomic) accesses in the releasing thread that happened before the release
- an acquire fence turns all prior loads into one giant acquire load
- a release fence turns all future stores into one giant release store
this implies:
- no access before a release store can be reordered after it
- no access after an acquire load can be reordered before it
- no access after an acquire fence can be reordered before it
- no access before a release fence can be reordered after it
note that if you want to get language lawyery fences technically only operate on atomic ops, i.e. they can lift relaxed into acquire/release but not regular accesses, but on any reasonable implementation (including gcc and clang) they operate on regular accesses as well
also monkuous congrats on mod!!!!
if you want a full, language-lawyer-correct, seq lock implementation, do_read and do_write need to use relaxed atomic ops
thanks
🥳
what is language lawyer correct huh?
language-lawyer-correct means 100% correct according to the C Standard
but that often imposes really inconvenient restrictions, this for example
idrc
so most people only care about "this will be correct on any reasonable implementation"
oh theres a problem with the atomic relaxed line because ur not checking it with the version variable we defined?
oh oops
lol
fixed now
what atomic operations (RELEASE, ACQUIRE, RELAX etc) would each be? for this version of this write
size_t version = __atomic_load_n(&seq_version, __ATOMIC_RELAXED); // relaxed is fine here, we don't care about stuff before this being reordered after it
for (;;) {
if (version & 1) {
spin_loop_hint();
continue;
}
if (__atomic_compare_exchange_n(&seq_version, &version, version + 1, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) break; // cmpxchg is (partially) a load, so it can be acquire - which means we do not need any fences to prevent do_write() from being reordered before it
}
do_write();
__atomic_store_n(&seq_version, version + 2, __ATOMIC_RELEASE); // release prevents do_write() from being reordered after it
the pause instruction on x86
it's not necessary but it helps with hyper-threading performance, hypervisor performance, and (supposedly) power usage
i guess it would be applicable there yeah
epic
@brisk zenith this looks good?
i copied your comments too
so i would understand it reading it later in my code
lgtm
epic
@brisk zenith question about your code for hydrogen what is boot_timestamp_seq and why is there uint64_t boot_timestamp_low;
uint64_t boot_timestamp_high;
and what unit is it in, ms? ns? fentoseconds?
boot_timestamp_seq is the seqlock version field
boot_timestamp_low/boot_timestamp_high are together a signed 128-bit integer that is the number of nanoseconds since 1970-01-01T00:00Z for ns_since_boot=0
aka the current posix time is (boot_timestamp_low:boot_timestamp_high + ns_since_boot())/NS_PER_SEC
and boot_timestamp?
i don't think i have just boot_timestamp anywhere
i guess i accidentally committed that somehow
good choice
i'm currently dealing with trying to make an architecture-generic system that can handle both PAE and non-PAE 32-bit paging in one binary and wow this is incredibly annoying
especially without hhdm
worst part (imo) is that cr3 still only accepts 32 bit addresses even with PAE so the assumption "pmap->root is the top-level page table" is no longer valid
(always allocating the top level page table in <4G memory is a Bad Idea imo, so instead i have one top-level page table for every cpu and copy the new top-level page table to it whenever i switch page tables)
(this isn't as bad as it sounds because with pae the top level page table is only 32 bytes, but it's really annoying to work into the arch generic stuff)
that is so genuinely annoying
also monkuous maybe ur other systems faulting is related to that framebuffer issue nyaux still had this one their systems@flat nymph
?
the top level "table" must be like 64-byte aligned right
yeah
and the values are cached right after its loaded
32-byte aligned yeah
so it can be reused
not on AMD!
I think I also had issues with framebuffer
that is most likely that then
On pmOS and someone's PC
ah right
My kernel supports it
this is the pae switch function i use
yeah u have to have a per cpu "magazine" to reload the cr3 every time lmao
But I have arch-specific paging functions
wtf were they smoking when designing 32-bit pae is beyond me
why the hell is cr3 still 32 bits also
Not specifications of page table geometries per arch
why the hell are all bits reserved as well
How would you load a 64 bit one?
exactly, this is the sole cause for the most invasive changes to my pmap code
You would need a separate register
similar to how you load msrs/xcrs
^

just make cr3 use two 32-bit regs
And they had those in Pentium
in both modes
xcr is newer but msr is since pentium yeah
cr3h 
IA32_PDPT
I mean RISC-V is also broken
The common denominator is flanterm
wait this is the nyaux thread
yes lol
lounge 3
real
loungeos
!!!
uLounge
Though uACPI thread is still winning by the message count 
quickly spam messages here
lol
how many messages were related to owc?
me when ironclad crashes
I need to learn Ada
just to verify this is correct on the kernel side right?
wait HUH????
hold on need to regen and rebuild mlibc
is this?
whta ze fuck
wait
nyaux you really should stop stack tracing userland
yea that fixed it
yea ill fix that
just catch pagefaults 
well my stack trace checks if the return address is mapped
yea i have no idea because the mlibc typedefs for time_t are confusing me
vscode says its long but
i dont trust it
All of my memory reads from user just let it pagefault and catch those
also the fact that its int128_t is how i store it in my kernel and nanoseconds is a long???? how is that gonna work?? @brisk zenith
cause it would just get cutoff
right
mini-lspci is written in a way that allows adding different backends easily
It's u64 seconds and u64 nanoseconds
Probably
nanoseconds is the number of nanoseconds to add to time (which is in seconds)
so you return *time = timestamp / NS_PER_SEC; *nanoseconds = timestamp % NS_PER_SEC; (plus all the stuff to handle negative timestamps correctly)
number of nanoseconds to add to time??
converting nanoseconds to timespec is this
so like nanoseconds contains the second half of the timespec??
yeah
makes sense
and how would i handle negative timestamps?
sorry im really bad at math
Signed seconds probably
also should i have a kernel thread consistently update timestamp?
// C compilers are allowed to (and do) use truncating division, where i.e. (-15 / 10, -15 % 10) = (-1, -5) instead of (-2, 5)
// This ensures that no matter whether the compiler uses truncating division the result is the latter.
__int128_t seconds = (timestamp - (NS_PER_SEC - 1)) / NS_PER_SEC;
*time = seconds;
*nanoseconds = timestamp - (seconds * NS_PER_SEC);
now i do not know how this math works but who cares !!!!
the timestamp you store under the seqlock is the time at boot, you only have to update it if the time warps (i.e. NTP or similar)
you get the real timestamp using stored_timestamp + ns_since_boot()
right that makes sense
it's similar to how when you want to a rounding-up integer division you do (x + (y - 1)) / y
oh i dont even know how THAT works, thats how bad i am at math
trust me
i dont even know how to add fractions
😭
i don't know how to do that without a calculator either except in the most simple cases
well i know the procedure but not how to execute it
get the denominators equal and add the numerators
but i do not know the algorithm for getting the denominators equal
i dont know either
static void read_shit(void *data, void *variable) {
*(__int128_t*)variable = info.timestamp;
}
struct __syscall_ret syscall_clockget(int clock, long *time, long *nanosecs) {
switch (clock) {
case CLOCK_REALTIME:
__int128_t timestamp = 0;
seq_read(&info.lock, read_shit, NULL, ×tamp);
timestamp = timestamp + GenericTimerGetns();
__int128_t seconds = (timestamp - (1000000000 - 1)) / 1000000000;
*time = seconds;
*nanosecs = timestamp - (seconds * 1000000000);
return (struct __syscall_ret){.ret = 0, .errno = 0};
break;
}
return (struct __syscall_ret){.ret = 0, .errno = ENOSYS};
}
should i add the timestamp with get_ns_since_boot?
yeah
alright
uhh
dunno if thats right
bru
ill regen + rebuild mlibc and see if that fixes it?
still doesnt work

umm

that is all my code for that
int sys_clock_get(int clock, time_t *secs, long *nanos) {
__syscall_ret ret = __syscall2(SYSCALL_CLOCKGET, (uint64_t)secs, (uint64_t)nanos);
if (ret.err != 0)
return ret.err;
return 0;
}
mlibc side
is the math im doing wrong at all?
you're not passing clock
bru
💀 💀 💀
yea good catch
looks normal i think
still wrong
forgot to rebuild mlibc
💀
bro stop time travelling
its fucking animated
tho that doesnt look right
now it stopped being animated?
timer problems

:cccc
ok so ill explain whats happening
time is being inconsisent
sometimes it gives reasonably values
sometimes no
I hate when I accidentally time travel
ok its not deadlocking
with the seqlock
ill try printing timestamp
this looks write yea?
wtf???
yea the math is wrong somehow
no i didnt add limine time
lets see now
its doing the same thing when seconds get to 5?
i store nanoseconds with a size_t
perhaps thats bad?
no still storing it with u128
still the same issue
@brisk zenith i believe the math u sent me is wrong (sorry for the ton of pings 😭 )
i might've not been clear on this, the fancy math (as opposed to / and %) is only for when timestamp is negative
its store as u64
c++lover grammar 
ok bro
okay so i need to check when timestamp is negative somehow??
bro
wasup
it's just timestamp < 0
wdym somehow

yea im idiot
MY BAD
HOLY SHIT
my brain is fried today
sorry i didnt read the question properly
i think my brain is dying
moment of truth
moment of lies
real
yea it didnt solve shit
fuck
am i supposed to set timestamp ?
its not doing the other code path
you're like an astronaut who depressurised the airlock and then started to put on the suit
real
__int128_t timestamp = 0;
seq_read(&info.lock, read_shit, NULL, ×tamp);
kprintf("TIMESTAMP: from boot: %lld\r\n", (long long)timestamp);
timestamp = timestamp + GenericTimerGetns();
kprintf("TIMESTAMP: added with getns: %lld\r\n", (long long)timestamp);
if (timestamp < 0) {
__int128_t seconds = (timestamp - (1000000000 - 1)) / 1000000000;
*time = seconds;
*nanosecs = timestamp - (seconds * 1000000000);
kprintf("TIMESTAMP: time is %ld, nanosecs is: %ld\r\n", *time, *nanosecs);
return (struct __syscall_ret){.ret = 0, .errno = 0};
} else {
*time = timestamp / 1000000000;
*nanosecs = timestamp % 1000000000;
kprintf("TIMESTAMP: different time is %ld, nanosecs is: %ld\r\n", *time, *nanosecs);
return (struct __syscall_ret){.ret = 0, .errno = 0};
}
what i do now
its always different time so timestamp never gets negative
^
yea i have no fucking clue
now u see the thing here is that timestamp never goes negative
meaning that fancy math never gets executed
yea that sounds about right
you mean the code or the behavior i outlined
both
what does the full function look like? the weird time output might be because it's not intending to use CLOCK_REALTIME
alright hold on
static void read_shit(void *data, void *variable) {
*(__int128_t*)variable = info.timestamp;
}
struct __syscall_ret syscall_clockget(int clock, long *time, long *nanosecs) {
switch (clock) {
case CLOCK_REALTIME:
__int128_t timestamp = 0;
seq_read(&info.lock, read_shit, NULL, ×tamp);
kprintf("TIMESTAMP: from boot: %lld\r\n", (long long)timestamp);
timestamp = timestamp + GenericTimerGetns();
kprintf("TIMESTAMP: added with getns: %lld\r\n", (long long)timestamp);
if (timestamp < 0) {
__int128_t seconds = (timestamp - (1000000000 - 1)) / 1000000000;
*time = seconds;
*nanosecs = timestamp - (seconds * 1000000000);
kprintf("TIMESTAMP: time is %ld, nanosecs is: %ld\r\n", *time, *nanosecs);
return (struct __syscall_ret){.ret = 0, .errno = 0};
} else {
*time = timestamp / 1000000000;
*nanosecs = timestamp % 1000000000;
kprintf("TIMESTAMP: different time is %ld, nanosecs is: %ld\r\n", *time, *nanosecs);
return (struct __syscall_ret){.ret = 0, .errno = 0};
}
break;
default:
break;
}
return (struct __syscall_ret){.ret = 0, .errno = ENOSYS};
}
full function
also how i set it at boot
void GenericTimerInit() {
if ((!arch_check_kvm_clock()) && !arch_check_can_pvclock()) {
Timer = static_cast<void *>(new HPET);
} else {
Timer = static_cast<void *>(new pvclock);
}
info.timestamp = limine_boot_time.response->boot_time;
}
as scheduling isnt active when timer is inited its fine to just
yeah idk that all looks right
do this without going through the lock
i pushed the code, maybe take a look if you'd like to
(sorry for the late response) you don't seem to be using 128-bit arithmetic for kvmclock intermediates, this means it'll wrap every few seconds, perhaps that's what's causing it?
wdym?
one of the intermediate values for kvmclock to nanosecond conversion increases extremely quickly
rght
if you use 64 bit arithmetic for the calculation it'll overflow within seconds
i don't have it cloned locally and i'm about to go to sleep, but if you want to try it yourself it's this calculation https://github.com/proxima-os/hydrogen/blob/8491581f0f4e4b9ece7f620b6674e43565289cc5/arch/x86_64/private/kernel/x86_64/kvmclock.h#L46
that's cool
!!!
nice!
Love the managarm nerding in the background
im noticing a difference between pvclock and hpet, where the intial boot animation for doom doesnt play on pvclock and it just looks more choppy on pvclock in general weirdly, i know it def has something to do with the timer but yea
🔥🔥🔥🔥🎉
Yaaaaaay! 🎉
we got guis before disk drivers

epic ik
what are you want to develop now
now why is pvclock being weird with doom i have ABSOLUTELY no idea
input
ive printed the values and they look very similar to the HPET so
no idea
could be the seqlock implementation?
no
seqlock is used regardless of timer type
there's always the same answer to every problem imaginable
which is
skill issue
crazy
I know
now to be fair the pvclock implementation is very similar to monkuous's for getting the nanoseconds so idk exactly
broken cpu
lmao
ai cpu that fixes bugs at runtime when
real
maybe its because wsl is using hyperv clocksource
and that is probs using something else
plus its a nested vm
so idk
i would try this on native linux to verify this theory but
vms in wsl dont have a stable tsc iirc
then thats probs it
i'll try if you send an iso
ok hold on
iso is gonna still be big cause no initramfs compression
what i did for clock gettime is just time_at_boot + rdtsc() / freq essentially
speaking of which i should add that to nyaux
just compress the iso
kk
limine can do it for you
wait really?
yeah
whats the option
idk youd have to ask mint
mint when people ping instead of reading docs
real
but I remember I had a conversation with mint and pitust about tinf and stuff (which is used by limine to uncompress)
in the changelog it says this for limine 8.0.0 - Removed support for GZ-compressed files (and internal Limine protocol modules).
ah
fuck
:c
maybe i should port tinf to nyaux then
did u compress it
yea
how is it so big
compress it again
wtf did u put ther elmao
its just the iso
i mean on the iso
how did u make it bigger than ubuntu if u only support doom
how is it so big? proxima's initramfs with bash+coreutils+gcc-libs is only 84.3 MiB uncompressed
yea no idea
and that's with debug info for everything
i compile everything with debug info
idk could be my recipes
anyways
should be uploaded soon
btw i recommend speedcrunch for this sort of stuff
u just type number / gibi and it shows u
never hard of that
works with hex etc
heard*
very nice for converting shit like that
sounds cool
i just type whatever/1024/1024 in my app launcher and it shows the number of mebibytes
alright epic
no variant of tar has any special compression handling
i thought tar.gz might be its own format
tar.gz is just uncompressed tar piped through gz
tar.xz is just uncompressed tar piped through xz
etc
yeah that works, the cool thing about speedcrunch is it keeps the history and u can use ans to access the most recent one etc
well it didnt suck like it worked
every self respecting osdever made at least two filesystems:
- a custom shitty ramfs
- a custom shitty fat clone
i have made neither
L
guess i should get on that
didnt make fat clone or a shitty ramfs either tbf
but atp the decoding wasnt worth it when you could just lz4 the archive and get similar speeds and better compression
do it 
mine was essentially TAR with a weird custom compression algorithm
because I was trying to solve the problem of slow boot times
RLE 
slow boot times is due to the fuckin pmm in nyaux
huh?
it inserts a pmm node structure into every page via the hhdm
best compression algo
i really should write a better pmm
yes
yea it was basically that
maybe get a few pages to throw the pmm structures in which would point to the pages and not be in the pages themselves
each block of unique data had its own block
which could be referenced by the files
this is probs how the impl for most freelist allocators work
i mean deflate is like this as well its just two separate compression algos combined
so that avoids data deduplication
do a buddy
https://files.catbox.moe/q60gie.gz @brisk zenith
I would just do it on a per-region basis
?
buddy sounds painful
nah its not painful
buddy isn't as hard as it sounds, but imo there are very few reasons to prefer it over just a regular old free list
if you only have one page per free list entry sure but it's trivial to add a counter
on init, for every memory map entry one free list entry
the fact that NT pretty much still uses a free list to this day tells me it's good enough
on alloc, remove one from the entry
on free, add an entry with 1 page
yea that's what I said
also im gonna assume ill need continuous phys pages for shit like DMA soon
u mean like have contiguous freelist entries?
sounds like it would fragment instantly
avoiding fragmentation is not an objective
no because good devices dont need it
?
ig
worst case, everything degrades to page-size at runtime which is equivalent to initializing all the pages
which doesnt do anything
if u attach to __alloc_pages with bpftrace u will see quite a lot of allocations bigger than order 0
since it's still O(1)
usually in the network stack at least
they do use kvmalloc which can fallback to vmalloc usually
but still
basically this ```c
page_t *allocate(void) {
page_t *page = free_pages;
size_t idx = --page->free.count;
if (idx == 0) free_pages = page->free.next;
return page + idx;
}
void free(page_t *page) {
page->free.count = 1;
page->free.next = free_pages;
free_pages = page;
}
void add_region(page_t *page, size_t count) {
page->free.count = count;
page->free.next = free_pages;
free_pages = page;
}
yeah
im still confused what u mean by this lol
good devices support scatter-gather
modern/good devices use scatter gather lists
scatter gather?
old devices u can still DMA using IOMMU
interesting
a list of fragmented physical pages to use as one DMA region
or even sub pages
so it allows e.g. read(2) to read with one request even though the physical memory for page cache is fragmented
to this day i still dont know how an iommu works lol
thats cool
it just makes physical address u give to devices be virtual
holy thats cool
that use a special translation table only usable from the device
why is it supposidly tricky tho
alright what am i testing again
because there are IOMMU groups
doomgeneric using pvclock
see if its stable on native linux
so there are multiple devices using one iommu usually etc
right makes sense
and u have to carefully program all memory that might be accessible by the device
so its not that trivial
easy to foget something
for example scratch memory u give to xhci
etc
which sucks
what's the iwad path
/root/doom.wad
i should implement more userspace fs shit ngl, things like opendir or chdir
but ideally your dma allocator api allows to transparently map via iommu
i forget what its called on linux
how do i force it to use hpet? i don't have a point of reference
heres nvme scatter gather list building code for example
ill have to compile a different iso for u
hold on
really should make nyaux kernel options an actual thing
yeah on proxima it'd just be cmdline: x86.notsc x86.nokvmclock
really unweidly to hard code shit like the init program
yea makes sense
uploading
@brisk zenith https://files.catbox.moe/6pcqog.gz non pvclock iso
i'll have to compare it with hpet but this is what kvmclock looks like
seems fine to me
looks like a wsl issue
no ps2 driver still damn
also damn 5 servers 😭
thats next up
i want to make an input abstraction interface first
then get ps2
yeah hpet looks exactly the same
yep wsl issue then
ofc u would say that
xhci when
Im trying to revive but the haters are sabotaging me
huh that was easier than i thought
what are u trying to do?
also why is doom on the top left for u?
why is it in the middle for me?
real
I was just curious how hard it is to port doom
turned out I already had basically everything necessary and only had to add a way for userspace to know where the framebuffer is
it's in the top left because the positioning is handled by DG_DrawFrame and my code is different from yours
hold on lemme check my dg draw frame
i stole it from astral
it seems like positioning is handled ??
so why is it in the middle then
hmmm
moffset
oh right
for reference this is what mine looks like
as you can see it just starts from the top left, no offset applied
right makes sense
and i see u directly right to it because u have the mmap file shit implemented
huh? why does mmap have to go through /dev/mem???
I should probably have a separate fb device, rn I just use /dev/mem and have the kernel create text files in /dev/fb/<n> that store the fb details
right
it doesn't, I just don't have any other devices implemented that map physical memory directly into userspace
makes sense
it'd be trivial to add another but I didn't feel like it
the way I do it is I have it as a vfs op and the vfs op is a thin wrapper around vmm_map
whats the shit u need to do in order to get that working
depends on the type of file you're mmapping
for stuff like /dev/mem and /dev/fb which map physical memory directly into userspace all you need is vaddr allocation and paging
for mmapping regular files you need a page cache (and copy-on-write if you want to support private mappings)
yeah a page cache is pretty much the only way to have correct semantics for mmapping regular files
:c
for map_private you could get away with reading from the file into a new anonymous page in the page fault handler, but for map_shared you need to be able to map the authoritative contents of a file directly into userspace, aka you need a page cache
:cc
it's not as hard as it sounds, especially if you only have tmpfs
the hard parts of a page cache are mostly related to page eviction, which on a tmpfs will never happen outside of truncate()
and how exactly does mmaping a device working
like u just give userspace the virt addr to the phys address of the device?? or smt like that. sorry if im not understanding i dont rlly know how it works
also i have no idea how page caches work tbf, which is even more pain
page eviction?
wdym truncate
truncate() is the vfs op for setting the size of a file
right
page eviction is when the system is low on memory and needs to kick a page out of the page cache to use as some other memory
i see
for devices mmap is pretty much entirely device dependent
imagine u had some memory that is the fb, how would mmaping that work exactly. is there a general concept around mmaping files in general?
the way I do it: /dev/mem's mmap function passes a memory object to vmm_map whose post_map callback (which is called as soon as the region is allocated and mapped but before the vmm is unlocked) uses my paging code to map the allocated area to physical memory
oh and my memory objects have two callbacks: get_page (used by file objects, called by the page fault handler and returns a page_t *) and post_map (called whenever the object is mapped somewhere)
the /dev/mem object doesn't have get_page, so if a page fault is taken in a /dev/mem region a SIGBUS is sent
post_map takes care of ensuring that there will never be a page fault taken in a /dev/mem region (well, actually, it uses this mechanism to deny userspace access to memory that's owned by the kernel, but conceptually speaking it immediately maps the region to physical memory, preventing page faults)
memory object??? why callback??? what is SIGBUS??? holy shit im so stupid
i lit cannot understand anything
a memory object is, conceptually, just some object that can be mapped to memory
a callback is used because it must be done after the region is allocated but before the vmm is unlocked, otherwise a different thread might erroneously get an unhandled page fault
what must be done?? the mapping??
okay i guess that makes sense
im assuming in this case the file is what gets mapped
im probs misunderstanding tbf
SIGBUS is just a signal that says "hey you tried to access memory that's backed by an object, but we encountered an error while accessing said object"
right
it's also what's sent if e.g. an I/O error is encountered while accessing an mmapped file
mapping the physical memory (since this is /dev/mem)
so dev mem is like
memory objects for regular files don't need (or have) this callback because they can handle page faults gracefully
a file that reparents physical memory
a device file yeah
yea okay, so bascially on mmap, all of physical memory gets mapped to some addr or ?
well user space gives a hint of where it wants it
right
and mmap returns the actual mapped address
right
not all of physical memory, just [offset,offset+size) (offset and size are parameters to the mmap call)
whatever you want them to be
userspace can give a hint to the kernel for where it wants the mapping to be placed in virtual memory but unless MAP_FIXED is specified the kernel can choose a different one
if MAP_FIXED is specified it must place the mapping at the specified address, if that's not possible it must error and if there's already a mapping there said mapping must be removed
right
that's how I do it yes
have you reached osdev final boss
what exactly do they store
page cache is osdev final boss real
gpu accelarated doom when
u have ur priorities straight tho fr
depends on the memory object, the object for /dev/mem doesn't store anything (it doesn't need state) but anonymous memory objects (which are the backing memory for tmpfs regular files) store a radix tree of pages
radix tree 
page tables are radix trees if that helps conceptualize it
ig
again is there a reason for memory objects in the first place exactly
why is it needed for mmaping files
they're just a convenient primitive imo
you can implement mmap in other ways as well
i mean, what im trying to ask is basically what is the technical reason that memory objects would be used for. other then storing the pages that some file mmap'd or smt (not talking abt /dev/mem)
these questions are rlly stupid sorry ik, im js trying to wrap my head around this 
I just like the separation of concerns, if you're going full POSIX-like there's no reason not to put these callbacks in the file description or the vnode or something like that
that's what linux does I think
so theres no reason conceptually that memory objects is rlly needed for mmaping files in general
if im understanding this
have a post_map-like thing in the file description and a radix tree stored in the vnode
nope it's just my preferred implementation
right, i dont think ill implement memory objects
i mean this is my first implementation for mmaping files at all, i never got this far in kernel dev so i wanna make this shrimple ig
what ill do for /dev/fb<num> for mmaping it is quite simple, unless MAP_FIXED is said ill just pick some random ah vaddr. make the fbs phys addr map to said vaddr and return vaddr to said mmap vfs op
i think that is reasonable
as for mmaping normal files
that will probs be later on the timeline ™️ as i need page cache
im assuming this sounds fine and reasonable?
@brisk zenith ?
as long as "random ah vaddr" isn't literally an RNG it should be fine yeah
rng real
alright epic
thanks for explaining this stuff, you really have the patience of a saint
you should probs add a way to make that restricted tho
it already is restricted
/dev/mem does not allow you to access ram owned by the kernel
only firmware ram and mmio
ah yeah that makes sense
well it does have an ioctl for allocating physical memory which gives you an fd through which you can access kernel owned ram, but that doesn't count
what i had in mind for something similar but not really is I give a capability over the whole available physical memory range to init and it carves it up and gives it to the proper servers
so the only "unsafe" process would be init but even then it's just firmware and mmio
that's what I'd do for a "true" microkernel
hydrogen isn't really a microkernel because other than device drivers everything that a monolithic kernel would have in kernel space is in kernel space
ah I see
I'm not really sure how to do that dynamically tho yet
oh well
I guess the driver could request a physical memory range to init
when registering
also there's no real way around at least one unsafe process like this because acpi requires arbitrary physical memory and pio access
yeah I know
I'm not sure how to handle that yet
ACPI will just be a special privileged process I guess
(no i will not put acpi in the kernel!)
I'd just make it so any given handle can only access a single contiguous range of memory, and if a device has multiple ranges, the driver gets multiple handles
it doesn't really need special privileges? just make init give it the full unrestricted handle
what I had in mind involved only one driver having access to a certain memory range
mmm that doesn't really work because AML sometimes requests hardware memory that might already be claimed by other drivers
for example I believe qemu q35's AML accesses the hpet?
one of the 0xfeX00000 things anyway
I'd just give the acpi/pci process the unrestricted handle (that's necessary for aml anyway) and make it responsible for carving out restricted handles to give to drivers
oh yeah I guess acpi/pci can do bus enumeration and driver loading
when nyaux buddy allocator
freelists are (relatively) simple but also not super efficient
O(n) worst time
I was just wondering since I'm trying to do a freelist lol (and it kind of sucks)
Buddy allocators are cool
Think of it as some extra alignment requirements (each block aligned to its size) and then instead of a list of free pages, it's more like multiple lists.
Was gonna do a buddy, but I think a freelist is slightly faster? (I dunno I could be wrong)
no? they're O(1) in all but the rarest cases
the vast vast vast majority of allocations are 1 page
and for the vast vast vast majority of allocations freelist is the fastest possible allocator (disregarding allocator-independent optimizations like magazines)
ye I thought so
I think he meant O(n) if you need to allocate contiguous pages
exactly
but thats not something you do with a freelist anyway
those are the said rarest cases
freelist is pretty much always the fastest
No but you should be using buddy for physical memory because it makes doing stuff like superpages easier.
eh
tf is a super page
super pages (also known as huge pages) are when you use direct mappings in the upper level of the page tables, i.e. 2M or 1G pages on x86
Ohh, okay that makes sense
super -> greater than
RISC-V has mega, giga, peta, etc. pages.
XD
actually it has kibi(1024)pages, and no kilo(1000)/mega(1000000)/mebi(1048576)/giga(1000000000)/gibi(1073741824)/etc pages
what is tf
Tempted to call them überpages just to fuck with anyone reading my srcs
You'd think so but no, I just gave the official names
It also doesn't have kilopages, it starts at mega
yes
made logging better
text
?
isn't it?
i mean yea
text
but i wanted to make logging look nicer
You mean the [time][level] message thing?
yes
very good
not all kprintf instances are converted to the new function
try to add DEBUG and INFO
there is
make all messages to your log
i will
k
What do you plan to put in your userspace?
How are you gonna do drm? That seems like quite the undertaking with anything but downright ancient cards
udrm 
If BadgerOS ever gets to DRM, remind me to make it modular enough so that it can be integrated into other peoples' kernels
Because DRM seems to be the big unsolved problem in hobby OSDev land and if I'm going to at least try, you guys might as well be able to use it too right?
wait what
How did I not know this
Oh it actually has a multitude of gfx drivers too
it is
but idc
why are ur functions so yappy
me personally
i only print real bad things to stdout
i am working on a logging interface now that i have a vfs working and ext2/fat/iso9660(RO)
If your os doesnt yap is it really an os
real
add gooning
add stability
when will we get an os called goonix
it'll be like nyarch but all custom and not linux
i have loglevel
crazy
little tired for the past few days
tmrw we get to work tho
It’s not trust
If you don't mind me asking, how long did it take for the PMM to init before? (used Nyaux as a ref for reworking my PMM, but I feel like I've done something terribly wrong cuz it takes 16 seconds to init, without kvm. 4-ish with kvm)
Some people say nyaux pmm is still initializing to this day
dw in the next commit u can reference it
pmm boots instantly basically
my pmm takes infinity to initialise (I am booting up on a turing machine
@brisk zenith i have a question, okay so i have some page structure that points to a page and a next variable
so my question is, how exactly do i make this fast because freeing would be extremely slow like if i added a bool to pnode that says its allocated and then took its phys, freeing would be hellla HELLLA slow, is there a better way to do this without having the pnode structure in the page itself. i know menix has something called a PAGE_DB? im confused as to what it is tbf
a few seconds is an insanely long time
like unironically
freeing is just putting it on the list
allocating is popping from the list
the page db is likely what is typically called a PFN db
i lose the ptr and the structure
its for vmm stuff
wdym
you store the next pointer in the free page
like if i just pop off the structure where does that structure go
void *pmm_alloc() {
if (head.next == NULL) {
panic("no memory");
return NULL;
}
panic("no");
pnode *him = (pnode *)head.next;
if ((uint64_t)him->ptr < hhdm_request.response->offset) {
sprintf("pmm(): addr is %p\r\n", him);
}
assert((uint64_t)him->ptr >= hhdm_request.response->offset);
head.next = (struct pnode *)him->next;
allocated.next =
memset(him->ptr + hhdm_request.response->offset, 0, 4096); // just in case :)
return (void *)him->ptr + hhdm_request.response->offset;
}
where does him go
that is what i mean
then you have some pfn db?
wtf is a pfn db
why else am i rewritting the pmm
because its slow as shit on init
so im rewritting it to NOT be
it should be per region
just a collection of struct page basically
i dont do that
i dont do that srry
how would u access it exactly
old = region->ptr; region->ptr+whaever.. return old;
I know 😭
some do a virtually continuous mapping where you access it like pfndb_base[phys addr >> page shift], but that requires you to map stuff early
I just do it per region, at the start of every region I reserve some amount of space for every page in the region, then the page meta is stored there
wha
virtually contiguous is kinda tricky a bit to setup too, but it's worth it
per region is a bit slower (you dont get constant access time) but it's much easier
I was too lazy
how does phys addr >> page shift get u the right shit wtf
what does pfndb store
wdym by region in this context?
region=memory region/physically contiguous area of usable RAM
mhm
okay so wdym by per region, just for context i want my freelist allocator not to care about mutiple pages in one struct
each struct points to the page
why not
and points to the next struct
the phys_addr>>page_shift, so for example for 0x1000 you shift right by 12, then you get 1 as pfn. That would then access pfndb_base[1], pfndb_base could be some struct page*
I'll making my PMM use a per-region basis, it does seem much quicker than having 1 entry per page
For init anyhow
uh huh
how do u calculate the page shift
and
wdym by struct page*, what is struct page storing here
page shift is just 12
the page shift is just the 000 part of the architecture page size
yes
makes sense
but keeping it generic I define it per arch
struct page is storing whatever page metadata you need. I store my freelist pointers in them, along some other stuff
would it be okay if i just stored a ptr to the actual pnode struct thing
struct pnode {
void *ptr;
struct pnode *next;
}
this for context
what is ptr? or what is pnode
also cant i just reverse some pages from the memmap to store said pfndb
ptr points to the physical page
next is the next in the freelist
it is the actual freelist structure itself
thats basically the struct page, i just call it that cause I followed linux name or something
right
how do you mean?
that is how im doing that for the freelist itself, i find a memmap usable region that can store the freelist, store it in there and then increase its base and decrease its length by the amount i have "allocated" for the freelist
well its just a list then, but of free pages, so a freelist
its basically a pfndb already
^
well you could loop through all of them
its a physically continuous pfndb xd
if you never have to store additional stuff (why would you need to do that if that pnode is all you have) it shouldn’t be slow
like just put on the list, pop off the list
how so
^
where does him go
yea then u need a pfndb
if you want to go with the region approach:
make some region struct storing base/length of region
reserve size of region + size of pnode*total pages in region
put all pnodes after the reserved pages onto the freelist
I‘d store the pfn in the pnode, not a hhdm pointer)
then you‘ll want to have some boot allocator that chips memory of the memmap or bump allocates or whatever
you‘ll also want to have a way to map memory before your pmm is init‘d with this early allocator
ill just do the same i did for the freelist
what why
i have the hhdm
if you want an actual pfndb
define actual here