proto
— Generic
prototyping and diagnostics driver
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):
To have the driver attach to a device instead of its regular
driver, mention it in the list of devices assigned to the following loader
variable:
hw.proto.attach="desc[,desc]"
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. The driver itself has no knowledge of the device it attaches to.
Programs can open these device special files and perform register-level
reads and writes. As such, the proto
device driver
is nothing but a conduit or gateway between user space programs and the
hardware device.
Examples for why this is useful include hardware diagnostics and
prototyping. In both these use cases, it is far more convenient to develop
and run the logic in user space. Especially hardware diagnostics requires a
somewhat user-friendly interface and adequate reporting. Neither is done
easily as kernel code.
Device special files created for I/O port resources allow
lseek(2),
read(2),
write(2)
and
ioctl(2)
operations to be performed on them. The
read(2)
and
write(2)
system calls are used to perform input and output (resp.) on the port. The
amount of data that can be read or written at any single time is either 1, 2
or 4 bytes. While the proto
driver does not prevent
reading or writing 8 bytes at a time for some architectures, it should not
be assumed that such actually produces correct results. The
lseek(2)
system call is used to select the port number, relative to the I/O port
region being represented by the device special file. If, for example, 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. The
ioctl(2)
system call can be used for the PROTO_IOC_REGION
request. This ioctl request returns the extend of the resource covered by
this device special file. The extend is returned in the following
structure:
struct proto_ioc_region {
unsigned long address;
unsigned long size;
};
The device special files created for memory mapped I/O resources
behave in the same way as those created for I/O port resources.
Additionally, device special files for memory mapped I/O resources allow the
memory to 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. As such the use of
read(2)
and
write(2)
can be avoided, reducing the access overhead significantly. Alignment and
access width constraints put forth by the underlying device apply. Also,
make sure the compiler does not optimize memory accesses away or has them
coalesced into bigger accesses.
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).
The PROTO_IOC_BUSDMA
request has an argument that is
both in and out and 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;
};
The request field is used to specify which DMA operation
is to be performed. The key field is used to specify
which object the operation applies to. An object is either a tag or a memory
descriptor (md). The following DMA operations 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 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.
- 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 contigous 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 contigous region of physical memory. In practice this also tends to
give a single contigous 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.
- 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 contigous 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.
Access to PCI configuration space is possible through the
pcicfg device special file. The device special file
supports
lseek(2),
read(2)
and
write(2).
Usage is the asme as for I/O port resources.
All device special files corresponding to a PCI device are located
under
/dev/proto/pci<d>:<b>:<s>:<f>
with pci<d>:<b>:<s>:<f>
representing the location of the PCI device in the PCI hierarchy. A PCI
location includes:
- <d>
- The PCI domain number
- <b>
- The PCI bus number
- <s>
- The PCI slot or device number
- <f>
- The PCI function number
Every PCI device has a device special file called
pcicfg. This device special file gives access to the
PCI configuration space. A device special file called
busdma is also created. This device special file
provides the interfaces needed for doing DMA. For each valid base address
register (BAR), a device special file is created that contains the BAR
offset and the resource type. A resource type can be either
io or mem representing I/O
port or memory mapped I/O space (resp.)
ISA devices do not have a location. Instead, they are identified
by the first I/O port address or first memory mapped I/O address.
Consequently, all device special files corresponding to an ISA device are
located under /dev/proto/isa:<addr> with
addr the address in hexadecimal notation. For each
I/O port or memory mapped I/O address, a device special file is created that
contains the resource identification used by the kernel and the resource
type. The resource type can be either io or
mem representing I/O port or memory mapped I/O space
(resp.) When the device has a DMA channel assigned to it, a device special
file with the name busdma is created as well. This
device special file provides the interfaces needed for doing DMA.
If the ISA device is not a Plug-and-Play device nor present in the
ACPI device tree, it must have the appropriate hints so that the kernel can
reserve the resources for it.
A single function PCI device in domain 0, on bus 1, in slot 2 and
having a single memory mapped I/O region will have the following device
special files:
- /dev/proto/pci0:1:2:0/10.mem
-
- /dev/proto/pci0:1:2:0/pcicfg
-
A legacy floppy controller will have the following device
files:
- /dev/proto/isa:0x3f0/00.io
-
- /dev/proto/isa:0x3f0/01.io
-
- /dev/proto/isa:0x3f0/busdma
-
The proto
device driver and this manual
page were written by Marcel Moolenaar
<marcel@xcllnt.net>.
Because programs have direct access to the hardware, the
proto
driver is inherently insecure. It is not
advisable to use this driver on a production machine.
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 for the application to obtain all segments.
The proto
driver does not yet support
interrupts. Since interrupts cannot 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.
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.