This chapter discusses some of the interfaces that VMEbus device drivers use. See Section 1.7 for information on which VMEbus interfaces are obsolete.
When writing device drivers for the VMEbus, you need to be familiar with kernel interfaces that:
The final section in this chapter discusses the mechanism for passing information to the busphys_to_iohandle interface.
Direct memory access (DMA) describes the ability of a device to directly access (read from and write to) CPU memory, without CPU intervention. On Digital UNIX systems, there is no static mapping between the VMEbus address space and system memory. The kernel maps pages of system memory to the VMEbus address spaces by using page map registers (PMRs). Each page is mapped independently by one or more PMRs. Usually, PMRs can map any physical memory page so that I/O can be directly moved into and out of user buffers. To allocate the DMA space and then set up the mapping registers for DMA transfer, call the dma_map_alloc or dma_map_load interfaces or both.
During DMA allocation, extra page map registers (PMRs) will be reserved in addition to the PMRs needed to map the system's memory buffer. If you pass DMA_GUARD_UPPER, DMA_GUARD_LOWER, or both to the flags argument of the dma_map_alloc or dma_map_load interfaces, one page map register is reserved for each flag specification. If you call both dma_map_alloc and dma_map_load, you must OR the DMA_GUARD_UPPER and DMA_GUARD_LOWER flags in the flags argument of both interfaces. (The dma_map_alloc interface reserves the PMR resources.) You specify DMA_GUARD_UPPER if the VME device is accessing mapped system memory in an ascending order. You specify DMA_GUARD_LOWER if the VME device is accessing mapped system memory in a descending order. The purpose of the guard pages is to protect system memory from a VME device that exceeds its requested boundaries. Protection is only to a page and not to the residual data from the end of the system memory buffer to the end of the alpha page.
In addition, an extra page map register is reserved for the system memory buffer. This is done for two reasons:
Following every call to the dma_map_load interface, a VMEbus device driver must call the dma_get_curr_sgentry interface to obtain the VMEbus address and byte count mapped by dma_map_load. The VMEbus device driver then passes this information to the VME device to perform VME device-to-memory read or write transfers. The VMEbus address consists of two parts:
The number of the first page map register allocated multiplied by the size of the bus that the page map register represents (typically an alpha page) plus any VMEbus offsets that are needed to get to the first allocatable resource (page map register).
The following code fragment shows the preferred method for VMEbus device drivers: calling the dma_map_load interface. See Writing Device Drivers: Tutorial for a description and example of the dma_map_alloc interface. Section 6.7 shows how the /dev/dmaex driver calls the dma_map_load interface.
.
.
.
dmaexstrategy(bp) struct buf *bp; { register struct controller *ctlr = dmaexinfo[unit]; sg_entry_t dmaex_sg;
.
.
.
sc->sc_dma_handle = (dma_handle_t) NULL; [1]
.
.
.
if (dma_map_load ((unsigned long) bp->b_bcount, [2] (vm_offset_t) bp->b_un.b_addr, [3] bp->b_proc, [4] ctlr, [5] &sc->sc_dma_handle, [6] 0, [7] VME_A32_UDATA_D32 | VME_BS_NOSWAP | DMA_GUARD_UPPER | DMA_ALL) == 0) { [8]
.
.
.
The maximum size passed to byte_count is the value stored in the b_bcount member of the buf structure pointer. The b_bcount member specifies the size of the requested transfer (in bytes). [Return to example]
In this call, the example passes a proc structure pointer stored in the b_proc member of the buf structure pointer. The b_proc member specifies a pointer to the proc structure that represents the process performing the I/O. [Return to example]
In this call, the example passes the controller structure pointer associated with this device. [Return to example]
Typically, the device driver passes an argument of type dma_handle_t *. This argument contains the address of the DMA handle returned by dma_map_alloc, if you previously called it. If the device driver does not make a previous call to dma_map_alloc, as is the case here, then you must set the dma_handle_p argument to the value NULL.
In this call, the example passes the DMA handle stored in the sc_dma_handle member of the driver's softc structure associated with this device (in this case the value NULL). [Return to example]
You can pass the bitwise inclusive OR of the following special condition bits defined in /usr/sys/include/io/common/devdriver.h:
Value | Meaning |
DMA_GUARD_UPPER | Allocates additional resources so that contiguous data overruns are captured by the system map error functions. This bit is probably most useful during device driver development and debugging. |
DMA_GUARD_LOWER | Allocates additional resources so that contiguous data underruns are captured by the system map error functions. This bit is probably most useful during device driver development and debugging. |
DMA_SLEEP | Puts the process to sleep if the system cannot allocate the necessary resources to perform a data transfer of size byte_count at the time the driver calls the interface. |
DMA_ALL | Returns a nonzero value, only if the system can satisfy a DMA transfer of size byte_count. |
Note
VME device drivers must not specifiy flags DMA_IN or DMA_OUT as flag parameters to the flags argument of the dma_map_alloc or dma_map_load interfaces. These flags are reserved for use by VME software interfaces that support a hardware DMA engine on the VME adapter. See Section D.3 for a description and examples of using the VME adapter's DMA engine for performing master block transfers. Also refer to the vba_set_dma_addr and vba_dma interfaces to set up and perform master block transfers with the local DMA engine.
In addition, VMEbus device drivers must pass special condition bits defined in /usr/sys/include/io/dec/vme/vbareg.h. The following table describes some of these bits: [Return to example]
Bit Category | Value | Meaning |
Swap mode bits | VME_BS_NOSWAP | Specifies no byte swapping. |
VME_BS_BYTE | Specifies byte swapping in bytes. | |
VME_BS_WORD | Specifies byte swapping in words. | |
VME_BS_LWORD | Specifies byte swapping in longwords. | |
VME_BS_QUAD | Specifies byte swapping in quadwords. | |
Address space bits | VME_A16 | Specifies a request for the 16-bit address space. |
VME_A24 | Specifies a request for the 24-bit address space. | |
VME_A32 | Specifies a request for the 32-bit address space. | |
VME_A64 | Specifies a request for the 64-bit address space. | |
Transfer size bits | VME_D08 | Specifies a request for the 8-bit data size. |
VME_D16 | Specifies a request for the 16-bit data size. | |
VME_D32 | Specifies a request for the 32-bit data size. | |
Access mode bits | VME_UDATA | Specifies user data. |
VME_UPROG | Specifies a user program. | |
VME_SDATA | Specifies supervisory data. | |
VME_SPROG | Specifies a supervisory program. | |
CPU allocation space bits | The default CPU allocation space is sparse space. To specify dense space, you use the VME_DENSE bit. |
To unload system direct memory access (DMA) resources that you previously allocated in a call to dma_map_load, call the dma_map_unload interface. The dma_map_unload interface unloads (invalidates) the resources that were loaded and set up in a previous call to dma_map_load. A call to dma_map_unload does not release or deallocate the resources that were allocated in a previous call to dma_map_alloc unless the driver sets the flags argument to the DMA_DEALLOC bit. If you did not specify the DMA_DEALLOC bit, you must call dma_map_dealloc to release the resources. Use of the dma_map_unload interface makes device drivers more portable between DMA hardware-mapping implementations across different hardware platforms because it masks out any future changes in the kernel- and system-level DMA mapping data structures.
The following code fragment shows a call to the dma_map_unload interface. Because this example driver sets the flags argument to the DMA_DEALLOC bit, it does not need to call dma_map_dealloc.
.
.
.
if (dma_map_load ((unsigned long) bp->b_bcount, (vm_offset_t) bp->b_un.b_addr, bp->b_proc, ctlr, &sc->sc_dma_handle, 0, VME_A32_UDATA_D32 | VME_BS_NOSWAP | DMA_GUARD_UPPER | DMA_ALL) == 0) {
.
.
.
dma_map_unload (DMA_DEALLOC, sc->sc_dma_handle); [1]
.
.
.
The dma_map_unload interface takes two arguments:
This bit setting is analogous to setting the dma_handle_p argument to the value zero (0) for dma_map_load to allocate the DMA mapping resources.
In this call, the example passes the value zero (0).
In this call, the example passes the DMA handle stored in the sc_dma_handle member of the driver's softc structure associated with this device. This DMA handle was passed in a previous call to dma_map_load.
Upon successful completion, dma_map_unload returns the value 1. Otherwise, it returns the value zero (0). [Return to example]
There are situations when your device driver may need to know the VMEbus address that corresponds to the I/O handle that the VMEbus configuration code passed to the driver's probe interface. To retrieve this address, you call the vba_get_vmeaddr interface.
The following code fragment shows a call to vba_get_vmeaddr:
.
.
.
vme_addr_t vmeaddr; io_handle_t addr; register struct controller *ctlr; addr = (io_handle_t)ctlr->addr;
.
.
.
vmeaddr = vba_get_vmeaddr (ctlr, [1] addr); [2]
.
.
.
The addr2 member of the pointer to the controller structure associated with this VMEbus device would have been used if the driver wanted the second CSR space. [Return to example]
To read data from a device register, call the read_io_port interface. The read_io_port interface is a generic interface that maps to a bus- and machine-specific interface that actually performs the read operation. Using this interface to read data from a device register makes the device driver more portable across different bus architectures, different CPU architectures, and different CPU types within the same CPU architecture.
The following code fragment shows a call to read_io_port:
.
.
.
iohandle_t reg[NDMAEX]; [1]
.
.
.
int unit;
.
.
.
unit = ctlr->ctlr_num; [2] reg[unit] = addr; [3]
.
.
.
csr = (char) read_io_port (reg[unit], DMAEX_CSR_SIZE, 0); [4] if (csr & ERROR) { return(0); }
.
.
.
The read_io_port interface takes three arguments:
In this call, the example passes the I/O handle passed to the driver's xxprobe interface.
In this call, the example passes the constant DMAEX_CSR_SIZE.
Upon successful completion, read_io_port returns the requested data from the device register located in the bus address space: a byte (8 bits), a word (16 bits), a longword (32 bits), or a quadword (64 bits). This interface returns data justified to the low-order byte lane. For example, a byte (8 bits) is always returned in byte lane 0 and a word (16 bits) is always returned in byte lanes 0 and 1. [Return to example]
To write data to a device register, call the write_io_port interface. The write_io_port interface is a generic interface that maps to a bus- and machine-specific interface that actually performs the write operation. Using this interface to write data to a device register makes the device driver more portable across different bus architectures, different CPU architectures, and different CPU types within the same CPU architecture.
The following code fragment shows a call to write_io_port:
.
.
.
iohandle_t reg[NDMAEX]; [1]
.
.
.
int unit;
.
.
.
unit = ctlr->ctlr_num; [2] reg[unit] = addr; [3]
.
.
.
write_io_port (reg[unit], DMAEX_CSR_SIZE, 0, RESET); [4] mb(); DELAY(10000);
.
.
.
The write_io_port interface takes four arguments:
In this call, the example passes the I/O handle passed to the driver's xxprobe interface.
In this call, the example passes the constant DMAEX_CSR_SIZE.
In this call, the data is encapsulated in the constant RESET.
The write_io_port interface does not return a value. [Return to example]
The VMEbus flags associated with the atype argument of the vba_map_csr interface are passed to the busphys_to_iohandle interface through the following controller structure pointer member: ctlr->vme_flags. The vme_flags member is defined in /usr/sys/include/io/dec/vme/vbareg.h as the conn_priv[4] member of the controller structure.
The I/O handle returned by busphys_to_iohandle represents the mapped VMEbus address under the following conditions:
If the specified VMEbus address and the VMEbus flags associated with the atype argument of the vba_map_csr interface could not be found within the mapped VMEbus addresses or the VMEbus adapter support does not provide this functionality, busphys_to_iohandle returns a NULL I/O handle.
The following example calls busphys_to_iohandle to obtain an I/O handle associated with a previously mapped VMEbus address of 0x100, address space of A16, access mode of supervisory, and a swap mode of no swap:
.
.
.
struct controller *ctlr; io_handle_t ioh; ctlr->vme_flags = (void *)(VME_A16_SUPER_ACC | VME_BS_NOSWAP); ioh = busphys_to_iohandle(0x100,BUS_MEMORY,ctlr);
.
.
.
Note
The I/O handle returned by busphys_to_iohandle may not be a handle that this driver may have mapped through the autoconfiguration software or through a call to the vba_map_csr interface. Other device drivers may have mapped to the same VMEbus address space but used only a portion of that space for device CSR accesses. If this is the case, another device driver may invoke vba_unmap_csr, thus unmapping the I/O handle returned by the busphys_to_iohandle interface.