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)andwrite(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
protodriver 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. Theresultfield is set on output with the key of the DMA tag. The tag is created with the constraints given by thetagsub-structure. These constraints correspond roughly to those that can be given to thebus_dma_tag_create(9)function.PROTO_IOC_BUSDMA_TAG_DERIVEβ Create a derived tag. Thekeyfield is used to identify the parent tag from which to derive the new tag. The key of the derived tag is returned in theresultfield. The derived tag combines the constraints of the parent tag with those given by thetagsub-structure. The combined constraints are written back to thetagsub-structure on return.PROTO_IOC_BUSDMA_TAG_DESTROYβ Destroy a root or derived tag previously created. Thekeyfield 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 thetagfield of themdsub-structure. The key of the memory descriptor for this memory is returned in theresultfield. Themdsub-structure is filled on return with details of the allocation. The kernel virtual address and the size of the allocated memory are returned in thevirt_addrandvirt_sizefields. The number of contiguous physical memory segments and the address of the first segment are returned in thephys_nsegsandphys_addrfields. 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 thebus_nsegsandbus_addrfields. The behaviour of this operation banks heavily on howbus_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. Theprotodriver 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. Theprotodriver 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 thetagfield of themdsub-structure. The key of the memory descriptor is returned in theresultfield.PROTO_IOC_BUSDMA_MD_DESTROYβ Destroy the previously created memory descriptor specified by thekeyfield. 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 thekeyfield. The size and address in the process' virtual address space are specified by thevirt_sizeandvirt_addrfields. On return, themdsub-structure contains the result of the operation. The number of physical segments and the address of the first segment is returned in thephys_nsegsandphys_addrfields. The number of bus space segments and the address of the first segment in bus space is returned in thebus_nsegsandbus_addrfields.PROTO_IOC_BUSDMA_MD_UNLOADβ Unload the memory descriptor specified by thekeyfield.PROTO_IOC_BUSDMA_SYNCβ Guarantee that all hardware components have a coherent view of the memory tracked by the memory descriptor, specified by thekeyfield. 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 thebaseandsizefields of thesyncsub-structure. Theopfield holds the sync operation to be performed. This is similar to thebus_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)
ioctl(2)lseek(2)mmap(2)read(2)write(2)bus_dma_tag_create(9)bus_dmamap_sync(9)bus_dmamem_alloc(9)
π«Ά 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
- debian bookworm: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- debian trixie: freebsd-manpages 12.2-2 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu jammy: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu noble: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu questing: freebsd-manpages 12.2-2
- ubuntu resolute: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu stonking: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- Copyright (c) 2014, 2015 Marcel Moolenaar
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms