PROTO(4)

✨ NAME ✨

proto β€” the Generic prototyping and diagnostics driver πŸ¦‹ (yes bestie, this is proto(4), straight outta the Kernel Interfaces Manual, section 4, kernel-side πŸ’…)


🌈 SYNOPSIS 🌈

Two ways to get this driver into your life: bake it into the kernel, or slide it in as a module at boot. Choose your fighter. ✨

To compile this driver into the kernel, place the following line in your kernel configuration file:

device proto

Alternatively, to load the driver as a module at boot time, place the following line in loader.conf(5):

proto_load="YES"

And okay, plot twist πŸŽ€ β€” if you want the driver to attach to a device instead of its regular driver, you have to mention it in the list of devices assigned to this loader variable:

hw.proto.attach="desc[,desc]"

πŸ’– DESCRIPTION πŸ’–

So here's the vibe. The proto device driver attaches to PCI or ISA devices when no other device drivers are present for those devices, and it creates device special files for all resources associated with the device. And the wild part? The driver itself has zero knowledge of the device it attaches to. None. It's giving "I don't know her" energy. πŸ’€

Programs can open these device special files and do register-level reads and writes. So yeah, the proto device driver is nothing but a conduit, a gateway, a little portal πŸŒ€ between user space programs and the hardware device.

Why is this useful, you ask? Examples include hardware diagnostics and prototyping. In both those use cases it is far more convenient to develop and run the logic in user space. Especially hardware diagnostics, because that needs a somewhat user-friendly interface and adequate reporting β€” and neither of those is done easily as kernel code. Periodt. πŸ’…


🌸 I/O port resources 🌸

Device special files created for I/O port resources let you do lseek(2), read(2), write(2) and ioctl(2) operations on them. πŸŽ€

  • read(2) and write(2) are used to perform input and output (respectively) on the port.
  • The amount of data that can be read or written at any single time is either 1, 2 or 4 bytes. And like, the proto driver doesn't prevent you from reading or writing 8 bytes at a time on some architectures, but do not assume that actually produces correct results. It's a trap, bestie. πŸ™ˆ
  • lseek(2) is what you use to select the port number, relative to the I/O port region that the device special file represents.

Real talk example: if the device special file corresponds to an I/O port region from 0x3f8 to 0x3ff inclusive, then an offset of 4 given to lseek with a whence value of SEEK_SET will target port 0x3fc on the next read or write operation. Slay. ✨

ioctl(2) can be used for the PROTO_IOC_REGION request. This ioctl request returns the extend of the resource covered by this device special file, and the extend comes back in the following structure:

struct proto_ioc_region {
        unsigned long   address;
        unsigned long   size;
};

πŸ¦‹ Memory mapped I/O resources πŸ¦‹

Device special files created for memory mapped I/O resources behave in the same way as those created for I/O port resources. But wait, there's more. ✨

Additionally, device special files for memory mapped I/O resources let the memory be mapped into the process' address space using mmap(2). Reads and writes to the memory address returned by mmap(2) go directly to the hardware. So you can avoid using read(2) and write(2) entirely, which reduces the access overhead significantly. Ate. πŸ’…

But heads up: alignment and access width constraints put forth by the underlying device apply. And also, make sure the compiler does not optimize memory accesses away or have them coalesced into bigger accesses. πŸ‘‘

πŸŽ€ DMA pseudo resource πŸŽ€

A device special file named busdma is created for the purpose of doing DMA. It only supports ioctl(2), and only for the PROTO_IOC_BUSDMA request. This device special file does not support read(2) nor write(2). Not the vibe, sorry. 😭

The PROTO_IOC_BUSDMA request has an argument that is both in and out, and it is defined as follows:

struct proto_ioc_busdma {
        unsigned int    request;
        unsigned long   key;
        union {
                struct {
                        unsigned long   align;
                        unsigned long   bndry;
                        unsigned long   maxaddr;
                        unsigned long   maxsz;
                        unsigned long   maxsegsz;
                        unsigned int    nsegs;
                        unsigned int    datarate;
                        unsigned int    flags;
                } tag;
                struct {
                        unsigned long   tag;
                        unsigned int    flags;
                        unsigned long   virt_addr;
                        unsigned long   virt_size;
                        unsigned int    phys_nsegs;
                        unsigned long   phys_addr;
                        unsigned long   bus_addr;
                        unsigned int    bus_nsegs;
                } md;
                struct {
                        unsigned int    op;
                        unsigned long   base;
                        unsigned long   size;
                } sync;
        } u;
        unsigned long   result;
};

So the request field is used to specify which DMA operation is to be performed, and the key field is used to specify which object the operation applies to. An object is either a tag or a memory descriptor (md). And now, the moment you've all been waiting for β€” here are the DMA operations that are defined: 🌟

  • PROTO_IOC_BUSDMA_TAG_CREATE β€” Create a root tag. The result field is set on output with the key of the DMA tag. The tag is created with the constraints given by the tag sub-structure. These constraints correspond roughly to those that can be given to the bus_dma_tag_create(9) function.
  • PROTO_IOC_BUSDMA_TAG_DERIVE β€” Create a derived tag. The key field is used to identify the parent tag from which to derive the new tag. The key of the derived tag is returned in the result field. The derived tag combines the constraints of the parent tag with those given by the tag sub-structure. The combined constraints are written back to the tag sub-structure on return.
  • PROTO_IOC_BUSDMA_TAG_DESTROY β€” Destroy a root or derived tag previously created. The key field specifies the tag to destroy. A tag can only be destroyed when it is not referenced anymore. This means that derived tags that have this tag as a parent, and memory descriptors created from this tag, must be destroyed first. Order matters, bestie. 🫢
  • PROTO_IOC_BUSDMA_MEM_ALLOC β€” Allocate memory that satisfies the constraints put forth by the tag given in the tag field of the md sub-structure. The key of the memory descriptor for this memory is returned in the result field. The md sub-structure is filled on return with details of the allocation. The kernel virtual address and the size of the allocated memory are returned in the virt_addr and virt_size fields. The number of contiguous physical memory segments and the address of the first segment are returned in the phys_nsegs and phys_addr fields. Allocated memory is automatically loaded and thus mapped into bus space. The number of bus segments and the address of the first segment are returned in the bus_nsegs and bus_addr fields. The behaviour of this operation banks heavily on how bus_dmamem_alloc(9) is implemented, which means that memory is currently always allocated as a single contiguous region of physical memory. In practice this also tends to give a single contiguous region in bus space. This may change over time. πŸ‘€
  • PROTO_IOC_BUSDMA_MEM_FREE β€” Free previously allocated memory and destroy the memory descriptor. The proto driver is not in a position to track whether the memory has been mapped in the process' address space, so the application is responsible for unmapping the memory before it is freed. The proto driver also cannot protect against the hardware writing to or reading from the memory, even after it has been freed. When the memory is reused for other purposes it can be corrupted, or cause the hardware to behave in unpredictable ways, when DMA has not stopped completely before freeing. Yeah this one is a little scary ngl. πŸ’€
  • PROTO_IOC_BUSDMA_MD_CREATE β€” Create an empty memory descriptor with the tag specified in the tag field of the md sub-structure. The key of the memory descriptor is returned in the result field.
  • PROTO_IOC_BUSDMA_MD_DESTROY β€” Destroy the previously created memory descriptor specified by the key field. When the memory descriptor is still loaded, it is unloaded first.
  • PROTO_IOC_BUSDMA_MD_LOAD β€” Load a contiguous region of memory in the memory descriptor specified by the key field. The size and address in the process' virtual address space are specified by the virt_size and virt_addr fields. On return, the md sub-structure contains the result of the operation. The number of physical segments and the address of the first segment is returned in the phys_nsegs and phys_addr fields. The number of bus space segments and the address of the first segment in bus space is returned in the bus_nsegs and bus_addr fields.
  • PROTO_IOC_BUSDMA_MD_UNLOAD β€” Unload the memory descriptor specified by the key field.
  • PROTO_IOC_BUSDMA_SYNC β€” Guarantee that all hardware components have a coherent view of the memory tracked by the memory descriptor, specified by the key field. A sub-section of the memory can be targeted by specifying the relative offset and size of the memory to make coherent. The offset and size are given by the base and size fields of the sync sub-structure. The op field holds the sync operation to be performed. This is similar to the bus_dmamap_sync(9) function.

πŸ’œ PCI configuration space πŸ’œ

Access to PCI configuration space is possible through the pcicfg device special file. This device special file supports lseek(2), read(2) and write(2). Usage is the same as for I/O port resources. Easy. ✨


🌸 more of this iconic page coming right up in part 2, don't go anywhere 🌸

πŸ—‚οΈ FILES (the map of where everything lives)

okay bestie, this is where the ✨filesystem lore✨ lives. Every device special file that belongs to a PCI device hangs out under:

/dev/proto/pci<d>:<b>:<s>:<f>

where pci<d>:<b>:<s>:<f> is literally just where that PCI device sits in the PCI hierarchy. Think of it like a lil address, and it's made of:

  • <d> β€” the PCI domain number 🌐
  • <b> β€” the PCI bus number 🚌
  • <s> β€” the PCI slot, aka the device number 🎰
  • <f> β€” the PCI function number 🧩

Every single PCI device gets a device special file called pcicfg, no exceptions. That one is your backstage pass into the PCI configuration space πŸ’…. There's also a device special file called busdma that gets created β€” that's the one handing out the interfaces you need to do DMA.

And then, for every valid base address register (BAR), a device special file gets made whose name has the BAR offset and the resource type in it. Resource type is either io or mem, meaning I/O port space or memory mapped I/O space respectively 🫢


Now ISA devices, and ngl they're the chaotic ones: they don't have a location. Instead, they get identified by their first I/O port address or their first memory mapped I/O address. So because of that, every device special file for an ISA device lives under:

/dev/proto/isa:<addr>

with addr being the address in hexadecimal notation πŸ”’

For each I/O port or memory mapped I/O address, a device special file gets created that contains the resource identification used by the kernel and the resource type. Resource type can be either io or mem β€” I/O port space or memory mapped I/O space respectively, you already know the drill πŸ’–

And when the device has a DMA channel assigned to it? Yep, a device special file named busdma gets created too, and it provides the interfaces needed for doing DMA.

🚨 important side quest: if that ISA device is not a Plug-and-Play device and it's not present in the ACPI device tree, then it must have the appropriate hints so the kernel can reserve the resources for it. No hints, no resources, no vibes. periodt.

🌈 EXAMPLES (real scenarios, slay)

Say you've got a single function PCI device in domain 0, on bus 1, in slot 2, and it has a single memory mapped I/O region. It's gonna have these device special files:

/dev/proto/pci0:1:2:0/10.mem
/dev/proto/pci0:1:2:0/pcicfg

And a legacy floppy controller? That one's giving ISA energy and gets these device files:

/dev/proto/isa:0x3f0/00.io
/dev/proto/isa:0x3f0/01.io
/dev/proto/isa:0x3f0/busdma

see how clean that is 😭 iconic honestly


πŸ‘€ SEE ALSO (go read your siblings)


🫢 AUTHORS (give credit where credit is due)

The proto device driver and this manual page were written by Marcel Moolenaar <marcel@xcllnt.net>. thank you for your service king πŸ‘‘


πŸ” SECURITY CONSIDERATIONS (read this part, fr fr)

Because programs have direct access to the hardware, the proto driver is inherently insecure. Like, that's just the nature of the beast, no cap.

πŸ’€ It is not advisable to use this driver on a production machine. Do not do it. I'm begging.

🚧 MISSING FUNCTIONALITY (the "we're working on it" section)

The proto driver does not fully support memory descriptors that need multiple physical memory segments or multiple bus space segments. At the very least, an operation is needed on the DMA pseudo resource so that the application can obtain all segments. 🧚

The proto driver does not yet support interrupts. And since interrupts can't be handled by the driver itself, they must be converted into signals and delivered to the program that has registered for interrupts. A satisfactory mechanism for keeping the interrupt masked during the signal handling is still being worked out β€” the way that's still in progress 😭

DMA support for devices other than busmaster devices is not present yet. The details of how a program is to interact with the DMA controller still need to be fleshed out. soon hopefully πŸ™ˆ


okay that's the whole page bestie, you're officially a proto(4) girlie now πŸ’… this one came from the Debian crew, dated August 7, 2015, and it lives at PROTO(4) β€” go forth and read your device special files responsibly 🌸✨

πŸ“¦ where this page comes from