Module Rig_pci
GPUs driven through their PCI function.
A driver that drives a GPU without a kernel driver does that driver's work from the process. It takes the GPU's PCI function, reads and writes its registers and memory, gives it system memory, writes its page tables and loads its firmware. This library is what such a driver needs below it. It knows no vendor: each driver gives its vendor's GPUs, page-table format and reset. It is the expert layer under Rig_amd_pci and Rig_nv_pci, public for the driver of another GPU; a program opens a GPU through those.
The library has six types:
- A machine (
Machine.t) is a computer whose PCI functions the process reaches, either this one or another one that a transport reaches. A transport is a library that runs this library's operations on another computer. The same driver code drives a GPU on either. - A function (
Function.t) is a PCI function of a machine that the process has taken: its configuration space, BARs, interrupts and reset, and the system memory it reaches by DMA. - A window (
Window.t) is a range of a machine's addresses that the process reads and writes, such as a function's BAR or system memory allocated for it. A driver's C code accesses windows throughrig_pci.h, so a submission is the same C on every machine. - A space (
Space.t) is the range of virtual addresses that the GPUs of one vendor share. An address in it means the same memory on each of them and, for system memory, in the process. - A GPU's page tables (
Page_table.t) map the space onto the GPU's physical memory. - A GPU's memory (
Memory.t) is what the GPU addresses through its page tables, placed by kind.
Machine.t --take--> Function.t --map, alloc_dma--> Window.t <-- rig_pci.h
|
Space.t --> Page_table.t --> Memory.t --alloc--> Memory.regionA driver's open of a GPU usually goes like this:
Driver.open_holds GPUiof a machine, takes its function and calls the driver'sf, which starts the GPU.freserves its vendor's space on the machine (Machine.reserve), maps the function's BARs (Function.map) and boots the GPU through them, with the imagesFirmware.findverified. It makes the GPU's page tables over the space (Page_table.create) and the GPU'sMemoryover them, from which it allocates its rings and its users' buffers.- Before the GPU can be left running,
fgives the hold the GPU's stop (Driver.set_stop). - The driver submits work through windows.
Driver.stopends the GPU's work and releases its function, which gives back the memory, pins and addresses the function holds. The process's exit does this for each GPU it still holds.
The modules below are in reading order, from the hardware up; each builds on the ones before it.
What an open needs
A PCI function is one device on a machine's PCI bus, named by its bus address, such as 0000:03:00.0. A GPU is one function, beside others of its card such as its audio function. GPU i of a machine is the ith GPU of its vendor in bus order, whichever driver holds it (Driver).
An open of a GPU with no kernel driver needs:
- The GPU detached from its kernel driver, once, outside the program (
Driver.detach). Changes to the machine states the privileges this needs and what persists. - A function the process can take, behind an IOMMU or physically, as the machine's state decides (
Function). - For a function taken physically, the free huge pages its system memory lies in (system memory).
- The firmware images the driver boots the GPU with, verified by digest, in rig's firmware directory or in directories the driver's caller names (
Firmware).
Driver defines the states a function then passes through: detached, held and lost. Each driver states what its next open does in each.
What a function can reach
A function reaches system memory by DMA once its bus mastering is on (Function.set_bus_master). Function states what it reaches behind an IOMMU and taken physically, the privileges each way needs, and when its access ends: after its release, at the process's exit and after its death. System memory states the memory that outlives the process and which pages the kernel can still move under a function taken physically. Driver states the changes to the machine that persist.
An IOMMU is the only protection from a wrong address in a function's work and from a page the kernel moves, so a machine whose GPU is taken physically trusts that GPU's work with all of its memory.
Errors
- A request is a call that the system or the machine may refuse: taking a function, mapping a BAR, allocating or pinning system memory, resetting a function, reserving addresses, opening or changing GPUs, finding firmware, mapping memory for a GPU. A request returns
Error why. - An allocator that runs out of GPU memory or virtual addresses for what was asked returns
None. - An access never fails. A function that has left the bus reads as all ones and drops writes, and nothing tells the process. Through a machine whose transport has failed, accesses do the same. Failure is state, which
Machine.failedreports for a machine andFunction.failedfor a function. - Misuse raises
Invalid_argument, such as an index below zero, bytes outside a window, or memory given back twice. - A system call that fails only for a bug in this library, such as one on a descriptor it closed, raises
Failurenaming what it was doing and the error. - The library's locks count among the locks a thread holds (
rig_holdinrig_edge.h), so a call of rig's or of a driver's from a callback inside one raisesInvalid_argumentas it starts (Rig's domains section).Driver.open_,Driver.reset,Driver.detach,Driver.attachandFunction.finish_releases, which wait for other threads, raise it as they start too. Another call of this library's from such a callback raisesInvalid_argumentwhere it would take a lock its thread holds, without taking it. What it did before stays done, as after any exception there, and aDriver.stopthat raised so gives its GPU back asDriver.stopstates.
A message names what failed and, where something grants what is missing, the command, privilege or setting that grants it.
A driver checks for failure in four places, each just before it acts on bytes it read:
- Its wait for the device.
Function.waitreturns in itsErrora failure of the machine when it happens, and a failure of the function at the wait's bound. - Its submission from C. It calls
rig_pci_failedonce, after its last access (rig_pci.h). - A timeline word read through a window. It reads the word, then asks
Function.failed. Once the function has failed, the word keeps its last value. - A read whose bytes leave the driver, such as a copy of results. It asks
Function.failedafter the read. On a mapped window this is needed only when the bytes hold a word of all ones.
Platforms
The library builds everywhere. This machine's PCI functions, and the addresses Machine.reserve reserves for them, need Linux. Elsewhere this machine has none, and another machine's functions are reached through its transport.
References
- PCI-SIG. PCI Express Base Specification, chapter 7, "Software Initialization and Configuration": configuration space, BARs and their resizing.
- The Linux kernel's VFIO documentation: functions behind an IOMMU.
- The Linux kernel's sysfs-bus-pci ABI: functions taken physically, detached and attached.
- The Linux kernel's pagemap documentation: the physical addresses of system memory.
Reaching the hardware
A GPU's memory
module Page_table : sig ... endA GPU's physical memory and the page tables that map it.