Transcript
Tru64 UNIX VMEbus Device Driver Example Technical Update May 1999 Product Version:
Tru64 UNIX Version 4.0F or higher
This technical update provides a revised VMEbus device driver example for the Tru64 UNIX (formerly DIGITAL UNIX) Device Driver Kit. The revision reflects VMEbus adapter support enhancements introduced in Tru64 UNIX Version 4.0F. Updated source files for the VMEbus device driver example can be obtained at the following web location: http://www.digital.com/oem/software/software.htm This document supersedes Chapter 5 of Writing VMEbus Device Drivers, June 1997 edition.
Compaq Computer Corporation Houston, Texas
© Digital Equipment Corporation 1999 All rights reserved. COMPAQ, the Compaq logo, and the Digital logo are registered in the U.S. Patent and Trademark Office. The following are trademarks of Digital Equipment Corporation: ALL–IN–1, Alpha AXP, AlphaGeneration, AlphaServer, AltaVista, ATMworks, AXP, Bookreader, CDA, DDIS, DEC, DEC Ada, DECevent, DEC Fortran, DEC FUSE, DECnet, DECstation, DECsystem, DECterm, DECUS, DECwindows, DTIF, Massbus, MicroVAX, OpenVMS, POLYCENTER, PrintServer, Q–bus, StorageWorks, Tru64, TruCluster, TURBOchannel, ULTRIX, ULTRIX Mail Connection, ULTRIX Worksystem Software, UNIBUS, VAX, VAXstation, VMS, and XUI. Other product names mentioned herein may be the trademarks of their respective companies. UNIX is a registered trademark and The Open Group is a trademark of The Open Group in the US and other countries. All other trademarks and registered trademarks are the property of their respective holders. Restricted Rights: Use, duplication, or disclosure by the U.S. Government is subject to restrictions as set forth in subparagraph (c) (1) (ii). Compaq Computer Corporation makes no representations that the use of its products in the manner described in this publication will not infringe on existing or future patent rights, nor do the descriptions contained in this publication imply the granting of licenses to make, use, or sell equipment or software in accordance with the description. Possession, use, or copying of the software described in this publication is authorized only pursuant to a valid written license from Compaq or an authorized sublicensor. Compaq conducts its business in a manner that conserves the environment and protects the safety and health of its employees, customers, and the community.
Contents
1
VMEbus Device Driver Example 1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.2.1 1.4.2.2 1.4.2.3 1.4.2.4 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 1.6.2 1.7 1.7.1 1.7.1.1 1.7.1.2 1.7.2 1.7.2.1 1.7.2.2 1.7.2.3 1.7.2.4 1.7.2.5 1.7.2.6 1.7.3 1.8 1.8.1 1.8.2 1.8.2.1
Device Register Header File dmaexreg.h .. . .. . .. . .. . . .. . .. . .. . .. . Include Files Section . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Declarations Section . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Configure Section . . .. . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Providing Declarations Required for Configuration . . .. . .. . Implementing the dmaex_configure Interface . . .. . .. . .. . .. . Implementing register_configuration . .. . .. . . .. . .. . .. . .. . Implementing register_major_number . . .. . . .. . .. . .. . .. . Implementing callback_register_configuration .. . .. . .. . Implementing callback_register_major_number . .. . .. . Autoconfiguration Support Section .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Implementing the dmaex_probe Interface .. . .. . . .. . .. . .. . .. . Implementing the dmaex_cattach Interface . .. . . .. . .. . .. . .. . Implementing the dmaex_ctlr_unattach Interface .. . .. . .. . Open and Close Device Section . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Implementing the dmaex_open Interface . .. . .. . . .. . .. . .. . .. . Implementing the dmaex_close Interface . .. . .. . . .. . .. . .. . .. . Programming User-Mode I/O to a Memory-Mapped VMEbus Window . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . User Code for Sparse or Dense Access . . .. . . .. . .. . .. . .. . User Code for Linear Byte/Word Access .. . . .. . .. . .. . .. . I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. . Implementing SETUP_VME_FOR_MMAP_PIO . .. . .. . Implementing GET_VME_INFO_FOR_MMAP_PIO . . Implementing UNMAP_VME_FOR_MMAP_PIO .. . .. . Implementing SET_MMAP_MODE .. . .. . .. . . .. . .. . .. . .. . Implementing GET_MMAP_MODE .. . .. . .. . . .. . .. . .. . .. . Memory Map (mmap) Section . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Programming Block DMA Data Transfers . .. . .. . .. . . .. . .. . .. . .. . User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. .
1–3 1–5 1–7 1–11 1–11 1–14 1–21 1–23 1–24 1–26 1–27 1–27 1–34 1–34 1–38 1–38 1–40 1–42 1–43 1–43 1–57 1–71 1–71 1–73 1–75 1–76 1–76 1–77 1–78 1–80 1–82 1–89 1–89
Contents iii
1.8.2.2 Implementing SET_STRATEGY_INFO_BLK_DMA .. . 1.8.2.3 Implementing GET_STRATEGY_INFO_BLK_DMA . . 1.8.2.4 Implementing CLR_STRATEGY_INFO_BLK_DMA . . 1.8.2.5 Implementing SET_STRATEGY_XFER_MODE . .. . .. . 1.8.2.6 Implementing GET_STRATEGY_XFER_MODE . .. . .. . 1.8.3 Read and Write Device Section .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.8.3.1 Implementing the dmaex_read Interface . . . .. . .. . .. . .. . 1.8.3.2 Implementing the dmaex_write Interface . . . .. . .. . .. . .. . 1.8.4 Strategy Section . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.8.4.1 Implementing the dmaex_minphys Interface . .. . .. . .. . 1.8.4.2 Implementing the dmaex_strategy Interface . . .. . .. . .. . 1.9 Programming PIO Data Transfers . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.9.1 User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.9.2 I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.9.2.1 Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. . 1.9.2.2 Implementing SETUP_VME_FOR_STRATEGY_PIO . 1.9.2.3 Implementing GET_VME_INFO_FOR_STRATEGY_PIO . . .. . .. . .. . .. . 1.9.2.4 Implementing UNMAP_VME_FOR_STRATEGY_PIO 1.9.2.5 Implementing SET_STRATEGY_XFER_MODE . .. . .. . 1.9.2.6 Implementing GET_STRATEGY_XFER_MODE . .. . .. . 1.9.3 Read and Write Device Section .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.9.3.1 Implementing the dmaex_read Interface . . . .. . .. . .. . .. . 1.9.3.2 Implementing the dmaex_write Interface . . . .. . .. . .. . .. . 1.9.4 Strategy Section . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.9.4.1 Implementing the dmaex_minphys Interface . .. . .. . .. . 1.9.4.2 Implementing the dmaex_strategy Interface . . .. . .. . .. . 1.10 Programming Device DMA Data Transfers .. . .. . .. . . .. . .. . .. . .. . 1.10.1 User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.10.2 I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.10.2.1 Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. . 1.10.2.2 Implementing SET_STRATEGY_XFER_MODE . .. . .. . 1.10.2.3 Implementing GET_STRATEGY_XFER_MODE . .. . .. . 1.10.3 Read and Write Device Section .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.10.3.1 Implementing the dmaex_read Interface . . . .. . .. . .. . .. . 1.10.3.2 Implementing the dmaex_write Interface . . . .. . .. . .. . .. . 1.10.4 Strategy Section . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.10.4.1 Implementing the dmaex_minphys Interface . .. . .. . .. . 1.10.4.2 Implementing the dmaex_strategy Interface . . .. . .. . .. . 1.10.5 Interrupt Section . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.11 Programming Device Input to Wired Memory with Signaling
iv Contents
1–91 1–92 1–93 1–94 1–95 1–95 1–96 1–99 1–100 1–100 1–101 1–107 1–108 1–114 1–115 1–117 1–119 1–120 1–121 1–121 1–122 1–122 1–125 1–126 1–127 1–127 1–132 1–133 1–136 1–136 1–138 1–139 1–139 1–139 1–142 1–143 1–143 1–144 1–150 1–152
1.11.1 User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.11.2 I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.11.2.1 Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. . 1.11.2.2 Implementing SET_INT_HANDLER . .. . .. . . .. . .. . .. . .. . 1.11.2.3 Implementing CLR_INT_HANDLER . .. . .. . . .. . .. . .. . .. . 1.11.2.4 Implementing MAP_SYS_MEM_TO_VME . .. . .. . .. . .. . 1.11.2.5 Implementing GET_SYS_MEM_INFO . . .. . . .. . .. . .. . .. . 1.11.2.6 Implementing UNMAP_SYS_MEM_TO_VME .. . .. . .. . 1.11.3 Interrupt Section . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.12 Programming User-Space Buffer DMA Transfers . . . .. . .. . .. . .. . 1.12.1 User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.12.2 I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.12.2.1 Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. . 1.12.2.2 Implementing SETUP_DMA_BLK_WRT . . . .. . .. . .. . .. . 1.12.2.3 Implementing GET_DMA_BLK_WRT .. . .. . . .. . .. . .. . .. . 1.12.2.4 Implementing DO_DMA_BLK_WRT . . .. . .. . . .. . .. . .. . .. . 1.12.2.5 Implementing CLR_DMA_BLK_WRT .. . .. . . .. . .. . .. . .. . 1.12.2.6 Implementing SETUP_DMA_BLK_RD . . .. . . .. . .. . .. . .. . 1.12.2.7 Implementing GET_DMA_BLK_RD . . .. . .. . . .. . .. . .. . .. . 1.12.2.8 Implementing DO_DMA_BLK_RD . .. . .. . .. . . .. . .. . .. . .. . 1.12.2.9 Implementing CLR_DMA_BLK_RD . . .. . .. . . .. . .. . .. . .. . 1.12.2.10 Implementing dmaex_setup_blk_mode . . .. . . .. . .. . .. . .. . 1.13 Programming Kernel-Space Buffer DMA Transfers .. . .. . .. . .. . 1.13.1 User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.13.2 I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.13.2.1 Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. . 1.13.2.2 Implementing SETUP_DMA_BLK_WRT . . . .. . .. . .. . .. . 1.13.2.3 Implementing GET_DMA_BLK_WRT .. . .. . . .. . .. . .. . .. . 1.13.2.4 Implementing DO_DMA_BLK_WRT . . .. . .. . . .. . .. . .. . .. . 1.13.2.5 Implementing CLR_DMA_BLK_WRT .. . .. . . .. . .. . .. . .. . 1.13.2.6 Implementing SETUP_DMA_BLK_RD . . .. . . .. . .. . .. . .. . 1.13.2.7 Implementing GET_DMA_BLK_RD . . .. . .. . . .. . .. . .. . .. . 1.13.2.8 Implementing DO_DMA_BLK_RD . .. . .. . .. . . .. . .. . .. . .. . 1.13.2.9 Implementing CLR_DMA_BLK_RD . . .. . .. . . .. . .. . .. . .. . 1.13.2.10 Implementing SET_MMAP_MODE .. . .. . .. . . .. . .. . .. . .. . 1.13.2.11 Implementing dmaex_setup_blk_mode . . .. . . .. . .. . .. . .. . 1.13.3 Memory Map (mmap) Section . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.14 Programming VMEbus Interrupt Requests .. . .. . .. . . .. . .. . .. . .. . 1.14.1 User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.14.2 I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.14.2.1 Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. .
1–153 1–159 1–159 1–162 1–164 1–164 1–167 1–168 1–169 1–170 1–171 1–178 1–178 1–181 1–182 1–183 1–184 1–185 1–186 1–187 1–188 1–189 1–193 1–194 1–202 1–202 1–205 1–206 1–207 1–208 1–209 1–210 1–211 1–212 1–213 1–213 1–218 1–220 1–221 1–222 1–223
Contents v
1.14.2.2 Implementing VME_POST_IRQ . .. . .. . .. . .. . . .. . .. . .. . .. . 1.14.2.3 Implementing VME_CLR_IRQ . . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.14.3 Interrupt Section . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.15 Programming User-Level Software Byte Swapping .. . .. . .. . .. . 1.16 Generating Platform-Specific Flag Values . . .. . .. . .. . . .. . .. . .. . .. . 1.16.1 User-Level Code . . .. . .. . . .. . .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.16.2 I/O Control (ioctl) Section .. . .. . .. . .. . . .. . .. . .. . .. . . .. . .. . .. . .. . 1.16.2.1 Implementing the dmaex_ioctl Interface .. . . .. . .. . .. . .. . 1.16.2.2 Implementing GET_HW_BYTE_SWAP_INFO .. . .. . .. . 1.16.2.3 Implementing GET_VBA_BUS_TYPE_INFO . .. . .. . .. .
Index
vi Contents
1–225 1–226 1–227 1–228 1–231 1–233 1–234 1–235 1–237 1–238
1 VMEbus Device Driver Example This chapter provides you with an opportunity to study a VMEbus device driver called /dev/dmaex, or dmaex. Although this driver does not operate on a real device, you can use it as the basis for writing your own working VMEbus device drivers. The dmaex device driver supports three methods of data transfer over the 32-bit VMEbus: programmed I/O (PIO), block-mode direct memory access (DMA), and device DMA. The dmaex driver shows you how to write a single binary model device driver that operates on the VMEbus and that you can statically or dynamically configure into your system. You can compile this driver into a single binary module, dmaex.mod, that can be: •
Statically configured into the /vmunix kernel
•
Statically configured into a sysconfigtab database image when a user boots the /vmunix kernel or when the OSFboot links the sysconfigtab image
•
Dynamically configured into the /vmunix kernel when the user makes a request, using the sysconfig utility, at single-user or multiuser time
The first six sections of this chapter list and describe the following parts of the dmaex device driver: Driver Part
Section
Device Register header file dmaexreg.h
Section 1.1
Include Files Section
Section 1.2
Declarations Section
Section 1.3
Configure Section
Section 1.4
Autoconfiguration Support Section
Section 1.5
Open and Close Device Section
Section 1.6
The remaining sections of the chapter take a different approach, grouping functionally related dmaex driver interfaces with user-level code examples that invoke those interfaces to perform common VMEbus I/O operations. The sections include:
VMEbus Device Driver Example 1–1
VMEbus Operation
Driver Parts
Section
User-mode I/O to a memory-mapped ioctl Section VMEbus window mmap Section
Section 1.7
Block DMA data transfers
ioctl Section Read and Write Device Section Strategy Section
Section 1.8
PIO data transfers
ioctl Section Read and Write Device Section Strategy Section
Section 1.9
Device DMA data transfers
ioctl Section Read and Write Device Section Strategy Section Interrupt Section
Section 1.10
Device input to wired memory with signaling
ioctl Section Interrupt Section
Section 1.11
User-space buffer DMA transfers
ioctl Section
Section 1.12
Kernel-space buffer DMA transfers
ioctl Section mmap Section
Section 1.13
VMEbus interrupt requests
ioctl Section Interrupt Section
Section 1.14
User-level software byte swapping
-
Section 1.15
Generating platform-specific flag values
ioctl Section
Section 1.16
_______________________
Note
_______________________
Both the example device driver, dmaex, and the user program that exercises the driver, dmaex_test, are included in C source format on the Tru64 UNIX Device Driver Kit CD–ROM. Any significant technical updates to the VMEbus device driver example will be posted on the Compaq Tru64 UNIX documentation web page. The source code uses the following convention: #define IE 0001 1 1
Numbers appear after some line or lines of code in the dmaex device driver example. Following the example, a corresponding number appears that contains an explanation for the associated line or lines. Some inline comments have been removed from the displayed source code. If you prefer to read the dmaex driver source code in its entirety
1–2 VMEbus Device Driver Example
with the inline comments, see the source files included on the driver kit CD–ROM.
1.1 Device Register Header File dmaexreg.h The following code shows dmaexreg.h, the device register header file for the dmaex device driver. It contains device register offsets for the dmaex device, as well as constants the driver’s ioctl and mmap interfaces use. #include #define DMAEX_CSR_OFF 0 1 #define DMAEX_COUNT_OFF 1 #define DMAEX_ADDR_OFF 4 #define DMAEX_CSR_SIZE #define DMAEX_COUNT_SIZE #define DMAEX_ADDR_SIZE #define #define #define #define #define
IE DMA_GO RESET ERROR READ
sizeof(char) 2 sizeof(short) sizeof(vme_addr_t)
0001 3 0002 0010 0020 0040
struct dmaex_ioctl_data { 4 unsigned long data[6]; }; #define PHYS_CONTIG_BUF_SIZE (128 * 1024) 5 #define CONTIG_RD_WRT_BUF_SIZE (PHYS_CONTIG_BUF_SIZE / 2) 6 #define PIO_XFER_MODE 0 7 #define DEVICE_DMA_MODE 1 #define BLOCK_DMA_MODE 2 #define MMAP_VME_TO_U_MEM 0 8 #define MMAP_K_TO_U_MEM_WRT 1 #define MMAP_K_TO_U_MEM_RD 2 enum dmaex_commands {
#define #define #define #define #define #define #define #define #define #define #define #define #define #define #define
C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C18, C19, C20, C21, C22, C23, C24, C25, C26, C27, C28, C29, C30 }; 9
SET_MMAP_MODE _IOWR(’t’, C1, struct dmaex_ioctl_data) GET_MMAP_MODE _IOWR(’t’, C2, struct dmaex_ioctl_data) SET_STRATEGY_XFER_MODE _IOWR(’t’, C3, struct dmaex_ioctl_data) GET_STRATEGY_XFER_MODE _IOWR(’t’, C4, struct dmaex_ioctl_data) SETUP_VME_FOR_MMAP_PIO _IOWR(’t’, C5, struct dmaex_ioctl_data) GET_VME_INFO_FOR_MMAP_PIO _IOWR(’t’, C6, struct dmaex_ioctl_data) UNMAP_VME_FOR_MMAP_PIO _IOWR(’t’, C7, struct dmaex_ioctl_data) SET_STRATEGY_INFO_BLK_DMA _IOWR(’t’, C8, struct dmaex_ioctl_data) GET_STRATEGY_INFO_BLK_DMA _IOWR(’t’, C9, struct dmaex_ioctl_data) CLR_STRATEGY_INFO_BLK_DMA _IOWR(’t’, C10, struct dmaex_ioctl_data) SETUP_VME_FOR_STRATEGY_PIO _IOWR(’t’, C11, struct dmaex_ioctl_data) GET_VME_INFO_FOR_STRATEGY_PIO _IOWR(’t’, C12, struct dmaex_ioctl_data) UNMAP_VME_FOR_STRATEGY_PIO _IOWR(’t’, C13, struct dmaex_ioctl_data) MAP_SYS_MEM_TO_VME _IOWR(’t’, C14, struct dmaex_ioctl_data) GET_SYS_MEM_INFO _IOWR(’t’, C15, struct dmaex_ioctl_data)
VMEbus Device Driver Example 1–3
#define #define #define #define #define #define #define #define #define #define #define #define #define #define #define
UNMAP_SYS_MEM_TO_VME SETUP_DMA_BLK_WRT GET_DMA_BLK_WRT DO_DMA_BLK_WRT CLR_DMA_BLK_WRT SETUP_DMA_BLK_RD GET_DMA_BLK_RD DO_DMA_BLK_RD CLR_DMA_BLK_RD SET_INT_HANDLER CLR_INT_HANDLER VME_POST_IRQ VME_CLR_IRQ GET_HW_BYTE_SWAP_INFO GET_VBA_BUS_TYPE_INFO
_IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’, _IOWR(’t’,
C16, C17, C18, C19, C20, C21, C22, C23, C24, C25, C26, C27, C28, C29, C30,
struct struct struct struct struct struct struct struct struct struct struct struct struct struct struct
dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data) dmaex_ioctl_data)
1
Defines three device register byte offsets, for an 8-bit control status register (CSR) field at offset 0, a 16-bit byte count field, and a 32-bit VMEbus transfer address field, respectively.
2
Defines size constants, suitable for use with the sizeof operator, for the same three fields for which offsets were defined. The dmaex device driver passes these constants to a variety of kernel interfaces.
3
Defines constants that represent device CSR bits: •
IE represents the interrupt enable bit, used by the dmaex_strategy interface to set up the device for a device DMA transfer.
•
DMA_GO represents the start DMA bit, used by the dmaex_strategy interface to trigger a device DMA transfer.
•
RESET represents the reset bit, used by the dmaex_probe interface to instruct the device to reset itself as part of its test to determine if the device is responding.
•
ERROR represents the error bit, checked by the dmaex_probe interface and the device DMA interrupt handler as an indication of a hardware device error.
•
READ represents the data-transfer-is-read/write bit, set by the dmaex_strategy interface to indicate when a device DMA transfer is a read from the device into memory.
4
Declares a six-longword argument block to be moved between the user program and the dmaex_ioctl driver interface whenever the user issues an ioctl command to the dmaex device. The use of the argument block can differ for each ioctl command implemented in the driver. In this driver example all the arguments are read/write, such that user code and the driver can write into the dmaex_ioctl structure parameter.
5
Defines the PHYS_CONTIG_BUF_SIZE constant, which represents the total number of bytes of physically contiguous memory to be allocated by the driver for user-requested kernel-space buffer DMA transfers, as
1–4 VMEbus Device Driver Example
described in Section 1.13. This value must match the value specified in the CMA_Option entry in the sysconfigtab file fragment. 6
Defines the CONTIG_RD_WRT_BUF_SIZE constant, which represents the sizes of a read and a write buffer, each of which occupy half of the physically contiguous memory chunk.
7
Defines three data transfer modes (programmed I/O, device DMA, and block DMA) that determine how the dmaex_read and dmaex_write driver interfaces interpret a read or write issued to the dmaex device from user code. The dmaex_read and dmaex_write interfaces call the physio kernel interface, which in turn invokes the dmaex_minphys and dmaex_strategy driver interfaces. The dmaex_strategy interface then uses the specified transfer mode to perform the data transfer.
8
Defines three mmap modes that determine how the dmaex_mmap driver interface interprets an mmap call from user code:
9
•
MMAP_VME_TO_U_MEM causes the dmaex_mmap interface to return a kernel page frame number that corresponds to an outbound mapped VMEbus address.
•
MMAP_K_TO_U_MEM_WRT causes the dmaex_mmap interface to return a kernel page frame number that corresponds to the physically contiguous write buffer allocated by the contig_malloc and cma_dd interfaces.
•
MMAP_K_TO_U_MEM_RD causes the dmaex_mmap interface to return a kernel page frame number that corresponds to the physically contiguous read buffer allocated by the contig_malloc and cma_dd interfaces.
Defines the names of the ioctl commands that the dmaex driver supports.
1.2 Include Files Section Writing Device Drivers: Reference provides reference descriptions of the header files that Tru64 UNIX device drivers most commonly use. The following code shows the Include Files Section for the dmaex device driver: #include #include #include #include #include #include #include #include #include
VMEbus Device Driver Example 1–5
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include
#include #include
#include #include
1
#include
"dmaexreg.h" 2
#define NO_DEV -1 3 #define MAJOR_INSTANCE 1 #define MAX_NDMAEX 8 4
1
Includes the io/dec/vme/vbareg.h bus-specific header file, which is required for any device driver that you write to operate on the VMEbus. This file contains definitions for the VMEbus adapters that Tru64 UNIX supports. The vbareg reference page provides a summary description of this header file. Because the fictitious dmaex device controller can be connected to a TURBOchannel or PCI bus, the tc.h and pci.h bus-specific header files also are included in the dmaex driver. Typically, you would not need these files for VMEbus device driver development.
2
Includes the device register header file, created for the dmaex device, that contains device register offset definitions that describe the device. The file also defines constants that dmaex device driver interfaces use. Section 1.1 lists the dmaex device offset definitions.
3
Defines two constants, NO_DEV and MAJOR_INSTANCE, that the dmaex driver’s register_major_number interface uses to obtain a device major number.
4
Defines the MAX_NDMAEX constant, which represents the maximum number of controllers that the dmaex device driver can support. The value is used to allocate enough sets of data structures to support MAX_NDMAEX devices. There can be at most eight instances of the dmaex device controller in the system. Because there are so few instances of the device controller and the data structures needed are not large, it is acceptable to allocate for the maximum configuration.
1–6 VMEbus Device Driver Example
Note that MAX_NDMAEX is used in the dmaex_attribute table, specifically as the maximum value for the numunit attribute field. Another variable, NDAMEX, is declared in the Configure Section and represents the number of controllers to create for this driver.
1.3 Declarations Section The following code shows the Declarations Section for the dmaex device driver: #define #define
DMAEXOPEN DMAEXCLOSE
1 1 0
int dmaex_configure(cfg_op_t, cfg_attr_t *, size_t, cfg_attr_t *, size_t); 2 static static static static
int int void void
register_configuration(void); register_major_number(void); callback_register_configuration(int, int, ulong, ulong); callback_register_major_number(int, int, ulong, ulong);
static static static static static static static static static static static static static static static static
int int int int int int int int int int int int int int int int
dmaex_probe(io_handle_t, struct controller *); dmaex_cattach(struct controller *); dmaex_ctlr_unattach(struct bus *, struct controller *); dmaex_open(dev_t, int, int); dmaex_close(dev_t, int, int); dmaex_mmap(dev_t, off_t, int); dmaex_ioctl(dev_t, int, struct dmaex_ioctl_data *, int); dmaex_read(dev_t, struct uio *, int); dmaex_write(dev_t, struct uio *, int); dmaex_minphys(struct buf *); dmaex_strategy(struct buf *); dmaex_intr1(int); dmaex_intr2(int); dmaex_iack_isr(struct controller *, unsigned int); dmaex_post_psignal(struct proc *, long); dmaex_rcv_int_srv(int);
struct controller *dmaex_ctlr[MAX_NDMAEX]; 3 struct
driver dmaexdriver = dmaex_probe, 0, dmaex_cattach, 0, 0, 0, 0, 0, "dmaex", dmaex_ctlr, 0, 0x8, VME_A24_UDATA_D32, 0, 0, dmaex_ctlr_unattach, 0
{ 4 /* probe /* slave /* cattach /* dattach /* go /* addr_list /* dev_name /* dev_list /* ctlr_name /* ctlr_list /* xclu /* addr1_size /* addr1_atype /* addr2_size /* addr2_atype /* ctlr_unattach /* dev_unattach
*/ */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ */
}; struct dmaex_softc { 5 int sc_open;
/* DMAEXOPEN, DMAEXCLOSE
*/
VMEbus Device Driver Example 1–7
io_handle_t event_t event_t
csr_handle; /* Device CSR I/O handle */ isi_event; /* Used by device ISI interrupts */ post_iack_event[8]; /* Used to acknowledge posted IRQs*/
dma_handle_t
dma_handle;
/* handle used by dmaex_strategy
*/
io_handle_t vme_addr_t vme_atype_t unsigned int unsigned int
pio_handle; pio_vme_addr; pio_vme_am; pio_vme_size; pio_access_type;
/* /* /* /* /* /*
Entries for mmap support I/O handle for VMEbus mapping VMEbus address VMEbus address modifiers VMEbus byte count Access type
*/ */ */ */ */ */
int vme_addr_t vme_atype_t unsigned int vme_addr_t unsigned int
dma_is_set; dma_vme_addr; dma_vme_am; dma_vme_size; dma_wrk_vme_addr; dma_wrk_vme_size;
/* /* /* /* /* /* /*
Strategy block mode DMA entries*/ Flag for DMA transfers */ DMA VMEbus address */ DMA VMEbus address modifiers */ DMA VMEbus byte count */ Updated DMA bus address */ Updated transfer byte count */
int int
strategy_xfer_mode; /* Strategy transfer mode mmap_mode; /* mmap mapping mode
io_handle_t vme_addr_t vme_atype_t unsigned int io_handle_t unsigned int
vme_handle; vme_addr; vme_am; vme_size; vme_wrk_handle; vme_wrk_size;
/* /* /* /* /* /* /*
Strategy PIO entries I/O handle for VMEbus mapping VMEbus address VMEbus address modifiers VMEbus byte count Updated VME io_handle_t Updated transfer byte count
dma_handle_t vme_addr_t vme_atype_t unsigned int caddr_t
sys_mem_dma_handle; sys_mem_vme_addr; sys_mem_vme_am; sys_mem_vme_size; sys_mem_vir_addr;
/* /* /* /* /*
Mapped system memory DMA handle*/ VMEbus address for sys memory */ VMEbus address modifiers */ VMEbus/system memory byte count*/ System memory virtual address */
vme_addr_t vme_atype_t unsigned int dma_handle_t caddr_t struct proc
blk_wrt_vme_addr; blk_wrt_vme_am; blk_wrt_vme_size; blk_wrt_dma_handle; blk_wrt_buf_ptr; *blk_wrt_proc_p;
/* /* /* /* /* /*
MBLT write VMEbus address MBLT write VMEbus addr mod MBLT write VMEbus byte count MBLT write DMA handle MBLT write memory buffer process pointer for write
*/ */ */ */ */ */
vme_addr_t vme_atype_t unsigned int dma_handle_t caddr_t struct proc
blk_rd_vme_addr; blk_rd_vme_am; blk_rd_vme_size; blk_rd_dma_handle; blk_rd_buf_ptr; *blk_rd_proc_p;
/* /* /* /* /* /*
MBLT read VMEbus address MBLT read VMEbus addr mod MBLT read VMEbus byte count MBLT read DMA handle MBLT read memory buffer process pointer for read
*/ */ */ */ */ */
ihandler_id_t *rcvisr_id_t; struct proc *rcvisr_proc_p; int rcvisr_irq; struct buf io_buf; } dmaex_softc[MAX_NDMAEX]; static int
*/ */ */ */ */ */ */ */ */
/* handler ID for VME receive ISI */ /* isr user task process pointer */ /* VMEbus irq for recive ISI */ /* read/write i/o request buffer
*/
dmaex_setup_blk_mode(struct controller *, struct dmaex_softc *, struct dmaex_ioctl_data *, int); 6
int dmaex_config = FALSE; 7 int dmaex_devno = NO_DEV; 8
1–8 VMEbus Device Driver Example
#define NINTS_PER_DMAEX 2 9 ihandler_id_t *dmaex_id_t[MAX_NDMAEX][NINTS_PER_DMAEX]; 10 int num_dmaex = 0; 11 int dmaex_is_dynamic = 0; 12 int dmaexcallback_return_status = ESUCCESS; 13 extern int nodev(); 14 extern int hz; 15 struct dsent dmaex_devsw_entry = { 16 dmaex_open, /* d_open */ dmaex_close, /* d_close */ dmaex_strategy, /* d_strategy */ dmaex_read, /* d_read */ dmaex_write, /* d_write */ dmaex_ioctl, /* d_ioctl */ nodev, /* d_dump */ nodev, /* d_psize */ nodev, /* d_stop */ nodev, /* d_reset */ nodev, /* d_select */ dmaex_mmap, /* d_mmap */ 0, /* d_segmap */ NULL, /* d_ttys */ DEV_FUNNEL_NULL, /* d_funnel */ 0, /* d_bflags */ 0, /* d_cflags */ };
1
2 3
4
Defines the DMAEXOPEN and DMAEXCLOSE constants to represent whether a specified dmaex device is open or closed in its softc structure. Forward-declares the dmaex driver’s callable interfaces. Declares an array of controller structure pointers. MAX_NDMAEX is used as the maximum number of controllers that this driver can support. Initializes the driver structure for the dmaex device. Fields initialized to a nonzero value include: •
dmaex_probe, the driver’s probe interface.
•
dmaex_cattach, the driver’s controller attach interface.
•
dmaex, the device controller name.
•
dmaex_ctrlr, a reference to the list of declared dmaex controller structures. You index this array with the controller number as specified in the ctlr_num member of the controller structure.
•
dmaex_ctlr_unattach, the driver’s controller unattach interface.
If you require the VMEbus autoconfiguration software to map a VMEbus address space into kernel space for the purpose of device register access, you must:
VMEbus Device Driver Example 1–9
•
Specify a VMEbus address in the Csr1 field of the VBA_Option entry in the sysconfigtab file fragment
•
Specify a memory allocation size and VMEbus address modifiers in the addr1_size and addr1_atype fields of the driver structure (specified in this example driver as 0x8 and VME_A24_UDATA_D32, respectively)
The VMEbus autoconfiguration software uses these combined entries to map the specified VMEbus address space to kernel address space. The result of this mapping will be an io_handle_t structure that the driver can use with the read_io_port and write_io_port interfaces to access the device. The io_handle_t for the Csr1 address will be passed to the driver’s probe interface and saved in the controller structure’s addr element (ctlr->addr). You can also request that the autoconfiguration software map a second VMEbus address space. You do this by specifying a second VMEbus address in the Csr2 field of VBA_Option in sysconfigtab and by specifying size and type values in the addr2_size and addr2_atype fields of the driver structure. The VMEbus autoconfiguration software performs the appropriate mapping and stores the resultant io_handle_t in the controller structure’s addr2 element. 5
Declares an array of dmaex_softc structures to allow the dmaex device driver’s interfaces to share data. Again the MAX_NDMAEX constant, representing the maximum number of controllers allowed, is used to size the array.
6
Forward-declares the dmaex_setup_blk_mode driver interface. Because of a dmaex_softc parameter dependency, this interface needs to be declared after the dmaex_softc structure; the other callable driver interfaces were declared near the beginning of the Declarations Section.
7
Initializes the dmaex_config variable, which indicates whether the driver currently is configured, to FALSE.
8
Initializes the dmaex_devno variable, which indicates the device major number, to NO_DEV, indicating a major number is not yet assigned.
9
Defines the NINTS_PER_DMAEX constant equal to 2, indicating that the (fictitious) dmaex device can present two unique interrupts to the system. The device driver must install two interrupt handlers per dmaex controller.
10
Declares an array of interrupt handler IDs that will be used to enable, disable, and deregister interrupt handlers. The maximum number of controllers (MAX_NDMAEX) and the number of interrupt handlers per controller (NINTS_PER_DMAEX) define the dimensions of the array.
1–10 VMEbus Device Driver Example
11
Declares the num_dmaex variable, which is used to count the number of controllers probed. The dmaex_probe interface increments this variable each time it probes a dmaex controller.
12
Declares the dmaex_is_dynamic variable, which is used to control (conditionalize) any differences in tasks performed by the dmaex driver based on whether it was statically or dynamically configured. Implementing a device driver to handle a static or dynamic configuration request with minimal impact to driver operation gives users maximum flexibility in how they configure the driver into the kernel.
13
Declares the dmaexcallback_return_status variable, which is used by the callback_register_major_number driver interface to determine if a previous failure has occurred in statically configuring the dmaex driver.
14
Externally declares the nodev function, which is required for the devsw declaration that follows.
15
Externally declares the hz variable, which is required for event_wait timeout functionality used in connection with interrupt handling. The value for hz is defined in param.c. Declares a dsent table entry for the dmaex driver, setting dsent structure members to appropriate values. Among the values filled in are the addresses of the driver’s open, close, strategy, read, write, ioctl, and mmap interfaces. The address of this structure is passed to the devsw_add interface, which registers the I/O services interfaces in the dsent table and reserves a major number for the dmaex driver.
16
1.4 Configure Section The following sections explain how to implement Configure Section declarations and interfaces: Providing Declarations Required for Configuration
Section 1.4.1
Implementing the dmaex_configure Interface
Section 1.4.2
1.4.1 Providing Declarations Required for Configuration The following code shows the declarations required for driver configuration: #define DMAEX_BUSNAME1 "vba" 1 static static static static
int int int int
NDMAEX = 1; 2 majnum = NO_DEV; begunit = 0; dmaex_version = 1;
VMEbus Device Driver Example 1–11
static static static static static static static static static static static static static static static
int dmaex_no_dev = 1; int dmaex_int_wait = 1; u_int dmaex_max_dma = (1024 * 1024); int dmaex_univ_adapter = 0; int dmaex_sw_byte_swap = 0; int dmaex_developer_debug = 0; char *dmaex_contig_buf = NULL; char *dmaex_contig_rd_buf = NULL; char *dmaex_contig_wrt_buf = NULL; unsigned char subsysname[CFG_ATTR_NAME_SZ] = ""; unsigned char mcfgname[CFG_ATTR_NAME_SZ] = ""; unsigned char devmajor[CFG_ATTR_NAME_SZ] = ""; unsigned char unused[300] = ""; unsigned char cma_dd[120] = ""; unsigned char vba_option[300] = "";
#define DMAEX_DEBUG 3 #ifdef DMAEX_DEBUG #define DMAEX_DBG1 if (dmaex_developer_debug & 0x01) printf #define DMAEX_DBG2 if (dmaex_developer_debug & 0x02) printf #else #define DMAEX_DBG1 ; #define DMAEX_DBG2 ; #endif cfg_subsys_attr_t dmaex_attributes[] = { 4 {"Subsystem_Description",CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)subsysname,0,CFG_ATTR_NAME_SZ,0}, {"Module_Config_Name", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)mcfgname,2,CFG_ATTR_NAME_SZ,0}, {"Device_Char_Major", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)devmajor,0,CFG_ATTR_NAME_SZ,0}, {"Device_Char_Minor", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)unused,0,300,0}, {"Device_Char_Files", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)unused,0,300,0}, {"Device_Major_Req", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)unused,0,300,0}, {"Device_User", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)unused,0,300,0}, {"Device_Group", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)unused,0,300,0}, {"Device_Mode", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)unused,0,300,0}, {"CMA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)cma_dd,0,120,0}, {"numunit", CFG_ATTR_INTTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)&NDMAEX,0,MAX_NDMAEX,0}, {"VBA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)vba_option,0,300,0}, {"majnum", CFG_ATTR_INTTYPE, CFG_OP_QUERY, 5 (caddr_t)&majnum,0,512,0}, {"begunit", CFG_ATTR_INTTYPE, CFG_OP_QUERY, (caddr_t)&begunit,0,8,0}, {"Dmaex_version", CFG_ATTR_INTTYPE, CFG_OP_QUERY, (caddr_t)&dmaex_version,0,9999999,0}, {"Dmaex_No_Dev", CFG_ATTR_INTTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)&dmaex_no_dev,0,1,0}, {"Dmaex_Int_Wait", CFG_ATTR_INTTYPE, CFG_OP_QUERY | CFG_OP_RECONFIGURE | CFG_OP_CONFIGURE, (caddr_t)&dmaex_int_wait,1,9999999,0}, {"Dmaex_Max_DMA", CFG_ATTR_UINTTYPE, CFG_OP_QUERY |
1–12 VMEbus Device Driver Example
{"Dmaex_Contig_Mem", {"Dmaex_Sw_Byte_Swap",
{"Dmaex_Univ_Adapter", {"Dmaex_Developer_Debug",
CFG_OP_RECONFIGURE | CFG_OP_CONFIGURE, (caddr_t)&dmaex_max_dma,8,0xFFFFFFFF,0}, CFG_ATTR_ULONGTYPE,CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)&dmaex_contig_buf,0,0xFFFFFFFFFFFFFFFF,0}, CFG_ATTR_INTTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY | CFG_OP_RECONFIGURE, (caddr_t)&dmaex_sw_byte_swap,0,1,0}, CFG_ATTR_INTTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY, (caddr_t)&dmaex_univ_adapter,0,1,0}, CFG_ATTR_INTTYPE, CFG_OP_QUERY | CFG_OP_RECONFIGURE | CFG_OP_CONFIGURE, (caddr_t)&dmaex_developer_debug,0,7,0},
{"",0,0,0,0,0,0} };
1
Defines the DMAEX_BUSNAME1 constant equal to vba. This constant is passed to the configure driver interface, which the dmaex driver’s dmaex_configure interface calls. All VMEbus device drivers must specify vba as the bus name.
2
Declares variables that are required by the cfgmgr framework’s device driver method. You use these variables as fields in the dmaex_attributes driver attributes table, which is declared later in the Configure Section. The NDMAEX variable, which represents the number of controllers to create for this driver, is initialized to 1. Another variable, MAX_NDMAEX, declared in the Include Files Section, represents the maximum number of controllers the driver can support (eight).
3
Sets up the dmaex driver’s DMAEX_DBG1 and DMAEX_DBG2 debugging macros, based on the value of the constant dmaex_developer_debug: •
0 requests no debug messages
•
1 requests level 1 debug messages only
•
2 requests level 2 debug messages only
•
3 requests both level 1 and 2 debug messages
The result is that DMAEX_DBG1 and DMAEX_DBG2 statements placed throughout the driver source code will display (printf) debug messages or not, according to the level of detail the driver developer requested. 4
Declares the dmaex_attributes driver attributes table, which is an array of cfg_subsys_attr_t structures. The cfgmgr framework uses the driver’s attributes table during static and dynamic configuration of a device driver into the kernel. The cfgmgr framework initializes the attributes table early in the kernel boot process and whenever it receives a sysconfig command request from either the kernel or the user command line.
VMEbus Device Driver Example 1–13
_____________________
Note
_____________________
There are a number of fields (attributes) present in the sysconfigtab file fragment that are not present in the device driver’s attributes table. The cfgmgr framework’s device driver method consumes these fields to perform kernel configuration work and to make device special files. To avoid warning messages from the cfgmgr framework, you should add these attributes to the driver’s attributes table. The cfgmgr framework’s device driver method fills in the elements in dmaex_attributes because the table entries specify operation type CFG_OP_CONFIGURE. The method reads the entries for the dmaex driver from the sysconfigtab database. (It is assumed that a sysconfigtab file fragment created for the dmaex driver, such as the one provided as an example on the Tru64 UNIX Device Driver Kit CD–ROM, has been appended by the sysconfigdb utility to the /etc/sysconfigtab database.) To determine if any of these element reads failed, the dmaex driver verifies each from the cfg_attr_t structure passed into the dmaex_configure interface. Several of the fields shown are used in the dmaex device driver, while other fields (marked with the value unused) are included to illustrate types of information that cfgmgr can load into the driver attributes table. These fields can represent tunable parameters to indicate whether the driver is statically or dynamically configured into the kernel. 5
Includes in the driver table several attributes (beginning with entry majnum) that the dmaex driver modifies during a configure or unconfigure operation. The cfgmgr framework’s device driver method uses these attributes to provide the device special files that device drivers need.
1.4.2 Implementing the dmaex_configure Interface The dmaex_configure interface is called indirectly by the cfgmgr framework, which is responsible for calling all single binary modules for registration and integration into the kernel. The cfgmgr framework requires that a single binary module has both an attributes table and a configure interface before the module can be registered within the cfgmgr framework and the Tru64 UNIX kernel. The dmaex driver defines the dmaex_attributes table and the dmaex_configure interface for this purpose.
1–14 VMEbus Device Driver Example
The dmaex_configure interface cooperates with the cfgmgr framework to complete configure, unconfigure, query, and other requests. The interface also provides code a device driver must supply to produce a single binary module, allowing the dmaex driver to be statically or dynamically configured into the kernel. The dmaex_configure interface takes five arguments: •
A value of type cfg_op_t that specifies the requested configure operation; valid values include CFG_OP_CONFIGURE, CFG_OP_UNCONFIGURE, CFG_OP_QUERY, and CFG_OP_RECONFIGURE
•
A pointer to a structure of type cfg_attr_t that provides configuration input data
•
The size (in bytes) of the input data structure
•
The configuration output data structure and size arguments, not used in this example driver
The dmaex_configure interface returns integer status, including the success code ESUCCESS and the failure codes EBUSY, EINVAL, ESRCH, and ENOTSUP. The following code shows the implementation of the dmaex_configure interface: int dmaex_configure(cfg_op_t op, cfg_attr_t *indata, size_t indatalen, cfg_attr_t *outdata, size_t outdatalen) { int retval, i; 1 int driver_cfg_state; switch (op) { 2 case CFG_OP_CONFIGURE: DMAEX_DBG1("dmaex_configure: CFG_OP_CONFIGURE.\n"); for( i = 0; i < indatalen; i++) 3 switch(indata[i].type) { case CFG_ATTR_STRTYPE: break; default: switch(indata[i].status) { case CFG_FRAME_SUCCESS: break; default: printf("%s:",indata[i].name); switch(indata[i].status) { case CFG_ATTR_EEXISTS: printf("**Attribute does not exist\n"); break;
VMEbus Device Driver Example 1–15
case CFG_ATTR_EOP: printf("**Attribute does not support operation\n"); break; case CFG_ATTR_ESUBSYS: printf("**Subsystem Failure\n"); break; case CFG_ATTR_ESMALL: printf("**Attribute size/value too small\n"); break; case CFG_ATTR_ELARGE: printf("**Attribute size/value too large\n"); break; case CFG_ATTR_ETYPE: printf("**Attribute invalid type\n"); break; case CFG_ATTR_EINDEX: printf("**Attribute invalid index\n"); break; case CFG_ATTR_EMEM: printf("**Attribute memory allocation error\n"); break; default: printf("**Unknown attribute: "); printf("%x\n", indata[i].status); } return(EINVAL); } break; } if(dmaex_config == TRUE) { 4 printf("dmaex_configure: already configured\n"); return(EINVAL); } if ( strcmp((caddr_t)mcfgname,"") == 0 ) { 5 strcpy((caddr_t)mcfgname,"dmaex"); } else { } if (cfgmgr_get_state((caddr_t)mcfgname, &driver_cfg_state) != ESUCCESS) { 6 printf("dmaex_configure: cfgmgr_get_state failed\n"); return(EINVAL); } if (driver_cfg_state == SUBSYSTEM_STATICALLY_CONFIGURED)
{
dmaexcallback_return_status = ESUCCESS; 7 dmaex_is_dynamic = SUBSYSTEM_STATICALLY_CONFIGURED; 8 register_callback( callback_register_configuration, 9 CFG_PT_PRECONFIG, CFG_ORD_NOMINAL, (long) 0 ); register_callback( callback_register_major_number, CFG_PT_POSTCONFIG, CFG_ORD_NOMINAL, (long) 0 ); } else { DMAEX_DBG1("dmaex_configure: CFG_OP_CONFIGURE - Dynamic loadable\n"); dmaex_is_dynamic = SUBSYSTEM_DYNAMICALLY_CONFIGURED; 10 retval = register_configuration(); 11 if (retval != ESUCCESS) { printf("dmaex_configure: register_configuration error status = 0x%x\n",
1–16 VMEbus Device Driver Example
retval); return(retval); } retval = configure_driver(mcfgname, DRIVER_WILDNUM, DMAEX_BUSNAME1, 12 &dmaexdriver); if (retval != ESUCCESS) { printf("dmaex_configure: configure_driver error status = 0x%x\n", retval); return(retval); } retval = register_major_number(); 13 if (retval != ESUCCESS) { printf("dmaex_configure: register_major_number error status = 0x%x\n", retval); return(retval); } } break; case CFG_OP_UNCONFIGURE: DMAEX_DBG1("dmaex_configure: CFG_OP_UNCONFIGURE.\n"); if(dmaex_is_dynamic == SUBSYSTEM_STATICALLY_CONFIGURED) { 14 return(ENOTSUP); } for (i = 0; i < num_dmaex; i++) { 15 if (dmaex_softc[i].sc_open != 0) { return(EBUSY); } } retval = devsw_del(mcfgname, MAJOR_INSTANCE); 16 if (retval == NO_DEV) { printf("dmaex_configure: devsw_del failed - error status 0x%x\n", retval); return(ESRCH); } DMAEX_DBG1("dmaex_configure: CFG_OP_UNCONFIGURE: devsw_del = %d\n",retval); retval = unconfigure_driver(DMAEX_BUSNAME1,DRIVER_WILDNUM,&dmaexdriver, 17 mcfgname,DRIVER_WILDNUM); if (retval != 0) { printf("dmaex_configure: unconfigure driver failed - status 0x%x\n", retval); return(ESRCH); } num_dmaex = 0; dmaex_is_dynamic = 0; dmaex_config = FALSE; break; case CFG_OP_QUERY: 18 DMAEX_DBG1("dmaex_configure: CFG_OP_QUERY.\n"); break; case CFG_OP_RECONFIGURE: 19
VMEbus Device Driver Example 1–17
DMAEX_DBG1("dmaex_configure: CFG_OP_RECONFIGURE.\n"); break; default: printf("**Unknown operation type\n"); return(ENOTSUP); } return(ESUCCESS); 20 }
1
2
3
Declares variables including i, used for loop control, and driver_cfg_state, which stores the configuration state (for example, static or dynamic). Dispatches control to operation-specific implementation code, based on the operation value provided in the op argument to the dmaex_configure interface. Configure operations implemented include CFG_OP_CONFIGURE, CFG_OP_UNCONFIGURE, CFG_OP_QUERY, and CFG_OP_RECONFIGURE. Begins processing a request to load the driver (CFG_OP_CONFIGURE). The first step is to traverse the dmaex attribute list passed to the dmaex_configure interface and verify the status of each attribute. Attributes passed through the input data structure (type cfg_attr_t) are not known to be valid until the dmaex driver can check their encoded status. Any status other than CFG_FRAME_SUCCESS represents an error condition. You can check the status field in each attribute string (structure type cfg_subsys_attr_t) to determine the cfgmgr status for that attribute, which might reflect success, a loading failure, or some other problem found by cfgmgr. You can display any errors detected or, alternatively, display the contents and status of every attribute residing in the sysconfigtab database for your device driver.
4
5
The dmaex driver checks each driver attribute and, if any error status is recorded, displays an error message and returns immediately from dmaex_configure with the EINVAL error status. Checks that the device driver has not previously been configured, either statically or dynamically. If it has, the interface displays an error message and returns the EINVAL error status to the cfgmgr framework. Establishes the device driver name that the cfgmgr framework will use to statically or dynamically configure the driver into the kernel. First the interface checks the value of the mcfgname attribute variable in sysconfigtab. If the module configuration name stored in mcfgname is not NULL, it is used as the device driver name. If the module configuration name stored in mcfgname is NULL, the interface copies the controller name (dmaex) stored in the driver
1–18 VMEbus Device Driver Example
structure’s ctlr_name member into mcfgname. (The driver structure for the dmaex driver was declared as structure dmaexdriver and initialized to appropriate values in the Declarations Section.) If an operator is interested in knowing the configuration name of the driver, you can set the corresponding attribute to CFG_OP_QUERY in the driver’s cfg_subsys_attr_t structure. The configuration name stored in the mcfgname variable is significant; later it is matched to the driver name provided in the Driver_Name field of a VBA_Option VMEbus configuration structure. This is the mechanism by which a hardware device is matched to its associated driver. The mcfgname variable is used in subsequent calls to driver-configuration interfaces. 6
Calls the cfgmgr_get_state interface to obtain the state of the dmaex driver subsystem, including whether it is statically or dynamically configured.
7
If the driver is being statically configured, sets the global flag dmaexcallback_return_status to the value ESUCCESS. During static configuration, cfgmgr subsystem callbacks scheduled to run in the boot path have no way to determine if previous callbacks worked and if dmaex configuration is still in good standing. The dmaexcallback_return_status global flag is checked in each of the callback jackets to determine if the dmaex driver subsystem should still be configured in the boot path.
8
If the driver is being statically configured, sets the dmaex_is_dynamic variable to indicate static configuration. This variable is used to control (conditionalize) any differences in tasks performed by the dmaex driver based on whether it is statically or dynamically configured.
9
If the driver is being statically configured, calls the register_callback interface to register two callback routines, callback_register_configuration and callback_register_major_number, with the cfgmgr framework. Once registered, the callback routines will be invoked at specific points in the configuration process. Section 1.4.2.3 and Section 1.4.2.4 describe the implementations of the callback_register_configuration and callback_register_major_number interfaces. During static configuration, the cfgmgr schedules configuration work to be done at pre- and postautoconfiguration, but cannot know in advance whether a subsystem will successfully configure into a kernel. Therefore, callback routines such as callback_register_configuration and callback_register_major_number are responsible for determining
VMEbus Device Driver Example 1–19
10
11
12
13
14
15
16
17
the status of a subsystem’s configuration in the boot path and for reporting failure to the cfgmgr framework, using the cfgmgr_set_state call. If the driver is being dynamically configured, sets the dmaex_is_dynamic variable to indicate dynamic configuration. This variable is used to control (conditionalize) any differences in tasks performed by the dmaex driver based on whether it is statically or dynamically configured. If the driver is being dynamically configured, calls the register_configuration driver interface to register the dmaex driver controller and device information. Section 1.4.2.1 describes the implementation of the register_configuration interface. If the driver is being dynamically configured, calls the configure_driver interface to merge the driver’s connectivity information into the system (hardware) configuration tree, which consists of bus, controller, and device structures. The call to configure_driver results in the system calling dmaex_probe for each instance of the dmaex controller persent in the system. The configure_driver interface is described in Writing Device Drivers: Reference. If the driver is being dynamically configured, calls the register_major_number driver interface to register the dmaex driver I/O services interface and receive a major number. Section 1.4.2.2 describes the implementation of the register_major_number interface. Begins processing a request to unload the driver (CFG_OP_UNCONFIGURE). The first step is to verify that the caller is not trying to unload a statically configured driver. If the driver is not currently dynamically configured, the interface returns to the caller with the ENOTSUP error status. Verifies that the caller is not trying to unload an active driver. If any users currently have the device open, the interface returns to the caller with the EBUSY error status. Calls the devsw_del interface to remove the driver entry points from the in-memory cdevsw table. You do this prior to deleting the loadable configuration and handlers to prevent users from accessing the device in the middle of an unconfigure operation. Calls the unconfigure_driver interface to deregister the driver’s configuration data structures from the hardware topology and to cause the interrupt handlers to be deleted. The unconfigure_driver interface is described in Writing Device Drivers: Reference. The bus number argument is wildcarded in order to deregister the driver on all instances of the vba bus. The controller number is
1–20 VMEbus Device Driver Example
wildcarded to cause all controller instances that match the specified driver structure to be deregistered. Through the bus-specific code, calling this interface results in a call to the dmaex_ctlr_unattach interface for each instance of the dmaex controller present in the system.
18
19
20
Note that while static drivers physically remain in the kernel, whether or not they are configured, dynamic drivers that you unconfigure are unloaded from the kernel. In response to a request to query the dmaex subsystem (CFG_OP_QUERY), the dmaex driver currently does nothing except return success status. In general, requests to query a loadable subsystem will succeed only if the CFG_OP_QUERY entry point returns success. In response to a request to reconfigure a loaded driver (CFG_OP_QUERY), the dmaex driver currently does nothing except return success status. Code to perform the tasks associated with an operator request to reconfigure the dmaex driver is not shown. If no dmaex_configure execution path returned with an error status, returns to the cfgmgr framework with a success status.
1.4.2.1 Implementing register_configuration The register_configuration interface is responsible for registering controller and device information during dmaex driver configuration. During dynamic configuration, the dmaex_configure driver interface directly calls register_configuration. During static configuration, the dmaex_configure interface calls the register_callback interface to register the register_configuration work for execution at a specific point later in the configuration process. Ultimately, the dmaex driver calls register_configuration through a register callback called callback_register_configuration. Writing an interface similar to register_configuration is the recommended method for specifying the controller and device information associated with a device driver. The register_configuration interface causes the appropriate controller and device structures to be created and integrated into the system (hardware) configuration tree for statically and dynamically configured drivers. Device driver writers can determine how much operator control will be allowed over the system’s topology creation support code. If appropriate, you can modify or add sysconfigtab database fields that allow an
VMEbus Device Driver Example 1–21
operator more or less control over the hardware-related information that this interface passes to the controller and device structures. The register_configuration interface takes no arguments and returns an integer status (ESUCCESS or ENOMEM). The following code shows the implementation of the register_configuration interface: static int register_configuration() { struct controller_config struct controller_config struct device_config struct device_config int
ctlr_register; 1 *ctlr_register_ptr = &ctlr_register; device_register; *device_register_ptr = &device_register; i,status;
DMAEX_DBG1("dmaex: register_configuration:\n"); for (i = 0; i < NDMAEX; i++) { 2 ctlr_register_ptr->revision = CTLR_CONFIG_REVISION; strcpy(ctlr_register_ptr->subsystem_name, (caddr_t)mcfgname); strcpy(ctlr_register_ptr->bus_name,DMAEX_BUSNAME1); ctlr_register_ptr->devdriver = &dmaexdriver; status = create_controller_struct(ctlr_register_ptr); if(status != ESUCCESS) { printf("register_configuration: create_controller_struct failed\n"); return (status); } /* device_register_ptr->revision = DEVICE_CONFIG_REVISION; 3 strcpy(device_register_ptr->device_type,"disk"); strcpy(device_register_ptr->device_name,"dmaexdev"); strcpy(device_register_ptr->controller_name,mcfgname); device_register_ptr->phys_unit_num = 0; device_register_ptr->logical_unit_number = 0; device_register_ptr->controller_num = i; status = create_device_struct(device_register_ptr); if (status != ESUCCESS) { printf("register_configuration: create_device_struct failed - 0x%x\n", status); return(status); } */ } dmaex_contig_buf = (char *)contig_malloc(PHYS_CONTIG_BUF_SIZE, 0x10000, 0, M_DEVBUF, M_ZERO | M_WAITOK); 4 if (!(dmaex_contig_buf)) printf("register_configuration: No physically contig memory allocated\n"); else { dmaex_contig_wrt_buf = dmaex_contig_buf; dmaex_contig_rd_buf = dmaex_contig_buf + CONTIG_RD_WRT_BUF_SIZE; } return(ESUCCESS); }
1
Declares controller_config and device_config structures and structure pointers. These structures are the storage mechanism for
1–22 VMEbus Device Driver Example
populating the controller and device structures associated with a specific device driver. 2
Registers a controller structure for the dmaex driver. To create the dmaex controller structure, the interface calls the create_controller_struct interface. The system manager can limit the number of controller structures to be created by modifying the attribute fields for the dmaex entry in the sysconfigtab database. In the register_configuration interface, you would use the NDMAEX attribute to regulate the number of controller structures to be created, up to the MAX_NDMAEX value (eight) defined by the driver.
3
If required, registers a device structure for the driver. To create the controller structure, the interface calls the create_device_struct interface. _____________________
Note
_____________________
The dmaex driver does not require a device structure, but the code is included here, commented out, for reference. 4
Requests a 128 KB physically contiguous buffer aligned to a 64 KB boundary. For purposes of supporting kernel-space buffer DMA transfers that use the VMEbus adapter’s DMA engine, this buffer will be divided in two equal-sized parts: a 64 KB read buffer and a 64 KB write buffer. (Section 1.13 describes their use.) Dynamically loaded drivers use the sysconfigtab file fragment’s CMA_Option entry to specify the size and alignment of the physically contiguous buffer. They use the contig_malloc interface to return a pointer to the physically contiguous buffer. Statically loaded drivers also use the contig_malloc interface to obtain a pointer to a physically contiguous buffer.
1.4.2.2 Implementing register_major_number The register_major_number interface calls the devsw_add interface to register a driver’s I/O services interfaces (and other information) and to receive a major number. During dynamic configuration, the dmaex_configure driver interface directly calls register_major_number. During static configuration, the dmaex_configure interface calls the register_callback interface to register the register_major_number work for execution at a specific point later in the configuration process.
VMEbus Device Driver Example 1–23
Ultimately, the dmaex driver calls register_major_number through a register callback called callback_register_major_number. The register_major_number interface takes no arguments and returns an integer status (ESUCCESS or ENODEV). The following code shows the implementation of the register_major_number interface: static int register_major_number() { DMAEX_DBG1("dmaex: register_major_num:\n"); if (num_dmaex == 0) { 1 printf("register_major_number: num_dmaex not updated by probe\n"); return (ENODEV); } majnum = devsw_add(mcfgname, MAJOR_INSTANCE, 2 majnum, &dmaex_devsw_entry); if (majnum == NO_DEV) { printf("register_major_num: no major number assigned\n"); return (ENODEV); } dmaex_devno = majnum; 3 begunit = 0; 4 dmaex_config = TRUE; 5 return(ESUCCESS); }
1
2
3 4 5
Verifies that a device is present in the system; if not, the interface returns immediately with the ENODEV error status. This verification addresses the situation where, for example, static configuration of the dmaex driver is initiated but fails at a point after callbacks are registered, resulting in an orphan callback to this interface. Calls the devsw_add interface to register the driver’s I/O services interfaces and other information and to reserve a major number in the device switch table. Each allocation provides a block and character entry. Stores the major number returned by the devsw_add interface so that it can be used later to unconfigure the device. Designates that the beginning minor number will be zero (0). Sets the dmaex_config variable to TRUE to indicate that the driver has been successfully configured.
1.4.2.3 Implementing callback_register_configuration The callback_register_configuration interface is the cfgmgr jacket routine called at a specific point during static configuration to register
1–24 VMEbus Device Driver Example
dmaex controller and device information. To accomplish its task, it calls the same register_configuration interface that the dmaex driver calls during dynamic configuration. For callback_register_configuration to be invoked, earlier in the static configuration process the dmaex_configure driver interface must have called the register_callback interface to register this jacket routine with the cfgmgr framework. The callback_register_configuration interface takes four arguments (supplied by the cfgmgr framework when it schedules the callback) and communicates completion status through the dmaexcallback_return_status global variable. The following code shows the implementation of the callback_register_configuration interface: static void callback_register_configuration(int point, int order, ulong args, ulong event_arg) { int status; DMAEX_DBG1("dmaex: callback_register_configuration:\n"); if(dmaexcallback_return_status != ESUCCESS) { 1 printf ("callback_register_configuration: dmaexcallback_return_status error\n"); return; } status
= register_configuration(); 2
if(status != ESUCCESS) { 3 printf ("callback_register_configiguration: register_configuration error\n"); cfgmgr_set_status((caddr_t)mcfgname); dmaexcallback_return_status = status; return; } }
1
Checks the dmaexcallback_return_status global variable to detect if a failure has occurred in the earlier stages of statically configuring this driver. If so, the interface exits the callback without doing any configuration work.
2
Calls the register_configuration driver interface to register this driver’s configuration. Section 1.4.2.1 describes the implementation of register_configuration.
3
If configuration registration was successful, returns from this callback. If an error occurred, the interface signals the cfgmgr framework that driver configuration failed and sets the callback_return_status
VMEbus Device Driver Example 1–25
global variable (checked by all dmaex configuration jacket routines) to the failure condition. _____________________
Note
_____________________
A side effect of calling the cfgmgr_set_status interface to signal configuration failure is that the dmaex_configure driver interface is called with an unconfigure operation requested. The dmaex_configure interface must be prepared for this event.
1.4.2.4 Implementing callback_register_major_number The callback_register_major_number interface is the cfgmgr jacket routine called at a specific point during static configuration to register the dmaex I/O services interfaces and to obtain a major number. To accomplish its task, it calls the same register_major_number interface the dmaex driver calls during dynamic configuration. For callback_register_major_number to be invoked, earlier in the static configuration process the dmaex_configure driver interface must have called the register_callback interface to register this jacket routine with the cfgmgr framework. The callback_register_major_number interface takes four arguments (supplied by the cfgmgr framework when it schedules the callback) and communicates completion status through the dmaexcallback_return_status global variable. The following code shows the implementation of the callback_register_major_number interface: static void callback_register_major_number(int int ulong ulong { int status;
point, order, args, event_arg)
DMAEX_DBG1("dmaex: callback_register_major_number:\n"); if(dmaexcallback_return_status != ESUCCESS) { 1 printf ("callback_register_major_number: dmaexcallback_return_status error\n"); return; } status
= register_major_number(); 2
if(status != ESUCCESS) { 3 printf("callback_register_major_number: register_major_number error \n");
1–26 VMEbus Device Driver Example
cfgmgr_set_status((caddr_t)mcfgname); dmaexcallback_return_status = status; return; } }
1
2
3
Checks the dmaexcallback_return_status global variable to detect if a failure has occurred in the earlier stages of statically configuring this driver. If so, the interface exits the callback without doing any configuration work. Calls the register_major_number driver interface to register this driver’s I/O service interface and receive a major number. Section 1.4.2.2 describes the implementation of register_major_number. If configuration registration was successful, returns from this callback. If an error occurred, the interface signals the cfgmgr framework that driver configuration failed and sets the callback_return_status global variable (checked by all dmaex configuration jacket routines) to the failure condition. _____________________
Note
_____________________
A side effect of calling the cfgmgr_set_status interface to signal configuration failure is that the dmaex_configure driver interface is called with an unconfigure operation requested. The dmaex_configure interface must be prepared for this event.
1.5 Autoconfiguration Support Section The following sections explain how to implement Autoconfiguration Support Section interfaces: Implementing the dmaex_probe Interface
Section 1.5.1
Implementing the dmaex_cattach Interface
Section 1.5.2
Implementing the dmaex_ctlr_unattach Interface
Section 1.5.3
1.5.1 Implementing the dmaex_probe Interface The dmaex_probe interface is called by the bus configuration interface during dynamic and static configuration to determine whether the device being configured is present. The dmaex_probe interface calls the vba_badaddr interface (Tru64 UNIX Version 4.0F or later) or the BADADDR interface (versions before 4.0F) to
VMEbus Device Driver Example 1–27
determine if the device is present. If the device is not present, the interface returns an error. If the device is present, dmaex_probe registers the driver’s interrupt service interfaces. The dmaex_probe interface takes two arguments: •
The base I/O handle (of type io_handle_t) for the dmaex device to be probed
•
A pointer to the controller structure for the dmaex device to be probed
The interface returns the value 1 for success or the value zero (0) for failure. The following code shows the implementation of the dmaex_probe interface: static int dmaex_probe(io_handle_t addr, struct controller *ctlr) { ihandler_t struct vme_handler_info int char u_long caddr_t register struct dmaex_softc
handler; 1 info; unit,i; csr; phys_addr; sva; *sc = &dmaex_softc[ctlr->ctlr_num];
unit = ctlr->ctlr_num; DMAEX_DBG1("dmaex_probe: dmaex%d\n",unit); sc->csr_handle = (io_handle_t)addr; #ifdef VBA_TYPE_MSK 2 if ((vba_get_info(ctlr) & VBA_BYTE_SWAP_MSK) == VBA_NO_HW_BYTE_SWAP) 3 dmaex_sw_byte_swap = 1; 4 if ((vba_get_info(ctlr) & VBA_TYPE_MSK) == VBA_TYPE_UNIV) 5 dmaex_univ_adapter = 1; else dmaex_univ_adapter = 0; #endif if (!dmaex_no_dev) { 6 #ifdef VBA_TYPE_MSK /**************************************************** * If the device driver is written for or migrating * * to UNIX V4.0F or greater and will never run on * * an earlier UNIX release, the preferred method * * for bus probing is shown below. The new * * interface, vba_badaddr was added to UNIX V4.0F. * ****************************************************/ if (vba_badaddr(ctlr, 7
1–28 VMEbus Device Driver Example
sc->csr_handle + DMAEX_CSR_OFF, ctlr->driver->addr1_atype, DMAEX_CSR_SIZE)) return(0); #else /**************************************************** * Device drivers that need to be compatible with * * UNIX version V4.0F and earlier releases must * * still use the older method of setting up and * * probing the bus. This method is shown below. * ****************************************************/ if ( (!ctlr->driver->addr1_atype & VME_DENSE) && (!dmaex_univ_adapter) ) 8 phys_addr = iohandle_to_phys(sc->csr_handle + DMAEX_CSR_OFF, HANDLE_BYTE | HANDLE_SPARSE_SPACE); else phys_addr = iohandle_to_phys(sc->csr_handle + DMAEX_CSR_OFF, HANDLE_LONGWORD | HANDLE_DENSE_SPACE); sva = (caddr_t)PHYS_TO_KSEG((vm_offset_t)phys_addr); if (BADADDR(sva, DMAEX_CSR_SIZE, ctlr)) 9 return(0); #endif write_io_port (sc->csr_handle + DMAEX_CSR_OFF, DMAEX_CSR_SIZE, 0, RESET); 10 mb(); csr = (char) read_io_port (sc->csr_handle + DMAEX_CSR_OFF, 11 DMAEX_CSR_SIZE, 0); if (csr & ERROR) return(0); write_io_port (sc->csr_handle + DMAEX_CSR_OFF, 12 DMAEX_CSR_SIZE, 0, 0); mb(); } info.gen_intr_info.configuration_st = (caddr_t) ctlr; 13 info.gen_intr_info.intr = dmaex_intr1; info.gen_intr_info.param = (caddr_t) unit; info.gen_intr_info.config_type = CONTROLLER_CONFIG_TYPE; info.vec = ctlr->ivnum; info.irq = ctlr->bus_priority; handler.ih_bus = ctlr->bus_hd; handler.ih_bus_info = (char *) &info; dmaex_id_t[unit][0] = handler_add (&handler); 14 if (dmaex_id_t[unit][0] == (ihandler_id_t *) NULL) { return (0); } if (handler_enable (dmaex_id_t[unit][0]) != 0) { 15 handler_del (dmaex_id_t[unit][0]); return (0); } info.gen_intr_info.intr = dmaex_intr2; 16 info.vec = ctlr->ivnum + 1; dmaex_id_t[unit][1] = handler_add (&handler); if (dmaex_id_t[unit][1] == (ihandler_id_t *) NULL) {
VMEbus Device Driver Example 1–29
handler_disable(dmaex_id_t[unit][0]); handler_del(dmaex_id_t[unit][0]); return (0); } if (handler_enable (dmaex_id_t[unit][1]) != 0) { handler_del (dmaex_id_t[unit][1]); handler_disable(dmaex_id_t[unit][0]); handler_del(dmaex_id_t[unit][0]); return (0); } event_init( (event_t *)&sc->isi_event); 17 event_clear( (event_t *)&sc->isi_event); for (i = 1; i < 8; i++) { 18 event_init( (event_t *)&sc->post_iack_event[i]); event_clear( (event_t *)&sc->post_iack_event[i]); } num_dmaex++; 19 return (1); 20 }
1
2
Declares variables for the probe operation, including: •
An ihandler_t structure to represent device driver interrupt handling information
•
A vme_handler_info structure to represent interrupt handler information
•
A unit variable to store the unit number for this dmaex device, which is obtained from the ctlr_num field of the device’s controller structure
•
A CSR variable to receive the return value from the interface read_io_port
•
A pointer to the dmaex_softc structure for this device, referenced using the device unit number
Tests whether the VBA_TYPE_MSK flag is defined, indicating whether VMEbus adapter type checking support is present in the system. If adapter type checking is present, the interface will set platform-specific flag values. Beginning with Tru64 UNIX Version 4.0F, the VBA_TYPE_MASK flag is defined in vbareg.h and indicates that the vba_get_info kernel interface can be called to return information about the VMEbus adapter type and capabilities. (It also indicates the availability of a bus-probing interface introduced in Tru64 UNIX Version 4.0F, vba_badaddr.)
3
Calls the vba_get_info interface and checks the return value for hardware byte swap capabilities.
1–30 VMEbus Device Driver Example
The vba_get_info interface takes one argument, the device’s controller information structure. The return value contains flags, defined in vbareg.h, that indicate the following: •
The adapter type (VBA_TYPE_VIPVIC, VBA_TYPE_UNIV, VBA_TYPE_DWP64, VBA_TYPE_DWPVC, or VBA_TYPE_TC)
•
Whether the adapter supports hardware byte swapping (VBA_HW_BYTE_SWAP or VBA_NO_HW_BYTE_SWAP)
• 4
5
6
7
Whether the platform is byte/word I/O capable (VBA_BW_CAPABLE or VBA_NOT_BW_CAPABLE) Sets the sysconfigtab parameter dmaex_sw_byte_swap to indicate to driver or user code that the system’s VMEbus adapter does not support hardware byte swapping and that software byte swapping may be needed before sending and after receiving data. Software byte swapping will be performed whenever a byte swap mode other than VME_BS_NOSWAP is specified for a data transfer. Calls the vba_get_info interface, described above, to determine the VMEbus adapter type. The interface then sets the sysconfigtab parameter dmaex_univ_adapter to indicate to driver or user code whether or not the adapter is the UNIVERSE II. If the adapter type is UNIVERSE II, driver code will ensure that all mapping to the VMEbus is through PCI dense space, regardless of the sparse/dense setting specified in the vba_map_csr request. The dmaex_no_dev flag indicates whether the dmaex example driver is controlling a fictitious device (TRUE) or a hardware device (FALSE). The dmaex driver controls a fictitious device. If the dmaex_no_dev flag is set, there is no hardware device, so the dmaex_probe interface bypasses all accesses to the device’s control and status hardware registers. For Version 4.0F or later of Tru64 UNIX, calls the vba_badaddr interface to determine if the device is present. The interface returns zero (0) if the device is present or a nonzero value if the device is not present. If the vba_badaddr return indicates the device is not present, the dmaex_probe interface returns a failure status (0) to the bus configuration interface. The vba_badaddr interface takes four arguments: •
The device’s controller information structure.
•
The I/O handle for the dmaex device being probed; must have a valid byte alignment that corresponds to the size (fourth) parameter.
•
VMEbus address modifier flags for the address to be probed.
VMEbus Device Driver Example 1–31
•
The size of the transfer in bytes, which determines the type of transfer to the VMEbus. Values of 1, 2, 4, and 8 results in byte, word, long, and quadword transfers respectively. If the I/O handle was mapped with the VME_DENSE flag set, only long (4) and quadword (8) transfers will be performed.
If the VMEbus adapter is the Universe II, all I/O handles are mapped to PCI DENSE space regardless of the VME_DENSE flag setting. The Universe II requires that the processor be byte/word I/O capable. On the UNIVERSE II, all I/O transactions to the VMEbus will be through the processor’s byte/word I/O memory space. This allows byte, word, long, and quadword transactions to be performed to the VMEbus. 8
If the driver must be compatible with versions of UNIX before Version 4.0F, constructs the system virtual address required for BADADDR. The interface calls the iohandle_to_phys interface to convert the I/O handle for the dmaex device being probed to a valid system physical address. The I/O handle is mapped to PCI sparse or dense space as appropriate unless the VMEbus adapter is the UNIVERSE II; for UNIVERSE II the I/O handle is always mapped to PCI dense space. The resulting system physical address is then converted to a system virtual address, using the PHYS_TO_KSEG interface.
9
Calls the BADADDR interface, passing the system virtual address obtained in the previous step, to determine if the device is present. BADADDR returns zero (0) if the device is present or a nonzero value if the device is not present. If the BADADDR return indicates the device is not present, the dmaex_probe interface returns a failure status (0) to the bus configuration interface.
10
If a hardware device is present, calls the write_io_port interface to write a byte to the VMEbus that causes the device to reset. The dmaex_probe interface then performs a memory barrier (mb), which flushes the write buffer, to ensure sequential writes to I/O space. You should perform memory barriers after each write to I/O space. Section 4.3 of the driver kit manual Writing VMEbus Device Drivers provides more information about the write_io_port interface and its arguments.
11
If a hardware device is present, calls the read_io_port interface to read the byte back from the VMEbus. The dmaex_probe interface then performs a bitwise AND on the value returned to the csr variable to test for a set error bit. If an error bit is set, the device is not responding as expected, so the dmaex_probe interface returns a failure status (0) to the bus configuration interface.
1–32 VMEbus Device Driver Example
12
13
14
15
16
17
18
Section 4.3 of the driver kit manual Writing VMEbus Device Drivers provides more information about the read_io_port interface and its arguments. If a hardware device is present, calls the write_io_port interface to write a byte to the VMEbus that causes the CSR to be cleared. Again, a memory barrier is performed to ensure sequential writes to I/O space. In preparation for calls to the handler_add and handler_enable interfaces to register and enable the dmaex driver’s dmaex_intr1 interrupt service interface, initializes several fields in the device’s vme_handler_info structure. These include fields for the device’s controller structure pointer, name of the driver interrupt interface to be registered (dmaex_intr1), device unit number, and the VMEbus interrupt vector and priority level to be established (from the ivnum and bus_priority fields of the device’s controller structure). A pointer to the device’s vme_handler_info structure is placed in the ih_bus_info field of the device’s ihandler_t structure, which will be passed to handler_add. Registers the driver’s dmaex_intr1 interrupt service interface by calling handler_add. The handler_add interface takes one argument: the ihandler_t structure prepared in the previous step. The interface returns a pointer to an initialized handler ID (type ihandler_id_t *). Enables the driver’s dmaex_intr1 interrupt service interface by calling handler_enable. The handler_enable interface takes one argument: the ihandler_id_t * structure pointer returned by the handler_add interface in the previous step. The interface returns success (0) or failure (–1) status. Repeats the handler_add and handler_enable sequence for a second interrupt service interface, dmaex_intr2. Generally, the next VMEbus interrupt vector is sequentially incremented from what is provided in the ivnum field of the device’s controller structure. Calls the event_init and event_clear interfaces to initialize and clear an event for the device to signal when an interrupt has been received. Section 4.6 of the driver kit manual Writing VMEbus Device Drivers describes the event_init and event_clear interfaces. Calls event_init and event_clear to initialize and clear events to be used by the ioctl interface (VME_POST_IRQ command) and another interrupt service interface, dmaex_iack_isr, when posting VMEbus interrupts. Section 1.14 describes the use of these events. Because interrupts can be posted on VMEbus IRQs 1 through 7, an event is initialized and cleared for each IRQ. Section 4.6 of the driver
VMEbus Device Driver Example 1–33
19
20
kit manual Writing VMEbus Device Drivers describes the event_init and event_clear interfaces. Increments the number of dmaex devices successfully probed. The num_dmaex variable is checked by other interfaces to verify that dmaex devices are present and running in the system — for example, by static configuration callback routines that should execute their tasks only if dmaex devices are present and running. Returns the value 1 to the bus configuration interface, indicating a successful probe of the dmaex device.
1.5.2 Implementing the dmaex_cattach Interface The dmaex_cattach interface is called by the bus configuration interface during dynamic and static configuration. This interface is usually used to perform tasks necessary to establish communication with an actual hardware device. Because the dmaex driver controls a fictitious device, the dmaex_cattach interface does not currently perform any tasks, other than to return to the bus configuration interface with success. The interface is provided in the driver source as a stub for future development. The dmaex_cattach interface takes one argument: a pointer to the controller structure for the device. The interface returns an integer status. The following code shows the current stub implementation of the dmaex_cattach interface: static int dmaex_cattach(struct controller *ctlr) { struct dmaex_softc *sc = &dmaex_softc[ctlr->ctlr_num]; DMAEX_DBG1("dmaex_cattach: dmaex%d\n",ctlr->ctlr_num); return(1); }
1.5.3 Implementing the dmaex_ctlr_unattach Interface The dmaex_ctlr_unattach interface is called directly from the bus configuration code’s controller unconfigure interface when a driver is being unloaded. Statically configured device drivers cannot be unloaded. If the user tries to unload a statically configured driver, the interface returns the ENODEV error status to the bus configuration interface. Dynamically loaded drivers must deregister interrupt handlers, return physical memory allocated with contig_malloc back to the system,
1–34 VMEbus Device Driver Example
unwire memory that may have been wired down by vm_map_pageable, unmap VMEbus programmed I/O space, unmap VMEbus DMA resources, and return any other system resources back to the system. The dmaex_ctlr_unattach interface takes two arguments: pointers to the dmaex driver’s bus and controller structures. The interface returns an integer completion status (ESUCCESS or ENODEV) to the bus configuration interface. The following code shows the implementation of the dmaex_ctlr_unattach interface: static int dmaex_ctlr_unattach(struct struct { struct dmaex_softc *sc register int unit int i;
bus *bus, controller *ctlr) = &dmaex_softc[ctlr->ctlr_num]; = ctlr->ctlr_num;
DMAEX_DBG1("dmaex_ctlr_unattach: dmaex%d\n",unit); if ((unit > num_dmaex) || (unit < 0)) { 1 printf("dmaex_ctlr_unattach: unit %d not valid\n",unit); return(ENODEV); } if (dmaex_is_dynamic == SUBSYSTEM_STATICALLY_CONFIGURED) { 2 printf("dmaex_ctlr_unattach: statically configured driver\n"); return(ENODEV); } for (i = 0; i < NINTS_PER_DMAEX; i++) { 3 if (dmaex_id_t[unit][i]) { if (handler_disable(dmaex_id_t[unit][i]) != 0) { printf("dmaex_ctlr_unattach: dmaex%d handler_disable failure\n",unit); return(ENODEV); } if (handler_del(dmaex_id_t[unit][i]) != 0) { printf("dmaex_ctlr_unattach: dmaex%d handler_del failure\n",unit); return(ENODEV); } } else { printf("dmaex_ctlr_unattach: dmaex[%d][%d] no interrupt handler\n", unit,i); } } if (sc->rcvisr_id_t) { 4 if (handler_disable(sc->rcvisr_id_t) != 0) { printf("dmaex_ctlr_unattach: dmaex%d handler_disable failed\n",unit); return(ENODEV); } else { if (handler_del(sc->rcvisr_id_t) != 0) { printf("dmaex_ctlr_unatttach: dmaex%d handler_del failed\n",unit); return(ENODEV); } else { sc->rcvisr_irq = 0; sc->rcvisr_id_t = (ihandler_id_t *)NULL; sc->rcvisr_proc_p = (struct proc *)NULL; }
VMEbus Device Driver Example 1–35
} } if (dmaex_contig_buf) { 5 contig_free(dmaex_contig_buf,PHYS_CONTIG_BUF_SIZE,M_DEVBUF); dmaex_contig_buf = NULL; } if (sc->pio_handle) { 6 vba_unmap_csr(ctlr,sc->pio_handle); sc->pio_handle = (io_handle_t)0; sc->pio_vme_addr = 0; sc->pio_vme_am = 0; sc->pio_vme_size = 0; sc->pio_access_type = 0; } if (sc->vme_handle) { 7 vba_unmap_csr(ctlr,sc->vme_handle); sc->vme_handle = (io_handle_t)0; sc->vme_addr = 0; sc->vme_am = 0; sc->vme_size = 0; sc->vme_wrk_handle = (io_handle_t)0; sc->vme_wrk_size = 0; } if (sc->dma_handle) { 8 dma_map_unload (DMA_DEALLOC, sc->dma_handle); sc->dma_handle = (dma_handle_t) NULL; } if (sc->sys_mem_dma_handle) { dma_map_unload (DMA_DEALLOC, sc->sys_mem_dma_handle); sc->sys_mem_dma_handle = (dma_handle_t) NULL; vm_map_pageable(current_task()->map, trunc_page(sc->sys_mem_vir_addr), round_page(sc->sys_mem_vir_addr + sc->sys_mem_vme_size), VM_PROT_NONE); } if (sc->blk_wrt_dma_handle) { 9 dma_map_unload(DMA_DEALLOC, sc->blk_wrt_dma_handle); } if (sc->blk_rd_dma_handle) { dma_map_unload(DMA_DEALLOC, sc->blk_rd_dma_handle); } event_terminate( (event_t *)&sc->isi_event); 10 for (i = 1; i < 8; i++) event_terminate( (event_t *)&sc->post_iack_event[i]); return(ESUCCESS); 11 }
1
Validates the dmaex controller unit number, which is obtained from the ctlr_num field of the dmaex controller structure. If it is not in the range 0 to num_dmaex (number of configured dmaex controllers), the interface displays an error message and returns the ENODEV error status to the bus configuration interface.
1–36 VMEbus Device Driver Example
2
Verifies that the dmaex driver being unloaded was dynamically configured. (Statically configured drivers cannot be unloaded.) If the interface detects that the dmaex driver was statically configured, it displays an error message and returns the ENODEV error status to the bus configuration interface.
3
Deregisters each dmaex interrupt handler by first calling the handler_disable interface to disable any further interrupts and then calling the handler_del interface to remove the interrupt handler. The number of interrupt handlers to deregister is contained in the NINTS_PER_DMAEX variable.
4
Removes the dmaex_rcv_int_srv interrupt handler by calling the handler_disable and handler_del interfaces. This interrupt handler is installed when user code invokes the SET_INT_HANDLER ioctl command to set up device input to wired memory with signaling. Section 1.11.2 describes the use of this interrupt handler.
5
If a 128 KB physically contiguous buffer was preallocated by the driver’s register_configuration interface, calls the contig_free interface to return the memory to the system’s physical memory resource pool. The buffer is created to support kernel-space buffer DMA transfers that use the VMEbus adapter’s DMA engine. Section 1.13 describes the use of this buffer.
6
If VMEbus-to-processor I/O space mapping was performed to support the use of mmap from user space, returns the associated VMEbus system resources.
7
If VMEbus-to-processor I/O space mapping was performed to support file system read or write data transfers, returns the associated VMEbus system resources.
8
Frees VMEbus DMA resources that still may be mapped from the VMEbus to the system’s memory by calling the dma_map_unload interface and, to unwire the memory associated with the mapping, the vm_map_pageable interface.
9
Frees DMA block mode write resources that still may be mapped for the VMEbus adapter’s hardware DMA engine by calling the dma_map_unload interface.
10
Releases system resources used by the device’s interrupt service interfaces by calling the event_terminate interface. Section 4.6 of the driver kit manual Writing VMEbus Device Drivers describes the event_terminate interface.
11
Returns the ESUCCESS completion status to the bus configuration interface.
VMEbus Device Driver Example 1–37
1.6 Open and Close Device Section The following sections explain how to implement Open and Close Device Section interfaces: Implementing the dmaex_open Interface
Section 1.6.1
Implementing the dmaex_close Interface
Section 1.6.2
1.6.1 Implementing the dmaex_open Interface The file system invokes the dmaex_open interface whenever user code performs an open operation on the dmaex device. The interface validates that the device exists and is not currently opened by another process, and sets a flag that marks the device as currently opened. The dmaex_open interface takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
An integer flags argument that contains flag values defined in the flag.h header file (ignored by this interface)
•
An integer format argument that contains special-device formatting information (ignored by this interface)
The dmaex_open interface returns an integer completion status (ESUCCESS, ENODEV, EBUSY, or ENXIO) to the file system. The following code shows the implementation of the dmaex_open interface: static int dmaex_open(dev_t dev, int flag, int format) { register int unit = minor(dev); 1 register struct controller *ctlr; register struct dmaex_softc *sc; DMAEX_DBG1("dmaex_open: dmaex%d\n",unit); if ((unit >= NDMAEX ) || (unit < 0)) 2 return (EIO); sc = &dmaex_softc[unit]; 3 if (sc == (struct dmaex_softc *)NULL) 4 return (ENODEV); if (sc->sc_open == DMAEXOPEN) return (EBUSY);
1–38 VMEbus Device Driver Example
ctlr = dmaex_ctlr[unit]; 5 if ((ctlr) && (ctlr->alive & ALV_ALIVE)) { 6 sc->sc_open = DMAEXOPEN; return(ESUCCESS); } else return(ENXIO); }
1
Declares and initializes device variables, including: •
The device minor number, which is initialized by invoking the minor interface and passing it the dev value that the file system supplied in the dmaex_open call. The device minor number is stored in the unit variable.
•
A pointer to this dmaex device’s controller structure.
•
A pointer to this dmaex device’s dmaex_softc structure.
2
Verifies that the device unit number is in the range 0 through NDMAEX, where NDMAEX is the user-modifiable value for the maximum number of dmaex controllers configured in the system. If not, the interface returns the EIO error status to the file system. Note that the NDMAEX value may not match the driver-defined value for the maximum number of dmaex controllers supported, MAX_NDMAEX (eight).
3
Initializes a pointer to the address of the dmaex_softc structure associated with this dmaex device, referenced using the verified unit number.
4
Tests the device’s dmaex_softc structure to establish that the device exists in the system and is not already opened. If the device is found not to exist in the system, the interface returns the ENODEV error status to the file system. If the device is already opened, the interface returns EBUSY.
5
Initializes a pointer to the address of the controller structure associated with this dmaex device, referenced using the verified unit number.
6
Tests the device’s controller structure to establish that the device is initialized and ready for I/O requests. If the device is found not to be initialized, the interface returns the ENXIO error status to the file system. If the device is initialized and ready, the interface sets the sc_open field of the dmaex_softc structure to DMAEXOPEN to mark the device as currently opened, and returns the ESUCCESS completion status to the file system to indicate a successful open of the dmaex device.
VMEbus Device Driver Example 1–39
1.6.2 Implementing the dmaex_close Interface The file system invokes the dmaex_close interface whenever user code performs a close operation on the dmaex device. The interface clears the flag that marks the device as currently opened so that other processes can use the device. The dmaex_close interface takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
An integer flags argument that contains flag values defined in the flag.h header file (ignored by this interface)
•
An integer format argument that contains special-device formatting information (ignored by this interface)
The dmaex_close interface returns an integer completion status (ESUCCESS or ENODEV) to the file system. The following code shows the implementation of the dmaex_close interface: static int dmaex_close(dev_t int int {
dev, flag, format)
register int unit = minor(dev); 1 register struct controller *ctlr; register struct dmaex_softc *sc; DMAEX_DBG1("dmaex_close: dmaex%d\n",unit); sc = &dmaex_softc[unit]; 2 sc->sc_open = DMAEXCLOSE; 3 ctlr = dmaex_ctlr[unit]; 4 if (!(ctlr)) return (ENODEV); if (sc->csr_handle == (io_handle_t)0) 5 return (ENODEV); if (!dmaex_no_dev) { 6 write_io_port (sc->csr_handle + DMAEX_CSR_OFF, DMAEX_CSR_SIZE, 0, 0); mb(); } return(ESUCCESS); 7 }
1
Declares and initializes device variables, including:
1–40 VMEbus Device Driver Example
•
The device minor number, initialized by invoking the minor interface and passing it the dev value that the file system supplied in the dmaex_close call. The device minor number is stored in the unit variable.
•
A pointer to this dmaex device’s controller structure.
•
A pointer to this dmaex device’s dmaex_softc structure.
2
Initializes a pointer to the address of the dmaex_softc structure associated with this dmaex device, referenced using the device unit number.
3
Sets the sc_open field of the dmaex_softc structure to DMAEXCLOSE to mark the device as currently closed, allowing other processes to open and use the device.
4
Initializes a pointer to the address of the controller structure associated with this dmaex device, referenced using the device unit number.
5
Verifies that the dmaex device exists in the system by testing both the controller pointer value and the csr_handle field of the device’s dmaex_softc structure. (Note that the latter test assumes that the driver writer requested the configuration software to map VMEbus address space for device register access.) If the device is found not to exist in the system, the interface returns the ENODEV error status to the file system.
6
If the dmaex driver is controlling a hardware device (versus a fictitious device), calls the write_io_port interface to write a byte to the VMEbus that causes the CSR to be cleared. The interface then performs a memory barrier (mb), which flushes the write buffer, to ensure sequential writes to I/O space. You should perform memory barriers after each write to I/O space. Section 4.3 of the driver kit manual Writing VMEbus Device Drivers provides more information about the write_io_port interface and its arguments. The dmaex_no_dev flag indicates whether the dmaex example driver is controlling a fictitious device (TRUE) or a hardware device (FALSE). The dmaex driver controls a fictitious device. If the dmaex_no_dev flag is set, there is no hardware device, so the dmaex_probe interface bypasses all accesses to the device’s control and status hardware registers.
7
Returns the ESUCCESS completion status to the file system to indicate a successful close of the dmaex device.
VMEbus Device Driver Example 1–41
1.7 Programming User-Mode I/O to a Memory-Mapped VMEbus Window Programming user-mode I/O to a memory-mapped VMEbus window involves first coding device driver support for calls to the ioctl and mmap interfaces, invoking those interfaces from user code, and finally initiating the transfers using one of the following: •
read/write_sparse macros (sparse space)
•
=* operations (integer or quadword transfers in dense/linear space)
•
stb/ldbu/stw/ldwu macros (byte or word transfers in linear space)
Compaq provides driver and user code templates that you can use as a starting point for implementing user mode I/O to a memory-mapped VMEbus window for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design. The example user code for implementing user mode I/O to a memory-mapped VMEbus window performs the following steps: 1.
Calls the kernel-mode driver’s ioctl interface to map outbound, specifying a VMEbus address, address modifiers, and the number of bytes to map for the programmed I/O transfer.
2.
Calls the driver’s ioctl interface to set the mmap mode to MMAP_VME_TO_U_MEM, indicating a VMEbus-to-user address space mapping.
3.
Calls the Tru64 UNIX mmap interface to map the VMEbus address space into the user address space for programmed I/O.
4.
Depending on whether the test caller specified sparse, dense, or linear byte/word memory access, uses C macros to perform byte, word, and integer transfers to sparse space, uses =* operations to perform integer and quadword transfers to dense space, or uses a combination of C macros and =* operations to perform byte, word, integer, and quadword accesses to linear space.
The following sections explain the user and driver code required for user-mode I/O to a memory-mapped VMEbus window:
1–42 VMEbus Device Driver Example
User-level code
Section 1.7.1
I/O Control (ioctl) Section
Section 1.7.2
Memory Map (mmap) Section
Section 1.7.3
1.7.1 User-Level Code The following subsections explain the user-level code required to program user-mode I/O to a memory-mapped VMEbus window: User Code for Sparse or Dense Access
Section 1.7.1.1
User Code for Linear Byte/Word Access
Section 1.7.1.2
To use linear byte/word access, you must be running Tru64 UNIX (formerly DIGITAL UNIX) Version 4.0D or later on a byte/word I/O capable platform. 1.7.1.1 User Code for Sparse or Dense Access The following test, from dmaex_test.c, shows how user-level code can program user-mode I/O to a memory-mapped VMEbus window using sparse or dense access, assuming the required driver-level support code has been implemented. The test requires three parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The total number of bytes to map for data transfers
•
data.data[1] — The starting VMEbus address of the data buffer
•
data.data[2] — The VMEbus address modifiers, data size, byte swap mode, and memory access mode (sparse or dense); values are defined in the io/dec/vme/vbareg.h header file _______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. The third argument to the test specifies whether software byte swapping is needed (1) or not (0); and if software byte swapping is needed, the fourth argument provides the byte swap mode.
VMEbus Device Driver Example 1–43
For PIO on UNIVERSE II based platforms, the memory access mode is assumed to have been set to dense space before the test was called. int dmaex_mmap_test(int fd, struct dmaex_ioctl_data *data, int sw_byte_swap_needed, vme_atype_t byte_swap_mode) { unsigned char *read_byte_data, *write_byte_data, *byte_vaddr; unsigned short *read_word_data, *write_word_data, *word_vaddr; unsigned int *read_int_data, *write_int_data, *int_vaddr; unsigned long *read_quad_data, *write_quad_data, *quad_vaddr; int *read_buffer = NULL; int *write_buffer = NULL; int *wrt_buffer = NULL; unsigned char *wrt_byte_data = NULL; int buf_size,err,i,j; vme_addr_t vme_addr; vme_atype_t vme_am; u_int vme_size; u_int vme_access; io_handle_t vme_iohandle; u_long offset_from_base = 0; size_t len; int *mapped_va; 1 int *bufp; caddr_t status; int byte_swap_needed = 0; if ( (!data->data[0]) || (!(data->data[2] & VME_BITS_MASK)) ) { 2 printf("No test parameters specified\n"); return(1); } buf_size = (int)data->data[0]; if ((sw_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) 3 byte_swap_needed = 1; write_buffer = (int *)malloc(buf_size); 4 if(!(write_buffer)) { perror("write malloc failed for write_buffer"); return(1); } wrt_buffer = (int *)malloc(buf_size); 5 if(!(wrt_buffer)) { free(write_buffer); perror("write malloc failed for wrt_buffer"); return(1); } write_byte_data = (unsigned char wrt_byte_data = (unsigned char for (i = 0, j = 0; i < buf_size; write_byte_data[i] = (unsigned wrt_byte_data[i] = (unsigned }
*)write_buffer; *)wrt_buffer; i++, j = i/256) { 6 char)(i + j); char)(i + j);
if (byte_swap_needed) { 7 if (dmaex_sw_byte_swap(write_buffer,
1–44 VMEbus Device Driver Example
wrt_buffer, buf_size, byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); perror("Error detected swapping write data"); return(1); } } read_buffer = (int *)malloc(buf_size); 8 if (!(read_buffer)) { free(write_buffer); free(wrt_buffer); perror("read malloc failed"); return(1); } if (data->data[2] & VME_DENSE) data->data[3] = (unsigned long)(HANDLE_LONGWORD | HANDLE_DENSE_SPACE); else data->data[3] = (unsigned long)(HANDLE_LONGWORD | HANDLE_SPARSE_SPACE); if ((err = ioctl(fd, SETUP_VME_FOR_MMAP_PIO, data)) != 0) { 9 free(write_buffer); free(wrt_buffer); free(read_buffer); perror("SETUP_VME_FOR_MMAP_PIO"); return(1); } printf("Successfully mapped the VME address to "); if (data->data[2] & VME_DENSE) printf("dense space\n"); else printf("sparse space\n"); if ((err = ioctl(fd, GET_VME_INFO_FOR_MMAP_PIO, data)) != 0) { 10 free(write_buffer); free(wrt_buffer); free(read_buffer); perror("GET_VME_INFO_FOR_MMAP_PIO"); if ((err = ioctl(fd, UNMAP_VME_FOR_MMAP_PIO, data)) != 0) perror("UNMAP_VME_FOR_MMAP_PIO"); return(1); } printf("PIO mapping info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" PIO Access value = 0x%x\n",(unsigned int)data->data[3]); printf(" PIO I/O handle = 0x%lx\n",data->data[4]); vme_size vme_addr vme_am vme_access vme_iohandle
= = = = =
(unsigned int)data->data[0]; (vme_addr_t)data->data[1]; (vme_atype_t)data->data[2]; (unsigned int)data->data[3]; data->data[4];
data->data[0] = (unsigned long)MMAP_VME_TO_U_MEM; if ( ioctl(fd, SET_MMAP_MODE, data) != 0 ) 11 perror("error in ioctl SET_MMAP_MODE"); if ( ioctl(fd, GET_MMAP_MODE, data) != 0 ) 12 perror("error in ioctl GET_MMAP_MODE");
VMEbus Device Driver Example 1–45
switch ((int)data->data[0]) { case MMAP_VME_TO_U_MEM: printf("Mmap mode = MMAP_VME_TO_U_MEM\n"); break; case MMAP_K_TO_U_MEM_WRT: printf("Mmap mode = MMAP_K_TO_U_MEM_WRT\n"); break; case MMAP_K_TO_U_MEM_RD: printf("Mmap mode = MMAP_K_TO_U_MEM_RD\n"); break; default: printf("Invalid mmap mode returned\n"); } offset_from_base = (u_long)get_offset(vme_addr,vme_am); 13 printf("*** VME base address 0x%x - sparse or dense space offset = 0x%lx\n", vme_addr,offset_from_base); if (vme_am & VME_DENSE) len = (vme_size + offset_from_base); else len = (vme_size * PAGE_MULT) + offset_from_base; mapped_va = (int *)mmap((caddr_t)0, 14 len, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0); if (mapped_va == (int *)-1) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("mmap: failed"); if ((err = ioctl(fd, UNMAP_VME_FOR_MMAP_PIO, data)) != 0) perror("UNMAP_VME_FOR_MMAP_PIO"); return(1);; } printf("Successfully called mmap \n"); (u_long)bufp = (u_long)mapped_va + (u_long)offset_from_base; 15 printf("*** buffer address = 0x%x\n",(u_long)bufp); if (!(vme_am & VME_DENSE)) { 16 printf("Writing byte data\n"); write_byte_data = (unsigned char*)wrt_buffer; read_byte_data = (unsigned char*)read_buffer; byte_vaddr = (unsigned char*)bufp; for (i = 0; i < vme_size; i++, byte_vaddr++) { 17 read_byte_data[i] = 0; write_sparse_byte(bufp, ((u_long)byte_vaddr - (u_long)bufp), write_byte_data[i]); } printf("Reading byte data\n"); byte_vaddr = (unsigned char*)bufp; for (i = 0; i < vme_size; i++, byte_vaddr++) 18 read_sparse_byte(bufp, ((u_long)byte_vaddr - (u_long)bufp),
1–46 VMEbus Device Driver Example
read_byte_data[i]); if (byte_swap_needed) { 19 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for byte reads"); return(1); } } printf("Comparing byte data\n"); write_byte_data = (unsigned char *)write_buffer; for (i = 0, j = 0; i < vme_size; i++) { 20 if (write_byte_data[i] != read_byte_data[i]) { printf("Compare failed i = 0x%x read = 0x%x, write = 0x%x\n", i, read_byte_data[i], write_byte_data[i]); if (++j > 32) break; } } printf("Writing word data\n"); write_word_data = (unsigned short*)wrt_buffer; read_word_data = (unsigned short*)read_buffer; word_vaddr = (unsigned short*)bufp; for (i = 0; i < vme_size/2; i++, word_vaddr++) { 21 read_word_data[i]=0; write_sparse_word(bufp, ((u_long)word_vaddr - (u_long)bufp), write_word_data[i]); } printf("Reading word data\n"); word_vaddr = (unsigned short*)bufp; for (i = 0; i < vme_size/2; i++, word_vaddr++) 22 read_sparse_word(bufp, ((u_long)word_vaddr - (u_long)bufp), read_word_data[i]); if (byte_swap_needed) { 23 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for word reads"); return(1); } } printf("Comparing word data\n"); write_word_data = (unsigned short *)write_buffer; for (i = 0, j = 0; i < vme_size/2; i++) { 24 if (write_word_data[i] != read_word_data[i]) { printf("Compare failed i = 0x%x read = 0x%x, write = 0x%x\n", i, read_word_data[i], write_word_data[i]); if (++j > 32)
VMEbus Device Driver Example 1–47
break; } } printf("Writing integer data\n"); write_int_data = (unsigned int*)wrt_buffer; read_int_data = (unsigned int*)read_buffer; int_vaddr = (unsigned int*)bufp; for (i = 0; i < vme_size/4; i++, int_vaddr++) { 25 read_int_data[i]=0; write_sparse_int(bufp, ((u_long)int_vaddr - (u_long)bufp), write_int_data[i]); } printf("Reading integer data\n"); int_vaddr = (unsigned int*)bufp; for (i = 0; i < vme_size/4; i++, int_vaddr++) 26 read_sparse_int(bufp, ((u_long)int_vaddr - (u_long)bufp), read_int_data[i]); if (byte_swap_needed) { 27 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for integer reads"); return(1); } } printf("Comparing integer data\n"); write_int_data = (unsigned int *)write_buffer; for (i = 0, j = 0; i < vme_size/4; i++) { 28 if (write_int_data[i] != read_int_data[i]) { printf("Compare failed i = 0x%x read = 0x%x, write = 0x%x\n", i, read_int_data[i], write_int_data[i]); if (++j > 32) break; } } } else { /* end if (!(vme_am & VME_DENSE)) */ 29 printf("Writing integer data\n"); write_int_data = (unsigned int*)wrt_buffer; read_int_data = (unsigned int*)read_buffer; int_vaddr = (unsigned int*)bufp; for ( i = 0; i < vme_size/4; i++, int_vaddr++) { 30 read_int_data[i]=0; *int_vaddr = write_int_data[i]; } printf("Reading integer data\n"); int_vaddr = (unsigned int*)bufp; for (i=0; i < vme_size/4; i++, int_vaddr++) 31 read_int_data[i] = *int_vaddr;
1–48 VMEbus Device Driver Example
if (byte_swap_needed) { 32 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for integer reads"); return(1); } } printf("Comparing integer data\n"); write_int_data = (unsigned int *)write_buffer; for (i = 0, j = 0; i < vme_size/4; i++) { 33 if (write_int_data[i] != read_int_data[i]) { printf("Compare failed i = 0x%x read = 0x%x, write = 0x%x\n", i, read_int_data[i], write_int_data[i]); if (++j > 32) break; } } printf("Writing quad data\n"); write_quad_data = (unsigned long*)wrt_buffer; read_quad_data = (unsigned long*)read_buffer; quad_vaddr = (unsigned long*)bufp; for (i = 0; i < vme_size/8; i++, quad_vaddr++) { 34 read_quad_data[i] = 0; *quad_vaddr = write_quad_data[i]; } printf("Reading quad data\n"); quad_vaddr = (unsigned long*)bufp; for (i = 0; i < vme_size/8; i++, quad_vaddr++) 35 read_quad_data[i] = *quad_vaddr; if (byte_swap_needed) { 36 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for quad reads"); return(1); } } printf("Comparing quad data\n"); write_quad_data = (unsigned long *)write_buffer; for (i = 0, j = 0; i < vme_size/8; i++) { 37 if (write_quad_data[i] != read_quad_data[i]) { printf("Compare failed i = 0x%x read = 0x%lx, write = 0x%lx\n", i, read_quad_data[i], write_quad_data[i]); if (++j > 32) break; } } }
VMEbus Device Driver Example 1–49
if ((err = ioctl(fd, UNMAP_VME_FOR_MMAP_PIO, data)) != 0) { 38 free(write_buffer); free(wrt_buffer); free(read_buffer); perror("UNMAP_VME_FOR_MMAP_PIO"); return(1); } status = (caddr_t) munmap((caddr_t)mapped_va,len); 39 free(write_buffer); 40 free(wrt_buffer); free(read_buffer); return(0); }
1
Declares and initializes variables, including: •
The mapped_va variable, which will receive the address of the start of the memory-mapped area
•
2
3
4 5
6 7
The bufp variable, which will receive the address of the start of the buffer within the memory-mapped area Verifies that the test caller specified a byte count and a VMEbus address modifiers argument. If not, the user code displays an error message and returns the error status 1 to the test caller. If software byte swapping is needed, sets the local flag byte_swap_needed. Software byte swapping is needed if the test caller passed 1 for the sw_byte_swap_needed test argument and a byte swap mode other than VME_BS_NOSWAP for the byte_swap_mode test argument. This implies that the caller has determined both that the data transfer requires byte swapping and that the system’s VMEbus adapter does not provide hardware byte swapping. (The caller may also select software byte swapping over hardware byte swapping where both are available.) The swap arguments provided by dmaex_test to this test are based in part on a platform-specific flag value generated by GET_HW_BYTE_SWAP_INFO, a driver ioctl command that dmaex_test uses to determine the swapping capabilities of the system’s VMEbus adapter. See Section 1.16 for more information. Allocates a write buffer to hold data to be transferred by the test. Allocates a buffer to hold the actual write data. If software byte swap was not needed or selected, this buffer will contain the same data as the original write buffer; otherwise, it will contain the byte-swapped data. Initializes both write buffers with the same data. If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the write data as 32-bit integer values based on the specified byte swap mode. After swapping,
1–50 VMEbus Device Driver Example
write_buffer retains the original data pattern (for later comparison) and wrt_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments:
8 9
•
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. Allocates a read buffer to receive data transferred by the test. Calls the SETUP_VME_FOR_MMAP_PIO ioctl command to map outbound to a VMEbus address for a specified size with specified address modifiers. Note that after the driver maps outbound on the VMEbus for the exact buffer size, the user then maps with mmap either to ((32 * buf_size) + offset) for sparse accesses or to the actual (buf_size + offset) for dense accesses. Whether you specify sparse or dense access, the macros used for access perform the correct manipulations. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SETUP_VME_FOR_MMAP_PIO command takes four arguments:
10
•
data->data[0] — The total number of bytes to map on the VMEbus for programmed I/O
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The PIO memory access mode, sparse or dense (HANDLE_LONGWORD|HANDLE_SPARSE_SPACE or HANDLE_LONGWORD|HANDLE_DENSE_SPACE)
Section 1.7.2.2 describes the implementation of the SETUP_VME_FOR_MMAP_PIO command. Calls the GET_VME_INFO_FOR_MMAP_PIO ioctl command to display the results of the previous call to
VMEbus Device Driver Example 1–51
SETUP_VME_FOR_MMAP_PIO and to save the VMEbus outbound mapping information.
11
Section 1.7.2.3 describes the implementation of the GET_VME_INFO_FOR_MMAP_PIO command. Calls the SET_MMAP_MODE ioctl command to specify the type of memory mapping, MMAP_VME_TO_U_MEM, to be performed using the mmap interface. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block containing command arguments. The SET_MMAP_MODE command takes one argument: data->data[0] — The memory mapping mode, MMAP_VME_TO_U_MEM
12
13
Section 1.7.2.5 describes the implementation of the SET_MMAP_MODE command. For testing purposes, the user code calls the GET_MMAP_MODE ioctl command and displays the results of the previous call to SET_MMAP_MODE. Section 1.7.2.6 describes the implementation of the GET_MMAP_MODE command. Invokes the get_offset user macro to calculate the offset that the user program will apply into the memory region mapped by mmap. The mmap interface returns the starting address of the mapped region, truncated to a page boundary. (See dmaex_mmap, which returns the page frame number only.) Therefore, the user program must offset into that page. To map the entire buffer size, the user code calculates the offset into the page to which the buffer is mapped and adds this offset to the length of the buffer to be mapped. The get_offset macro is defined in the dmaex_test.c user code as follows: #define get_offset(vme_address,vme_am) \ ((vme_am)&VME_DENSE) ? ((vme_address)%0x2000) : (((vme_address)%0x100)*32)
14
Calls the mmap interface to map memory according to the access mode: •
For sparse access, maps a size of 32 times the buffer size, plus the offset into the mapped page (calculated in the previous step)
• 15
For dense access, maps to the size of the buffer plus the offset into the mapped page (calculated in the previous step) Calculates the base virtual address of the mapped buffer, adding the calculated offset to the memory region base virtual address that mmap returned.
1–52 VMEbus Device Driver Example
16
If using sparse memory access, begins sparse space testing. The user code tests byte, word, and integer writes and reads to sparse space. Each sparse space test involves clearing a read buffer, invoking a macro to write data to the mapped memory, invoking a macro to read data back from mapped memory, and, for testing purposes, comparing the read and write buffers.
17
Invokes the write_sparse_byte sparse space macro to write byte data to the mapped memory. The write_sparse_byte macro is defined in the dmaex_test.c user code as follows: #define write_sparse_byte(base, offset,data) { \ unsigned char *va; \ unsigned long write_data = \ (unsigned long) data << (((u_long)offset & 3) << 3); \ va = (unsigned char *) ((((u_long)offset) << 5) + \ ((u_long)base )); \ *(int *)va = write_data; \ (void)asm("mb"); }
18
Invokes the read_sparse_byte sparse space macro to read byte data back from the mapped memory. The read_sparse_byte macro is defined in the dmaex_test.c user code as follows: #define read_sparse_byte(base, offset, dest) { \ unsigned char *va; \ unsigned long data=0; \ va = (unsigned char *) ((((u_long)offset) << 5) + \ ((u_long)base )); \ data = (unsigned long) *(int *)va; \ read_shift(((u_long)offset & 3), data, 1); \ dest = (unsigned char) data; }
19
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface.
VMEbus Device Driver Example 1–53
20 21
Initializes the write buffer pointer to the original write data and compares it with the newly read data. Invokes the write_sparse_word sparse space macro to write word data to the mapped memory. The write_sparse_word macro is defined in the dmaex_test.c user code as follows: #define write_sparse_word(base,offset,data) { \ unsigned short *va; \ unsigned long write_data = \ (unsigned long) data << (((u_long)offset & 3) << 3); \ va = (unsigned short *) ((((u_long)offset) << 5) + \ ((u_long)base ) | 8); \ *(int *)va = write_data; \ (void)asm("mb"); }
22
Invokes the read_sparse_word sparse space macro to read word data back from the mapped memory. The read_sparse_word macro is defined in the dmaex_test.c user code as follows: #define read_sparse_word(base, offset, dest) { \ unsigned short *va; \ unsigned long data=0; \ va = (unsigned short *) ((((u_long)offset) << 5) + \ ((u_long)base ) | 8); \ data = (unsigned long) *(int *)va; \ read_shift(((u_long)offset & 3), data, 2); \ dest = (unsigned short) data; }
23
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments:
24 25
•
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. Initializes the write buffer pointer to the original write data and compares it with the newly read data. Invokes the write_sparse_int sparse space macro to write integer data to the mapped memory. The write_sparse_int macro is defined in the dmaex_test.c user code as follows:
1–54 VMEbus Device Driver Example
#define write_sparse_int(base, offset, data) { \ unsigned int *va; \ unsigned long write_data = \ (unsigned long) data << (((u_long)offset & 3) << 3); \ va = (unsigned int *) ((((u_long)offset) << 5) + \ ((u_long)base ) | 0x18); \ *(int *)va = write_data; \ (void)asm("mb"); }
26
Invokes the read_sparse_int sparse space macro to read integer data back from the mapped memory. The read_sparse_int macro is defined in the dmaex_test.c user code as follows: #define read_sparse_int(base, offset, dest) { \ unsigned int *va; \ unsigned long data=0; \ va = (unsigned int *) ((((u_long)offset) << 5) + \ ((u_long)base ) | 0x18); \ data = (unsigned long) *(int *)va; \ read_shift(((u_long)offset & 3), data, 4); \ dest = (unsigned int) data; }
27
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 28
Initializes the write buffer pointer to the original write data and compares it with the newly read data.
29
If using dense memory access, begins dense space testing. The user code tests integer and quadword writes and reads to dense space. Each dense space test involves clearing a read buffer, using simple pointer references (*=) to write data to the mapped memory and then read data back from mapped memory, and, for testing purposes, comparing the read and write buffers. You can write and read dense space directly without having to define macros.
VMEbus Device Driver Example 1–55
_____________________
Note
_____________________
If you are running a version of Tru64 UNIX (formerly DIGITAL UNIX) before 4.0D, or if your platform is not byte/word I/O capable, you can reliably perform only integer and quadword (32 and 64 bit) accesses to densely mapped memory. Byte and word accesses will work (on AXPvme systems), but they are implemented as a read followed by a write on 32 or 64 bits. 30
Writes integer data to the mapped dense space memory.
31
Reads integer data back from the mapped dense space memory.
32
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 33
Initializes the write buffer pointer to the original write data and compares it with the newly read data.
34
Writes quadword data to the mapped dense space memory.
35
Reads quadword data back from the mapped dense space memory.
36
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
1–56 VMEbus Device Driver Example
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 37
Initializes the write buffer pointer to the original write data and compares it with the newly read data.
38
Calls the UNMAP_VME_FOR_MMAP_PIO ioctl command to release resources allocated by SETUP_VME_FOR_MMAP_PIO back to the system. The UNMAP_VME_FOR_MMAP_PIO command takes no arguments. Section 1.7.2.4 describes the implementation of the UNMAP_VME_FOR_MMAP_PIO command.
39
Calls the munmap interface to unmap the user-level memory mapping.
40
Releases the write and read buffer memory used for testing and returns success (0) to the test caller.
1.7.1.2 User Code for Linear Byte/Word Access The following test, from dmaex_test.c, shows how user-level code can program user-mode I/O to a memory-mapped VMEbus window using linear byte/word access, assuming the required driver-level support code has been implemented. _______________________
Note
_______________________
To use linear byte/word access, you must be running Tru64 UNIX (formerly DIGITAL UNIX) Version 4.0D or later on a byte/word I/O capable platform. The test requires three parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The total number of bytes to map for data transfers
•
data.data[1] — The starting VMEbus address of the data buffer
•
data.data[2] — The VMEbus address modifiers, data size, byte swap mode, and memory access mode; values are defined in the io/dec/vme/vbareg.h header file
VMEbus Device Driver Example 1–57
_______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. The third argument to the test specifies whether software byte swapping is needed (1) or not (0); and if software byte swapping is needed, the fourth argument provides the byte swap mode. This test code can be extracted from dmaex_test.c into a separate file, compiled with the -arch ev56 compiler switch, and then linked to the main dmaex_test program. Doing this causes all memory references and bus references to be compiled and accessed using the appropriate data size. The C macros can be left unchanged, or the bus access can be modified to use the appropriate pointers to char or short data types. The specific steps involved are as follows: 1.
Edit dmaex_test.c and extract the interface dmaex_mmap_bw_test to a new file called dmaex_bw_test.c. At the same time, remove the interface from dmaex_test.c.
2.
At the beginning of dmaex_test.c, add extern to the forward declaration of the dmaex_mmap_bw_test interface. This completes the changes to dmaex_test.c.
3.
Edit dmaex_bw_test.c and add the following lines to the start of the file: #include #include #include #include #include #include #include #include #include
"dmaexreg.h"
#define mb() asm("mb") extern dmaex_sw_byte_swap(int *, int *, int, vme_atype_t);
4.
In dmaex_mmap_bw_test, change: stb(byte_vaddr,write_byte_data[i]);
to: *byte_vaddr = write_byte_data[i];
Change:
1–58 VMEbus Device Driver Example
read_byte_data[i] = ldbu(byte_vaddr);
to: read_byte_data[i] = *byte_vaddr;
Change: stw(word_vaddr,write_word_data[i]);
to: *word_vaddr = write_word_data[i];
Change: read_word_data[i] = ldwu(word_vaddr);
to: read_word_data[i] = *word_vaddr;
This completes the changes to dmaex_bw_test.c. 5.
Compile and link the two files as follows: cc -c dmaex_test.c -g2 cc -c dmaex_bw_test.c -g2 cc dmaex_test.o dmaex_bw_test.o -o dmaex_test
The following listing assumes the test has not been extracted to a separate file or modified according to the steps listed above. #ifdef HANDLE_LINEAR_SPACE 1 int dmaex_mmap_bw_test(int fd, struct dmaex_ioctl_data *data, int sw_byte_swap_needed, vme_atype_t byte_swap_mode) { unsigned char *read_byte_data, *write_byte_data, *byte_vaddr; unsigned short *read_word_data, *write_word_data, *word_vaddr; unsigned int *read_int_data, *write_int_data, *int_vaddr; unsigned long *read_quad_data, *write_quad_data, *quad_vaddr; int *read_buffer = NULL; int *write_buffer = NULL; int *wrt_buffer = NULL; unsigned char *wrt_byte_data = NULL; int buf_size,err,i,j; vme_addr_t vme_addr; vme_atype_t vme_am; u_int vme_size; u_int vme_access; io_handle_t vme_iohandle; u_long offset_from_base = 0; size_t len; int *mapped_va; 2 int *bufp; caddr_t status; int byte_swap_needed = 0; if ( (!data->data[0]) || (!(data->data[2] & VME_BITS_MASK)) ) { 3 printf("No test parameters specified\n");
VMEbus Device Driver Example 1–59
return(1); } buf_size = (int)data->data[0]; if ((sw_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) 4 byte_swap_needed = 1; write_buffer = (int *)malloc(buf_size); 5 if(!(write_buffer)) { perror("write malloc failed for write_buffer"); return(1); } wrt_buffer = (int *)malloc(buf_size); 6 if(!(wrt_buffer)) { free(write_buffer); perror("write malloc failed for wrt_buffer"); return(1); } write_byte_data = (unsigned char wrt_byte_data = (unsigned char for (i = 0, j = 0; i < buf_size; write_byte_data[i] = (unsigned wrt_byte_data[i] = (unsigned }
*)write_buffer; *)wrt_buffer; i++, j = i/256) { 7 char)(i + j); char)(i + j);
if (byte_swap_needed) { 8 if (dmaex_sw_byte_swap(write_buffer, wrt_buffer, buf_size, byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); perror("Error detected swapping write data"); return(1); } } read_buffer = (int *)malloc(buf_size); 9 if (!(read_buffer)) { free(write_buffer); free(wrt_buffer); perror("read malloc failed"); return(1); } data->data[3] = (unsigned long)(HANDLE_LINEAR_SPACE); if ((err = ioctl(fd, SETUP_VME_FOR_MMAP_PIO, data)) != 0) { 10 free(write_buffer); free(wrt_buffer); free(read_buffer); perror("SETUP_VME_FOR_MMAP_PIO"); return(1); } printf("Successfully mapped the VME address to "); if (data->data[2] & VME_DENSE) printf("dense space\n"); else printf("sparse space\n"); if ((err = ioctl(fd, GET_VME_INFO_FOR_MMAP_PIO, data)) != 0) { 11
1–60 VMEbus Device Driver Example
free(write_buffer); free(wrt_buffer); free(read_buffer); perror("GET_VME_INFO_FOR_MMAP_PIO"); if ((err = ioctl(fd, UNMAP_VME_FOR_MMAP_PIO, data)) != 0) perror("UNMAP_VME_FOR_MMAP_PIO"); return(1); } printf("PIO mapping info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" PIO Access value = 0x%x\n",(unsigned int)data->data[3]); printf(" PIO I/O handle = 0x%lx\n",data->data[4]); vme_size vme_addr vme_am vme_access vme_iohandle
= = = = =
(unsigned int)data->data[0]; (vme_addr_t)data->data[1]; (vme_atype_t)data->data[2]; (unsigned int)data->data[3]; data->data[4];
data->data[0] = (unsigned long)MMAP_VME_TO_U_MEM; if ( ioctl(fd, SET_MMAP_MODE, data) != 0 ) 12 perror("error in ioctl SET_MMAP_MODE"); if ( ioctl(fd, GET_MMAP_MODE, data) != 0 ) 13 perror("error in ioctl GET_MMAP_MODE"); switch ((int)data->data[0]) { case MMAP_VME_TO_U_MEM: printf("Mmap mode = MMAP_VME_TO_U_MEM\n"); break; case MMAP_K_TO_U_MEM_WRT: printf("Mmap mode = MMAP_K_TO_U_MEM_WRT\n"); break; case MMAP_K_TO_U_MEM_RD: printf("Mmap mode = MMAP_K_TO_U_MEM_RD\n"); break; default: printf("Invalid mmap mode returned\n"); } offset_from_base = (u_long)(vme_addr % 0x2000); 14 printf("*** VME base address 0x%x - byte offset = 0x%lx\n", vme_addr,offset_from_base); len = (vme_size + offset_from_base); mapped_va = (int *)mmap((caddr_t)0, 15 len, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0); if (mapped_va == (int *)-1) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("mmap: failed"); if ((err = ioctl(fd, UNMAP_VME_FOR_MMAP_PIO, data)) != 0) perror("UNMAP_VME_FOR_MMAP_PIO"); return(1);; } printf("Successfully called mmap \n");
VMEbus Device Driver Example 1–61
(u_long)bufp = (u_long)mapped_va + (u_long)offset_from_base; 16 printf("*** buffer address = 0x%x\n",(u_long)bufp); printf("Writing byte data\n"); 17 write_byte_data = (unsigned char *)wrt_buffer; read_byte_data = (unsigned char *)read_buffer; byte_vaddr = (unsigned char *)bufp; for (i = 0; i < vme_size; i++, byte_vaddr++) { 18 read_byte_data[i] = 0; stb(byte_vaddr,write_byte_data[i]); mb(); } printf("Reading byte data\n"); byte_vaddr = (unsigned char*)bufp; for (i = 0; i < vme_size; i++, byte_vaddr++) 19 read_byte_data[i] = ldbu(byte_vaddr); if (byte_swap_needed) { 20 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for byte reads"); return(1); } } printf("Comparing byte data\n"); write_byte_data = (unsigned char *)write_buffer; for (i = 0, j = 0; i < vme_size; i++) { 21 if (write_byte_data[i] != read_byte_data[i]) { printf("Byte R/W compare failed i = 0x%x read = 0x%x, write = 0x%x\n", i, read_byte_data[i], write_byte_data[i]); if (++j > 32) break; } } printf("Writing word data\n"); write_word_data = (unsigned short *)wrt_buffer; read_word_data = (unsigned short *)read_buffer; word_vaddr = (unsigned short *)bufp; for (i = 0; i < vme_size/2; i++, word_vaddr++) { 22 read_word_data[i] = 0; stw(word_vaddr,write_word_data[i]); mb(); } printf("Reading word data\n"); word_vaddr = (unsigned short*)bufp; for (i = 0; i < vme_size/2; i++, word_vaddr++) 23 read_word_data[i] = ldwu(word_vaddr); if (byte_swap_needed) { 24
1–62 VMEbus Device Driver Example
if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for word reads"); return(1); } } printf("Comparing word data\n"); write_word_data = (unsigned short *)write_buffer; for (i = 0, j = 0; i < vme_size/2; i++) { 25 if (write_word_data[i] != read_word_data[i]) { printf("Word R/W compare failed i = 0x%x read = 0x%x, write = 0x%x\n", i, read_word_data[i], write_word_data[i]); if (++j > 32) break; } } printf("Writing integer data\n"); 26 write_int_data = (unsigned int *)wrt_buffer; read_int_data = (unsigned int *)read_buffer; int_vaddr = (unsigned int *)bufp; for ( i = 0; i < vme_size/4; i++, int_vaddr++) { 27 read_int_data[i] = 0; *int_vaddr = write_int_data[i]; } mb(); printf("Reading integer data\n"); int_vaddr = (unsigned int*)bufp; for (i=0; i < vme_size/4; i++, int_vaddr++) 28 read_int_data[i] = *int_vaddr; if (byte_swap_needed) { 29 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for integer reads"); return(1); } } printf("Comparing integer data\n"); write_int_data = (unsigned int *)write_buffer; for (i = 0, j = 0; i < vme_size/4; i++) { 30 if (write_int_data[i] != read_int_data[i]) { printf("Lword R/W compare failed i = 0x%x read = 0x%x, write = 0x%x\n", i, read_int_data[i], write_int_data[i]); if (++j > 32) break; } } printf("Writing quad data\n");
VMEbus Device Driver Example 1–63
write_quad_data = (unsigned long *)wrt_buffer; read_quad_data = (unsigned long *)read_buffer; quad_vaddr = (unsigned long *)bufp; for (i = 0; i < vme_size/8; i++, quad_vaddr++) { 31 read_quad_data[i] = 0; *quad_vaddr = write_quad_data[i]; } mb(); printf("Reading quad data\n"); quad_vaddr = (unsigned long*)bufp; for (i = 0; i < vme_size/8; i++, quad_vaddr++) 32 read_quad_data[i] = *quad_vaddr; if (byte_swap_needed) { 33 if (dmaex_sw_byte_swap(read_buffer,read_buffer, vme_size,byte_swap_mode) == 0) { free(write_buffer); free(wrt_buffer); free(read_buffer); perror("Error detected swapping read data for quad reads"); return(1); } } printf("Comparing quad data\n"); write_quad_data = (unsigned long *)write_buffer; for (i = 0, j = 0; i < vme_size/8; i++) { 34 if (write_quad_data[i] != read_quad_data[i]) { printf("Quad R/W compare failed i = 0x%x read = 0x%lx, write = 0x%lx\n", i, read_quad_data[i], write_quad_data[i]); if (++j > 32) break; } } if ((err = ioctl(fd, UNMAP_VME_FOR_MMAP_PIO, data)) != 0) { 35 free(write_buffer); free(wrt_buffer); free(read_buffer); perror("UNMAP_VME_FOR_MMAP_PIO"); return(1); } status = (caddr_t) munmap((caddr_t)mapped_va,len); 36 free(write_buffer); 37 free(wrt_buffer); free(read_buffer); return(0); } #else 38 int dmaex_mmap_bw_test(int struct dmaex_ioctl_data int vme_atype_t {
1–64 VMEbus Device Driver Example
fd, *data, sw_byte_swap_needed, byte_swap_mode)
printf("Mmap byte/word addressing not supported by this version of UNIX.\n"); return(0); } #endif
1
Tests whether the HANDLE_LINEAR_SPACE flag is defined, indicating whether mmap byte/word support is present in the system. If undefined, you are running a version of Tru64 UNIX (formerly DIGITAL UNIX) before 4.0D and an error is returned. Beginning with Tru64 UNIX (formerly DIGITAL UNIX) Version 4.0D, the HANDLE_LINEAR_SPACE flag is defined in io/common/devdriver.h and indicates that the iohandle_to_phys kernel interface is able to translate a sparse or dense I/O handle to an appropriate linear byte/word address.
2
Declares and initializes variables, including: •
The mapped_va variable, which will receive the address of the start of the memory-mapped area
•
The bufp variable, which will receive the address of the start of the buffer within the memory-mapped area
3
Verifies that the test caller specified a byte count and a VMEbus address modifiers argument. If not, the user code displays an error message and returns the error status 1 to the test caller.
4
If software byte swapping is needed, sets the local flag byte_swap_needed. Software byte swapping is needed if the test caller passed 1 for the sw_byte_swap_needed test argument and a byte swap mode other than VME_BS_NOSWAP for the byte_swap_mode test argument. This implies that the caller has determined both that the data transfer requires byte swapping and that the system’s VMEbus adapter does not provide hardware byte swapping. (The caller may also select software byte swapping over hardware byte swapping where both are available.) The swap arguments provided by dmaex_test to this test are based in part on a platform-specific flag value generated by GET_HW_BYTE_SWAP_INFO, a driver ioctl command that dmaex_test uses to determine the swapping capabilities of the system’s VMEbus adapter. See Section 1.16 for more information.
5
Allocates a write buffer to hold data to be transferred by the test.
6
Allocates a buffer to hold the actual write data. If software byte swap was not needed or selected, this buffer will contain the same data as the original write buffer; otherwise, it will contain the byte-swapped data.
7
Initializes both write buffers with the same data.
VMEbus Device Driver Example 1–65
8
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the write data as 32-bit integer values based on the specified byte swap mode. After swapping, write_buffer retains the original data pattern (for later comparison) and wrt_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 9 10
Allocates a read buffer to receive data transferred by the test. Calls the SETUP_VME_FOR_MMAP_PIO ioctl command to map outbound to a VMEbus address for a specified size with specified address modifiers. HANDLE_LINEAR_SPACE must be specified as the PIO memory access type in order to force byte/word I/O space translation. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SETUP_VME_FOR_MMAP_PIO command takes four arguments: •
data->data[0] — The total number of bytes to map on the VMEbus for programmed I/O
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The PIO memory access mode, linear byte/word (HANDLE_LINEAR_SPACE)
Section 1.7.2.2 describes the implementation of the SETUP_VME_FOR_MMAP_PIO command. 11
Calls the GET_VME_INFO_FOR_MMAP_PIO ioctl command to display the results of the previous call to
1–66 VMEbus Device Driver Example
SETUP_VME_FOR_MMAP_PIO and to save the VMEbus outbound mapping information.
12
Section 1.7.2.3 describes the implementation of the GET_VME_INFO_FOR_MMAP_PIO command. Calls the SET_MMAP_MODE ioctl command to specify the type of memory mapping, MMAP_VME_TO_U_MEM, to be performed using the mmap interface. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block containing command arguments. The SET_MMAP_MODE command takes one argument: data->data[0] — The memory mapping mode, MMAP_VME_TO_U_MEM
13
14
15
16
17
Section 1.7.2.5 describes the implementation of the SET_MMAP_MODE command. For testing purposes, the user code calls the GET_MMAP_MODE ioctl command and displays the results of the previous call to SET_MMAP_MODE. Section 1.7.2.6 describes the implementation of the GET_MMAP_MODE command. Calculates the offset that the user program will apply into the memory region mapped by mmap. The mmap interface returns the starting address of the mapped region, truncated to a page boundary. (See dmaex_mmap, which returns the page frame number only.) Therefore, the user program must offset into that page. To map the entire buffer size, the user code calculates the offset into the page to which the buffer is mapped and adds this offset to the length of the buffer to be mapped. Calls the mmap interface to map memory according to the access mode. For linear byte/word access, maps to the size of the buffer plus the offset into the mapped page (calculated in the previous step). Calculates the base virtual address of the mapped buffer, adding the calculated offset to the memory region base virtual address that mmap returned. Begins linear space testing, performing byte and word writes and reads. Each linear space byte or word test involves clearing a read buffer, invoking a macro to write data to the mapped memory, invoking a macro to read data back from mapped memory, and, for testing purposes, comparing the read and write buffers.
VMEbus Device Driver Example 1–67
18
Invokes the stb macro to write byte data to the mapped memory. The stb macro is defined in the dmaex_test.c user code as follows: #define stb(addr,data) (asm(".arch ev56; stb %a1, 0(%a0)",addr,data)) #endif
If software byte swap was needed, the starting VMEbus address must be aligned on a longword boundry (bits 1:0 must be 0). 19
Invokes the ldbu macro to read byte data back from the mapped memory. The ldbu macro is defined in the dmaex_test.c user code as follows: #define ldbu(addr)
20
(asm(".arch ev56; ldbu %v0, 0(%a0)",addr))
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 21
Initializes the write buffer pointer to the original write data and compares it with the newly read data.
22
Invokes the stw macro to write word data to the mapped memory. The stw macro is defined in the dmaex_test.c user code as follows: #define stw(addr,data) (asm(".arch ev56; stw %a1, 0(%a0)",addr,data))
If software byte swap was needed, the starting VMEbus address must be aligned on a longword boundry (bits 1:0 must be 0). 23
Invokes the ldwu macro to read word data back from the mapped memory. The ldwu macro is defined in the dmaex_test.c user code as follows: #define ldwu(addr)
24
(asm(".arch ev56; ldwu %v0, 0(%a0)",addr))
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as
1–68 VMEbus Device Driver Example
32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments:
25 26
27
28 29
•
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. Initializes the write buffer pointer to the original write data and compares it with the newly read data. Continues linear space testing, performing integer and quadword writes and reads. Each linear space integer or quadword test involves clearing a read buffer, using simple pointer references (*=) to write data to the mapped memory and then read data back from mapped memory, and, for testing purposes, comparing the read and write buffers. For integer and quadword accesses, you can write and read linear space directly without having to define macros. Writes integer data to the mapped linear space memory. An mb is issued only after all data has been written, because the order in which writes occur is not important. Reads integer data back from the mapped linear space memory. If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
VMEbus Device Driver Example 1–69
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 30
Initializes the write buffer pointer to the original write data and compares it with the newly read data.
31
Writes quadword data to the mapped linear space memory. An mb is issued after only all data has been written, because the order in which writes occur is not important.
32
Reads quadword data back from the mapped linear space memory.
33
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly read data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 34
Initializes the write buffer pointer to the original write data and compares it with the newly read data.
35
Calls the UNMAP_VME_FOR_MMAP_PIO ioctl command to release resources allocated by SETUP_VME_FOR_MMAP_PIO back to the system. The UNMAP_VME_FOR_MMAP_PIO command takes no arguments. Section 1.7.2.4 describes the implementation of the UNMAP_VME_FOR_MMAP_PIO command.
36
Calls the munmap interface to unmap the user-level memory mapping.
37
Releases the write and read buffer memory used for testing and returns success (0) to the test caller.
38
If the HANDLE_LINEAR_SPACE flag is undefined, you are running a version of Tru64 UNIX (formerly DIGITAL UNIX) before 4.0D, under which mmap byte/word addressing is not supported. The test immediately displays an error message and returns error status.
1–70 VMEbus Device Driver Example
1.7.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support user-mode I/O to a memory-mapped VMEbus window: Implementing the dmaex_ioctl interface
Section 1.7.2.1
Implementing SETUP_VME_FOR_MMAP_PIO
Section 1.7.2.2
Implementing GET_VME_INFO FOR MMAP_PIO
Section 1.7.2.3
Implementing UNMAP_VME_FOR_MMAP_PIO
Section 1.7.2.4
Implementing SET_MMAP_MODE
Section 1.7.2.5
Implementing GET_MMAP_MODE
Section 1.7.2.6
1.7.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation). The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifyies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status). The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands:
VMEbus Device Driver Example 1–71
static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p; sg_entry_t dmaex_sg; ihandler_t handler; struct vme_handler_info info; unsigned int irq; unsigned int vector;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 case SET_MMAP_MODE 4 . . . break; case GET_MMAP_MODE 5 . . . break; . . . case SETUP_VME_FOR_MMAP_PIO 6 . . . break; case GET_VME_INFO_FOR_MMAP_PIO 7 . . . break; case UNMAP_VME_FOR_MMAP_PIO 8 . . . break; . . . default: ret_val = EINVAL; 9 break; } return(ret_val); 10 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value that the file system supplied in the dmaex_ioctl call
1–72 VMEbus Device Driver Example
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS)
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number.
3
Dispatches control, based on the ioctl command value, to a case block that implements the requested command.
4
Section 1.7.2.5 describes the implementation of the SET_MMAP_MODE command.
5
Section 1.7.2.6 describes the implementation of the GET_MMAP_MODE command.
6
Section 1.7.2.2 describes the implementation of the SETUP_VME_FOR_MMAP_PIO command.
7
Section 1.7.2.3 describes the implementation of the GET_VME_INFO_FOR_MMAP_PIO command.
8
Section 1.7.2.4 describes the implementation of the UNMAP_VME_FOR_MMAP_PIO command.
9
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
10
Returns the last-set dmaex_ioctl completion-status value.
1.7.2.2 Implementing SETUP_VME_FOR_MMAP_PIO The SETUP_VME_FOR_MMAP_PIO ioctl command maps outbound to a user-specified VMEbus address for a specified size and specified VMEbus address modifier. The information provided to this command will be used when the kernel invokes the dmaex_mmap interface in response to a user mmap request. PIO mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus for programmed I/O
•
data->data[1] — The starting VMEbus address to map
VMEbus Device Driver Example 1–73
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The PIO memory access mode, sparse or dense (HANDLE_LONGWORD|HANDLE_SPARSE_SPACE or HANDLE_LONGWORD|HANDLE_DENSE_SPACE)
Before the dmaex_mmap interface can use the PIO mapping information, you must also call the SET_MMAP_MODE ioctl command to set the mmap mode to MMAP_VME_TO_U_MEM. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SETUP_VME_FOR_MMAP_PIO ioctl command in the dmaex driver: case SETUP_VME_FOR_MMAP_PIO: DMAEX_DBG2("dmaex_ioctl: SETUP_VME_FOR_MMAP_PIO\n"); if (sc->pio_handle) { 1 printf("SETUP_VME_FOR_MMAP_PIO failed, a PIO mapping exists\n"); ret_val = EBUSY; break; } sc->pio_handle = vba_map_csr(ctlr, 2 (vme_addr_t)data->data[1], (u_int)data->data[0], (u_int)(data->data[2] & VME_BITS_MASK)); if (!(sc->pio_handle)) { printf("SETUP_VME_FOR_MMAP_PIO: vba_map_csr interface failed\n"); printf(" pio_addr = 0x%x pio_size = 0x%x pio_am = 0x%x\n", (vme_addr_t)data->data[1], (u_int)data->data[0], (vme_atype_t)(data->data[2] & VME_BITS_MASK)); ret_val = EINVAL; break; } sc->pio_vme_size = (unsigned int)data->data[0]; 3 sc->pio_vme_addr = (vme_addr_t)data->data[1]; sc->pio_vme_am = (vme_atype_t)(data->data[2] & VME_BITS_MASK); sc->pio_access_type = (unsigned int)data->data[3]; break;
1
2
Checks the pio_handle field of the driver information structure to verify that a PIO mapping does not already exist for the device. If it does, the interface displays an error message and sets the ioctl completion status to EBUSY. Calls the vba_map_csr interface to perform the requested outbound mapping. The vba_map_csr interface takes four arguments: •
The controller information structure for this device
•
The starting VMEbus address to map for programmed I/O, as supplied by the ioctl caller
1–74 VMEbus Device Driver Example
•
The total number of bytes to map, as supplied by the ioctl caller
•
The VMEbus address modifiers supplied by the ioctl caller, combined with a VMEbus flags bit mask
The vba_map_csr interface returns a PIO handle, representing the allocated system resources associated with the outbound mapping, to the pio_handle field of the device’s dmaex_softc structure. 3
Copies the PIO mapping information that the ioctl caller provided into the pio_vme_size, pio_vme_addr, pio_vme_am, and pio_access_type fields of the driver information structure.
1.7.2.3 Implementing GET_VME_INFO_FOR_MMAP_PIO The GET_VME_INFO_FOR_MMAP_PIO ioctl command returns PIO mapping information for the device through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus for programmed I/O
•
data->data[1] — The starting VMEbus address mapped
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The PIO memory access mode, sparse or dense (HANDLE_LONGWORD|HANDLE_SPARSE_SPACE or HANDLE_LONGWORD|HANDLE_DENSE_SPACE)
•
data->data[4] — The PIO handle (of type io_handle_t) associated with the outbound mapping
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_VME_INFO_FOR_MMAP_PIO ioctl command in the dmaex driver: case GET_VME_INFO_FOR_MMAP_PIO: DMAEX_DBG2("dmaex_ioctl: GET_VME_INFO_FOR_MMAP_PIO\n"); data->data[0] data->data[1] data->data[2] data->data[3] data->data[4] break;
= = = = =
(unsigned (unsigned (unsigned (unsigned (unsigned
long)sc->pio_vme_size; 1 long)sc->pio_vme_addr; long)sc->pio_vme_am; long)sc->pio_access_type; long)sc->pio_handle; 2
1
Copies the PIO mapping information, previously stored in the driver information structure by the SETUP_VME_FOR_MMAP_PIO command, back into the first four fields of the data parameter.
2
Copies a PIO handle, representing the allocated system resources associated with the outbound mapping for the device, to the fifth field of the data parameter. The PIO handle was created by a call to the
VMEbus Device Driver Example 1–75
vba_map_csr interface by the SETUP_VME_FOR_MMAP_PIO command. 1.7.2.4 Implementing UNMAP_VME_FOR_MMAP_PIO The UNMAP_VME_FOR_MMAP_PIO ioctl command clears the outbound mapping for programmed I/O previously established for the device when user code called the SETUP_VME_FOR_MMAP_PIO command. VMEbus mapping resources allocated by SETUP_VME_FOR_MMAP_PIO are returned to the system. No parameters are passed to this command. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the UNMAP_VME_FOR_MMAP_PIO ioctl command in the dmaex driver: case UNMAP_VME_FOR_MMAP_PIO: DMAEX_DBG2("dmaex_ioctl: UNMAP_VME_FOR_MMAP_PIO\n"); if (sc->pio_handle) { vba_unmap_csr(ctlr,sc->pio_handle); 1 sc->pio_handle = (unsigned long)0; 2 sc->pio_vme_addr = 0; 3 sc->pio_vme_am = 0; sc->pio_vme_size = 0; sc->pio_access_type = 0; } else { printf("dmaex_ioctl: UNMAP_VME_FOR_MMAP_PIO, no PIO to unmap.\n"); ret_val = ENXIO; } break;
1
Calls the vba_unmap_csr interface to unmap the VMEbus address previously mapped for programmed I/O by the SETUP_VME_FOR_MMAP_PIO command. The vba_unmap_csr interface takes two arguments: •
The controller information structure for this device
•
2 3
The PIO handle returned by the SETUP_VME_FOR_MMAP_PIO command invocation that created the device’s PIO outbound mapping Clears the PIO handle created by a call to the vba_map_csr interface by the SETUP_VME_FOR_MMAP_PIO command. Clears the PIO mapping information stored in the pio_vme_addr, pio_vme_am, pio_vme_size, and pio_access_type fields of the driver information structure by the SETUP_VME_FOR_MMAP_PIO command.
1.7.2.5 Implementing SET_MMAP_MODE The SET_MMAP_MODE ioctl command defines how the dmaex_mmap interface will interpret a user mmap request. The mmap mode, which should
1–76 VMEbus Device Driver Example
equal MMAP_VME_TO_U_MEM for user-mode I/O to a memory-mapped VMEbus window, is specified by the ioctl caller in the data parameter: data->data[0] — The memory mapping mode (MMAP_VME_TO_U_MEM, MMAP_K_TO_U_MEM_WRT, or MMAP_K_TO_U_MEM_RD) The dmaex_mmap interface returns a page frame value based on the selected mmap mode. MMAP_VME_TO_U_MEM returns a page frame number that represents a mapped VMEbus address, MMAP_K_TO_U_MEM_WRT returns a page frame number that represents a physically contiguous cma_dd kernel write buffer, and MMAP_K_TO_U_MEM_RD returns a page frame number that represents a physically contiguous cma_dd kernel read buffer. Before the dmaex_mmap interface can use the MMAP_VME_TO_U_MEM mapping mode, you must also call the SETUP_VME_INFO_FOR_MMAP_PIO ioctl command to establish a PIO outbound mapping. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SET_MMAP_MODE ioctl command in the dmaex driver: case SET_MMAP_MODE: DMAEX_DBG2("dmaex_ioctl: SET_MMAP_MODE\n"); switch ((int)data->data[0]) { case MMAP_VME_TO_U_MEM: case MMAP_K_TO_U_MEM_WRT: case MMAP_K_TO_U_MEM_RD: sc->mmap_mode = (int)data->data[0]; 1 break; default: ret_val = EINVAL; 2 } break;
1 2
Copies the mmap mode provided by the caller into the mmap_mode field of the driver information structure. If an invalid mmap mode value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1.7.2.6 Implementing GET_MMAP_MODE The GET_MMAP_MODE ioctl command returns the current mmap mode for the device, as specified in a previous call to the SET_MMAP_MODE command. The mmap mode is returned through the data parameter of the ioctl interface: data->data[0] — The memory mapping mode (MMAP_VME_TO_U_MEM, MMAP_K_TO_U_MEM_WRT, or MMAP_K_TO_U_MEM_RD)
VMEbus Device Driver Example 1–77
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_MMAP_MODE ioctl command in the dmaex driver: case GET_MMAP_MODE: DMAEX_DBG2("dmaex_ioctl: GET_MMAP_MODE\n"); data->data[0] = (unsigned long)sc->mmap_mode; 1 break;
1
Copies the mmap mode value, previously stored in the driver information structure by the SET_MMAP_MODE command, back into the data parameter.
1.7.3 Memory Map (mmap) Section This section explains how to implement a Memory Map Section that supports user-mode I/O to a memory-mapped VMEbus window. The kernel invokes the dmaex_mmap interface whenever user code performs an mmap(2) system call on the dmaex device. The interface returns the page frame number that corresponds to the page at the specified offset. If an error condition is detected, the interface returns the value –1. Depending on the mmap mode set in the driver information structure (sc->mmap_mode), the interface can map a mapped VMEbus address into user space for user programmed I/O to the device, or map a kernel physically contiguous read or write buffer mapped to VMEbus DMA space into user space for DMA transfers. In the case of user-mode I/O to a memory-mapped VMEbus window, user code must previously have set the mmap mode to MMAP_VME_TO_U_MEM, indicating that a mapped VMEbus address is to be mapped into user space for user programmed I/O. The dmaex_mmap interface takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
An argument of type off_t that specifies a page offset (in bytes)
•
A page protection value (ignored by this implementation)
The following code shows the implementation of the dmaex_mmap interface for user-mode I/O to a memory-mapped VMEbus window: static int dmaex_mmap(dev_t dev, off_t off, int prot) { long register int register struct controller register struct dmaex_softc caddr_t
1–78 VMEbus Device Driver Example
kpfnum,flags; unit = minor(dev); 1 *ctlr = dmaex_ctlr[unit]; *sc = &dmaex_softc[unit]; phys_addr = 0;
int
error
= 0;
DMAEX_DBG1("dmaex_mmap: dmaex%d\n",unit); switch (sc->mmap_mode) { 2 case MMAP_VME_TO_U_MEM: if (!sc->pio_handle) { 3 printf("dmaex_mmap: You must do an outbound mapping first. \n"); error++; } else { #ifdef HANDLE_LINEAR_SPACE if (!(flags & HANDLE_LINEAR_SPACE)) { #endif flags = (u_int)(sc->pio_access_type); 4 if (!(flags & (HANDLE_DENSE_SPACE | HANDLE_SPARSE_SPACE))) { if ((sc->pio_vme_am) & VME_DENSE) flags = (HANDLE_LONGWORD | HANDLE_DENSE_SPACE); else flags = (HANDLE_LONGWORD | HANDLE_SPARSE_SPACE); } #ifdef HANDLE_LINEAR_SPACE } #endif phys_addr = ((caddr_t)iohandle_to_phys(sc->pio_handle, flags)); 5 DMAEX_DBG2("*** io_handle = 0x%lx\n",(u_long)sc->pio_handle); } break; case MMAP_K_TO_U_MEM_WRT: . . . break; case MMAP_K_TO_U_MEM_RD: . . . break; default: printf("dmaex_mmap: Invalid mmap selection mode\n"); error++; } if (error) 6 return(-1); DMAEX_DBG2("*** Bus Physical address = 0x%lx offset = 0x%x\n",phys_addr,off); kpfnum = (long) (((u_long)phys_addr + off) >> 13); 7 DMAEX_DBG2("*** Page frame number = 0x%lx\n",kpfnum); return((int)kpfnum); }
1
Declares and initializes device variables, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value that the kernel supplied in the dmaex_mmap call
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
VMEbus Device Driver Example 1–79
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
2
Dispatches control, based on the mmap mode value stored in field mmap_mode of the driver information structure, to a case block that implements the requested form of memory map. In the case of user mode I/O to a memory-mapped VMEbus window, user code must previously have set the mmap mode to the value MMAP_VME_TO_U_MEM by issuing a call to the SET_MMAP_MODE ioctl command.
3
Checks whether the user called the SETUP_VME_FOR_MMAP_PIO ioctl command to establish the required PIO outbound mapping. If not, the interface displays an error message and increments an error reference count.
4
If the access is to linear byte/word space, available on byte/word I/O capable platforms running Tru64 UNIX (formerly DIGITAL UNIX) Version 4.0D or later, this step is not executed and sparse or dense space flags are not set. Otherwise, in preparation for generating a page frame number that represents the caller-specified mapped VMEbus address, constructs a flags argument for the iohandle_to_phys interface. The dmaex_mmap interface checks the PIO memory access mode flags set up by the SETUP_VME_FOR_MMAP_PIO command. If the HANDLE_SPARSE_SPACE or HANDLE_DENSE_SPACE flags are not present, the interface sets up appropriate flags (HANDLE_LONGWORD|HANDLE_SPARSE_SPACE or HANDLE_LONGWORD|HANDLE_DENSE_SPACE) for sparse or dense mapping to the VMEbus.
5
Calls the iohandle_to_phys interface to convert the PIO handle, which represents an outbound (CPU I/O space to VMEbus) mapping, to a bus physical address.
6
Checks the error reference count for a nonzero (failure) value. If an error condition was detected, the dmaex_mmap interface returns –1 to the kernel.
7
Calculates an Alpha page frame number that represents the bus physical address plus the offset (in bytes) specified by the caller. The interface returns the page frame number to the kernel.
1.8 Programming Block DMA Data Transfers Programming block DMA data transfers involves first coding device driver support for calls to the ioctl, read, and write file system interfaces, and
1–80 VMEbus Device Driver Example
then invoking those interfaces from user code. The VMEbus adapter’s hardware DMA engine performs the physical data transfers. Compaq provides driver and user code templates that you can use as a starting point for implementing block DMA transfers for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design. The example user code for implementing block DMA transfers performs the following steps: 1.
Calls the kernel-mode driver’s ioctl interface to set up the DMA transfer, specifying the VMEbus address, address modifiers, data size, byte swap mode, and the number of bytes that will be used.
2.
Calls the driver’s ioctl interface to set the transfer mode to block mode DMA.
3.
Calls the Tru64 UNIX read and write interfaces to perform the transfers.
The actual transfers are performed in the driver’s dmaex_strategy interface, which is called as often as necessary during a read or write. (Before each call to dmaex_strategy, a call to the driver’s dmaex_minphys interface defines the maximum size of each DMA transfer.) The dmaex_strategy interface calls several kernel interfaces to accomplish the DMA transfer. First it calls the vba_set_dma_addr interface to specify the VMEbus address, the VMEbus address modifiers, the data size, the swap mode, and the direction of the transfer (DMA_IN or DMA_OUT). Either DMA_IN or DMA_OUT indicate that the VMEbus adapter’s hardware DMA engine will be used. The value returned from vba_set_dma_addr is then passed to the dma_map_load interface, which allocates resources needed for the master block DMA transfer. Next, the vba_dma interface is invoked to cause the DMA transfer to occur. Once the DMA has completed, the DMA resources are released by calling the dma_map_unload interface. The following sections explain the user and driver code required for block DMA transfers:
VMEbus Device Driver Example 1–81
User-level code
Section 1.8.1
I/O Control (ioctl) Section
Section 1.8.2
Read and Write Device Section
Section 1.8.3
Strategy Section
Section 1.8.4
1.8.1 User-Level Code The following test, from dmaex_test.c, shows how user-level code can program master block DMA transfers, assuming the required driver-level support code has been implemented. The test requires three parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The total number of bytes to transfer
•
data.data[1] — The starting VMEbus address of the DMA transfer
•
data.data[2] — The VMEbus address modifiers, data size, and byte swap mode; values are defined in the io/dec/vme/vbareg.h header file _______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. The third argument to the test specifies whether software byte swapping is needed (1) or not (0); and if software byte swapping is needed, the fourth argument provides the byte swap mode. int dmaex_strategy_dma_block_xfer_test(int fd, struct dmaex_ioctl_data *data, int sw_byte_swap_needed, vme_atype_t byte_swap_mode) { int buf_size,err,i,xfer_mode; int *read_buffer, *write_buffer; int data_error = 0; int byte_swap_needed = 0; if ( (!data->data[0]) || (!(data->data[2] & VME_BITS_MASK)) ) { 1 printf("No test parameters specified \n"); return(1); } buf_size = (int)data->data[0];
1–82 VMEbus Device Driver Example
if ((sw_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) { if ((byte_swap_mode == VME_BS_QUAD) && 2 (((vme_atype_t)data->data[2] & VME_DSIZE_MASK) != VME_D64)) { perror("VME_BS_QUAD requires VME_D64 to be specified"); return(1); } byte_swap_needed = 1; 3 } write_buffer = (int *)malloc(buf_size); 4 if(!(write_buffer)) { perror("write malloc failed"); return(1); } read_buffer = (int *)malloc(buf_size); 5 if (!(read_buffer)) { free(write_buffer); perror("read malloc failed"); return(1); } if ( ioctl(fd, SET_STRATEGY_INFO_BLK_DMA, data) != 0 ) { 6 free(read_buffer); free(write_buffer); perror("error in ioctl SET_STRATEGY_INFO_BLK_DMA"); return(1); } data->data[0] = (unsigned long)BLOCK_DMA_MODE; if ( ioctl(fd, SET_STRATEGY_XFER_MODE, data) != 0 ) { 7 free(read_buffer); free(write_buffer); perror("error in ioctl SET_STRATEGY_XFER_MODE"); return(1); } if ( ioctl(fd, GET_STRATEGY_INFO_BLK_DMA, data) != 0 ) { 8 free(read_buffer); free(write_buffer); perror("error in ioctl GET_STRATEGY_INFO_BLK_DMA"); return(1); } printf("DMA mapping setup info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" VME ending address = 0x%x\n",(unsigned int)data->data[3]); printf(" VME bytes transferred = 0x%x\n",(unsigned int)data->data[4]); if ( ioctl(fd, GET_STRATEGY_XFER_MODE, data) != 0 ) { free(read_buffer); free(write_buffer); perror("error in ioctl GET_STRATEGY_XFER_MODE"); return(1); } xfer_mode = (int)data->data[0]; switch (xfer_mode) { case PIO_XFER_MODE: printf("PIO transfer mode\n"); break; case DEVICE_DMA_MODE: printf("Device DMA transfer mode\n"); break;
VMEbus Device Driver Example 1–83
case BLOCK_DMA_MODE: printf("Block DMA transfer mode\n"); break; default: printf("Invalid Transfer Mode Specified\n"); } for (i = 0; i < (buf_size/sizeof(int)); i++) { 9 write_buffer[i] = i; read_buffer[i] = 0; } if (byte_swap_needed) { 10 if (dmaex_sw_byte_swap(write_buffer,write_buffer, buf_size,byte_swap_mode) == 0) { free(write_buffer); free(read_buffer); perror("Error detected byte swapping write buffer data"); return(1); } } if ((err = write(fd, write_buffer, buf_size)) < 0) { 11 free(write_buffer); free(read_buffer); perror("BLOCK_DMA_MODE write error"); } else { if ( ioctl(fd, GET_STRATEGY_INFO_BLK_DMA, data) != 0 ) { 12 free(read_buffer); free(write_buffer); perror("error in ioctl GET_STRATEGY_INFO_BLK_DMA"); return(1); } printf("DMA mapping info following write: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" VME ending address = 0x%x\n",(unsigned int)data->data[3]); printf(" VME bytes transferred = 0x%x\n",(unsigned int)data->data[4]); if ((err = read(fd, read_buffer, buf_size)) < 0) { 13 free(write_buffer); free(read_buffer); perror("BLOCK_DMA_MODE read error"); } else { if ( ioctl(fd, GET_STRATEGY_INFO_BLK_DMA, data) != 0 ) { 14 free(read_buffer); free(write_buffer); perror("error in ioctl GET_STRATEGY_INFO_BLK_DMA"); return(1); } printf("DMA mapping info following read: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME starting address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" VME ending address = 0x%x\n",(unsigned int)data->data[3]); printf(" VME bytes transferred = 0x%x\n",(unsigned int)data->data[4]); if (byte_swap_needed) { 15 dmaex_sw_byte_swap(write_buffer, write_buffer, buf_size, byte_swap_mode); dmaex_sw_byte_swap(read_buffer,
1–84 VMEbus Device Driver Example
read_buffer, buf_size, byte_swap_mode); } printf("Comparing data transferred using adapter’s DMA engine\n"); for (i = 0; i < (buf_size/sizeof(int)); i++) { 16 if (write_buffer[i] != read_buffer[i]) { printf ("BLOCK_DMA_MODE data error - index 0x%x good = 0x%x bad = 0x%x\n", i,write_buffer[i],read_buffer[i]); data_error++; if (data_error > 16) break; } } } } if ( ioctl(fd, CLR_STRATEGY_INFO_BLK_DMA, data) != 0 ) { 17 free(read_buffer); free(write_buffer); perror("error in ioctl CLR_STRATEGY_INFO_BLK_DMA"); return(1); } free(write_buffer); 18 free(read_buffer); return(0); }
1
Verifies that the test caller specified a byte count and a VMEbus address modifiers argument. If not, the interface displays an error message and returns the error status 1 to the test caller. The second parameter, the starting VMEbus address of the DMA transfer, is not verified because an address of zero (0) is acceptable.
2
If the quadword byte swap mode (VME_BS_QUAD) is specified, verifies that a VMEbus data size of 64 (VME_D64) also is specified. This is a hardware restriction of the VIP/VIC64 VMEbus adapter.
3
If software byte swapping is needed, sets the local flag byte_swap_needed. Software byte swapping is needed if the test caller passed 1 for the sw_byte_swap_needed test argument and a byte swap mode other than VME_BS_NOSWAP for the byte_swap_mode test argument. This implies that the caller has determined both that the data transfer requires byte swapping and that the system’s VMEbus adapter does not provide hardware byte swapping. (The caller may also select software byte swapping over hardware byte swapping where both are available.) The swap arguments provided by dmaex_test to this test are based in part on a platform-specific flag value generated by GET_HW_BYTE_SWAP_INFO, a driver ioctl command that dmaex_test uses to determine the swapping capabilities of the system’s VMEbus adapter. See Section 1.16 for more information.
VMEbus Device Driver Example 1–85
4
Allocates a write buffer to hold data to be transferred from system memory to the VMEbus by the test.
5
Allocates a read buffer to receive data transferred from the VMEbus to system memory by the test.
6
Calls the SET_STRATEGY_INFO_BLK_DMA ioctl command to save VMEbus mapping information for later use by the dmaex_read, dmaex_write, and dmaex_strategy interfaces. According to the dmaex driver design for block DMA transfers, this command must be invoked prior to calling the file system’s read and write operations. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SET_STRATEGY_INFO_BLK_DMA command takes three arguments, in this case the same three arguments that were passed to the test itself: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
Section 1.8.2.2 describes the implementation of the SET_STRATEGY_INFO_BLK_DMA command. 7
Calls the SET_STRATEGY_XFER_MODE ioctl command to specify the mode of data transfer, BLOCK_DMA_MODE, to be performed using read and write operations. According to the dmaex driver design for block DMA transfers, this command must be invoked prior to calling the file system’s read and write operations. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block containing command arguments. The SET_STRATEGY_XFER_MODE command takes one argument: data->data[0] — The data transfer mode, BLOCK_DMA_MODE Section 1.8.2.5 describes the implementation of the SET_STRATEGY_XFER_MODE command.
8
For testing purposes, the user code calls the GET_STRATEGY_INFO_BLOCK_DMA and GET_STRATEGY_XFER_MODE ioctl commands and displays the
1–86 VMEbus Device Driver Example
results of the previous calls to SET_STRATEGY_INFO_BLOCK_DMA and SET_STRATEGY_XFER_MODE.
9
10
Section 1.8.2.3 and Section 1.8.2.6 describe the implementations of the GET_STRATEGY_INFO_BLK_DMA and GET_STRATEGY_XFER_MODE commands. Initializes the write and read buffers to be used in the data transfers. For testing purposes, the write buffer is filled with a 32-bit binary count pattern and the read buffer is cleared. If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the write data as 32-bit integer values based on the specified byte swap mode. After swapping, write_buffer contains the byte-swapped data. The data will be swapped back to its original value after the read. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments:
11
12
13
•
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. Calls the write interface to perform a block DMA data transfer from the write buffer to the VMEbus address specified in the earlier call to SET_STRATEGY_INFO_BLK_DMA. The VMEbus adapter’s hardware DMA engine performs the physical data transfers. The write interface takes three arguments: the file descriptor of the open device, a write buffer, and a buffer size. Calling write causes the dmaex_write driver interface to be invoked, then the dmaex_minphys and dmaex_strategy driver interfaces to be invoked, as many times as necessary, to accomplish the block DMA data transfer. Section 1.8.3 and Section 1.8.4 describe the implementations of these driver interfaces. For testing purposes, the user code calls the GET_STRATEGY_INFO_BLOCK_DMA ioctl command and displays the results of the write, including the starting and ending VMEbus addresses and total bytes transferred. Calls the read interface to perform a block DMA data transfer to the read buffer from the VMEbus address specified in the earlier call to
VMEbus Device Driver Example 1–87
SET_STRATEGY_INFO_BLK_DMA. The VMEbus adapter’s hardware DMA engine performs the physical data transfers. The read interface takes three arguments: the file descriptor of the open device, a read buffer, and a buffer size. Calling read causes the dmaex_read driver interface to be invoked, then the dmaex_minphys and dmaex_strategy driver interfaces to be invoked, as many times as necessary, to accomplish the block DMA data transfer. Section 1.8.3 and Section 1.8.4 describe the implementations of these driver interfaces. 14
For testing purposes, the user code calls the GET_STRATEGY_INFO_BLOCK_DMA ioctl command and displays the results of the read, including the starting and ending VMEbus addresses and total bytes transferred.
15
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the read and write data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer and write_buffer contain the byte-swapped data. The write data is restored to its original value. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 16
For testing purposes, the user code compares the read data to the original write data, reporting any difference as an error.
17
Calls the CLR_STRATEGY_INFO_BLK_DMA ioctl command to clear VMEbus mapping information previously set by SET_STRATEGY_INFO_BLK_DMA and modified by the block DMA transfers. The CLR_STRATEGY_INFO_BLK_DMA command takes no arguments. Section 1.8.2.4 describes the implementation of the CLR_STRATEGY_INFO_BLK_DMA command.
18
Releases the write and read buffer memory used for testing and returns success (0) to the test caller.
1–88 VMEbus Device Driver Example
1.8.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support block DMA transfers: Implementing the dmaex_ioctl interface
Section 1.8.2.1
Implementing SET_STRATEGY_INFO_BLK_DMA
Section 1.8.2.2
Implementing GET_STRATEGY_INFO_BLK_DMA
Section 1.8.2.3
Implementing CLR_STRATEGY_INFO_BLK_DMA
Section 1.8.2.4
Implementing SET_STRATEGY_XFER_MODE
Section 1.8.2.5
Implementing GET_STRATEGY_XFER_MODE
Section 1.8.2.6
1.8.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation). The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifyies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status). The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t
dev,
VMEbus Device Driver Example 1–89
int cmd, struct dmaex_ioctl_data *data, int flag) { int struct dmaex_softc struct controller int struct proc sg_entry_t ihandler_t struct vme_handler_info unsigned int unsigned int
unit = *sc = *ctlr = ret_val = *proc_p; dmaex_sg; handler; info; irq; vector;
minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 . . . case SET_STRATEGY_XFER_MODE 4 . . . break; case GET_STRATEGY_XFER_MODE 5 . . . break; . . . case SET_STRATEGY_INFO_BLK_DMA 6 . . . break; case GET_STRATEGY_INFO_BLK_DMA 7 . . . break; case CLR_STRATEGY_INFO_BLK_DMA 8 . . . break; . . . default: ret_val = EINVAL; 9 break; } return(ret_val); 10 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
1–90 VMEbus Device Driver Example
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS)
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number.
3
Dispatches control, based on the ioctl command value, to a case block that implements the requested command.
4
Section 1.8.2.5 describes the implementation of the SET_STRATEGY_XFER_MODE command.
5
Section 1.8.2.6 describes the implementation of the GET_STRATEGY_XFER_MODE command.
6
Section 1.8.2.2 describes the implementation of the SET_STRATEGY_INFO_BLK_DMA command.
7
Section 1.8.2.3 describes the implementation of the GET_STRATEGY_INFO_BLK_DMA command.
8
Section 1.8.2.4 describes the implementation of the CLR_STRATEGY_INFO_BLK_DMA command.
9
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
10
Returns the last-set dmaex_ioctl completion-status value.
1.8.2.2 Implementing SET_STRATEGY_INFO_BLK_DMA The SET_STRATEGY_INFO_BLK_DMA ioctl command is called to prepare for performing block DMA transfers. The VMEbus adapter’s hardware DMA engine will perform the physical data transfers. The command sets up VMEbus mapping information for later use by the dmaex_read, dmaex_write, and dmaex_strategy interfaces. VMEbus mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
VMEbus Device Driver Example 1–91
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
Before other driver interfaces can use the mapping information, you must also call the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to BLOCK_DMA_MODE. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SET_STRATEGY_INFO_BLK_DMA ioctl command in the dmaex device driver: case SET_STRATEGY_INFO_BLK_DMA: DMAEX_DBG2("dmaex_ioctl: SET_STRATEGY_INFO_BLK_DMA\n"); if ( (!(unsigned int)data->data[0]) || 1 (!((unsigned int)data->data[2] & VME_BITS_MASK)) ) { printf("dmaex_ioctl: SET_STRATEGY_INFO_BLK_DMA, invalid parameters\n"); ret_val = EINVAL; } else { sc->dma_is_set = 1; 2 sc->dma_vme_size = (unsigned int)data->data[0]; 3 sc->dma_vme_addr = (vme_addr_t)data->data[1]; sc->dma_vme_am = (vme_atype_t)(data->data[2] & VME_BITS_MASK); sc->dma_wrk_vme_addr = 0; 4 sc->dma_wrk_vme_size = 0; } break;
1
Verifies that the caller specified a byte count and a VMEbus address modifiers argument. The second parameter, the starting VMEbus address to map, is not verified because an address of zero (0) is acceptable.
2
Sets a flag in the driver information structure (previously declared and initialized in dmaex_ioctl code) to indicate that DMA transfer setup has occurred.
3
Copies the DMA mapping information provided by the caller into the dma_vme_size, dma_vme_addr, and dma_vme_am fields of the driver information structure.
4
Zeroes the dma_wrk_vme_addr and dma_wrk_vme_size fields of the driver information structure, which subsequently are updated by the dmaex_strategy interface to record the ending address and transfer size (in bytes) for each DMA transfer. User code can obtain these values after each transfer by calling the GET_STRATEGY_INFO_BLK_DATA ioctl command.
1.8.2.3 Implementing GET_STRATEGY_INFO_BLK_DMA The GET_STRATEGY_INFO_BLK_DMA ioctl command returns the current VMEbus mapping information for the device. You use it only in
1–92 VMEbus Device Driver Example
connection with block DMA transfers. The command returns the initial mapping information specified to the SET_STRATEGY_INFO_BLK_DMA command, along with ending-address and transfer-size information updated by the dmaex_strategy interface during the most recent data transfer. The mapping information is returned through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address mapped for the transfer
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The ending VMEbus address of the most recent transfer
•
data->data[4] — The total size (in bytes) of the most recent transfer
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_STRATEGY_INFO_BLK_DMA ioctl command in the dmaex device driver: case GET_STRATEGY_INFO_BLK_DMA: DMAEX_DBG2("dmaex_ioctl: GET_STRATEGY_INFO_BLK_DMA\n"); data->data[0] data->data[1] data->data[2] data->data[3] data->data[4] break;
= = = = =
(unsigned (unsigned (unsigned (unsigned (unsigned
long)sc->dma_vme_size; 1 long)sc->dma_vme_addr; long)sc->dma_vme_am; long)sc->dma_wrk_vme_addr; 2 long)sc->dma_wrk_vme_size;
1
Copies the initial mapping information, previously stored in the driver information structure by the SET_STRATEGY_INFO_BLK_DMA command, back into the first three fields of the data parameter.
2
Copies VMEbus ending-address and transfer-size information, updated by the dmaex_strategy interface during the most recent block DMA data transfer, into the fourth and fifth fields of the data parameter. If no transfer has yet occurred, both fields will be zero (0).
1.8.2.4 Implementing CLR_STRATEGY_INFO_BLK_DMA The CLR_STRATEGY_INFO_BLK_DMA ioctl command clears the current VMEbus mapping information for the device. You use it only in connection with block DMA transfers. The command clears the initial mapping information specified to the SET_STRATEGY_INFO_BLK_DMA command, along with ending-address and transfer-size information
VMEbus Device Driver Example 1–93
updated by the dmaex_strategy interface. No parameters are passed to this command. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the CLR_STRATEGY_INFO_BLK_DMA ioctl command in the dmaex device driver: case CLR_STRATEGY_INFO_BLK_DMA: DMAEX_DBG2("dmaex_ioctl: CLR_STRATEGY_INFO_BLK_DMA\n"); sc->dma_is_set = 0; 1 sc->dma_vme_size = 0; 2 sc->dma_vme_addr = 0; sc->dma_vme_am = 0; sc->dma_wrk_vme_addr = 0; 3 sc->dma_wrk_vme_size = 0; break;
1
Clears the flag in the driver information structure that indicates whether or not DMA transfer setup has occurred.
2
Clears the initial mapping information previously stored in the dma_vme_size, dma_vme_addr, and dma_vme_am fields of the driver information structure by the SET_STRATEGY_INFO_BLK_DMA command.
3
Clears dma_wrk_vme_addr and dma_wrk_vme_size, the fields of the driver information data that are updated by the dmaex_strategy interface during block DMA transfers.
1.8.2.5 Implementing SET_STRATEGY_XFER_MODE The SET_STRATEGY_XFER_MODE ioctl command is called to prepare for performing block DMA transfers. The VMEbus adapter’s hardware DMA engine will perform the physical data transfers. The command sets the data transfer mode to be used by the dmaex_strategy interface during reads and writes. The transfer mode, which should equal BLOCK_DMA_MODE for block DMA transfers, is specified by the ioctl caller in the data parameter: data->data[0] — The data transfer mode (PIO_XFER_MODE, DEVICE_DMA_MODE, or BLOCK_DMA_MODE) Before driver interfaces can use the BLOCK_DMA_MODE data transfer mode, you must also call the SET_STRATEGY_INFO_BLOCK_DMA ioctl command to set up VMEbus mapping information. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SET_STRATEGY_XFER_MODE ioctl command in the dmaex device driver:
1–94 VMEbus Device Driver Example
case SET_STRATEGY_XFER_MODE: DMAEX_DBG2("dmaex_ioctl: SET_STRATEGY_XFER_MODE\n"); switch ((int)data->data[0]) { case PIO_XFER_MODE: case DEVICE_DMA_MODE: case BLOCK_DMA_MODE: sc->strategy_xfer_mode = (int)data->data[0]; 1 break; default: ret_val = EINVAL; 2 } break;
1
Copies the data transfer mode provided by the caller into the strategy_xfer_mode field of the driver information structure.
2
If an invalid data transfer mode value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1.8.2.6 Implementing GET_STRATEGY_XFER_MODE The GET_STRATEGY_XFER_MODE ioctl command returns the current data transfer mode for the device, as specified in a previous call to the SET_STRATEGY_XFER_MODE command. The transfer mode is returned through the data parameter of the ioctl interface: data->data[0] — The data transfer mode (PIO_XFER_MODE, DEVICE_DMA_MODE, or BLOCK_DMA_MODE) The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_STRATEGY_XFER_MODE ioctl command in the dmaex device driver: case GET_STRATEGY_XFER_MODE: DMAEX_DBG2("dmaex_ioctl: GET_STRATEGY_XFER_MODE\n"); data->data[0] = (unsigned long)sc->strategy_xfer_mode; 1 break;
1
Copies the data transfer mode value, previously stored in the driver information structure by the SET_STRATEGY_XFER_MODE command, back into the data parameter.
1.8.3 Read and Write Device Section The following sections explain how to implement Read and Write Device Section interfaces that support block DMA transfers:
VMEbus Device Driver Example 1–95
Implementing the dmaex_read interface
Section 1.8.3.1
Implementing the dmaex_write interface
Section 1.8.3.2
1.8.3.1 Implementing the dmaex_read Interface The file system invokes the dmaex_read interface whenever user code performs a read operation on the dmaex device. The read interface called by user code takes three arguments: the file descriptor of the open device, a read buffer, and a buffer size. The dmaex_read interface invoked for you by the file system takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
A uio structure that contains information for transferring data to and from the address space of the user’s process
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_read interface calls the physio kernel interface to perform a buffer lock, check the buffer, and set up an I/O packet. The physio interface then calls the dmaex_minphys and dmaex_strategy driver interfaces to access the device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the read can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of block DMA, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to BLOCK_DMA_MODE before issuing a read to the dmaex device. The following code shows the implementation of the dmaex_read interface: static int dmaex_read(dev_t dev, struct uio *uio, int flag) { register int unit = minor(dev); 1 register struct dmaex_softc *sc = &dmaex_softc[unit]; register struct buf *bp = &sc->io_buf; DMAEX_DBG1("dmaex_read: dmaex%d\n",unit); if ((unit >= NDMAEX ) || (unit < 0)) 2 return(EIO);
1–96 VMEbus Device Driver Example
if (sc->strategy_xfer_mode == BLOCK_DMA_MODE) { if (!(sc->dma_is_set)) { 3 printf("dmaex_read: ioctl:SET_STRATEGY_INFO_BLK_DMA not invoked\n"); return(EINVAL); } else { sc->dma_wrk_vme_addr = sc->dma_vme_addr; 4 sc->dma_wrk_vme_size = 0; } } else { if (sc->strategy_xfer_mode == PIO_XFER_MODE) { if (!(sc->vme_handle)) { printf("dmaex_read: ioctl:SETUP_VME_FOR_STRATEGY_PIO not invoked\n"); return(EINVAL); } else { sc->vme_wrk_handle = sc->vme_handle; sc->vme_wrk_size = 0; } } } bzero(bp,sizeof(struct buf)); 5 return (physio((void (*)())dmaex_strategy, bp, dev, B_READ, (void (*)())dmaex_minphys, uio)); 6 }
1
Declares and initializes variables required for read I/O, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_read call. The device minor number is stored in the unit variable.
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure.
•
A pointer to the buf structure associated with this device. This pointer later is provided to the physio interface, which performs the block DMA transfer. The buf structure contains information used by the driver to handle I/O requests, such as the binary status flags, the major/minor device numbers, and the address of the I/O buffer associated with this dmaex device. The sc->io_buf field of the device’s driver information structure is accessed to reference the correct buf structure.
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the EIO error constant is returned to indicate an I/O error.
3
Performs mode-specific error checking. For block DMA transfers (data transfer mode = BLOCK_DMA_MODE), the interface verifies that the user code called the SET_STRATEGY_INFO_BLK_DMA ioctl command to set up mapping information before issuing a read. If the sc->dma_is_set flag in the driver information structure is clear (0),
VMEbus Device Driver Example 1–97
the interface displays an error message and returns the EINVAL error status to the file system. 4
Performs mode-specific variable initialization. For block DMA transfers, the interface sets up two fields of the driver information structure to contain working values for the most recent transfer’s ending VMEbus address and total size. Because no transfer has yet occurred, field sc->dma_wrk_vme_addr is initialized with the starting VMEbus address mapped for the transfer and field sc->dma_wrk_vme_size is initialized to 0. These values will be updated on each iteration of the dmaex_strategy interface during the read, and are the source for the ending VMEbus address and total size information returned by the GET_STRATEGY_INFO_BLK_DMA ioctl command.
5
Calls the bzero interface to zero the read buffer. The bzero interface takes two arguments: a buffer pointer and the buffer size (in bytes).
6
Calls the physio kernel interface to implement character I/O. The physio interface will perform a buffer lock on the caller-provided I/O buffer, check the buffer, set up the I/O packet, and invoke caller-specified minphys and strategy interfaces to perform the data transfer. Upon return from the physio call, control and transfer completion status return immediately to the dmaex_read caller (the file system). The physio interface takes six arguments: •
A pointer to a strategy interface, which for this driver is the dmaex_strategy interface. Section 1.8.4.2 discusses the implementation of dmaex_strategy.
•
A pointer to the buf structure associated with this dmaex device.
•
The device number, which in this call is the dev argument supplied to this interface by the file system.
•
A read/write flag. In this call, the B_READ constant is passed to indicate a read operation.
•
A pointer to a minphys interface. A device driver can call the minphys kernel interface, which bounds the data transfer size, or provide its own minphys interface. In this call, the driver passes a driver-specific minphys interface called dmaex_minphys. Section 1.8.4.1 discusses the implementation of dmaex_minphys.
•
A pointer to a uio structure, which in this call is the uio argument supplied to this interface by the file system.
1–98 VMEbus Device Driver Example
1.8.3.2 Implementing the dmaex_write Interface The file system invokes the dmaex_write interface whenever user code performs a write operation on the dmaex device. The flow of execution corresponds exactly to that described for read. The write interface called by user code takes three arguments: the file descriptor of the open device, a write buffer, and a buffer size. The dmaex_write interface invoked for you by the file system takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
A uio structure that contains information for transferring data to and from the address space of the user’s process
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_write interface calls the physio kernel interface to perform a buffer lock, check the buffer, and set up an I/O packet. The physio interface then calls the dmaex_minphys and dmaex_strategy driver interfaces to access the device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the write can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of block DMA, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to BLOCK_DMA_MODE before issuing a write to the dmaex device. The following code shows the implementation of the dmaex_write interface: static int dmaex_write(dev_t dev, struct uio *uio, int flag) { register int unit = minor(dev); register struct dmaex_softc *sc = &dmaex_softc[unit]; register struct buf *bp = &sc->io_buf; DMAEX_DBG1("dmaex_write: dmaex%d\n",unit); if ((unit >= NDMAEX) || (unit < 0)) return (EIO);
VMEbus Device Driver Example 1–99
if (sc->strategy_xfer_mode == BLOCK_DMA_MODE) { if (!(sc->dma_is_set)) { printf("dmaex_write: ioctl:SET_STRATEGY_INFO_BLK_DMA not invoked\n"); return(EINVAL); } else { sc->dma_wrk_vme_addr = sc->dma_vme_addr; sc->dma_wrk_vme_size = 0; } } else { if (sc->strategy_xfer_mode == PIO_XFER_MODE) { if (!(sc->vme_handle)) { printf("dmaex_write: ioctl:SETUP_VME_FOR_STRATEGY_PIO not invoked\n"); return(EINVAL); } else { sc->vme_wrk_handle = sc->vme_handle; sc->vme_wrk_size = 0; } } } bzero(bp,sizeof(struct buf)); return (physio((void (*)())dmaex_strategy, bp, dev, B_WRITE, 1 (void (*)())dmaex_minphys, uio)); }
1
The dmaex_write interface is almost identical to the dmaex_read interface. The only difference (disregarding minor changes in the text of debug and error messages) is that dmaex_write uses the B_WRITE bit instead of the B_READ bit for the read/write flag to indicate that this is a write operation.
1.8.4 Strategy Section The following sections explain how to implement a Strategy Section that supports block DMA transfers: Implementing the dmaex_minphys interface
Section 1.8.4.1
Implementing the dmaex_strategy interface
Section 1.8.4.2
1.8.4.1 Implementing the dmaex_minphys Interface The dmaex_minphys interface is specified as a parameter to the physio interface invoked from dmaex_read or dmaex_write. During a file system read or write, the physio interface will call dmaex_minphys once or several times, depending on the requested block DMA transfer size. It is called prior to each invocation of dmaex_strategy from physio. You use the dmaex_minphys interface to limit the size of data transfers. This may represent a software limit to throttle the transfer or a hardware-imposed limit. If the requested byte count from physio exceeds the maximum byte count contained in the dmaex_max_dma variable, a
1–100 VMEbus Device Driver Example
revised byte count equalling the maximum is returned to the physio interface. The physio interface then calls the strategy interface with this byte count to perform the transfer. The dmaex_minphys interface takes one argument: a pointer to the buf structure associated with this dmaex device. The physio interface passes the same buf pointer passed to it by dmaex_read or dmaex_write. The following code shows the implementation of the dmaex_minphys interface: static int dmaex_minphys (register struct buf *bp) { DMAEX_DBG1("dmaex_minphys: entered\n"); if (bp->b_bcount > dmaex_max_dma) 1 bp->b_bcount = dmaex_max_dma; return(ESUCCESS); }
1
Compares the user-requested transfer size, contained in the b_count field of the device’s buf structure, to the maximum-allowable transfer size (in bytes), contained in the dmaex_max_dma variable. If the requested size exceeds the limit defined for this device, the b_count field is reset to the maximum allowable, dmaex_max_dma.
1.8.4.2 Implementing the dmaex_strategy Interface The dmaex_strategy interface is specified as a parameter to the physio interface invoked from dmaex_read or dmaex_write. During a file system read or write, the physio interface will call dmaex_strategy once or several times, depending on the requested block DMA transfer size. The dmaex_strategy interface is used by physio to perform all or part of a user-requested read or write on a dmaex device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the read or write can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of block DMA, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to BLOCK_DMA_MODE before issuing a read or write to the dmaex device. You control the maximum size of the data transfer by using the dmaex_minphys interface, which is invoked by physio just before every call to dmaex_strategy. The dmaex_strategy interface takes one argument: a pointer to the buf structure associated with this dmaex device. The physio interface passes the same buf pointer passed to it by dmaex_read or dmaex_write.
VMEbus Device Driver Example 1–101
The following code shows the implementation of the dmaex_strategy interface for block DMA transfers: static int dmaex_strategy(struct buf *bp) { register int unit = minor(bp->b_dev); 1 register struct controller *ctlr = dmaex_ctlr[unit]; register struct dmaex_softc *sc = &dmaex_softc[unit]; sg_entry_t dmaex_sg; unsigned long b_count; register int *mem_ptr; int flags,i,j; char csr; DMAEX_DBG1("dmaex_strategy: dmaex%d\n",unit); if ( (!ctlr) || (!sc) ) 2 return (EINVAL); sc->dma_handle = (dma_handle_t) NULL; 3 switch (sc->strategy_xfer_mode) { 4 case PIO_XFER_MODE: . . . break; case DEVICE_DMA_MODE: . . . break; case BLOCK_DMA_MODE: flags = sc->dma_vme_am | DMA_SLEEP | DMA_GUARD_UPPER | DMA_ALL; if (bp->b_flags & B_READ) 5 flags |= DMA_IN; else flags |= DMA_OUT; flags = vba_set_dma_addr(ctlr, flags, sc->dma_wrk_vme_addr); 6 b_count = dma_map_load ((unsigned long) bp->b_bcount, 7 (vm_offset_t) bp->b_un.b_addr, bp->b_proc, ctlr, &sc->dma_handle, 0, flags); if ( (!b_count) || (!sc->dma_handle) || (b_count != (unsigned long)bp->b_bcount)) { printf("dmaex_strategy: dma_map_load error\n"); bp->b_error = EIO; bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; iodone(bp); break; } dmaex_sg = dma_get_curr_sgentry (sc->dma_handle); 8 DMAEX_DBG2("dmaex_strategy: sg.ba = 0x%lx sg.bc 0x%lx\n", dmaex_sg->ba, dmaex_sg->bc);
1–102 VMEbus Device Driver Example
b_count = vba_dma(ctlr, sc->dma_handle); 9 if ((!b_count) || (b_count == -1) || (b_count != (unsigned long)bp->b_bcount)) { printf("dmaex_strategy: error during DMA transfer\n"); bp->b_error = EIO; bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; dma_map_unload(DMA_DEALLOC,sc->dma_handle); sc->dma_handle = (dma_handle_t) NULL; iodone(bp); break; } bp->b_resid = 0; 10 sc->dma_wrk_vme_addr += (vme_addr_t)b_count; sc->dma_wrk_vme_size += (unsigned int)b_count; dma_map_unload(DMA_DEALLOC,sc->dma_handle); sc->dma_handle = (dma_handle_t) NULL; iodone(bp); break; } /* end of strategy_xfer_mode */ return(ESUCCESS); 11 }
1
Declares and initializes variables required for data transfers, including: •
The device minor number, initialized by invoking the minor interface and passing it the b_dev value encoded in the caller-supplied buf structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
2
3
4
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure Checks the validity of the controller and driver structure pointers. If not valid, the EINVAL error constant is returned to indicate an invalid structure pointer. Zeroes the DMA handle field in the driver information structure; this ensures the proper operation of the dma_map_load interface without previously calling dma_map_alloc. A subsequent call to dma_map_load will write a DMA handle into this field. Dispatches control, based on the data transfer mode value stored in the strategy_xfer_mode field of the driver information structure, to a case block that implements the requested form of data transfer. In the case of block DMA transfers, user code must previously have called the SET_STRATEGY_XFER_MODE ioctl command to set the transfer mode to the value BLOCK_MODE_DMA.
VMEbus Device Driver Example 1–103
5
6
Constructs a flags argument in preparation for calling the vba_set_dma_addr and dma_map_load interfaces, as follows: 1.
Copies the VMEbus address modifiers that user code set up with a call to the SET_STRATEGY_INFO_BLK_DMA ioctl command. VMEbus flag values are defined in the io/dec/vme/vbareg.h header file.
2.
Adds the DMA_SLEEP, DMA_GUARD_UPPER, and DMA_ALL flags. DMA flag values are defined in the io/common/devdriver.h header file.
3.
Adds either DMA_IN, if the data transfer is a read, or DMA_OUT, if the transfer is a write. The presence of either DMA_IN or DMA_OUT selects the VMEbus adapter’s DMA engine to perform the data transfer.
Calls the vba_set_dma_addr interface to set up the VMEbus address and modifiers for a pending DMA transfer. For block DMA transfers, the VMEbus address and modifiers must be specified using this interface. The vba_set_dma_addr interface takes three arguments:
7
•
The controller information structure for this dmaex device
•
A flags argument that contains DMA and VMEbus address modifier flags, as constructed in the previous step; the return value of this interface is a (potentially updated) flags value that you supply to the dma_map_load interface
•
The VMEbus working address, which at any given point equals either the most recent transfer’s ending VMEbus address or, if no transfer has yet occurred, the starting VMEbus address initially set up by user code
Calls the dma_map_load interface to allocate, load, and set system resources for the pending DMA transfer. Upon success, dma_map_load returns the number of bytes mapped; additionally, a DMA handle is placed in the dma_handle field of the driver information structure. The dma_map_load interface takes seven arguments: •
The maximum size (in bytes) of the data to be transferred during the DMA transfer. The kernel uses this size to determine the software resources (such as mapping registers and I/O channels) to allocate. The transfer request size passed to dmaex_strategy in the b_count field of the device’s buf structure may reflect the transfer
1–104 VMEbus Device Driver Example
size limit imposed by the call to dmaex_minphys that preceded the call to dmaex_strategy. •
The virtual address at which the DMA transfer occurs. The interface uses this and the third argument to obtain the physical addresses of the system memory pages to load into DMA mapping resources. The address in this example was extracted from the device’s buf structure and specifies the address at which to pull or push the data.
•
A pointer to a proc structure, representing valid context for the virtual address specified in the second argument. The interface uses this pointer to retrieve the pmap needed to translate the virtual address to a physical address. If zero (0) is specified in this argument, the second argument is a kernel address. The argument in this example was extracted from the device’s buf structure and points to the proc structure that represents the process performing the I/O.
•
The controller information structure for this dmaex device. The interface accesses this structure to obtain the bus-specific interfaces and data structures it needs to allocate mapping resources.
•
A pointer to the dma_handle field in the driver information structure, which will receive a DMA handle. A DMA handle represents DMA resources associated with the mapping of an in-memory I/O buffer onto a controller’s I/O bus. This handle provides the information to access bus address/byte count pairs. A bus address/byte count pair is represented by the ba and bc members of an sg_entry structure. Device driver writers can view the DMA handle as the tag to the allocated system resources needed to perform a DMA operation.
•
The maximum-size byte-count value that should be stored in the bc members of the sg_entry structures. This example passes the value zero (0).
•
Flags that represent special conditions the device driver needs the system to support. This example passes the flags argument returned by vba_set_dma_addr. DMA-specific and VMEbus-specific driver condition flags are defined in the io/common/devdriver.h and io/dec/vme/vbareg.h header files, respectively.
Section 4.1 of the driver kit manual Writing VMEbus Device Drivers provides more information about the dma_map_load interface and its arguments.
VMEbus Device Driver Example 1–105
8
Calls the dma_get_curr_sgentry interface to obtain the current sg_entry data structure, which includes the bus address/byte count pair (ba and bc fields) for the just-mapped data transfer. (Note that the returned bus address value should match the VMEbus working address stored in the dma_wrk_vme_addr field of the driver information structure.) The dma_get_curr_sgentry interface takes one argument: the DMA handle returned by the previous call to dma_map_load, as stored in the dma_handle field of the driver information structure.
9
Calls the vba_dma interface to perform the actual block mode DMA transfer. The vba_dma interface blocks until the DMA has completed or an error occurs. The return value of the interface is the actual number of bytes transferred. (Note that the return value should match the byte count stored in the b_bcount field of the device’s buf structure.) The vba_dma interface takes two arguments:
10
11
•
The controller information structure for this dmaex device
•
The DMA handle returned by the previous call to dma_map_load, as stored in the dma_handle field of the driver information structure
To indicate that the transfer completed successfully, the interface: •
Clears the residual buffer count (a residual count indicates an unfinished transfer).
•
Updates the VMEbus ending address and transfer size, stored in the dma_wrk_vme_addr and dma_wrk_vme_size fields of the driver information structure, based on the count of bytes transferred returned by the vba_dma interface. User code can obtain the resulting values by invoking the GET_STRATEGY_INFO_BLK_DMA ioctl command.
•
Releases DMA system resources, by first calling dma_map_unload (specifying the DMA_DEALLOC flag) and then clearing the DMA handle in the device’s driver information structure.
•
Calls the iodone interface (passing the device’s buf structure) to indicate that the I/O operation has completed.
•
Exits from the case block with a break statement.
Returns to the caller (physio) with ESUCCESS completion status and with the VMEbus ending address and byte count updated to reflect the successful data transfer. Depending on whether or not this intermediate data transfer completes the dmaex read or write requested by user code, the physio interface
1–106 VMEbus Device Driver Example
will either return to its caller (dmaex_read or dmaex_write) or issue another pair of calls to dmaex_minphys and dmaex_strategy to carry the user request another step toward completion.
1.9 Programming PIO Data Transfers Performing programmed I/O (PIO) data transfers involves first coding device driver support for calls to the ioctl, read, and write file system interfaces, and then invoking those interfaces from user code. Compaq provides driver and user code templates that you can use as a starting point for implementing PIO transfers for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design. The example user code for implementing PIO transfers performs the following steps: 1.
Calls the kernel-mode driver’s ioctl interface to map a window to the VMEbus address space, specifying the VMEbus address, address modifiers, data size, byte swap mode, the number of bytes, and the memory access mode (sparse or dense) to map for the programmed I/O transfer.
2.
Calls the driver’s ioctl interface to set the transfer mode to programmed I/O.
3.
Calls the Tru64 UNIX read and write interfaces to perform the transfers.
The actual transfers are performed in the driver’s dmaex_strategy interface, which is called as often as necessary during a read or write. (Before each call to dmaex_strategy, a call to the driver’s dmaex_minphys interface defines the maximum size of each PIO transfer.) The dmaex_strategy interface uses one of the following kernel interfaces to perform the PIO transfer: •
For PIO reads, io_copyin or read_io_port
•
For PIO writes, io_copyout or write_io_port
The following sections explain the user and driver code required for PIO transfers:
VMEbus Device Driver Example 1–107
User-level code
Section 1.9.1
I/O Control (ioctl) Section
Section 1.9.2
Read and Write Device Section
Section 1.9.3
Strategy Section
Section 1.9.4
1.9.1 User-Level Code The following test, from dmaex_test.c, shows how user-level code can program PIO transfers, assuming the required driver-level support code has been implemented. The test requires three parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The total number of bytes to transfer
•
data.data[1] — The starting VMEbus address of the PIO transfer
•
data.data[2] — The VMEbus address modifiers, data size, byte swap mode, and memory access mode (sparse or dense); values are defined in the io/dec/vme/vbareg.h header file _______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. The third argument to the test specifies whether software byte swapping is needed (1) or not (0); and if software byte swapping is needed, the fourth argument provides the byte swap mode. For PIO on UNIVERSE II based platforms, the memory access mode is assumed to have been set to dense space before the test was called. int dmaex_strategy_pio_xfer_test(int struct dmaex_ioctl_data int vme_atype_t { int buf_size,err,i,xfer_mode; int *read_buffer, *write_buffer; int data_error = 0; int byte_swap_needed = 0;
fd, *data, sw_byte_swap_needed, byte_swap_mode)
if ( (!data->data[0]) || (!(data->data[2] & VME_BITS_MASK)) ) { 1
1–108 VMEbus Device Driver Example
printf("No test parameters specified \n"); return(1); } buf_size = (int)data->data[0]; if ((sw_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) 2 byte_swap_needed = 1; write_buffer = (int *)malloc(buf_size); 3 if(!(write_buffer)) { perror("write malloc failed"); return(1); } read_buffer = (int *)malloc(buf_size); 4 if (!(read_buffer)) { free(write_buffer); perror("read malloc failed"); return(1); } if ( ioctl(fd, SETUP_VME_FOR_STRATEGY_PIO, data) != 0 ) { 5 free(read_buffer); free(write_buffer); perror("error in ioctl SETUP_VME_FOR_STRATEGY_PIO"); return(1); } data->data[0] = (unsigned long)PIO_XFER_MODE; if ( ioctl(fd, SET_STRATEGY_XFER_MODE, data) != 0 ) { 6 free(read_buffer); free(write_buffer); perror("error in ioctl SET_STRATEGY_XFER_MODE"); return(1); } if ( ioctl(fd, GET_VME_INFO_FOR_STRATEGY_PIO, data) != 0 ) { 7 free(read_buffer); free(write_buffer); perror("error in ioctl GET_VME_INFO_FOR_STRATEGY_PIO"); return(1); } printf("VME mapping setup info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" VME I/O handle = 0x%lx\n",data->data[3]); printf(" VME ending I/O handle = 0x%lx\n",data->data[4]); printf(" VME bytes transferred = 0x%x\n",(unsigned int)data->data[5]); if ( ioctl(fd, GET_STRATEGY_XFER_MODE, data) != 0 ) { free(read_buffer); free(write_buffer); perror("error in ioctl GET_STRATEGY_XFER_MODE"); return(1); } xfer_mode = (int)data->data[0]; switch (xfer_mode) { case PIO_XFER_MODE: printf("PIO transfer mode\n"); break; case DEVICE_DMA_MODE: printf("Device DMA transfer mode\n");
VMEbus Device Driver Example 1–109
break; case BLOCK_DMA_MODE: printf("Block DMA transfer mode\n"); break; default: printf("Invalid Transfer Mode Specified\n"); } for (i = 0; i < (buf_size/sizeof(int)); i++) { 8 write_buffer[i] = i; read_buffer[i] = 0; } if (byte_swap_needed) { 9 if (dmaex_sw_byte_swap(write_buffer,write_buffer, buf_size,byte_swap_mode) == 0) { free(write_buffer); free(read_buffer); perror("Error detected byte swapping write buffer data"); return(1); } } if ((err = write(fd, write_buffer, buf_size)) < 0) { 10 free(write_buffer); free(read_buffer); perror("PIO_XFER_MODE write error"); } else { if ( ioctl(fd, GET_VME_INFO_FOR_STRATEGY_PIO, data) != 0 ) { 11 free(read_buffer); free(write_buffer); perror("error in ioctl GET_VME_INFO_FOR_STRATEGY_PIO"); return(1); } printf("VME mapping info following write: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" VME I/O handle = 0x%lx\n",data->data[3]); printf(" VME ending I/O handle = 0x%lx\n",data->data[4]); printf(" VME bytes transferred = 0x%x\n",(unsigned int)data->data[5]); if ((err = read(fd, read_buffer, buf_size)) < 0) { 12 free(write_buffer); free(read_buffer); perror("PIO_XFER_MODE read error"); } else { if ( ioctl(fd, GET_VME_INFO_FOR_STRATEGY_PIO, data) != 0 ) { 13 free(read_buffer); free(write_buffer); perror("error in ioctl GET_VME_INFO_FOR_STRATEGY_PIO"); return(1); } printf("VME mapping info following read: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" VME I/O handle = 0x%lx\n",data->data[3]); printf(" VME ending I/O handle = 0x%lx\n",data->data[4]); printf(" VME bytes transferred = 0x%x\n",(unsigned int)data->data[5]); if (byte_swap_needed) { 14 dmaex_sw_byte_swap(write_buffer, write_buffer,
1–110 VMEbus Device Driver Example
buf_size, byte_swap_mode); dmaex_sw_byte_swap(read_buffer, read_buffer, buf_size, byte_swap_mode); } printf("Comparing data transferred using PIO operations.\n"); for (i = 0; i < (buf_size/sizeof(int)); i++) { 15 if (write_buffer[i] != read_buffer[i]) { printf ("PIO_XFER_MODE data error - index 0x%x good = 0x%x bad = 0x%x\n", i,write_buffer[i],read_buffer[i]); data_error++; if (data_error > 16) break; } } } } if ( ioctl(fd, UNMAP_VME_FOR_STRATEGY_PIO, data) != 0 ) { 16 free(read_buffer); free(write_buffer); perror("error in ioctl UNMAP_VME_FOR_STRATEGY_PIO"); return(1); } free(write_buffer); 17 free(read_buffer); return(0); }
1
2
3
Verifies that the test caller specified a byte count and a VMEbus address modifiers argument. If not, the interface displays an error message and returns the error status 1 to the test caller. The second parameter, the starting VMEbus address of the PIO transfer, is not verified because an address of zero (0) is acceptable. If software byte swapping is needed, sets the local flag byte_swap_needed. Software byte swapping is needed if the test caller passed 1 for the sw_byte_swap_needed test argument and a byte swap mode other than VME_BS_NOSWAP for the byte_swap_mode test argument. This implies that the caller has determined both that the data transfer requires byte swapping and that the system’s VMEbus adapter does not provide hardware byte swapping. (The caller may also select software byte swapping over hardware byte swapping where both are available.) The swap arguments provided by dmaex_test to this test are based in part on a platform-specific flag value generated by GET_HW_BYTE_SWAP_INFO, a driver ioctl command that dmaex_test uses to determine the swapping capabilities of the system’s VMEbus adapter. See Section 1.16 for more information. Allocates a write buffer to hold data to be transferred from system memory to the VMEbus by the test.
VMEbus Device Driver Example 1–111
4
Allocates a read buffer to receive data transferred from the VMEbus to system memory by the test.
5
Calls the SETUP_VME_FOR_STRATEGY_PIO ioctl command to save VMEbus mapping information for later use by the dmaex_read, dmaex_write, and dmaex_strategy interfaces. According to the dmaex driver design for PIO transfers, this command must be invoked prior to calling the file system’s read and write operations. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SETUP_VME_FOR_STRATEGY_PIO command takes three arguments, in this case the same three arguments that were passed to the test itself: •
data->data[0] — The total number of bytes to map on the VMEbus for the PIO data transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
Section 1.9.2.2 describes the implementation of the SETUP_VME_FOR_STRATEGY_PIO command. 6
Calls the SET_STRATEGY_XFER_MODE ioctl command to specify the mode of data transfer, PIO_XFER_MODE, to be performed using read and write operations. According to the dmaex driver design for PIO transfers, this command must be invoked prior to calling the file system’s read and write operations. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SET_STRATEGY_XFER_MODE command takes one argument: data->data[0] — The data transfer mode, PIO_XFER_MODE Section 1.9.2.5 describes the implementation of the SET_STRATEGY_XFER_MODE command.
7
For testing purposes, the user code calls the GET_VME_INFO_FOR_STRATEGY_PIO and GET_STRATEGY_XFER_MODE ioctl commands and displays the results of the previous calls to SETUP_VME_FOR_STRATEGY_PIO and SET_STRATEGY_XFER_MODE.
1–112 VMEbus Device Driver Example
Section 1.9.2.3 and Section 1.9.2.6 describe the implementations of the GET_VME_INFO_FOR_STRATEGY_PIO and GET_STRATEGY_XFER_MODE commands. 8
Initializes the write and read buffers to be used in the data transfers. For testing purposes, the write buffer is filled with a 32-bit binary count pattern and the read buffer is cleared.
9
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the write data as 32-bit integer values based on the specified byte swap mode. After swapping, write_buffer contains the byte-swapped data. The data will be swapped back to its original value after the read. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 10
Calls the write interface to perform a data transfer from the write buffer to the VMEbus address specified in the earlier call to SETUP_VME_FOR_STRATEGY_PIO, using programmed I/O operations. The write interface takes three arguments: the file descriptor of the open device, a write buffer, and a buffer size. Calling write causes the dmaex_write driver interface to be invoked, then the dmaex_minphys and dmaex_strategy driver interfaces to be invoked, as many times as necessary, to accomplish the PIO data transfer. Section 1.9.3 and Section 1.9.4 describe the implementations of these driver interfaces.
11
For testing purposes, the user code calls the GET_VME_INFO_FOR_STRATEGY_PIO ioctl command and displays the results of the write, including the starting and ending VMEbus I/O handles and total bytes transferred.
12
Calls the read interface to perform a data transfer to the read buffer from the VMEbus address specified in the earlier call to SETUP_VME_FOR_STRATEGY_PIO, using programmed I/O operations.
VMEbus Device Driver Example 1–113
13
14
The read interface takes three arguments: the file descriptor of the open device, a read buffer, and a buffer size. Calling read causes the dmaex_read driver interface to be invoked, then the dmaex_minphys and dmaex_strategy driver interfaces to be invoked, as many times as necessary, to accomplish the PIO data transfer. Section 1.9.3 and Section 1.9.4 describe the implementations of these driver interfaces. For testing purposes, the user code calls the GET_VME_INFO_FOR_STRATEGY_PIO ioctl command and displays the results of the read, including the starting and ending VMEbus I/O handles and total bytes transferred. If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the read and write data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer and write_buffer contain the byte-swapped data. The write data is restored to its original value. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments:
15 16
17
•
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. For testing purposes, the user code compares the read data to the original write data, reporting any difference as an error. Calls the UNMAP_VME_FOR_STRATEGY_PIO ioctl command to clear VMEbus mapping information previously set by SETUP_VME_FOR_STRATEGY_PIO and modified by the PIO data transfers. The UNMAP_VME_FOR_STRATEGY_PIO command takes no arguments. Section 1.9.2.4 describes the implementation of the UNMAP_VME_FOR_STRATEGY_PIO command. Releases the write and read buffer memory used for testing and returns success (0) to the test caller.
1.9.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support PIO transfers:
1–114 VMEbus Device Driver Example
Implementing the dmaex_ioctl interface
Section 1.9.2.1
Implementing SETUP_VME_FOR_STRATEGY_PIO
Section 1.9.2.2
Implementing GET_VME_INFO_FOR_STRATEGY_PIO
Section 1.9.2.3
Implementing UNMAP_VME_FOR_STRATEGY_PIO
Section 1.9.2.4
Implementing SET_STRATEGY_XFER_MODE
Section 1.9.2.5
Implementing GET_STRATEGY_XFER_MODE
Section 1.9.2.6
1.9.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation). The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status). The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
VMEbus Device Driver Example 1–115
sg_entry_t ihandler_t struct vme_handler_info unsigned int unsigned int
dmaex_sg; handler; info; irq; vector;
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 . . . case SET_STRATEGY_XFER_MODE 4 . . . break; case GET_STRATEGY_XFER_MODE 5 . . . break; . . . case SETUP_VME_FOR_STRATEGY_PIO 6 . . . break; case GET_VME_INFO_FOR_STRATEGY_PIO 7 . . . break; case UNMAP_VME_FOR_STRATEGY_PIO 8 . . . break; . . . default: ret_val = EINVAL; 9 break; } return(ret_val); 10 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
1–116 VMEbus Device Driver Example
•
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS)
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number.
3
Dispatches control, based on the ioctl command value, to a case block that implements the requested command.
4
Section 1.9.2.5 describes the implementation of the SET_STRATEGY_XFER_MODE command.
5
Section 1.9.2.6 describes the implementation of the GET_STRATEGY_XFER_MODE command.
6
Section 1.9.2.2 describes the implementation of the SETUP_VME_FOR_STRATEGY_PIO command.
7
Section 1.9.2.3 describes the implementation of the GET_VME_INFO_FOR_STRATEGY_PIO command.
8
Section 1.9.2.4 describes the implementation of the UNMAP_VME_FOR_STRATEGY_PIO command.
9
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
10
Returns the last-set dmaex_ioctl completion-status value.
1.9.2.2 Implementing SETUP_VME_FOR_STRATEGY_PIO The SETUP_VME_FOR_STRATEGY_PIO ioctl command is called to provide VMEbus mapping information for programmed I/O data transfers. This command maps outbound to a specified VMEbus address for a specified number of bytes and specified VMEbus address modifiers. The PIO handle (of type io_handle_t) produced by the mapping is used by the dmaex_strategy interface to perform the programmed I/O data transfer. VMEbus mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus for the PIO data transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
Before driver interfaces can use the mapping information, you must also call the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to PIO_XFER_MODE.
VMEbus Device Driver Example 1–117
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SETUP_VME_FOR_STRATEGY_PIO ioctl command in the dmaex driver: case SETUP_VME_FOR_STRATEGY_PIO: DMAEX_DBG2("dmaex_ioctl: SETUP_VME_FOR_STRATEGY_PIO\n"); if (sc->vme_handle) { 1 printf("SETUP_VME_FOR_STRATEGY_PIO failed, a PIO mapping exists.\n"); ret_val = EBUSY; break; } sc->vme_handle = vba_map_csr(ctlr, 2 (vme_addr_t)data->data[1], (u_int)data->data[0], (u_int)(data->data[2] & VME_BITS_MASK)); if (!(sc->vme_handle)) { printf("SETUP_VME_FOR_STRATEGY_PIO: vba_map_csr interface failed\n"); printf(" pio_addr = 0x%x pio_size = 0x%x pio_am = 0x%x\n", (vme_addr_t)data->data[1], (u_int)data->data[0], (vme_atype_t)(data->data[2] & VME_BITS_MASK)); ret_val = EINVAL; break; } sc->vme_size sc->vme_addr sc->vme_am sc->vme_wrk_handle sc->vme_wrk_size break;
1
2
= = = = =
(unsigned int)data->data[0]; 3 (vme_addr_t)data->data[1]; (vme_atype_t)(data->data[2] & VME_BITS_MASK); (io_handle_t)0; 4 0;
Checks the vme_handle field of the driver information structure to verify that a PIO mapping does not already exist for the device. If it does, the interface displays an error message and sets the ioctl completion status to EBUSY. Calls the vba_map_csr interface to perform the requested outbound mapping. The vba_map_csr interface takes four arguments:
3
•
The controller information structure for this device
•
The starting VMEbus address to map for programmed I/O, as supplied by the ioctl caller
•
The total number of bytes to map, as supplied by the ioctl caller
•
The VMEbus address modifiers supplied by the ioctl caller, combined with a VMEbus flags bit mask
The vba_map_csr interface returns a PIO handle, representing the allocated system resources associated with the outbound mapping, to the vme_handle field of the device’s dmaex_softc structure. Copies the PIO mapping information provided by the ioctl caller into the vme_size, vme_addr, and vme_am fields of the driver information structure.
1–118 VMEbus Device Driver Example
4
Zeroes the vme_wrk_handle and vme_wrk_size fields of the driver information structure, which subsequently are updated by the dmaex_strategy interface to record the ending I/O handle and transfer size (in bytes) for each PIO transfer. User code can obtain these values after each transfer by calling the GET_VME_INFO_FOR_STRATEGY_PIO ioctl command.
1.9.2.3 Implementing GET_VME_INFO_FOR_STRATEGY_PIO The GET_VME_INFO_FOR_STRATEGY_PIO ioctl command returns the current PIO mapping information for the device. It is used only in connection with PIO data transfers. The command returns the initial mapping information specified to the SETUP_VME_FOR_STRATEGY_PIO command, along with ending-I/O-handle and transfer-size information updated by the dmaex_strategy interface during the most recent data transfer. The mapping information is returned through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus for the PIO transfer
•
data->data[1] — The starting VMEbus address mapped for the transfer
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The PIO handle of the mapped VMEbus address
•
data->data[4] — The ending PIO handle of the most recent transfer
•
data->data[5] — The total size (in bytes) of the most recent transfer
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_VME_INFO_FOR_STRATEGY_PIO ioctl command in the dmaex driver: case GET_VME_INFO_FOR_STRATEGY_PIO: DMAEX_DBG2("dmaex_ioctl: GET_VME_INFO_FOR_STRATEGY_PIO\n"); data->data[0] = (unsigned long)sc->vme_size; 1 data->data[1] = (unsigned long)sc->vme_addr; data->data[2] = (unsigned long)sc->vme_am; data->data[3] = (unsigned long)sc->vme_handle; data->data[4] = (unsigned long)sc->vme_wrk_handle; 2 data->data[5] = (unsigned long)sc->vme_wrk_size; break;
1
Copies the initial mapping information, previously stored in the driver information structure by the SETUP_VME_FOR_STRATEGY_PIO command, back into the first four fields of the data parameter.
VMEbus Device Driver Example 1–119
2
Copies VMEbus ending-I/O-handle and transfer-size information, updated by the dmaex_strategy interface during the most recent PIO data transfer, into the fifth and sixth fields of the data parameter. If no transfer has yet occurred, both fields will be zero (0).
1.9.2.4 Implementing UNMAP_VME_FOR_STRATEGY_PIO The UNMAP_VME_FOR_STRATEGY_PIO ioctl command clears the outbound mapping for programmed I/O previously established for the device by the SETUP_VME_FOR_STRATEGY_PIO command. VMEbus mapping resources allocated by SETUP_VME_FOR_STRATEGY_PIO are returned to the system. No parameters are passed to this command. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the UNMAP_VME_FOR_STRATEGY_PIO ioctl command in the dmaex driver: case UNMAP_VME_FOR_STRATEGY_PIO: DMAEX_DBG2("dmaex_ioctl: UNMAP_VME_FOR_STRATEGY_PIO\n"); if (sc->vme_handle) { vba_unmap_csr(ctlr,sc->vme_handle); 1 sc->vme_handle = (unsigned long)0; 2 sc->vme_addr = 0; 3 sc->vme_am = 0; sc->vme_size = 0; sc->vme_wrk_handle = (io_handle_t)0; 4 sc->vme_wrk_size = 0; } else { printf("dmaex_ioctl: UNMAP_VME_FOR_STRATEGY_PIO, no VME to unmap.\n"); ret_val = ENXIO; } break;
1
Calls the vba_unmap_csr interface to unmap the VMEbus address previously mapped for programmed I/O by the SETUP_VME_FOR_STRATEGY_PIO command. The vba_unmap_csr interface takes two arguments: •
The controller information structure for this device
•
The PIO handle returned by the SETUP_VME_FOR_STRATEGY_PIO command invocation that created the device’s PIO outbound mapping
2
Clears the PIO handle created by a call to the vba_map_csr interface by the SETUP_VME_FOR_STRATEGY_PIO command.
3
Clears the PIO mapping information stored in the vme_addr, vme_am, and vme_size fields of the driver information structure by the SETUP_VME_FOR_STRATEGY_PIO command.
1–120 VMEbus Device Driver Example
4
Clears vme_wrk_handle and vme_wrk_size, the fields of the driver information structure that are updated by the dmaex_strategy interface during PIO data transfers.
1.9.2.5 Implementing SET_STRATEGY_XFER_MODE The SET_STRATEGY_XFER_MODE ioctl command is called to prepare for performing programmed I/O data transfers. The command sets the data transfer mode to be used by the dmaex_strategy interface during reads and writes. The transfer mode, which should equal PIO_XFER_MODE for PIO transfers, is specified by the ioctl caller in the data parameter: data->data[0] — The data transfer mode (PIO_XFER_MODE, DEVICE_DMA_MODE, or BLOCK_DMA_MODE) Before driver interfaces can use the PIO_XFER_MODE data transfer mode, you must also call the SETUP_VME_FOR_STRATEGY_PIO ioctl command to set up PIO mapping information. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SET_STRATEGY_XFER_MODE ioctl command in the dmaex driver: case SET_STRATEGY_XFER_MODE: DMAEX_DBG2("dmaex_ioctl: SET_STRATEGY_XFER_MODE\n"); switch ((int)data->data[0]) { case PIO_XFER_MODE: case DEVICE_DMA_MODE: case BLOCK_DMA_MODE: sc->strategy_xfer_mode = (int)data->data[0]; 1 break; default: ret_val = EINVAL; 2 } break;
1 2
Copies the data transfer mode provided by the caller into the strategy_xfer_mode field of the driver information structure. If an invalid data transfer mode value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1.9.2.6 Implementing GET_STRATEGY_XFER_MODE The GET_STRATEGY_XFER_MODE ioctl command returns the current data transfer mode for the device, as specified in a previous call to the SET_STRATEGY_XFER_MODE command. The transfer mode is returned through the data parameter of the ioctl interface: data->data[0] — The data transfer mode (PIO_XFER_MODE, DEVICE_DMA_MODE, or BLOCK_DMA_MODE)
VMEbus Device Driver Example 1–121
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the ioctl command GET_STRATEGY_XFER_MODE in the dmaex driver: case GET_STRATEGY_XFER_MODE: DMAEX_DBG2("dmaex_ioctl: GET_STRATEGY_XFER_MODE\n"); data->data[0] = (unsigned long)sc->strategy_xfer_mode; 1 break;
1
Copies the data transfer mode value, previously stored in the driver information structure by the SET_STRATEGY_XFER_MODE command, back into the data parameter.
1.9.3 Read and Write Device Section The following sections explain how to implement Read and Write Device Section interfaces that support PIO transfers: Implementing the dmaex_read interface
Section 1.9.3.1
Implementing the dmaex_write interface
Section 1.9.3.2
1.9.3.1 Implementing the dmaex_read Interface The file system invokes the dmaex_read interface whenever user code performs a read operation on the dmaex device. The read interface called by user code takes three arguments: the file descriptor of the open device, a read buffer, and a buffer size. The dmaex_read interface invoked for you by the file system takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
A uio structure that contains information for transferring data to and from the address space of the user’s process
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_read interface calls the physio kernel interface to perform a buffer lock, check the buffer, and set up an I/O packet. The physio interface then calls the dmaex_minphys and dmaex_strategy driver interfaces to access the device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the read can be performed by
1–122 VMEbus Device Driver Example
means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of programmed I/O, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to PIO_XFER_MODE before issuing a read to the dmaex device. The following code shows the implementation of the dmaex_read interface: static int dmaex_read(dev_t dev, struct uio *uio, int flag) { register int unit = minor(dev); 1 register struct dmaex_softc *sc = &dmaex_softc[unit]; register struct buf *bp = &sc->io_buf; DMAEX_DBG1("dmaex_read: dmaex%d\n",unit); if ((unit >= NDMAEX ) || (unit < 0)) 2 return(EIO); if (sc->strategy_xfer_mode == BLOCK_DMA_MODE) { if (!(sc->dma_is_set)) { printf("dmaex_read: ioctl:SET_STRATEGY_INFO_BLK_DMA not invoked\n"); return(EINVAL); } else { sc->dma_wrk_vme_addr = sc->dma_vme_addr; sc->dma_wrk_vme_size = 0; } } else { if (sc->strategy_xfer_mode == PIO_XFER_MODE) { if (!(sc->vme_handle)) { 3 printf("dmaex_read: ioctl:SETUP_VME_FOR_STRATEGY_PIO not invoked\n"); return(EINVAL); } else { sc->vme_wrk_handle = sc->vme_handle; 4 sc->vme_wrk_size = 0; } } } bzero(bp,sizeof(struct buf)); 5 return (physio((void (*)())dmaex_strategy, bp, dev, B_READ, (void (*)())dmaex_minphys, uio)); 6 }
1
Declares and initializes variables required for read I/O, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_read call. The device minor number is stored in the unit variable.
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure.
•
A pointer to the buf structure associated with this device. This pointer later is provided to the physio interface, which performs
VMEbus Device Driver Example 1–123
the PIO data transfer. The buf structure contains information used by the driver to handle I/O requests, such as the binary status flags, the major/minor device numbers, and the address of the I/O buffer associated with this dmaex device. The sc->io_buf field of the device’s driver information structure is accessed to reference the correct buf structure. 2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the EIO error constant is returned to indicate an I/O error.
3
Performs mode-specific error checking. For PIO transfers (data transfer mode = PIO_XFER_MODE), the interface verifies that the user code called the SETUP_VME_FOR_STRATEGY_PIO ioctl command to set up mapping information before issuing a read. If the sc->vme_handle flag in the driver information structure is clear (0), the interface displays an error message and returns the EINVAL error status to the file system.
4
Performs mode-specific variable initialization. For PIO transfers, the interface sets up two fields of the driver information structure to contain working values for the most recent transfer’s ending I/O handle and total size. Because no transfer has yet occurred, the sc->vme_wrk_handle field is initialized with the I/O handle for the starting VMEbus address mapped for the transfer and the sc->vme_wrk_size field is initialized to zero (0). These values will be updated on each iteration of the dmaex_strategy interface during the read, and are the source for the ending I/O handle and total size information returned by the GET_VME_INFO_FOR_STRATEGY_PIO ioctl command.
5
Calls the bzero interface to zero the read buffer. The bzero interface takes two arguments: a buffer pointer and the buffer size (in bytes).
6
Calls the physio kernel interface to implement character I/O. The physio interface will perform a buffer lock on the caller-provided I/O buffer, check the buffer, set up the I/O packet, and invoke caller-specified minphys and strategy interfaces to perform the data transfer. Upon return from the physio call, control and transfer completion status return immediately to the dmaex_read caller (the file system). The physio interface takes six arguments: •
A pointer to a strategy interface, which for this driver is the dmaex_strategy interface. Section 1.9.4.2 discusses the implementation of dmaex_strategy.
1–124 VMEbus Device Driver Example
•
A pointer to the buf structure associated with this dmaex device.
•
The device number, which in this call is the dev argument supplied to this interface by the file system.
•
A read/write flag. In this call, the B_READ constant is passed to indicate a read operation.
•
A pointer to a minphys interface. A device driver can call the minphys kernel interface, which bounds the data transfer size, or provide its own minphys interface. In this call, the driver passes a driver-specific minphys interface called dmaex_minphys. Section 1.9.4.1 discusses the implementation of dmaex_minphys.
•
A pointer to a uio structure, which in this call is the uio argument supplied to this interface by the file system.
1.9.3.2 Implementing the dmaex_write Interface The file system invokes the dmaex_write interface whenever user code performs a write operation on the dmaex device. The flow of execution corresponds exactly to that described for read. The write interface called by user code takes three arguments: the file descriptor of the open device, a write buffer, and a buffer size. The dmaex_write interface invoked for you by the file system takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
A uio structure that contains information for transferring data to and from the address space of the user’s process
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_write interface calls the physio kernel interface to perform a buffer lock, check the buffer, and set up an I/O packet. The physio interface then calls the dmaex_minphys and dmaex_strategy driver interfaces to access the device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the write can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of programmed I/O, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to PIO_XFER_MODE before issuing a write to the dmaex device.
VMEbus Device Driver Example 1–125
The following code shows the implementation of the dmaex_write interface: static int dmaex_write(dev_t dev, struct uio *uio, int flag) { register int unit = minor(dev); register struct dmaex_softc *sc = &dmaex_softc[unit]; register struct buf *bp = &sc->io_buf; DMAEX_DBG1("dmaex_write: dmaex%d\n",unit); if ((unit >= NDMAEX) || (unit < 0)) return (EIO); if (sc->strategy_xfer_mode == BLOCK_DMA_MODE) { if (!(sc->dma_is_set)) { printf("dmaex_write: ioctl:SET_STRATEGY_INFO_BLK_DMA not invoked\n"); return(EINVAL); } else { sc->dma_wrk_vme_addr = sc->dma_vme_addr; sc->dma_wrk_vme_size = 0; } } else { if (sc->strategy_xfer_mode == PIO_XFER_MODE) { if (!(sc->vme_handle)) { printf("dmaex_write: ioctl:SETUP_VME_FOR_STRATEGY_PIO not invoked\n"); return(EINVAL); } else { sc->vme_wrk_handle = sc->vme_handle; sc->vme_wrk_size = 0; } } } bzero(bp,sizeof(struct buf)); return (physio((void (*)())dmaex_strategy, bp, dev, B_WRITE, 1 (void (*)())dmaex_minphys, uio)); }
1
The dmaex_write interface is almost identical to the dmaex_read interface. The only difference (disregarding minor changes in the text of debug and error messages) is that dmaex_write uses the B_WRITE bit instead of the B_READ bit for the read/write flag to indicate that this is a write operation.
1.9.4 Strategy Section The following sections explain how to implement a Strategy Section that supports PIO transfers:
1–126 VMEbus Device Driver Example
Implementing the dmaex_minphys interface
Section 1.9.4.1
Implementing the dmaex_strategy interface
Section 1.9.4.2
1.9.4.1 Implementing the dmaex_minphys Interface The dmaex_minphys interface is specified as a parameter to the physio interface invoked from dmaex_read or dmaex_write. During a file system read or write, the physio interface will call dmaex_minphys once or several times, depending on the requested PIO transfer size. It is called prior to each invocation of dmaex_strategy from physio. You use the dmaex_minphys interface to limit the size of data transfers. This may represent a software limit to throttle the transfer or a hardware-imposed limit. If the requested byte count from physio exceeds the maximum byte count contained in the dmaex_max_dma variable, a revised byte count equalling the maximum is returned to the physio interface. The physio interface then calls the strategy interface with this byte count to perform the transfer. The dmaex_minphys interface takes one argument: a pointer to the buf structure associated with this dmaex device. The physio interface passes the same buf pointer passed to it by dmaex_read or dmaex_write. The following code shows the implementation of the dmaex_minphys interface: static int dmaex_minphys (register struct buf *bp) { DMAEX_DBG1("dmaex_minphys: entered\n"); if (bp->b_bcount > dmaex_max_dma) 1 bp->b_bcount = dmaex_max_dma; return(ESUCCESS); }
1
Compares the user-requested transfer size, contained in the b_count field of the device’s buf structure, to the maximum-allowable transfer size (in bytes), contained in the dmaex_max_dma variable. If the requested size exceeds the limit defined for this device, the b_count field is reset to the maximum allowable, dmaex_max_dma.
1.9.4.2 Implementing the dmaex_strategy Interface The dmaex_strategy interface is specified as a parameter to the physio interface invoked from dmaex_read or dmaex_write. During a file system read or write, the physio interface will call dmaex_strategy once or several times, depending on the requested PIO transfer size.
VMEbus Device Driver Example 1–127
The dmaex_strategy interface is used by physio to perform all or part of a user-requested read or write on a dmaex device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the read or write can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of programmed I/O, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to PIO_XFER_MODE before issuing a read or write to the dmaex device. You control the maximum size of the data transfer by using the dmaex_minphys interface, which is invoked by physio just before every call to dmaex_strategy. The dmaex_strategy interface takes one argument: a pointer to the buf structure associated with this dmaex device. The physio interface passes the same buf pointer passed to it by dmaex_read or dmaex_write. The following code shows the implementation of the dmaex_strategy interface for PIO transfers: static int dmaex_strategy(struct buf *bp) { register int unit = minor(bp->b_dev); 1 register struct controller *ctlr = dmaex_ctlr[unit]; register struct dmaex_softc *sc = &dmaex_softc[unit]; sg_entry_t dmaex_sg; unsigned long b_count; register int *mem_ptr; int flags,i,j; char csr; DMAEX_DBG1("dmaex_strategy: dmaex%d\n",unit); if ( (!ctlr) || (!sc) ) 2 return (EINVAL); sc->dma_handle = (dma_handle_t) NULL; switch (sc->strategy_xfer_mode) { 3 case PIO_XFER_MODE: if (bp->b_flags & B_READ) { if (sc->vme_am & VME_DENSE) { mem_ptr = (int *)bp->b_un.b_addr; for (i=0,j=0; i < bp->b_bcount/sizeof(int); i++, j+=sizeof(int)) *mem_ptr++ = (int)read_io_port(sc->vme_wrk_handle + j,4,0); 4 } else { if (io_copyin(sc->vme_wrk_handle, (vm_offset_t)bp->b_un.b_addr, 5 (unsigned long)bp->b_bcount) == -1) { bp->b_error = EIO; bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; iodone(bp);
1–128 VMEbus Device Driver Example
break; } } } else { if (sc->vme_am & VME_DENSE) { mem_ptr = (int *)bp->b_un.b_addr; for (i=0,j=0; i < bp->b_bcount/sizeof(int); i++, j+=sizeof(int)) write_io_port(sc->vme_wrk_handle + j,4,0,(long)(*mem_ptr++)); 6 mb(); } else { if (io_copyout((vm_offset_t)bp->b_un.b_addr, sc->vme_wrk_handle, 7 (unsigned long)bp->b_bcount) == -1) { bp->b_error = EIO; bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; iodone(bp); break; } } } bp->b_resid = 0; 8 sc->vme_wrk_handle += (unsigned long)bp->b_bcount; sc->vme_wrk_size += (unsigned int)bp->b_bcount; iodone(bp); break; case DEVICE_DMA_MODE: . . . break; case BLOCK_DMA_MODE: . . . break; return(ESUCCESS); 9 }
1
Declares and initializes variables required for data transfers, including: •
The device minor number, initialized by invoking the minor interface and passing it the b_dev value encoded in the caller-supplied buf structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
2
Checks the validity of the controller and driver structure pointers. If not valid, the EINVAL error constant is returned to indicate an invalid structure pointer.
3
Dispatches control, based on the data transfer mode value stored in the strategy_xfer_mode field of the driver information structure, to
VMEbus Device Driver Example 1–129
a case block that implements the requested form of data transfer. In the case of PIO transfers, user code must previously have set the transfer mode to the value PIO_XFER_MODE by calling the SET_STRATEGY_XFER_MODE ioctl command. 4
If mapped to the VMEbus through dense space for a DMA read, calls the read_io_port interface in a loop to read the data from the VMEbus into the user’s mapped memory. The loop terminates when the caller-requested number of bytes has been transferred to the read buffer. The read_io_port interface takes three arguments: •
The current working I/O handle, referencing a location in the bus address space where the read originates. You can perform standard C mathematical operations (addition and subtraction only) on the I/O handle. In this case, the j loop variable is applied as an offset.
•
The width (in bytes) of the data to be read, 4.
•
Flags to indicate special processing requests; currently, no flags are used.
Section 4.3 of the driver kit manual Writing VMEbus Device Drivers provides more information about the read_io_port interface and its arguments. 5
If mapped to the VMEbus through sparse space for a DMA read, calls the io_copyin interface to read the data from the VMEbus into the user’s mapped memory. The io_copyin interface takes three arguments:
6
•
The current working I/O handle, referencing a location in the bus address space where the read originates
•
A pointer to the user read buffer to receive the data
•
The number of bytes to transfer
If mapped to the VMEbus through dense space for a DMA write, calls the write_io_port interface in a loop to write the data from the user’s mapped memory to the VMEbus. The loop terminates when the caller-requested number of bytes has been transferred to the VMEbus. The interface then performs a memory barrier (mb), which flushes the write buffer, to ensure sequential writes to I/O space. You should perform memory barriers after each write to I/O space. The write_io_port interface takes four arguments: •
The current working I/O handle, referencing a location in the bus address space where the write occurs. You can perform standard C mathematical operations (addition and subtraction only) on the I/O handle. In this case, the j loop variable is applied as an offset.
1–130 VMEbus Device Driver Example
•
The width (in bytes) of the data to be read, 4.
•
Flags to indicate special processing requests; currently, no flags are used.
•
A pointer to the user’s write buffer that contains the data to be transferred.
Section 4.3 of the driver kit manual Writing VMEbus Device Drivers provides more information about the write_io_port interface and its arguments. 7
If mapped to the VMEbus through sparse space for a DMA write, calls the io_copyout interface to write the data from the user’s mapped memory to the VMEbus. The io_copyout interface takes three arguments:
8
9
•
A pointer to the user write buffer that contains the data to be transferred to the VMEbus
•
The current working I/O handle, referencing a location in the bus address space where the write occurs
•
The number of bytes to transfer
To indicate that the transfer has completed successfully, the interface: •
Clears the residual buffer count (a residual count indicates an unfinished transfer).
•
Updates the VMEbus ending I/O handle and transfer size, stored in the vme_wrk_handle and vme_wrk_size fields of the driver information structure, based on the count of bytes successfully transferred. User code can obtain the resulting values by invoking the GET_VME_INFO_FOR_STRATEGY_PIO ioctl command.
•
Calls the iodone interface (passing the device’s buf structure) to indicate that the I/O operation has completed.
•
Exits from the case block with a break statement.
Returns to the caller (physio) with ESUCCESS completion status and with the ending I/O handle and byte count updated to reflect the successful data transfer. Depending on whether or not this intermediate data transfer completes the dmaex read or write requested by user code, the physio interface will either return to its caller (dmaex_read or dmaex_write) or issue another pair of calls to dmaex_minphys and dmaex_strategy to carry the user request another step toward completion.
VMEbus Device Driver Example 1–131
1.10 Programming Device DMA Data Transfers Programming device DMA data transfers involves first coding device driver support for calls to the ioctl, read, and write file system interfaces, coding interrupt handler routines, and then invoking the file system interfaces from user code. The VMEbus device’s hardware DMA engine performs the physical data transfers. Compaq provides driver and user code templates that you can use as a starting point for implementing device DMA transfers for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design. The example user code for implementing device DMA transfers performs the following steps: 1.
Calls the kernel-mode driver’s ioctl interface to set the transfer mode to device DMA.
2.
Calls the Tru64 UNIX read and write interfaces to perform the transfers.
The actual transfers are performed in the driver’s dmaex_strategy interface, which is called as often as necessary during a read or write. (Before each call to dmaex_strategy, a call to the driver’s dmaex_minphys interface defines the maximum size of each DMA transfer.) The dmaex_strategy interface first maps the user’s buffer to the VMEbus and obtains the VMEbus address of the mapped buffer. Next, it writes the VMEbus address and byte count to the device’s CSRs and instructs the VMEbus device to perform a read or write DMA transfer. For each read or write transfer it initiates, the interface waits for a DMA complete interrupt, an error interrupt, or a timeout to occur. At the completion of each transfer, the user buffer is unmapped from the VMEbus. The following sections explain the user and driver code required for device DMA transfers: User-level code
Section 1.10.1
I/O Control (ioctl) Section
Section 1.10.2
Read and Write Device Section
Section 1.10.3
1–132 VMEbus Device Driver Example
Strategy Section
Section 1.10.4
Interrupt Section
Section 1.10.5
1.10.1 User-Level Code The following test, from dmaex_test.c, shows how user-level code can program device DMA transfers, assuming the required driver-level support code has been implemented. The test requires one parameter to be passed in the dmaex_ioctl_data data structure as the second argument to the test: data.data[0] — The total number of bytes to transfer _______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. int dmaex_strategy_device_dma_xfer_test(int fd, struct dmaex_ioctl_data *data) { int buf_size,err,i,xfer_mode; int *read_buffer, *write_buffer; int data_error = 0; if (!data->data[0]) { 1 printf("Size of transfer not specified.\n"); return(1); } buf_size = (int)data->data[0]; write_buffer = (int *)malloc(buf_size); 2 if(!(write_buffer)) { perror("write malloc failed"); return(1); } read_buffer = (int *)malloc(buf_size); 3 if (!(read_buffer)) { free(write_buffer); perror("read malloc failed"); return(1); } data->data[0] = (unsigned long)DEVICE_DMA_MODE; if ( ioctl(fd, SET_STRATEGY_XFER_MODE, data) != 0 ) { 4 free(read_buffer); free(write_buffer); perror("error in ioctl SET_STRATEGY_XFER_MODE"); return(1);
VMEbus Device Driver Example 1–133
} if ( ioctl(fd, GET_STRATEGY_XFER_MODE, data) != 0 ) { 5 free(read_buffer); free(write_buffer); perror("error in ioctl GET_STRATEGY_XFER_MODE"); return(1); } xfer_mode = (int)data->data[0]; switch (xfer_mode) { case PIO_XFER_MODE: printf("PIO transfer mode\n"); break; case DEVICE_DMA_MODE: printf("Device DMA transfer mode\n"); break; case BLOCK_DMA_MODE: printf("Block DMA transfer mode\n"); break; default: printf("Invalid Transfer Mode Specified\n"); } for (i = 0; i < (buf_size/sizeof(int)); i++) { 6 write_buffer[i] = i; read_buffer[i] = 0; } if ((err = write(fd, write_buffer, buf_size)) < 0) { 7 free(write_buffer); free(read_buffer); perror("DEVICE_DMA_MODE write error"); } else { if ((err = read(fd, read_buffer, buf_size)) < 0) { 8 free(write_buffer); free(read_buffer); perror("DEVICE_DMA_MODE read error"); } else { printf("Comparing data transferred via device DMA.\n"); for (i = 0; i < (buf_size/sizeof(int)); i++) { 9 if (write_buffer[i] != read_buffer[i]) { printf ("DEVICE_DMA_MODE data error - index 0x%x good = 0x%x bad = 0x%x\n", i,write_buffer[i],read_buffer[i]); data_error++; if (data_error > 16) break; } } } } free(write_buffer); 10 free(read_buffer); return(0); }
1
Verifies that the test caller specified a byte count. If not, the interface displays an error message and returns error status 1 to the test caller.
2
Allocates a write buffer to hold data to be transferred from system memory to the device by the test.
1–134 VMEbus Device Driver Example
3
Allocates a read buffer to receive data transferred from the device to system memory by the test.
4
Calls the SET_STRATEGY_XFER_MODE ioctl command to specify the mode of data transfer, DEVICE_DMA_MODE, to be performed using read and write operations. According to the dmaex driver design for device DMA transfers, this command must be invoked prior to calling the read and write file system interfaces. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SET_STRATEGY_XFER_MODE command takes one argument: data->data[0] — The data transfer mode, DEVICE_DMA_MODE Section 1.10.2.2 describes the implementation of the SET_STRATEGY_XFER_MODE command.
5
For testing purposes, the user code calls the GET_STRATEGY_XFER_MODE ioctl command and displays the results of the previous call to SET_STRATEGY_XFER_MODE. Section 1.10.2.3 describes the implementation of the GET_STRATEGY_XFER_MODE command.
6
Initializes the write and read buffers to be used in the data transfers. For testing purposes, the write buffer is filled with a 32-bit binary count pattern and the read buffer is cleared.
7
Calls the write interface to request that the device perform a DMA transfer from the write buffer in system memory to the device. The device’s hardware DMA engine performs the physical data transfers. The write interface takes three arguments: the file descriptor of the open device, a write buffer, and a buffer size. Calling write causes the dmaex_write driver interface to be invoked, then the dmaex_minphys and dmaex_strategy driver interfaces to be invoked, as many times as necessary, to accomplish the device DMA data transfer. Section 1.10.3 and Section 1.10.4 describe the implementations of these driver interfaces.
8
Calls the read interface to request that the device perform a DMA transfer from the device to the read buffer in system memory. The device’s hardware DMA engine performs the physical data transfers. The read interface takes three arguments: the file descriptor of the open device, a read buffer, and a buffer size. Calling read causes the driver interface dmaex_read to be invoked, then the driver interfaces
VMEbus Device Driver Example 1–135
dmaex_minphys, and dmaex_strategy to be invoked, as many times as necessary, to accomplish the device DMA data transfer. Section 1.10.3 and Section 1.10.4 describe the implementations of these driver interfaces. 9
10
For testing purposes, the user code compares the read data to the original write data, reporting any difference as an error. Releases the write and read buffer memory used for testing and returns success (0) to the test caller.
1.10.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support device DMA transfers: Implementing the dmaex_ioctl interface
Section 1.10.2.1
Implementing SET_STRATEGY_XFER_MODE
Section 1.10.2.2
Implementing GET_STRATEGY_XFER_MODE
Section 1.10.2.3
1.10.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation). The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status).
1–136 VMEbus Device Driver Example
The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p; sg_entry_t dmaex_sg; ihandler_t handler; struct vme_handler_info info; unsigned int irq; unsigned int vector;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 . . . case SET_STRATEGY_XFER_MODE 4 . . . break; case GET_STRATEGY_XFER_MODE 5 . . . break; . . . default: ret_val = EINVAL; 6 break; } return(ret_val); 7 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
VMEbus Device Driver Example 1–137
•
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS)
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number.
3
Dispatches control, based on the ioctl command value, to a case block that implements the requested command.
4
Section 1.10.2.2 describes the implementation of the SET_STRATEGY_XFER_MODE command.
5
Section 1.10.2.3 describes the implementation of the GET_STRATEGY_XFER_MODE command.
6
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
7
Returns the last-set dmaex_ioctl completion-status value.
1.10.2.2 Implementing SET_STRATEGY_XFER_MODE The SET_STRATEGY_XFER_MODE ioctl command is called to prepare for performing device DMA transfers. The VMEbus device’s hardware DMA engine will perform the physical data transfers. The command sets the data transfer mode to be used by the dmaex_strategy interface during reads and writes. The transfer mode, which should equal DEVICE_DMA_MODE for device DMA transfers, is specified by the ioctl caller in the data parameter: data->data[0] — The data transfer mode (PIO_XFER_MODE, DEVICE_DMA_MODE, or BLOCK_DMA_MODE) The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SET_STRATEGY_XFER_MODE ioctl command in the dmaex driver: case SET_STRATEGY_XFER_MODE: DMAEX_DBG2("dmaex_ioctl: SET_STRATEGY_XFER_MODE\n"); switch ((int)data->data[0]) { case PIO_XFER_MODE: case DEVICE_DMA_MODE: case BLOCK_DMA_MODE: sc->strategy_xfer_mode = (int)data->data[0]; 1 break; default: ret_val = EINVAL; 2 } break;
1
Copies the data transfer mode provided by the caller into the strategy_xfer_mode field of the driver information structure.
1–138 VMEbus Device Driver Example
2
If an invalid data transfer mode value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1.10.2.3 Implementing GET_STRATEGY_XFER_MODE The GET_STRATEGY_XFER_MODE ioctl command returns the current data transfer mode for the device, as specified in a previous call to the SET_STRATEGY_XFER_MODE command. The transfer mode is returned through the data parameter of the ioctl interface: data->data[0] — The data transfer mode (PIO_XFER_MODE, DEVICE_DMA_MODE, or BLOCK_DMA_MODE) The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_STRATEGY_XFER_MODE ioctl command in the dmaex driver: case GET_STRATEGY_XFER_MODE: DMAEX_DBG2("dmaex_ioctl: GET_STRATEGY_XFER_MODE\n"); data->data[0] = (unsigned long)sc->strategy_xfer_mode; 1 break;
1
Copies the data transfer mode value, previously stored in the driver information structure by the SET_STRATEGY_XFER_MODE command, back into the data parameter.
1.10.3 Read and Write Device Section The following sections explain how to implement Read and Write Device Section interfaces that support device DMA transfers: Implementing the dmaex_read interface
Section 1.10.3.1
Implementing the dmaex_write interface
Section 1.10.3.2
1.10.3.1 Implementing the dmaex_read Interface The file system invokes the dmaex_read interface whenever user code performs a read operation on the dmaex device. The read interface called by user code takes three arguments: the file descriptor of the open device, a read buffer, and a buffer size. The dmaex_read interface invoked for you by the file system takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
VMEbus Device Driver Example 1–139
•
A uio structure that contains information for transferring data to and from the address space of the user’s process
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_read interface calls the physio kernel interface to perform a buffer lock, check the buffer, and set up an I/O packet. The physio interface then calls the dmaex_minphys and dmaex_strategy driver interfaces to access the device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the read can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of device DMA, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to DEVICE_DMA_MODE before issuing a read to the dmaex device. The following code shows the implementation of the dmaex_read interface: static int dmaex_read(dev_t dev, struct uio *uio, int flag) { register int unit = minor(dev); 1 register struct dmaex_softc *sc = &dmaex_softc[unit]; register struct buf *bp = &sc->io_buf; DMAEX_DBG1("dmaex_read: dmaex%d\n",unit); if ((unit >= NDMAEX ) || (unit < 0)) 2 return(EIO); if (sc->strategy_xfer_mode == BLOCK_DMA_MODE) { if (!(sc->dma_is_set)) { printf("dmaex_read: ioctl:SET_STRATEGY_INFO_BLK_DMA not invoked\n"); return(EINVAL); } else { sc->dma_wrk_vme_addr = sc->dma_vme_addr; sc->dma_wrk_vme_size = 0; } } else { if (sc->strategy_xfer_mode == PIO_XFER_MODE) { if (!(sc->vme_handle)) { printf("dmaex_read: ioctl:SETUP_VME_FOR_STRATEGY_PIO not invoked\n"); return(EINVAL); } else { sc->vme_wrk_handle = sc->vme_handle; sc->vme_wrk_size = 0; } } } bzero(bp,sizeof(struct buf)); 3 return (physio((void (*)())dmaex_strategy, bp, dev, B_READ,
1–140 VMEbus Device Driver Example
(void (*)())dmaex_minphys, uio)); 4 }
1
Declares and initializes variables required for read I/O, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_read call. The device minor number is stored in the unit variable.
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure.
•
2
3
4
A pointer to the buf structure associated with this device. Later this pointer is provided to the physio interface, which performs the device DMA transfer. The buf structure contains information used by the driver to handle I/O requests, such as the binary status flags, the major/minor device numbers, and the address of the I/O buffer associated with this dmaex device. The sc->io_buf field of the device’s driver information structure is accessed to reference the correct buf structure. Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the EIO error constant is returned to indicate an I/O error. Calls the bzero interface to zero the read buffer.
The bzero interface takes two arguments: a buffer pointer and the buffer size (in bytes). Calls the physio kernel interface to implement character I/O. The physio interface will perform a buffer lock on the caller-provided I/O buffer, check the buffer, set up the I/O packet, and invoke caller-specified minphys and strategy interfaces to perform the data transfer. Upon return from the physio call, control and transfer completion status return immediately to the dmaex_read caller (the file system). The physio interface takes six arguments: •
A pointer to a strategy interface, which for this driver is the dmaex_strategy interface. Section 1.10.4.2 discusses the implementation of dmaex_strategy.
•
A pointer to the buf structure associated with this dmaex device.
•
The device number, which in this call is the dev argument supplied to this interface by the file system.
•
A read/write flag. In this call, the B_READ constant is passed to indicate a read operation.
VMEbus Device Driver Example 1–141
•
A pointer to a minphys interface. A device driver can call the minphys kernel interface, which bounds the data transfer size, or provide its own minphys interface. In this call, the driver passes a driver-specific minphys interface called dmaex_minphys. Section 1.10.4.1 discusses the implementation of dmaex_minphys.
•
A pointer to a uio structure, which in this call is the uio argument supplied to this interface by the file system.
1.10.3.2 Implementing the dmaex_write Interface The file system invokes the dmaex_write interface whenever user code performs a write operation on the dmaex device. The flow of execution corresponds exactly to that described for read. The write interface called by user code takes three arguments: the file descriptor of the open device, a write buffer, and a buffer size. The dmaex_write interface invoked for you by the file system takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
A uio structure that contains information for transferring data to and from the address space of the user’s process
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_write interface calls the physio kernel interface to perform a buffer lock, check the buffer, and set up an I/O packet. The physio interface then calls the dmaex_minphys and dmaex_strategy driver interfaces to access the device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the write can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of device DMA, user code calls the SET_STRATEGY_XFER_MODE ioctl command to set the data transfer mode to DEVICE_DMA_MODE before issuing a write to the dmaex device. The following code shows the implementation of the dmaex_write interface: static int dmaex_write(dev_t
dev,
1–142 VMEbus Device Driver Example
struct uio *uio, int flag) { register int unit = minor(dev); register struct dmaex_softc *sc = &dmaex_softc[unit]; register struct buf *bp = &sc->io_buf; DMAEX_DBG1("dmaex_write: dmaex%d\n",unit); if ((unit >= NDMAEX) || (unit < 0)) return (EIO); if (sc->strategy_xfer_mode == BLOCK_DMA_MODE) { if (!(sc->dma_is_set)) { printf("dmaex_write: ioctl:SET_STRATEGY_INFO_BLK_DMA not invoked\n"); return(EINVAL); } else { sc->dma_wrk_vme_addr = sc->dma_vme_addr; sc->dma_wrk_vme_size = 0; } } else { if (sc->strategy_xfer_mode == PIO_XFER_MODE) { if (!(sc->vme_handle)) { printf("dmaex_write: ioctl:SETUP_VME_FOR_STRATEGY_PIO not invoked\n"); return(EINVAL); } else { sc->vme_wrk_handle = sc->vme_handle; sc->vme_wrk_size = 0; } } } bzero(bp,sizeof(struct buf)); return (physio((void (*)())dmaex_strategy, bp, dev, B_WRITE, 1 (void (*)())dmaex_minphys, uio)); }
1
The dmaex_write interface is almost identical to the dmaex_read interface. The only difference (disregarding minor changes in the text of debug and error messages) is that dmaex_write uses the B_WRITE bit instead of the B_READ bit for the read/write flag to indicate that this is a write operation.
1.10.4 Strategy Section The following sections explain how to implement a Strategy Section that supports device DMA transfers: Implementing the dmaex_minphys interface
Section 1.10.4.1
Implementing the dmaex_strategy interface
Section 1.10.4.2
1.10.4.1 Implementing the dmaex_minphys Interface The dmaex_minphys interface is specified as a parameter to the physio interface invoked from dmaex_read or dmaex_write. During a file system
VMEbus Device Driver Example 1–143
read or write, the physio interface will call dmaex_minphys once or several times, depending on the requested device DMA transfer size. It is called prior to each invocation of dmaex_strategy from physio. You use the dmaex_minphys interface to limit the size of data transfers. This may represent a software limit to throttle the transfer or a hardware-imposed limit. If the requested byte count from physio exceeds the maximum byte count contained in the dmaex_max_dma variable, a revised byte count equalling the maximum is returned to the physio interface. The physio interface then calls the strategy interface with this byte count to perform the transfer. The dmaex_minphys interface takes one argument: a pointer to the buf structure associated with this dmaex device. The physio interface passes the same buf pointer passed to it by dmaex_read or dmaex_write. The following code shows the implementation of the dmaex_minphys interface: static int dmaex_minphys (register struct buf *bp) { DMAEX_DBG1("dmaex_minphys: entered\n"); if (bp->b_bcount > dmaex_max_dma) 1 bp->b_bcount = dmaex_max_dma; return(ESUCCESS); }
1
Compares the user-requested transfer size, contained in the b_count field of the device’s buf structure, to the maximum-allowable transfer size (in bytes), contained in the dmaex_max_dma variable. If the requested size exceeds the limit defined for this device, the b_count field is reset to the maximum allowable, dmaex_max_dma.
1.10.4.2 Implementing the dmaex_strategy Interface The dmaex_strategy interface is specified as a parameter to the physio interface invoked from dmaex_read or dmaex_write. During a file system read or write, the physio interface will call dmaex_strategy once or several times, depending on the requested device DMA transfer size. The dmaex_strategy interface is used by physio to perform all or part of a user-requested read or write on a dmaex device. Depending on the data transfer mode set in the driver information structure (sc->strategy_xfer_mode), the read or write can be performed by means of programmed I/O, device DMA that uses the VMEbus device’s DMA engine, or block DMA that uses the VMEbus adapter’s DMA engine. In the case of device DMA, user code calls the SET_STRATEGY_XFER_MODE
1–144 VMEbus Device Driver Example
ioctl command to set the data transfer mode to DEVICE_DMA_MODE before issuing a read or write to the dmaex device. You control the maximum size of the data transfer by using the dmaex_minphys interface, which is invoked by physio just before every call to dmaex_strategy. The dmaex_strategy interface takes one argument: a pointer to the buf structure associated with this dmaex device. The physio interface passes the same buf pointer passed to it by dmaex_read or dmaex_write. The following code shows the implementation of the dmaex_strategy interface for device DMA transfers: static int dmaex_strategy(struct buf *bp) { register int unit = minor(bp->b_dev); 1 register struct controller *ctlr = dmaex_ctlr[unit]; register struct dmaex_softc *sc = &dmaex_softc[unit]; sg_entry_t dmaex_sg; unsigned long b_count; register int *mem_ptr; int flags,i,j; char csr; DMAEX_DBG1("dmaex_strategy: dmaex%d\n",unit); if ( (!ctlr) || (!sc) ) 2 return (EINVAL); sc->dma_handle = (dma_handle_t) NULL; 3 switch (sc->strategy_xfer_mode) { 4 case PIO_XFER_MODE: . . . break; case DEVICE_DMA_MODE: if (dma_map_load ((unsigned long) bp->b_bcount, 5 (vm_offset_t) bp->b_un.b_addr, bp->b_proc, ctlr, &sc->dma_handle, 0, VME_A24_UDATA_D32 | VME_BS_NOSWAP | DMA_GUARD_UPPER | DMA_ALL) == 0) { bp->b_error = EIO; bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; iodone(bp); break; } dmaex_sg = dma_get_curr_sgentry (sc->dma_handle); 6 if (!dmaex_no_dev) { write_io_port (sc->csr_handle + DMAEX_COUNT_OFF, 7
VMEbus Device Driver Example 1–145
DMAEX_COUNT_SIZE, 0, dmaex_sg->bc); mb(); write_io_port (sc->csr_handle + DMAEX_ADDR_OFF, DMAEX_ADDR_SIZE, 0, (long)dmaex_sg->ba); mb(); csr = (bp->b_flags & B_READ) ? (READ | IE) : IE; write_io_port (sc->csr_handle + DMAEX_CSR_OFF, DMAEX_CSR_SIZE, 0, (csr | DMA_GO)); mb(); } if (event_wait((event_t *)&sc->isi_event,TRUE,dmaex_int_wait * hz)) { 8 DMAEX_DBG1("dmaex_strategy: dmaex%d timed out after %d seconds\n", unit,dmaex_int_wait); bp->b_error = ETIMEDOUT; bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; } event_clear((event_t *)&sc->isi_event); 9 dma_map_unload (DMA_DEALLOC, sc->dma_handle); 10 sc->dma_handle = (dma_handle_t) NULL; iodone(bp); break; case BLOCK_DMA_MODE: . . . break; } return(ESUCCESS); 11 }
1
Declares and initializes variables required for data transfers, including: •
The device minor number, initialized by invoking the minor interface and passing it the b_dev value encoded in the caller-supplied buf structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
2
Checks the validity of the controller and driver structure pointers. If not valid, the EINVAL error constant is returned to indicate an invalid structure pointer.
3
Zeroes the DMA handle field in the driver information structure; this ensures the proper operation of the dma_map_load interface without
1–146 VMEbus Device Driver Example
previously calling dma_map_alloc. A subsequent call to dma_map_load will write a DMA handle into this field. 4
Dispatches control, based on the data transfer mode value stored in the strategy_xfer_mode field of the driver information structure, to a case block that implements the requested form of data transfer. In the case of device DMA transfers, user code must previously have set the transfer mode to the value DEVICE_MODE_DMA by calling the SET_STRATEGY_XFER_MODE ioctl command.
5
Calls the dma_map_load interface to allocate, load, and set system resources for the pending DMA transfer. (The dma_map_load interface invokes dma_map_alloc as a result of zeroing the DMA handle, sc->dma_handle, in an earlier step.) Upon success, dma_map_load returns the number of bytes mapped; additionally, a DMA handle is placed in the dma_handle field of the driver information structure. The dma_map_load interface takes seven arguments: •
The maximum size (in bytes) of the data to be transferred during the DMA transfer. The kernel uses this size to determine the software resources (such as mapping registers and I/O channels) to allocate. The transfer request size passed to dmaex_strategy in the b_count field of the device’s buf structure may reflect the transfer size limit imposed by the call to dmaex_minphys that preceded the call to dmaex_strategy.
•
The virtual address at which the DMA transfer occurs. The interface uses this and the third argument to obtain the physical addresses of the system memory pages to load into DMA mapping resources. The address in this example was extracted from the device’s buf structure and specifies the address at which to pull or push the data.
•
A pointer to a proc structure, representing valid context for the virtual address specified in the second argument. The interface uses this pointer to retrieve the pmap needed to translate the virtual address to a physical address. If zero (0) is specified in this argument, the second argument is a kernel address. The argument in this example was extracted from the device’s buf structure and points to the proc structure that represents the process performing the I/O.
•
The controller information structure for this dmaex device. The interface accesses this structure to obtain the bus-specific interfaces and data structures it needs to allocate mapping resources.
VMEbus Device Driver Example 1–147
•
A pointer to the dma_handle field in the driver information structure, which will receive a DMA handle. A DMA handle represents DMA resources associated with the mapping of an in-memory I/O buffer onto a controller’s I/O bus. This handle provides the information to access bus address/byte count pairs. A bus address/byte count pair is represented by the ba and bc members of an sg_entry structure. Device driver writers can view the DMA handle as the tag to the allocated system resources needed to perform a DMA operation.
•
The maximum-size byte-count value that should be stored in the bc members of the sg_entry structures. This example passes the value zero (0).
•
Flags that represent special conditions the device driver needs the system to support. This example passes the VME_A24_UDATA_D32 and VME_BS_NOSWAP VMEbus modifier flags and the DMA_GUARD_UPPER and DMA_ALL DMA-specific flags. VMEbus-specific and DMA-specific driver condition flags are defined in the io/dec/vme/vbareg.h and io/common/devdriver.h header files, respectively.
Section 4.1 of the driver kit manual Writing VMEbus Device Drivers provides more information about the dma_map_load interface and its arguments. 6
Calls the dma_get_curr_sgentry interface to obtain the current sg_entry data structure, which includes the bus address/byte count pair (ba and bc fields) for the just-mapped data transfer. The dma_get_curr_sgentry interface takes one argument: the DMA handle returned by the previous call to dma_map_load, as stored in the dma_handle field of the driver information structure.
7
If the dmaex driver is controlling a hardware device (versus a fictitious device), starts the device DMA transfer by calling the write_io_port interface three times: •
Writes the byte count of user memory mapped to the VMEbus to CSR offset DMAEX_COUNT_OFF
•
Writes the bus address of user memory mapped to the VMEbus to CSR offset DMAEX_ADDR_OFF
•
Writes to the device CSR with IE, DMA_GO, and (if a read) READ bits set to trigger a device DMA transfer with interrupts enabled
After each write_io_port, the dmaex_strategy interface performs a memory barrier (mb), which flushes the write buffer, to ensure sequential writes to I/O space. You should perform memory barriers after each write to I/O space.
1–148 VMEbus Device Driver Example
Section 4.3 of the driver kit manual Writing VMEbus Device Drivers provides more information about the write_io_port interface and its arguments. The dmaex_no_dev flag indicates whether the dmaex example driver is controlling a fictitious device (TRUE) or a hardware device (FALSE). As provided by Compaq, the dmaex driver controls a fictitious device. If the dmaex_no_dev flag is set, there is no hardware device, so the dmaex_strategy interface bypasses all accesses to the device’s control and status hardware registers. 8
Calls the event_wait interface to wait for a DMA completion interrupt or a timeout. Section 4.6 of the driver kit manual Writing VMEbus Device Drivers describes the event_wait and event_clear interfaces that dmaex_strategy uses. The dmaex_intr1 interrupt handler will post an event to signal DMA completion. Section 1.10.5 describes the dmaex_intr1 handler. The dmaex_int_wait variable specifies how many seconds to wait for an interrupt to occur before timing out. If the event_wait times out, the dmaex_strategy interface sets the ETIMEDOUT error condition and sets the residual buffer count to the transfer byte count to indicate an unfinished transfer.
9
10
11
Calls the event_clear interface to clear the event that signaled DMA completion after its use. To indicate the transfer has completed successfully, the interface: •
Releases DMA system resources, by first calling dma_map_unload (specifying the DMA_DEALLOC flag) and then clearing the DMA handle in the device’s driver information structure. (Resources are freed here rather than in an interrupt handler for performance reasons.)
•
Calls the iodone interface (passing the device’s buf structure) to indicate that the I/O operation has completed.
•
Exits from the case block with a break statement.
Returns to the caller (physio) with ESUCCESS completion status and with detailed transfer completion status encoded in the b_resid, b_flags, and b_error fields of the device’s buf structure. Depending on whether or not this intermediate data transfer completes the dmaex read or write requested by user code, the physio interface will either return to its caller (dmaex_read or dmaex_write) or issue another pair of calls to dmaex_minphys and dmaex_strategy to carry the user request another step toward completion.
VMEbus Device Driver Example 1–149
1.10.5 Interrupt Section This section explains how to implement an Interrupt Section that supports device DMA transfers. Two interrupt service interfaces (ISIs), dmaex_intr1 and dmaex_intr2, are provided. The ISIs described in this section are registered in the system during device configuration by the dmaex_probe interface, as described in Section 1.5.1. Both ISIs take one argument: the integer logical unit number for the dmaex device. The dmaex_intr1 interface checks the results of device DMA transfers. When a DMA completion interrupt is received, the interface posts an event to wake up the dmaex_strategy interface to complete the I/O operation. Section 1.10.4.2 describes this interaction. The dmaex_intr2 interface is included only to illustrate how multiple interrupts and their handlers are registered. It has no role in the device DMA transfer example. The following code shows the implementation of the dmaex_intr1 and dmaex_intr2 ISIs: static int dmaex_intr1(int unit) { register struct controller register struct dmaex_softc register struct buf char
*ctlr = dmaex_ctlr[unit]; 1 *sc = &dmaex_softc[unit]; *bp = &sc->io_buf; csr;
DMAEX_DBG2("dmaex_intr1: dmaex%d\n",unit); if (!dmaex_no_dev) { 2 csr = read_io_port (sc->csr_handle + DMAEX_CSR_OFF, DMAEX_CSR_SIZE, 0); if (csr & ERROR) { bp->b_error = EIO; bp->b_flags |= B_ERROR; } } if (dmaex_no_dev) 3 bp->b_resid = 0; else bp->b_resid = read_io_port(sc->csr_handle + DMAEX_COUNT_OFF, DMAEX_COUNT_SIZE, 0); rt_post_callout(event_post, (long)&sc->isi_event, (long)NULL); 4 return(ESUCCESS); } static int
1–150 VMEbus Device Driver Example
dmaex_intr2(int unit) 5 { register struct dmaex_softc *sc = &dmaex_softc[unit]; register struct buf *bp = &sc->io_buf; DMAEX_DBG2("dmaex_intr2: dmaex%d\n",unit); bp->b_error = EIO; bp->b_flags |= B_ERROR; bp->b_resid = bp->b_bcount; rt_post_callout(event_post, (long)&sc->isi_event, (long)NULL); return(ESUCCESS); }
1
Declares and initializes variables required for interrupt processing, including: •
A pointer to the device’s controller information structure, initialized using the caller-provided device logical unit number to reference the correct dmaex_ctlr structure
•
A pointer to the device’s driver information structure, initialized using the device logical unit number to reference the correct dmaex_softc structure
•
2
A pointer to the buf structure associated with this device, initialized using the io_buf field of the driver information structure to reference the correct buf structure If the dmaex driver is controlling a hardware device (not a fictitious device), reads a byte from the device CSR on the VMEbus by calling the read_io_port interface. If an ERROR bit is set in the CSR value, the interface sets error condition EIO in the fields of the device’s buf structure. Section 4.3 of the driver kit manual Writing VMEbus Device Drivers provides more information about the read_io_port interface and its arguments. The dmaex_no_dev flag indicates whether the dmaex example driver is controlling a fictitious device (TRUE) or a hardware device (FALSE). As provided by Compaq, the dmaex driver controls a fictitious device.
3
If the dmaex_no_dev flag is set, there is no hardware device, so the dmaex_intr1 ISI bypasses all accesses to the device’s control and status hardware registers. Sets the buffer residual count (number of bytes transferred using device DMA and awaiting main driver code’s attention) in the device’s buf structure. If the dmaex driver is controlling a fictitous device, not a hardware device, sets the buffer residual count (number of bytes remaining) to zero (0), indicating successful completion.
VMEbus Device Driver Example 1–151
If the dmaex driver is controlling a hardware device, calls read_io_port to obtain the byte count of the transferred buffer and copies it to the buffer residual count. 4
Calls the event_post interface to post an event notifying the dmaex_strategy interface of device DMA completion. Section 4.6 of the driver kit manual Writing VMEbus Device Drivers describes the event_post interface. Because ISIs executing at SPLDEVRT (SPL 6) must not call kernel interfaces directly, the interface uses the rt_post_callout interface to call event_post while deferring its execution until a time when kernel interfaces can be invoked. The function invoked by rt_post_callout runs at an elevated SPL and is subject to the same restrictions as an ISI. The vme_manual_setup(7) and vme_univ_manual_setup(7) reference pages, in the Tru64 UNIX base operating system man tree, describe the rt_post_callout interface; see the sections titled Realtime Interrupt-Handling Function rt_post_callout. If you know that VMbus interrupts are never dispatched at SPLDEVRT SPL, then you can substitute the following line of code for the invocation of rt_post_callout: event_post( (event_t *)&sc->isi_event);
5
The dmaex_intr2 ISI is a prototype of a second (through nth) ISI that would handle other interrupts from the dmaex controller. This and associated code in the dmaex_probe interface are included only for the purpose of illustrating how multiple interrupts and their handlers are registered. The ISI has no functional role in the device DMA example.
1.11 Programming Device Input to Wired Memory with Signaling Programming device input to wired memory with signaling involves first coding device driver support for calls to the ioctl file system interface, coding interrupt handler routines, writing user code to set up signal handling, invoking the ioctl commands from user code, and then calling the sigsuspend interface to wait for device input. Compaq provides driver and user code templates that you can use as a starting point for implementing device input to wired memory with signaling for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design.
1–152 VMEbus Device Driver Example
The example user code for implementing device input to wired memory with signaling performs the following steps: 1.
Calls kernel interfaces to set up a signal handler for SIGUSR1, which will be signaled by an interrupt handler upon detection of a VMEbus device interrupt (device data written to a buffer).
2.
Calls the kernel-mode driver’s ioctl interface to install an interrupt handler to a specified VMEbus interrupt vector and IRQ level. Upon receipt of a VMEbus device interrupt (device data written to a buffer), the installed handler will issue a psignal to SIGUSR1.
3.
Calls the driver’s ioctl interface to map a user buffer in system memory to the VMEbus for VMEbus slave accesses. The user’s buffer is wired down so that it cannot be swapped out while mapped to the VMEbus.
4.
Calls the sigsuspend interface to wait for SIGUSR1 to be signaled. Receipt of a VMEbus device interrupt (device data written to a buffer) will activate the user-installed interrupt handler, which will issue a psignal to SIGUSR1. Once SIGUSR1 is signaled, user code resumes execution and can access the received data.
The following sections explain the user and driver code required for device input to wired memory with signaling: User-level code
Section 1.11.1
I/O Control (ioctl) Section
Section 1.11.2
Interrupt Section
Section 1.11.3
1.11.1 User-Level Code The following test, from dmaex_test.c, shows how user-level code can program device input to wired memory with signaling, assuming the required driver-level support code has been implemented. The test requires five parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The total number of bytes to map to the VMEbus
•
data.data[1] — The starting VMEbus address (not used; the VMEbus address of mapped memory subsequently can be obtained using the driver ioctl interface)
•
data.data[2] — The VMEbus address modifiers, data size, and byte swap mode; values are defined in the io/dec/vme/vbareg.h header file
VMEbus Device Driver Example 1–153
•
data.data[3] — The VMEbus IRQ level of the interrupt requests to be handled by the interrupt handler
•
data.data[4] — The VMEbus interrupt vector at which to install the interrupt handler _______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. The third argument to the test specifies whether software byte swapping is needed (1) or not (0); and if software byte swapping is needed, the fourth argument provides the byte swap mode. int dmaex_map_unmap_sys_mem_test(int struct dmaex_ioctl_data int vme_atype_t { int buf_size,err,i; char *unaligned_buffer; unsigned long offset; int *buffer; char operator; sigset_t empty_set,new_set; struct dmaex_ioctl_data tmp_data; int byte_swap_needed = 0;
fd, *data, sw_byte_swap_needed, byte_swap_mode)
if ( (!data->data[0]) || (!(data->data[2] & VME_BITS_MASK)) ) { 1 printf("No test parameters specified \n"); return(1); } buf_size = (int)data->data[0]; if ( (!data->data[3]) || (!data->data[4]) ) { printf("No VMEbus vector or IRQ specified\n"); return(1); } if ((sw_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) 2 byte_swap_needed = 1; unaligned_buffer = (char *)malloc(buf_size + 8192); 3 if(!(unaligned_buffer)) { perror("malloc failed"); return(1); } bzero(unaligned_buffer,(buf_size + 8192)); offset = (unsigned long)unaligned_buffer % 8192L; buffer = (int *)(&unaligned_buffer[8192 - offset]); dmaex_sigsetup(); 4
1–154 VMEbus Device Driver Example
sigemptyset( &new_set ); sigaddset( &new_set, SIGUSR1 ); sigprocmask( SIG_BLOCK, &new_set, NULL); 5 tmp_data.data[0] = data->data[3]; tmp_data.data[1] = data->data[4]; if ( ioctl(fd, SET_INT_HANDLER, &tmp_data) != 0 ) { 6 perror("error in ioctl SET_INT_HANDLER"); free(buffer); return(1); } printf("\nVME ISI installed at vector 0x%x for VME IRQ level 0x%x\n\n", tmp_data.data[1],tmp_data.data[0]); sigemptyset( &empty_set ); 7 data->data[3] = (unsigned long)buffer; if ( ioctl(fd, MAP_SYS_MEM_TO_VME, data) != 0 ) { 8 free(unaligned_buffer); perror("error in ioctl MAP_SYS_MEM_TO_VME"); if ( ioctl(fd, CLR_INT_HANDLER, &tmp_data) != 0 ) perror("error in ioctl CLR_INT_HANDLER"); return(1); } if ( ioctl(fd, GET_SYS_MEM_INFO, data) != 0 ) { 9 free(unaligned_buffer); perror("error in ioctl GET_SYS_MEM_INFO"); if ( ioctl(fd, CLR_INT_HANDLER, &tmp_data) != 0 ) perror("error in ioctl CLR_INT_HANDLER"); return(1); } printf("Map System Memory to VME info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" System Memory Address = 0x%lx\n",(unsigned int)data->data[3]); printf("\nSystem Memory has been mapped to VMEbus address 0x%x.\n", (unsigned int)data->data[1]); printf("Waiting for signal from kernel interrupt service - pid = %d\n", getpid()); sigsuspend( &empty_set ); 10 if (byte_swap_needed) { 11 if (dmaex_sw_byte_swap(buffer, buffer, buf_size, byte_swap_mode) == 0) perror("Error detected swapping read data"); } for (i = 0; i < 10; i++) 12 printf("index = %d value = 0x%x\n",i, buffer[i]); if ( ioctl(fd, UNMAP_SYS_MEM_TO_VME, data) != 0 ) { 13 free(unaligned_buffer); perror("error in ioctl UNMAP_SYS_MEM_TO_VME"); if ( ioctl(fd, CLR_INT_HANDLER, &tmp_data) != 0 ) perror("error in ioctl CLR_INT_HANDLER"); return(1); } if ( ioctl(fd, GET_SYS_MEM_INFO, data) != 0 ) { 14
VMEbus Device Driver Example 1–155
free(unaligned_buffer); perror("error in ioctl GET_SYS_MEM_INFO"); if ( ioctl(fd, CLR_INT_HANDLER, &tmp_data) != 0 ) perror("error in ioctl CLR_INT_HANDLER"); return(1); } printf("Map System Memory to VME info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" System Memory Address = 0x%lx\n",(unsigned int)data->data[3]); free(unaligned_buffer); 15 if ( ioctl(fd, CLR_INT_HANDLER, &tmp_data) != 0 ) { perror("error in ioctl CLR_INT_HANDLER"); return(1); } else return(0); }
1
2
3
4
Verifies that the test caller specified a byte count, a VMEbus address modifiers argument, a VMEbus IRQ level, and a VMEbus interrupt vector. If not, the interface displays an error message and returns the error status 1 to the test caller. If software byte swapping is needed, sets the local flag byte_swap_needed. Software byte swapping is needed if the test caller passed 1 for the sw_byte_swap_needed test argument and a byte swap mode other than VME_BS_NOSWAP for the byte_swap_mode test argument. This implies that the caller has determined both that the data transfer requires byte swapping and that the system’s VMEbus adapter does not provide hardware byte swapping. (The caller may also select software byte swapping over hardware byte swapping where both are available.) The swap arguments provided by dmaex_test to this test are based in part on a platform-specific flag value generated by GET_HW_BYTE_SWAP_INFO, a driver ioctl command that dmaex_test uses to determine the swapping capabilities of the system’s VMEbus adapter. See Section 1.16 for more information. Allocates and clears a device input buffer. The user code aligns the allocated buffer to an Alpha page. It is not necessary to do this; however, it will align the VMEbus page offset and memory buffer offset to coincide. Calls the dmaex_sigsetup driver interface to set up a signal handler, dmaex_sig_catcher, to catch the SIGUSR1 signal. A dmaex interrupt handler will signal SIGUSR1 upon detection of a VMEbus interrupt (dmaex device data written to the user’s buffer). Once SIGUSR1 is signaled, user code resumes execution and can access the received data. You can also enter the signal handler by typing ^C (Control/C) on the console. The dmaex_sigsetup interface that user code invokes to set up the SIGUSR1 signal handler is implemented as follows:
1–156 VMEbus Device Driver Example
void dmaex_sigsetup() { struct sigaction newsig, oldsig; newsig.sa_handler = dmaex_sig_catcher; sigemptyset( &newsig.sa_mask ); newsig.sa_flags = 0; if (sigaction( SIGUSR1, &newsig, &oldsig )) printf("dmaex_setup: sigaction failed\n"); if (sigaction( SIGINT, &newsig, &oldsig )) printf("dmaex_setup: sigaction failed\n"); }
The SIGUSR1 signal handler, dmaex_sig_catcher, is implemented as follows: void dmaex_sig_catcher(int signo) { printf("\ndmaex_sig_catcher: received signal %d from driver\n\n",signo); }
5
Blocks the SIGUSR1 signal until the driver is ready to receive it. A subsequent call to the sigsuspend interface (to wait for SIGUSR1) will unblock SIGUSR1.
6
Calls the SET_INT_HANDLER ioctl command to install a VMEbus interrupt service interface (ISI), dmaex_rcv_int_srv, to the VMEbus interrupt vector and interrupt request level specified by the test caller. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SET_INT_HANDLER command takes two arguments: •
data->data[0] — The VMEbus IRQ level of the interrupt requests to be handled by the interrupt handler
•
data->data[1] — The VMEbus interrupt vector at which to install the interrupt handler
Section 1.11.2.2 describes the implementation of the SET_INT_HANDLER command. The dmaex_rcv_int_srv ISI that SET_INT_HANDLER installs is activated when device data is written to the user’s buffer. When activated, it issues a psignal to SIGUSR1, which wakes up the user program waiting for dmaex device data. Section 1.11.3 describes the implementation of the dmaex_rcv_int_srv interface. 7
Creates an empty signal set to receive SIGUSR1.
8
Invokes the MAP_SYS_MEM_TO_VME ioctl command to map the user’s system memory buffer to the VMEbus and wire the system
VMEbus Device Driver Example 1–157
memory down so that it cannot be swapped out while mapped to the VMEbus. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The MAP_SYS_MEM_TO_VME command takes four arguments:
9
10
11
•
data->data[0] — The total number of bytes to map to the VMEbus
•
data->data[1] — The starting VMEbus address of the data buffer (not used in the current implementation)
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s system memory buffer
Section 1.11.2.4 describes the implementation of the MAP_SYS_MEM_TO_VME command. For testing purposes, the user code calls the GET_SYS_MEM_INFO ioctl command and displays the results of the previous call to MAP_SYS_MEM_TO_VME, including the VMEbus address at which the user’s system memory buffer was mapped. Section 1.11.2.5 describes the implementation of the GET_SYS_MEM_INFO command. Calls the sigsuspend interface to wait for SIGUSR1 to be signaled. Receipt of a VMEbus interrupt to the user-specified VMEbus interrupt vector (device data written to the user’s buffer) will activate a dmaex interrupt handler, which will issue a psignal to SIGUSR1. Once SIGUSR1 is signaled, user code resumes execution and can access the received data. If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the newly received data as 32-bit integer values based on the specified byte swap mode. After swapping, buffer contains the byte-swapped data. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
1–158 VMEbus Device Driver Example
12 13
14
15
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. For testing purposes, the user code displays the first ten longwords of the received device data. Calls the UNMAP_SYS_MEM_TO_VME ioctl command to unmap the user’s system memory buffer from the VMEbus, release VMEbus mapping resources used by MAP_SYS_MEM_TO_VME, and to unwire the user’s memory such that it can again be swapped by the system. The UNMAP_SYS_MEM_TO_VME command takes no arguments. Section 1.11.2.6 describes the implementation of the UNMAP_SYS_MEM_TO_VME command. For testing purposes, the user code calls the GET_SYS_MEM_INFO ioctl command and displays the results of the previous call to UNMAP_SYS_MEM_TO_VME, which unmapped the user’s system memory buffer from the VMEbus. Releases the buffer memory used for testing, invokes the CLR_INT_HANDLER ioctl command to remove the dmaex_rcv_int_srv interrupt handler installed by SET_INT_HANDLER, and returns success (0) to the test caller.
1.11.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support device input to wired memory with signaling: Implementing the dmaex_ioctl interface
Section 1.11.2.1
Implementing SET_INT_HANDLER
Section 1.11.2.2
Implementing CLR_INT_HANDLER
Section 1.11.2.3
Implementing MAP_SYS_MEM_TO_VME
Section 1.11.2.4
Implementing GET_SYS_MEM_INFO
Section 1.11.2.5
Implementing UNMAP_SYS_MEM_TO_VME
Section 1.11.2.6
1.11.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation).
VMEbus Device Driver Example 1–159
The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status). The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p; sg_entry_t dmaex_sg; ihandler_t handler; struct vme_handler_info info; unsigned int irq; unsigned int vector;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 . . . case MAP_SYS_MEM_TO_VME 4 . . . break; case GET_SYS_MEM_INFO 5 . . . break; case UNMAP_SYS_MEM_TO_VME 6 . . . break;
1–160 VMEbus Device Driver Example
. . . case SET_INT_HANDLER 7 . . . break; case CLR_INT_HANDLER 8 . . . break; . . . default: ret_val = EINVAL; 9 break; } return(ret_val); 10 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS)
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number.
3
Dispatches control, based on the ioctl command value, to a case block that implements the requested command.
4
Section 1.11.2.4 describes the implementation of the MAP_SYS_MEM_TO_VME command.
5
Section 1.11.2.5 describes the implementation of the GET_SYS_MEM_INFO command.
6
Section 1.11.2.6 describes the implementation of the UNMAP_SYS_MEM_TO_VME command.
7
Section 1.11.2.2 describes the implementation of the SET_INT_HANDLER command.
8
Section 1.11.2.3 describes the implementation of the CLR_INT_HANDLER command.
VMEbus Device Driver Example 1–161
9
10
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL. Returns the last-set dmaex_ioctl completion-status value.
1.11.2.2 Implementing SET_INT_HANDLER The SET_INT_HANDLER ioctl command installs the dmaex_rcv_int_srv interrupt service interface (ISI) to receive VMEbus interrupts at a user-specified VMEbus IRQ level and interrupt vector. The installed ISI will use the psignal interface to signal a waiting user program when a VMEbus interrupt (representing device input to the user’s buffer) is received at the specified IRQ and vector. The VMEbus IRQ level and interrupt vector are provided by the ioctl caller in the data parameter: •
data->data[0] — The VMEbus IRQ level of the interrupt requests to be handled by the interrupt handler
•
data->data[1] — The VMEbus interrupt vector at which to install the interrupt handler
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SET_INT_HANDLER ioctl command in the dmaex driver: case SET_INT_HANDLER: DMAEX_DBG2("dmaex_ioctl: SET_INT_HANDLER\n"); if (sc->rcvisr_id_t) { 1 printf("dmaex_ioctl: SET_INT_HANDLER handler previously installed\n"); ret_val = EBUSY; } else { irq = (unsigned int)data->data[0]; vector = (unsigned int)data->data[1]; if ((irq < 1) || (irq > 7)) { 2 printf("dmaex_ioctl: SET_INT_HANDLER, invalid IRQ specified\n"); ret_val = EINVAL; } else { info.gen_intr_info.configuration_st = (caddr_t) ctlr; 3 info.gen_intr_info.intr = dmaex_rcv_int_srv; info.gen_intr_info.param = (caddr_t) unit; info.gen_intr_info.config_type = CONTROLLER_CONFIG_TYPE; info.vec = vector; info.irq = (int)irq; handler.ih_bus = ctlr->bus_hd; handler.ih_bus_info = (char *) &info; sc->rcvisr_id_t = handler_add(&handler); 4 if (sc->rcvisr_id_t == (ihandler_id_t *)NULL) { printf("dmaex_ioctl: SET_INT_HANDLER - error in handler_add\n"); ret_val = EIO; } else { if (handler_enable(sc->rcvisr_id_t) != 0) { 5
1–162 VMEbus Device Driver Example
handler_del(sc->rcvisr_id_t); sc->rcvisr_irq = 0; sc->rcvisr_id_t = (ihandler_id_t *)NULL; sc->rcvisr_proc_p = (struct proc *)NULL; ret_val = EIO; } else sc->rcvisr_irq = info.irq; 6 sc->rcvisr_proc_p = (struct proc *)task_to_proc(current_task()); } } } break;
1
Checks that the SET_INT_HANDLER handler is not currently installed. If it is, the interface displays an error message and sets the ioctl completion status to EBUSY.
2
Checks that the caller specified a VMEbus IRQ level in the range 1 through 7. If not, the interface displays an error message and sets the ioctl completion status to EINVAL.
3
In preparation for calls to the handler_add and handler_enable interfaces to register and enable the dmaex_rcv_int_srv ISI, initializes several fields in the device’s vme_handler_info structure. These include fields for the device’s controller structure pointer, name of the driver interrupt interface to be registered (dmaex_rcv_int_srv), device unit number, and the VMEbus interrupt vector and priority level. A pointer to the device’s vme_handler_info structure is placed in the ih_bus_info field of the device’s ihandler_t structure, which will be passed to handler_add.
4
Registers the driver’s dmaex_rcv_int_srv ISI by calling handler_add. The handler_add interface takes one argument: the ihandler_t structure prepared in the previous step. The interface returns a pointer to an initialized handler ID (type ihandler_id_t *).
5
Enables the driver’s dmaex_rcv_int_srv ISI by calling handler_enable. The handler_enable interface takes one argument: the ihandler_id_t * structure pointer returned by the handler_add interface in the previous step. The interface returns success (0) or failure (–1) status.
6
Places information that the driver’s dmaex_rcv_int_srv ISI requires into the device’s driver information structure for later use. This information includes the user-specified VMEbus IRQ level and a pointer to a proc structure for the current task. The ISI will supply the proc structure pointer as the second argument to the psignal call that wakes up the waiting user task.
VMEbus Device Driver Example 1–163
1.11.2.3 Implementing CLR_INT_HANDLER The CLR_INT_HANDLER ioctl command removes the dmaex_rcv_int_srv ISI previously installed with SET_INT_HANDLER. No parameters are passed to this command. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the CLR_INT_HANDLER ioctl command in the dmaex driver: case CLR_INT_HANDLER: DMAEX_DBG2("dmaex_ioctl: CLR_INT_HANDLER\n"); if (sc->rcvisr_id_t) { if (handler_disable(sc->rcvisr_id_t) != 0) { 1 printf("dmaex_ioctl: CLR_INT_HANDLER handler_disable failed\n"); ret_val = EIO; } else { if (handler_del(sc->rcvisr_id_t) != 0) { printf("dmaex_ioctl: CLR_INT_HANDLER handler_del failed\n"); ret_val = EIO; } else { sc->rcvisr_irq = 0; 2 sc->rcvisr_id_t = (ihandler_id_t *)NULL; sc->rcvisr_proc_p = (struct proc *)NULL; } } } break;
1
2
Removes the dmaex_rcv_int_srv interrupt handler installed by SET_HANDLER_INT by first calling the handler_disable interface to disable any further interrupts, then calling the handler_del interface to remove the interrupt handler. Clears the following driver information structure fields set up by SET_HANDLER_INT: the VMEbus IRQ level, an interrupt handler ID pointer, and a calling-task proc structure pointer.
1.11.2.4 Implementing MAP_SYS_MEM_TO_VME The MAP_SYS_MEM_TO_VME ioctl command is called to prepare for performing device input to wired memory with signaling. The command maps the user’s system memory buffer to the VMEbus for a specified number of bytes. The user’s system memory is wired down so that it cannot be swapped while mapped to the VMEbus. Mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus
•
data->data[1] — The starting VMEbus address to map (not used in the current implementation)
1–164 VMEbus Device Driver Example
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s system memory buffer
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the MAP_SYS_MEM_TO_VME ioctl command in the dmaex driver: case MAP_SYS_MEM_TO_VME: DMAEX_DBG2("dmaex_ioctl: MAP_SYS_MEM_TO_VME\n"); if (sc->sys_mem_dma_handle) { 1 printf("MAP_SYS_MEM_TO_VME failed, a system memory mapping exists.\n"); ret_val = EBUSY; break; } if (!data->data[0] || !data->data[2] || !data->data[3]) { 2 printf("MAP_SYS_MEM_TO_VME failed, no bc, VME am or vir mem buf\n"); ret_val = EINVAL; break; } sc->sys_mem_dma_handle = (dma_handle_t)NULL; 3 sc->sys_mem_vme_size = (unsigned int)data->data[0]; sc->sys_mem_vme_am = (vme_atype_t)data->data[2]; sc->sys_mem_vir_addr = (caddr_t)data->data[3]; proc_p = task_to_proc(current_task()); if (dma_map_load ((unsigned long) sc->sys_mem_vme_size, 4 (vm_offset_t) sc->sys_mem_vir_addr, proc_p, ctlr, &sc->sys_mem_dma_handle, 0, sc->sys_mem_vme_am | DMA_GUARD_UPPER | DMA_ALL) == 0) { printf("MAP_SYS_MEM_TO_VME failed, dma_map_load returned zero\n"); ret_val = EINVAL; break; } dmaex_sg = dma_get_curr_sgentry (sc->sys_mem_dma_handle); 5 sc->sys_mem_vme_addr = (vme_addr_t)dmaex_sg->ba; sc->sys_mem_vme_size = (unsigned int)dmaex_sg->bc; if (vm_map_pageable(current_task()->map, 6 trunc_page(sc->sys_mem_vir_addr), round_page(sc->sys_mem_vir_addr + sc->sys_mem_vme_size), (VM_PROT_READ | VM_PROT_WRITE))) { printf("MAP_SYS_MEM_TO_VME: vm_map_pageable failed\n"); dma_map_unload (DMA_DEALLOC, sc->sys_mem_dma_handle); sc->sys_mem_dma_handle = (dma_handle_t)NULL; sc->sys_mem_vme_addr = 0; sc->sys_mem_vme_am = 0; sc->sys_mem_vme_size = 0; sc->sys_mem_vir_addr = (caddr_t)NULL; ret_val = EIO; break; }
VMEbus Device Driver Example 1–165
break;
1
2
3
4
Checks that a system memory mapping does not already exist. If one does, the interface displays an error message and sets the ioctl completion status to EBUSY. Verifies that the caller specified a byte count, VMEbus address modifiers, and the virtual address of the user’s system memory buffer. If not, the interface displays an error message and sets the ioctl completion status to EINVAL. Copies the caller-specified mapping information into the sys_mem_dma_handle, sys_mem_vme_size, sys_mem_vme_am, and sys_mem_vir addr fields of the device’s driver information structure. Calls the dma_map_load interface to map the user’s system memory buffer to the VMEbus. Upon success, dma_map_load returns the number of bytes mapped; additionally, a DMA handle is placed in the dma_handle field of the driver information structure. The dma_map_load interface takes seven arguments: •
The maximum size (in bytes) of the data to be transferred during the DMA transfer. The kernel uses this size to determine the software resources (such as mapping registers and I/O channels) to allocate.
•
The virtual address at which the DMA transfer occurs. The interface uses this and the third argument to obtain the physical addresses of the system memory pages to load into DMA mapping resources.
•
A pointer to a proc structure for the current task, representing valid context for the virtual address specified in the second argument. The interface uses this pointer to retrieve the pmap needed to translate the virtual address to a physical address. If zero (0) is specified in this argument, the second argument is a kernel address.
•
The controller information structure for this dmaex device. The interface accesses this structure to obtain the bus-specific interfaces and data structures it needs to allocate mapping resources.
•
A pointer to the dma_handle field in the driver information structure, which will receive a DMA handle. A DMA handle represents DMA resources associated with the mapping of an in-memory I/O buffer onto a controller’s I/O bus. This handle provides the information to access bus address/byte count pairs. A bus address/byte count pair is represented by the ba and bc members of an sg_entry structure. Device driver writers can view the DMA handle as the tag to the allocated system resources needed to perform a DMA operation.
1–166 VMEbus Device Driver Example
•
The maximum-size byte-count value that should be stored in the bc members of the sg_entry structures. This example passes the value zero (0).
•
Flags that represent special conditions the device driver needs the system to support. This example passes the VMEbus address modifier flags specified by the caller as well as the DMA_GUARD_UPPER and DMA_ALL flags. VMEbus-specific and DMA-specific driver condition flags are defined in the io/dec/vme/vbareg.h and io/common/devdriver.h header files, respectively.
Section 4.1 of the driver kit manual Writing VMEbus Device Drivers provides more information about the dma_map_load interface and its arguments. 5
Calls the dma_get_curr_sgentry interface to obtain the current sg_entry data structure, which includes the bus address/byte count pair (ba and bc fields) for the just-mapped system memory. The bus address and byte count returned by this call are copied into the sys_mem_vme_addr and sys_mem_vme_size driver information structure fields, respectively. The dma_get_curr_sgentry interface takes one argument: the DMA handle returned by the previous call to dma_map_load, as stored in field dma_handle of the driver information structure.
6
Calls the vm_map_pageable interface to wire down the user’s system memory buffer so that it cannot be swapped out while it is mapped to the VMEbus.
1.11.2.5 Implementing GET_SYS_MEM_INFO The GET_SYS_MEM_INFO ioctl command returns the results of the system memory to VMEbus mapping performed by the MAP_SYS_MEM_TO_VME ioctl command. The mapping information is returned through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus
•
data->data[1] — The VMEbus address at which the user’s system memory buffer is mapped
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The virtual address of the user’s system memory buffer
VMEbus Device Driver Example 1–167
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_SYS_MEM_INFO ioctl command in the dmaex driver: case GET_SYS_MEM_INFO: DMAEX_DBG2("dmaex_ioctl: GET_SYS_MEM_INFO\n"); data->data[0] = (unsigned long)sc->sys_mem_vme_size; 1 data->data[1] = (unsigned long)sc->sys_mem_vme_addr; data->data[2] = (unsigned long)sc->sys_mem_vme_am; data->data[3] = (unsigned long)sc->sys_mem_vir_addr; break;
1
Copies the system memory to VMEbus mapping information, previously stored in the driver information structure by the MAP_SYS_MEM_TO_VME command, back into the first four fields of the data parameter.
1.11.2.6 Implementing UNMAP_SYS_MEM_TO_VME The UNMAP_SYS_MEM_TO_VME ioctl command unmaps the user’s system memory buffer from the VMEbus. The command releases VMEbus mapping resources allocated by the MAP_SYS_MEM_TO_VME ioctl command and unwires the user’s system memory buffer such that it can be swapped by the system. No parameters are passed to this command. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the UNMAP_SYS_MEM_TO_VME ioctl command in the dmaex driver: case UNMAP_SYS_MEM_TO_VME: DMAEX_DBG2("dmaex_ioctl: UNMAP_SYS_MEM_TO_VME\n"); if (sc->sys_mem_dma_handle) { dma_map_unload (DMA_DEALLOC, sc->sys_mem_dma_handle); 1 if (vm_map_pageable(current_task()->map, 2 trunc_page(sc->sys_mem_vir_addr), round_page(sc->sys_mem_vir_addr + sc->sys_mem_vme_size), (VM_PROT_NONE))) { printf("UNMAP_SYS_MEM_TO_VME: vm_map_pageable failed\n"); ret_val = EIO; } sc->sys_mem_dma_handle = (dma_handle_t)NULL; 3 sc->sys_mem_vme_addr = 0; sc->sys_mem_vme_am = 0; sc->sys_mem_vme_size = 0; sc->sys_mem_vir_addr = (caddr_t)NULL; } else { printf("dmaex_ioctl: UNMAP_SYS_MEM_TO_VME, no VME to unmap.\n"); ret_val = ENXIO; } break;
1
2
Calls the dma_map_unload interface (with the DMA_DEALLOC flag) to unmap the system memory previously mapped to the VMEbus, using dma_map_load, by the MAP_SYS_MEM_TO_VME ioctl command. Calls the vm_map_pageable interface to unwire the just-unmapped system memory.
1–168 VMEbus Device Driver Example
3
Clears driver information structure fields set up by the MAP_SYS_MEM_TO_VME ioctl command, including the DMA handle in the sys_mem_dma_handle field.
1.11.3 Interrupt Section This section explains how to implement an Interrupt Section that supports device input to wired memory with signaling. This example Interrupt Section provides an interrupt service interface (ISI) for signaling device interrupts, dmaex_rcv_int_srv, and an ISI callout routine, dmaex_post_psignal. The dmaex_rcv_int_srv ISI is installed in the system at run time when a user program invokes the SET_INT_HANDLER ioctl command. (This differs from the normal practice of using the dmaex_probe interface to install ISIs at device configuration.) The dmaex_rcv_int_srv ISI is entered when a VMEbus interrupt has been received at the VMEbus interrupt vector and VMEbus interrupt request level specified in a call to the SET_INT_HANDLER ioctl command. The interface will either wake up the user task waiting for device input immediately by issuing a psignal to SIGUSR1, or, if running at SPLDEVRT (SPL 6), wake up the waiting user task at a slightly later point by invoking the rt_post_callout interface to submit a callout routine, dmaex_post_psignal, for later execution. The dmaex_rcv_int_srv ISI takes one argument: the integer logical unit number for the dmaex device. The following code shows the implementation of the dmaex_rcv_int_srv ISI: static int dmaex_rcv_int_srv(int unit) { register struct dmaex_softc *sc = &dmaex_softc[unit]; 1 if (sc->rcvisr_irq == 7) rt_post_callout(dmaex_post_psignal,(long)sc->rcvisr_proc_p,(long)SIGUSR1); 2 else psignal(sc->rcvisr_proc_p,SIGUSR1); 3 return(ESUCCESS); }
1
2
Declares and initializes a pointer to the device’s driver information structure, initialized using the device logical unit number to reference the correct dmaex_softc structure. If the VMEbus interrupt was received at SPLDEVRT (SPL 6), invokes the rt_post_callout interface and passes it a callout interface,
VMEbus Device Driver Example 1–169
dmaex_post_psignal, that will issue a psignal to the waiting user task when it is safe to do so. On several VMEbus systems, interrupts that are received at VMEbus IRQ level 7 are dispatched to the ISI at SPLDEVRT (SPL 6). Because ISIs executing at SPLDEVRT (SPL 6) must not call kernel interfaces directly, the interface uses the rt_post_callout interface to call psignal while deferring its execution until a time when kernel interfaces can be invoked. The function invoked by rt_post_callout runs at an elevated SPL and is subject to the same restrictions as an ISI. The vme_manual_setup(7) and vme_univ_manual_setup(7) reference pages, in the Tru64 UNIX base operating system man tree, describe the rt_post_callout interface; see the sections titled Realtime Interrupt-Handling Function rt_post_callout. The callout interface, dmaex_post_psignal, is implemented as follows: static int dmaex_post_psignal(struct proc *proc_p,long signal) { psignal(proc_p,signal); return(ESUCCESS); }
3
If the VMEbus interrupt was received at an SPL lower than SPLDEVRT, invokes the psignal interface immediately to wake up the user task waiting on SIGUSR1 for device input.
1.12 Programming User-Space Buffer DMA Transfers Programming user-space buffer DMA transfers involves first coding device driver support for calls to the ioctl file system interface and then invoking the ioctl commands from user code. The VMEbus adapter’s hardware DMA engine performs the physical data transfers. Compaq provides driver and user code templates that you can use as a starting point for implementing user-space buffer DMA transfers for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design. The example user code for implementing user-space buffer DMA transfers performs the following steps: 1.
Calls the kernel-mode driver’s ioctl interface to set up the DMA read or write transfer, specifying the VMEbus address, address modifiers, data size, byte swap mode, the number of bytes to map, and the address of a read or write buffer allocated in user space.
2.
Calls the driver’s ioctl interface to initiate the DMA read or write transfer. During the DMA transfer, the user-space buffer is wired down
1–170 VMEbus Device Driver Example
so that it cannot be swapped out. At the completion of the transfer, system and VMEbus resources are released. The following sections explain the user and driver code required for user-space buffer DMA transfers: User-level code
Section 1.12.1
I/O Control (ioctl) Section
Section 1.12.2
1.12.1 User-Level Code The following test, from dmaex_test.c, shows how user-level code can program user-space buffer DMA transfers, assuming the required driver-level support code has been implemented. The test requires three parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The total number of bytes to transfer
•
data.data[1] — The starting VMEbus address of the DMA transfer
•
data.data[2] — The VMEbus address modifiers, data size, and byte swap mode; values are defined in the io/dec/vme/vbareg.h header file _______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. The third argument to the test specifies whether software byte swapping is needed (1) or not (0); and if software byte swapping is needed, the fourth argument provides the byte swap mode. int dmaex_dma_to_from_user_buffers(int struct dmaex_ioctl_data int vme_atype_t { int buf_size,err,i; int *read_buffer, *write_buffer; int data_error = 0; int error = 0; int b_count = 0; int byte_swap_needed = 0;
fd, *data, sw_byte_swap_needed, byte_swap_mode)
VMEbus Device Driver Example 1–171
if ( (!data->data[0]) || (!(data->data[2] & VME_BITS_MASK)) ) { 1 printf("No test parameters specified \n"); return(1); } buf_size = (int)data->data[0]; if ((sw_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) { if ((byte_swap_mode == VME_BS_QUAD) && 2 (((vme_atype_t)data->data[2] & VME_DSIZE_MASK) != VME_D64)) { perror("VME_BS_QUAD requires VME_D64 to be specified"); return(1); } byte_swap_needed = 1; 3 } write_buffer = (int *)malloc(buf_size); 4 if(!(write_buffer)) { perror("write malloc failed"); return(1); } read_buffer = (int *)malloc(buf_size); 5 if (!(read_buffer)) { free(write_buffer); perror("read malloc failed"); return(1); } data->data[3] = (unsigned long)write_buffer; if ( ioctl(fd, SETUP_DMA_BLK_WRT, data) != 0 ) { 6 free(read_buffer); free(write_buffer); perror("error in ioctl SETUP_DMA_BLK_WRT"); return(1); } data->data[3] = (unsigned long)read_buffer; if ( ioctl(fd, SETUP_DMA_BLK_RD, data) != 0 ) { 7 perror("error in ioctl SETUP_DMA_BLK_RD"); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); free(read_buffer); free(write_buffer); return(1); } if ( ioctl(fd, GET_DMA_BLK_WRT, data) != 0 ) 8 perror("error in ioctl GET_DMA_BLK_WRT"); printf("DMA BLOCK MODE WRITE info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" DMA handle = 0x%lx\n",(unsigned long)data->data[3]); printf(" System Memory Address = 0x%lx\n",(unsigned long)data->data[4]); if ( ioctl(fd, GET_DMA_BLK_RD, data) != 0 ) perror("error in ioctl GET_DMA_BLK_RD"); printf("DMA BLOCK MODE READ info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" DMA handle = 0x%lx\n",(unsigned long)data->data[3]); printf(" System Memory Address = 0x%lx\n",(unsigned long)data->data[4]);
1–172 VMEbus Device Driver Example
for (i = 0; i < (buf_size/sizeof(int)); i++) { 9 write_buffer[i] = i; read_buffer[i] = 0; } if (byte_swap_needed) { 10 if (dmaex_sw_byte_swap(write_buffer,write_buffer, buf_size,byte_swap_mode) == 0) { perror("Error detected byte swapping write buffer data"); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); free(read_buffer); free(write_buffer); return(1); } } if ( ioctl(fd, DO_DMA_BLK_WRT, data) != 0 ) { 11 perror("error in ioctl DO_DMA_BLK_WRT"); error++; } else { if ((b_count = (int)data->data[0]) != buf_size) { perror("error in ioctl DO_DMA_BLK_WRT - incorrect byte count returned"); printf("DO_DMA_BLK_WRT: expected bc = 0x%x actual bc = 0x%x\n", buf_size,b_count); error++; } } if (error) { if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); free(read_buffer); free(write_buffer); return(1); } if ( ioctl(fd, DO_DMA_BLK_RD, data) != 0 ) { 12 perror("error in ioctl DO_DMA_BLK_RD"); error++; } else { if ((b_count = (int)data->data[0]) != buf_size) { perror("error in ioctl DO_DMA_BLK_RD - incorrect byte count returned"); printf("DO_DMA_BLK_RD: expected bc = 0x%x actual bc = 0x%x\n", buf_size,b_count); error++; } } if (error) { if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); free(read_buffer); free(write_buffer); return(1); }
VMEbus Device Driver Example 1–173
if (byte_swap_needed) { 13 dmaex_sw_byte_swap(write_buffer, write_buffer, buf_size, byte_swap_mode); dmaex_sw_byte_swap(read_buffer, read_buffer, buf_size, byte_swap_mode); } printf("Comparing DMA data transferred using ioctl operations.\n"); for (i = 0; i < (buf_size/sizeof(int)); i++) { 14 if (write_buffer[i] != read_buffer[i]) { printf("DMA data error - index 0x%x good = 0x%x bad = 0x%x\n", i,write_buffer[i],read_buffer[i]); data_error++; if (data_error > 16) break; } } if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) 15 perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); free(write_buffer); 16 free(read_buffer); return(0); }
1
Verifies that the test caller specified a byte count and a VMEbus address modifiers argument. If not, the interface displays an error message and returns the error status 1 to the test caller. The second parameter, the starting VMEbus address of the DMA transfer, is not verified because an address of zero (0) is acceptable.
2
If the quadword byte swap mode (VME_BS_QUAD) is specified, verifies that a VMEbus data size of 64 (VME_D64) also is specified. This is a hardware restriction of the VIP/VIC64 VMEbus adapter.
3
If software byte swapping is needed, sets the local flag byte_swap_needed. Software byte swapping is needed if the test caller passed 1 for the sw_byte_swap_needed test argument and a byte swap mode other than VME_BS_NOSWAP for the byte_swap_mode test argument. This implies that the caller has determined both that the data transfer requires byte swapping and that the system’s VMEbus adapter does not provide hardware byte swapping. (The caller may also select software byte swapping over hardware byte swapping where both are available.) The swap arguments provided by dmaex_test to this test are based in part on a platform-specific flag value generated by GET_HW_BYTE_SWAP_INFO, a driver ioctl command that dmaex_test uses to determine the swapping capabilities of the system’s VMEbus adapter. See Section 1.16 for more information.
1–174 VMEbus Device Driver Example
4 5 6
Allocates a write buffer to hold data to be transferred from system memory to the VMEbus by the test. Allocates a read buffer to receive data transferred from the VMEbus to system memory by the test. Calls the SETUP_DMA_BLK_WRT ioctl command to set up DMA mapping for the VMEbus adapter’s hardware DMA engine to perform block DMA writes. This command must be invoked prior to calling the DO_DMA_BLK_WRT ioctl command. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SETUP_DMA_BLK_WRT command takes four arguments:
7
•
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s write buffer in system memory
Section 1.12.2.2 describes the implementation of the SETUP_DMA_BLK_WRT command. Calls the SETUP_DMA_BLK_RD ioctl command to set up DMA mapping for the VMEbus adapter’s hardware DMA engine to perform block DMA reads. This command must be invoked prior to calling the DO_DMA_BLK_RD ioctl command. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SETUP_DMA_BLK_RD command takes four arguments: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s read buffer in system memory
Section 1.12.2.6 describes the implementation of the SETUP_DMA_BLK_RD command.
VMEbus Device Driver Example 1–175
8
For testing purposes, with read and write DMA paths now set up, the user code calls the GET_DMA_BLK_WRT and GET_DMA_BLK_RD ioctl commands and displays the results of the previous calls to SETUP_DMA_BLK_WRT and SETUP_DMA_BLK_RD. Section 1.12.2.3 and Section 1.12.2.7 describe the implementations of the GET_DMA_BLK_WRT and GET_DMA_BLK_RD commands.
9
Initializes the write and read buffers to be used in the data transfers. For testing purposes, the write buffer is filled with a 32-bit binary count pattern and the read buffer is cleared.
10
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the write data as 32-bit integer values based on the specified byte swap mode. After swapping, write_buffer contains the byte-swapped data. The data will be swapped back to its original value after the read. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 11
Calls the DO_DMA_BLK_WRT ioctl command to perform the block DMA write. The number of bytes transferred is returned through the data parameter of the ioctl interface. The user’s write buffer will be wired down before the transfer, so that it cannot be swapped out, and unwired after the transfer. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The DO_DMA_BLK_WRT command takes one argument: data->data[0] — The total number of bytes transferred Section 1.12.2.4 describes the implementation of the DO_DMA_BLK_WRT command.
1–176 VMEbus Device Driver Example
12
Calls the DO_DMA_BLK_RD ioctl command to perform the block DMA read. The number of bytes transferred is returned through the data parameter of the ioctl interface. The user’s read buffer will be wired down before the transfer, so that it cannot be swapped out, and unwired after the transfer. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The DO_DMA_BLK_RD command takes one argument: data->data[0] — The total number of bytes transferred
13
Section 1.12.2.8 describes the implementation of the DO_DMA_BLK_RD command. If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the read and write data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer and write_buffer contain the byte-swapped data. The write data is restored to its original value. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments:
14 15
16
•
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. For testing purposes, the user code compares the read data to the original write data, reporting any difference as an error. Calls the CLR_DMA_BLK_WRT and CLR_DMA_BLK_RD ioctl commands to release the DMA resources used for the transfers. The CLR_DMA_BLK_WRT and CLR_DMA_BLK_RD commands take no arguments. Section 1.12.2.5 and Section 1.12.2.9 describe the implementations of the CLR_DMA_BLK_WRT and CLR_DMA_BLK_RD commands. Releases the write and read buffer memory used for testing and returns success (0) to the test caller.
VMEbus Device Driver Example 1–177
1.12.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support user-space buffer DMA transfers: Implementing the dmaex_ioctl interface
Section 1.12.2.1
Implementing SETUP_DMA_BLK_WRT
Section 1.12.2.2
Implementing GET_DMA_BLK_WRT
Section 1.12.2.3
Implementing DO_DMA_BLK_WRT
Section 1.12.2.4
Implementing CLR_DMA_BLK_WRT
Section 1.12.2.5
Implementing SETUP_DMA_BLK_RD
Section 1.12.2.6
Implementing GET_DMA_BLK_RD
Section 1.12.2.7
Implementing DO_DMA_BLK_RD
Section 1.12.2.8
Implementing CLR_DMA_BLK_RD
Section 1.12.2.9
Implementing dmaex_setup_blk_mode
Section 1.12.2.10
1.12.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation). The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status).
1–178 VMEbus Device Driver Example
The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p; sg_entry_t dmaex_sg; ihandler_t handler; struct vme_handler_info info; unsigned int irq; unsigned int vector;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data,data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 . . . case SETUP_DMA_BLK_WRT 4 . . . break; case GET_DMA_BLK_WRT 5 . . . break; case DO_DMA_BLK_WRT 6 . . . break; case CLR_DMA_BLK_WRT 7 . . . break; case SETUP_DMA_BLK_RD 8 . . . break; case GET_DMA_BLK_RD 9 . . . break; case DO_DMA_BLK_RD 10 . . . break; case CLR_DMA_BLK_RD 11 . . . break; . . .
VMEbus Device Driver Example 1–179
default: ret_val = EINVAL; 12 break; } return(ret_val); 13 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS)
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number.
3
Dispatches control, based on the ioctl command value, to a case block that implements the requested command.
4
Section 1.12.2.2 describes the implementation of the SETUP_DMA_BLK_WRT command.
5
Section 1.12.2.3 describes the implementation of the GET_DMA_BLK_WRT command.
6
Section 1.12.2.4 describes the implementation of the DO_DMA_BLK_WRT command.
7
Section 1.12.2.5 describes the implementation of the CLR_DMA_BLK_WRT command.
8
Section 1.12.2.6 describes the implementation of the SETUP_DMA_BLK_RD command.
9
Section 1.12.2.7 describes the implementation of the GET_DMA_BLK_RD command.
10
Section 1.12.2.8 describes the implementation of the DO_DMA_BLK_RD command.
11
Section 1.12.2.9 describes the implementation of the CLR_DMA_BLK_RD command.
12
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1–180 VMEbus Device Driver Example
13
Returns the last-set dmaex_ioctl completion-status value.
1.12.2.2 Implementing SETUP_DMA_BLK_WRT The SETUP_DMA_BLK_WRT ioctl command sets up the VMEbus adapter’s hardware DMA engine for a block DMA write to a specified VMEbus address for a specified number of bytes applying specified address modifiers. VMEbus mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s write buffer in system memory, or zero (0) to map a physically contiguous kernel-space write buffer preallocated by the driver _______________________
Note
_______________________
For purposes of illustrating user-space buffer DMA writes, the SETUP_DMA_BLK_WRT commands in this section always specify a user-allocated write buffer. (Section 1.13 provides examples of kernel-space buffer DMA transfers that use the physically contiguous read and write buffers preallocated by the driver.) The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SETUP_DMA_BLK_WRT ioctl command in the dmaex driver: case SETUP_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: SETUP_DMA_BLK_WRT\n"); if ( (!(unsigned int)data->data[0]) || 1 (!((unsigned int)data->data[2] & VME_BITS_MASK)) ) { printf("dmaex_ioctl: SETUP_DMA_BLK_WRT, invalid parameters\n"); ret_val = EINVAL; break; } if (sc->blk_wrt_dma_handle) { 2 printf("SETUP_DMA_BLK_WRT failed, a DMA mapping exists.\n"); ret_val = EBUSY; break; }
VMEbus Device Driver Example 1–181
ret_val = dmaex_setup_blk_mode(ctlr,sc,data,DMA_OUT); 3 break;
1
Verifies that the caller specified a mapping size and a VMEbus address modifiers argument.
2
Verifies that a previous DMA mapping request for the device is not active.
3
Calls the dmaex_setup_blk_mode driver interface to perform the main tasks of block DMA write setup. The dmaex_setup_blk_mode interface sets up the VMEbus adapter’s DMA engine for a block DMA read or write. The dmaex_setup_blk_mode interface takes four arguments: •
A pointer to the device’s controller information structure
•
A pointer to the device’s driver information structure
•
A pointer to the data parameter supplied by the ioctl caller
•
An integer directional value: DMA_IN (read) or DMA_OUT (write)
Section 1.12.2.10 describes the implementation of the dmaex_setup_blk_mode interface. 1.12.2.3 Implementing GET_DMA_BLK_WRT The GET_DMA_BLK_WRT ioctl command returns the current block DMA write mapping information for the device. The mapping information is returned through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address mapped
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The DMA handle associated with the block DMA write mapping
•
data->data[4] — The write buffer virtual address
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_DMA_BLK_WRT ioctl command in the dmaex driver: case GET_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: GET_DMA_BLK_WRT\n"); data->data[0] = (unsigned long)sc->blk_wrt_vme_size; 1 data->data[1] = (unsigned long)sc->blk_wrt_vme_addr; data->data[2] = (unsigned long)sc->blk_wrt_vme_am; data->data[3] = (unsigned long)sc->blk_wrt_dma_handle; data->data[4] = (unsigned long)sc->blk_wrt_buf_ptr;
1–182 VMEbus Device Driver Example
break;
1
Copies the block DMA write mapping information from the device’s driver information structure into the first five longwords of the data parameter.
1.12.2.4 Implementing DO_DMA_BLK_WRT The DO_DMA_BLK_WRT ioctl command performs the block DMA write previously set up with a call to the SETUP_DMA_BLK_WRT command. For user-space buffer DMA transfers, DO_DMA_BLK_WRT invokes the vm_map_pageable and vba_dma interfaces. The transfer size is returned through the data parameter of the ioctl interface: data->data[0] — The total number of bytes transferred The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the DO_DMA_BLK_WRT ioctl command in the dmaex driver: case DO_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: DO_DMA_BLK_WRT\n"); if (!sc->blk_wrt_dma_handle) { 1 printf("DO_DMA_BLK_WRT failed, no DMA mapping exists.\n"); ret_val = EINVAL; break; } if (sc->blk_wrt_proc_p) { 2 if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_wrt_buf_ptr), round_page(sc->blk_wrt_buf_ptr + sc->blk_wrt_vme_size), (VM_PROT_READ | VM_PROT_WRITE))) { printf("DO_DMA_BLK_WRT - vm_map_pageable failed wiring\n"); data->data[0] = (unsigned long)0; ret_val = EIO; break; } } data->data[0] = (unsigned long)vba_dma(ctlr,sc->blk_wrt_dma_handle); 3 if (sc->blk_wrt_proc_p) { 4 if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_wrt_buf_ptr), round_page(sc->blk_wrt_buf_ptr + sc->blk_wrt_vme_size), (VM_PROT_NONE))) { printf("DO_DMA_BLK_WRT: vm_map_pageable failed unwiring\n"); ret_val = EIO; } } break;
1
Verifies that a block DMA write mapping has been set up using the SETUP_DMA_BLK_WRT command.
VMEbus Device Driver Example 1–183
2
If the caller mapped a user-space buffer to the VMEbus (always true in this example), invokes the vm_map_pageable interface to wire down the user’s memory so that it cannot be swapped out during hardware DMA. If you use two different DMA buffers from user space, you must wire the system memory before the hardware DMA and unwire it after.
3
Calls the vba_dma interface to perform the actual hardware DMA transfer. The vba_dma interface blocks until the DMA has completed or an error occurs. The return value of the interface is the actual number of bytes transferred. The vba_dma interface takes two arguments:
4
•
The device’s controller information structure
•
The DMA handle returned by the previous call to dma_map_load, as stored in the blk_wrt_dma_handle field of the driver information structure
If the caller mapped a user-space buffer to the VMEbus (always true in this example), unwires the user’s memory so that it again can be swapped out.
1.12.2.5 Implementing CLR_DMA_BLK_WRT The CLR_DMA_BLK_WRT ioctl command releases block DMA write mapping resources allocated by the SETUP_DMA_BLK_WRT command. CLR_DMA_BLK_WRT invokes the dma_map_unload interface. No information is passed or returned in the data parameter of the ioctl interface. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the CLR_DMA_BLK_WRT ioctl command in the dmaex driver: case CLR_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: CLR_DMA_BLK_WRT\n"); if (sc->blk_wrt_dma_handle) { dma_map_unload (DMA_DEALLOC, sc->blk_wrt_dma_handle); 1 sc->blk_wrt_vme_addr = 0; 2 sc->blk_wrt_vme_am = 0; sc->blk_wrt_vme_size = 0; sc->blk_wrt_dma_handle = (dma_handle_t)NULL; sc->blk_wrt_buf_ptr = (caddr_t)NULL; sc->blk_wrt_proc_p = (struct proc *)NULL; } else { printf("dmaex_ioctl: CLR_DMA_BLK_WRT, no VME to unmap.\n"); ret_val = ENXIO; }
1–184 VMEbus Device Driver Example
break;
1
Calls the dma_map_unload interface (with the DMA_DEALLOC flag) to unmap the user’s system memory from the VMEbus and to return the associated mapping resources to the system.
2
Clears driver information structure fields set up by the SETUP_DMA_BLK_WRT ioctl command, including the DMA handle in the blk_wrt_dma_handle field.
1.12.2.6 Implementing SETUP_DMA_BLK_RD The SETUP_DMA_BLK_RD ioctl command sets up the VMEbus adapter’s hardware DMA engine for a block DMA read from a specified VMEbus address for a specified number of bytes applying specified VMEbus address modifiers. VMEbus mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s read buffer in system memory, or zero (0) to map a physically contiguous kernel-space read buffer preallocated by the driver _______________________
Note
_______________________
For purposes of illustrating user-space buffer DMA reads, the SETUP_DMA_BLK_RD commands in this section always specify a user-allocated read buffer. (Section 1.13 provides examples of kernel-space buffer DMA transfers that use the physically contiguous read and write buffers preallocated by the driver.) The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SETUP_DMA_BLK_RD ioctl command in the dmaex driver: case SETUP_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: SETUP_DMA_BLK_RD\n"); if ( (!(unsigned int)data->data[0]) || 1 (!((unsigned int)data->data[2] & VME_BITS_MASK)) ) { printf("dmaex_ioctl: SETUP_DMA_BLK_RD, invalid parameters\n"); ret_val = EINVAL; break;
VMEbus Device Driver Example 1–185
} if (sc->blk_rd_dma_handle) { 2 printf("SETUP_DMA_BLK_RD failed, a DMA mapping exists.\n"); ret_val = EBUSY; break; } ret_val = dmaex_setup_blk_mode(ctlr,sc,data,DMA_IN); 3 break;
1
Verifies that the caller specified a mapping size and a VMEbus address modifiers argument.
2
Verifies that a previous DMA mapping request for the device is not active.
3
Calls the dmaex_setup_blk_mode driver interface to perform the main tasks of block DMA read setup. The dmaex_setup_blk_mode interface sets up the VMEbus adapter’s DMA engine for a block DMA read or write. The dmaex_setup_blk_mode interface takes four arguments: •
A pointer to the device’s controller information structure
•
A pointer to the device’s driver information structure
•
A pointer to the data parameter supplied by the ioctl caller
•
An integer directional value: DMA_IN (read) or DMA_OUT (write)
Section 1.12.2.10 describes the implementation of the dmaex_setup_blk_mode interface. 1.12.2.7 Implementing GET_DMA_BLK_RD The GET_DMA_BLK_RD ioctl command returns the current block DMA read mapping information for the device. The mapping information is returned through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address mapped
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The DMA handle associated with the block DMA read mapping
•
data->data[4] — The read buffer virtual address
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_DMA_BLK_RD ioctl command in the dmaex driver: case GET_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: GET_DMA_BLK_RD\n");
1–186 VMEbus Device Driver Example
data->data[0] data->data[1] data->data[2] data->data[3] data->data[4] break;
1
= = = = =
(unsigned (unsigned (unsigned (unsigned (unsigned
long)sc->blk_rd_vme_size; 1 long)sc->blk_rd_vme_addr; long)sc->blk_rd_vme_am; long)sc->blk_rd_dma_handle; long)sc->blk_rd_buf_ptr;
Copies the block DMA read mapping information from the device’s driver information structure into the first five longwords of the data parameter.
1.12.2.8 Implementing DO_DMA_BLK_RD The DO_DMA_BLK_RD ioctl command performs the block DMA read previously set up with a call to the SETUP_DMA_BLK_RD command. For user-space buffer DMA transfers, DO_DMA_BLK_RD invokes the vm_map_pageable and vba_dma interfaces. The transfer size is returned through the data parameter of the ioctl interface: data->data[0] — The total number of bytes transferred The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the DO_DMA_BLK_RD ioctl command in the dmaex driver: case DO_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: DO_DMA_BLK_RD\n"); if (!sc->blk_rd_dma_handle) { 1 printf("DO_DMA_BLK_RD failed, no DMA mapping exists.\n"); ret_val = EINVAL; break; } if (sc->blk_rd_proc_p) { 2 if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_rd_buf_ptr), round_page(sc->blk_rd_buf_ptr + sc->blk_rd_vme_size), (VM_PROT_READ | VM_PROT_WRITE))) { printf("DO_DMA_BLK_RD - vm_map_pageable failed wiring\n"); data->data[0] = (unsigned long)0; return(EIO); } } data->data[0] = (unsigned long)vba_dma(ctlr,sc->blk_rd_dma_handle); 3 if (sc->blk_rd_proc_p) { 4 if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_rd_buf_ptr), round_page(sc->blk_rd_buf_ptr + sc->blk_rd_vme_size), (VM_PROT_NONE))) { printf("DO_DMA_BLK_RD: vm_map_pageable failed unwiring\n"); ret_val = EIO; } }
VMEbus Device Driver Example 1–187
break;
1
Verifies that a block DMA read mapping has been set up using the SETUP_DMA_BLK_RD command.
2
If the caller mapped a user-space buffer to the VMEbus (always true in this example), invokes the vm_map_pageable interface to wire down the user’s memory so that it cannot be swapped out during hardware DMA. If you use two different DMA buffers from user space, you must wire the system memory before the hardware DMA and unwire it after.
3
Calls the vba_dma interface to perform the actual hardware DMA transfer. The vba_dma interface blocks until the DMA has completed or an error occurs. The return value of the interface is the actual number of bytes transferred. The vba_dma interface takes two arguments:
4
•
The device’s controller information structure
•
The DMA handle returned by the previous call to dma_map_load, as stored in the blk_rd_dma_handle field of the driver information structure
If the caller mapped a user-space buffer to the VMEbus (always true in this example), unwires the user’s memory so that it again can be swapped out.
1.12.2.9 Implementing CLR_DMA_BLK_RD The CLR_DMA_BLK_RD ioctl command releases block DMA read mapping resources allocated by the SETUP_DMA_BLK_RD command. CLR_DMA_BLK_RD invokes the dma_map_unload interface. No information is passed or returned in the data parameter of the ioctl interface. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the CLR_DMA_BLK_RD ioctl command in the dmaex driver: case CLR_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: CLR_DMA_BLK_RD\n"); if (sc->blk_rd_dma_handle) { dma_map_unload (DMA_DEALLOC, sc->blk_rd_dma_handle); 1 sc->blk_rd_vme_addr sc->blk_rd_vme_am sc->blk_rd_vme_size sc->blk_rd_dma_handle sc->blk_rd_buf_ptr sc->blk_rd_proc_p } else {
1–188 VMEbus Device Driver Example
= = = = = =
0; 2 0; 0; (dma_handle_t)NULL; (caddr_t)NULL; (struct proc *)NULL;
printf("dmaex_ioctl: CLR_DMA_BLK_RD, no VME to unmap.\n"); ret_val = ENXIO; } break;
1
Calls the dma_map_unload interface (with the DMA_DEALLOC flag) to unmap the user’s system memory from the VMEbus and to return the associated mapping resources to the system.
2
Clears driver information structure fields set up by the SETUP_DMA_BLK_RD ioctl command, including the DMA handle in the blk_rd_dma_handle field.
1.12.2.10 Implementing dmaex_setup_blk_mode The dmaex_setup_blk_mode driver interface is called by the SETUP_DMA_BLK_WRT and SETUP_DMA_BLK_RD ioctl commands. The interface sets up the VMEbus adapter’s DMA engine for a block DMA write or read operation, using the vba_set_dma_addr, dma_map_load, and dma_get_curr_sgentry interfaces. The actual hardware DMA is performed by the DO_DMA_BLK_WRT and DO_DMA_BLK_RD ioctl commands. The dmaex_setup_blk_mode interface takes four arguments: •
A pointer to the device’s controller information structure
•
A pointer to the device’s driver information structure
•
A pointer to the data parameter supplied by the ioctl caller
•
An integer directional value: DMA_IN (read) or DMA_OUT (write)
The following code shows the implementation of the dmaex_setup_blk_mode interface: static int dmaex_setup_blk_mode(struct controller *ctlr, struct dmaex_softc *sc, struct dmaex_ioctl_data *data, int direction) { struct proc *proc_p = (struct proc *)NULL; unsigned long b_count; sg_entry_t dmaex_sg; vme_atype_t flags; DMAEX_DBG2("dmaex_setup_blk_mode: - direction = 0x%x\n",direction); if (data->data[3]) proc_p = task_to_proc(current_task()); 1 else { if ((data->data[0] > (unsigned long)(CONTIG_RD_WRT_BUF_SIZE)) || (!dmaex_contig_buf)) { printf("dmaex_setup_blk_mode: buffer to large or no kernel buffer\n"); return(EINVAL); }
VMEbus Device Driver Example 1–189
} flags = (vme_atype_t) 2 ((data->data[2] & VME_BITS_MASK) | DMA_SLEEP | DMA_GUARD_UPPER | DMA_ALL); if (direction & DMA_OUT) { 3 sc->blk_wrt_vme_size = (unsigned int)data->data[0]; sc->blk_wrt_vme_addr = (vme_addr_t)data->data[1]; sc->blk_wrt_vme_am = (vme_atype_t)(data->data[2] & VME_BITS_MASK); sc->blk_wrt_dma_handle = (dma_handle_t)NULL; sc->blk_wrt_proc_p = proc_p; flags |= DMA_OUT; if (proc_p) sc->blk_wrt_buf_ptr = (caddr_t)data->data[3]; else sc->blk_wrt_buf_ptr = dmaex_contig_wrt_buf; } else { 4 sc->blk_rd_vme_size = (unsigned int)data->data[0]; sc->blk_rd_vme_addr = (vme_addr_t)data->data[1]; sc->blk_rd_vme_am = (vme_atype_t)(data->data[2] & VME_BITS_MASK); sc->blk_rd_dma_handle = (dma_handle_t)NULL; sc->blk_rd_proc_p = proc_p; flags |= DMA_IN; if (proc_p) sc->blk_rd_buf_ptr = (caddr_t)data->data[3]; else sc->blk_rd_buf_ptr = dmaex_contig_rd_buf; } flags = vba_set_dma_addr(ctlr, flags, (vme_addr_t)data->data[1]); 5 if (direction == DMA_OUT) { b_count = dma_map_load( (unsigned long)sc->blk_wrt_vme_size, 6 (vm_offset_t)sc->blk_wrt_buf_ptr, proc_p, ctlr, &sc->blk_wrt_dma_handle, 0, flags); if ( (!b_count) || (!sc->blk_wrt_dma_handle) ) { printf("dmaex_setup_blk_mode: - write - dma_map_load failed.\n"); return(EIO); } dmaex_sg = dma_get_curr_sgentry(sc->blk_wrt_dma_handle); 7 } else { b_count = dma_map_load( (unsigned long)sc->blk_rd_vme_size, (vm_offset_t)sc->blk_rd_buf_ptr, proc_p, ctlr, &sc->blk_rd_dma_handle, 0, flags); if ( (!b_count) || (!sc->blk_rd_dma_handle) ) { printf("dmaex_setup_blk_mode: - read - dma_map_load failed.\n"); return(EIO); } dmaex_sg = dma_get_curr_sgentry(sc->blk_rd_dma_handle); } DMAEX_DBG2("dmaex_setup_blk_mode: sg.ba = 0x%lx sg.bc = 0x%x\n", dmaex_sg->ba, dmaex_sg->bc);
1–190 VMEbus Device Driver Example
return(ESUCCESS); 8 }
1
If the address of the caller’s read or buffer is a system virtual address (always true in this example), it is a user-space buffer and a process pointer for the current task will be required for the call to the dma_map_load interface. (If it is a kernel-space buffer, a process pointer is not required.) Calls the task_to_proc interface to obtain the process pointer.
2
Constructs a flags argument for the vba_set_dma_addr and dma_map_load interfaces by taking the caller-supplied VMEbus address modifiers and adding DMA-specific flags DMA_SLEEP, DMA_GUARD_UPPER, and DMA_ALL. VMEbus-specific and DMA-specific driver condition flags are defined in the io/dec/vme/vbareg.h and io/common/devdriver.h header files, respectively.
3
If a write is requested (DMA_OUT), sets up for a block mode write as follows:
4
5
•
Copies the caller-supplied block DMA write mapping information into the driver controller structure
•
Stores the address of the write buffer, which in this example is always a user-space buffer supplied by the caller
If a read is requested (DMA_IN), sets up for a block mode read as follows: •
Copies the caller-supplied block DMA read mapping information into the driver controller structure
•
Stores the address of the read buffer, which in this example is always a user-space buffer supplied by the caller
Calls the vba_set_dma_addr interface to provide the VMEbus starting address and modifier flags for a pending DMA transfer. For a block DMA transfer, you must invoke this interface and the flags must include DMA_IN or DMA_OUT, either of which signals a hardware DMA engine transfer to the interface. The vba_set_dma_addr interface takes three arguments: •
A pointer to the device controller information structure
•
A flags argument that contains DMA and VMEbus address modifier flags, as constructed in a previous step; the return value of this interface is a (potentially updated) flags value that you supply to the dma_map_load interface
•
The VMEbus working address, which in this example is the VMEbus starting address specified by the caller
VMEbus Device Driver Example 1–191
6
Calls the dma_map_load interface, passing appropriate read or write variants of arguments, to map the user’s memory buffer to the VMEbus. Upon success, dma_map_load returns the number of bytes mapped; additionally, a DMA handle is placed into a field in the driver information structure. The dma_map_load interface takes seven arguments: •
The maximum size (in bytes) of the data to be transferred during the DMA transfer. The kernel uses this size to determine the software resources (such as mapping registers and I/O channels) to allocate.
•
The virtual address at which the DMA transfer occurs. The interface uses this and the third argument to obtain the physical addresses of the system memory pages to load into DMA mapping resources.
•
A pointer to a proc structure for the current task, representing valid context for the virtual address specified in the second argument. The interface uses this pointer to retrieve the pmap needed to translate the virtual address to a physical address.
•
The controller information structure for this dmaex device. The interface accesses this structure to obtain the bus-specific interfaces and data structures it needs to allocate mapping resources.
•
A pointer to a DMA handle field in the driver information structure, which will receive a DMA handle. A DMA handle represents DMA resources associated with the mapping of an in-memory I/O buffer onto a controller’s I/O bus. This handle provides the information to access bus address/byte count pairs. A bus address/byte count pair is represented by the ba and bc members of an sg_entry structure. Device driver writers can view the DMA handle as the tag to the allocated system resources needed to perform a DMA operation.
•
The maximum-size byte-count value that should be stored in the bc members of the sg_entry structures. This example passes the value zero (0).
•
Flags that represent special conditions the device driver needs the system to support. This example passes the VMEbus address modifier flags returned by the earlier call to the vba_set_dma_addr interface. VMEbus-specific and DMA-specific driver condition flags are defined in the io/dec/vme/vbareg.h and io/common/devdriver.h header files, respectively.
1–192 VMEbus Device Driver Example
Section 4.1 of the driver kit manual Writing VMEbus Device Drivers provides more information about the dma_map_load interface and its arguments. 7
Calls the dma_get_curr_sgentry interface, passing the read or write DMA handle returned by dma_map_load, to obtain the bus address/byte count value pair for the user-space buffer mapped to the VMEbus.
8
On successful completion of either the DMA_OUT (write) or DMA_IN (read) variants of the dma_map_load and dma_get_curr_sgentry calls, returns completion status ESUCCESS to the calling ioctl command (SETUP_DMA_BLK_WRT or SETUP_DMA_BLK_RD).
1.13 Programming Kernel-Space Buffer DMA Transfers Programming kernel-space buffer DMA transfers involves first coding device driver support for calls to the ioctl and mmap interfaces and then invoking those interfaces from user code. The VMEbus adapter’s hardware DMA engine performs the physical data transfers. Compaq provides driver and user code templates that you can use as a starting point for implementing kernel-space buffer DMA transfers for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design. The example user code for implementing kernel-space buffer DMA transfers performs the following steps: 1.
Calls the kernel-mode driver’s ioctl interface to set up the DMA read or write transfer, specifying the VMEbus address, address modifiers, data size, byte swap mode, and the number of bytes to map. The user code does not supply the kernel-space read and write buffers, instead taking advantage of a 128 KB physically contiguous memory section preallocated by the driver.
2.
Calls the driver’s ioctl interface to set the mmap mode to either MMAP_K_TO_U_MEM_RD or MMAP_K_TO_U_MEM_WRT, indicating that a kernel-space buffer is to be mapped to user space for reading or writing, respectively.
3.
Calls the Tru64 UNIX mmap interface to map a kernel-space buffer into user space for DMA read or write transfers.
4.
Calls the driver’s ioctl interface to initiate the DMA read or write transfer.
The driver allocates the 128 KB of physically contiguous kernel-space memory, relied on by the user code in this example, using the cma_dd
VMEbus Device Driver Example 1–193
configuration option in sysconfigtab and the contig_malloc interface in the driver. The example divides this contiguous memory into two equal pieces of 64 KB each. The first section is used for the write buffer and the second is used for the read buffer. These read and write buffers are mapped into user space during this test. When the DMA has completed, the mapped memory and the VMEbus resources are released. The following sections explain the user and driver code required for kernel-space buffer DMA transfers: User-level code
Section 1.13.1
I/O Control (ioctl) Section
Section 1.13.2
Memory Map (mmap) Section
Section 1.13.3
1.13.1 User-Level Code The following test, from dmaex_test.c, shows how user-level code can program kernel-space buffer DMA transfers, assuming the required driver-level support code has been implemented. The test requires three parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The total number of bytes to map and transfer
•
data.data[1] — The starting VMEbus address of the DMA transfer
•
data.data[2] — The VMEbus address modifiers, data size, and byte swap mode; values are defined in the io/dec/vme/vbareg.h header file _______________________
Note
_______________________
The test code assumes that the dmaex device was opened before the test was called and will be closed on return. The file descriptor of the open device is passed as the first argument to the test. The third argument to the test specifies whether software byte swapping is needed (1) or not (0); and if software byte swapping is needed, the fourth argument provides the byte swap mode. int dmaex_dma_to_from_kernel_buffers(int struct dmaex_ioctl_data int vme_atype_t {
1–194 VMEbus Device Driver Example
fd, *data, sw_byte_swap_needed, byte_swap_mode)
int int caddr_t int int int int
buf_size,err,i; *read_buffer, *write_buffer; status; data_error = 0; error = 0; b_count = 0; byte_swap_needed = 0;
if ( (!data->data[0]) || (!(data->data[2] & VME_BITS_MASK))) { 1 printf("No test parameters specified \n"); return(1); } buf_size = (int)data->data[0]; if ((sw_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) { if ((byte_swap_mode == VME_BS_QUAD) && 2 (((vme_atype_t)data->data[2] & VME_DSIZE_MASK) != VME_D64)) { perror("VME_BS_QUAD requires VME_D64 to be specified"); return(1); } byte_swap_needed = 1; 3 } if (buf_size > CONTIG_RD_WRT_BUF_SIZE) { 4 printf("The byte count is to large. The maximum byte count can not be\n"); printf("any larger then 0x%lx when using contiguous kernel buffers\n", CONTIG_RD_WRT_BUF_SIZE); return(1); } data->data[3] = (unsigned long)0; if ( ioctl(fd, SETUP_DMA_BLK_WRT, data) != 0 ) { 5 perror("error in ioctl SETUP_DMA_BLK_WRT"); return(1); } data->data[3] = (unsigned long)0; if ( ioctl(fd, SETUP_DMA_BLK_RD, data) != 0 ) { 6 perror("error in ioctl SETUP_DMA_BLK_RD"); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); return(1); } if ( ioctl(fd, GET_DMA_BLK_WRT, data) != 0 ) 7 perror("error in ioctl GET_DMA_BLK_WRT"); printf("DMA BLOCK MODE WRITE info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" DMA handle = 0x%lx\n",(unsigned long)data->data[3]); printf(" System Memory Address = 0x%lx\n",(unsigned long)data->data[4]); if ( ioctl(fd, GET_DMA_BLK_RD, data) != 0 ) perror("error in ioctl GET_DMA_BLK_RD"); printf("DMA BLOCK MODE READ info: \n"); printf(" VME size = 0x%x\n",(unsigned int)data->data[0]); printf(" VME address = 0x%x\n",(unsigned int)data->data[1]); printf(" VME address modifiers = 0x%x\n",(unsigned int)data->data[2]); printf(" DMA handle = 0x%lx\n",(unsigned long)data->data[3]); printf(" System Memory Address = 0x%lx\n",(unsigned long)data->data[4]); data->data[0] = (unsigned long)MMAP_K_TO_U_MEM_WRT; 8
VMEbus Device Driver Example 1–195
if ( ioctl(fd, SET_MMAP_MODE, data) != 0 ) perror("error in ioctl SET_MMAP_MODE"); write_buffer = (int *)mmap((caddr_t)0, 9 buf_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0); if (write_buffer == (int *)-1) { perror("mmap: failed to mmap kernel write buffer"); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); return(1);; } printf("Successfully called mmap for write. Buffer = 0x%lx\n",write_buffer); for (i = 0; i < (buf_size/sizeof(int)); i++) { 10 write_buffer[i] = i; } if (byte_swap_needed) { 11 if (dmaex_sw_byte_swap(write_buffer,write_buffer, buf_size,byte_swap_mode) == 0) { perror("Error detected byte swapping write buffer data"); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); return(1); } } data->data[0] = (unsigned long)MMAP_K_TO_U_MEM_RD; 12 if ( ioctl(fd, SET_MMAP_MODE, data) != 0 ) perror("error in ioctl SET_MMAP_MODE"); read_buffer = (int *)mmap((caddr_t)0, 13 buf_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0); if (read_buffer == (int *)-1) { perror("mmap: failed to mmap kernel read buffer"); status = (caddr_t) munmap((caddr_t)write_buffer,buf_size); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); return(1);; } printf("Successfully called mmap for read. Buffer = 0x%lx\n",read_buffer); for (i = 0; i < (buf_size/sizeof(int)); i++) { 14 read_buffer[i] = 0; } if ( ioctl(fd, DO_DMA_BLK_WRT, data) != 0 ) { 15 perror("error in ioctl DO_DMA_BLK_WRT"); error++; } else {
1–196 VMEbus Device Driver Example
if ((b_count = (int)data->data[0]) != buf_size) { perror("error in ioctl DO_DMA_BLK_WRT - incorrect byte count returned"); printf("DO_DMA_BLK_WRT: expected bc = 0x%x actual bc = 0x%x\n", buf_size,b_count); error++; } } if (error) { status = (caddr_t) munmap((caddr_t)write_buffer,buf_size); status = (caddr_t) munmap((caddr_t)read_buffer,buf_size); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); return(1); } if ( ioctl(fd, DO_DMA_BLK_RD, data) != 0 ) { 16 perror("error in ioctl DO_DMA_BLK_RD"); error++; } else { if ((b_count = (int)data->data[0]) != buf_size) { perror("error in ioctl DO_DMA_BLK_RD - incorrect byte count returned"); printf("DO_DMA_BLK_RD: expected bc = 0x%x actual bc = 0x%x\n", buf_size,b_count); error++; } } if (error) { status = (caddr_t) munmap((caddr_t)write_buffer,buf_size); status = (caddr_t) munmap((caddr_t)read_buffer,buf_size); if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); return(1); } if (byte_swap_needed) { 17 dmaex_sw_byte_swap(write_buffer, write_buffer, buf_size, byte_swap_mode); dmaex_sw_byte_swap(read_buffer, read_buffer, buf_size, byte_swap_mode); } printf("Comparing DMA data transferred using ioctl operations.\n"); for (i = 0; i < (buf_size/sizeof(int)); i++) { 18 if (write_buffer[i] != read_buffer[i]) { printf("DMA data error - index 0x%x good = 0x%x bad = 0x%x\n", i,write_buffer[i],read_buffer[i]); data_error++; if (data_error > 16) break; } } status = (caddr_t) munmap((caddr_t)write_buffer,buf_size); 19 status = (caddr_t) munmap((caddr_t)read_buffer,buf_size);
VMEbus Device Driver Example 1–197
if ( ioctl(fd, CLR_DMA_BLK_WRT, data) != 0 ) 20 perror("error in ioctl CLR_DMA_BLK_WRT"); if ( ioctl(fd, CLR_DMA_BLK_RD, data) != 0 ) perror("error in ioctl CLR_DMA_BLK_WRT"); return(0); 21 }
1
Verifies that the test caller specified a byte count and a VMEbus address modifiers argument. If not, the interface displays an error message and returns the error status 1 to the test caller. The second parameter, the starting VMEbus address of the DMA transfer, is not verified because an address of zero (0) is acceptable.
2
If the quadword byte swap mode (VME_BS_QUAD) is specified, verifies that a VMEbus data size of 64 (VME_D64) also is specified. This is a hardware restriction of the VIP/VIC64 VMEbus adapter.
3
If software byte swapping is needed, sets the local flag byte_swap_needed. Software byte swapping is needed if the test caller passed 1 for the sw_byte_swap_needed test argument and a byte swap mode other than VME_BS_NOSWAP for the byte_swap_mode test argument. This implies that the caller has determined both that the data transfer requires byte swapping and that the system’s VMEbus adapter does not provide hardware byte swapping. (The caller may also select software byte swapping over hardware byte swapping where both are available.) The swap arguments provided by dmaex_test to this test are based in part on a platform-specific flag value generated by GET_HW_BYTE_SWAP_INFO, a driver ioctl command that dmaex_test uses to determine the swapping capabilities of the system’s VMEbus adapter. See Section 1.16 for more information.
4
Verifies that the specified byte count does not exceed the number of bytes preallocated for each of two physically contiguous kernel-space buffers.
5
Calls the SETUP_DMA_BLK_WRT ioctl command to set up DMA mapping for the VMEbus adapter’s hardware DMA engine to perform block DMA writes. To use the physically contiguous kernel-space write buffer for this test, the fourth data argument must be NULL. This command must be invoked prior to calling the DO_DMA_BLK_WRT ioctl command and mapping the kernel buffer to user space. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SETUP_DMA_BLK_WRT command takes four arguments: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
1–198 VMEbus Device Driver Example
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s write buffer in system memory; specify NULL
Section 1.13.2.2 describes the implementation of the SETUP_DMA_BLK_WRT command. 6
Calls the SETUP_DMA_BLK_RD ioctl command to set up DMA mapping for the VMEbus adapter’s hardware DMA engine to perform block DMA reads. To use the physically contiguous kernel-space read buffer for this test, the fourth data argument must be NULL. This command must be invoked prior to calling the DO_DMA_BLK_RD ioctl command and mapping the kernel buffer to user space. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The SETUP_DMA_BLK_RD command takes four arguments: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s read buffer in system memory; specify NULL
Section 1.13.2.6 describes the implementation of the SETUP_DMA_BLK_RD command. 7
For testing purposes, with read and write DMA paths now set up, the user code calls the GET_DMA_BLK_WRT and GET_DMA_BLK_RD ioctl commands and displays the results of the previous calls to SETUP_DMA_BLK_WRT and SETUP_DMA_BLK_RD. Section 1.13.2.3 and Section 1.13.2.7 describe the implementations of the GET_DMA_BLK_WRT and GET_DMA_BLK_RD commands.
8
Calls the SET_MMAP_MODE ioctl command to specify the type of memory mapping, MMAP_K_TO_U_MEM_WRT, to be performed using the mmap interface. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments.
VMEbus Device Driver Example 1–199
The SET_MMAP_MODE command takes one argument: data->data[0] — The memory mapping mode, MMAP_K_TO_U_MEM_WRT Section 1.13.2.10 describes the implementation of the SET_MMAP_MODE command. 9
Calls the mmap interface to map the kernel write buffer into user space. This mapping will actually be done on the user access, as described in the next step.
10
Initializes the write buffer to be used in the data transfers. The write buffer is filled with a 32-bit binary count pattern. This must be done with the driver’s mmap mode set to MMAP_K_TO_U_MEM_WRT. The driver’s mmap interface will be invoked during the fill in order to wire the kernel memory buffer to user space.
11
If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the write data as 32-bit integer values based on the specified byte swap mode. After swapping, write_buffer contains the byte-swapped data. The data will be swapped back to its original value after the read. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface. 12
Calls the SET_MMAP_MODE ioctl command to specify the type of memory mapping, MMAP_K_TO_U_MEM_RD, to be performed using the mmap interface. Section 1.13.2.10 describes the implementation of the SET_MMAP_MODE command.
13
Calls the mmap interface to map the kernel read buffer into user space. This mapping will actually be done on the user access, as described in the next step.
14
Clears the read buffer to be used in the data transfers. This must be done with the driver’s mmap mode set to MMAP_K_TO_U_MEM_RD.
1–200 VMEbus Device Driver Example
15
The driver’s mmap interface will be invoked during the fill in order to wire the kernel memory buffer to user space. Calls the DO_DMA_BLK_WRT ioctl command to perform the block DMA write. The number of bytes transferred is returned through the data parameter of the ioctl interface. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The DO_DMA_BLK_WRT command takes one argument: data->data[0] — The total number of bytes transferred
16
Section 1.13.2.4 describes the implementation of the DO_DMA_BLK_WRT command. Calls the DO_DMA_BLK_RD ioctl command to perform the block DMA read. The number of bytes transferred is returned through the data parameter of the ioctl interface. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The DO_DMA_BLK_RD command takes one argument: data->data[0] — The total number of bytes transferred
17
Section 1.13.2.8 describes the implementation of the DO_DMA_BLK_RD command. If software byte swapping is needed or selected and the specified byte swap mode is not VME_BS_NOSWAP, swaps the read and write data as 32-bit integer values based on the specified byte swap mode. After swapping, read_buffer and write_buffer contain the byte-swapped data. The write data is restored to its original value. To perform software byte swapping, the test calls dmaex_sw_byte_swap, a user-level interface provided in dmaex_test.c. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
Section 1.15 describes the implementation of the dmaex_sw_byte_swap interface.
VMEbus Device Driver Example 1–201
18
For testing purposes, the user code compares the read data to the original write data, reporting any difference as an error.
19
Calls the munmap interface to unmap the kernel-to-user-level mapping of the kernel write and read buffers used for testing.
20
Calls the CLR_DMA_BLK_WRT and CLR_DMA_BLK_RD ioctl commands to release the DMA resources used for the transfers. The CLR_DMA_BLK_WRT and CLR_DMA_BLK_RD commands take no arguments. Section 1.13.2.5 and Section 1.13.2.9 describe the implementations of the CLR_DMA_BLK_WRT and CLR_DMA_BLK_RD commands.
21
Returns success (0) to the test caller.
1.13.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support kernel-space buffer DMA transfers: Implementing the dmaex_ioctl interface
Section 1.13.2.1
Implementing SETUP_DMA_BLK_WRT
Section 1.13.2.2
Implementing GET_DMA_BLK_WRT
Section 1.13.2.3
Implementing DO_DMA_BLK_WRT
Section 1.13.2.4
Implementing CLR_DMA_BLK_WRT
Section 1.13.2.5
Implementing SETUP_DMA_BLK_RD
Section 1.13.2.6
Implementing GET_DMA_BLK_RD
Section 1.13.2.7
Implementing DO_DMA_BLK_RD
Section 1.13.2.8
Implementing CLR_DMA_BLK_RD
Section 1.13.2.9
Implementing SET_MMAP_MODE
Section 1.13.2.10
Implementing dmaex_setup_blk_mode
Section 1.13.2.11
1.13.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation).
1–202 VMEbus Device Driver Example
The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status). The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p; sg_entry_t dmaex_sg; ihandler_t handler; struct vme_handler_info info; unsigned int irq; unsigned int vector;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 case SET_MMAP_MODE 4 . . . break; . . . case SETUP_DMA_BLK_WRT 5 . . . break; case GET_DMA_BLK_WRT 6 . . . break;
VMEbus Device Driver Example 1–203
case DO_DMA_BLK_WRT 7 . . . break; case CLR_DMA_BLK_WRT 8 . . . break; case SETUP_DMA_BLK_RD 9 . . . break; case GET_DMA_BLK_RD 10 . . . break; case DO_DMA_BLK_RD 11 . . . break; case CLR_DMA_BLK_RD 12 . . . break; . . . default: ret_val = EINVAL; 13 break; } return(ret_val); 14 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS)
2
Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number.
3
Dispatches control, based on the ioctl command value, to a case block that implements the requested command.
4
Section 1.13.2.10 describes the implementation of the SET_MMAP_MODE command.
1–204 VMEbus Device Driver Example
5 6 7 8 9 10 11 12 13 14
Section 1.13.2.2 describes the implementation of the SETUP_DMA_BLK_WRT command. Section 1.13.2.3 describes the implementation of the GET_DMA_BLK_WRT command. Section 1.13.2.4 describes the implementation of the DO_DMA_BLK_WRT command. Section 1.13.2.5 describes the implementation of the CLR_DMA_BLK_WRT command. Section 1.13.2.6 describes the implementation of the SETUP_DMA_BLK_RD command. Section 1.13.2.7 describes the implementation of the GET_DMA_BLK_RD command. Section 1.13.2.8 describes the implementation of the DO_DMA_BLK_RD command. Section 1.13.2.9 describes the implementation of the CLR_DMA_BLK_RD command. If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL. Returns the last-set dmaex_ioctl completion-status value.
1.13.2.2 Implementing SETUP_DMA_BLK_WRT The SETUP_DMA_BLK_WRT ioctl command sets up the VMEbus adapter’s hardware DMA engine for a block DMA write to a specified VMEbus address for a specified number of bytes applying specified address modifiers. VMEbus mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s write buffer in system memory, or zero (0) to map a physically contiguous kernel-space write buffer preallocated by the driver _______________________
Note
_______________________
For purposes of illustrating kernel-space buffer DMA writes, the SETUP_DMA_BLK_WRT commands in this section always
VMEbus Device Driver Example 1–205
specify the preallocated kernel-space write buffer. (Section 1.12 provides examples of user-space buffer DMA transfers that use user-allocated read and write buffers.) The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SETUP_DMA_BLK_WRT ioctl command in the dmaex driver: case SETUP_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: SETUP_DMA_BLK_WRT\n"); if ( (!(unsigned int)data->data[0]) || 1 (!((unsigned int)data->data[2] & VME_BITS_MASK)) ) { printf("dmaex_ioctl: SETUP_DMA_BLK_WRT, invalid parameters\n"); ret_val = EINVAL; break; } if (sc->blk_wrt_dma_handle) { 2 printf("SETUP_DMA_BLK_WRT failed, a DMA mapping exists.\n"); ret_val = EBUSY; break; } ret_val = dmaex_setup_blk_mode(ctlr,sc,data,DMA_OUT); 3 break;
1 2 3
Verifies that the caller specified a mapping size and a VMEbus address modifiers argument. Verifies that a previous DMA mapping request for the device is not active. Calls the dmaex_setup_blk_mode driver interface to perform the main tasks of block DMA write setup. The dmaex_setup_blk_mode interface sets up the VMEbus adapter’s DMA engine for a block DMA read or write. The interface takes four arguments: •
A pointer to the device’s controller information structure
•
A pointer to the device’s driver information structure
•
A pointer to the data parameter supplied by the ioctl caller
•
An integer directional value: DMA_IN (read) or DMA_OUT (write)
Section 1.13.2.11 describes the implementation of the dmaex_setup_blk_mode interface. 1.13.2.3 Implementing GET_DMA_BLK_WRT The GET_DMA_BLK_WRT ioctl command returns the current block DMA write mapping information for the device. The mapping information is returned through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus for the DMA transfer
1–206 VMEbus Device Driver Example
•
data->data[1] — The starting VMEbus address mapped
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The DMA handle associated with the block DMA write mapping
•
data->data[4] — The write buffer virtual address
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_DMA_BLK_WRT ioctl command in the dmaex driver: case GET_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: GET_DMA_BLK_WRT\n"); data->data[0] = (unsigned long)sc->blk_wrt_vme_size; 1 data->data[1] = (unsigned long)sc->blk_wrt_vme_addr; data->data[2] = (unsigned long)sc->blk_wrt_vme_am; data->data[3] = (unsigned long)sc->blk_wrt_dma_handle; data->data[4] = (unsigned long)sc->blk_wrt_buf_ptr; break;
1
Copies the block DMA write mapping information from the device’s driver information structure into the first five longwords of the data parameter.
1.13.2.4 Implementing DO_DMA_BLK_WRT The DO_DMA_BLK_WRT ioctl command performs the block DMA write previously set up with a call to the SETUP_DMA_BLK_WRT command. For kernel-space buffer DMA transfers, DO_DMA_BLK_WRT invokes the vba_dma driver interface. The transfer size is returned through the data parameter of the ioctl interface: data->data[0] — The total number of bytes transferred The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the DO_DMA_BLK_WRT ioctl command in the dmaex driver: case DO_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: DO_DMA_BLK_WRT\n"); if (!sc->blk_wrt_dma_handle) { 1 printf("DO_DMA_BLK_WRT failed, no DMA mapping exists.\n"); ret_val = EINVAL; break; } if (sc->blk_wrt_proc_p) { if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_wrt_buf_ptr), round_page(sc->blk_wrt_buf_ptr + sc->blk_wrt_vme_size), (VM_PROT_READ | VM_PROT_WRITE))) { printf("DO_DMA_BLK_WRT - vm_map_pageable failed wiring\n");
VMEbus Device Driver Example 1–207
data->data[0] = (unsigned long)0; ret_val = EIO; break; } } data->data[0] = (unsigned long)vba_dma(ctlr,sc->blk_wrt_dma_handle); 2 if (sc->blk_wrt_proc_p) { if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_wrt_buf_ptr), round_page(sc->blk_wrt_buf_ptr + sc->blk_wrt_vme_size), (VM_PROT_NONE))) { printf("DO_DMA_BLK_WRT: vm_map_pageable failed unwiring\n"); ret_val = EIO; } } break;
1
Verifies that a block DMA write mapping has been set up using the SETUP_DMA_BLK_WRT command.
2
Calls the vba_dma interface to perform the actual hardware DMA transfer. The vba_dma interface blocks until the DMA has completed or an error occurs. The return value of the interface is the actual number of bytes transferred. The vba_dma interface takes two arguments: •
The device’s controller information structure
•
The DMA handle returned by the previous call to dma_map_load, as stored in the blk_wrt_dma_handle field of the driver information structure
1.13.2.5 Implementing CLR_DMA_BLK_WRT The CLR_DMA_BLK_WRT ioctl command releases block DMA write mapping resources allocated by the SETUP_DMA_BLK_WRT command. CLR_DMA_BLK_WRT invokes the dma_map_unload interface. No information is passed or returned in the data parameter of the ioctl interface. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the CLR_DMA_BLK_WRT ioctl command in the dmaex driver: case CLR_DMA_BLK_WRT: DMAEX_DBG2("dmaex_ioctl: CLR_DMA_BLK_WRT\n"); if (sc->blk_wrt_dma_handle) { dma_map_unload (DMA_DEALLOC, sc->blk_wrt_dma_handle); 1 sc->blk_wrt_vme_addr sc->blk_wrt_vme_am sc->blk_wrt_vme_size sc->blk_wrt_dma_handle
1–208 VMEbus Device Driver Example
= = = =
0; 2 0; 0; (dma_handle_t)NULL;
sc->blk_wrt_buf_ptr = (caddr_t)NULL; sc->blk_wrt_proc_p = (struct proc *)NULL; } else { printf("dmaex_ioctl: CLR_DMA_BLK_WRT, no VME to unmap.\n"); ret_val = ENXIO; } break;
1
2
Calls the dma_map_unload interface (with the DMA_DEALLOC flag) to unmap the user’s system memory from the VMEbus and to return the associated mapping resources to the system. Clears driver information structure fields set up by the SETUP_DMA_BLK_WRT ioctl command, including the DMA handle in the blk_wrt_dma_handle field.
1.13.2.6 Implementing SETUP_DMA_BLK_RD The SETUP_DMA_BLK_RD ioctl command sets up the VMEbus adapter’s hardware DMA engine for a block DMA read from a specified VMEbus address for a specified number of bytes applying specified VMEbus address modifiers. VMEbus mapping information is provided by the ioctl caller in the data parameter: •
data->data[0] — The total number of bytes to map on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address to map
•
data->data[2] — The VMEbus address modifiers (bits 32:16); values are defined in the io/dec/vme/vbareg.h header file
•
data->data[3] — The virtual address of the user’s read buffer in system memory, or zero (0) to map a physically contiguous kernel-space read buffer preallocated by the driver _______________________
Note
_______________________
For purposes of illustrating kernel-space buffer DMA reads, the SETUP_DMA_BLK_RD commands in this section always specify the preallocated kernel-space read buffer. (Section 1.12 provides examples of user-space buffer DMA transfers that use user-allocated read and write buffers.) The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SETUP_DMA_BLK_RD ioctl command in the dmaex driver: case SETUP_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: SETUP_DMA_BLK_RD\n");
VMEbus Device Driver Example 1–209
if ( (!(unsigned int)data->data[0]) || 1 (!((unsigned int)data->data[2] & VME_BITS_MASK)) ) { printf("dmaex_ioctl: SETUP_DMA_BLK_RD, invalid parameters\n"); ret_val = EINVAL; break; } if (sc->blk_rd_dma_handle) { 2 printf("SETUP_DMA_BLK_RD failed, a DMA mapping exists.\n"); ret_val = EBUSY; break; } ret_val = dmaex_setup_blk_mode(ctlr,sc,data,DMA_IN); 3 break;
1 2 3
Verifies that the caller specified a mapping size and a VMEbus address modifiers argument. Verifies that a previous DMA mapping request for the device is not active. Calls the dmaex_setup_blk_mode driver interface to perform the main tasks of block DMA read setup. The dmaex_setup_blk_mode interface sets up the VMEbus adapter’s DMA engine for a block DMA read or write. The interface takes four arguments: •
A pointer to the device’s controller information structure
•
A pointer to the device’s driver information structure
•
A pointer to the data parameter supplied by the ioctl caller
•
An integer directional value: DMA_IN (read) or DMA_OUT (write)
Section 1.13.2.11 describes the implementation of the dmaex_setup_blk_mode interface. 1.13.2.7 Implementing GET_DMA_BLK_RD The GET_DMA_BLK_RD ioctl command returns the current block DMA read mapping information for the device. The mapping information is returned through the data parameter of the ioctl interface: •
data->data[0] — The total number of bytes mapped on the VMEbus for the DMA transfer
•
data->data[1] — The starting VMEbus address mapped
•
data->data[2] — The VMEbus address modifiers
•
data->data[3] — The DMA handle associated with the block DMA read mapping
•
data->data[4] — The read buffer virtual address
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_DMA_BLK_RD ioctl command in the dmaex driver:
1–210 VMEbus Device Driver Example
case GET_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: GET_DMA_BLK_RD\n"); data->data[0] = (unsigned long)sc->blk_rd_vme_size; 1 data->data[1] = (unsigned long)sc->blk_rd_vme_addr; data->data[2] = (unsigned long)sc->blk_rd_vme_am; data->data[3] = (unsigned long)sc->blk_rd_dma_handle; data->data[4] = (unsigned long)sc->blk_rd_buf_ptr; break;
1
Copies the block DMA read mapping information from the device’s driver information structure into the first five longwords of the data parameter.
1.13.2.8 Implementing DO_DMA_BLK_RD The DO_DMA_BLK_RD ioctl command performs the block DMA read previously set up with a call to the SETUP_DMA_BLK_RD command. For kernel-space buffer DMA transfers, DO_DMA_BLK_RD invokes the vba_dma interface. The transfer size is returned through the data parameter of the ioctl interface: data->data[0] — The total number of bytes transferred The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the DO_DMA_BLK_RD ioctl command in the dmaex driver: case DO_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: DO_DMA_BLK_RD\n"); if (!sc->blk_rd_dma_handle) { 1 printf("DO_DMA_BLK_RD failed, no DMA mapping exists.\n"); ret_val = EINVAL; break; } if (sc->blk_rd_proc_p) { if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_rd_buf_ptr), round_page(sc->blk_rd_buf_ptr + sc->blk_rd_vme_size), (VM_PROT_READ | VM_PROT_WRITE))) { printf("DO_DMA_BLK_RD - vm_map_pageable failed wiring\n"); data->data[0] = (unsigned long)0; return(EIO); } } data->data[0] = (unsigned long)vba_dma(ctlr,sc->blk_rd_dma_handle); 2 if (sc->blk_rd_proc_p) { if (vm_map_pageable(current_task()->map, trunc_page(sc->blk_rd_buf_ptr), round_page(sc->blk_rd_buf_ptr + sc->blk_rd_vme_size), (VM_PROT_NONE))) { printf("DO_DMA_BLK_RD: vm_map_pageable failed unwiring\n"); ret_val = EIO;
VMEbus Device Driver Example 1–211
} } break;
1 2
Verifies that a block DMA read mapping has been set up using the SETUP_DMA_BLK_RD command. Calls the vba_dma interface to perform the actual hardware DMA transfer. The vba_dma interface blocks until the DMA has completed or an error occurs. The return value of the interface is the actual number of bytes transferred. The vba_dma interface takes two arguments: •
The device’s controller information structure
•
The DMA handle returned by the previous call to dma_map_load, as stored in the blk_rd_dma_handle field of the driver information structure
1.13.2.9 Implementing CLR_DMA_BLK_RD The CLR_DMA_BLK_RD ioctl command releases block DMA read mapping resources allocated by the SETUP_DMA_BLK_RD command. CLR_DMA_BLK_RD invokes the dma_map_unload interface. No information is passed or returned in the data parameter of the ioctl interface. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the CLR_DMA_BLK_RD ioctl command in the dmaex driver: case CLR_DMA_BLK_RD: DMAEX_DBG2("dmaex_ioctl: CLR_DMA_BLK_RD\n"); if (sc->blk_rd_dma_handle) { dma_map_unload (DMA_DEALLOC, sc->blk_rd_dma_handle); 1 sc->blk_rd_vme_addr = 0; 2 sc->blk_rd_vme_am = 0; sc->blk_rd_vme_size = 0; sc->blk_rd_dma_handle = (dma_handle_t)NULL; sc->blk_rd_buf_ptr = (caddr_t)NULL; sc->blk_rd_proc_p = (struct proc *)NULL; } else { printf("dmaex_ioctl: CLR_DMA_BLK_RD, no VME to unmap.\n"); ret_val = ENXIO; } break;
1
2
Calls the dma_map_unload interface (with the DMA_DEALLOC flag) to unmap the user’s system memory from the VMEbus and to return the associated mapping resources to the system. Clears driver information structure fields set up by the SETUP_DMA_BLK_RD ioctl command, including the DMA handle in the blk_rd_dma_handle field.
1–212 VMEbus Device Driver Example
1.13.2.10 Implementing SET_MMAP_MODE The SET_MMAP_MODE ioctl command defines how the dmaex_mmap interface will interpret a user mmap request. The mmap mode, which should equal MMAP_K_TO_U_MEM_WRT or MMAP_K_TO_U_MEM_RD for mapping kernel-space write or read buffers into user space for DMA transfers, is specified by the ioctl caller in the data parameter: data->data[0] — The memory mapping mode (MMAP_VME_TO_U_MEM, MMAP_K_TO_U_MEM_WRT, or MMAP_K_TO_U_MEM_RD) The dmaex_mmap interface returns a page frame value based on the selected mmap mode. MMAP_VME_TO_U_MEM returns a page frame number representing a mapped VMEbus address, MMAP_K_TO_U_MEM_WRT returns a page frame number representing a physically contiguous cma_dd kernel write buffer, and MMAP_K_TO_U_MEM_RD returns a page frame number representing a physically contiguous cma_dd kernel read buffer. Before the dmaex_mmap interface can use the MMAP_K_TO_U_MEM_WRT and MMAP_K_TO_U_MEM_RD mapping modes, you must also call the SETUP_DMA_BLK_WRT and SETUP_DMA_BLK_RD ioctl commands to establish block DMA (write and read) mapping. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the SET_MMAP_MODE ioctl command in the dmaex driver: case SET_MMAP_MODE: DMAEX_DBG2("dmaex_ioctl: SET_MMAP_MODE\n"); switch ((int)data->data[0]) { case MMAP_VME_TO_U_MEM: case MMAP_K_TO_U_MEM_WRT: case MMAP_K_TO_U_MEM_RD: sc->mmap_mode = (int)data->data[0]; 1 break; default: ret_val = EINVAL; 2 } break;
1 2
Copies the mmap mode provided by the caller into the mmap_mode field of the driver information structure. If an invalid mmap mode value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1.13.2.11 Implementing dmaex_setup_blk_mode The dmaex_setup_blk_mode driver interface is called by the SETUP_DMA_BLK_WRT and SETUP_DMA_BLK_RD ioctl commands.
VMEbus Device Driver Example 1–213
The interface sets up the VMEbus adapter’s DMA engine for a block DMA write or read operation, using the vba_set_dma_addr, dma_map_load, and dma_get_curr_sgentry interfaces. The actual hardware DMA is performed by the DO_DMA_BLK_WRT and DO_DMA_BLK_RD ioctl commands. The dmaex_setup_blk_mode interface takes four arguments: •
A pointer to the device’s controller information structure
•
A pointer to the device’s driver information structure
•
A pointer to the data parameter supplied by the ioctl caller
•
An integer directional value: DMA_IN (read) or DMA_OUT (write)
The following code shows the implementation of the dmaex_setup_blk_mode interface: static int dmaex_setup_blk_mode(struct controller *ctlr, struct dmaex_softc *sc, struct dmaex_ioctl_data *data, int direction) { struct proc *proc_p = (struct proc *)NULL; unsigned long b_count; sg_entry_t dmaex_sg; vme_atype_t flags; DMAEX_DBG2("dmaex_setup_blk_mode: - direction = 0x%x\n",direction); if (data->data[3]) proc_p = task_to_proc(current_task()); else { if ((data->data[0] > (unsigned long)(CONTIG_RD_WRT_BUF_SIZE)) || 1 (!dmaex_contig_buf)) { printf("dmaex_setup_blk_mode: buffer to large or no kernel buffer\n"); return(EINVAL); } } flags = (vme_atype_t) 2 ((data->data[2] & VME_BITS_MASK) | DMA_SLEEP | DMA_GUARD_UPPER | DMA_ALL); if (direction & DMA_OUT) { 3 sc->blk_wrt_vme_size = (unsigned int)data->data[0]; sc->blk_wrt_vme_addr = (vme_addr_t)data->data[1]; sc->blk_wrt_vme_am = (vme_atype_t)(data->data[2] & VME_BITS_MASK); sc->blk_wrt_dma_handle = (dma_handle_t)NULL; sc->blk_wrt_proc_p = proc_p; flags |= DMA_OUT; if (proc_p) sc->blk_wrt_buf_ptr = (caddr_t)data->data[3]; else sc->blk_wrt_buf_ptr = dmaex_contig_wrt_buf; } else { 4 sc->blk_rd_vme_size = (unsigned int)data->data[0]; sc->blk_rd_vme_addr = (vme_addr_t)data->data[1]; sc->blk_rd_vme_am = (vme_atype_t)(data->data[2] & VME_BITS_MASK); sc->blk_rd_dma_handle = (dma_handle_t)NULL;
1–214 VMEbus Device Driver Example
sc->blk_rd_proc_p = proc_p; flags |= DMA_IN; if (proc_p) sc->blk_rd_buf_ptr = (caddr_t)data->data[3]; else sc->blk_rd_buf_ptr = dmaex_contig_rd_buf; } flags = vba_set_dma_addr(ctlr, flags, (vme_addr_t)data->data[1]); 5 if (direction == DMA_OUT) { b_count = dma_map_load( (unsigned long)sc->blk_wrt_vme_size, 6 (vm_offset_t)sc->blk_wrt_buf_ptr, proc_p, ctlr, &sc->blk_wrt_dma_handle, 0, flags); if ( (!b_count) || (!sc->blk_wrt_dma_handle) ) { printf("dmaex_setup_blk_mode: - write - dma_map_load failed.\n"); return(EIO); } dmaex_sg = dma_get_curr_sgentry(sc->blk_wrt_dma_handle); 7 } else { b_count = dma_map_load( (unsigned long)sc->blk_rd_vme_size, (vm_offset_t)sc->blk_rd_buf_ptr, proc_p, ctlr, &sc->blk_rd_dma_handle, 0, flags); if ( (!b_count) || (!sc->blk_rd_dma_handle) ) { printf("dmaex_setup_blk_mode: - read - dma_map_load failed.\n"); return(EIO); } dmaex_sg = dma_get_curr_sgentry(sc->blk_rd_dma_handle); } DMAEX_DBG2("dmaex_setup_blk_mode: sg.ba = 0x%lx sg.bc = 0x%x\n", dmaex_sg->ba, dmaex_sg->bc); return(ESUCCESS); 8 }
1
If the caller is using a kernel-space buffer (always true in this example), verifies that the requested byte count does not exceed the capacity of the physically contiguous read and write buffers preallocated by the driver at configuration time.
2
Constructs a flags argument for the vba_set_dma_addr and dma_map_load interfaces by taking the caller-supplied VMEbus address modifiers and adding DMA-specific flags DMA_SLEEP, DMA_GUARD_UPPER, and DMA_ALL. VMEbus-specific and DMA-specific driver condition flags are defined in the io/dec/vme/vbareg.h and io/common/devdriver.h header files, respectively.
VMEbus Device Driver Example 1–215
3
If a write is requested (DMA_OUT), sets up for a block mode write as follows: •
Copies the caller-supplied block DMA write mapping information into the driver controller structure
•
4
Stores the address of the write buffer, which in this example is always a physically contiguous kernel-space write buffer preallocated by the driver at configuration time If a read is requested (DMA_IN), sets up for a block mode read as follows: •
Copies the caller-supplied block DMA read mapping information into the driver controller structure
•
5
Stores the address of the read buffer, which in this example is always a physically contiguous kernel-space read buffer preallocated by the driver at configuration time Calls the vba_set_dma_addr interface to provide the VMEbus starting address and modifier flags for a pending DMA transfer. For a block DMA transfer, you must invoke this interface and the flags must include DMA_IN or DMA_OUT, either of which signals a hardware DMA engine transfer to the interface. The vba_set_dma_addr interface takes three arguments: •
A pointer to the device controller information structure
•
A flags argument that contains DMA and VMEbus address modifier flags, as constructed in a previous step; the return value of this interface is a (potentially updated) flags value that you supply to the dma_map_load interface
• 6
The VMEbus working address, which in this example is the VMEbus starting address specified by the caller Calls the dma_map_load interface, passing appropriate read or write variants of arguments, to map the user’s memory buffer to the VMEbus. Upon success, dma_map_load returns the number of bytes mapped; additionally, a DMA handle is placed into a field in the driver information structure. The dma_map_load interface takes seven arguments: •
The maximum size (in bytes) of the data to be transferred during the DMA transfer. The kernel uses this size to determine the software resources (such as mapping registers and I/O channels) to allocate.
•
The virtual address at which the DMA transfer occurs. The interface uses this and the third argument to obtain the physical
1–216 VMEbus Device Driver Example
addresses of the system memory pages to load into DMA mapping resources. In this example, this argument is always a kernel address. •
A pointer to a proc structure representing valid context for the virtual address specified in the second argument. In this example, this argument is always zero (0), indicating that the second argument is a kernel address.
•
The controller information structure for this dmaex device. The interface accesses this structure to obtain the bus-specific interfaces and data structures it needs to allocate mapping resources.
•
A pointer to a DMA handle field in the driver information structure, which will receive a DMA handle. A DMA handle represents DMA resources associated with the mapping of an in-memory I/O buffer onto a controller’s I/O bus. This handle provides the information to access bus address/byte count pairs. A bus address/byte count pair is represented by the ba and bc members of an sg_entry structure. Device driver writers can view the DMA handle as the tag to the allocated system resources needed to perform a DMA operation.
•
The maximum-size byte-count value that should be stored in the bc members of the sg_entry structures. This example passes the value zero (0).
•
Flags that represent special conditions the device driver needs the system to support. This example passes the VMEbus address modifier flags returned by the earlier call to the vba_set_dma_addr interface. VMEbus-specific and DMA-specific driver condition flags are defined in the io/dec/vme/vbareg.h and io/common/devdriver.h header files, respectively.
Section 4.1 of the driver kit manual Writing VMEbus Device Drivers provides more information about the dma_map_load interface and its arguments. 7
Calls the dma_get_curr_sgentry interface, passing the read or write DMA handle returned by dma_map_load, to obtain the bus address/byte count value pair for the kernel-space buffer mapped to the VMEbus.
8
On successful completion of either the DMA_OUT (write) or DMA_IN (read) variants of the dma_map_load and dma_get_curr_sgentry calls, returns completion status ESUCCESS to the calling ioctl command (SETUP_DMA_BLK_WRT or SETUP_DMA_BLK_RD).
VMEbus Device Driver Example 1–217
1.13.3 Memory Map (mmap) Section This section explains how to implement a Memory Map Section that supports kernel-space buffer DMA transfers. The dmaex_mmap interface is invoked by the kernel whenever user code performs an mmap(2) system call on the dmaex device. The interface returns the page frame number corresponding to the page at the specified offset. If an error condition is detected, the interface returns the value –1. Depending on the mmap mode set in the driver information structure (sc->mmap_mode), the interface can map a mapped VMEbus address into user space for user programmed I/O to the device, or map a kernel physically contiguous read or write buffer mapped to VMEbus DMA space into user space for DMA transfers. In the case of kernel-space buffer DMA transfers, user code sets the mmap mode to MMAP_K_TO_U_MEM_RD or MMAP_K_TO_U_MEM_WRT, indicating that a kernel physically contiguous read or write buffer mapped to VMEbus DMA space is to be mapped into user space for DMA transfers, before issuing an mmap to the dmaex device. The dmaex_mmap interface takes three arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
An argument of type off_t that specifies a page offset (in bytes)
•
A page protection value (ignored by this implementation)
The following code shows the implementation of the dmaex_mmap interface for kernel-space buffer DMA transfers: static int dmaex_mmap(dev_t dev, off_t off, int prot) { long register int register struct controller register struct dmaex_softc caddr_t int
kpfnum,flags; unit = minor(dev); 1 *ctlr = dmaex_ctlr[unit]; *sc = &dmaex_softc[unit]; phys_addr = 0; error = 0;
DMAEX_DBG1("dmaex_mmap: dmaex%d\n",unit); switch (sc->mmap_mode) { 2 case MMAP_VME_TO_U_MEM: . . . break;
1–218 VMEbus Device Driver Example
case MMAP_K_TO_U_MEM_WRT: if (!sc->blk_wrt_dma_handle) { 3 printf("dmaex_mmap: need to call SETUP_DMA_BLK_WRT ioctl\n"); error++; } else { if (sc->blk_wrt_proc_p) { 4 printf("dmaex_mmap: user buffer mapped - SETUP_DMA_BLK_WRT ioctl\n"); error++; } else { phys_addr = (char *)KSEG_TO_PHYS(sc->blk_wrt_buf_ptr); 5 } } break; case MMAP_K_TO_U_MEM_RD: if (!sc->blk_rd_dma_handle) { 6 printf("dmaex_mmap: need to call SETUP_DMA_BLK_RD ioctl\n"); error++; } else { if (sc->blk_wrt_proc_p) { 7 printf("dmaex_mmap: user buffer mapped - SETUP_DMA_BLK_RD ioctl\n"); error++; } else { phys_addr = (char *)KSEG_TO_PHYS(sc->blk_rd_buf_ptr); 8 } } break; default: printf("dmaex_mmap: Invalid mmap selection mode\n"); error++; } if (error) 9 return(-1); DMAEX_DBG2("*** Bus Physical address = 0x%lx offset = 0x%x\n",phys_addr,off); kpfnum = (long) (((u_long)phys_addr + off) >> 13); 10 DMAEX_DBG2("*** Page frame number = 0x%lx\n",kpfnum); return((int)kpfnum); }
1
Declares and initializes device variables, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the kernel supplied in the dmaex_mmap call
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
•
2
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure Dispatches control, based on the mmap mode value stored in the mmap_mode field of the driver information structure, to a case block that implements the requested form of memory map. In the case of kernel-space buffer DMA transfers, user code must previously have set
VMEbus Device Driver Example 1–219
3
4
5
6
7
8
9
10
the mmap mode to the value MMAP_K_TO_U_MEM_WRT or MMAP_K_TO_U_MEM_WRT by issuing a call to the SET_MMAP_MODE ioctl command. For a kernel-space buffer DMA write, checks whether the required DMA block write mapping was established using the SETUP_DMA_BLK_WRT ioctl command, including mapping of the physically contiguous kernel buffer preallocated by this driver (using contig_malloc) into user space. If not, the interface displays an error message and increments an error reference count. For a kernel-space buffer DMA write, checks whether user code specified its own buffer to the SETUP_DMA_BLK_WRT command, rather than accepting the default physically contiguous kernel buffer preallocated by the driver to support this test. If so, the interface displays an error message and increments an error reference count. For a kernel-space buffer DMA write, retrieves the kseg memory address of the write buffer from the location in which it was stored by the SETUP_DMA_BLK_WRT command and translates the kseg address into a bus physical address. For a kernel-space buffer DMA read, checks whether the required DMA block read mapping was established using the SETUP_DMA_BLK_RD ioctl command, including mapping of the physically contiguous kernel buffer preallocated by this driver (using contig_malloc) into user space. If not, the interface displays an error message and increments an error reference count. For a kernel-space buffer DMA read, checks whether user code specified its own buffer to the SETUP_DMA_BLK_RD command, rather than accepting the default physically contiguous kernel buffer preallocated by the driver to support this test. If so, the interface displays an error message and increments an error reference count. For a kernel-space buffer DMA read, retrieves the kseg memory address of the read buffer from the location in which it was stored by the SETUP_DMA_BLK_RD command and translates the kseg address into a bus physical address. Checks the error reference count for a nonzero (failure) value. If an error condition was detected, the dmaex_mmap interface returns –1 to the kernel. Calculates an Alpha page frame number representing the bus physical address plus the offset (in bytes) specified by the caller. The interface returns the page frame number to the kernel.
1.14 Programming VMEbus Interrupt Requests Programming VMEbus interrupt requests involves first coding device driver support for calls to the ioctl file system interface, coding an interrupt handler routine, and then invoking the ioctl commands from user code.
1–220 VMEbus Device Driver Example
Compaq provides driver and user code templates that you can use as a starting point for implementing VMEbus interrupt requests for your VMEbus device; alternatively, you can create the modules from scratch and use part or all of the Compaq design. The example user code for implementing VMEbus interrupt requests performs either or both of the following operations, which are mutually independent: •
Calls the kernel-mode driver’s ioctl interface to post an interrupt on a specified VMEbus interrupt request (IRQ) line
•
Calls the driver’s ioctl interface to clear a previously posted VMEbus interrupt request or any request on the specified IRQ line
The following sections explain the user and driver code required for VMEbus interrupt requests: User-level code
Section 1.14.1
I/O Control (ioctl) Section
Section 1.14.2
Interrupt Section
Section 1.14.3
1.14.1 User-Level Code The following two tests, from dmaex_test.c, show how VMEbus interrupt requests can be programmed from user level, assuming the required driver-level support code has been implemented. The interrupt-posting test requires two parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The VMEbus IRQ level of the interrupt request to post
•
data.data[1] — The VMEbus interrupt vector to present upon interrupt acknowledgement _______________________
Note
_______________________
Both tests assume that the dmaex device was previously opened and subsequently will be closed. The file descriptor of the open device is required by ioctl commands invoked by the tests. if ( ioctl(fd, VME_POST_IRQ, &data) != 0 ) 1 perror("error in ioctl VME_POST_IRQ");
1
Calls the VME_POST_IRQ ioctl command to post an interrupt on a specified VMEbus IRQ line and to present the VMEbus interrupt
VMEbus Device Driver Example 1–221
vector on acknowledgement. The driver waits for 1 second for the VMEbus request to be acknowledged. If the request times out, the VMEbus interrupt request will be cleared. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The VME_POST_IRQ command takes two arguments, in this case the same arguments that were passed to the test itself (VMEbus IRQ level and VMEbus interrupt vector). Section 1.14.2.2 describes the implementation of the VME_POST_IRQ command. The interrupt-clearing test requires two parameters to be passed in the dmaex_ioctl_data data structure as the second argument to the test: •
data.data[0] — The VMEbus IRQ level to clear
•
data.data[1] — The VMEbus interrupt vector of a previously posted interrupt request, or zero (0) to unconditionally clear an interrupt at the specified IRQ if ( ioctl(fd, VME_CLR_IRQ, &data) != 0 ) 1 perror("error in ioctl VME_CLR_IRQ");
1
Calls the VME_CLR_IRQ ioctl command to clear a previously posted VMEbus interrupt request or any request on the specified IRQ line. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The VME_POST_IRQ command takes two arguments, in this case the same arguments that were passed to the test itself (VMEbus IRQ level and optional VMEbus interrupt vector to match). Section 1.14.2.3 describes the implementation of the VME_CLR_IRQ command.
1.14.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to support VMEbus interrupt requests:
1–222 VMEbus Device Driver Example
Implementing the dmaex_ioctl interface
Section 1.14.2.1
Implementing VME_POST_IRQ
Section 1.14.2.2
Implementing VME_CLR_IRQ
Section 1.14.2.3
1.14.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation). The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status). The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p; sg_entry_t dmaex_sg; ihandler_t handler; struct vme_handler_info info; unsigned int irq; unsigned int vector;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
VMEbus Device Driver Example 1–223
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 . . . case VME_POST_IRQ 4 . . . break; case VME_CLR_IRQ 5 . . . break; . . . default: ret_val = EINVAL; 6 break; } return(ret_val); 7 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
• 2
3 4
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS) Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number. Dispatches control, based on the ioctl command value, to a case block that implements the requested command. Section 1.14.2.2 describes the implementation of the VME_POST_IRQ command.
5
Section 1.14.2.3 describes the implementation of the VME_CLR_IRQ command.
6
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1–224 VMEbus Device Driver Example
7
Returns the last-set dmaex_ioctl completion-status value.
1.14.2.2 Implementing VME_POST_IRQ The VME_POST_IRQ ioctl command calls the vba_post_irq interface to post an interrupt at the specified VMEbus IRQ level and interrupt vector. The VMEbus adapter code, upon detection of a VMEbus IACK interrupt acknowledging the post, dispatches control to an IACK service interface provided by the dmaex driver, dmaex_iack_isr. The dmaex_iack_isr interface signals an IACK event to indicate that the interrupt request was acknowledged. The VME_POST_IRQ command waits on the IACK event with a 1-second timeout. If the request times out without receiving an IACK interrupt, the command dismisses the VMEbus interrupt with a call to the vba_clear_irq interface. If the IACK EVENT is signaled and received, the ioctl interface returns success. VMEbus interrupt information is provided by the ioctl caller in the data parameter: •
data->data[0] — The VMEbus IRQ level of the interrupt request to post
•
data->data[1] — The VMEbus interrupt vector to present upon interrupt acknowledgement
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the VME_POST_IRQ ioctl command in the dmaex driver: case VME_POST_IRQ: DMAEX_DBG2("dmaex_ioctl: VME_POST_IRQ\n"); irq = (unsigned int)data->data[0]; vector = (unsigned int)data->data[1]; if ((irq < 1) || (irq > 7)) { 1 printf("dmaex_ioctl: VME_POST_IRQ, invalid IRQ specified\n"); ret_val = EINVAL; break; } event_clear((event_t *)&sc->post_iack_event[irq]); 2 ret_val = vba_post_irq(ctlr,irq,vector,dmaex_iack_isr); 3 switch (ret_val) { case 0: printf("dmaex_ioctl: Interrupt could not be posted\n"); ret_val = EBUSY; break; case 1: ret_val = event_wait((event_t *)&sc->post_iack_event[irq], 4 TRUE, 1 * hz); if (ret_val) { 5
VMEbus Device Driver Example 1–225
printf("dmaex_ioctl: Failed to receive IACK interrupt\n"); if (vba_clear_irq(ctlr,irq,vector) == FALSE) printf("Failed to clear posted IRQ\n"); ret_val = EIO; } event_clear((event_t *)&sc->post_iack_event[irq]); 6 break; case -1: printf("dmaex_ioctl: Interface not supported\n"); ret_val = ENOTSUP; break; default: printf("dmaex_ioctl: Unexpected status returned\n"); ret_val = EIO; break; } break;
1
2
3
4
5
6
Verifies that the caller-specified VMEbus IRQ level is in the range 1 through 7. If not, the command displays an error message and sets the ioctl completion status to EINVAL. Calls the event_clear interface to clear the IACK event before posting the VMEbus interrupt request. Section 4.6 of the driver kit manual Writing VMEbus Device Drivers describes the event_clear interface. Calls the vba_post_irq interface to post the VMEbus interrupt request at the specified VMEbus IRQ level and interrupt vector. The fourth argument to vba_post_irq provides an IACK service interface, dmaex_iack_isr, to be invoked upon detection of the IACK interrupt acknowledging the interrupt request. Upon successful posting of the VMEbus interrupt, calls the event_wait interface to wait up to 1 second for the IACK interrupt to be signaled by the dmaex_iack_isr interface. Section 4.6 of the driver kit manual Writing VMEbus Device Drivers describes the event_wait interface. If the event_wait times out before the IACK interrupt is signaled, the command displays an error message, calls the vba_clear_irq interface to dismiss the posted interrupt, and sets the ioctl completion status to EIO. Whether or not the IACK interrupt was signaled, calls the event_clear interface to clear the IACK event.
1.14.2.3 Implementing VME_CLR_IRQ The VME_CLR_IRQ ioctl command clears an interrupt request previously posted by the VME_POST_IRQ command or, alternatively, can unconditionally clear an interrupt request at a specified IRQ level. VMEbus interrupt information is provided by the ioctl caller in the data parameter:
1–226 VMEbus Device Driver Example
•
data->data[0] — The VMEbus IRQ level to clear
•
data->data[1] — The VMEbus interrupt vector of a previously posted interrupt request, or zero (0) to unconditionally clear an interrupt request at the specified IRQ
The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the VME_CLR_IRQ ioctl command in the dmaex driver: case VME_CLR_IRQ: DMAEX_DBG2("dmaex_ioctl: VME_CLR_IRQ\n"); irq = (unsigned int)data->data[0]; vector = (unsigned int)data->data[1]; if ((irq < 1) || (irq > 7)) { 1 printf("dmaex_ioctl: VME_CLR_IRQ, invalid IRQ specified\n"); ret_val = EINVAL; break; } if (vba_clear_irq(ctlr,irq,vector) == FALSE) { 2 printf("IRQ not cleared or invalid vector/irq specified\n"); ret_val = EIO; } break;
1
Verifies that the caller-specified VMEbus IRQ level is in the range 1 through 7. If not, the command displays an error message and sets the ioctl completion status to EINVAL.
2
Calls the vba_clear_irq interface to clear an interrupt request. If the caller-specified interrupt vector equals zero (0), this will unconditionally clear an interrupt request at the specified IRQ. If the vector is nonzero, it must match the vector previously specified to the VME_POST_IRQ command at the specified VMEbus IRQ.
1.14.3 Interrupt Section This section explains how to implement an Interrupt Section that supports VMEbus interrupt requests. This example Interrupt Section provides an interrupt service interface (ISI) for IACKs of posted interrupts, dmaex_iack_isr. The dmaex_iack_isr ISI is installed in the system not at device configuration by the dmaex_probe interface but at runtime when a user program invokes the VME_POST_IRQ ioctl command. The dmaex_iack_isr ISI is entered when a VMEbus IACK interrupt has been detected by the VMEbus adapter code. The VMEbus IACK interrupt occurs in response to posting of an interrupt request by the VME_POST_IRQ command. The ISI communicates receipt of the interrupt acknowledgement to the driver task executing the VME_POST_IRQ
VMEbus Device Driver Example 1–227
command by posting an IACK event. The driver waits up to 1 second after posting an interrupt for an IACK event to be posted. The dmaex_iack_isr interface takes two arguments: •
A pointer to the device’s controller information structure
•
The current IRQ level
The following code shows the implementation of the dmaex_iack_isr interface: static int dmaex_iack_isr(struct controller *ctlr, unsigned int irq) { register struct dmaex_softc *sc = &dmaex_softc[ctlr->ctlr_num]; 1 DMAEX_DBG1("dmaex_iack_isr: dmaex%d\n",ctlr->ctlr_num); event_post( (event_t *)&sc->post_iack_event[irq]); 2 return(ESUCCESS); }
1
2
Declares a pointer to the device’s driver information structure and initializes it by using the controller number stored in the controller information structure to reference the correct dmaex_softc structure for the device. Calls the event_post interface to post an IACK event to the VME_POST_IRQ command to indicate that the posted interrupt was acknowledged. Section 4.6 of the driver kit manual Writing VMEbus Device Drivers describes the event_post interface.
1.15 Programming User-Level Software Byte Swapping This section shows how user-level code can program software byte swapping to support the needs of platforms based on VMEbus adapters that do not implement hardware byte swapping, such as the UNIVERSE II. The dmaex_sw_byte_swap interface, available in dmaex_test.c, is invoked by several of the dmaex_test tests to perform software byte swapping when running on UNIVERSE II based SBCs. The dmaex_sw_byte_swap interface takes four arguments: •
A pointer to the source data buffer
•
A pointer to a buffer to receive the byte-swapped data
•
The size of the data buffer in bytes
•
The byte swap mode: VME_BS_NOSWAP, VME_BS_BYTE, VME_BS_WORD, VME_BS_LWORD, or VME_BS_QUAD
The dmaex_sw_byte_swap interface returns a success (1) or failure (0) status value.
1–228 VMEbus Device Driver Example
The following code shows the implementation of the dmaex_sw_byte_swap interface: int dmaex_sw_byte_swap(int *src, int *dst, int bc, vme_atype_t byte_swap_mode) { int i,tmp1,tmp; int cnt_of_lwords = (bc/sizeof(int)); if (bc % sizeof(int)) { printf("dmaex_sw_byte_swap: byte count %d not multiple of %d\n", bc,sizeof(int)); return(0); } if (((u_long)src & 3UL) || ((u_long)dst & 3UL)) { printf("dmaex_sw_byte_swap: buffers must be long word aligned\n"); return(0); } if ((byte_swap_mode == VME_BS_QUAD) && (bc % sizeof(long))) { printf ("dmaex_sw_byte_swap: VME_BS_QUAD byte count %d not multiple of %d\n", bc,sizeof(long)); return(0); } switch (byte_swap_mode) { case VME_BS_NOSWAP: /**************************************** * exact copy of data * ****************************************/ for(i = 0; i < cnt_of_lwords; i++, src++, dst++) *dst = *src; break; case VME_BS_BYTE: /**************************************** * swap bytes within words * ****************************************/ for(i = 0; i < cnt_of_lwords; i++, src++, dst++) *dst = (int)swap_word_bytes(*(u_int *)src); 1 break; case VME_BS_WORD: /**************************************** * swap words within long words * ****************************************/ for(i = 0; i < cnt_of_lwords; i++, src++, dst++) *dst = (int)swap_words(*(u_int *)src); 2 break; case VME_BS_LWORD: /**************************************** * swap bytes within words and * * swap words within long word * ****************************************/ for(i = 0; i < cnt_of_lwords; i++, src++, dst++) *dst = (int)swap_lw_bytes(*(u_int *)src); 3 break;
VMEbus Device Driver Example 1–229
case VME_BS_QUAD: /**************************************** * swap bytes within words and * * swap words within long word and * * swap long words within quad word * ****************************************/ for(i = 0; i < cnt_of_lwords; i += 2) { tmp1 = (int)swap_lw_bytes((u_int)src[i]); 4 tmp = (int)swap_lw_bytes((u_int)src[i+1]); dst[i] = tmp; dst[i+1] = tmp1; } break; default: printf("Byte swap mode 0x%x invalid\n",byte_swap_mode); } return(1); }
1
Invokes the swap_word_bytes macro to swap bytes within words. For example, if you start with: 31 0 +---+---+---+---+ | a | b | c | d | +---+---+---+---+
you end with: 31 0 +---+---+---+---+ | b | a | d | c | +---+---+---+---+
The macro is defined in dmaex_test.c user code as follows: #define swap_word_bytes(data) \ (u_int)((((u_int)data >> 8) & (((u_int)data & 0xFF) ((((u_int)data >> 16) ((((u_int)data >> 24)
2
0xFF) | << 8) | & 0xFF) & 0xFF)
\ \ << 24) | \ << 16))
Device drivers can also use a corresponding swap_word_bytes kernel interface. Invokes the swap_words macro to swap words within longwords. For example, if you start with: 31 0 +---+---+---+---+ | a | b | c | d | +---+---+---+---+
you end with: 31 0 +---+---+---+---+ | c | d | a | b | +---+---+---+---+
The macro is defined in dmaex_test.c user code as follows: #define swap_words(data) \ (u_int)((((u_int)data >> 16) & 0xFFFF) | \
1–230 VMEbus Device Driver Example
(((u_int)data & 0xFFFF) << 16))
Device drivers can also use a corresponding swap_words kernel interface. 3
Invokes the swap_lw_bytes macro to swap bytes within words and words within longwords. For example, if you start with: 31 0 +---+---+---+---+ | a | b | c | d | +---+---+---+---+
you end with: 31 0 +---+---+---+---+ | d | c | b | a | +---+---+---+---+
The macro is defined in dmaex_test.c user code as follows: #define swap_lw_bytes(data) \ (u_int)(((((((u_int)data >> 8) & 0xFF) | \ (((u_int)data & 0xFF) << 8)) << 16) | \ (((u_int)data >> 24) & 0xFF) | \ ((((u_int)data >> 16) & 0xFF) << 8)))
Device drivers can also use a corresponding swap_lw_bytes kernel interface. 4
Executes a loop to swap quadword bytes; that is, bytes within words, words within longwords, and longwords within quadwords. For example, if you start with: 63 0 +---+---+---+---+---+---+---+---+ | a | b | c | d | e | f | g | h | +---+---+---+---+---+---+---+---+
you end with: 63 0 +---+---+---+---+---+---+---+---+ | h | g | f | e | d | c | b | a | +---+---+---+---+---+---+---+---+
There is no corresponding kernel interface available to drivers for quadword swap. Device drivers must use the kernel interface swap_lw_bytes and address manipulation to emulate D64 quadword swap.
1.16 Generating Platform-Specific Flag Values With the addition of UNIVERSE II adapter support in Tru64 UNIX Version 4.0F, VMEbus device drivers that support both UNIVERSE II and non-UNIVERSE II platforms require the ability to determine key attributes of the system’s VMEbus adapter:
VMEbus Device Driver Example 1–231
•
Adapter type (VIP/VIC, UNIVERSE II, DWP64, DWPVC, TurboChannel)
•
Whether the adapter supports hardware byte swapping
•
Whether the platform is byte/word I/O capable
Knowledge of the adapter’s type and capabilities bears on coding decisions such as whether software byte swapping must be performed, whether sparse space mapping is provided in the system, and where to introduce platform-specific conditional coding. Beginning with Version 4.0F, Tru64 UNIX provides the vba_get_info interface, which returns the VMEbus adapter type and capabilities. Also, supplementing the vba_get_info interface, the dmaex example driver provides two ioctl commands, GET_HW_BYTE_SWAP_INFO and GET_VBA_BUS_TYPE_INFO, that generate platform-specific flag variables usable (and used by the driver example itself) for platform-specific conditional coding. During Tru64 UNIX system initialization, bus configuration code calls the dmaex_probe driver interface to determine whether the VMEbus device being configured is present. If the Tru64 UNIX version is 4.0F or later, dmaex_probe calls the vba_get_info interface to determine the system adapter type, and whether the adapter supports hardware byte swapping. Based on the adapter information returned, dmaex_probe sets two sysconfigtab parameters: •
dmaex_sw_byte_swap, indicating to driver and user code whether software byte swapping is needed
•
dmaex_univ_adapter, indicating to driver and user code whether the adapter is a UNIVERSE II (requiring that VMEbus mapping for PIO be exclusively through PCI dense space)
The dmaex_probe code for doing this is shown and described in Section 1.5.1. The dmaex_test user code performs similar adapter checks when it starts up: issuing the dmaex driver ioctl commands GET_HW_BYTE_SWAP_INFO and GET_VBA_BUS_TYPE_INFO (each of which call vba_get_info in turn) and adjusting test arguments that specify swap and mapping information acccording to the system adapter’s type and capabilities. The following sections illustrate how the dmaex_test user-level and dmaex driver-level code create and use platform-specific flag variables:
1–232 VMEbus Device Driver Example
User-level code
Section 1.16.1
I/O Control (ioctl) Section
Section 1.16.2
1.16.1 User-Level Code The following user code from the main routine of dmaex_test.c invokes the ioctl command GET_HW_BYTE_SWAP_INFO and uses the returned information to supply swap-related arguments to user tests that read and write data: if ((ioctl(fd, GET_HW_BYTE_SWAP_INFO, &data)) != 0) { 1 perror("GET_HW_BYTE_SWAP_INFO"); close(fd); exit(1); } software_byte_swap_needed = (int)data.data[0]; 2 if (software_byte_swap_needed) printf("The application must perform software byte swap when needed.\n"); . . . byte_swap_mode = (vme_atype_t)(data.data[2] & (u_long)VME_BS_MASK); 3 if ((software_byte_swap_needed) && (byte_swap_mode != VME_BS_NOSWAP)) { 4 data.data[2] &= ~((u_long)VME_BS_MASK); data.data[2] |= (u_long)VME_BS_NOSWAP; } }
1
Calls the GET_HW_BYTE_SWAP_INFO ioctl command to return information about the system adapter’s byte swap capability. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments. The GET_HW_BYTE_SWAP_INFO command requires no input arguments in the data parameter but returns a value to data->data[0]: 0 if the adapter supports hardware byte swapping, 1 if software byte swapping will be required. Section 1.16.2.2 describes the implementation of the GET_HW_BYTE_SWAP_INFO command.
2
Sets the flag variable software_byte_swap_needed equal to the value returned in data.data[0] by GET_HW_BYTE_SWAP_INFO. This variable is subsequently passed as the third argument to every dmaex_test test that reads and writes data and might be required to byte swap.
3
Saves the user-specified byte swap mode in the flag variable byte_swap_mode. This variable represents the type of byte swap that software must perform and is subsequently passed as the fourth argument to every dmaex_test test that reads and writes data and might be required to byte swap.
VMEbus Device Driver Example 1–233
4
If software byte swapping is needed or selected, overwrites the original user-specified byte swap mode encoded in the second test argument (and copied to the fourth test argument in the previous step) with VME_BS_NOSWAP, indicating no hardware swapping. The user code will need to implement software byte swapping before writing and after reading data.
The following user code from the main routine of dmaex_test.c invokes the ioctl command GET_VBA_BUS_TYPE_INFO and uses the returned information to alter a VMEbus address modifer in the second argument to user tests that map to the VMEbus through PCI space for programmed I/O: if ((ioctl(fd, GET_VBA_BUS_TYPE_INFO, &data)) != 0) { 1 perror("GET_HW_BYTE_SWAP_INFO"); close(fd); exit(1); } universe_adapter = (int)data.data[0]; 2 if (universe_adapter) printf("The Universe II PCI to VME adapter has been detected\n"); . . . if ((universe_adapter) && ((test_num == 1) || (test_num == 3))) 3 data.data[2] |= VME_DENSE;
1
Calls the GET_VBA_BUS_TYPE_INFO ioctl command to return information about the system adapter type. The ioctl interface takes three arguments: the file descriptor of the open device, the command value, and a data block that contains command arguments.
2 3
The GET_VBA_BUS_TYPE_INFO command requires no input arguments in the data parameter but returns a value to data->data[0] that indicates whether the adapter is a UNIVERSE II (1) or not (0). Section 1.16.2.3 describes the implementation of the GET_VBA_BUS_TYPE_INFO command. Sets the flag variable universe_adapter equal to the value returned in data.data[0] by GET_VBA_BUS_TYPE_INFO. For the two tests that map to the VMEbus for programmed I/O, if the system adapter is a UNIVERSE II: Unconditionally sets the VME_DENSE flag field encoded in the second test argument. This ensures that all mapping to the VMEbus on UNIVERSE II based platforms will be through PCI dense space (not sparse space).
1.16.2 I/O Control (ioctl) Section The following sections explain how to implement ioctl commands that the dmaex device driver uses to return platform-specific flag values.
1–234 VMEbus Device Driver Example
Implementing the dmaex_ioctl interface
Section 1.16.2.1
Implementing GET_HW_BYTE_SWAP_INFO
Section 1.16.2.2
Implementing GET_VBA_BUS_TYPE_INFO
Section 1.16.2.3
1.16.2.1 Implementing the dmaex_ioctl Interface The file system invokes the dmaex_ioctl interface whenever user code performs an ioctl operation on the dmaex device. The ioctl interface called by user code takes four arguments: the file descriptor of the open device, the ioctl command value, a data block that contains command arguments (if any), and the access mode of the device (ignored by this implementation). The dmaex_ioctl interface invoked for you by the file system takes four arguments: •
An argument of type dev_t that specifies the major and minor numbers for a specific dmaex device
•
The ioctl command value
•
A pointer to a structure of type dmaex_ioctl_data that contains command arguments (if any)
•
An integer bit-mask argument that specifies the access mode of the device (not used by this implementation); flags that represent access modes are defined in the sys/fcntl.h header file
The dmaex_ioctl interface returns an integer completion status (ESUCCESS or error status). The following code shows the implementation of the dmaex_ioctl interface, which provides a simple framework for implementing driver-specific I/O control commands: static int dmaex_ioctl(dev_t int struct dmaex_ioctl_data int { int unit = struct dmaex_softc *sc = struct controller *ctlr = int ret_val = struct proc *proc_p; sg_entry_t dmaex_sg; ihandler_t handler; struct vme_handler_info info; unsigned int irq; unsigned int vector;
dev, cmd, *data, flag) minor(dev); 1 &dmaex_softc[unit]; dmaex_ctlr[unit]; ESUCCESS;
VMEbus Device Driver Example 1–235
DMAEX_DBG1("dmaex_ioctl: dmaex%d\n",unit); DMAEX_DBG2("param0 = 0x%lx param1 = 0x%lx param2 = 0x%lx\n", data->data[0],data->data[1],data->data[2]); if ((unit >= NDMAEX) || (unit < 0)) 2 return(ENODEV); switch(cmd) { 3 . . . case GET_HW_BYTE_SWAP_INFO 4 . . . break; case GET_VBA_BUS_TYPE_INFO 5 . . . break; . . . default: ret_val = EINVAL; 6 break; } return(ret_val); 7 }
1
Declares and initializes variables required for I/O control, including: •
The device minor number, initialized by invoking the minor interface and passing it the dev value the file system supplied in the dmaex_ioctl call
•
A pointer to the device’s driver information structure, initialized using the device minor number to reference the correct dmaex_softc structure
•
A pointer to the device’s controller information structure, initialized using the device minor number to reference the correct dmaex_ctlr structure
• 2
3 4
The dmaex_ioctl completion-status value, initialized to a default of success (ESUCCESS) Verifies that the device minor number falls within the range 0 through NDMAEX–1, where NDMAEX is the number of device units allowed on the system. If not, the ENODEV error constant is returned to indicate an invalid device number. Dispatches control, based on the ioctl command value, to a case block that implements the requested command. Section 1.16.2.2 describes the implementation of the GET_HW_BYTE_SWAP_INFO command.
5
Section 1.16.2.3 describes the implementation of the GET_VBA_BUS_TYPE_INFO command.
6
If an invalid command value was passed, sets the dmaex_ioctl completion-status value to EINVAL.
1–236 VMEbus Device Driver Example
7
Returns the last-set dmaex_ioctl completion-status value.
1.16.2.2 Implementing GET_HW_BYTE_SWAP_INFO The GET_HW_BYTE_SWAP_INFO ioctl command returns a value to indicate whether the VMEbus adapter supports hardware byte swapping (0) or the application must perform software byte swapping (1). The GET_HW_BYTE_SWAP_INFO command requires no input arguments in the data parameter but returns its 0 or 1 value to data->data[0]. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_HW_BYTE_SWAP_INFO ioctl command in the dmaex driver: case GET_HW_BYTE_SWAP_INFO: DMAEX_DBG2("dmaex_ioctl: GET_HW_BYTE_SWAP_INFO\n"); #ifdef VBA_TYPE_MSK if (((vba_get_info(ctlr) & VBA_BYTE_SWAP_MSK) == VBA_NO_HW_BYTE_SWAP) || 1 (dmaex_sw_byte_swap)) dmaex_sw_byte_swap = 1; 2 #else if ((dmaex_univ_adapter) || (dmaex_sw_byte_swap)) dmaex_sw_byte_swap = 1; 3 #endif data->data[0] = (unsigned long)dmaex_sw_byte_swap; 4 break;
1
If the VBA_TYPE_MSK flag is defined, indicating that the vba_get_info interface is available in the software: Calls the vba_get_info interface and checks the return value to determine whether the system’s VMEbus adapter supports hardware byte swapping. The vba_get_info interface takes one argument, the device’s controller information structure. The return value contains flags, defined in vbareg.h, that indicate the following:
2
•
The adapter type (VBA_TYPE_VIPVIC, VBA_TYPE_UNIV, VBA_TYPE_DWP64, VBA_TYPE_DWPVC, or VBA_TYPE_TC)
•
Whether the adapter supports hardware byte swapping (VBA_HW_BYTE_SWAP or VBA_NO_HW_BYTE_SWAP)
•
Whether the platform is byte/word I/O capable (VBA_BW_CAPABLE or VBA_NOT_BW_CAPABLE)
If the VMEbus adapter does not support hardware byte swapping, or if the sysconfigtab parameter dmaex_sw_byte_swap selects software byte swapping, dmaex_sw_byte_swap is set to 1 to indicate that the application must call software byte swapping interfaces before transferring data and after receiving data.
VMEbus Device Driver Example 1–237
3
If the sysconfigtab parameter dmaex_univ_adapter indicates the system’s VMEbus adapter is a UNIVERSE II, or if the sysconfigtab parameter dmaex_sw_byte_swap selects software byte swapping, dmaex_sw_byte_swap is set to 1 to indicate that the application must call software byte swapping interfaces before transferring data and after receiving data.
4
Returns the value of dmaex_sw_byte_swap in data->data[0]. 0 is returned if the adapter supports hardware byte swapping and software byte swapping was not selected.
1.16.2.3 Implementing GET_VBA_BUS_TYPE_INFO The GET_VBA_BUS_TYPE_INFO ioctl command returns a value to indicate whether the VMEbus adapter is a UNIVERSE II (1) or not (0). The GET_VBA_BUS_TYPE_INFO command requires no input arguments in the data parameter but returns its 0 or 1 value to data->data[0]. The following code, located in a case statement within the dmaex_ioctl interface, shows the implementation of the GET_VBA_BUS_TYPE_INFO ioctl command in the dmaex driver: case GET_VBA_BUS_TYPE_INFO: DMAEX_DBG2("dmaex_ioctl: GET_VBA_BUS_TYPE_INFO\n"); data->data[0] = 0UL; 1 #ifdef VBA_TYPE_MSK if ((vba_get_info(ctlr) & VBA_TYPE_MSK) == VBA_TYPE_UNIV) { 2 dmaex_univ_adapter = 1; 3 data->data[0] = 1UL; } #else if (dmaex_univ_adapter) 4 data->data[0] = 1UL; #endif break;
1
Initializes the return value to indicate the VMEbus adapter is not a UNIVERSE II.
2
If the VBA_TYPE_MSK flag is defined, indicating that the vba_get_info interface is available in the software: Calls the vba_get_info interface and checks the return value to determine whether the system’s VMEbus adapter is a UNIVERSE II. The vba_get_info interface takes one argument, the device’s controller information structure. The return value contains flags, defined in vbareg.h, that indicate the following: •
The adapter type (VBA_TYPE_VIPVIC, VBA_TYPE_UNIV, VBA_TYPE_DWP64, VBA_TYPE_DWPVC, or VBA_TYPE_TC)
1–238 VMEbus Device Driver Example
•
Whether the adapter supports hardware byte swapping (VBA_HW_BYTE_SWAP or VBA_NO_HW_BYTE_SWAP)
•
Whether the platform is byte/word I/O capable (VBA_BW_CAPABLE or VBA_NOT_BW_CAPABLE)
3
If the vba_get_info return value indicates the VMEbus adapter is a UNIVERSE II, sets both the GET_VBA_BUS_TYPE_INFO return value and the sysconfigtab parameter dmaex_univ_adapter to indicate a UNIVERSE II.
4
[If the VBA_TYPE_MSK flag is undefined, indicating that the vba_get_info interface is not available:] If the sysconfigtab parameter dmaex_univ_adapter indicates the adapter is a UNIVERSE II, sets the return value to indicate a UNIVERSE II.
VMEbus Device Driver Example 1–239
Index A Autoconfiguration Support Section for dmaex device driver, 1–27
B B_READ constant to indicate a read operation in call to physio, 1–98, 1–125, 1–142 B_WRITE constant to indicate a write operation in call to physio, 1–100, 1–126, 1–143
C callback_register_configuration driver interface description of code example for dmaex driver, 1–25 callback_register_major_number driver interface description of code example for dmaex driver, 1–27 Close Device Section for dmaex device driver, 1–38 CLR_DMA_BLK_RD ioctl command description of code example for dmaex driver, 1–189, 1–212 CLR_DMA_BLK_WRT ioctl command description of code example for dmaex driver, 1–185, 1–209 CLR_INT_HANDLER ioctl command
description of code example for dmaex driver, 1–164 CLR_STRATEGY_INFO_BLK_DMA ioctl command description of code example for dmaex driver, 1–94 Configure Section description of example declarations for, 1–13 for dmaex device driver, 1–11
D Declarations Section description of code example for dmaex driver, 1–9 for dmaex device driver, 1–7 /dev/dmaex device driver ( See dmaex device driver ) device driver example of, 1–1 device register header file for the dmaex device driver, 1–3 dmaex device driver Autoconfiguration Support Section, 1–27 Configure Section, 1–11 convention used in example code, 1–2 Declarations Section, 1–7 dmaexreg.h header file, 1–3 Include Files Section, 1–5 introductory discussion, 1–1 Open and Close Device Section, 1–38 dmaex_close driver interface description of code example for, 1–40
Index–1
dmaex_configure driver interface description of code example for, 1–18 dmaex_ctlr_unattach driver interface description of code example for, 1–36 dmaex_iack_isr driver interface description of code example for, 1–228 dmaex_intr1 driver interface description of code example for, 1–151 dmaex_intr2 driver interface description of code example for, 1–151 dmaex_ioctl driver interface description of code example for, 1–72, 1–90, 1–116, 1–137, 1–161, 1–180, 1–204, 1–224, 1–236 dmaex_minphys driver interface description of code example for, 1–101, 1–127, 1–144 dmaex_mmap driver interface description of code example for, 1–79, 1–219 dmaex_open driver interface description of code example for, 1–39 dmaex_post_psignal driver interface description of code example for, 1–169 dmaex_probe driver interface description of code example for, 1–30 dmaex_rcv_int_srv driver interface description of code example for, 1–169 dmaex_read driver interface description of arguments, 1–97, 1–123, 1–141 description of code example for, 1–97, 1–123, 1–141
Index–2
dmaex_setup_blk_mode driver interface description of code example for, 1–191, 1–215 dmaex_strategy driver interface description of code example for, 1–103, 1–129, 1–146 dmaex_sw_byte_swap user-level interface description of code example for, 1–230 dmaex_test device driver exerciser operations, 1–2 dmaex_write driver interface description of arguments, 1–100, 1–126, 1–143 description of code example for, 1–100, 1–126, 1–143 dmaexreg.h header file description of code example for, 1–4 device register definitions for dmaex device driver, 1–3 DO_DMA_BLK_RD ioctl command description of code example for dmaex driver, 1–188, 1–212 DO_DMA_BLK_WRT ioctl command description of code example for dmaex driver, 1–183, 1–208
G GET_DMA_BLK_RD ioctl command description of code example for dmaex driver, 1–187, 1–211 GET_DMA_BLK_WRT ioctl command description of code example for dmaex driver, 1–183, 1–207 GET_HW_BYTE_SWAP_INFO ioctl command description of code example for dmaex driver, 1–237
GET_MMAP_MODE ioctl command description of code example for dmaex driver, 1–78 GET_STRATEGY_INFO_BLK_DMA ioctl command description of code example for dmaex driver, 1–93 GET_STRATEGY_XFER_MODE ioctl command description of code example for dmaex driver, 1–95, 1–122, 1–139 GET_SYS_MEM_INFO ioctl command description of code example for dmaex driver, 1–168 GET_VBA_BUS_TYPE_INFO ioctl command description of code example for dmaex driver, 1–238 GET_VME_INFO_FOR_MMAP_PIO ioctl command description of code example for dmaex driver, 1–75 GET_VME_INFO_FOR_STRATEGY_PIO ioctl command description of code example for dmaex driver, 1–119
H header files dmaexreg.h, 1–3
I Include Files Section description of code example for dmaex driver, 1–6 for dmaex device driver, 1–5
M MAP_SYS_MEM_TO_VME ioctl command description of code example for dmaex driver, 1–166 minor interface used by dmaex_read to obtain device minor number, 1–97, 1–123, 1–141 minphys kernel interface to bound data transfer size in call to physio, 1–98, 1–125, 1–142
O Open Device Section for dmaex device driver, 1–38
P physio kernel interface called by dmaex_read, 1–98, 1–124, 1–141
R register_configuration driver interface description of code example for dmaex driver, 1–22 register_major_number driver interface description of code example for dmaex driver, 1–24
S SET_INT_HANDLER ioctl command description of code example for dmaex driver, 1–163
Index–3
SET_MMAP_MODE ioctl command description of code example for dmaex driver, 1–77, 1–213 SET_STRATEGY_INFO_BLK_DMA ioctl command description of code example for dmaex driver, 1–92 SET_STRATEGY_XFER_MODE ioctl command description of code example for dmaex driver, 1–95, 1–121, 1–138 SETUP_DMA_BLK_RD ioctl command description of code example for dmaex driver, 1–186, 1–210 SETUP_DMA_BLK_WRT ioctl command description of code example for dmaex driver, 1–182, 1–206 SETUP_VME_FOR_MMAP_PIO ioctl command description of code example for dmaex driver, 1–74 SETUP_VME_FOR_STRATEGY_PIO ioctl command description of code example for dmaex driver, 1–118
U uio structure declared by dmaex_read driver interface, 1–97, 1–123, 1–141 UNMAP_SYS_MEM_TO_VME ioctl command description of code example for dmaex driver, 1–168 UNMAP_VME_FOR_MMAP_PIO ioctl command description of code example for dmaex driver, 1–76 UNMAP_VME_FOR_STRATEGY_PIO ioctl command
Index–4
description of code example for dmaex driver, 1–120 user code examples description of block DMA transfer for dmaex driver, 1–85 description of device DMA transfer for dmaex driver, 1–134 description of device input to wired memory with signaling for dmaex driver, 1–156 description of interrupt clearing for dmaex driver, 1–222 description of interrupt posting for dmaex driver, 1–221 description of kernel-space buffers DMA for dmaex driver, 1–198 description of programmed I/O transfer for dmaex driver, 1–111 description of software byte swapping for dmaex driver, 1–230 description of user-mode I/O to a memory-mapped VMEbus window for dmaex driver, 1–50, 1–65 description of user-space buffers DMA for dmaex driver, 1–174
V VME_CLR_IRQ ioctl command description of code example for dmaex driver, 1–227 VME_POST_IRQ ioctl command description of code example for dmaex driver, 1–226
W Write Device Section, 1–99, 1–126, 1–142
Index–5