CONCEPTS AND GUIDELINES
Bus spaces are described by bus space tags, which can be created only by machine-dependent code. A given machine may have several different types of bus space (e.g. memory space and I/O space), and thus may provide multiple different bus space tags. Individual busses or devices on a machine may use more than one bus space tag. For instance, ISA devices are given an ISA memory space tag and an ISA I/O space tag. Architectures may have several different tags which represent the same type of space, for instance because of multiple different host bus interface chipsets.
A range in bus space is described by a bus address and a bus size. The bus address describes the start of the range in bus space. The bus size describes the size of the range in bytes. Busses which are not byte addressable may require use of bus space ranges with appropriately aligned addresses and properly rounded sizes.
Access to regions of bus space is facilitated by use of bus space handles, which are usually created by mapping a specific range of a bus space. Handles may also be created by allocating and mapping a range of bus space, the actual location of which is picked by the implementation within bounds specified by the caller of the allocation function.
All of the bus space access functions require one bus space tag argument, at least one handle argument, and at least one offset argument (a bus size). The bus space tag specifies the space, each handle specifies a region in the space, and each offset specifies the offset into the region of the actual location(s) to be accessed. Offsets are given in bytes, though busses may impose alignment constraints. The offset used to access data relative to a given handle must be such that all of the data being accessed is in the mapped region that the handle describes. Trying to access data outside that region is an error.
Because some architectures memory systems use buffering to improve memory and device access performance, there is a mechanism which can be used to create "barriers" in the bus space read and write stream. There are three types of barriers: read, write, and read/write. All reads started to the region before a read barrier must complete before any reads after the read barrier are started. (The analogous requirement is true for write barriers.) Read/write barriers force all reads and writes started before the barrier to complete before any reads or writes after the barrier are started. Correctly-written drivers will include all appropriate barriers, and assume only the read/write ordering imposed by the barrier operations.
People trying to write portable drivers with the bus_space functions should try to make minimal assumptions about what the system allows. In particular, they should expect that the system requires bus space addresses being accessed to be naturally aligned (i.e., base address of handle added to offset is a multiple of the access size), and that the system does alignment checking on pointers (i.e., pointer to objects being read and written must point to properly-aligned data).
The descriptions of the bus_space functions given below all assume that they are called with proper arguments. If called with invalid arguments or arguments that are out of range (e.g. trying to access data outside of the region mapped when a given handle was created), undefined behaviour results. In that case, they may cause the system to halt, either intentionally (via panic) or unintentionally (by causing a fatal trap of by some other means) or may cause improper operation which is not immediately fatal. Functions which return void or which return data read from bus space (i.e., functions which do not obviously return an error code) do not fail. They could only fail if given invalid arguments, and in that case their behaviour is undefined.
Several types are defined in
.In machine/bus.h to facilitate use of the bus_space functions by drivers.
.Vt bus_addr_t type is used to describe bus addresses. It must be an unsigned integral type capable of holding the largest bus address usable by the architecture. This type is primarily used when mapping and unmapping bus space.
.Vt bus_size_t type is used to describe sizes of ranges in bus space. It must be an unsigned integral type capable of holding the size of the largest bus address range usable on the architecture. This type is used by virtually all of the bus_space functions, describing sizes when mapping regions and offsets into regions when performing space access operations.
.Vt bus_space_tag_t type is used to describe a particular bus space on a machine. Its contents are machine-dependent and should be considered opaque by machine-independent code. This type is used by all bus_space functions to name the space on which they are operating.
.Vt bus_space_handle_t type is used to describe a mapping of a range of bus space. Its contents are machine-dependent and should be considered opaque by machine-independent code. This type is used when performing bus space access operations.
MAPPING AND UNMAPPING BUS SPACE
This section is specific to the
.Nx version of these functions and may or may not apply to the
Bus space must be mapped before it can be used, and should be unmapped when it is no longer needed. The bus_space_map and bus_space_unmap functions provide these capabilities.
Some drivers need to be able to pass a subregion of already-mapped bus space to another driver or module within a driver. The bus_space_subregion function allows such subregions to be created.
Fn bus_space_map space address size flags handlep
The bus_space_map function maps the region of bus space named by the space, address, and size arguments. If successful, it returns zero and fills in the bus space handle pointed to by handlep with the handle that can be used to access the mapped region. If unsuccessful, it will return non-zero and leave the bus space handle pointed to by handlep in an undefined state.
The flags argument controls how the space is to be mapped. Supported flags include: