#BadgerOS

1 messages · Page 3 of 1

fading elm
#

Yeah now it's connecting it all together time. Hopefully I'll have tested by end of week

fading elm
#

Moment of truth: In which way will the new VMM horribly crash?

fading elm
#

I've been using page numbers but that's been causing so many problems I'm strongly considering converting everything to page-aligned addresses (for both virtual and physical)

#

Yeah okay that's it

#

(It fucks with the sign bits)

flint shell
#

for VAs?

fading elm
#

Both virtual and physical addresses in the entire memory management schtick were page numbers (i.e. actual address divided by PAGE_SIZE)

flint shell
#

ig you could use a signed right shift

fading elm
#

But you get sign extension memes if you do that for virtual addresses, which I don't feel like dealing with.

#

But I'll fix that tomorrow, already been programming all day

fading elm
#

About halfway through that conversion

#

But it is very tedious because I need to manually review every instance of interacting with the VPN and PPN types

#

(Which have been removed)

#

But now there's still code that thinks the API talks in pages, so I need to change that too

fading elm
#

I am manually interpreting page tables once again

fading elm
#

Getting closer, page tables good enough to run in but calling virt2phys causes a load access fault so I suspect there's still something wrong with the page tablage

#

It thinks a page table has physical address 0, which I suspect may be due to incorrect flags?

fading elm
#

Turns out it was mapping things in the same spot twice because I wasn't keeping my list sorted

#

Now it fails when trying to interact with the SATA drive, thinking it has a sector size of 0

fading elm
#

Another bug located: PhysMap wasn't actually unmapping things correctly, causing later mappings to be overriden by long-gone ones.

fading elm
#

Incremental progress... The new process segfaults accessing 0x0 after fork()

fading elm
#

Still slowly squashing bugs... VMM seems mostly stable, but has revealed a set of what are likely unrelated bugs.

#

After that I want to implement file window mappings

#

When I have that I'll be satisfied with VMM for the moment.

fading elm
#

Final bugs squashed there, very nice. Also fixed an init-order problem that caused devices not to have their interrupts enabled if the interrupt controller was later down the device tree.

fading elm
#

Now that the kernec has enough features to support it, I want to port more stuff. In particular a c++ library, readline and a GNU toolchain
because if I have GCC working then I can get my sysdeps upstreamed

fading elm
#

Just switched my syscalls over to a new system that automatically generates the marshalling. This will make it easier to add new syscalls and also make the marshalling logic less error-prone because less of it is manually written.

#

Though while testing other things I keep running into the device subsystem being horrifically broken. Time to finally re-write all that as well.

fading elm
#

Once again I realize that Rust's type system is just NOT ideal for the kind of device model I want.

#

Without class-style inheritance, I must either make an "is XYZ" (e.g. fn is_char(&self) -> Option<&dyn CharInterface>) method for every type of non-bus interface (e.g. interrupt controller interrupts, character API for serial drivers, etc.) or require a struct-based abstraction, requiring wrappers in places where you would otherwise not need them (e.g. struct CharInterface(Arc<dyn CharInterfaceTrait>) and then Arc<CharInterface>)

#

If I had class-style inheritance I could simply have used a composition-based model where you have e.g. Ns16550a : CharDevice and CharDevice : Device

#

And, notably, in languages where you have this style of composition, you can usually explicitly check if it is a CharDevice without checking first whether it is a Ns16550a

#

All of this combined means you can't easily create a centralized device subsystem that supports registering device types with modules (i.e. types not baked directly into the kernel)

#

And you can forget about the idea of multiple levels of inheritance (e.g. choosting to do Ns16550a : TtyDevice, TtyDevice : CharDevice and CharDevice : Device)

flint shell
#

fwiw we never have this kind of inheritance pattern for devices in Managarm so it's definitely not mandatory

fading elm
#

Really, if we're talking language considerations then this is a huge con for Rust (given the model I want) compared to C++

fading elm
#

But I want to represent it this way

lusty vale
#

step 1 dont use arc

fading elm
flint shell
#
trait Oop {
    fn get_vtable() -> Arc<dyn Any>;
}
lusty vale
fading elm
#

Because Rust vtables are lazy

lusty vale
#

and use a macro to make it like not an issue

#

and you can use try_as_dyn (nightly) to get a vtable if the trait is implemented (statically ofc)

fading elm
#

So you have to check for the specific implementation of the trait, which defeats the point of this

fading elm
lusty vale
#

you can do something like this: ```rs
pub struct BasicallyArc<T> {
vtable: &'static Vtable,
ptr: NonNull<()>, // actually T
marker: PhantomData<fn(T) -> T>, // invariant
}
struct Vtable {
trait_1: Option<DynMetadata<dyn Trait1>>, // repeat for all of the traits you have
}

#

idk i dont see an issue, i have a (very simple) inheritance-style hierarchy in my kernel for objects (objects can be an Object or VMObject, where VMObject subtypes Object) and you could totally extend the same idea to other things

fading elm
#

I'm not saying it's impossible to do inheritance

#

That's just a bit more manual

#

I'm saying it's infeasible to make a system where by trait alone you look for a device

lusty vale
#

yeah i disagree

fading elm
#

So the least-worst option (imo) would be a system where you explicitly add a is_xyz() for every device type

fading elm
lusty vale
#

there is zero type system fighting here imo

fading elm
#

It also doesn't solve device types defined by modules alone (completely unknown to the kernel before the module is loaded)

#

Though I can drop that requirement

lusty vale
#

okay so what do you even want to inherit

#

thats like the main question i think

#

because if you want to support diamond inheritance and all of the really stupid shit you can in c++ thats pretty annoying

#

but ime thats not very common

#

like your example is literally two levels of inheritance

#

and its two interface-only levels too pretty much

#

and that is actually quite simple

#

and idk how common is it to have a device which provides two distinct kinds of thing

fading elm
#

Well let's use an NS16550A-compatible (non-diamond inheritance btw) driver example:
Somewhere you have a device that implements a trait InterruptController.
Then you have an MmioBus (NOT a device, DTB-discovered) which references its interrupt controller
And finally you would have your Ns16550a which implements e.g. CharDevice (and in turn Device).

The device registry would store Arc<dyn Device> so now you have a situation where both your InterruptController and CharDevice traits have been erased from the question

lusty vale
#

but thats not really all that hard

#

you can obviously have a as_[whatever trait] function per trait

#

which is a bit annoying but totally managable

fading elm
#

Yes. And I keep saying that this needs a pattern I find ugly (sorry, your BasicallyArc is ugly) or to explicitly have checks in the Device trait for each subtype (and maybe some Arc casting helpers). Again, it can be done but is inferior to C++'s approach (ignoring diamond inheritance)

lusty vale
#

yeah the internals of this kind of setup dont look great

#

the basicallyarc approach isnt really that ugly imo

#

especially if you dont look at the internals meme

fading elm
#

And either way I can forget about module-defined traits

lusty vale
#

you can define as_trait(typeid: TypeId), and then to downcast to dyn Foo, typeid is TypeId::of::<dyn Foo + 'static>()

#

and you can wrap it in a nice macro or with some helpers

#

there are other ways of generating IDs too:

  • if you can have a macro per-interface-definition, you can create a global there and use that as the ID
  • if your inheritance path is always one level at most (which is pretty common in practice), its even easier because you only need a single field for "top level subtype"
  • you can obviously just accept not having interfaces in modules, its not like required (and how many modules actually end up declaring interfaces anyway)
fading elm
#

An alternative solution could be to have a fn register_interface<T: Device + 'static>(...) and a matching search of some kind in the registry that does the TypeId hacks internally

lusty vale
#

sure, or a static or whatever else

fading elm
#

(Actually the trait Device should probably just have the lifetime bound 'static)

lusty vale
#

yes

#

i mean personally i wouldnt use Arc at all because it requires infallible oom handling so its kinda bad anyway

and if you are building your own refcounted types there is really no issue in having your own magic header/vtable state for doing more complex downcasting behaviors

fading elm
#

because it requires infallible oom handling
Only sort of. You can try_new with them. And they're convenient for refcounting because every type that refcounts itself needs even more unsafe blocks.

lusty vale
#

and like if your device model is more complex than just "lol arc"

fading elm
#

yes

lusty vale
#

idk it didnt work with vec or string so i decided to ignore it all

fading elm
#

It works with both

#

try_reserve

#

(before the push)

lusty vale
#

but there isnt a try_push

fading elm
#

sure

#

but you still can

lusty vale
#

and theres no way im remembering to use try_reserve every time lmao

lusty vale
#

except its my own arc type that can be told to do whatever

fading elm
#

Though the discussion of std types for Box, Arc, Vec, etc. is a different one imo. If I were doing my own thing with these (which I might in the future if I end up doing actual OOM handling) then I'd still make my own replacements with largely the same API instead of putting the refcount directly in the type being refcounted instead of the smart pointer's wrapper.

lusty vale
#

but yeah thats like not the point anyway

fading elm
#

I think register_interface is probably the cleanest solution here. And when a hot-pluggable device is removed you'd end up having to do a bunch of remove_ / uninstall_ calls anyway so not like that matters too much.

lusty vale
#

i would still make custom refcounting types for devices tho

#

because yeah the funny type casting required is a bit hard to do otherwise

fading elm
lusty vale
fading elm
#

And those implement refcounting for the devices with some generic struct every device has to fit into like BaseDevice<T> or something?

lusty vale
#

but you could also totally have Ref<Object<dyn Foo>>

lusty vale
#

i just have a repr(C) wrapper struct tho bc easier

fading elm
#

this is going to need more thought than I have energy for right now

lusty vale
#

via field_projections

#

or with hacky macros

#

which allows you to write an object like ```rs
pub struct Project<Base, Type> { .. }

fading elm
#

what are field projections

flint shell
lusty vale
lusty vale
#

you just cant use vec or string without really ugly APIs

flint shell
#

hmm

#

maybe you can make some extension traits

fading elm
flint shell
#

that make it less ugly

lusty vale
#

which tells you what the base and target type is and the offset

fading elm
#

I see

#

And you would use field projections to safely implement inheritance here

lusty vale
#

its also possible to write it on stable with some slightly ugly macro hacks

lusty vale
fading elm
#

Yeah but I use unstable Rust for many things already

lusty vale
#

its actually quite handy as well it also lets you write slightly more fun intrusive collections

#

and it can make raw pointers cleaner

fading elm
#

Well either way I'll have to come back to this discussion again later

#

Because I've now spent over 3 hours doing nothing but thinking of this and closely related stuff

#

I'll make sure to ping you next time it comes up :^)

lilac surge
#

what you need is a meta layer over rust

#

an Objective-Rust if you will

fading elm
lusty vale
#

lol

lusty vale
lilac surge
fading elm
#

smh just make a custom programming language at this point trl

lilac surge
#

drivers are too high-level for most low-level languages

lusty vale
#

a metalayer would make the DX even worse though

lilac surge
#

only C++ has really achieved something like a language with an integral metalayer but it's frightfully complex and not as meta as objective-

fading elm
#

stuff like reflection?

lilac surge
fading elm
#

Can you link me something about this meta-stuff to read up on

#

or point me in a general direction

lilac surge
#

he talks about it quite often on his blog

#

he is drawing on something that's had a long history though, he talks about the "two language problem" which is a similar problematic (you'll note that two-language situations are everywhere, python + libraries written in C or Fortran is a good example)

fading elm
#

BadgerOS is always going to be two language because uACPI

#

for example

#

actually flanterm too

lilac surge
#

they're equivalent languages so it doesn't count much there

#

it's the dynamic + static language combo that's more interesting

fading elm
#

and I'm taking that I'm running into the fact Rust is static but the driver model I want is dynamic

limpid dust
#

The first time I heard Jimi Hendrix I had no idea what all the fuss was about. Sure, it was great, but it wasn't changing my life. Maybe a decade later, it hit me: thirty years previous, he'd changed every guitarist's life and changed the sound of guitar music forever. He was the first person to play a guitar like that but it happened before I was born, so I just accepted the post-Jimi guitar sound as normal and nothing special.

I'm getting some sense that Objective-C is like that. Books, manuals and grey-bearded Objective-C programmers bang on about how marvellous the runtime is and the power of code introspection and method swizzling and dynamism and I'm thinking, "Yeah, so what?". Even as a Java programmer[1], I just expect my programming environment to have those features.

But as a C programmer in the 1980s, this stuff must have been fucking mindblowing! Having joined the industry in the late 90s, the languages I've used have been mostly paving over the trails blazed by Objective C (...

#

jimi hendrix mention

fading elm
#

This whole schtick about objc is actually exactly what I needed to hear

#

Because it's true: I want something that reeks of dynamic typing in a statically-typed language

fading elm
#

The struct of shenanigans in the registry:

/// A device as stored in the registry.
struct Entry {
    /// Raw [`Arc`] pointer.
    ptr: *const (),
    /// The type ID of the interface trait this provides, which may be [`Device`].
    trait_id: TypeId,
    /// The v-table for [`Device`].
    base: *const (),
    /// The v-table for the trait named by [`Self::type_id`].
    specialized: *const (),
}
fading elm
#

Just arbitrary pointer

lusty vale
#

well base is always DynMetadata<dyn Device> right?

fading elm
#

Because this is a decomposed arc

fading elm
lusty vale
#

and all of the pointers can be nonnull<()> in general

fading elm
#

That's kinda pointless because I'd have to turn it into a NonNull and back everywhere

#

Just duplicates the amount of random BS

fading elm
#
#

I have run into a problem

#

That ?Sized relaxation is needed to actually call to e.g. register_device::<dyn CharDevice>(...) but it also prevents the cast to dyn Device pointer types

lusty vale
#

ah no this is different

#

oh hmm yeah this is kinda tricky

fading elm
#

I need the compiler to emit that cast so I have the base metadata pointer

#

But I don't see how

lusty vale
#

i mean the way i would approach it is to construct my own vtable in a fixed object header

#

and then you kinda dont have this class of issue

fading elm
#

I'm so close

#

If only I could inform the trait solver that that cast has to be possible too

lusty vale
#

but its not

#

ah wait you can write Trait: Unsize<dyn Device>

fading elm
#

yo

#

that was it

#

Well that and being more specific that I don't want it to unsize in some specific spots

lusty vale
#

hmm

#

i wonder

#

you can assert that the unsized version has the same metadata theoretically

#

i wonder what the actual layout is

fading elm
#

The fact that I unsize it there forces the coercion to dyn Device and every pointer of dyn Device will have metadata be DynMetadata<dyn Device>

fading elm
#

Fianlyl fixed the DTB parser

#

Gonna play around with exactly how I want DTB-based probing to work

#

But the current thing seems to work in that regard.

#

The new model in use for associating interrupt controllers per CPU needs at least a small amount of tweaking still to support x86's model

#

Because for that to work, you need a CPU irqno -> device map whereas the current one is a blanket CPU -> device map

fading elm
#

In my new device subsystem, I want to use a concept called "busses" (loosely adapted from nubs in IOKit). The idea is that a bus is a location where one device can connect, so that drivers which attach to buses can be appropriately probed (either immediately or delayed for drivers loaded later on).

The question: How do I handle multiple bus types?
For example, there will be very generic buses like MMIO and PCIe, but potentially also something like an abstract ATA bus (which would let the same ATA driver be used for multiple interfaces that use it under the hood).

I've considered using a trait Bus: Any so that a specific specialization can be found at runtime, but I think this is somewhat clunky and it also doesn't work if the specialization is itself a trait.

Another I've considered is using an enum Bus { MmioBus(MmioBus), PciBus(PciBus), ... }; this would make it very explicit which types of bus exist, but also make the implementation of it slightly more rigid. Do note that because of how I handle devices, you could still feasibly have multiple implementers of ATA by simply moving that trait onto the device itself (e.g. trait AtaControllerDevice: Device.

What do you think? Maybe a third option I've missed?

solid bronze
#

i wouldn't call MMIO a bus

#

it's way too generic

#

pcie yes very much

#

most devices are behind some kind of bus

fading elm
#

#polls message

fading elm
solid bronze
#

ah

#

then I'd rather call it a soc bus

#

:P

fading elm
#

Details, details

fading elm
#

Alright so @solid bronze I've decided: Bus will be a plain trait. It happens to be dyn compatible but the devices are gonna downcast it to whatever specific bus type they want for the extra functions.

#

Doing the ops thing for devices is still under consideration as it would indeed allow arbitrary expansion of functionality, but a concrete implementation with it needs some more details sorted out first.

#

Especially considering there would almost always be two nested levels of these ops: the first is the subclass and the second is implementation.

solid bronze
#

@fading elm did you see this

#

(nightly)

fading elm
#

No but this is still compile time only

fading elm
#

Been working on the interrupt bit of the new device subsystem. Not sure just how lenient I should be for strange DTB configurations. Do I allow random devices connected directly to the CPU interrupt controller? Especially because CPU interrupt controllers aren't proper devices but instead an arch implementation detail.

#

I'm probably going to check how lenient Linux is in this regard

fading elm
#

Slightly more progress once again, need to create a thread that probes buses for drivers as either become available. But that needs a new way for threads to block I hadn't made yet, so looks like scheduler detour time.

#

On DTB this is for literally everything, including the PLIC for example, but on ACPI systems some of that is informed by the MADT instead, where devices such as IOAPICs and the PLIC are directly instantiated in the MADT parsing code.

#

Seems like this new device subsystem concept is actually going to work after all

mental pagoda
fading elm
#

Very long, yes

#

Devices are one of the things I wanted to experiment with doing better

#

I could've easily done something simpler.

fading elm
#

Progress!

fading elm
#

@solid bronze I've finally solved the dyn Device problem:

pub trait Device: Any + Send + Sync + 'static {
    /// Test whether this device implements a trait and get its metadata if so.
    /// Should not be used directly.
    fn get_trait_vtable(&self, trait_: TypeId) -> Option<DevDynMetadata>;
}

impl dyn Device {
    pub fn try_as_ref<T: ?Sized + Pointee<Metadata = DynMetadata<T>> + 'static>(
        &self,
    ) -> Option<&T> {
        let meta = self.get_trait_vtable(TypeId::of::<T>())?;
        unsafe {
            let ptr: *const T = core::ptr::from_raw_parts(
                self as *const dyn Device as *const (),
                core::mem::transmute(meta),
            );
            Some(&*ptr)
        }
    }
}
solid bronze
#

holy shit

fading elm
#

You use the Any to get the specific implementation if you know it, or use a kinda hacky function to get the VTable as a trait of choice

#

Helper macro means all you do in impl is write device_get_trait_vtable!(IrqCtlDevice) for example

#

So you just list traits you want to advertise

fading elm
solid bronze
#

9

fading elm
#

lolololol

#

Oh well

#

It's only one place where you actually see how jank it is

#

I don't think there was ever gonna be a non-jank solution to the exact kind of thing I wanted to do anyway

solid bronze
#

i wonder how linux does sysfs interactions in rust

fading elm
#

Well, again, I'm trying to do dynamic programming in a static language

#

There was never going to be an idiomatic solution

fading elm
#

I'm probably going to finally implement hugepages after this, because I'm starting to get sick of waiting so long for the HHDM to build on startup (which is made worse by Rust debug builds)

fading elm
#

With the whole device subsystem coming together nicely, it's time to port the PICe controller driver and the AHCI driver

fading elm
#

PCIe enumeration is working, now going on a small detour to get me an ID allocator and a mechanism to make sure only one device can use a bus at a time

fading elm
#

@slate osprey what is the laughing react emoji on this post for

slate osprey
#

fat finger

fading elm
fading elm
#

Well, s/port/rewrite/ I guess. I'm making the SATA driver a lot more proper than I had last time.

#

It'll actually be interrupt-driven, with a driver thread per port, and it isn't implementing BlockDevice directly but rather exposes an AtaBus and I'll have a separate impl of BlockDevice for that.

#

I have the basic loop for the port done, now time to build me an AtaBus and try sending some identification commands

#

@trim harness i am now shilling BadgerOS to you

trim harness
#

Nice nice

fading elm
#

I didn't even notice you weren't in this thread

trim harness
#

I prolly was at one point til the incedent™

fading elm
#

oh

#

oh yeah that

#

well now you can see my overengineeringposting

fading elm
#

So that's a new one...

#

My limine base revision stopped working...

#

Care to know why?

#

Because a string table somewhere contains the same magic KEKW

#

So I'm going to move the requests earlier into the binary, and into their own phdr

thorn thorn
#

How's that even possible, the magic constants aren't normal ASCII

trim harness
#

shouldnt the requests be near the top too anyways?

thorn thorn
#

And you dont need a phdr, just a section is enough to order them

fading elm
fading elm
#

Which is near the end lol

#

Now they're the very first thing in the kernel

thorn thorn
fading elm
#

Anyway I'm finally switching over to my Rust Limine bindings

#

Fucked smth up in HHDM calculation I think

#

but anyway soon™ there will be no more C

#

There are three C parts the kernel depends on:

  • malloc
  • old device subsystem
  • boot protocol
    And two of them I am actively working on replacing, leaving only malloc.
    I happen to already have a suitable slab allocator written in Rust so I can just steal that one.
fading elm
#

Next up is definitely hugepages

#

Because Rust is undebuggable with any opt on

#

But that also means that only very basic opt happens at all

#

And because of the large number of layers in atomics, the pmap code is super slow while creating the hhdm

solid bronze
fading elm
#

Well there are some things I just can't properly debug even at -O1

fading elm
#

While testing device rewrite, I found two unrelated bugs, the first of which likely a regression:

  • In specific circumstances, an async signal causes a segfault in the target
  • Using SIGKILL doesn't actually stop the userland thread, causing a half-dead process to run forever
fading elm
#

BadgerOS now supports hugepages, mainly used for the HHDM atm

#

Leaving space for things like page caches to support them in the future.

fading elm
#

Working on VFS again, going to add support for remounting so that e.g. all devtmpfs have the same instance

#

This involves changing the mounts table

#

Also needs significant changing to umount as there is now more need to check what is open

fading elm
#

Hah

#

This is actually turning out to be a big refactor

solid bronze
#

@fading elm

fading elm
solid bronze
#

how tf do i point it to a different C compiler

fading elm
#

You edit those config files

#

it specifies the c compiler

solid bronze
#

bruh there's two

fading elm
#

you need to edit all

#

leftover from my earlier, very shitty, "build system"

#

I'm fixing it after I finish removing C

solid bronze
#

least cursed rust toolchain

fading elm
#

What? You think I'm stupid enough not to pin my nightly?

solid bronze
#

no the problem is that you didn't include rust-src meme

#
$ cat repos/zinnia/sources/zinnia/rust-toolchain.toml 
[toolchain]
channel = "nightly-2025-12-30"
components = ["rust-src", "rust-analyzer", "rustfmt", "clippy"]
#

(i have a rust port so it doesn't matter for me anymore, but consider doing this so it pulls that automatically)

fading elm
#

will do

solid bronze
#

ok i think i have a kernel

fading elm
#

now dump it on <insert limine riscv box>

solid bronze
#

holy damn

#

18MB

fading elm
#

-O0 + not stripped = big

solid bronze
#

hmmmmmmmmmmmmmmm

fading elm
#

Limine base revision must be before the start marker it appears

solid bronze
#

huh

fading elm
#

wait no

solid bronze
#

no

#

i'm pulling out Ghidra

fading elm
#

I checked the VM memory by the way

#

Limine has not modified the actual base revision marker

#

And the magic checks out

solid bronze
#

i have a theory of what's happening

#

let me check it

#

i see the problem

#

the markers get removed because of dead code elim

#

you need to mark them as used

fading elm
#

ffs

solid bronze
#

the start marker only ever shows up in the segment

fading elm
#

And indeed

#

That fixed it

#

And even still

#

I have to set opt back to -O0 because random BS bug and even -O1 discards info I need to debug

solid bronze
#

anyway

#

glad i could hel

#

p

fading elm
#

[00000.873] FATAL src/filesystem/vfs.rs:674:21: get_ops_as::<kernel::filesystem::ext2::E2Fs> failed for E2Fs on Media { offset: 4211712, size: 533742080, storage: /soc/pci@30000000 func 00:02.0 port 0 }
I am going to commit sudoku

#

fym downcast failed