Re-designing the kernel device_manager

The documentation about drivers ( more so for the “new” style ) is a bit lacking, or not-so-didatic in some parts.

One suggestion would to work a bit in it, then starting converting the “old style” drivers to it. In that process we could even fix some bugs or improve things a little, That could pave the way to reimplement parts of it in C++, as suggested , for a “NG - New Generation” style of drivers.

Is there some conditions in that the old style drivers cannot be reimplemented in the new style ? Like, functions not supported or the like ?

1 Like

This is what this thread is about, more or less :slight_smile:

The main “pain point” is that currently the device_manager needs to be modified for each new type of driver we add. There is a piece of hardcoded C++ code which decides which parts of the hardware to probe when, for example, the OS is looking for an audio device. I have explained it in a previous post.

So, we should improve on that. We can do it while adjusting all the existing “new style” drivers at the same time, rather than going with “let’s rewrite everything from scratch” and end up with 3 concurrent APIs, I think? But I always approach things in the “I think we can fix it” way, maybe sometimes I’m wrong about that

2 Likes

I expect that all drivers can migrate to new driver API V2. After redesign, basic concepts like device tree and attributes will be the same, so it should be not hard to adapt code.

Maybe adjust / extend the “new driver” subsystem, in a way that it doesn´t incapacitate the existing drivers ? Or designing, on paper, a new system from scratch, until it is complete enough to judge if it would work ? If a system can be designed in a way that seem it would work much better / correctly, then it could be implemented and with enough documentation t make easier to convert the existing drivers ?

The “old style” seems clearer/easier to understand, but maybe it is due to having more documentation, both from here and from BeOS. The “new style” seems a bit vague, maybe due to missing some more in-depth article describing it, from the base to a finished simple example.

4 Likes

My branch for drafting new C++ based device manager API: GitHub - X547/haiku at device_manager2.

Most simple target for initial testing would be TinyEMU RISC-V virtual machine. For it, it would be enough only 5 drivers to boot to GUI:

  • fdt
  • plic
  • virtio_mmio
  • virtio_block
  • virtio_input
11 Likes

I made initial functional device manager v2 implementation enough to boot to desktop on TinyEMU.

Some design considerations:

  • C++ API is used, but only with pure virtual functions and no virtual destructors to allow potential use of C or Rust programming languages.

  • Unlike separate device and driver nodes in device manager v1, only one node is used both for device and driver. Device node expose 2 interfaces: bus interface set by bus manager when registering node and driver interface provided by dynamically found and attached device driver module.

  • Drivers may provide multiple interfaces identified by interface name string. It allows to export multiple different interfaces or different versions of the same interface for backward compatibility.

  • Device node supports only single driver. For multi-driver nodes such as root node, new child node is automatically created. Parent node have control over creation of child node and it can set bus driver interface.

  • “Device” is renamed to “DevFsNode” to avoid confusion.

  • Device manager is moved to separate add-on for now (can be moved back later).

Device node tree:

Node tree:
Node("Devices Root"): no driver
  Node("Root subnode"): bus_managers/fdt/driver/v1
    Node("Root"): no driver
      Node("cpus"): no driver
        Node("cpu@0"): no driver
          Node("interrupt-controller"): no driver
      Node("memory@80000000"): no driver
      Node("htif"): no driver
      Node("soc"): no driver
        Node("clint@2000000"): no driver
        Node("plic@40100000"): interrupt_controllers/plic/driver/v1
        Node("serial@10000000"): no driver
        Node("virtio@40010000"): busses/virtio/virtio_mmio/driver/v1
          Node("Virtio MMIO"): no driver
        Node("virtio@40011000"): busses/virtio/virtio_mmio/driver/v1
          Node("Virtio MMIO"): no driver
        Node("virtio@40012000"): busses/virtio/virtio_mmio/driver/v1
          Node("Virtio MMIO"): drivers/disk/virtual/virtio_block/driver/v1
        Node("virtio@40013000"): busses/virtio/virtio_mmio/driver/v1
          Node("Virtio MMIO"): drivers/input/virtio_input/driver/v1
        Node("virtio@40014000"): busses/virtio/virtio_mmio/driver/v1
          Node("Virtio MMIO"): drivers/input/virtio_input/driver/v1
        Node("framebuffer@41000000"): no driver
      Node("chosen"): no driver

19 Likes

/dev/null driver with my API proposal: haiku/src/add-ons/kernel/drivers/common/null.cpp at device_manager2 · X547/haiku · GitHub.

3 Likes

This mostly looks like the old device-manager API, only in C++ and with a rather different implementation behind it, no?

I’m not sure that solves most of the problems discussed earlier up thread, though it does deal with some of the issues with boilerplate and the like. Ultimately I’m not sure about using C++, even just C++ with a standard vtable setup, for the APIs (though certainly for the implementations.)

Well, it solves issues mentioned in top post except power management (power management need proper test cases first). Please ignore hard-coded driver lookup table (DriverRoster::Lookup), driver roster is not implemented yet. Driver roster is supposed to read compatible hardware information from driver add-ons resources or BFS attributes.

Can I ask for reasoning?

Does it? It looks like it solves some part of the “inflexible abstractions” but I’m not so sure how much. Your example “null” driver just ‘attaches’ itself to the first thing ‘Probe’ is called on. Why not just have a facility to register a device node without having called ‘Probe’ at all?

That’s just one example, I didn’t look through all your commits in serious detail, but I think I saw other items from the original posts which weren’t resolved, or at least weren’t fully resolved.

C++ vtables are equivalent to C structures-of-function-pointers, as you pointed out, but this equivalence is very fragile and easily broken. Maintaining C++ ABI is already tricky in userland, and there we don’t worry about C interoperability. Maintaining it in the kernel and worrying about C interoperability at the same time sounds like a lot of work for not much particular gain.

Probe is just an entry point of driver (device manager attempts to call it to attempt to attach potential driver to device node). How it can to load itself and register something without an entry point?

Or you mean use devfs API directly for registering devfs nodes? Who and why will load add-on in this case?

It is fully equivalent to C in terms of ABI maintenance. As long you add new methods to the end of interface, you are fine. No FBC padding is needed because implementation inheritance is not used.

C interoperability is only theoretical for now, probably nobody will actually care about it because all Haiku drivers are written in C++. And FreeBSD drivers compat layer is written in C++.

1 Like

Global module initialization is the real “entry point”, isn’t it?

Except for the ones that are currently written in C, though we could admittedly rename the files.

Nonetheless, knowing what modifications break C++ ABI is annoying to deal with. In C structures, it’s very obvious what changes break ABI and which don’t.

Using module entry point will only complicate things. You will need to do the same with addition to register probe handler in entry point. Maybe you can skip node probe in simple drivers like /dev/null, but in actual hardware drivers you will need probe function for each device node.

Nonetheless, knowing what modifications break C++ ABI is annoying to deal with.

I repeat: it is the same as in C. If you modify virtual function or add new virtual function not at the end of interface, you will break ABI. Current C based device manager is very fragile to extension because it use structure inheritance. You can’t add new methods to base interfaces because device interfaces extend it (see bus_manager, pci_device_info etc.).

It is much more annoying to write C to C++ wrappers in every driver.

1 Like

Oh, of course. I meant only for drivers like /dev/null or the “misc” drivers that aren’t attached to any particular root.

Then we can write a single-file header library to do the wrapping.

Why make most drivers more complicated only to make “hello world” type drivers a bit shorter?

Also attaching drivers to some bus (root, generic etc.) is needed for driver automatic loading, unless some separate kernel add-on auto-loading mechanism will be invented.

It may be reasonable to make devfs nodes to registered directly with devfs public interface and not manage devfs nodes with device manager.

First, it will mean something similar to dual C and C++ interface vtable definitions. It will be need to write each interface twice and maintain coherence. Second even worse, it will actually need to write interfaces three (!) times: C interface, C to C++ wrapper (for C++ implementations of C interfaces), C++ to C wrapper (for calling C interfaces from C++ code). For what!? C++ phobia? In C++ OS with strong C++ ABI maintenance traditions? Are you kidding?

I wasn’t advocating that. Making such “hello world” drivers shorter shouldn’t make any other drivers longer. Perhaps my proposal isn’t clear?

Why would you need to write this at all? The other two, maybe, but why would this one be needed in my proposed setup?

I don’t know if a C++ wrapper is really necessary anyway. Just a few #ifdef __cplusplus to add short-form versions of functions. But we already have this in some places, e.g. inline functions with default arguments instead of macros or whatever.

Perhaps my proposal isn’t clear?

Updated previous post. Managing devfs nodes by device manager is useful anyway for introspection purposes (see which driver and which device node published devfs nodes). It is already available with current device manager v1.

Again, why bother with C if kernel code and drivers are mostly C++? I really don’t understand. There are no practical reasons in near perspective. I know nobody who want to write Haiku drivers in pure C.

For now I think it is perfectly fine to define interfaces in C++ only and add C/Rust ABI-compatible definitions if someone really need it and actually will write new drivers.

What’s the real benefit of C++ drivers over the inline function syntax in module struct definitions you introduced already, though? What does it save?

It will give type safety and clear code for calling bus interfaces, for example in virtio_mmio Init method.

What about the virtio_mmio Init method? I presume you mean this one:

status_t
VirtioMmioDevice::Init(phys_addr_t regs, size_t regsLen, int32 irq, int32 queueCnt)