#OBOS (not vibecoded)
1 messages ยท Page 15 of 1
the refcnt for the vnode includes dirents
so the page cache wouldn't be freed even if there aren't opened files
I'm going to add a mutex to the page cache entry
for locking IO on it
actually
I'll have two mutexes, one for modifying the structure (e.g., expanding the page cache size)
and another for the dirty region list
which I'll actually make an rb tree
maybe not
this is all just me thinking
not final
maybe not an rb tree
but regardless of the type of the structure
on read, the dirty list will be queried to see if there is a dirty entry
if so, it's locked
same thing on write
this way, reads on non-dirty regions can be done without any locking
and writes will ensure synchronization
(I finally spelled 'synchronization' right first try wow)
I'm also thinking that async io will be done using an event lock (not really a lock)
since it seems pretty good for the job
when the kernel is done the IO, it can set the event
causing any thread waiting on it to wake
and it can also be queried
so a thread can peridoically check if it's done
the work itself will probably be done on a worker thread
but the driver interface doesn't currently support async io
I'll do async io after fixing the page cache
when a write wants to add a dirty entry, it will first check if there is already one which contains the current offset
if so, it's expanded if needed
otherwise a new one is made
typedef LIST_HEAD(dirty_pc_list, struct pagecache_dirty_region) dirty_pc_list;
LIST_PROTOTYPE(dirty_pc_list, struct pagecache_dirty_region, node);
typedef struct pagecache_dirty_region
{
// take this lock when any reads or writes are done on the region this represents.
// also take it if expanding 'sz'
mutex lock;
size_t fileoff;
size_t sz;
LIST_NODE(dirty_pc_list, struct pagecache_dirty_region) node;
} pagecache_dirty_region;
typedef struct pagecache
{
// Take this lock when expanding the page cache.
mutex lock;
char* data;
size_t sz;
// Take this lock when appending a node to the dirty region list.
mutex dirty_list_lock;
dirty_pc_list dirty_regions;
atomic_size_t refcnt;
} pagecache;```
Those are my finalized page cache structs
now cached reads are as simple as:
pagecache_dirty_region* dirty = VfsH_PCDirtyRegionLookup(&desc->vn->pagecache, desc->offset);
if (dirty)
Core_MutexAcquire(&dirty->lock);
if (desc->vn->pagecache.sz <= desc->offset)
VfsH_PageCacheResize(&desc->vn->pagecache, desc->vn, desc->offset + nBytes);
memcpy(buf, desc->vn->pagecache.data + desc->offset, nBytes);
if (dirty)
Core_MutexRelease(&dirty->lock);```
cached writes are essentially the same
except the memcpy goes the other way
page cache flushing is pretty easy:
driver_id* driver = vn->mount_point->fs_driver->driver;
for (pagecache_dirty_region* curr = LIST_GET_HEAD(dirty_pc_list, &pc->dirty_regions); curr; )
{
pagecache_dirty_region* next = LIST_GET_NEXT(dirty_pc_list, &pc->dirty_regions, curr);
driver->header.ftable.write_sync(vn->desc, pc->data + curr->fileoff, curr->sz, curr->fileoff, nullptr);
curr = next;
}```
I have no way to test writing atm, as I have no RW fs drivers
but I did implement it at least
however, it is practically a memcpy
for cached writes
damn
I have ||2244|| additions in two days
and ||98|| deletions
maybe I should start going outside
now time for async io
kernel is sitting at 26K loc
which is 3k more than my third kernel
this rewrite is starting to reach the state of the third kernel
all that's different is the fact that it had userspace code that ran in ring 3
I think async io will use a combination of event (signallable objects) and worker thread(s)
when an async read or write is made, the kernel starts a (kernel-mode?) thread
which does the IO, rather through the filesystem driver, or through the page cache, depending on the fd's flags
when the request is completed, the kernel will set the event
causing any thread waiting on it to wake
but if the data is already present in the page cache, no worker thread is made, and the operation completes immediately
(taking slight inspiration from NT)
the same thing happens if the data < the disk sector size
if the filesystem is not backed by a disk
then this case simply does not exist
however, if it is not already present, a worker thread is made which fills the missing parts
actually no
the data is copied over
but a worker thread is made to fill in the missing parts in the user buffer
and not the page cache
I might rewrite the initialization code soon
to not be so fucking cluttered
arch/x86_64/entry.c is 1100 lines
I am currently looking for a bug with the vma and huge pages
some funky shi be happenin'
currentNodeAddr < lastAddress
uintptr_t currentNodeAddr = currentNode->addr - (currentNode->addr % pgSize);
idk why I round it down
now it allocated the virtual memory
probably an artifact from the thing I ported it from
ok async reads seem to work properly...
I think I might use async reads while loading additional drivers from the cmdline
why? because async is cool
the vfs is probably nearly done
I just need to fix bugs
and also add some way to list directories
and open directories
but that won't be too much effort
and also unmount
that might take a bit
now I could do that tomorrow
or I could touch grass
tbh
consider trying this
MMPFN DirectPages[12], *IndirectPtr, **DoublyIndirectPtr, ***TriplyIndirectPtr /*etc*/;
hey thats my idea.
yeah
it's also your idea
but it's also something ext2 does
not just ext2
the very first unix filesystem did it
probably got the idea from multics
idk thats where I remember the idea from
I don't quite understand the purpose of that
because it seems to me like you're allocating extra bytes and using extra time you dont need to use by keeping a linked list
That was an old pagecache entry
This is a new one
That is what I figured as well, which is why I revamped it before it was too late
I'm going to implement file mapping in a bit
shouldn't be too hard
just map some regions with the page cache
the parts that don't exist in the page cache
will be unmapped
so on page fault, that part of the page cache can be filled
and the pages can be mapped
Yeah then do CoW on those pages
*shared file mapping
for private maps, then yeah I'll do ๐
moo
what are you going to use to track file mappings?
I would use a tree keyed by initial address in your case
the normal page struct which is an rb-tree
and then use something like RB_NFIND except the condition is opposite to find the relevant mapping
so RB_FIND
RB_FIND finds an exact matching element
im talking about something that looks for the greatest element less than or equal to the key
RB_NFIND finds the least element greater than or equal to the key
so like:
page what = { ... };
page* found = RB_NFIND(page_tree, &context->pages, &what);
if (found->addr == what.addr) // the address is the key
return found;
return RB_PREV(found);```
idk why I would want to do this instead of just finding the exact element
that
cause you can fault on a page inside the region but not at the start
this is what i am doing for another case
my pf handler has code to round down the page
https://github.com/OBOS-dev/obos/blob/master/src/oboskrnl/arch/x86_64/entry.c#L254-L257
uintptr_t virt = getCR2();
virt &= ~0xfff;
if (Arch_GetPML2Entry(getCR3(), virt) & (1<<7))
virt &= ~0x1fffff;```
maybe that'll be a problem for getting the offset
this only alleviates the problem within the page
certainly not within a range that spans several pages
eh you might be able to use the PTE to find the address of the VAD list entry
I am currently working on CoW
before implementing file mapping
it should work when the page is shared across two contexts
more than two contexts, likely not
actually maybe
I am also making OOM within page faults be handled better
if a page is being swapped in, but there is not enough physical memory, it will try to swap out enough pages to get enough
if it cannot
then it will block until there is enough memory
I think I will use a synchronization event to implement this
along with a linked list of threads, along with how much memory they are requesting
then whenever a page is swapped out, the VMM will check wake as many threads as it can
actually, I could just use a bare struct waitable_header to implement this
then keep how much memory the thread needs in struct thread
but this will only work if the pages are contiguous in phys. memory
so it'll only try to swap out one page
otherwise it will block until enough memory is freed
and to avoid code duplication in this case, the pmm will be made arch independant
it only took me 4 months to realize that I should've done that
the thingy broke
it's incorrectly calculating the physical memory boundaries somehow
this is all it is:
if ((phys + nPages * OBOS_PAGE_SIZE) > Mm_PhysicalMemoryBoundaries)
Mm_PhysicalMemoryBoundaries = (phys + nPages * OBOS_PAGE_SIZE);```
at some point phys became garbage it seems
ok I fixed it
wait...
the pmm has been lock free
the entire duration of the kernel
and I have had no problems
clearly a sign that it can remain lockfree
I'll just keep it lock free until I have problems 
I have no idea what this means but it seems like nice progress :D
I think it is
I'll be honest idk what's happening here either
When I wrote this code, God and I knew what it did.
Now, only God knows.

I'd lock at least the page queues
discord is being funny
i dont think that was a discord thing
why would he send the same message twice
I'm in a low cell coverage zone atm
but with a word swapped
I sent the first message then it disappeared somehow so I sent another one saying the same thing
ah ok
how would that even happen as a discord bug?
i could see it happening if they were identical
but swapping words seems unlikely
I thought he might've edited one of the messages
ah thats true yeah
but discord somehow sent it as a separate message
map pages as RO
then on fault, allocate a separate physical page
copy the contents there and remap one of the pages to use that phys. address instead
exit
this only works for two pages though
Ok but what if it's a page that wasn't forked
how do you know whether a page has been forked
struct page* copied_page; // If CoW is enabled on this page, this has a pointer to the page we're sharing data with.
basically if that's nullptr, the pf handler knows that the page faulted for some other reason
and not because of CoW
i didnt know there were multiple methods
i was thinking you just
remap to a new page if it faults
and memcpy from the original
and set the write bit obviously
shit I almost forgot to memcpy
good thing i expressed my confusion
nevermind im being stupid
i was thinking of a different situation which wouldnt really apply
I think I should maybe defer file mapping-related page faults
outside of the pf handler
and block until it's done
as I don't think reading from files and allocating memory for the page cache is a good idea while the pf handler is running
worker thread is always the solution
or I could implement APCs
then start a kernel APC on the current thread
that does the pf handler's work
or I could add:
TODO: Use kernel APCs instead
then use a DPC to do the work, then signal an event when it's done
which causes the thread to wake up
actually a kernel apc might not work
kinda sucks if the page is shared by multiple tasks
I know
The mach way is to do object chaining and shadow pages and stuff
I could just wait until after the context's lock is released within the pf handler
I'm unsure where refcounting comes from, I took it from uvm but it may have been done in solaris
but there's the possibility that something else was locked when the pf happened causing a possible deadlock
so I'll just use a DPC like I said here
that is all foreign to me
Yeah it's simpler and more efficient to do refcounting
Shadow objects implements copy on write. When a VM object is duplicated (e.g., at fork) a shadow object is created. A shadow object is initially empty, and points to underlying object. When the contents of a page of shadow object is modified, the page is copied and insert in the list of pages for the shadow object. A series of shadow objects pointing to shadow objects or original objects is a shadow chain.
https://pdos.csail.mit.edu/archive/6.097/lec/l18.html from this pdos MIT page
idk if this is a course, I know pdos is the MIT OS team tho
Looks cool anyway
I might need to use a worker thread instead of a dpc
and I'm also going to need to start using mutexes more often
and converting some places to use mutexes instead of spinlocks
or I could just map in the page after the context lock is released
as I've thought about it, and unless someone does something schizophrenic, it'll be fine
ill take a look at this, thanks
think I might change this to be a next and prev copied page pointer
so that it doesn't break with more than two pages doing CoW
I think this code is right if I want to insert a page inside a linked list:
if (pc_page->next_copied_page)
pc_page->next_copied_page->prev_copied_page = page;
page->next_copied_page = pc_page->next_copied_page;
page->prev_copied_page = pc_page;
pc_page->next_copied_page = page;```
specifically to insert page in front of pc_page
Don't tho, just read the UVM paper for a refcount based approach
i meant just to learn about it
i already intended on doing the refcounting
Yeah fair
it seems simple enough but ill read the paper anyway
You don't strictly need it but the concept of amaps is pretty simple and makes it easy to add other fancy features too
on failed write(page):
page.refcount++;
if page.refcount = 1:
set write bit in page
else:
allocate new phys page and map it to virtaddr
copy to the new page``` this is my current understanding basically
that is unfamiliar to me so i will definitely have a look
Yeah though it should be else if ref>1
and check perms too
could it ever be zero?
i just incremented it above
yes
how?
ah
but surely then it would be incremented again to 1 if another page writes to it
yeah that too
And refcount is not incremented per write but on copy
this is only on failed writes though which is the only time we would need to copy
or am i misunderstanding
so you need to copy the page when it was written to and was forked (refcnt>1)
So you increase the refcnt in fork()
just chiming in to say I also recommend the uvm paper, it's got some (imo) really interesting thoughts in it. None of which I've applied yet 
something weird happened
added:
ctx->workingSet.size -= (page->prot.huge_page ? OBOS_HUGE_PAGE_SIZE : OBOS_PAGE_SIZE);
when a page is removed from the working set
to be greeted with:
ASAN Violation at ffffffff80053ea9 while trying to write 8 bytes from 0xffff80000257f0b8 (Hint: Use of memory block after free).```
uStressTester strikes once again 
(that's what I'm calling uACPI from now on)
have you not stress tested your heap
I have
although this seems to be a bug in my vmm
that wasn't caught
because of another bug in my vmm
describe it
The vmm swaps out pages (specifically those in the working-set that are getting evicted) in the page replacement algo code. I seem to have forgotten to subtract that page from the size of the working-set pages (context->workingSet.size)
causing the working-set to keep the same pages in it practically forever
the fix to that bug uncovered other bugs in the vmm
like ?
use after frees because nodes aren't being removed from structures
ur swapping is bad btw
how come?
you do the same naive thing i did at first where you write out 1 (one) page and then immediately free it
probably on demand in response to a memory allocation that would otherwise fail
MmH_FindAvaliableAddress
typo
avaliable
ava liable
whats the non naive approach
what thehell is this doing...
supposed to initialize a spinload
*spinlock
shit
spinlocks aren't just atomic flags anymore
in an NT style vmm its
when you trim a page from a working set (or its refcount otherwise hits 0 but thats the most common reason)
and it is marked dirty
i.e., it has either never been written to the pagefile, or, it has been written to since the last time it was written to the pagefile
you place it on the 'modified page list'
when the number of pages on that list exceeds a threshold OR memory gets low, a thread called the modified page writer is awoken to 'clean' those pages by allocating contiguous clusters in the pagefile and writing them out in as big of clustered IOs as you can
this does not reclaim those pages immediately
they are merely marked clean and placed on the 'standby list'
which is where clean (non-dirty) pages go when their refcount hits 0
page frames on the standby list are considered free memory
and when the free list runs out, you yoink a standby page
for instance most of the page cache at any given time will (probably) be on the standby list
when both lists run out, by that point the modified page writer has been signaled to start writing pages for one reason or another and some will be available on the standby list at some point
so you block until they are
if you were holding any locks that need to be grabbed in order to free memory (such as your working set lock) then you drop them before blocking for free pages otherwise youll cause a deadlock
which may require you to back out of whatever half-finished thing you were doing and then retry it after youre unblocked and can reacquire the lock
file cache pages can also go on the modified list in which case they are written back to the appropriate file
everything i just said applies to anonymous (pagefile-backed) pages but analogous things occur with file pages as well
also an important detail here is that you place pages on the back of the standby list so that a more frequently used page is less likely to get reclaimed soon
which means you have more time, if you happen to need it again, to fault it back into your working set before it gets reclaimed
and then if it gets trimmed again and you only read from it while it was mapped, itll go back on the standby list immediately when trimmed cuz it already has valid backing in the pagefile from last time it was trimmed and went on the modified list
but if you wrote to it then youve dirtied it which invalidates its backing in the pagefile
so then itd go back on the modified page list
it was because of how I would detect if a page was in the referenced list
I would check if age == 1
but once the sampling interval ends, age is shifted left by one
but sometimes, I guess the page isn't removed from the referenced list at the end of the sampling interval
now a garbage physical address is being passed somewhere
:/
I added an assertion to allocate physical pages:
Assertion failed in function Mm_AllocatePhysicalPages. File: /home/oberrow/Code/obos/src/oboskrnl/mm/pmm.c, line 142. phys < Mm_PhysicalMemoryBoundaries```
maybe I should add a lock to the pmm
that didn't fix the issue (at hand)
I was passing a byte count instead of a page count to Mm_FreePhysicalPages in one specific case
but otherwise, I get
Stack corruption detected at IP=0xffffffff80005788 (overwrite of stack canary).```
"fixed" that
because now I get
Page fault at 0xffffffff8000516a in kernel-mode while to write page at 0x0000000000000000, which is unpresent. Error code: 2```
page* node = Mm_Allocator->ZeroAllocate(Mm_Allocator, 1, sizeof(page), &status);
that's where it faulted

bug doesn't happen on m68k, x86_64 support is dropped
the m68k port has so much less bullshit going in it
[ DEBUG ] Arch_KernelEntry: Initializing allocator.
[ DEBUG ] Arch_KernelEntry: Initialize kernel process.
[ DEBUG ] Arch_KernelEntry: Initializing IRQ interface.
[ DEBUG ] Arch_KernelEntry: Initializing VMM.
[ DEBUG ] Arch_KernelEntry: Initializing timer interface.
[ DEBUG ] Arch_KernelEntry: Initializing scheduler timer.
[ DEBUG ] Arch_KernelEntry: Loading kernel symbol table.
[ DEBUG ] Arch_KernelEntry: Loading test driver.
[ DEBUG ] Drv_LoadDriver: Loaded driver at 0xc04a3378.```
this is the log on the m68k
the one for x86_64 doesn't fit in a message
all for the exact same functionality (except for PnP)
don't we just love it when an allocation of 13 bytes fails
it was because huge pages aren't supported on the m68k (at least I don't know of such thing for the m68040's mmu)
causing the map function to return unimplemented
after fixing that I can do all x86_64 can
but with about 30 less files
and it doesn't crash either
no way I actually got the x86-64 port to OOM
has the obos-curse returned?
only for x86-64
maybe it's just hidden on m68k but still there
actually this isn't OOMing, I'm simply running out of swap space
one would think 64 mib would be enough
now compress it
nvm, the in-ram swap is getting deep fried
fixed that
but the swap still claims to have no space
(there's only ~15000 nodes with the exact amount of space needed)
I fixed that
to get a ubsan violation in swap_free
my in-ram temporary swap seems to be very broken
it's causing everything from ubsan violations to page faults to KASAN violations
bro wtf
when I copy+paste the same thing, but on the m68k port
it's completely fine
like the initial swap thing was copy+pasted from x86_64's implementation
all that's different are the IRQLs being raised
I wonder if this has anything to do with the fact I use my .pageable.* sections
it saw
*was
the ld script is broken
i'd honestly make them part of .text
like .text.init, .text.page
won't work, I have to be able to separate them through PHDRs
it's not quite allowed to use sections within an elf (program) loader
why
no
you dont need to do that
you simply do like
1s
/* Define the regular text section. */
.text : {
*(.text)
} :text
. = ALIGN(CONSTANT(MAXPAGESIZE));
/* Define the initialization text section, which will be permanently reclaimed after system initialization. */
KiTextInitStart = .;
.text.init : {
*(.text.init)
} :text
. = ALIGN(CONSTANT(MAXPAGESIZE));
KiTextInitEnd = .;
/* Define the paged text section, which may not be resident at all times, pages may be swapped out and
back in at the memory manager's discretion. */
KiTextPageStart = .;
.text.page : {
*(.text.page)
} :text
. = ALIGN(CONSTANT(MAXPAGESIZE));
KiTextPageEnd = .;
/* Glob all of the other text sections into .text. */
.text : {
*(.text.*)
} : text
this is what I do in boron
they all belong in the text phdr
the sections themselves are aligned so that they dont straddle page boundaries which would make pageout/reclamation impossible because part of untouched .text is in the way
time to lock in and get file mapping done
I've done like everything for file mapping- except for file mapping itself (i.e., in the virtual memory allocate function)
obos_status status = Vfs_FdOpen(&file, pathspec, FD_OFLAGS_READ_ONLY);
if (obos_is_error(status))
{
OBOS_Debug("Could not open %s. Status: %d\n", pathspec, status);
goto end;
}
Vfs_FdSeek(&file, 0, SEEK_END);
size_t filesize = Vfs_FdTellOff(&file);
Vfs_FdSeek(&file, 0, SEEK_SET);
char *buf = Mm_VirtualMemoryAlloc(&Mm_KernelContext, NULL, filesize, 0, VMA_FLAGS_HUGE_PAGE, &file, nullptr);
OBOS_Debug("Mapped %s. Contents:\n%*s\n", pathspec, filesize, buf);```
file mapping works
at least shared mapping does...
if the region is already in the page cache, it crashes
oops
now the region can already be in the page cache
as I expected, private mapping doesn't work
I think that's fixed
except I don't do CoW
for private mappings
I'll do that "quickly"
CoW seems to work
maybe that's mainly because there are no writes...
CoW works!
although I think there could be a bug where a page originally marked as RO can become RW if it's marked as CoW
before I commit this, I just need to make one last change with shared mappings
so that they are marked as dirty in the page cache if they are modified
and to do this, I think I should be able to just mark the page as RO, then on fault, add the region to the dirty list, then mark the page RW, then return
syscalls/mlibc now?
TODOs for tomorrow:
- Directory handles
- Making sure devices work when mapped on a vnode
- Maybe unmounting
probably in a couple days
I want to refactor the x86-64 code, as it is quite a mess
might as well do the 2nd right now
mapping the uart driver's file works flawlessly on the first try
idk about freeing though
that might be a problem
Oh my god I might be dumb
the virtual memory free never releases any physical pages
lol
who needs to release memory anyways computers have millions of pages
just put everything important in the swap space, reset the machine, memory freed!
so if the file isn't a mapped file or it's a shared mapping, then I think it's safe to free the physical page
but now something decides to hang
or maybe the PMM is just really, really slow
nvm something's hanging
I hate tlb shootdowns
this is the millionth time something's hung because one cpu decided it was special and didn't want to handle a tlb shootdown
idk how safe it is to wait until after the context lock is released to unmap pages
might cause a race condition in which one thread tries to map the reclaimed pages while they're being freed
context lock?
a lock for a vmm context
I can write to a serial port using the VFS!
I now need to fix a bug with the UART driver though
basically, a synchronous read is asynchronous
pathspec = "/dev/COM1";
Vfs_FdOpen(&file, pathspec, FD_OFLAGS_UNCACHED);
Vfs_FdIoctl(&file, 6, /* IOCTL_OPEN_SERIAL_CONNECTION */0,
1,
115200,
/* EIGHT_DATABITS */ 3,
/* ONE_STOPBIT */ 0,
/* PARITYBIT_NONE */ 0,
&file.vn->desc);
file.vn->un.device->desc = file.vn->desc;
char message[15] = {};
Vfs_FdRead(&file, message, 14, nullptr);
Vfs_FdWrite(&file, message, 14, nullptr);```
If you write 14 bytes onto the serial port, it will echo them back to you
(most exciting 14 bytes I've had in a while)
tomorrow, I will work on async IO from devices
as it needs to be handled slightly differently to async IO on files
actually maybe not
*pipe-style devices (such as a UART)
I will also add an API to the driver interface which adds a special file to /dev
I have thought about this, and there isn't really much I need to do
I've added some APIs to allocate vnodes and dirents for drivers
I am now working on block devices to make sure they work properly
Directory handles are unneeded, as I can just use dirents (for now)
in fact maybe even forever
time to implement unmounting 
in theory, this will be simple
for each dirent, deref the vnode (decrement its refcount, and free it once the ref count reaches zero)
then free the current dirent
then I will also need to close any open files on the mount point, and flush each vnode's page cache
and also free the namecache
and wait for any pending async IO operations to finish
Now in order, the unmount function needs to:
- Lock the mount point
- Close all opened files.
- Wait for pending async IO to finish
- Free the name cache.
- Flush the page cache
- Dereference each vnode within the mount point
- Free each dirent
- Unlock the mount point, and return
theoretically, I should be able to unmount now...
I can't because my foreach_dirent doesn't work
mainly because skill issue
(idk how to recursively iterate over the tree without using recursion)
you can
simply fetch the "successor" node each time
assuming a binary tree
it's not a binary tree
which kind of tree is it
the kind where a node can have any amount of children
no it's for directory/file lookup
oh path lookup
yeah
so whats path lookup have to do with unmounting
each mount point contains the dirent tree for path lookup
and I gotta free it on unmount
you can store all the dirent nodes on that mountpoint in a list as well as the path tree
that's what I'm thinking to do instead
you can do both
have a path tree to assist lookup, AND store them in a list to assist their deallocation on unmount
ye
I have finished implementing unmounting
now, all I have to do is make all this thread-safe
vector<Node*> stack {root};
while (!stack.is_empty()) {
Node* node = stack.pop();
// do whatever with node
for (Node* child : node->children) {
stack.push(child);
}
}
``` assuming you are fine with top down depth first (as in if you eg. have a subtree with children then the childs are visited before the subtree's siblings
accidentally got off track and yapped to someone about multithreading (oops)
and synchronization things
but now I'll make this thread-safe
VFS unmounting is done
this should all be kind of thread safe
almostโข time for userspace
for future reference:
if you want to iterate over a directory, you call VfsH_DirentLookup to get the directory's dirent, then you set your curr node to the first child of it (dir->d_children.head), then you iterate over that like it were it a linked list (curr = curr->next)
that's just so I can look back though if I forget how
probably
Finally, no merge conflicts
oh god
in 4 days too
I think I just saw some green stuff on the floor outside- what is that?
along with 3225 LoC added total
I can almost start porting stuff
I just need to implement a couple things
Anyone knows what this is
?
I went outside of the door of my house
And found it
I think I might be lagging
@real pecan
Do you know what that is
id need a spec to tell u
YOU CAN EXIT HOUSES????
I was surprised too
In fact the air out here smells different
Idk whether I like it or not
must be poisonous. Outside is dangerous
Yeah just sounds like a glitchy framebuffer to me
Do you know who manufactured it? Maybe you can get a refund
ok I'm back in
those things being:
- pnp with drivers from the initrd
- AHCI and FAT drivers
- finalizing VFS initialization
then I just implement a bunch of syscalls
then I port mlibc
then I suffer
basically what finalizing vfs initialization does is mounts the proper root fs
instead of the initrd
and then eventually I'll write documentation
eventually
eventually
eventually
eventually
-# eventually
time to do some PnP things

So you added that back in now
Yes
Now make it possible to get validated drivers from the internet

I also need to load drivers as passed on the kernel cmd line
--load-modules=driver,driver,...
this should even work on real hw
Forgot uacpi broke stiff
*stuff
But I'll need to fix that for pnp to work
Just noticed there is a security vulnerability in OBOS
Basically if you free a vnode which includes the page cache
You get a use after free in any virtual memory region that's allocated it
But specifically in the physical pages backing the pages, which is undetectable by the kernel
Which means someone (no one) could exploit this to get access to arbitrary data
Wouldn't say the severity is too high though, since the data is undefined
unless...
it uses VfsH_PageCacheResize with the size parameter set to zero to free the page cache
and this function does seem to remap the mapped regions
*any shared virtual memory region mapping said vnode
although there is still a use-after-free
but it's not related to the mapped regions themselves
instead it's related to the struct pages
@real pecan I think your thing might actually be broken this time. I decided to run the uACPI test runner on my computers DSDT and SSDTs to see if uACPI was failing, and to my surprise, something did seem to be broken
all goes well up to the point where my kernel crashes
uACPI's test runner's last message is:
[uACPI][ERROR] failed to disable fixed event 0```
before exiting
I'll set uACPI to trace
and see what happens
I can also try to run OBOS on other hardware
to see if it still crashes there
I can also hopefully run managarm to see if it crashes aswell
currently trying to build managarm
xbstrap: Action regenerate of source mlibc failed```
the managarm isn't managarming
I think I got that working
this is so slow
dam
there was a nightly build this entire time
Can't be bothered with that any more
Only thing I can think of tbh is a vmm bug assuming it's my problem
I'll try on another piece of hw
Because it crashes
Then crashes while crashing
Then just goes on recursively
Forgot that hyper had broken on the other test subject
On the other test subject it fails
Likely do to make kernel api
As it said invalid srgument
*argument
Thats normal
It cant perform io properly in userspace, so whatever it wrote it didn't read back
Thats a to-do for later
But thats not a bug nor a problem
Just extra log spam
Btw the runner is compiled with asan and ubsan, and also aborts on leaks, so if it did do something wrong you would see it
Soon โข๏ธ
Idk why but I find your VFS monologues very interesting
Good to know
I was able to get uacpi to initialize on real hw when I disabled the gsi
Which gsi
#9 for GPEs
Skill issue
I am aware
Lol
why it causes the kernel to crash, I don't know
and why it causes the kernel to be in an infinite panic loop, I don't know
There is 7676 kib of pageable pages on this hw
And 9328 kib paged out
Time to start an AHCI driver
if u wanna port stuff right now you could too
I know
I want to write this first
Before ports
I also need a syscall interface before ports
finally found a use for semaphores in the kernel
I can use them in the AHCI driver for ports
since you have a max of 32 command slots
Thats one of the places where I use them in astral too, but for nvme queues and stuff
bro wtf
when I comment out everything in get_bar_size in the pci file
all goes back to normal
I got very little code in the AHCI driver, but it's a start
just detects the AHCI controller after being loaded by pnp
then maps the HBA
I'll do more in a bit
Weirdly the ahci controller is in the same place on real hw, not that it's a problem or anything
Anyway, I need to initialize the hba
Then detect and initialize the ports
Then send identify ata to them
Then I need to implement reading and writing from the ports
Before this I need to get an irq handler for this
Then that's probably all I'm going to do for the ahci driver for a long time
As there isn't much I want to do with it
For each port I do this on, I register a vnode
For said port
When do we get obos graphics?
Eventually
some funny stuff is happening
basically something is remapping already-mapped pages
while it doesn't seem to be a problem directly
it's causing the kernel to hang waiting for a tlb shootdown to finish
one cpu has decided it's special
and doesn't want to handle the tlb shootdown irq
because eflags.if=false
because it's in a pf handler
which is a bug
which is easily solved by sti
haesn't caused a problem
something could've just been examining the state wrong

-# since it doesn't crash, it's fine
I've had:
223 files changed, 25330 insertions(+), 866 deletions(-)```
this summer
along with 20k LoC added
along with 6 PRs merged
Even on another test subject
What is so special about slot 31, function 2
On bus zero
from what i've seen bus 0, slot 31 is where the chipset likes to live.
so you get lpc, ahci, smbus and some other peripherals there
actually im not sure about that, that was based on a few intel systems I'd seen, but the one I'm on currently is amd and has the chipset on bus 1, slot 0.
ping?
Pong
Guess that explains it since both machines use intel cpus
I have just added a way to allocate physical memory but using strictly 32-bit addresses
will be useful for supporting old ahci controllers that don't have 32-bit addressing
and other devices
*don't have 64-bit addressing
yeah that's what I meant, mb
I can initialize the HBA, as well as the ports
I just need to send identify ata to get drive info
the coolest thing about this rewrite is that it will turn on the drive led
in the ahci driver
Holy shit obos best os
ikr
nothing that I've done over the past 3 or 4 months matters, drive led is highest priority
Oh it's controlled manually?
I could've sworn it was...
I was looking into the ahci spec to see what I needed to do
but found nothing
I'm testing to see if commands are sent
... and Core_SemaphoreAcquire is a hidden symbol
Kernel Panic on CPU 2 in thread 4, owned by process 1. Reason: OBOS_PANIC_FATAL_ERROR. Information on the crash is below:
Unhandled NMI!```

while sending an IPI
exciting
the IPI was sent in the function that's supposed to halt all cpus
which is called on panic
oh wait nvm
it was while the idle task was idling on cpu 3
I'm sure it's fine...
and I got another one...
@real pecan Am I getting the GSI for a PCI pin from uACPI properly here:
uacpi_namespace_node* pciBus = nullptr;
uacpi_find_devices("PNP0A03", pci_bus_match, &pciBus);
uacpi_pci_routing_table *pci_routing_table = nullptr;
uacpi_get_pci_routing_table(pciBus, &pci_routing_table);
for (size_t i = 0; i < pci_routing_table->num_entries; i++)
{
if (pci_routing_table->entries[i].pin != HbaIrqNumber)
continue;
if (pci_routing_table->entries[i].source == 0)
HbaIrqNumber = pci_routing_table->entries[i].index;
else
{
uacpi_resources* resources = nullptr;
uacpi_get_current_resources(pci_routing_table->entries[i].source, &resources);
HbaIrqNumber =
(resources->entries[pci_routing_table->entries[i].index].type == UACPI_RESOURCE_TYPE_IRQ) ?
resources->entries[pci_routing_table->entries[i].index].irq.irqs[0] :
resources->entries[pci_routing_table->entries[i].index].extended_irq.irqs[0];
uacpi_free_resources(resources);
}
break;
}
uacpi_free_pci_routing_table(pci_routing_table);```
HbaIrqNumber is a pin number based from zero
0=A
1=B
2=C
3=D
to the managarm source I go 
Yeah check managarm or ask @short mortar im on my phone rn
ok
I'm also on phone lol
from that I get GSI 20
but the only IRQs on the IOAPIC I see are 10 and 16
managarm does the same thing
except it indexes zero in the routing table
*resource table
to the ACPI spec I go
which I assume is the index field of the pci route table
There is a good freebsd page that explains it
Idk if you might have seen it already
Yes (I think)
Don't know if it could be of use (it's half-copied from Managram), but I'm doing this https://github.com/ThatMishakov/pmOS/blob/main/devicesd/generic/pci/pci.c#L221 and then this https://github.com/ThatMishakov/pmOS/blob/main/devicesd/generic/pci/pci.c#L588 to resolve the interrupts and it seems to be working on physical hw
I'm not the only one who reads managarm as manga arm right? O_O
I think it is in ACPI, but the PCI interrupt pin register (don't remember how it's called) is based from 1
I know
when I fetch it, I subtract one to make it the same as what ACPI expects
I fixed it
I was supposed to use the irq_line field as well as the pin
to get the IRQ
YEAH
I can identify sector count
and sector size
and get IRQs
from the AHCI controller
isn't irq line only for the legacy pic?
Nope
It depends on how the table is specified in hardware
Its 50/50 from what I've seen
Some hw links to a link device, some provides a gsi directly
uacpi_namespace_node* pciBus = nullptr;
uacpi_find_devices("PNP0A03", pci_bus_match, &pciBus);
uacpi_pci_routing_table *pci_routing_table = nullptr;
uacpi_get_pci_routing_table(pciBus, &pci_routing_table);
for (size_t i = 0, pi = 0; i < pci_routing_table->num_entries; i++, pi++)
{
if (pci_routing_table->entries[i].pin != HbaIrqNumber)
continue;
if (pi != PCINode.irq.int_line)
continue;
if (pci_routing_table->entries[i].source == 0)
HbaIrqNumber = pci_routing_table->entries[i].index;
else
{
uacpi_resources* resources = nullptr;
uacpi_get_current_resources(pci_routing_table->entries[i].source, &resources);
HbaIrqNumber =
(resources->entries[0].type == UACPI_RESOURCE_TYPE_IRQ) ?
resources->entries[0].irq.irqs[0] :
resources->entries[0].extended_irq.irqs[0];
uacpi_free_resources(resources);
}
break;
}
uacpi_free_pci_routing_table(pci_routing_table);```
I need to look at this code
until I see what's causing me to get the wrong GSI
I know the GSI should be 16
but instead it's 20
and the time it "worked" it was zero
which was the hpet IRQ
I think I know
why are there so many entries
and how do I know which to choose
Each object contains four members that describe the routing for a single PCI interrupt. The first member is an ACPI PCI address using the same format as _ADR. Thus, the upper four bytes contain the slot, and the lower four bytes contain the function. Since the PCI function is not part of a PCI interrupt's address, it is always specified as a wildcard value of 0xFFFF and should be ignored by the operating system. The second member is a single byte indicating the intpin. A value of 0 specifies INTA#, 1 specifies INTB#, etc.
from some free bsd thing
you should remove all the ifs (except the last one that checks for the source + the else for that) and add an if to check the routing's pci device + routing's irq pin to make sure that they match what you are trying to find and if they match then continue with the other ifs (and if there is a source then you want to actually loop through the resources instead of blindly assuming that the first one is even an irq)
based off this
oh yeah, ig I have been doing it wrong 
my irq interface is selling
(translation: my irq interface broke)
it's causing double + triple faults
stack overflow it seems
SP=0000:fffffeffffffffd8
fixed that
AHCI is terrible
*The AHCI driver
it occasionally hangs
during init
how fun right
because no irq ever gets sent
and as soon as I turn on ahci tracing it goes away
ok there it is
I occasionally get a (false positive?) KASAN violation
while accessing a command header
it's my implementation of kasan
so it sucks
it was probably non-zeroed memory from the allocator
*physical memory
that was reallocated after it was freed
it's not because of that
it's because sometimes an IRQ gets sent, but port->ci is still set
so something in the init waiting on a command to finish never gets signalled
because there aren't any IRQs after
I think the bug was because I never initialized the event
for the command
that's set when the command is done
managram
.!t managarm
It's Managarm, not Managram
it works
I need to implement reading and writing
and that's really all I have to implement for the AHCI driver (in the sense of what the kernel needs)
The kernel has decided it wants to triple fault during smp init on real hw
wut
It seems to be because of the new pmm changes
confirmed after setting qemu's memory to 4G + 1 mib
but it only ever triple faults in smp init...
could be because cr3 is allocated in a 64-bit memory region
changing it to use the 32-bit version of the pmm allocate function causes it to not triple fault
There is a bug with the ahci driver on real hw
I don't get an irq
It's probably more like I get an irq but resolved the wrong gsi
lemme see what lspci throws at me
it says IRQ 29
but idk if that's a gsi or just some random number chosen by linux
I will now become an ASL interpreter
to maybe find where I messed up
into the dsdt I go!
I think I got 0x11
Package (0x04)
{
0x0001FFFF,
One,
Zero,
0x11
}, ```
address.slot=0x1f
address.function=ANY
pin=PINB#
source=0
index=0x11 (GSI to connect the thingy to)
I match this:
Package (0x04)
{
0x001FFFFF,
Zero,
LNKF,
Zero
}, ```
LNKF is confusing
I think you can look at PCI status register to see if the device raises it
you see I would, except every cpu is on the idle task
so I can't
without some funny buisness
Can you not busy wait?
maybe not
I currently use WaitOnObject to wait on an event that's set on command completion
which blocks the current thread
I'll add logs to the ahci irq handler
you said you had some real hw where the AHCI GSI was 19, did it use an intel cpu or amd cpu
I had this problem and it was because of QEMU ignored polarity and active low stuff and it worked there
I got 19 on intel and 33 on Ryzen (I think)
hmmm
I got 19 on my intel computer
probably something to do with the chipset
let me guess, the ahci controller was on slot 31, function two on the intel machine
nah it doesn't matter
indeed
Look at linkf resources
I did
What do they say
Let me check
On my phone rn
Method (_CRS, 0, Serialized) // _CRS: Current Resource Settings
{
Name (RTLF, ResourceTemplate ()
{
IRQ (Level, ActiveLow, Shared, )
{}
})
CreateWordField (RTLF, One, IRQ0)
IRQ0 = Zero
IRQ0 = (One << (PFRC & 0x0F))
Return (RTLF) /* \_SB_.LNKF._CRS.RTLF */
}
Ah ok
the numbers, what do they mean
I guess it just reads the gsi from some hw reg
SRS seems to write to said register
Makes sense
Method (_SRS, 1, Serialized) // _SRS: Set Resource Settings
{
CreateWordField (Arg0, One, IRQ0)
FindSetRightBit (IRQ0, Local0)
Local0--
PFRC = Local0
}```
_STA reads from it to get the link status
I see
the command never completes
after temporarily switching to a busy loop
I've heard vbox's ahci emulation is better than qemu's
and behold; it hangs
last vbox log before it hangs is:
AHCI#0: Reset the HBA```
from what I've seen so far
I seem to have resolved the wrong IRQ number / have a bug with the IOAPIC implementation
I get HPET irqs
so it's not the IOAPIC
@flint idol does the AHCI controller not support MSI / MSI-X
I would imagine it probably does
So then he should consider using that
Pretty sure it does
But where's fun in that
Also MSI doesn't work everywhere
uhhh yeah that too
it's easy man
Fine. I'll take a look after I fix this bug with AHCI
msi is basically
Kernel Panic on CPU 1 in thread 6, owned by process 1. Reason: OBOS_PANIC_DRIVER_FAILURE. Information on the crash is below:
Port physical layer not online after reset.
device writes 32-bit payload to address
I only support msi(x), I very much prefer that since it doesnt depend on having acpi working
The device doesnt support it? Your computer is bad, sorry
i also prefer it because it allows a very nice integration with interrupt layering
that's the gist of it lol
oh well that's nice I guess
that address is usually an address in LAPIC MMIO
is it x86 specific though
i think its PCI specific
It doesn't work on RISC-V in QEMU
and not necessarily specific to the arch
ok
I know how I'm going to abstract interrupts to drivers further. I would've thought an irq interface which dispatches your IRQs would be good enough, guess not.
I'm pretty sure it's good enough
basically I should prefer MSI over the IOAPIC, and MSI-X over MSI
and if none of those exist, yell at the driver
(return OBOS_STATUS_INVALID_OPERATION)
i think ahci controllers that operate only via legacy IRQs are golden eggs
Leave a thread polling constantly 
although idk what these are supposed to be
nvm
I think I set mask to one to mask the IRQ
and pending is just to say whether there is an irq pending or not
If capable, you can mask individual messages by setting the corresponding bit (1 << n), in the mask register.
If a message is pending, then the corresponding bit in the pending register is set.
From the osdev wiki
I think I got MSI implemented
idk if it works yet
time to go back to debugging AHCI 
and this is why you don't write AHCI code at 12 in the morning...
so MSI works...
... except I'm checking if there was an IRQ wrong
I'm checking if bit 3 of the PCI status register is set
but it isn't for some reason
after the IRQ
Interrupt Status - Represents the state of the device's INTx# signal. If set to 1 and bit 10 of the Command register (Interrupt Disable bit) is set to 0 the signal will be asserted; otherwise, the signal will be ignored.
which means I need another way
I can check the pending bit for MSI(-X)
but as for the IOAPIC fallback
I'll have a weak function that the arch defines if it can check the IRQ status
otherwise it's up to the driver to check the IRQ status
if the IRQ checker callback is nullptr
guess I have to always use the devices IRQ checker
so I can receive MSI IRQs
I have no luck receiving an IRQ on vbox
maybe if I check what @ infy's OS does
because apparently that code works
PxIE does permit all irqs
same thing with GhcIe
maybe the PCI command register's IRQ disable bit is set
doesn't seem like it
it isn't set
at all
or maybe I don't know how to read
I was accessing command at status' offset
and vice versa
in the AHCI driver
I think the spec said that MSI bypassed that stuff
So this bit is what pulls the interrupt line down
So like I think you should be checking IS register?
I do
(I haven't tried vbox, but my code seems to be working on QEMU and 3 PCs)
With IRQ routing
so I'm using it instead of testing on real hw each time
VBox doesn't have ECAM
not a problem when you can just assume the PCI bus existence 
And I haven't implemented legacy PCI access to try it
Have you tried VMware?
No
OBOS on real hw hangs during HBA init
specifically during port init
I think I might git revert my HBA init
because I rewrote it at midnight when I was very tired
guess I'm rewriting HBA initialization code now
Enable interrupts, DMA, and memory space access in the PCI command register
Memory map BAR 5 register as uncacheable.
Perform BIOS/OS handoff (if the bit in the extended capabilities is set)
Reset controller
Register IRQ handler, using interrupt line given in the PCI register. This interrupt line may be shared with other devices, so the usual implications of this apply.
Enable AHCI mode and interrupts in global host control register.
Read capabilities registers. Check 64-bit DMA is supported if you need it.
For all the implemented ports:
Allocate physical memory for its command list, the received FIS, and its command tables. Make sure the command tables are 128 byte aligned.
Memory map these as uncacheable.
Set command list and received FIS address registers (and upper registers, if supported).
Setup command list entries to point to the corresponding command table.
Reset the port.
Start command list processing with the port's command register.
Enable interrupts for the port. The D2H bit will signal completed commands.
Read signature/status of the port to see if it connected to a drive.
Send IDENTIFY ATA command to connected drives. Get their sector size and count.
seems simple enough
I think I'll follow what the spec says instead...
The infy's post also follows that I think
The wiki seems incomplete..?
probably is, which is why I'm reading the spec instead
@infy has saved me twice, once with uACPI, and now twice with the AHCI init
when uAHCI
uKernel
Oh yeah I remember this torture
Lmao
Glad to be of service
I think there needs to be some sort of agreement for universal driver format for hobby OSes