This chapter provides you with an opportunity to study a PCI device driver called /dev/pnvram. You can use the /dev/pnvram device driver as the basis for writing your own working PCI bus device drivers. The /dev/pnvram device driver operates on a PCI bus and implements many of the device driver interfaces shown in Chapter 3. It also implements other sections that the PCI bus nonvolatile random access memory (NVRAM) expansion board needs.
The chapter begins with an overview of the tasks performed by the /dev/pnvram device driver. Following this overview are sections that describe each piece of the /dev/pnvram device driver. Table 6-1 lists the parts of the /dev/pnvram device driver and the sections of the chapter where each is described.
Part | Section |
The pnvram.h Header File | Section 6.2 |
Defining Data Structures | Section 6.3 |
Include Files Section | Section 6.4 |
Declarations Section | Section 6.5 |
Autoconfiguration Support Section | Section 6.6 |
Status Section | Section 6.7 |
Battery Status Section | Section 6.8 |
Read and Write Device Section | Section 6.9 |
Zero NVRAM Section | Section 6.10 |
Interrupt Section | Section 6.11 |
The source code uses the following convention:
#define PNVRAM_MAPPED 1 [1]
The /dev/pnvram device driver is a character device driver that provides read and write services to the /dev/presto device driver. The /dev/presto device driver is a disk driver that uses nonvolatile memory as a cache. It works as a layer between other drivers and the rest of the Digital UNIX kernel.
The /dev/presto device driver's interfaces appear as the entry points in the dsent switch table structure, instead of the interfaces of the other drivers (including the /dev/pnvram driver) it works with. Whenever /dev/presto needs to perform actual I/O operations (for example, when the cache needs filling or draining), it calls the layered driver's entry points (strategy, close, read, and write).
Figure 6-1 shows the relationship between the /dev/pnvram and /dev/presto device drivers. The figure shows the following flow of data between the different layers:
The pnvram.h header file is the header file for the /dev/pnvram device driver. It defines the following items:
The pnvram.h header file contains the following code:
#define PNV_ID 0x00L [1] #define PNV_CMD 0x04L #define PNV_STAT 0x06L #define PNV_ERROR 0x40L #define PNV_FADDR 0x44L #define PNV_MEM_CFG 0x48L #define PNV_EDC_CTRL 0x4CL #define PNV_EDC_SYND 0x50L #define PNV_BAT_CTRL 0x54L #define PNV_BAT_DIAG_RST 0x58L #define PNV_EEPROM_IF 0x5CL #define PNV_SLAVE_ADDR 0x60L #define PNV_MASTER_ADDR 0x64L #define PNV_BYTE_COUNT 0x68L #define PNV_DMA_CMD 0x6cL #define PNV_INTR_CTRL 0x70L
#define PNVR_ERR_SUM 0x01 [2] #define PNVR_CMD_PAR 0x0010 #define PNVR_WR_PAR 0x0020 #define PNVR_CRD_ERR 0x0040 #define PNVR_UCRD_ERR 0x0080 #define PNVR_M_CMD_ERR 0x1000 #define PNVR_M_WR_PAR 0x2000 #define PNVR_M_CRD_ERR 0x4000 #define PNVR_M_UCRD_ERR 0x8000
#define PNV_BANK_MASK 0x0003 [3] #define PNV_SIZE_MASK 0x000c #define PNV_MOD_REV_MASK 0x0070 #define PNV_CHIP_REV_MASK 0x0F00
#define PNV_SIZE_SHIFT 1 [4] #define PNV_512K 0x80000
#define PNVR_DIS_CRD_LOG 0x1000 [5] #define PNVR_DIS_EDC_SERR 0x8000
#define PNV_BCHRG 0x001 [6] #define PNV_BDISC 0x002 #define PNV_BFAIL 0x004 #define PNV_BAT_OK 0x008 #define PNV_SPEED_MASK 0x300
#define PNV_SCL 0x01 [7] #define PNV_XMT_SDA 0x20 #define PNV_RCV_SDA 0x40
#define PNV_MAX_XFER_SIZE 65532 [8]
#define PNV_NV_READ 0x01 [9] #define PNV_NV_WRITE 0x02
#define PNV_ENAB_DMA 0x01 [10] #define PNV_ENAB_CRD 0x02 #define PNV_DISAB_ABRT 0x04 #define PNV_DMA_INTR 0x100 #define PNV_CRD_INTR 0x200 #define PNV_ABRT_INTR 0x400 #define PNV_INTR_OCCURRED (PNV_DMA_INTR | PNV_CRD_INTR | PNV_ABRT_INTR)
#define PNV_CACHE_OFFSET 0x400 [11] #define PNV_DIAG_RESERVED 0x400 #define PNV_DIAG_RESULT 0x08L #define PNV_DIAG_SIZE 0x04 #define BOARD_FAILED 0x00000008
#define PNVRAM_MAPPED 1 [12] #define PNVRAM_NOTMAPPED 0 #define PNVRAM_CACHED 1 #define PNVRAM_NOTCACHED 0
struct pnvram_softc { [13] io_handle_t csr_base; vm_offset_t cache_phys_start; vm_offset_t cache_base; vm_offset_t cache_kseg_start; u_int cache_size; u_int cache_offset; u_int diag_status; volatile u_int dma_in_progress; dma_handle_t dma_p; struct controller *ctlr; ihandler_id_t *hid; u_int chip_rev; };
The following list describes each device register offset definition:
This register in the device's PCI memory space contains the device's vendor ID in its low 16 bits and the device's device ID in its upper 16 bits. Device power-on self-test code laces the appropriate values into this register. As described in Section 4.1.1, the PCI bus configuration code reads this register to determine which driver is associated with the device. You specify the same vendor and device IDs in the PCI_Option sysconfigtab entry (see Section 4.2.2).
The /dev/pnvram device driver does not access this register.
This 16-bit register in the device's PCI memory space contains a PCI bus device command value. These values are described in Section 4.1.2.
As discussed in Section 6.11, when a DMA operation is completed with an error status, the pnvram_intr interface writes the contents of the command member of the pci_cfg_hdr data structure to this register to reset it to its initial bootstrap value.
This 16-bit register in the device's PCI memory space contains a PCI bus device status value. These values are described in Section 4.1.3.
As discussed in Section 6.11, when a DMA operation is completed with an error status, the pnvram_intr interface reads and prints the contents of this register. It then clears the error bits in the register by writing its value back to it.
This NVRAM expansion board register in the device-dependent region of PCI memory space provides an error summary bit that indicates whether an error occurred during an NVRAM transaction and additional bits that identify the error. Errors are enabled when appropriate bits in the PNV_CMD and PNV_EDC_CTRL registers are set.
This header file defines symbols for each error bit in the PNV_ERROR register.
As shown in Section 6.6.1, the pnvram_probe interface clears this register prior to enabling the NVRAM for operation. The pnvram_intr interface, described in Section 6.11, reads and prints the value of this register when a DMA operation is completed with an error status. It then clears the appropriate error bits to reset this register and unlatch the PNV_FADDR register.
This read-only NVRAM expansion board register in the device-dependent region of PCI memory space records the PCI address associated with a failed NVRAM transaction. The PNV_FADDR register is latched when the appropriate error bit is set in the PNV_ERROR or PNV_EDC_SYND register. It is released when the error bits are cleared.
As discussed in Section 6.11, when a DMA operation is completed with an error status, the pnvram_intr interface reads and prints the contents of this register. It then clears the appropriate error bits to unlatch it.
This read-only NVRAM expansion board register in the device-dependent region of PCI memory space provides the following information:
The pnvram_probe interface, described in Section 6.6.1, reads this register and uses its contents to calculate the NVRAM size.
This NVRAM expansion board register in the device-dependent region of PCI memory space controls the function of the PINK chip's error detection and correction (EDC) logic. The /dev/pnvram device driver uses the power-on default values for this logic and does not access the PNV_EDC_CTRL register.
This read-only NVRAM expansion board register in the device-dependent region of PCI memory space contains information (EDC syndrome bits) that helps identify the source of an EDC error when a correctable or uncorrectable EDC error is detected in memory data returned from the NVRAM. The register's latches are released when the error bits in the PNV_ERROR register are cleared.
As discussed in Section 6.11, when a DMA operation is completed with an error status, the pnvram_intr interface reads and prints the contents of this register. It then clears the appropriate error bits to unlatch it.
This NVRAM expansion board register in the device-dependent region of PCI memory space contains the battery control and status information. It has diagnostic mode bits that manufacturers use to test battery-related circuitry. It also stores the PCI bus speed for battery-related counter operation.
As described in Section 6.8.1, the pnvram_battery_status interface reads this register to ensure that the battery is charged (PNV_BCHRG set) and that its voltage exceeds 2.5 volts (PNV_BAT_OKset).
The pnvram_battery_enable interface, discussed in Section 6.8.2, also reads this register to determine if the battery will be disconnected upon power failure (PNV_BDISCset). If this bit is set, the pnvram_battery_enable interface resets it to zero by writing to the register its previous value. This enables the battery to be connected to back up the NVRAM upon power failure.
The pnvram_battery_disable interface, described in Section 6.8.3, also reads this register to determine if the battery will be disconnected upon power failure (PNV_BDISCset). If this bit is not set, the pnvram_battery_disable interface issues a series of writes to the register that write the sequence 1, 1, 0, 0, and 1 to the PNV_BDISC bit. This enables the battery to be disconnected upon power failure.
This location in the device-dependent region of PCI memory space is written to by module test systems to cause a diagnostic reset to occur. There is no actual register at this location. Reads are not explicitly supported and may return random data.
The /dev/pnvram device driver does not access this location.
This NVRAM expansion board register in the device-dependent region of PCI memory space controls access to the 2Kx8 serial EEPROM located on the NVRAM module.
The /dev/pnvram device driver does not access this register.
This NVRAM expansion board register in the device-dependent region of PCI memory space contains the starting host memory address to be written to or read from by the board's DMA engine. The pnvram_write interface, as described in Section 6.9.2, loads this register with the source address in host memory to set up a DMA transfer. The pnvram_read interface, as described in Section 6.9.1, loads this register with the destination address in host memory to set up a DMA transfer. Once the interfaces start the DMA process (by loading the PNV_DMA_CMD register), the DMA engine proceeds to move the specified data, incrementing the PNV_SLAVE_ADDR register and decrementing the PNV_BYTE_COUNT register until it reaches zero. At that point, the NVRAM expansion board signals an interrupt.
This NVRAM expansion board register in the device-dependent region of PCI memory space contains the starting NVRAM address to be written to or read from by the board's DMA engine. The pnvram_write interface, as described in Section 6.9.2, loads this register with the destination address in NVRAM to set up a DMA transfer. The pnvram_read interface, as described in Section 6.9.1, loads this register with the source address in NVRAM to set up a DMA transfer. Once the interfaces start the DMA process (by loading the PNV_DMA_CMD register), the DMA engine proceeds to move the specified data, incrementing the PNV_MASTER_ADDR register and decrementing the PNV_BYTE_COUNT register until it reaches zero. At that point, the NVRAM expansion board signals an interrupt.
This NVRAM expansion board register in the device-dependent region of PCI memory space indicates the number of quadwords of data to be transferred between host memory and NVRAM by the board's DMA engine. As discussed in Section 6.9.1 and Section 6.9.2, the pnvram_read and pnvram_write interfaces both write the length of the DMA transfer to the PNV_BYTE_COUNT register prior to initiating the transfer. Once the interfaces start the DMA process (by loading the PNV_DMA_CMD register), the DMA engine proceeds to move the specified data, decrementing the PNV_BYTE_COUNT register until it reaches zero. At that point, the NVRAM expansion board signals an interrupt.
If DMA interrupts are not enabled in /dev/pnvram (that is, the PNVRAM_DMA_INTR symbol is not defined), both the pnvram_read and pnvram_write interfaces determine that a DMA operation has completed by spinwaiting on the PNV_BYTE_COUNT register (register-polled DMA versus interrupt-driven DMA). When a read of the register returns zero, the DMA operation is complete and the interfaces exit from their spin waits.
This NVRAM expansion board register in the device-dependent region of PCI memory space specifies the direction in which the board's DMA engine is to transfer data between host memory and NVRAM.
As shown in Section 6.6.1, pnvram_probe interface clears this register prior to enabling the NVRAM for operation. The pnvram_read interface, as described in Section 6.9.1, writes the constant PNV_NV_READ to this register to initiate a DMA read transaction from NVRAM to host memory. The pnvram_write interface, as described in Section 6.9.2, writes the constant PNV_NV_WRITE to this register to initiate a DMA write transaction from host memory to NVRAM. Once the interfaces start the DMA process by loading the PNV_DMA_CMD register, the DMA operation proceeds, decrementing the PNV_BYTE_COUNT register until it reaches zero. At that point, the NVRAM expansion board signals an interrupt.
As discussed in Section 6.11, when a DMA operation is completed with an error status, the pnvram_intr interface reads and prints the contents of this register.
This NVRAM expansion board register in the device-dependent region of PCI memory space enables the generation of PCI interrupts by the NVRAM module and indicates the cause of a PCI interrupt generated by the module.
As shown in Section 6.6.1, the pnvram_probe interface clears the interrupt status bits in this register to enable the NVRAM for operation. It then writes the logical OR of the values PNV_ENAB_DMA and PNV_ENAB_CRD to the register to enable interrupts upon either the completion of a DMA operation or the detection of a correctable read data (CRD) error by the NVRAM module's EDC logic.
As discussed in Section 6.11, the pnvram_intr interface reads the PNV_INTR_CTRL register to determine if the cause of the interrupt was the completion of a DMA transaction (PNV_DMA_INTR set), the detection of a CRD error (PNV_CRD_INTR set), or the abortion of a DMA operation (PNV_ABRT_INTR set). It then writes to the register, clearing only the bit corresponding to the interrupt that actually occurred.
Definition | Meaning |
PNVR_ERR_SUM | Error summary bit. When set, indicates that other error bits in the register are also set and that the contents of the PNV_FADDR and PNV_EDC_SYND registers are frozen. To clear the PNVR_ERR_SUM bit (and unlatch these registers), you must first clear the other error bits in the register. |
PNVR_CMD_PAR | Command or address parity error bit. When set, indicates bad parity during a PCI bus address phase. The PNV_FADDR register contains the address at which the failure occurred. You clear this bit by writing a 1 to it. |
PNVR_WR_PAR | Write data parity error bit. When set, indicates bad parity during a PCI write data phase. The PNV_FADDR register contains the address at which the failure occurred. You clear this bit by writing a 1 to it. |
PNVR_CRD_ERR | Correctable EDC error bit. When set, indicates an EDC correctable error in fetched NVRAM memory data. The PNV_FADDR register contains the address at which the failure occurred. You clear this bit by writing a 1 to it. |
PNVR_UCRD_ERR | Uncorrectable EDC error bit. When set, indicates an EDC uncorrectable error in fetched NVRAM memory data. The PNV_FADDR register contains the address at which the failure occurred. You clear this bit by writing a 1 to it. |
PNVR_M_CMD_ERR | Missed command or address parity error bit. When set, indicates that bad parity occurred during a PCI bus address phase and some other error bit was already set in this register. For this error, the PNV_FADDR register does not contain the address at which the failure occurred. You clear this bit by writing a 1 to it. |
PNVR_M_WR_PAR | Missed write data parity error bit. When set, indicates that bad parity occurred during a PCI write data phase and some other error bit was already set in this register. For this error, the PNV_FADDR register does not contain the address at which the failure occurred. You clear this bit by writing a 1 to it. |
PNVR_M_CRD_ERR | Missed correctable EDC error bit. When set, indicates that an EDC correctable error occurred in fetched memory data and that some other error bit was already set in this register. For this error, the PNV_FADDR register does not contain the address at which the failure occurred. You clear this bit by writing a 1 to it. |
PNVR_M_UCRD_ERR | Missed uncorrectable EDC error bit. When set, indicates that an EDC uncorrectable error occurred in fetched memory data and that some other error bit was already set in this register. For this error, the PNV_FADDR register does not contain the address at which the failure occurred. You clear this bit by writing a 1 to it. |
The pnvram_intr interface, discussed in Section 6.11, sets the PNVR_CRD_ERR bit in the PNV_ERROR register to indicate that a CRD error interrupt has occurred during a DMA transfer. This causes the PNVR_ERR_SUM also to be set. [Return to example]
Definition | Meaning |
PNV_BANK_MASK | Number of banks of SRAMs present (1, 2, or 4) |
PNV_SIZE_MASK | SRAM size (256K bit, 1M bit, or 4M bit) |
PNV_MOD_REV_MASK | Module revision number |
PNV_CHIP_REV_MASK | PCI Integrated NVRAM Controller (PINK) chip revision number |
The pnvram_probe interface, described in Section 6.6.1, reads the PNV_MEM_CFG register and uses the constants PNV_BANK_MASK and PNV_SIZE_MASK in calculating the NVRAM size. It ultimately prints the value of the PNV_MOD_REV_MASK bit mask in the bootstrap informational message. [Return to example]
Definition | Meaning |
PNV_SIZE_SHIFT | Factor applied to the PNV_SIZE_MASK bit of the PNV_MEM_CFG register to produce the size of an SRAM module |
PNV_512K | Size of a bank of SRAM modules |
Definition | Meaning |
PNVR_DIS_CRD_LOG | When set, disables the recording (in the PNV_ERROR and PNV_FADDR registers) of single bit errors detected in the data read from SRAM modules. |
PNVR_DIS_EDC_SERR | When set, suppresses the reporting of EDC uncorrectable errors to the PCI system error line. This does not affect the logging of uncorrectable errors to the PNV_ERROR and PNV_FADDR registers. |
The /dev/pnvram device driver uses the default power-on values of the PNV_EDC_CTRL register's bit definitions (that is, single bit errors and EDC uncorrectable errors generate interrupts). It does not directly access this register. [Return to example]
Definition | Meaning |
PNV_BCHRG | If this read-only bit is set, the battery is charged; if it is clear, the battery is charging. |
PNV_BDISC | When read, indicates, if set, that the battery will be disconnected upon power failure or, if clear, that the battery will be connected upon power failure to back up the NVRAM. The pnvram_battery_disable interface writes the special sequence 1, 1, 0, 0, and 1 to this bit to enable the battery to be disconnected upon power failure. |
PNV_BFAIL | When set, this read-only bit indicates that the battery has been charged successfully to at least 2.5 volts. When clear, it indicates that the battery failed to charge before the battery charge timer expired and needs replacement. |
PNV_BAT_OK | When set, this read-only bit indicates that the battery's real-time voltage is above 2.5 volts; when clear, indicates that the voltage is below 2.5 volts. |
PNV_SPEED_MASK | These two bits are set by system software to indicate one of three PCI bus speeds (33, 16.7, or 8.33 MHz) and are read to adjust the battery charge counter so that its timeout value is the same for each of the three speeds. |
As discussed in Section 6.8.1, Section 6.8.2, and Section 6.8.3, the pnvram_battery_status, pnvram_battery_enable, and pnvram_battery_disable interfaces all access the bits of the PNV_BAT_CTRL register when performing their various tasks. [Return to example]
Definition | Meaning |
PNV_SCL | When set, indicates that the EEPROM serial clock input is forced to a high logic level; when clear, indicates that the clock input is forced to a low logic level. |
PNV_XMT_SDA | Asserts the EEPROM serial data line to high or low logic levels, enabling software to transfer command, address, and write data to the EEPROM, with regard to the setting of the PNV_SCL bit. |
PNV_RCV_SDA | Returns the status of the EEPROM serial data line, enabling software to receive serial read data and EEPROM responses. |
The /dev/pnvram device driver does not access the PNV_EEPROM_IF register or use its bit definitions. [Return to example]
Definition | Meaning |
PNV_NV_READ | Starts a DMA transfer from NVRAM memory to host memory. |
PNV_NV_WRITE | Starts a DMA transfer from host memory to NVRAM memory. |
Definition | Meaning |
PNV_ENAB_DMA | When set, causes a PCI interrupt to be generated during a DMA transaction when the PNV_BYTE_COUNT register reaches zero; when clear, disables the DMA completion interrupt. The pnvram_probe interface sets this bit to enable DMA completion interrupts. |
PNV_ENAB_CRD | When set, causes a PCI interrupt to be generated upon detection of a correctable read data (CRD) error; when clear, disables the CRD detection interrupt. The pnvram_probe interface sets this bit to enable CRD detection interrupts. |
PNV_DISAB_ABRT | When clear, causes a DMA transaction to be terminated with an abort interrupt if a hard error occurs; when set, allows a DMA transaction to continue, despite the occurrence of a hard error. The /dev/pnvram device driver leaves this bit clear, thus enabling DMA abort interrupts. |
PNV_DMA_INTR | If set when read, indicates that a DMA transaction has completed. Writing a 1 to this bit clears the PCI interrupt if it was caused by DMA completion. The pnvram_intr interface reads the PNV_INTR_CTRL register to determine if the cause of an interrupt was the completion of a DMA transaction. If so, it clears the interrupt by setting the PNV_DMA_INTR bit and, if DMA sleeps are disabled (that is, the PNVRAM_DMA_SLEEP symbol is undefined), clears the dma_in_progress flag in the pnvram_softc structure. |
PNV_CRD_INTR | If set when read, indicates that a PCI interrupt was generated because of a CRD error. Writing a 1 to this bit clears the PCI interrupt if it was caused by CRD error detection. The pnvram_intr interface reads the PNV_INTR_CTRL register to determine if the cause of an interrupt was the detection of a CRD error. If so, it clears the interrupt by setting the PNV_CRD_INTR bit. |
PNV_ABRT_INTR | If set when read, indicates that a DMA transaction was truncated because of a hard error. Writing a 1 to this bit clears the PCI interrupt if it was caused by an aborted DMA transaction. The pnvram_intr interface reads the PNV_INTR_CTRL register to determine if the cause of an interrupt was the detection of a CRD error. If so, it clears the interrupt by setting the PNV_ABRT_INTR bit. |
Definition | Meaning |
PNV_CACHE_OFFSET | Defines the offset to the first NVRAM location from the start of the PCI slot address. The pnvram_probe interface initializes the cache_offset member of the pnvram_softc data structure with this value. |
PNV_DIAG_RESERVED | Indicates the amount of NVRAM address space (1024 bytes) that must be reserved for diagnostics and status information. The /dev/pnvram device driver does not use this constant. |
PNV_DIAG_RESULT | Provides an offset from the beginning of NVRAM cache to the lower byte address in the reserved diagnostic region in which the PCI NVRAM module writes its console diagnostic results. The pnvram_probe interface locates the console diagnostic results by subtracting PNV_DIAG_RESULT from the kernel segment address of the NVRAM cache and then reads them into the diag_status member of the pnvram_softc data structure. |
PNV_DIAG_SIZE | Defines the size of the PCI NVRAM console diagnostic results as a longword (4 bytes). The pnvram_probe interface uses this constant when reading the console diagnostic results. |
BOARD_FAILED | Indicates the bit in the console diagnostics longword that is set when the PCI NVRAM board fails its diagnostics. The pnvram_probe interface compares the value it reads into the diag_status member of the pnvram_softc data structure with this bit mask to determine if the diagnostics failed. |
Note
These constants are not relevant for the PCI bus NVRAM expansion board, but are used to emulate testing of the /dev/presto device.
The following table describes each constant:
Definition | Meaning |
PNVRAM_MAPPED |
Indicates that the PNVRAM buffer is mapped. The /dev/pnvram device driver does not currently use this constant. |
PNVRAM_NOTMAPPED |
Indicates that the PNVRAM buffer is unmapped. The pnvram_attach interface uses this constant to indicate to the /dev/presto device driver that the buffer is not mapped. |
PNVRAM_CACHED | Indicates to the /dev/presto device driver the use of kernel segment (kseg) space. The pnvram_attach interface passes this constant to the /dev/presto driver. |
PNVRAM_NOTCACHED | Indicates to the /dev/presto device driver the use of a cached space. The /dev/pnvram device driver does not currently use this constant. |
The following list describes the members contained in the pnvram_softc data structure:
Stores an I/O handle to the memory space base address of the NVRAM expansion board's CSR space. As shown in Section 6.5, the /dev/pnvram driver uses this member to construct the READ_BUS_D32 and WRITE_BUS_D32 interfaces it uses to read and write device registers. The pnvram_probe interface, discussed in Section 6.6.1, sets this member to the contents of the bar1 member of the pci_cfg_hdr data structure.
Note that this member of the pnvram_softc structure contains an I/O handle. An I/O handle is a data entity that is of type io_handle_t. A device driver uses an I/O handle to reference bus address space (either I/O space, memory space, or configuration space). On some buses, the bus configuration code passes the I/O handle to the device driver's xxprobe interface during device autoconfiguration. On the PCI bus, the bus configuration code instead passes a pointer to the pci_cfg_hdr data structure. The xxprobe interface can obtain the needed I/O handles from this structure.
One purpose of the I/O handle is to hide CPU-specific architecture idiosyncracies that describe how to access a device's control status registers (CSRs) and how to perform I/O copy operations.
Stores the base system physical address of the NVRAM cache that the /dev/presto driver uses. As shown in Section 6.6.1, the pnvram_probe interface sets this address to the sum of the cache_base and cache_offset members of the pnvram_softc data structure.
Stores the base system physical address of the NVRAM buffer in PCI bus memory space. As shown in Section 6.6.1, the pnvram_probe interface sets this member to the system physical address of the I/O handle that the PCI bus configuration code placed in the bar0 member of the pci_cfg_hdr data structure.
Stores the starting kseg address of the NVRAM cache that the /dev/presto driver uses. As shown in Section 6.6.1, the pnvram_probe interface sets this address to the kseg equivalent of the system physical address stored in the cache_phys_start member of the pnvram_softc data structure. It later uses this kseg address to locate the longword in the PNVRAM reserved diagnostic area where the console diagnostic results are written. Additionally, pnvram_attach, discussed in Section 6.6.2, passes this kseg address to the /dev/presto driver's presto_init interface.
Stores the size of the NVRAM cache. As shown in Section 6.6.1, the pnvram_probe interface calculates and initializes this member, ultimately printing it in the bootstrap informational message. The pnvram_attach interface, discussed in Section 6.6.2, passes this size to the /dev/presto driver's presto_init interface.
Stores the offset to the first NVRAM location from the start of the PCI NVRAM base address. As shown in Section 6.6.1, the pnvram_probe interface initializes this member to the value of the PNV_CACHE_OFFSET constant.
Stores the longword bit mask that indicates whether the PCI bus NVRAM expansion board passed its software diagnostic tests. As shown in Section 6.6.1, the pnvram_probe interface reads the longword bit mask from the NVRAM cache's reserved diagnostic region into this member. The pnvram_status interface, discussed in Section 6.7, also reads this member to determine the status value it returns to its caller.
Indicates that a DMA operation is in progress. If DMA sleeps are disabled (that is, the PNVRAM_DMA_SLEEP symbol is undefined), the pnvram_read and pnvram_write interfaces set this flag to 1 before starting a DMA operation. The pnvram_intr interface reads the PNV_INTR_CTRL register to determine if the cause of an interrupt was the completion of a DMA transaction. If so, it clears the interrupt by setting the PNV_DMA_INTR bit and clears the dma_in_progress bit in the pnvram_softc structure.
Specifies a handle to 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 pointer. Device driver writers can view the DMA handle as the tag to the allocated system resources needed to perform a direct memory access (DMA) operation.
As shown in Section 6.6.1, the pnvram_probe interface passes this member to the dma_map_alloc interface, which returns a DMA handle in it if contiguous DMA resources are successfully allocated. (If it later fails to register and enable an interrupt handle, pnvram_probe passes this member to the dma_map_dealloc interface to return the DMA resources to the system.)
As shown in Section 6.9.1 and Section 6.9.2, both the pnvram_read and pnvram_write interfaces provide the DMA handle in the dma_p member as input to the dma_map_load and dma_get_curr_sgentry interfaces. The dma_map_load interface loads the previously allocated DMA resources to prepare for the transfer, and the dma_get_curr_sgentry interface obtains the single sg_entry structure pointer that describes the contiguous mapping resources that were requested. The pnvram_read interface uses the returned information to initialize the DMA transfer's destination address in system memory. The pnvram_write interface uses the returned information to initialize the DMA transfer's source address in system memory.
Declares a pointer to the controller structure associated with this PCI bus NVRAM expansion board. Several members of the controller structure pointer are implicit inputs to the various driver interfaces.
Declares a pointer to an ihandler_id_t structure. As shown in Section 6.6.1, the pnvram_probe interface stores the key returned by its call to the handler_add interface in this member. The pnvram_probe interface calls the handler_add interface to register the driver's interrupt service interface and its associated ihandler_t data structure (defined in the pnvram_probe interface). It then supplies the hid member to the handler_enable interface to enable the interrupt service interface.
Declares a field to contain the PCI Integrated NVRAM Controller (PINK) chip revision number. As shown in Section 6.6.1, the pnvram_probe interface stores the revision number in this field.
You can allocate the softc and controller structure arrays for the driver by using static or dynamic allocation techniques. Static allocation is usually used when the maximum number of structures is small and the user cannot change that number. Dynamic allocation is usually used when the maximum number of structures is large or the user can change that number. The NVRAM driver defines data structures as follows, where the NPNVRAM constant is defined by the driver to allocate an array of six structures:
#define NPNVRAM 6 [1]
#include <io/dec/pci/pnvram.h> [2]
struct pnvram_softc *pnvram_softc[NPNVRAM]; [3] struct controller *pnvram_info[NPNVRAM]; [4]
The following code shows the Include Files Section for the /dev/pnvram device driver. Writing Device Drivers: Reference provides reference page descriptions of the header files most commonly used by Digital UNIX device drivers.
#include <sys/malloc.h> [1] #include <sys/presto.h> [2] #include <io/common/devdriver.h> [3] #include <io/dec/pci/pci.h> [4] #include <io/dec/pci/pnvram.h> [5]
The following code shows the Declarations Section for the /dev/pnvram device driver:
extern struct pnvram_softc *pnvram_softc[]; [1] extern struct controller *pnvram_info[]; extern int presto_init(); extern struct presto_interface presto_interface[]; extern struct nvram_battery_info nvram_batteries[]; extern void bzero(); extern int hz;
#define PNV_READIO(a) \ [2] READ_BUS_D32((io_handle_t)(sc->csr_base | a))
#define PNV_WRITEIO(a,d) \ [3] WRITE_BUS_D32((io_handle_t)(sc->csr_base | a),(long)d)
#define PNV_READ_CNFG(a) READ_BUS_D16((io_handle_t) \ [4] (((struct pci_config_hdr *)(sc->ctlr->private[0]))->config_base | a))
#define PNV_WRITE_CNFG(a,d) WRITE_BUS_D16((io_handle_t) \ [5] (((struct pci_config_hdr *)(sc->ctlr->private[0]))->config_base | a), (long)d)
int pnvram_probe(), pnvram_attach(); [6] int pnvram_battery_enable(), pnvram_battery_disable(); int pnvram_status(); int pnvram_battery_status(); void pnvram_zero(); void pnvram_write(); void pnvram_read(); u_long pnvram_ssn(); int pnvram_intr();
/* #define PNVRAM_DMA_INTR 1 */ #define PNVRAM_DMA_SLEEP 1 [7]
#ifdef PNVRAM_DMA_INTR static int dma_delay = 10000000; [8] #else static int poll_delay = 1000000; #endif /* DMA_INTR */
caddr_t pnvramstd[] = { 0 }; [9]
struct driver pnvramdriver = { [10] pnvram_probe, 0, pnvram_attach, 0, 0, 0, 0, 0, "pnvram", pnvram_info};
/* #define DEP_DEBUG */ #ifdef DEP_DEBUG [11] #define DEP(x) printf("**** PNVRAM **** "); printf x #else #define DEP(x) #endif
The call to the READ_BUS_D32 macro results in a call to the read_io_port interface, a generic interface that maps to a bus- and machine-specific interface that actually performs the task of reading the longword from a device register. Use of this interface to read data from a device register makes the device driver more portable across different buses, different CPU architectures, and different CPU types within the same architecture.
The read_io_port interface takes three arguments:
For this first argument, the /dev/pnvram driver performs a bitwise inclusive OR operation on the value stored in the csr_base member of the pointer to the pnvram_softc structure and a constant representing one of the device register offsets (for example, PNV_MEM_CFG).
For this third argument, the READ_BUS_D32 macro specifies the value zero (0).
The write_io_port interface takes four arguments:
For this first argument, the /dev/pnvram driver performs a bitwise inclusive OR operation on the value stored in the csr_base member of the pointer to the pnvram_softc structure and an address representing one of the device register offsets (for example, PNV_SLAVE_ADDR).
For this third argument, the /dev/pnvram driver specifies the value zero (0).
For this fourth argument, the /dev/pnvram driver specifies a variable to be passed by the different device driver interfaces.
The call to the READ_BUS_D16 macro results in a call to the read_io_port interface, a generic interface that maps to a bus- and machine-specific interface that actually performs the task of reading the word from a device register. Use of this interface to read data from a device register makes the device driver more portable across different buses, different CPU architectures, and different CPU types within the same architecture.
The read_io_port interface takes three arguments:
For this first argument, the /dev/pnvram driver performs a bitwise inclusive OR operation on the value stored in the config_base member of the pci_config_hdr data structure and a constant representing one of the device register offsets (either PNV_STAT or PNV_CMD). (The pnvram_probe interface, discussed in Section 6.6.1, previously placed the address of the pci_config_hdr data structure in the private member of the controller data structure.)
For this third argument, the READ_BUS_D16 macro specifies the value zero (0).
The write_io_port interface takes four arguments:
For this first argument, the /dev/pnvram driver performs a bitwise inclusive OR operation on the value stored in the config_base member of the pci_config_hdr data structure and a constant representing one of the device register offsets (either PNV_STAT or PNV_CMD). (The pnvram_probe interface, discussed in Section 6.6.1, previously placed the address of the pci_config_hdr data structure in the private member of the controller data structure.)
For this third argument, the /dev/pnvram driver specifies the value zero (0).
For this fourth argument, the /dev/pnvram driver specifies a variable to be passed by the different device driver interfaces.
The following list describes those members initialized to a nonzero value:
Table 6-2 lists the three interfaces implemented as part of the Autoconfiguration Support Section for the /dev/pnvram device driver along with the sections in the book where each is described.
Interface | Section |
Implementing the pnvram_probe Interface | Section 6.6.1 |
Implementing the pnvram_attach Interface | Section 6.6.2 |
Implementing the pnvram_ssn Interface | Section 6.6.3 |
Single binary configuration support | See Writing Device Drivers: Tutorial |
The pnvram_probe interface is called by the PCI bus code to perform the following tasks:
The following code implements pnvram_probe:
int pnvram_probe(struct pci_config_hdr *pci_cfg_hdr, struct controller *ctlr) [1] { register struct pnvram_softc *sc; [2] unsigned int mem_cfg; [3] int unit = ctlr->ctlr_num; [4] #ifdef PNVRAM_DMA_INTR struct handler_intr_info pnvram_intr_info; [5] ihandler_t pnvram_ihandle; [6] #endif volatile unsigned int synch_data; [7]
if (!(pci_cfg_hdr->command & CMD_MEM_SPACE) || \ [8] !(pci_cfg_hdr->command & CMD_BUS_MASTER) ) { printf("pnvram%d at pci%d not configured by PCI POST code.\n", unit, ctlr->bus_num); return(0); }
MALLOC(sc, struct pnvram_softc*, sizeof(struct pnvram_softc), \ [9] M_DEVBUF, M_NOWAIT); if (sc == (struct pnvram_softc *)(NULL)) { return(0); } else { bzero((char *)sc, sizeof(struct pnvram_softc)); pnvram_softc[unit] = sc; }
if (dma_map_alloc(PNV_MAX_XFER_SIZE, ctlr, &sc->dma_p, \ DMA_CONTIG) == 0) { [10] FREE(sc, M_DEVBUF); return(0); }
sc->ctlr = ctlr; [11]
ctlr->private[0] = pci_cfg_hdr; [12]
sc->csr_base = pci_cfg_hdr->bar1; [13] DEP(("pnvram%d pci_cfg_hdr->bar1:0x%lx\n", unit,pci_cfg_hdr->bar1));
sc->cache_offset = PNV_CACHE_OFFSET; [14]
mem_cfg = PNV_READIO(PNV_MEM_CFG); [15] DEP(("pnvram%d mem_cfg: 0x%x\n", unit,mem_cfg));
sc->cache_size = ((PNV_512K << (mem_cfg & PNV_BANK_MASK)) [16] << ((mem_cfg & PNV_SIZE_MASK) >> PNV_SIZE_SHIFT)); DEP(("pnvram%d sc->cache_size: 0x%x\n", unit,sc->cache_size));
sc->chip_rev = PNV_READIO(PNV_MEM_CFG) & PNV_CHIP_REV_MASK; [17] DEP(("pnvram%d chip_rev: 0x%x\n", unit, sc->chip_rev));
sc->cache_base = iohandle_to_phys(pci_cfg_hdr->bar0, [18] HANDLE_DENSE_SPACE); DEP(("pnvram%d cache_base:0x%lx\n", unit,sc->cache_base));
sc->cache_phys_start = sc->cache_base + sc->cache_offset; [19] DEP(("pnvram%d cache_phys_start:0x%lx\n", unit,sc->cache_phys_start));
sc->cache_kseg_start = PHYS_TO_KSEG(sc->cache_phys_start); [20] DEP(("pnvram%d cache_kseg_start:0x%lx\n", unit, sc->cache_kseg_start));
sc->cache_size = sc->cache_size - PNV_CACHE_OFFSET; [21]
pnvram_read((sc->cache_kseg_start - PNV_DIAG_RESULT), &sc->diag_status, PNV_DIAG_SIZE); [22] DEP(("pnvram%d Diag Register: 0x%x\n", unit,sc->diag_status));
if (sc->diag_status & BOARD_FAILED) { [23] printf("pnvram%d diag reg: 0x%x\n", unit, sc->diag_status); sc->diag_status = 0; } else { DEP(("pnvram%d diag reg 0x%x\n", unit, sc->diag_status)); sc->diag_status = 1; }
PNV_WRITEIO(PNV_DMA_CMD, 0); [24] PNV_WRITEIO(PNV_INTR_CTRL, (PNV_DMA_INTR | PNV_CRD_INTR | PNV_ABRT_INTR)); mb(); PNV_WRITEIO(PNV_ERROR, (PNVR_CMD_PAR | PNVR_WR_PAR | PNVR_CRD_ERR | PNVR_UCRD_ERR | PNVR_M_CMD_ERR | PNVR_M_WR_PAR | PNVR_M_CRD_ERR | PNVR_M_UCRD_ERR)); mb(); synch_data = PNV_READIO(PNV_ERROR);
#ifdef PNVRAM_DMA_INTR [25] pnvram_intr_info.configuration_st = (caddr_t)ctlr; pnvram_intr_info.intr = pnvram_intr; pnvram_intr_info.param = (caddr_t)unit; pnvram_intr_info.config_type = (CONTROLLER_CONFIG_TYPE | SHARED_INTR_CAPABLE);
pnvram_ihandle.ih_bus = ctlr->bus_hd; pnvram_ihandle.ih_bus_info = (char *)&pnvram_intr_info;
sc->hid = handler_add(&pnvram_ihandle); if (sc->hid == (ihandler_id_t *)(NULL)) { printf("pnvram%d: interrupt handler_add failed\n", unit); dma_map_dealloc(sc->dma_p); FREE(sc, M_DEVBUF); return(0); } else { handler_enable(sc->hid); PNV_WRITEIO(PNV_INTR_CTRL, (PNV_ENAB_DMA | PNV_ENAB_CRD)); mb(); synch_data = PNV_READIO(PNV_INTR_CTRL); } #endif /* PNVRAM_DMA_INTR */
printf("pnvram%d: Module Revision %d, Cache size: %d\n", [26] unit, (mem_cfg & PNV_MOD_REV_MASK), sc->cache_size);
return(1); [27]
}
The synch_data variable is declared as volatile to ensure that the compiler does not, as an optimization, remove the register read operation because the read data is not used. [Return to example]
If these bits are not set, the PCI power-on self-test (POST) code was unable to configure this PCI NVRAM device into the system. As a result, the base address values stored in the bar0 and bar1 members of the pci_cfg_hdr data structure are invalid and cannot be used to access device registers. In this case, the pnvram_probe interface prints a failure message identifying the failed unit and PCI bus number, and returns failure status (0) to the PCI bus configuration code. [Return to example]
The bzero interface takes two arguments:
The pnvram_probe interface then initializes the element that corresponds to this PCI NVRAM expansion board unit in the array of pnvram_softc data structures (see Section 6.3). [Return to example]
The dma_map_alloc interface takes four arguments:
In this call, pnvram_probe passes the constant PNV_MAX_XFER_SIZE, which represents the maximum size of the data to be transferred. Section 6.2 shows that this constant is defined in the /usr/sys/include/io/dec/pci/pnvram.h file.
In this call, pnvram_probe passes the ctlr pointer the PCI bus configuration passed to it.
Typically, the device driver passes an argument of type dma_handle_t *. The dma_map_alloc interface returns to this variable the address of the DMA handle. The device driver uses this address in a call to dma_map_load.
In this call, pnvram_probe simply passes the address of dma_p, the DMA handle accessed through the sc pointer. As shown in Section 6.2, the pnvram.h header file defines dma_p as a member of the pnvram_softc structure.
If dma_map_alloc cannot allocate the requested DMA resources, pnvram_probe calls the FREE interface to deallocate the memory previously allocated for the pnvram_softc data structure, and returns failure status (0) to the PCI bus configuration code. [Return to example]
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_probe echoes the completion of this instruction on the terminal. [Return to example]
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_probe echoes the completion of this instruction on the terminal. [Return to example]
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_probe echoes the completion of this instruction on the terminal. [Return to example]
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_probe echoes the completion of this instruction on the terminal. [Return to example]
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_probe echoes the completion of this instruction on the terminal. [Return to example]
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_probe echoes the completion of this instruction on the terminal. [Return to example]
If the handler_add interface returns NULL, pnvram_probe prints a failure message to the terminal and deallocates the DMA resources represented by the dma_p member of the pnvram_softc data structure. It then calls the FREE interface to return the memory occupied by the pnvram_softc data structure to the system and returns failure status (0) to the PCI bus configuration interface.
If the handler_add interface returns an opaque ihandler_id_t key, pnvram_probe passes the key as input to the handler_enable interface, thus enabling the pnvram_intr interrupt service interface. It then enables DMA completion interrupts and correctable read error (CRD) detection interrupts by calling the PNV_WRITEIO interface to write the logical OR of the PNV_ENAB_DMA and PNV_ENAB_CRD bit masks to the PNV_INTR_CTRL register. It forces the completion of this write by issuing an mb instruction and a PNV_READIO call to read the PNV_INTR_CTRL register. This causes the delivery of any pending writes to the PNV_INTR_CTRL register.
Note
As mentioned in Section 6.2, DMA abort interrupts are enabled at initialization (that is, the PNV_DISAB_ABRT bit in the PNV_INTR_CTRL register is clear).
The pnvram_attach interface is called by the PCI bus configuration code at system startup and performs the following tasks:
The pnvram_attach interface has the following implicit input:
.
.
.
ctlr->ctrl_num
.
.
.
This member is used to index into the arrray of softc addresses to obtain device unit-specific information.
The following code implements pnvram_attach:
int pnvram_attach(struct controller *ctlr) [1] { register int unit = ctlr->ctlr_num; [2] register struct pnvram_softc *sc = pnvram_softc[unit]; [3]
presto_interface[unit].nvram_status = pnvram_status; [4] presto_interface[unit].nvram_battery_status= pnvram_battery_status; presto_interface[unit].nvram_battery_disable= pnvram_battery_disable; presto_interface[unit].nvram_battery_enable= pnvram_battery_enable; presto_interface[unit].nvram_ioreg_read = pnvram_read; presto_interface[unit].nvram_ioreg_write = pnvram_write; presto_interface[unit].nvram_block_read = pnvram_read; presto_interface[unit].nvram_block_write = pnvram_write; presto_interface[unit].nvram_ioreg_zero = pnvram_zero; presto_interface[unit].nvram_block_zero = pnvram_zero;
presto_interface[unit].nvram_min_ioreg = sizeof(int); [5] presto_interface[unit].nvram_ioreg_align = sizeof(int); presto_interface[unit].nvram_min_block = PRFSIZE; presto_interface[unit].nvram_block_align = PRFSIZE;
presto_init(sc->cache_kseg_start, sc->cache_size, [6] PNVRAM_NOTMAPPED, PNVRAM_CACHED, pnvram_ssn(unit), unit); }
The pnvram_attach interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures (see Section 6.3). [Return to example]
Section 6.7, Section 6.8.1, Section 6.8.2, Section 6.8.3 Section 6.9.1, Section 6.9.2, and Section 6.10 discuss these interfaces.
The /usr/sys/include/sys/presto.h file provides additional information on the members of the presto_interface structure. [Return to example]
The presto_init interface takes six arguments:
In this call, pnvram_attach passes the constant PNVRAM_NOTMAPPED to indicate that the NVRAM is not mapped. Section 6.2 shows that this constant (and the PNVRAM_MAPPED constant) are defined in the pnvram.h file. This argument has no meaning on Alpha AXP CPUs.
Section 6.6.3 describes the pnvram_ssn interface. [Return to example]
The pnvram_attach interface passes pnvram_ssn as an argument to presto_init. The presto_init interface calls pnvram_ssn to obtain the machine (CPU) ID.
The pnvram_ssn interface generates a unique system-level and unit-level ID for this PCI bus NVRAM expansion board unit and returns it to its caller, the pnvram_attach interface. The following code implements pnvram_ssn:
u_long pnvram_ssn(int unit) [1] { u_int info_rtn; [2] struct item_list item_list; [3] u_long ssn; [4]
item_list.function = GET_SYSID; [5]
info_rtn = get_info(&item_list); [6]
if (info_rtn == TRUE) { [7] if (item_list.rtn_status == INFO_RETURNED) ssn = item_list.output_data | ((u_long)unit << 32); return(ssn); } else { printf("No System Serial Number for pnvram%d\n", unit); return((u_long)unit << 32); } }
If the get_info interface returns success status but the rtn_status member of the item_list structure does not contain the constant value INFO_RETURNED, or if the get_info interface returns failure status, the pnvram_ssn interface prints a message on the terminal indicating that no system serial number was found for this PCI bus NVRAM expansion board unit. In this case, it returns to the pnvram_attach interface the expansion board's unit number left-shifted by 32 bits. [Return to example]
The pnvram_status interface provides the /dev/presto device driver with the status of diagnostics run on the NVRAM. Section 6.6.1 shows that the pnvram_probe interface sets the diag_status member to indicate whether the PCI bus NVRAM expansion board passed software diagnostic tests. Section 6.6.2 shows that the pnvram_attach interface sets the nvram_status member of the presto_interface structure to pnvram_status. This is the mechanism the /dev/presto device driver uses to call the interface that returns the diagnostic status of the NVRAM.
The following code implements pnvram_status:
int pnvram_status(int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2]
if (sc->diag_status) [3] return(NVRAM_RDONLY); else return(NVRAM_BAD); }
The pnvram_status interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures (see Section 6.3). [Return to example]
If the board passed these tests, pnvram_status returns the constant NVRAM_RDONLY to the /dev/presto device driver. If the board failed these tests, pnvram_status returns the constant NVRAM_BAD to the /dev/presto device driver. [Return to example]
Table 6-3 lists the three interfaces implemented as part of the Battery Status Section for the /dev/pnvram device driver along with the sections in the book where each is described.
Part | Section |
Implementing the pnvram_battery_status Interface | Section 6.8.1 |
Implementing the pnvram_battery_enable Interface | Section 6.8.2 |
Implementing the pnvram_battery_disable Interface | Section 6.8.3 |
The pnvram_battery_status interface provides the /dev/presto device driver with the status of the battery on the NVRAM. Specifically, pnvram_battery_status performs the following tasks:
Section 6.6.2 shows that the pnvram_attach interface sets the nvram_battery_status member of the presto_interface data structure to pnvram_battery_status. This is the mechanism the /dev/presto device driver uses to call the interface that returns the status of the battery for the NVRAM.
The following code implements pnvram_battery_status:
int pnvram_battery_status(int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2] int tmp_reg; [3]
nvram_batteries[unit].nv_nbatteries = 1; [4] nvram_batteries[unit].nv_minimum_ok = 1; nvram_batteries[unit].nv_primary_mandatory = 1; nvram_batteries[unit].nv_test_retries = 1;
tmp_reg = PNV_READIO(PNV_BAT_CTRL); [5] DEP(("pnvram%d battery_status: csr:0x%x\n", unit, tmp_reg));
if ((tmp_reg & PNV_BAT_OK) && (tmp_reg & PNV_BCHRG)) { [6] nvram_batteries[unit].nv_status[0] = BATT_OK; DEP(("Status is BATT_OK\n")); return(0); } else { DEP(("Status is BAD\n")); return(1); } }
The pnvram_battery_status interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures (see Section 6.3). [Return to example]
The following list briefly describes these members:
Stores the number of batteries supported by the hardware. The pnvram_battery_status interface sets this member to the value 1, indicating that the PCI bus NVRAM expansion board supports one battery.
Stores the minimum number of batteries that are enabled and that have enough power for use by the /dev/presto driver. The pnvram_battery_status interface sets this member to the value 1 because the PCI bus NVRAM expansion board supports only one battery.
Stores the value indicating whether the primary battery is operational. The pnvram_battery_status interface sets this member to the value 1, indicating that the primary (and only) battery must be operational.
Stores the number of successive calls to pnvram_battery_status for each battery check made by the /dev/presto device driver. The pnvram_battery_status interface sets this member to the value 1, indicating one retry.
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_battery_status writes the contents of the register to the terminal. [Return to example]
In either case, if debug mode is enabled (that is, the DEP_DEBUG symbol is defined), pnvram_battery_status reports the battery status to the terminal. [Return to example]
The pnvram_battery_enable interface is called by the /dev/presto device driver and performs the following tasks:
Section 6.6.2 shows that the pnvram_attach interface sets the nvram_battery_enable member of the presto_interface data structure to pnvram_battery_enable. This is the mechanism the /dev/presto device driver uses to call the interface that enables the battery on the PCI bus NVRAM expansion board.
The following code implements pnvram_battery_enable:
int pnvram_battery_enable(int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2] unsigned int tmp_reg; [3]
tmp_reg = PNV_READIO(PNV_BAT_CTRL); [4] DEP(("pnvram%d battery_enable() BAT_CTRL:0x%x\n", unit, tmp_reg));
if (tmp_reg & PNV_BDISC) { [5] PNV_WRITEIO(PNV_BAT_CTRL, (tmp_reg & ~PNV_BDISC)); mb(); tmp_reg = PNV_READIO(PNV_BAT_CTRL); }
if (tmp_reg & PNV_BDISC) [6] return(1); else [7] return(0); }
The pnvram_battery_enable interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures (see Section 6.3). [Return to example]
If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), the pnvram_battery_enable interface writes the contents of the register to the terminal. [Return to example]
It forces the completion of this write by issuing an mb instruction, followed by another PNV_READIO call to read the PNV_BAT_CTRL register to the tmp_reg variable. This causes the delivery of any pending writes to the PCI NVRAM device registers. [Return to example]
The pnvram_battery_disable interface performs the following tasks:
The pnvram_battery_disable interface is called by the /dev/presto device driver when it needs to disable the battery on the PCI bus NVRAM expansion board. Section 6.6.2 shows that the pnvram_attach interface sets the nvram_battery_disable member of the presto_interface data structure to pnvram_battery_disable. This is the mechanism the /dev/presto device driver uses to call the interface that enables the battery on the PCI bus NVRAM expansion board.
The following code implements pnvram_battery_disable:
int pnvram_battery_disable(int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2] unsigned int tmp_reg; [3]
tmp_reg = PNV_READIO(PNV_BAT_CTRL); [4]
if (!(tmp_reg & PNV_BDISC)) { [5] PNV_WRITEIO(PNV_BAT_CTRL, (tmp_reg | PNV_BDISC) ); mb(); PNV_WRITEIO(PNV_BAT_CTRL, (tmp_reg | PNV_BDISC) ); mb(); PNV_WRITEIO(PNV_BAT_CTRL, (tmp_reg & ~PNV_BDISC) ); mb(); PNV_WRITEIO(PNV_BAT_CTRL, (tmp_reg & ~PNV_BDISC) ); mb(); PNV_WRITEIO(PNV_BAT_CTRL, (tmp_reg | PNV_BDISC) ); mb(); tmp_reg = PNV_READIO(PNV_BAT_CTRL); DEP(("pvnram%d battery_disable() PNV_BAT_CTRL:0x%x\n",unit,tmp_reg)); }
if (tmp_reg & PNV_BDISC) [6] return(0); else [7] return(1); }
The pnvram_battery_disable interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures (see Section 6.3). [Return to example]
The pnvram_battery_disable interface enforces the order of these writes to the PNV_BAT_CTRL register by interleaving them with mb instructions.
The final mb instruction is followed by another PNV_READIO call to read the PNV_BAT_CTRL register to the tmp_reg variable. This causes the delivery of any pending writes to the PCI NVRAM device registers. If debug mode is enabled (that is, the DEP_DEBUG symbol is defined), the pnvram_battery_disable interface writes the contents of the register to the terminal. [Return to example]
Table 6-4 lists the two interfaces implemented as part of the Read and Write Device Section for the /dev/pnvram device driver along with the sections in the book where each is described.
Part | Section |
Implementing the pnvram_read Interface | Section 6.9.1 |
Implementing the pnvram_write Interface | Section 6.9.2 |
The pnvram_read interface is called by the pnvram_probe interface and the /dev/presto device driver and performs the following tasks:
Section 6.6.2 shows that the pnvram_attach interface sets the nvram_ioreg_read (read small pieces of NVRAM) and nvram_block_read (read large pieces of NVRAM) members of the presto_interface data structure to pnvram_read. These members both point to the same interface, which means pnvram_read handles both small and large reads from the PCI bus NVRAM expansion board.
Note
An xxread interface implemented on Digital UNIX typically has three arguments: dev, uio, and flag. The reason that pnvram_read has different arguments is that it is not called directly from the I/O system as the result of a read system call. The read request from the I/O system is made to the /dev/presto driver's read entry point, which then calls pnvram_read to perform the actual read operation.
The following code implements pnvram_read:
void pnvram_read(vm_offset_t source, vm_offset_t dest, u_int len, int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2] vm_offset_t srcptr; [3] vm_offset_t destptr; [4] volatile int retry; [5]
sg_entry_t sg; [6] u_int xfer; [7] #if defined(PNVRAM_DMA_SLEEP) && defined(PNVRAM_DMA_INTR) int sleep_status; [8] #endif
DEP(("pnvram_read(source:0x%lx dest:0x%lx len:0x%x, unit:%d)\n", source,dest,len,unit)); [9]
while (len) { [10] xfer = len; if (xfer > PNV_MAX_XFER_SIZE) xfer = PNV_MAX_XFER_SIZE;
if (xfer >= 1024) { [11]
srcptr = KSEG_TO_PHYS(source) & 0x0FFFFFFFFL; [12]
if (!(dma_map_load(xfer, dest,(struct proc *)0, sc->ctlr, [13] &sc->dma_p,0,(DMA_ALL | DMA_CONTIG))) ) panic("pnvram_read: dma_map_load failure\n");
sg = dma_get_curr_sgentry(sc->dma_p); [14]
destptr = (u_long)sg->ba & 0x0FFFFFFFFL; [15]
mb(); sc->dma_in_progress = 1; [16] PNV_WRITEIO(PNV_SLAVE_ADDR, destptr); [17] PNV_WRITEIO(PNV_MASTER_ADDR, srcptr); PNV_WRITEIO(PNV_BYTE_COUNT, len); mb();
PNV_WRITEIO(PNV_DMA_CMD, PNV_NV_READ); [18] mb();
#ifdef PNVRAM_DMA_INTR [19] #ifdef PNVRAM_DMA_SLEEP sleep_status = mpsleep(sc, 0, 0, 1*hz, 0, 0); if (sleep_status == EWOULDBLOCK) { panic("pnvram_read: Timed-out DMA\n"); } else { return; } #else /* !PNVRAM_DMA_SLEEP */ [20] retry = dma_delay; while (--retry) { mb(); if (!(sc->dma_in_progress)) break; } if (!retry) { panic("pnvram_read: Timed-out DMA\n"); } #endif /* PNVRAM_DMA_SLEEP */
#else /* !PNVRAM_DMA_INTR */ [21] retry = poll_delay; while (--retry) { if (!(PNV_READIO(PNV_BYTE_COUNT) & 0x0ffffL)) break; if (!retry) panic("pnvram_read: DMA retry expired\n"); #endif /* PNVRAM_DMA_INTR */ } else { bcopy(source, dest, len); [22] } len -= xfer; source += xfer; dest += xfer; [23] } }
The pnvram_read interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures (see Section 6.3). [Return to example]
The KSEG_TO_PHYS interface takes one argument: the buffer virtual address to convert to a physical address.
The pnvram_read interface truncates the system physical address returned by KSEG_TO_PHYS to 32 bits and stores the result in srcptr. [Return to example]
The dma_map_load interface takes seven arguments:
In this call, pnvram_read passes the size stored in the xfer variable.
In this call, pnvram_read passes the address contained in dest. The /dev/presto driver passed in this address.
The interface uses this pointer to translate this virtual address to a physical address.
In this call, pnvram_read passes the value zero (0) to indicate that the address is a kernel address.
In this call, pnvram_read passes the controller structure pointer associated with this PCI bus NVRAM expansion board. Section 6.6.1 shows that pnvram_probe sets this controller structure pointer in the softc structure for this unit.
In this call, pnvram_read passes the address of the DMA handle defined in the softc structure. Section 6.2 shows the declaration of the dma_p member in the softc structure.
The pnvram_read interface uses an mb instruction to force these writes to be completed before it writes to the device register that initiates the DMA transaction. [Return to example]
The mp_sleep interface takes six arguments:
If the call to mpsleep returns the value EWOULDBLOCK, the timeout value of 1 second expired before the PCI bus NVRAM expansion board posted a DMA interrupt. In this case, pnvram_read calls panic to cause a system crash.
If the call to mpsleep returns the success status (0), pnvram_read returns to its caller, the /dev/presto device driver. [Return to example]
Note
Among the events that could cause the retry count to expire would be a DMA abort interrupt posted by the NVRAM expansion board. These interrupts are enabled at initialization (that is, the PNV_DISAB_ABRT bit in the PNV_INTR_CTRL register is clear) and are not affected by the definition or nondefinition of the DMA_INTR symbol.
Note
Because the PCI NVRAM source buffer is mapped in dense space, the pnvram_read interface can use the bcopy interface to perform the transfer. If the buffer were mapped in sparse space instead, pnvram_read would call the io_copyin interface, passing it an I/O handle for the source address, a system virtual address for the destination address, and a transfer byte count. The io_copyin interface is a generic interface that maps to a bus- and machine-specific interface that actually performs the copy from bus address space to system memory.
The pnvram_write interface is called by the /dev/presto device driver and performs the following tasks:
Section 6.6.2 shows that the pnvram_attach interface sets the nvram_ioreg_write (write small pieces of NVRAM) and nvram_block_write (write large pieces of NVRAM) members of the presto_interface data structure to pnvram_write. These members both point to the same interface. This means pnvram_write handles both small and large reads from the PCI bus NVRAM expansion board.
Note
An xxwrite interface implemented on Digital UNIX typically has three arguments: dev, uio, and flag. The reason that pnvram_write has different arguments is that it is not called directly from the I/O system as the result of a write system call. The read request from the I/O system is made to the /dev/presto driver's write entry point, which then calls pnvram_write to perform the actual write operation.
The following code implements pnvram_write:
void pnvram_write(vm_offset_t source, vm_offset_t dest, u_int len, int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2] vm_offset_t srcptr; [3] vm_offset_t destptr; [4] volatile int retry; [5] sg_entry_t sg; [6] u_int xfer; [7] #if defined(PNVRAM_DMA_SLEEP) && defined(PNVRAM_DMA_INTR) int sleep_status; [8] #endif
DEP(("pnvram_write(source:0x%lx dest:0x%lx len:0x%x, unit:%d)\n", [9] source,dest,len,unit));
while (len) { [10] xfer = len; if (xfer > PNV_MAX_XFER_SIZE) xfer = PNV_MAX_XFER_SIZE; if (xfer >= 1024) { destptr = KSEG_TO_PHYS(dest) & 0x0FFFFFFFFL; [11]
if (!(dma_map_load(xfer,source,(struct proc *)0, sc->ctlr, [12] &sc->dma_p, 0,(DMA_ALL | DMA_CONTIG))) ) panic("pnvram_write: dma_map_load failure\n");
sg = dma_get_curr_sgentry(sc->dma_p); [13]
srcptr = (u_long)sg->ba & 0x0FFFFFFFFL; [14]
mb(); sc->dma_in_progress = 1; [15] PNV_WRITEIO(PNV_SLAVE_ADDR, srcptr); [16] PNV_WRITEIO(PNV_MASTER_ADDR, destptr); PNV_WRITEIO(PNV_BYTE_COUNT, len); mb();
PNV_WRITEIO(PNV_DMA_CMD, PNV_NV_WRITE); [17] mb();
#ifdef PNVRAM_DMA_INTR [18] #ifdef PNVRAM_DMA_SLEEP sleep_status = mpsleep(sc, 0, 0, 1*hz, 0, 0); if (sleep_status == EWOULDBLOCK) { panic("pnvram_write: Timed-out DMA\n"); } else { return; } #else /* !PNVRAM_DMA_SLEEP */ [19] retry = dma_delay; while (--retry) { mb(); if (!(sc->dma_in_progress)) break; } if (!retry) { panic("pnvram_write: Timed-out DMA\n"); } #endif
#else /* !PNVRAM_DMA_INTR */ [20] retry = poll_delay; while (--retry) { if (!(PNV_READIO(PNV_BYTE_COUNT) & 0x0ffffL)) break; } if (!retry) panic("pnvram_write: DMA retry expired\n");
} #endif /* PNVRAM_DMA_INTR */ } else { bcopy(source, dest, len); [21] } len -= xfer; [22] source += xfer; dest += xfer;
} }
The pnvram_write interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures defined in the pnvram_data.c file (see Section 6.3). [Return to example]
If the length of the current transfer segment exceeds 1K bytes, performs the transfer as a direct memory access (DMA) transaction. Otherwise, it avoids the overhead of the DMA operation by calling the bcopy interface. [Return to example]
The KSEG_TO_PHYS interface takes one argument: the buffer virtual address to convert to a physical address.
The pnvram_write interface truncates the system physical address returned by KSEG_TO_PHYS to 32 bits and stores the result in destptr. [Return to example]
The dma_map_load interface takes seven arguments:
In this call, pnvram_write passes the size stored in the xfer variable.
In this call, pnvram_write passes the address contained in source. The /dev/presto driver passed in this address.
The interface uses this pointer to translate this virtual address to a physical address.
In this call, the pnvram_write interface passes the value zero (0) to indicate that the address is a kernel address.
In this call, pnvram_write passes the controller structure pointer associated with this PCI bus NVRAM expansion board. Section 6.6.1 shows that pnvram_probe set this controller structure pointer in the softc structure for this unit.
In this call, pnvram_write passes the address of the DMA handle defined in the softc structure. Section 6.2 shows the declaration of the dma_p member in the softc structure.
The pnvram_write interface uses an mb instruction to force these writes to be completed before it writes to the device register that initiates the DMA transaction. [Return to example]
The mp_sleep interface takes six arguments:
If the call to mpsleep returns the value EWOULDBLOCK, the timeout value of 1 second expired before the PCI bus NVRAM expansion board posted a DMA interrupt. In this case, pnvram_write calls panic to cause a system crash.
If the call to mpsleep returns the success status (0), pnvram_write returns to its caller, the /dev/presto device driver. [Return to example]
Note
Among the events that could cause the retry counter to expire would be a DMA abort interrupt posted by the NVRAM expansion board. These interrupts are enabled at initialization (that is, the PNV_DISAB_ABRT bit in the PNV_INTR_CTRL register is clear) and are not affected by the definition or nondefinition of the DMA_INTR symbol.
Note
Because the PCI NVRAM source buffer is mapped in dense space, the pnvram_write interface can use the bcopy interface to perform the transfer. If the buffer were mapped in sparse space instead, pnvram_write would call the io_copyout interface, passing it an I/O handle for the source address, a system virtual address for the destination address, and a transfer byte count. The io_copyout interface is a generic interface that maps to a bus- and machine-specific interface that actually performs the copy from system memory to bus address space.
The pnvram_zero interface is called by the /dev/presto device driver to zero (clear) a specified length of NVRAM starting at a specified address. Section 6.6.2 shows that the pnvram_attach interface sets the nvram_ioreg_zero (zero small pieces of NVRAM) and nvram_block_zero (zero large pieces of NVRAM) members of the presto_interface data structure to pnvram_zero. These members both point to the same interface. This means pnvram_zero zeros (clears) both small and large lengths from the PCI bus NVRAM expansion board.
The following code implements pnvram_zero:
void pnvram_zero(vm_offset_t addr, u_int len, int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2] register io_handle_t io_handle; [3] int zero_stat; [4]
DEP(("pnvram%d pnvram_zero(addr:0x%lx len:0x%x)\n",unit, addr,len)); bzero(addr, len); [5] }
The pnvram_zero interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures defined in the pnvram_data.c file (see Section 6.3). [Return to example]
Note
Because the PCI NVRAM source buffer is mapped in dense space, the pnvram_zero interface can use the bzero interface to perform the transfer. If the buffer were mapped in sparse space instead, pnvram_zero would call the io_zero interface, passing it an I/O handle for the source address, a system virtual address for the destination address, and a transfer byte count. The io_zero interface is a generic interface that maps to a bus- and machine-specific interface that actually performs the clearing of bus address space.
The pnvram_intr interface is called if interrupts are enabled from this PCI bus NVRAM expansion board unit, and the unit issues an interrupt. As discussed in Section 6.6.1, the pnvram_probe interface enables direct-memory access (PNV_ENAB_DMA) and correctable read data (PNV_ENAB_CRD) interrupts only if the DMA_INTR symbol is defined. It also registers pnvram_intr as this PCI bus NVRAM expansion board unit's interrupt service interface by calling handler_add and handler_enable.
When DMA interrupts are enabled, the pnvram_intr interface handles DMA completion interrupts, correctable read data (CRD) detection interrupts, and DMA abort interrupts (which are enabled at initialization).
The following code implements pnvram_intr:
int pnvram_intr(int unit) [1] { register struct pnvram_softc *sc = pnvram_softc[unit]; [2] int intr_status; [3] int error_status; [4] u_short pci_status; [5]
intr_status = PNV_READIO(PNV_INTR_CTRL); [6]
if (!(intr_status & PNV_INTR_OCCURRED)) [7] return(INTR_NOT_SERVICED);
if ((intr_status & PNV_DMA_INTR) && !(intr_status & PNV_ABRT_INTR)) { [8] PNV_WRITEIO(PNV_INTR_CTRL, (intr_status & ~(PNV_CRD_INTR|PNV_ABRT_INTR))); mb(); #ifdef PNVRAM_DMA_SLEEP wakeup((caddr_t)pnvram_softc[unit]); #else /* !PNVRAM_DMA_SLEEP */ [9] sc->dma_in_progress = 0; mb(); #endif /* PNVRAM_DMA_SLEEP */ }
if (intr_status & PNV_CRD_INTR) { [10] printf("CRD detected on pnvram%d:\n", unit); printf(" Failing Addr : 0x%x\n", PNV_READIO(PNV_FADDR)); printf(" EDC Syndrome : 0x%x\n", PNV_READIO(PNV_EDC_SYND)); printf(" Error Reg. : 0x%x\n", PNV_READIO(PNV_ERROR)); PNV_WRITEIO(PNV_ERROR, PNVR_CRD_ERR); mb(); PNV_WRITEIO(PNV_INTR_CTRL, (intr_status & ~(PNV_DMA_INTR | PNV_ABRT_INTR))); mb(); }
if (intr_status & PNV_ABRT_INTR) { [11] error_status = PNV_READIO(PNV_ERROR); pci_status = PNV_READ_CNFG(PNV_STAT); printf("*** pnvram%d fatal error ***\n", unit); printf(" DMA Slave Addr: 0x%x\n", PNV_READIO(PNV_SLAVE_ADDR)); printf(" DMA Master Addr: 0x%x\n", PNV_READIO(PNV_MASTER_ADDR)); printf(" DMA Byte Count : 0x%x\n", PNV_READIO(PNV_BYTE_COUNT)); printf(" DMA Command : 0x%x\n", PNV_READIO(PNV_DMA_CMD)); printf(" Failing Addr. : 0x%x\n", PNV_READIO(PNV_FADDR)); printf(" EDC Syndrome : 0x%x\n", PNV_READIO(PNV_EDC_SYND)); printf(" Error Reg. : 0x%x\n", error_status); printf(" PCI Status reg.: 0x%x\n", pci_status);
PNV_WRITEIO(PNV_SLAVE_ADDR, 0); PNV_WRITEIO(PNV_MASTER_ADDR, 0); PNV_WRITEIO(PNV_BYTE_COUNT, 0); PNV_WRITEIO(PNV_DMA_CMD, 0); PNV_WRITEIO(PNV_ERROR, error_status); mb();
PNV_WRITE_CNFG(PNV_CMD, ((struct pci_config_hdr *)(sc->ctlr->private[0]))->command); PNV_WRITE_CNFG(PNV_STAT, pci_status); mb();
panic("pnvram_intr: fatal dma-abort error\n"); } return(INTR_SERVICED); [12] }
The pnvram_intr interface initializes this pointer to the address of the element that corresponds to this PCI bus NVRAM expansion board unit in the array of pnvram_softc data structures defined in the pnvram_data.c file (see Section 6.3). [Return to example]