The appendixes provide the following background material:
Appendix A, Hardware Overview discusses multiplatform hardware issues for device drivers.
Appendix B, Summary of Oracle Solaris DDI/DKI Services provides tables of kernel functions for device drivers. Deprecated functions are indicated as well.
Appendix C, Making a Device Driver 64-Bit Ready provides guidelines for updating a device driver to run in a 64-bit environment.
Appendix D, Console Frame Buffer Drivers describes how to add the necessary interfaces to a frame buffer driver to enable the driver to interact with the Oracle Solaris kernel terminal emulator.
This appendix discusses general issues about hardware that is capable of supporting the Oracle Solaris OS. The discussion includes the processor, bus architectures, and memory models that are supported by the operating system. Various device issues and the PROM used in Oracle platforms are also covered.
The material in this appendix is for informational purposes only. This information might be of use during driver debugging. However, many of these implementation details are hidden from device drivers by the Oracle Solaris DDI/DKI interfaces.
This appendix provides information on the following subjects:
This section describes a number of SPARC processor-specific topics such as data alignment, byte ordering, register windows, and availability of floating-point instructions. For information on x86 processor-specific topics, see x86 Processor Issues.
Drivers should never perform floating-point operations, because these operations are not supported in the kernel.
All quantities must be aligned on their natural boundaries, using standard C data types:
short integers are aligned on 16-bit boundaries.
int integers are aligned on 32-bit boundaries.
long integers are aligned on 64-bit boundaries for SPARC systems. For information on data models, see Appendix C, Making a Device Driver 64-Bit Ready.
long long integers are aligned on 64-bit boundaries.
Usually, the compiler handles any alignment issues. However, driver writers are more likely to be concerned about alignment because the proper data types must be used to access the devices. Because device registers are commonly accessed through a pointer reference, drivers must ensure that pointers are properly aligned when accessing the device.
Because of the data alignment restrictions imposed by the SPARC processor, C structures also have alignment requirements. Structure alignment requirements are imposed by the most strictly aligned structure component. For example, a structure containing only characters has no alignment restrictions, while a structure containing a long long member must be constructed to guarantee that this member falls on a 64-bit boundary.
The SPARC processor uses big-endian byte ordering. The most significant byte (MSB) of an integer is stored at the lowest address of the integer. The least significant byte is stored at the highest address for words in this processor. For example, byte 63 is the least significant byte for 64-bit processors.
SPARC processors use register windows. Each register window consists of eight in registers, eight local registers, eight out registers, and eight global registers. Out registers are the in registers for the next window. The number of register windows ranges from 2 to 32, depending on the processor implementation.
Because drivers are normally written in C, the compiler usually hides the fact that register windows are used. However, you might have to use register windows when debugging the driver.
The Version 7 SPARC processors do not have multiply or divide instructions. The multiply and divide instructions are emulated in software. Because a driver might run on a Version 7, Version 8, or Version 9 processor, avoid intensive integer multiplication and division. Instead, use bitwise left and right shifts to multiply and divide by powers of two.
The SPARC Architecture Manual, Version 9, contains more specific information on the SPARC CPU. The SPARC Compliance Definition, Version 2.4, contains details of the application binary interface (ABI) for SPARC V9. The manual describes the 32-bit SPARC V8 ABI and the 64-bit SPARC V9 ABI. You can obtain this document from SPARC International at http://www.sparc.com.
Data types have no alignment restrictions. However, extra memory cycles might be required for the x86 processor to properly handle misaligned data transfers.
Drivers should not perform floating-point operations, as these operations are not supported in the kernel.
The x86 processors use little-endian byte ordering. The least significant byte (LSB) of an integer is stored at the lowest address of the integer. The most significant byte is stored at the highest address for data items in this processor. For example, byte 7 is the most significant byte for 64-bit processors.
Both Intel Corporation and AMD publish a number of books on the x86 family of processors. See http://www.intel.com and http://www.amd.com/.
To achieve the goal of multiple-platform, multiple-instruction-set architecture portability, host bus dependencies were removed from the drivers. The first dependency issue to be addressed was the endianness, that is, byte ordering, of the processor. For example, the x86 processor family is little-endian while the SPARC architecture is big-endian.
Bus architectures display the same endianness types as processors. The PCI local bus, for example, is little-endian, the SBus is big-endian, the ISA bus is little-endian, and so on.
To maintain portability between processors and buses, DDI-compliant drivers must be endian neutral. Although drivers can manage their endianness by runtime checks or by preprocessor directives like #ifdef _LITTLE_ENDIAN in the source code, long-term maintenance can be troublesome. In some cases, the DDI framework performs the byte swapping using a software approach. In other cases, byte swapping can be done by hardware page-level swapping as in memory management unit (MMU) or by special machine instructions. The DDI framework can take advantage of the hardware features to improve performance.
Along with being endian-neutral, portable drivers must also be independent from data ordering of the processor. Under most circumstances, data must be transferred in the sequence instructed by the driver. However, sometimes data can be merged, batched, or reordered to streamline the data transfer, as illustrated in the following figure. For example, data merging can be applied to accelerate graphics display on frame buffers. Drivers have the option to advise the DDI framework to use other optimal data transfer mechanisms during the transfer.
To improve performance, the CPU uses internal store buffers to temporarily store data. Using internal buffers can affect the synchronization of device I/O operations. Therefore, the driver needs to take explicit steps to make sure that writes to registers are completed at the proper time.
For example, consider the case where access to device space, such as registers or a frame buffer, is synchronized by a lock. The driver needs to check that the store to the device space has actually completed before releasing the lock. The release of the lock does not guarantee the flushing of I/O buffers.
To give another example, when acknowledging an interrupt, the driver usually sets or clears a bit in a device control register. The driver must ensure that the write to the control register has reached the device before the interrupt handler returns. Similarly, a device might require a delay, that is, driver busy-waits, after writing a command to the control register. In such a case, the driver must ensure that the write has reached the device before delaying.
Where device registers can be read without undesirable side effects, verification of a write can simply consist of reading the register immediately after the write. If that particular register cannot be read without undesirable side effects, another device register in the same register set can be used.
The system memory model defines the semantics of memory operations such as load and store and specifies how the order in which these operations are issued by a processor is related to the order in which they reach memory. The memory model applies to both uniprocessors and shared-memory multiprocessors. Two memory models are supported: total store ordering (TSO) and partial store ordering (PSO).
TSO guarantees that the sequence in which store, FLUSH, and atomic load-store instructions appear in memory for a given processor is identical to the sequence in which they were issued by the processor.
Both x86 and SPARC processors support TSO.
PSO does not guarantee that the sequence in which store, FLUSH, and atomic load-store instructions appear in memory for a given processor is identical to the sequence in which they were issued by the processor. The processor can reorder the stores so that the sequence of stores to memory is not the same as the sequence of stores issued by the CPU.
SPARC processors support PSO; x86 processors do not.
For SPARC processors, conformance between issuing order and memory order is provided by the system framework using the STBAR instruction. If two of the above instructions are separated by an STBAR instruction in the issuing order of a processor, or if the instructions reference the same location, the memory order of the two instructions is the same as the issuing order. Enforcement of strong data-ordering in DDI-compliant drivers is provided by the ddi_regs_map_setup(9F) interface. Compliant drivers cannot use the STBAR instruction directly.
See the SPARC Architecture Manual, Version 9, for more details on the SPARC memory model.
This section describes device identification, device addressing, and interrupts.
Device identification is the process of determining which devices are present in the system. Some devices are self-identifying meaning that the device itself provides information to the system so that the system can identify the device driver that needs to be used. SBus and PCI local bus devices are examples of self-identifying devices. On SBus, the information is usually derived from a small Forth program stored in the FCode PROM on the device. Most PCI devices provide a configuration space containing device configuration information. See the sbus(4) and pci(4) man pages for more information.
All modern bus architectures require devices to be self-identifying.
The Oracle Solaris platform supports both polling and vectored interrupts. The Oracle Solaris DDI/DKI interrupt model is the same for both types of interrupts. See Chapter 8, Interrupt Handlers for more information about interrupt handling.
This section covers addressing and device configuration issues specific to the buses that the Oracle Solaris platform supports.
The PCI local bus is a high-performance bus designed for high-speed data transfer. The PCI bus resides on the system board. This bus is normally used as an interconnect mechanism between highly integrated peripheral components, peripheral add-on boards, and host processor or memory systems. The host processor, main memory, and the PCI bus itself are connected through a PCI host bridge, as shown in Figure A–3.
A tree structure of interconnected I/O buses is supported through a series of PCI bus bridges. Subordinate PCI bus bridges can be extended underneath the PCI host bridge to enable a single bus system to be expanded into a complex system with multiple secondary buses. PCI devices can be connected to one or more of these secondary buses. In addition, other bus bridges, such as SCSI or USB, can be connected.
Every PCI device has a unique vendor ID and device ID. Multiple devices of the same kind are further identified by their unique device numbers on the bus where they reside.
The PCI host bridge provides an interconnect between the processor and peripheral components. Through the PCI host bridge, the processor can directly access main memory independent of other PCI bus masters. For example, while the CPU is fetching data from the cache controller in the host bridge, other PCI devices can also access the system memory through the host bridge. The advantage of this architecture is that this architecture separates the I/O bus from the processor's host bus.
The PCI host bridge also provides data access mappings between the CPU and peripheral I/O devices. The bridge maps every peripheral device to the host address domain so that the processor can access the device through programmed I/O. On the local bus side, the PCI host bridge maps the system memory to the PCI address domain so that the PCI device can access the host memory as a bus master. Figure A–3 shows the two address domains.
The PCI address domain consists of three distinct address spaces: configuration, memory, and I/O space.
Configuration space is defined geographically. The location of a peripheral device is determined by its physical location within an interconnected tree of PCI bus bridges. A device is located by its bus number and device (slot) number. Each peripheral device contains a set of well-defined configuration registers in its PCI configuration space. The registers are used not only to identify devices but also to supply device configuration information to the configuration framework. For example, base address registers in the device configuration space must be mapped before a device can respond to data access.
The method for generating configuration cycles is host dependent. In x86 machines, special I/O ports are used. On other platforms, the PCI configuration space can be memory-mapped to certain address locations corresponding to the PCI host bridge in the host address domain. When a device configuration register is accessed by the processor, the request is routed to the PCI host bridge. The bridge then translates the access into proper configuration cycles on the bus.
The PCI configuration space consists of up to six 32-bit base address registers for each device. These registers provide both size and data type information. System firmware assigns base addresses in the PCI address domain to these registers.
Each addressable region can be either memory or I/O space. The value contained in bit 0 of the base address register identifies the type. A value of 0 in bit 0 indicates a memory space and a value of 1 indicates an I/O space. The following figure shows two base address registers: one for memory and the other for I/O types.
PCI supports both 32-bit and 64-bit addresses for memory space. System firmware assigns regions of memory space in the PCI address domain to PCI peripherals. The base address of a region is stored in the base address register of the device's PCI configuration space. The size of each region must be a power of two, and the assigned base address must be aligned on a boundary equal to the size of the region. Device addresses in memory space are memory-mapped into the host address domain so that data access to any device can be performed by the processor's native load or store instructions.
PCI supports 32-bit I/O space. I/O space can be accessed differently on different platforms. Processors with special I/O instructions, like the Intel processor family, access the I/O space with in and out instructions. Machines without special I/O instructions will map to the address locations corresponding to the PCI host bridge in the host address domain. When the processor accesses the memory-mapped addresses, an I/O request will be sent to the PCI host bridge, which then translates the addresses into I/O cycles and puts them on the PCI bus. Memory-mapped I/O is performed by the native load/store instructions of the processor.
Hardware configuration files should be unnecessary for PCI local bus devices. However, on some occasions drivers for PCI devices need to use hardware configuration files to augment the driver private information. See the driver.conf(4) and pci(4) man pages for further details.
The standard PCI bus has evolved into PCI Express. PCI Express is the next generation high performance I/O bus for connecting peripheral devices in such applications as desktop, mobile, workstation, server, embedded computing and communication platforms.
PCI Express improves bus performance, reduces overall system cost and takes advantage of new developments in computer design. PCI Express uses a serial, point-to-point type interconnect for communication between two devices. Using switches enables users to connect a large number of devices together in a system. Serial interconnect implies fewer pins per device package, which reduces cost and makes the performance highly scalable.
The PCI Express bus has built-in features to accommodate the following technologies:
QoS (Quality of Service)
Hotplugging and hot swap
Advanced power management
RAS (Reliability, Available, Serviceable)
Improved error handling
MSI interrupts
A PCI Express interconnect that connects two devices together is called a link. A link can either be x1, x2, x4, x8, x12, x16 or x32 bidirectional signal pairs. These signals are called lanes. The bandwidth (x1) of each lane is 500 MB/sec in duplex mode. Although PCI-X and PCI Express have different hardware connections, the two buses are identical from a driver writer's point of view. PCI-X is a shared bus. For example, all the devices on the bus share a single set of data lines and signal lines. PCI-Express is a switched bus, which enables more efficient use of the bandwidth between the devices and the system bus.
For more information on PCI Express, please refer to the following web site: http://www.pcisig.com/home
Typical SBus systems consist of a motherboard (containing the CPU and SBus interface logic), a number of SBus devices on the motherboard itself, and a number of SBus expansion slots. An SBus can also be connected to other types of buses through an appropriate bus bridge.
The SBus is geographically addressed. Each SBus slot exists at a fixed physical address in the system. An SBus card has a different address, depending on which slot it is plugged into. Moving an SBus device to a new slot causes the system to treat this device as a new device.
The SBus uses polling interrupts. When an SBus device interrupts, the system only knows which of several devices might have issued the interrupt. The system interrupt handler must ask the driver for each device whether that device is responsible for the interrupt.
The following table shows the physical address space layout of the Sun UltraSPARC 2 computer. A physical address on the UltraSPARC 2 model consists of 41 bits. The 41-bit physical address space is further broken down into multiple 33-bit address spaces identified by PA(40:33).
Table A–1 Device Physical Space in the Ultra 2
PA(40:33) |
33-bit Space |
Usage |
---|---|---|
0x0 |
0x000000000 - 0x07FFFFFFF |
2 Gbytes main memory |
0x80 – 0xDF |
Reserved on Ultra 2 |
Reserved on Ultra 2 |
0xE0 |
Processor 0 |
Processor 0 |
0xE1 |
Processor 1 |
Processor 1 |
0xE2 – 0xFD |
Reserved on Ultra 2 |
Reserved on Ultra 2 |
0xFE |
0x000000000 - 0x1FFFFFFFF |
UPA Slave (FFB) |
0xFF |
0x000000000 - 0x0FFFFFFFF |
System I/O space |
|
0x100000000 - 0x10FFFFFFF |
SBus Slot 0 |
|
0x110000000 - 0x11FFFFFFF |
SBus Slot 1 |
|
0x120000000 - 0x12FFFFFFF |
SBus Slot 2 |
|
0x130000000 - 0x13FFFFFFF |
SBus Slot 3 |
|
0x1D0000000 - 0x1DFFFFFFF |
SBus Slot D |
|
0x1E0000000 - 0x1EFFFFFFF |
SBus Slot E |
|
0x1F0000000 - 0x1FFFFFFFF |
SBus Slot F |
The SBus has 32 address bits, as described in the SBus Specification. The following table describes how the Ultra 2 uses the address bits.
Table A–2 Ultra 2 SBus Address Bits
Bits |
Description |
---|---|
0 - 27 |
These bits are the SBus address lines used by an SBus card to address the contents of the card. |
28 - 31 |
Used by the CPU to select one of the SBus slots. These bits generate the SlaveSelect lines. |
This addressing scheme yields the Ultra 2 addresses shown in Table A–1. Other implementations might use a different number of address bits.
The Ultra 2 has seven SBus slots, four of which are physical. Slots 0 through 3 are available for SBus cards. Slots 4-12 are reserved. The slots are used as follows:
Slots 0-3 are physical slots that have DMA-master capability.
Slots D, E, and F are not actual physical slots, but refer to the onboard direct memory access (DMA), SCSI, Ethernet, and audio controllers. For convenience, these classes of devices are viewed as being plugged into slots D, E, and F.
Some SBus slots are slave-only slots. Drivers that require DMA capability should use ddi_slaveonly(9F) to determine whether their device is in a DMA-capable slot. For an example of this function, see attach() Entry Point.
Hardware configuration files are normally unnecessary for SBus devices. However, on some occasions, drivers for SBus devices need to use hardware configuration files to augment the information provided by the SBus card. See the driver.conf(4) and sbus(4) man page for further details.
This section describes issues with special devices.
While most driver operations can be performed without mechanisms for synchronization and protection beyond those provided by the locking primitives, some devices require that a sequence of events occur in order without interruption. In conjunction with the locking primitives, the function ddi_enter_critical(9F) asks the system to guarantee, to the best of its ability, that the current thread will neither be preempted nor interrupted. This guarantee stays in effect until a closing call to ddi_exit_critical(9F) is made. See the ddi_enter_critical(9F) man page for details.
Many chips specify that they can be accessed only at specified intervals. For example, the Zilog Z8530 SCC has a “write recovery time” of 1.6 microseconds. This specification means that a delay must be enforced with drv_usecwait(9F) when writing characters with an 8530. In some instances, the specifications do not make explicit what delays are needed, so the delays must be determined empirically.
Be careful not to compound delays for parts of devices that might exist in large numbers, for example, thousands of SCSI disk drives.
Devices with internal sequencing logic map multiple internal registers to the same external address. The various kinds of internal sequencing logic include the following types:
The Intel 8251A and the Signetics 2651 alternate the same external register between two internal mode registers. Writing to the first internal register is accomplished by writing to the external register. This write, however, has the side effect of setting up the sequencing logic in the chip so that the next read/write operation refers to the second internal register.
The NEC PD7201 PCC has multiple internal data registers. To write a byte into a particular register, two steps must be performed. The first step is to write into register zero the number of the register into which the following byte of data will go. The data is then written to the specified data register. The sequencing logic automatically sets up the chip so that the next byte sent will go into data register zero.
The AMD 9513 timer has a data pointer register that points at the data register into which a data byte will go. When sending a byte to the data register, the pointer is incremented. The current value of the pointer register cannot be read.
Note the following common interrupt-related issues:
A controller interrupt does not necessarily indicate that both the controller and one of its slave devices are ready. For some controllers, an interrupt can indicate that either the controller is ready or one of its devices is ready but not both.
Not all devices power up with interrupts disabled and can begin interrupting at any time.
Some devices do not provide a way to determine that the board has generated an interrupt.
Not all interrupting boards shut off interrupts when told to do so or after a bus reset.
Some platforms have a PROM monitor that provides support for debugging a device without an operating system. This section describes how to use the PROM on SPARC machines to map device registers so that they can be accessed. Usually, the device can be exercised enough with PROM commands to determine whether the device is working correctly.
See the boot(1M) man page for a description of the x86 boot subsystem.
The PROM has several purposes, including:
Bringing the machine up from power on, or from a hard reset PROM reset command
Providing an interactive tool for examining and setting memory, device registers, and memory mappings
Booting the Oracle Solaris system.
Simply powering up the computer and attempting to use its PROM to examine device registers can fail. While the device might be correctly installed, those mappings are specific to the Oracle Solaris OS and do not become active until the Oracle Solaris kernel is booted. Upon power up, the PROM maps only essential system devices, such as the keyboard.
Taking a system crash dump using the sync command
For complete documentation on the Open Boot PROM, see the Open Boot PROM Toolkit User's Guide and the monitor(1M) man page. The examples in this section refer to a Sun4U architecture. Other architectures might require different commands to perform actions.
The Open Boot PROM is currently used on Sun machines with an SBus or UPA/PCI. The Open Boot PROM uses an “ok” prompt. On older machines, you might have to type `n' to get the “ok” prompt.
If the PROM is in secure mode (the security-mode parameter is not set to none), the PROM password might be required (set in the security-password parameter).
The printenv command displays all parameters and their values.
Help is available with the help command.
EMACS-style command-line history is available. Use Control-N (next) and Control-P (previous) to traverse the history list.
The Open Boot PROM uses the Forth programming language. Forth is a stack-based language. Arguments must be pushed on the stack before running the correct command (called a word), and the result is left on the stack.
To place a number on the stack, type its value.
ok 57 ok 68 |
To add the two top values on the stack, use the + operator.
ok + |
The result remains on the stack. The stack is shown with the .s word.
ok .s bf |
The default base is hexadecimal. The hex and decimal words can be used to switch bases.
ok decimal ok .s 191 |
See the Forth User's Guide for more information.
The commands pwd, cd, and ls walk the PROM device tree to get to the device. The cd command must be used to establish a position in the tree before pwd will work. This example is from an Ultra 1 workstation with a cgsix frame buffer on an SBus.
ok cd / |
To see the devices attached to the current node in the tree, use ls.
ok ls f006a064 SUNW,UltraSPARC@0,0 f00598b0 sbus@1f,0 f00592dc counter-timer@1f,3c00 f004eec8 virtual-memory f004e8e8 memory@0,0 f002ca28 aliases f002c9b8 options f002c880 openprom f002c814 chosen f002c7a4 packages |
The full node name can be used:
ok cd sbus@1f,0 ok ls f006a4e4 cgsix@2,0 f0068194 SUNW,bpp@e,c800000 f0065370 ledma@e,8400010 f006120c espdma@e,8400000 f005a448 SUNW,pll@f,1304000 f005a394 sc@f,1300000 f005a24c zs@f,1000000 f005a174 zs@f,1100000 f005a0c0 eeprom@f,1200000 f0059f8c SUNW,fdtwo@f,1400000 f0059ec4 flashprom@f,0 f0059e34 auxio@f,1900000 f0059d28 SUNW,CS4231@d,c000000 |
Rather than using the full node name in the previous example, you could also use an abbreviation. The abbreviated command-line entry looks like the following example:
ok cd sbus |
The name is actually device@slot,offset (for SBus devices). The cgsix device is in slot 2 and starts at offset 0. If an SBus device is displayed in this tree, the device has been recognized by the PROM.
The .properties command displays the PROM properties of a device. These properties can be examined to determine which properties the device exports. This information is useful later to ensure that the driver is looking for the correct hardware properties. These properties are the same properties that can be retrieved with ddi_getprop(9F).
ok cd cgsix ok .properties character-set ISO8859-1 intr 00000005 00000000 interrupts 00000005 reg 00000002 00000000 01000000 dblbuf 00 00 00 00 vmsize 00 00 00 01 ... |
The reg property defines an array of register description structures containing the following fields:
uint_t bustype; /* cookie for related bus type*/ uint_t addr; /* address of reg relative to bus */ uint_t size; /* size of this register set */ |
For the cgsix example, the address is 0.
A device must be mapped into memory to be tested. The PROM can then be used to verify proper operation of the device by using data-transfer commands to transfer bytes, words, and long words. If the device can be operated from the PROM, even in a limited way, the driver should also be able to operate the device.
To set up the device for initial testing, perform the following steps:
Determine the SBus slot number the device is in.
In this example, the cgsix device is located in slot 2.
Determine the offset within the physical address space used by the device.
The offset used is specific to the device. In the cgsix example, the video memory happens to start at an offset of 0x800000.
Use the select-dev word to select the Sbus device and the map-in word to map the device in.
The select-dev word takes a string of the device path as its argument. The map-in word takes an offset, a slot number, and a size as arguments to map. Like the offset, the size of the byte transfer is specific to the device. In the cgsix example, the size is set to 0x100000 bytes.
In the following code example, the Sbus path is displayed as an argument to the select-dev word, and the offset, slot number, and size values for the frame buffer are displayed as arguments to the map-in word. Notice the space between the opening quote and / in the select-dev argument. The virtual address to use remains on top of the stack. The stack is shown using the .s word. The stack can be assigned a name with the constant operation.
ok " sbus@1f,0" select-dev ok 800000 2 100000 map-in ok .s ffe98000 ok constant fb |
The PROM provides a variety of 8-bit, 16-bit, and 32-bit operations. In general, a c (character) prefix indicates an 8-bit (one-byte) operation; a w (word) prefix indicates a 16-bit (two-byte) operation; and an L (longword) prefix indicates a 32-bit (four-byte) operation.
A suffix of ! indicates a write operation. The write operation takes the first two items off the stack. The first item is the address, and the second item is the value.
ok 55 ffe98000 c! |
A suffix of @ indicates a read operation. The read operation takes the address off the stack.
ok ffe98000 c@ ok .s 55 |
A suffix of ? is used to display the value without affecting the stack.
ok ffe98000 c? 55 |
Be careful when trying to query the device. If the mappings are not set up correctly, trying to read or write could cause errors. Special words are provided to handle these cases. cprobe, wprobe, and lprobe, for example, read from the given address but return zero if the location does not respond, or nonzero if it does.
ok fffa4000 c@ Data Access Error ok fffa4000 cprobe ok .s0 ok ffe98000 cprobe ok .s 0 ffffffffffffffff |
A region of memory can be shown with the dump word. This takes an address and a length, and displays the contents of the memory region in bytes.
In the following example, the fill word is used to fill video memory with a pattern. fill takes the address, the number of bytes to fill, and the byte to use. Use wfill and an Lfill for words and longwords. This fill example causes the cgsix to display simple patterns based on the byte passed.
ok " /sbus" select-dev ok 800000 2 100000 map-in ok constant fb ok fb 10000 ff fill ok fb 20000 0 fill ok fb 18000 55 fill ok fb 15000 3 fill ok fb 10000 5 fillok fb 5000 f9 fill |
This appendix discusses the interfaces provided by the Oracle Solaris DDI/DKI. These descriptions should not be considered complete or definitive, nor do they provide a thorough guide to usage. The descriptions are intended to describe what the functions do in general terms. See physio(9F) for more detailed information. The categories are:
The module functions are:
Query a loadable module
Add a loadable module
Remove a loadable module
The device information tree node functions are:
Return driver binding name
Tell whether a device is self-identifying
Return driver major device number
Return normalized driver name
Return the devinfo node name
Check device state
Get device instance number
Return driver binding name
Find the parent of a device information structure
Get the root of the dev_info tree
The device functions are:
Create a minor node for a device
Get kernel internal minor number from an external dev_t
Remove a minor mode for a device
Get major device number
Get minor device number
Make device number from major and minor numbers
The property functions are:
Check for the existence of a property
Free resources consumed by property lookup
Look up integer property
Look up 64-bit integer property
Look up byte array property
Look up integer array property
Look up 64-bit integer array property
Look up string property
Look up string array property
Remove a property of a device
Remove all properties of a device
Hide a property of a device
Create or update byte array property
Create or update integer property
Create or update 64-bit integer property
Create or update integer array property
Create or update 64-bit integer array property
Create or update string property
Create or update string array property
Deprecated Functions |
Replacements |
---|---|
ddi_getlongprop() |
see ddi_prop_lookup() |
ddi_getlongprop_buf() |
ddi_prop_lookup() |
ddi_getprop() |
ddi_prop_get_int() |
ddi_getproplen() |
ddi_prop_lookup() |
ddi_prop_create() |
ddi_prop_lookup() |
ddi_prop_modify() |
ddi_prop_lookup() |
ddi_prop_op() |
ddi_prop_lookup() |
The device software state functions are:
Get the address of the device's private data area
Get pointer to instance soft-state structure
Set the address of the device's private data area
Destroy driver soft-state structure
Free instance soft-state structure
Initialize driver soft-state structure
Allocate instance soft-state structure
The memory allocation and deallocation functions are:
Allocate kernel memory
Free kernel memory
Allocate zero-filled kernel memory
The following functions allocate and free memory intended to be used for DMA. See Direct Memory Access (DMA) Functions.
Allocate memory for DMA transfer
Free previously allocated DMA memory
The following functions allocate and free memory intended to be exported to user space. See User Space Access Functions.
Allocate page-aligned kernel memory
Free page-aligned kernel memory
Deprecated Function |
Replacement |
---|---|
ddi_iopb_alloc() |
ddi_dma_mem_alloc() |
ddi_iopb_free() |
ddi_dma_mem_free() |
ddi_mem_alloc() |
ddi_dma_mem_alloc() |
ddi_mem_free() |
ddi_dma_mem_free() |
The kernel thread control and synchronization functions are:
Wake up all waiting threads
Free an allocated condition variable
Allocate a condition variable
Wake up one waiting thread
Await an event with timeout
Await an event or signal with timeout
Await an event
Await an event or signal
Determine whether the current thread can receive a signal
Enter a critical region of control
Exit a critical region of control
Acquire mutual exclusion lock
Release mutual exclusion lock
Initialize mutual exclusion lock
Determine whether current thread is holding mutual exclusion lock
Attempt to acquire mutual exclusion lock without waiting
Downgrade a readers/writer lock holding from writer to reader
Acquire a readers/writer lock
Release a readers/writer lock
Initialize a readers/writer lock
Determine whether readers/writer lock is held for read or write
Attempt to acquire a readers/writer lock without waiting
Attempt to upgrade readers/writer lock holding from reader to writer
Destroy a semaphore
Initialize a semaphore
Decrement semaphore and possibly block
Decrement semaphore but do not block if signal is pending
Attempt to decrement semaphore but do not block
Increment semaphore and possibly unblock waiter
The task queue management functions are listed below. See the taskq(9F) man page for more information about these interfaces.
Create a task queue
Destroy a task queue
Add a task to a task queue
Wait for pending tasks to complete
Suspend a task queue
Check whether a task queue is suspended
Resume a suspended task queue
The interrupt functions are:
Adds an interrupt handler.
Adds a soft interrupt handler.
Allocates system resources and interrupt vectors for the specified type of interrupt.
Disables the specified range of interrupts. For MSI only.
Enables the specified range of interrupts. For MSI only.
Clears an interrupt mask if the specified interrupt is enabled.
Disables the specified interrupt.
Use with MSI-X only. Copies an address and data pair for an allocated interrupt vector to an unused interrupt vector on the same device.
Enables the specified interrupt.
Releases the system resources and interrupt vectors for a specified interrupt handle.
Returns interrupt capability flags for the specified interrupt.
Returns the minimum priority level for a high-level interrupt.
Returns the number of interrupts available for a particular hardware device and given interrupt type.
Get the number of interrupts that the device supports for the given interrupt type.
Read the interrupt pending bit if one is supported by either the host bridge or the device.
Returns the current software priority setting for the specified interrupt.
Returns the soft interrupt priority for the specified interrupt.
Returns the hardware interrupt types that are supported by both the device and the host.
Removes the specified interrupt handler.
Remove the specified soft interrupt handler.
Sets the DDI_INTR_FLAG_LEVEL or DDI_INTR_FLAG_EDGE flag for the specified interrupt.
Sets an interrupt mask if the specified interrupt is enabled.
Sets the interrupt priority level for the specified interrupt.
Changes the relative soft interrupt priority for the specified soft interrupt.
Trigger the specified soft interrupt.
To take advantage of the features of the new framework, use the above interfaces. Do not use the deprecated interfaces that are listed in the following table. These deprecated interfaces are retained for compatibility purposes only.
Table B–3 Deprecated Interrupt Functions
The programmed I/O functions are:
Return the number of register sets a device has
Return the size of a device's register
Set up a mapping for a register address space
Free a previously mapped register address space
Copy data from one device register to another device register
Zero fill the device
Check data access handle
Read 8-bit data from mapped memory, device register, or DMA memory
Read 16-bit data from mapped memory, device register, or DMA memory
Read 32-bit data from mapped memory, device register, or DMA memory
Read 64-bit data from mapped memory, device register, or DMA memory
Write 8-bit data to mapped memory, device register, or DMA memory
Write 16-bit data to mapped memory, device register, or DMA memory
Write 32-bit data to mapped memory, device register, or DMA memory
Write 64-bit data to mapped memory, device register, or DMA memory
Read multiple 8-bit data from mapped memory, device register, or DMA memory
Read multiple 16-bit data from mapped memory, device register, or DMA memory
Read multiple 32-bit data from mapped memory, device register, or DMA memory
Read multiple 64-bit data from mapped memory, device register, or DMA memory
Write multiple 8-bit data to mapped memory, device register, or DMA memory
Write multiple 16-bit data to mapped memory, device register, or DMA memory
Write multiple 32-bit data to mapped memory, device register, or DMA memory
Write multiple 64-bit data to mapped memory, device register, or DMA memory
Cautiously read an 8-bit value from a location
Cautiously read a 16-bit value from a location
Cautiously read a 32-bit value from a location
Cautiously read a 64-bit value from a location
Cautiously write an 8-bit value to a location
Cautiously write a 16-bit value to a location
Cautiously write a 32-bit value to a location
Cautiously write a 64-bit value to a location
The general programmed I/O functions listed above can always be used rather than the mem, io, and pci_config functions that follow. However, the following functions can be used as alternatives in cases where the type of access is known at compile time.
Read 8-bit data from a mapped device register in I/O space
Read 16-bit data from a mapped device register in I/O space
Read 32-bit data from a mapped device register in I/O space
Write 8-bit data to a mapped device register in I/O space
Write 16-bit data to a mapped device register in I/O space
Write 32-bit data to a mapped device register in I/O space
Read multiple 8-bit data from a mapped device register in I/O space
Read multiple 16-bit data from a mapped device register in I/O space
Read multiple 32-bit data from a mapped device register in I/O space
Write multiple 8-bit data to a mapped device register in I/O space
Write multiple 16-bit data to a mapped device register in I/O space
Write multiple 32-bit data to a mapped device register in I/O space
Read 8-bit data from a mapped device in memory space or DMA memory
Read 16-bit data from a mapped device in memory space or DMA memory
Read 32-bit data from a mapped device in memory space or DMA memory
Read 64-bit data from a mapped device in memory space or DMA memory
Write 8-bit data to a mapped device in memory space or DMA memory
Write 16-bit data to a mapped device in memory space or DMA memory
Write 32-bit data to a mapped device in memory space or DMA memory
Write 64-bit data to a mapped device in memory space or DMA memory
Read multiple 8-bit data from a mapped device in memory space or DMA memory
Read multiple 16-bit data from a mapped device in memory space or DMA memory
Read multiple 32-bit data from a mapped device in memory space or DMA memory
Read multiple 64-bit data from a mapped device in memory space or DMA memory
Write multiple 8-bit data to a mapped device in memory space or DMA memory
Write multiple 16-bit data to a mapped device in memory space or DMA memory
Write multiple 32-bit data to a mapped device in memory space or DMA memory
Write multiple 64-bit data to a mapped device in memory space or DMA memory
Set up access to PCI Local Bus Configuration space
Tear down access to PCI Local Bus Configuration space
Read 8-bit data from the PCI Local Bus Configuration space
Read 16-bit data from the PCI Local Bus Configuration space
Read 32-bit data from the PCI Local Bus Configuration space
Read 64-bit data from the PCI Local Bus Configuration space
Write 8-bit data to the PCI Local Bus Configuration space
Write 16-bit data to the PCI Local Bus Configuration space
Write 32-bit data to the PCI Local Bus Configuration space
Write 64-bit data to the PCI Local Bus Configuration space
Deprecated Function |
Replacement |
---|---|
ddi_getb() |
ddi_get8() |
ddi_getl() |
ddi_get32() |
ddi_getll() |
ddi_get64() |
ddi_getw() |
ddi_get16() |
ddi_io_getb() |
ddi_io_get8() |
ddi_io_getl() |
ddi_io_get32() |
ddi_io_getw() |
ddi_io_get16() |
ddi_io_putb() |
ddi_io_put8() |
ddi_io_putl() |
ddi_io_put32() |
ddi_io_putw() |
ddi_io_put16() |
ddi_io_rep_getb() |
ddi_io_rep_get8() |
ddi_io_rep_getl() |
ddi_io_rep_get32() |
ddi_io_rep_getw() |
ddi_io_rep_get16() |
ddi_io_rep_putb() |
ddi_io_rep_put8() |
ddi_io_rep_putl() |
ddi_io_rep_put32() |
ddi_io_rep_putw() |
ddi_io_rep_put16() |
ddi_map_regs() |
ddi_regs_map_setup() |
ddi_mem_getb() |
ddi_mem_get8() |
ddi_mem_getl() |
ddi_mem_get32() |
ddi_mem_getll() |
ddi_mem_get64() |
ddi_mem_getw() |
ddi_mem_get16() |
ddi_mem_putb() |
ddi_mem_put8() |
ddi_mem_putl() |
ddi_mem_put32() |
ddi_mem_putll() |
ddi_mem_put64() |
ddi_mem_putw() |
ddi_mem_put16() |
ddi_mem_rep_getb() |
ddi_mem_rep_get8() |
ddi_mem_rep_getl() |
ddi_mem_rep_get32() |
ddi_mem_rep_getll() |
ddi_mem_rep_get64() |
ddi_mem_rep_getw() |
ddi_mem_rep_get16() |
ddi_mem_rep_putb() |
ddi_mem_rep_put8() |
ddi_mem_rep_putl() |
ddi_mem_rep_put32() |
ddi_mem_rep_putll() |
ddi_mem_rep_put64() |
ddi_mem_rep_putw() |
ddi_mem_rep_put16() |
ddi_peekc() |
ddi_peek8() |
ddi_peekd() |
ddi_peek64() |
ddi_peekl() |
ddi_peek32() |
ddi_peeks() |
ddi_peek16() |
ddi_pokec() |
ddi_poke8() |
ddi_poked() |
ddi_poke64() |
ddi_pokel() |
ddi_poke32() |
ddi_pokes() |
ddi_poke16() |
ddi_putb() |
ddi_put8() |
ddi_putl() |
ddi_put32() |
ddi_putll() |
ddi_put64() |
ddi_putw() |
ddi_put16() |
ddi_rep_getb() |
ddi_rep_get8() |
ddi_rep_getl() |
ddi_rep_get32() |
ddi_rep_getll() |
ddi_rep_get64() |
ddi_rep_getw() |
ddi_rep_get16() |
ddi_rep_putb() |
ddi_rep_put8() |
ddi_rep_putl() |
ddi_rep_put32() |
ddi_rep_putll() |
ddi_rep_put64() |
ddi_rep_putw() |
ddi_rep_put16() |
ddi_unmap_regs() |
ddi_regs_map_free() |
inb() |
ddi_io_get8() |
inl() |
ddi_io_get32() |
inw() |
ddi_io_get16() |
outb() |
ddi_io_put8() |
outl() |
ddi_io_put32() |
outw() |
ddi_io_put16() |
pci_config_getb() |
pci_config_get8() |
pci_config_getl() |
pci_config_get32() |
pci_config_getll() |
pci_config_get64() |
pci_config_getw() |
pci_config_get16() |
pci_config_putb() |
pci_config_put8() |
pci_config_putl() |
pci_config_put32() |
pci_config_putll() |
pci_config_put64() |
pci_config_putw() |
pci_config_put16() |
repinsb() |
ddi_io_rep_get8() |
repinsd() |
ddi_io_rep_get32() |
repinsw() |
ddi_io_rep_get16() |
repoutsb() |
ddi_io_rep_put8() |
repoutsd() |
ddi_io_rep_put32() |
repoutsw() |
ddi_io_rep_put16() |
The DMA functions are:
Allocate a DMA handle
Free a DMA handle
Allocate memory for a DMA transfer
Free previously allocated DMA memory
Bind an address to a DMA handle
Bind a system buffer to a DMA handle
Unbind the address in a DMA handle
Retrieve the subsequent DMA cookie
Activate a new DMA window
Retrieve number of DMA windows
Synchronize CPU and I/O views of memory
Check a DMA handle
Allow 64-bit transfers on SBus
Report whether a device is installed in a slave access-only location
Find the minimum alignment and transfer size for DMA
Find out the allowed burst sizes for a DMA mapping
Find DMA mapping alignment and minimum transfer size
Acquire a DMA channel
Release a DMA channel
Get the DMA engine attributes
Program a DMA channel
Terminate a DMA engine operation
Disable a DMA channel
Enable a DMA channel
Get the remaining DMA engine count
Configure the DMA channel cascade mode
Convert a DMA cookie to an offset within a DMA handle
Deprecated Function |
Replacement |
---|---|
ddi_dma_addr_setup() |
ddi_dma_alloc_handle(), ddi_dma_addr_bind_handle() |
ddi_dma_buf_setup() |
ddi_dma_alloc_handle(), ddi_dma_buf_bind_handle() |
ddi_dma_curwin() |
ddi_dma_getwin() |
ddi_dma_free() |
ddi_dma_free_handle() |
ddi_dma_htoc() |
ddi_dma_addr_bind_handle(), ddi_dma_buf_bind_handle() |
ddi_dma_movwin() |
ddi_dma_getwin() |
ddi_dma_nextseg() |
ddi_dma_nextcookie() |
ddi_dma_segtocookie() |
ddi_dma_nextcookie() |
ddi_dma_setup() |
ddi_dma_alloc_handle(), ddi_dma_addr_bind_handle(), ddi_dma_buf_bind_handle() |
ddi_dmae_getlim() |
ddi_dmae_getattr() |
ddi_iopb_alloc() |
ddi_dma_mem_alloc() |
ddi_iopb_free() |
ddi_dma_mem_free() |
ddi_mem_alloc() |
ddi_dma_mem_alloc() |
ddi_mem_free() |
ddi_dma_mem_free() |
hat_getkpfnum() |
ddi_dma_addr_bind_handle(), ddi_dma_buf_bind_handle(), ddi_dma_nextcookie() |
The user space access functions are:
Copy data to a driver buffer
Copy data from a driver
Copy kernel data using a uio structure
Add character to a uio structure
Remove a character from a uio structure
Get minor device number.
Determine a data model type mismatch
Determine whether there is a need to translate M_IOCTL contents
Establish the handle to application data in a possibly differing data model
Establish the handle to application data in a possibly differing data model
Establish the handle to application data in a possibly differing data model
Establish the handle to application data in a possibly differing data model
Return the size of pointer in specified data model
Return the size of a structure in the specified data model
Return the size of a structure in the application data model
Return a pointer to the native mode instance of the structure
Return a pointer to the specified field of a structure
Return the specified field of a structure in the application data model
Return the specified pointer field of a structure in the application data model
Set a specified field of a structure in the application data model
Set a specified pointer field of a structure in the application data model
Deprecated Function |
Replacement |
---|---|
copyin() |
ddi_copyin() |
copyout() |
ddi_copyout() |
ddi_getminor() |
getminor() |
The user process event functions are:
Inform a process that an event has occurred
Get a handle on a process to signal
Release a handle on a process to signal
Send a signal to a process
The user process information functions are:
Return a pointer to the credential structure of the caller
Determine process credentials privilege
Return the process ID
Deprecated Functions |
Replacement |
---|---|
drv_getparm() |
ddi_get_pid(), ddi_get_cred() |
The user application kernel and device access functions are:
Return the number of register sets a device has
Return the size of a device's register
Set up a user mapping to device memory using the devmap framework
Export device memory to user space
Validate memory address translations
Invalidate memory address translations
Perform device context switching on a mapping
Set the timeout value for the context management callback
Default driver memory access function
Allocate page-aligned kernel memory
Free page-aligned kernel memory
Lock memory pages
Unlock memory pages
Setup I/O requests to application memory
Export kernel memory to user space
Determine data model type mismatch
Deprecated Function |
Replacement |
---|---|
ddi_mapdev() |
devmap_setup() |
ddi_mapdev_intercept() |
devmap_load() |
ddi_mapdev_nointercept() |
devmap_unload() |
ddi_mapdev_set_device_acc_attr() |
devmap() |
ddi_segmap() |
devmap() |
ddi_segmap_setup() |
devmap_setup() |
hat_getkpfnum() |
devmap() |
ddi_mmap_get_model() |
devmap() |
The time-related functions are:
Return the number of clock ticks since reboot
Return the current time in seconds
Issue nanosecond periodic timeout requests
Cancel nanosecond periodic timeout requests
Delay execution for a specified number of clock ticks
Convert clock ticks to microseconds
Convert microseconds to clock ticks
Busy-wait for specified interval
Get high-resolution time
Get high-resolution LWP virtual time
Execute a function after a specified length of time
Cancel the previous time out function call
ddi_get_lbolt(), ddi_get_time()
Deprecated Function |
Replacement |
---|---|
drv_getparm() |
ddi_get_lbolt(), ddi_get_time() |
The power management functions are:
Check if device loses power with DDI_SUSPEND
Report the power management capability of a PCI device
Mark a component as busy
Mark a component as idle
Raise the power level of a component
Lower the power level of a component
Notify the power management framework of an autonomous power level change
Device power cycle advisory check
Function Name |
Description |
---|---|
ddi_dev_is_needed() |
Inform the system that a device's component is required |
pm_create_components() |
Create power-manageable components |
pm_destroy_components() |
Destroy power-manageable components |
pm_get_normal_power() |
Get the normal power level of a device component |
pm_set_normal_power() |
Set the normal power level of a device component |
The fault management functions are:
Allocates and initializes resources based on declared fault management capabilities
Cleans up resources that were allocated for this device instance to support fault management capabilities declared to ddi_fm_init()
Returns the capability bit mask currently set for this device instance
Registers an error handler callback routine with the IO Fault Management framework
Removes an error handler callback routine that was registered with ddi_fm_handler_register()
Returns the error status for an access handle
Returns the error status for a DMA handle
Clears the error status for an access handle
Clears the error status for a DMA handle
Queues an encoded fault management error report name-value pair list for delivery to the Fault Manager daemon, fmd(1M)
Reports the impact of an error
Initializes support for error report generation and sets up the resources for subsequent accesses to PCI, PCI/X, or PCI Express configuration space
Releases any resources allocated and setup by pci_ereport_setup() for this device instance
Scans for and posts any PCI, PCI/X, or PCI Express bus errors
The kernel statistics (kstats) functions are:
Create and initialize a new kstat
Remove a kstat from the system
Add a fully initialized kstat to the system
Initialize a named kstat
Record a transaction migration from run queue to the wait queue
Record a transaction addition to the run queue
Record a transaction removal from the run queue
Record a transaction addition to the wait queue
Record a transaction removal from the wait queue
Record a transaction migration from the wait queue to the run queue
The kernel logging and printing functions are:
Display an error message
Announce a device
Submit messages to the log driver
Report a hardware failure
Display a SCSI request sense message
Display a SCSI-device-related message
Display a SCSI request sense message
The buffered I/O functions are:
Perform physical I/O
Perform asynchronous physical I/O
Prevent cancellation of an asynchronous I/O request
Limit the physio() buffer size
Suspend processes pending completion of block I/O
Release the buffer after buffer I/O transfer and notify blocked threads
Indicate the error in a buffer header
Return an I/O error
Allocate virtual address space
Deallocate virtual address space
Use a single-direction elevator seek strategy to sort for buffers
Get a raw buffer header
Free a raw buffer header
Return the size of a buffer structure
Initialize a buffer structure
Uninitialize a buffer structure
Reuse a private buffer header after I/O is complete
Clone another buffer
Check whether a buffer is modified
Erase the contents of a buffer
The virtual memory functions are:
Convert device bytes to pages (round down)
Convert device bytes to pages (round up)
Convert device pages to bytes
Convert size in bytes to size in pages (round down)
Convert size in bytes to size in pages (round up)
Convert size in pages to size in bytes
Deprecated Functions |
Replacement |
---|---|
hat_getkpfnum() |
devmap(), ddi_dma_*_bind_handle(), ddi_dma_nextcookie() |
The device ID functions are:
Allocate a device ID structure
Free a device ID structure
Register a device ID
Unregister a device ID
Compare two device IDs
Return the size of a device ID
Validate a device ID
Encode a device ID and minor_name into a null-terminated ASCII string; return a pointer to that string
Decode the device ID and minor_name from a previously encoded string; allocate and return pointers to the extracted parts
Free all strings returned by the ddi_devid_* functions
The SCSI functions are:
Probe a SCSI device
Free resources allocated during initial probing
Allocate an I/O buffer for SCSI DMA
Free a previously allocated SCSI DMA I/O buffer
Prepare a complete SCSI packet
Free an allocated SCSI packet and its DMA resource
Set up SCSI command descriptor block (CDB)
Start a SCSI command
Run a polled SCSI command
Get SCSI transport capability
Set SCSI transport capability
Synchronize CPU and I/O views of memory
Abort a SCSI command
Reset a SCSI bus or target
Notify the target driver of bus resets
Decode a SCSI command
Decode a SCSI peripheral device type
Decode a SCSI message
Decode a SCSI packet completion reason
Decode a SCSI sense key
Display a SCSI request sense message
Display a SCSI-device-related message
Display a SCSI request sense message
SCSI HBA system initialization routine
SCSI HBA system completion routine
SCSI HBA attach routine
SCSI HBA detach routine
Default SCSI HBA probe function
Allocate a transport structure
Free a transport structure
Allocate a scsi_pkt structure
Free a scsi_pkt structure
Return an index matching capability string
Deprecated Function |
Replacement |
---|---|
free_pktiopb() |
scsi_free_consistent_buf() |
get_pktiopb() |
scsi_alloc_consistent_buf() |
makecom_g0() |
scsi_setup_cdb() |
makecom_g0_s() |
scsi_setup_cdb() |
makecom_g1() |
scsi_setup_cdb() |
makecom_g5() |
scsi_setup_cdb() |
scsi_dmafree() |
scsi_destroy_pkt() |
scsi_dmaget() |
scsi_init_pkt() |
scsi_hba_attach() |
scsi_hba_attach_setup() |
scsi_pktalloc() |
scsi_init_pkt() |
scsi_pktfree() |
scsi_destroy_pkt() |
scsi_resalloc() |
scsi_init_pkt() |
scsi_resfree() |
scsi_destroy_pkt() |
scsi_slave() |
scsi_probe() |
scsi_unslave() |
scsi_unprobe() |
The resource map management functions are:
Allocate a resource map
Allocate a resource map, wait if necessary
Free a resource map
Allocate space from a resource map
Allocate space from a resource map, wait if necessary
Free space back into a resource map
Determine whether the system is in panic state
The utility functions are:
Zero return function
Error return function
Error return function for non-pollable devices
Expression verification
Copy data between address locations in the kernel
Clear memory for a given number of bytes
Compare two byte arrays
Find the first bit set in a long integer
Find the last bit set in a long integer
Swap bytes in 16-bit halfwords
Compare two null-terminated strings
Compare two null-terminated strings, with length limit
Determine the number of non-null bytes in a string
(Available starting with SXCE build 88) Determine the number of non-null bytes in a string, with length limit
Copy a string from one location to another
Copy a string from one location to another, with length limit
Find a character in a string
Format characters in memory
Convert an integer to a decimal string
Convert a decimal string to an integer
Return the larger of two integers
Return the lesser of two integers
Finds the next value in a variable argument list
Copies the state of a variable argument list
Deletes pointer to a variable argument list
Finds the pointer to the start of a variable argument list
This appendix provides information for device driver writers who are converting their device drivers to support the 64-bit kernel. It presents the differences between 32-bit and 64-bit device drivers and describes the steps to convert 32-bit device drivers to 64-bit. This information is specific to regular character and block device drivers only.
This appendix provides information on the following subjects:
For drivers that only need support for the 32-bit kernel, existing 32-bit device drivers will continue to work without recompilation. However, most device drivers require some changes to run correctly in the 64-bit kernel, and all device drivers require recompilation to create a 64-bit driver module. The information in this appendix will help you to enable drivers for 32-bit and 64-bit environments to be generated from common source code, thus increasing code portability and reducing the maintenance effort.
Before starting to modify a device driver for the 64-bit environment, you should understand how the 32-bit environment differs from the 64-bit environment. In particular, you must be familiar with the C language data type models ILP32 and LP64. See the following table.
Table C–1 Comparison of ILP32 and LP64 Data Types
C Type |
ILP32 |
LP64 |
---|---|---|
char |
8 |
8 |
short |
16 |
16 |
int |
32 |
32 |
long |
32 |
64 |
long long |
64 |
64 |
float |
32 |
32 |
double |
64 |
64 |
long double |
96 |
128 |
pointer |
32 |
64 |
The driver-specific issues due to the differences between ILP32 and LP64 are the subject of this appendix.
In addition to general code cleanup to support the data model changes for LP64, driver writers have to provide support for both 32-bit and 64-bit applications.
The ioctl(9E), devmap(9E), and mmap(9E) entry points enable data structures to be shared directly between applications and device drivers. If those data structures change size between the 32-bit and 64-bit environments, then the entry points must be modified so that the driver can determine whether the data model of the application is the same as that of the kernel. When the data models differ, data structures can be adjusted. See I/O Control Support for 64-Bit Capable Device Drivers, 32-bit and 64-bit Data Structure Macros, and Associating Kernel Memory With User Mappings.
In many drivers, only a few ioctls need this kind of handling. The other ioctls should work without change as long as these ioctls pass data structures that do not change in size.
The sections below provide information on converting drivers to run in a 64-bit environment. Driver writers might need to perform one or more of the following tasks:
Use fixed-width types for hardware registers.
Use fixed-width common access functions.
Check and extend use of derived types.
Check changed fields within DDI data structures.
Check changed arguments of DDI functions.
Modify the driver entry points that handle user data, where needed.
Check structures that use 64-bit long types on x86 platforms.
These steps are explained in detail below.
After each step is complete, fix all compiler warnings, and use lint to look for other problems. The SC5.0 (or newer) version of lint should be used with -Xarch=v9 and -errchk=longptr64 specified to find 64-bit problems.
Do not ignore compilation warnings during conversion for LP64. Warnings that were safe to ignore previously in the ILP32 environment might now indicate a more serious problem.
After all the steps are complete, compile and test the driver as both a 32-bit and 64-bit module.
Many device drivers that manipulate hardware devices use C data structures
to describe the layout of the hardware. In the LP64 data model, data structures
that use long
or unsigned long to define hardware registers are
almost certainly incorrect, because long
is now a 64-bit quantity.
Start by including <sys/inttypes.h>, and update this
class of data structure to use int32_t
or uint32_t
instead
of long
for 32-bit device data. This approach preserves the binary
layout of 32-bit data structures. For example, change:
struct device_regs { ulong_t addr; uint_t count; }; /* Only works for ILP32 compilation */
to:
struct device_regs { uint32_t addr; uint32_t count; }; /* Works for any data model */
The Oracle Solaris DDI allows device registers to be accessed by access functions for portability to multiple platforms. Previously, the DDI common access functions specified the size of data in terms of bytes, words, and so on. For example, ddi_getl(9F) is used to access 32-bit quantities. This function is not available in the 64-bit DDI environment, and has been replaced by versions of the function that specify the number of bits to be acted on.
These routines were added to the 32-bit kernel in the Solaris 2.6 operating environment, to enable their early adoption by driver writers. For example, to be portable to both 32-bit and 64-bit kernels, the driver must use ddi_get32(9F) to access 32-bit data rather than ddi_getl(9F).
All common access routines are replaced by their fixed-width equivalents. See the ddi_get8(9F), ddi_put8(9F), ddi_rep_get8(9F), and ddi_rep_put8(9F) man pages for details.
System-derived types, such as size_t
, should be used where
possible so that the resulting variables make sense when passed between functions.
The new derived types uintptr_t
or intptr_t
should
be used as the integral type for pointers.
Fixed-width integer types are useful for representing explicit sizes
of binary data structures or hardware registers, while fundamental C language
data types, such as int
, can still be used for loop counters
or file descriptors.
Some system-derived types represent 32-bit quantities on a 32-bit system
but represent 64-bit quantities on a 64-bit system. Derived types that change
size in this way include: clock_t
, daddr_t
, dev_t
, ino_t
, intptr_t
, off_t
, size_t
, ssize_t
, time_t
, uintptr_t
,
and timeout_id_t
.
When designing drivers that use these derived types, pay particular attention to the use of these types, particularly if the drivers are assigning these values to variables of another derived type, such as a fixed-width type.
The data types of some of the fields within DDI data structures, such as buf(9S), have been changed. Drivers that use these data structures should make sure that these fields are being used appropriately. The data structures and the fields that were changed in a significant way are listed below.
The fields listed below pertain to transfer size, which can now exceed more than 4 Gbytes.
size_t b_bcount; /* was type unsigned int */ size_t b_resid; /* was type unsigned int */ size_t b_bufsize; /* was type long */
The ddi_dma_attr(9S) structure defines attributes of the DMA engine and the device. Because these attributes specify register sizes, fixed-width data types have been used instead of fundamental types.
uint32_t dmac_address; /* was type unsigned long */ size_t dmac_size; /* was type u_int */
The ddi_dma_cookie(9S) structure contains a 32-bit DMA address, so a fixed-width
data type has been used to define the address. The size has been redefined
as size_t
.
uint_t sts_rqpkt_state; /* was type u_long */ uint_t sts_rqpkt_statistics; /* was type u_long */
These fields in the structure do not need to grow and have been redefined as 32-bit quantities.
uint_t pkt_flags; /* was type u_long */ int pkt_time; /* was type long */ ssize_t pkt_resid; /* was type long */ uint_t pkt_state; /* was type u_long */ uint_t pkt_statistics; /* was type u_long */
Because the pkt_flags
, pkt_state
, and pkt_statistics
fields in the scsi_pkt(9S) structure
do not need to grow, these fields have been redefined as 32-bit integers.
The data transfer size pkt_resid field does grow
and has been redefined as ssize_t
.
This section describes the DDI function argument data types that have been changed.
struct buf *getrbuf(int sleepflag);
In previous releases, sleepflag was defined as a type long
.
int drv_getparm(unsigned int parm, void *value_p);
In previous releases, value_p was defined as type unsigned long
. In the 64-bit kernel, drv_getparm(9F) can fetch both 32-bit and 64-bit quantities. The
interface does not define data types of these quantities, and simple programming
errors can occur.
The following new routines offer a safer alternative:
clock_t ddi_get_lbolt(void); time_t ddi_get_time(void); cred_t *ddi_get_cred(void); pid_t ddi_get_pid(void);
Driver writers are strongly urged to use these routines instead of drv_getparm(9F).
void delay(clock_t ticks); timeout_id_t timeout(void (*func)(void *), void *arg, clock_t ticks);
The ticks argument to the delay(9F) and timeout(9F) routines
has been changed from long
to clock_t
.
struct map *rmallocmap(size_t mapsize); struct map *rmallocmap_wait(size_t mapsize);
The mapsize argument
to the rmallocmap(9F) and rmallocmap_wait(9F) routines has been changed
from ulong_t
to size_t
.
struct buf *scsi_alloc_consistent_buf(struct scsi_address *ap, struct buf *bp, size_t datalen, uint_t bflags, int (*callback )(caddr_t), caddr_t arg);
In previous releases, datalen was defined as an int
and bflags was
defined as a ulong
.
int uiomove(caddr_t address, size_t nbytes, enum uio_rw rwflag, uio_t *uio_p);
The nbytes argument was defined
as a type long
, but because nbytes represents
a size in bytes, size_t
is more appropriate.
int cv_timedwait(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout); int cv_timedwait_sig(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout);
In previous releases, the timeout argument to
the cv_timedwait(9F) and cv_timedwait_sig(9F) routines was defined to be
of type long
. Because these routines represent time in ticks, clock_t
is more appropriate.
int ddi_device_copy(ddi_acc_handle_t src_handle, caddr_t src_addr, ssize_t src_advcnt, ddi_acc_handle_t dest_handle, caddr_t dest_addr, ssize_t dest_advcnt, size_t bytecount, uint_t dev_datasz);
The src_advcnt, dest_advcnt, dev_datasz arguments have changed type. These arguments were previously
defined as long
, long
, and ulong_t
respectively.
int ddi_device_zero(ddi_acc_handle_t handle, caddr_t dev_addr, size_t bytecount, ssize_t dev_advcnt, uint_t dev_datasz):
In previous releases, dev_advcnt was defined
as a type long
and dev_datasz as a ulong_t
.
int ddi_dma_mem_alloc(ddi_dma_handle_t handle, size_t length, ddi_device_acc_attr_t *accattrp, uint_t flags, int (*waitfp)(caddr_t), caddr_t arg, caddr_t *kaddrp, size_t *real_length, ddi_acc_handle_t *handlep);
In previous releases, length, flags, and real_length were defined with types uint_t
, ulong_t
, and uint_t *
.
If a device driver shares data structures
that contain long
s or pointers with a 32-bit application using ioctl(9E), devmap(9E), or mmap(9E), and the driver is recompiled
for a 64-bit kernel, the binary layout of data structures will be incompatible.
If a field is currently defined in terms of type long
and 64-bit
data items are not used, change the data structure to use data types that
remain as 32-bit quantities (int
and unsigned
int
). Otherwise, the driver needs to be aware of the different structure
shapes for ILP32 and LP64 and determine whether a model mismatch between the
application and the kernel has occurred.
To handle potential data model differences, the ioctl(), devmap(), and mmap() driver entry points, which interact directly with user applications, need to be written to determine whether the argument came from an application using the same data model as the kernel.
To determine whether a model mismatch exists between the application and the driver, the driver uses the FMODELS mask to determine the model type from the ioctl() mode argument. The following values are OR-ed into mode to identify the application data model:
The code examples in I/O Control Support for 64-Bit Capable Device Drivers show how this situation can be handled using ddi_model_convert_from(9F).
To enable a 64-bit driver and a 32-bit application to share memory, the binary layout generated by the 64-bit driver must be the same as the layout consumed by the 32-bit application. The mapped memory being exported to the application might need to contain data-model-dependent data structures.
Few memory-mapped devices face this problem because the device registers do not change size when the kernel data model changes. However, some pseudo-devices that export mappings to the user address space might want to export different data structures to ILP32 or LP64 applications. To determine whether a data model mismatch has occurred, devmap(9E) uses the model parameter to describe the data model expected by the application. The model parameter is set to one of the following values:
The model parameter can be passed untranslated to the ddi_model_convert_from(9F) routine or to STRUCT_INIT(). See 32-bit and 64-bit Data Structure Macros.
Because mmap(9E) does not have a parameter that can be used to pass data model information, the driver's mmap(9E) entry point can be written to use the new DDI function ddi_model_convert_from(9F). This function returns one of the following values to indicate the application's data type model:
DDI_FAILURE – Function was not called from mmap(9E)
As with ioctl() and devmap(), the model bits can be passed to ddi_model_convert_from(9F) to determine whether data conversion is necessary, or the model can be handed to STRUCT_INIT().
Alternatively, migrate the device driver to support the devmap(9E) entry point.
You should carefully check structures that use 64-bit long types, such as uint64_t, on the x86 platforms. The alignment and size can differ between compilation in 32-bit mode versus a 64-bit mode. Consider the following example.
#include <studio> #include <sys> struct myTestStructure { uint32_t my1stInteger; uint64_t my2ndInteger; }; main() { struct myTestStructure a; printf("sizeof myTestStructure is: %d\n", sizeof(a)); printf("offset to my2ndInteger is: %d\n", (uintptr_t)&a.bar - (uintptr_t)&a); }
On a 32-bit system, this example displays the following results:
sizeof myTestStructure is: 12 offset to my2ndInteger is: 4 |
Conversely, on a 64-bit system, this example displays the following results:
sizeof myTestStructure is: 16 offset to my2ndInteger is: 8 |
Thus, the 32-bit application and the 64-bit application view the structure differently. As a result, trying to make the same structure work in both a 32-bit and 64-bit environment can cause problems. This situation occurs often, particularly in situations where structures are passed into and out of the kernel through ioctl() calls.
Many ioctl(9E) operations are common to a class of device drivers. For example, most disk drivers implement many of the dkio(7I) family of ioctls. Many of these interfaces copy in or copy out data structures from the kernel, and some of these data structures have changed size in the LP64 data model. The following section lists the ioctlsthat now require explicit conversion in 64-bit driver ioctl routines for the dkio, fdio(7I), fbio(7I), cdio(7I), and mtio(7I) families of ioctls.
ioctl command |
Affected data structure |
Reference |
---|---|---|
DKIOCGAPART DKIOCSAPART |
dk_map dk_allmap | |
DKIOGVTOC DKIOSVTOC |
partition vtoc | |
FBIOPUTCMAP FBIOGETCMAP |
fbcmap | |
FBIOPUTCMAPI FBIOGETCMAPI |
fbcmap_i | |
FBIOCCURSOR FBIOSCURSOR |
fbcursor | |
CDROMREADMODE1 CDROMREADMODE2 |
cdrom_read | |
CDROMCDDA |
cdrom_cdda | |
CDROMCDXA |
cdrom_cdxa | |
CDROMSUBCODE |
cdrom_subcode | |
FDIOCMD |
fd_cmd | |
FDRAW |
fd_raw | |
MTIOCTOP |
mtop | |
MTIOCGET |
mtget | |
MTIOCGETDRIVETYPE |
mtdrivetype_request | |
USCSICMD |
uscsi_cmd |
The nblocks property is exported by each slice of a block device driver. This property contains the number of 512-byte blocks that each slice of the device can support. The nblocks property is defined as a signed 32-bit quantity, which limits the maximum size of a slice to 1 Tbyte.
Disk devices that provide more than 1 Tbyte of storage per disk must define the Nblocks property, which should still contain the number of 512 byte blocks that the device can support. However, Nblocks is a signed 64-bit quantity, which removes any practical limit on disk space.
The nblocks property is now deprecated. All disk devices should provide the Nblocks property.
Drivers for frame buffers that are used for the system console must provide interfaces to enable the system to display text on the console. The Oracle Solaris OS provides enhanced visual I/O interfaces to enable the kernel terminal emulator to display text directly on the console frame buffer. This appendix describes how to add the necessary interfaces to a frame buffer driver to enable the driver to interact with the Oracle Solaris kernel terminal emulator.
The role of the kernel terminal emulator is to render text onto the console frame buffer in the proper position and representation determined by the frame buffer's screen height, width, and pixel depth mode. The terminal emulator also drives scrolling, controls a software cursor, and interprets ANSI terminal escape sequences. The terminal emulator accesses the console frame buffer in either VGA text mode or pixel mode, depending upon the graphics card. To be used as a Oracle Solaris console frame buffer driver, your frame buffer driver must be compatible with the Oracle Solaris kernel terminal emulator. The target platform is the most significant factor that determines whether you need to modify your frame buffer driver to make your driver compatible with the Oracle Solaris kernel terminal emulator.
x86 platforms – Console frame buffer drivers do not need to be modified because x86 console frame buffer drivers already support the console frame buffer interfaces.
SPARC platforms – Console frame buffer drivers should use the interfaces described in this appendix to enable the driver to interact with the Oracle Solaris kernel terminal emulator.
On x86 platforms, the Oracle Solaris kernel terminal emulator module (tem) uses VGA text mode exclusively to interact with the vgatext module. The vgatext module uses industry standard VGA text mode to interact with x86 compatible frame buffer devices. Because the vgatext module already supports the console frame buffer interfaces, x86 frame buffer drivers are compatible with the kernel tem module. You do not need to add special interfaces to x86 frame buffer drivers.
The remainder of this appendix applies to SPARC platforms only.
SPARC frame buffer drivers typically do not operate in VGA text mode. SPARC frame buffer drivers typically are required to send pixel patterns that depict the text and images displayed. The kernel tem requires SPARC drivers to support specific interfaces to facilitate rendering data to the screen, perform scrolling, and display a text cursor. How the driver actually renders data sent from the tem onto the screen depends on the device. The driver typically draws the data into video memory according to the hardware and video mode.
The Oracle Solaris OS provides interfaces that enable the kernel terminal emulator to drive compatible console frame buffers directly. The advantages of converting a driver to be compatible with the kernel terminal emulator are:
Dramatically improved performance, particularly for scrolling
Enhanced ANSI text color capabilities
The ability to start a login session on the console frame buffer even when the system console stream is directed out the serial port
SPARC console frame buffer drivers are not required to be compatible with the kernel terminal emulator. If the console frame buffer driver is not compatible with the kernel terminal emulator, the system uses the FCode terminal emulator in the OpenBoot PROM.
The console frame buffer is identified through the EEPROM screen environment variable. The system determines whether the console frame buffer is compatible with the kernel terminal emulator module by checking whether the frame buffer driver exports the tem-support DDI property. If the tem-support property is exported, then the system issues the VIS_DEVINIT I/O control (ioctl) command to the frame buffer driver during system boot, while configuring the console. If the tem-support DDI property is exported and the VIS_DEVINIT ioctl command succeeds and returns a compatible version number to the tem, the system configures the system console to utilize that frame buffer driver through the kernel terminal emulator. See the ioctl(9E) man page for information about the I/O control driver entry point.
SPARC drivers that support the kernel terminal emulator should export the tem-support DDI property. This property indicates that the driver supports the kernel terminal emulator. If a frame buffer driver exports the tem-support DDI property, then that driver will be handled early in the boot process, while the console is being configured. If a frame buffer driver does not export the tem-support property, then that driver might not be handled early enough in the boot process.
When set to 1, this DDI property indicates that this driver is compatible with the console kernel frame buffer interface.
The kernel terminal emulator module interacts with the console frame buffer driver through two major interfaces:
Through ioctl interfaces during normal system operation
Through polled I/O interfaces during standalone mode
The following section provides detailed information.
The kernel terminal emulator interacts with the console frame buffer driver through two interfaces. During normal system activity (after a successful boot of the system), communication between the kernel terminal emulator and the console frame buffer driver is through ioctl interfaces. During standalone mode (before system boot or during debugging), communication between the kernel terminal emulator and the console frame buffer driver is through polled I/O interfaces. All activity between the kernel terminal emulator and the console frame buffer driver is initiated by the kernel terminal emulator, with the exception of a callback function used by the console frame buffer driver to notify the kernel terminal emulator of changes in the video mode.
The console visual I/O interfaces are documented in detail in the visual_io(7I) man page. For more information on the video mode change callback function, see Video Mode Change Callback Interface.
During normal system activity, the kernel terminal emulator communicates with the console frame buffer driver through the ioctl interfaces listed in the following table:
ioctl Name |
Corresponding Data Structure |
Description |
---|---|---|
VIS_DEVINIT |
vis_devinit |
Initializes the session between the terminal emulator module and the frame buffer. See VIS_DEVINIT. |
VIS_DEVFINI |
Not Applicable |
Terminates the session between the terminal emulator module and the
frame buffer. See |
VIS_CONSDISPLAY |
vis_consdisplay |
Displays pixels as a rectangle. See |
VIS_CONSCOPY |
vis_conscopy |
Copies a rectangle of pixels (scroll). See |
VIS_CONSCURSOR |
vis_conscursor |
Displays or hides a text cursor. See |
VIS_PUTCMAP |
vis_cmap |
Sends the terminal emulator module color map to the frame buffer driver.
See |
VIS_GETCMAP |
vis_cmap |
Reads the terminal emulator module color map from the frame buffer.
See |
The polled
I/O interfaces provide the same functionality as the VIS_CONSDISPLAY
, VIS_CONSCOPY
, and VIS_CONSCURSOR
ioctl interfaces. The polled I/O interfaces are called only when the
operating system is quiesced and in standalone mode.
See Implementing Polled I/O in Console Frame Buffer Drivers for more information.
While in standalone mode, the kernel terminal emulator communicates with the console frame buffer driver through the polled I/O interfaces listed in the following table:
Polled I/O Function |
Corresponding Data Structure |
Description |
---|---|---|
(*display)() |
vis_consdisplay |
Displays pixels as a rectangle. |
(*copy)() |
vis_conscopy |
Copies a rectangle of pixels (scroll). |
(*cursor)() |
vis_conscursor |
Displays or hides a text cursor. |
The console frame buffer driver and the kernel terminal emulator must be in agreement about the video mode at all times. Video mode includes the console screen height, width, and depth in pixels. Video mode also includes whether communication between the kernel terminal emulator and the console frame buffer is in VGA text mode or pixel mode.
In order for the console frame buffer driver to notify the kernel terminal emulator of changes in the video mode, the console frame buffer driver is initialized with the address of the (*modechg_cb)() kernel terminal emulator callback function described in the following table:
Callback Function |
Corresponding Data Structures |
Description |
---|---|---|
(*modechg_cb)() |
vis_modechg_arg vis_devinit |
Keep the terminal emulator module synchronized with the driver video mode (screen height, width, and pixel depth). |
Except for the video mode change callback, all activity between the driver and the kernel terminal emulator is initiated by the tem (terminal emulator module). This means that the tem issues all of the ioctl commands described in this document. The following sections provide implementation details for each ioctl command. For more information, see the visual_io(7I) man page and the /usr/include/sys/visual_io.h include file. See Video Mode Change Callback Interface for detailed information about the video mode change callback function.
Each ioctl command should determine whether the FKIOCTL is set in the ioctl flag argument and return EPERM if that bit is not set.
The VIS_DEVINIT
ioctl command
initializes the frame buffer driver as the system console device. This ioctl passes the address of a vis_devinit structure.
The tem first loads the address of its video mode change callback function into the modechg_cb field of the vis_devinit structure and loads its soft state into the modechg_arg field. The tem then issues the VIS_DEVINIT ioctl command. The frame buffer driver then initializes itself and returns a summary of its configuration back to the tem by setting the version, width, height, linebytes, depth, mode, and polledio fields in the vis_devinit structure. The vis_devinit structure is shown in the following code.
struct vis_devinit { /* * This set of fields are used as parameters passed from the * layered frame buffer driver to the terminal emulator. */ int version; /* Console IO interface rev */ screen_size_t width; /* Width of the device */ screen_size_t height; /* Height of the device */ screen_size_t linebytes; /* Bytes per scan line */ int depth; /* Device depth */ short mode; /* Display mode Mode */ struct vis_polledio *polledio; /* Polled output routines */ /* * The following fields are used as parameters passed from the * terminal emulator to the underlying frame buffer driver. */ vis_modechg_cb_t modechg_cb; /* Video mode change callback */ struct vis_modechg_arg *modechg_arg; /* Mode change cb arg */ };
To implement the VIS_DEVINIT
ioctl command in the console frame buffer driver, follow these general
steps:
Define a struct to contain the console-specific state. This structure is private to the console frame buffer driver. This structure is referred to as consinfo in this appendix. The consinfo structure contains information such as:
Current size of the blit buffer
Pointer to the blit buffer
Color map information
Driver rendering mode information such as line pitch
Background color
Video memory address
Terminal emulator callback address
Allocate memory:
Allocate a blit buffer large enough to store a reasonable
default sized rectangle of pixels at the highest video depth. Additional memory
can be allocated if an incoming request exceeds the size of the buffer. The
frame buffer driver's largest font is 12×22. Assuming DEFAULT_HEIGHT is 12, DEFAULT_WIDTH is 22, and the maximum
video depth is 32, the buffer size should be 8448 bytes (DEFAULT_HEIGHT
× DEFAULT_WIDTH
× 32).
Allocate a vis_polledio structure.
Allocate a buffer to hold a cursor. This buffer should be the size of the largest character. This buffer will not change size.
Obtain the video change callback address and callback context of the tem from modechg_cb and modechg_ctx and store this information in the consinfo structure.
Populate the vis_polledio structure with entry point addresses for the polled display, copy, and cursor functions.
Provide the appropriate information in the fields of the vis_devinit structure that was passed to the driver by the tem:
Set the version field to VIS_CONS_REV
, which is a constant defined in the /usr/include/sys/visual_io.h header file.
Set the mode field to VIS_PIXEL
.
Set the polledio field to the address of the vis_polledio structure.
Set the height field to the video mode height in pixels.
Set the width field to the video mode width in pixels.
Set the depth field to the frame buffer pixel depth in bytes (for example, a 32-bit pixel depth would be 4 bytes).
Set the linebytes field to the value of height × width × depth.
This information is sent from the driver to the tem by using the vis_devinit structure. This information tells the terminal emulator how to render information and pass it to the graphics driver.
Whenever the console frame buffer driver changes its video mode (specifically height, width, or depth), the driver must call the video mode change callback function of the tem to update the vis_devinit structure and to pass this structure back to the terminal emulator. The terminal emulator passes its mode change callback function address in the modechg_cb field of the vis_devinit structure. The mode change callback function has the following function signature:
typedef void (*vis_modechg_cb_t) (struct vis_modechg_arg *, struct vis_devinit *);
As shown in the preceding typedef, the mode change callback function takes two arguments. The first argument is the modechg_arg and the second argument is the vis_devinit structure. The modechg_arg is sent from the tem to the driver during the VIS_DEVINIT ioctl command initialization. The driver must send the modechg_arg back to the tem with each video mode change callback.
Initialize the context of the kernel console. Specific requirements vary depending upon the capability of the graphics device. This initialization might include such steps as setting the draw engine state, initializing the palette, or locating and mapping video memory or the rendering engine so that data can be blitted onto the screen.
Return the vis_devinit structure to the caller.
VIS_DEFINI
The VIS_DEFINI
ioctl command
releases the driver's console resources and finishes the session.
To implement the VIS_DEVFINI
ioctl command in the console frame buffer driver, follow these general
steps:
Reset the console frame buffer driver state.
Clear the polled I/O entry points and the kernel terminal emulator video change function callback address.
Release memory.
VIS_CONSDISPLAY
The VIS_CONSDISPLAY
ioctl command
displays a rectangle of pixels at a specified location. This display is also
referred to as blitting a rectangle. The vis_consdisplay structure contains the information necessary to render a rectangle
at the video depth that both the driver and the tem are
using. The vis_consdisplay structure is shown in
the following code.
struct vis_consdisplay { screen_pos_t row; /* Row (in pixels) to display data at */ screen_pos_t col; /* Col (in pixels) to display data at */ screen_size_t width; /* Width of data (in pixels) */ screen_size_t height; /* Height of data (in pixels) */ unsigned char *data; /* Address of pixels to display */ unsigned char fg_color; /* Foreground color */ unsigned char bg_color; /* Background color */ };
To implement the VIS_CONSDISPLAY
ioctl command in the console frame buffer driver, follow these general
steps:
Copy the vis_consdisplay structure.
Validate the display parameters. Return an error if any of the display parameters is out of range.
Calculate the size of the rectangle to be blitted into video
memory. Validate this size against the size of the blit buffer created during VIS_DEVINIT
. Allocate additional memory for the
blit buffer if necessary.
Retrieve the blit data. This data has been prepared by the kernel terminal emulator at the agreed upon pixel depth. That depth is the same pixel depth that was conveyed by the tem during VIS_DEVINIT. The pixel depth is updated whenever the device driver changes video modes through callback to the tem. Typical pixel depths are 8-bit color map indexed, and 32-bit TrueColor.
Invalidate any user context so that user applications cannot
simultaneously access the frame buffer hardware through user memory mappings.
This step is neither allowed nor necessary in polled I/O mode because user
applications are not running. Be sure to hold a lock so that users cannot
restore the mapping through a page fault until the VIS_CONSDISPLAY
ioctl completes.
Establish the driver-specific console rendering context.
If the frame buffer is running in 8-bit color indexed mode,
restore the kernel console color map that the tem set up
through a previous VIS_PUTCMAP
ioctl. A lazy color map loading scheme is recommended to optimize performance.
In a lazy scheme, the console frame buffer only restores
colors it has actually used since the VIS_DEVINIT ioctl was issued.
Display the data passed from the tem at the pixel coordinates sent by the tem. You might need to transform the RGB pixel data byte order.
VIS_CONSCOPY
The VIS_CONSCOPY
ioctl command
copies a rectangular region of pixels from one location to another location.
One use for this ioctl is to scroll.
To implement the VIS_CONSCOPY
ioctl command in the console frame buffer driver, follow these
general steps:
Copy the vis_conscopy structure. The vis_conscopy structure describes the source and target rectangle sizes and locations.
Validate the display parameters. Return an error if any of the display parameters is out of range.
Invalidate any user context so that user applications cannot
simultaneously access the frame buffer hardware through user memory mappings.
This step is neither allowed nor necessary in polled I/O mode because user
applications are not running. Be sure to hold a lock so that users cannot
restore the mapping through a page fault until the VIS_CONSDISPLAY
ioctl completes.
Call the function to copy the rectangle.
For optimal performance, use the rendering engine of the graphic device to implement the copy function. You need to decide how to do the context management within the driver to set up the rendering engine for best performance.
VIS_CONSCURSOR
The VIS_CONSCURSOR
ioctl command
displays or hides a cursor. The vis_conscursor structure
is shown in the following code.
struct vis_conscursor { screen_pos_t row; /* Row to display cursor (in pixels) */ screen_pos_t col; /* Col to display cursor (in pixels) */ screen_size_t width; /* Width of cursor (in pixels) */ screen_size_t height; /* Height of cursor (in pixels) */ color_t fg_color; /* Foreground color */ color_t bg_color; /* Background color */ short action; /* Show or Hide cursor */ };
To implement the VIS_CONSCOPY
ioctl command in the console frame buffer driver, follow these general
steps:
Copy the vis_conscursor structure from the kernel terminal emulator.
Validate the display parameters. Return an error if any of the display parameters are out of range.
Invalidate any user context so that user applications cannot
simultaneously access the frame buffer hardware through user memory mappings.
This step is neither allowed nor necessary in polled I/O mode because user
applications are not running. Be sure to hold a lock so that users cannot
restore the mapping through a page fault until the VIS_CONSDISPLAY
ioctl completes.
The terminal emulator can call the VIS_CONSCOPY
ioctl with one of the following two actions: SHOW_CURSOR
and HIDE_CURSOR
.
The following steps describe how to implement this functionality by reading
and writing video memory. You might also be able to use the rendering engine
to do this work. Whether you can use the rendering engine depends on the frame
buffer hardware.
Take these steps to implement the SHOW_CURSOR
functionality:
Save the pixels within the rectangle where the cursor will be drawn. These saved pixels will be needed to hide the cursor.
Scan all the pixels on the screen bounded by the rectangle where the cursor will be drawn. Within this rectangle, replace the pixels that match the specified cursor foreground color (fg_color) with white pixels. Replace the pixels that match the specified cursor background color (bg_color) with black pixels. The visual effect is of a black cursor over white text. This method works with any foreground and background color of text. Attempting to invert colors based upon color map position is not feasible. More sophisticated strategies, such as attempting color inversion using HSB coloring (Hue, Saturation, Brightness), are not necessary.
To implement the HIDE_CURSOR
functionality,
replace the pixels beneath the cursor rectangle with the pixels saved from
the previous SHOW_CURSOR
action.
VIS_PUTCMAP
The VIS_PUTCMAP
ioctl command
establishes the console color map. The terminal emulator calls this function
to set up the color map of the kernel. The vis_cmap structure
is shown in the following code. This structure only applies to 8-bit color
indexed mode.
struct vis_cmap { int index; /* Index into colormap to start updating */ int count; /* Number of entries to update */ unsigned char *red; /* List of red values */ unsigned char *green; /* List of green values */ unsigned char *blue; /* List of blue values */ };
The VIS_PUTCMAP
ioctl command
is similar to the FBIOPUTCMAP
command.
The VIS_PUTCMAP
command is specific
to the frame buffer terminal-emulator compatible console code.
VIS_GETCMAP
The terminal emulator calls the VIS_GETCMAP
ioctl command to retrieve the console color map.
The polled I/O interfaces are implemented as functions in the driver and are called directly by the kernel terminal emulator. The driver passes the address of its polled I/O entry points to the terminal emulator during the execution of the VIS_DEVINIT ioctl command. The VIS_DEVINIT command is initiated by the terminal emulator.
The vis_polledio structure is shown in the following code.
typedef void * vis_opaque_arg_t; struct vis_polledio { struct vis_polledio_arg *arg; void (*display)(vis_opaque_arg_t, struct vis_consdisplay *); void (*copy)(vis_opaque_arg_t, struct vis_conscopy *); void (*cursor)(vis_opaque_arg_t, struct vis_conscursor *); };
The polled I/O interfaces provide the same functionality as the VIS_CONSDISPLAY
, VIS_CONSCOPY
, and VIS_CONSCURSOR
ioctl interfaces. The polled
I/O interfaces should follow the same steps that are described above for the
respective ioctl commands. The polled I/O interfaces must
very strictly adhere to the additional restrictions that are described in
the remainder of this section.
The polled I/O interfaces are called only when the operating system is quiesced and in standalone mode. The system enters standalone mode whenever the user enters OpenBoot PROM or enters the kmdb debugger, or when the system panics. Only one CPU and one thread are active. All other CPUs and threads are stopped. Timesharing, DDI interrupts, and system services are turned off.
Standalone mode severely restricts driver functionality but simplifies driver synchronization requirements. For example, a user application cannot access the console frame buffer driver by way of the driver's memory mappings from within a polled I/O routine.
In standalone mode, the console frame buffer driver must not perform any of the following actions:
Wait for interrupts
Wait for mutexes
Allocate memory
Use DDI or LDI interfaces
Use system services
These restrictions are not difficult to obey since the polled I/O functions are relatively simple operations. For example, when working with the rendering engine, the console frame buffer driver can poll a bit in the device rather than wait for an interrupt. The driver can use pre-allocated memory to render blit data. DDI or LDI interfaces should not be needed.
When the driver-specific fbconfig() module causes a change in resolution or color depth, that fbconfig() module must send an ioctl to the frame buffer driver. This ioctl triggers the frame buffer driver to call the terminal emulator's mode change callback function with the new screen size and depth. The frame buffer driver and the terminal emulator must agree about the video mode at all times. When the frame buffer driver and the terminal emulator do not agree about the video mode, the information on the screen is illegible and meaningless.
When the X Window System exits to the command line, the frame buffer's DDX module must send an ioctl to the frame buffer driver. This ioctl triggers the frame buffer driver to call the terminal emulator's mode change callback function. This communication keeps the frame buffer driver and the terminal emulator in agreement about the video mode if the X Window System starts and then changes the video resolution before exiting. The frame buffer driver and the terminal emulator must agree about the video mode at all times. When the frame buffer driver and the terminal emulator do not agree about the video mode, the information on the screen is illegible and meaningless.
Debugging a console frame buffer driver on an active system can be problematic.
Errors that are encountered in the early stages of booting the system do not generate a core dump.
Error or informative messages might not be displayed correctly on the screen.
USB keyboard input might fail.
This section offers some suggestions to help you develop, test, and debug console frame buffer drivers.
To test the ioctl commands, create additional ioctl entry points that are callable from a user application. Be sure to copy in the arguments appropriately. Use the ddi_copyin(9F) and ddi_copyout(9F) routines to transfer data to and from user address space. Then write an application to validate rendering, scrolling, and cursor behavior. This way, these ioctl commands do not affect your console while you develop and test the commands.
To ensure that the ioctl commands are working correctly, boot the system and log in. Check whether you get expected behavior when you execute commands such as prstat(1M), ls(1), vi(1), and man(1).
Execute the following script to validate that ANSI color is working correctly:
#!/bin/bash printf "\n\n\n\e[37;40m Color List \e[m\n\n" printf "\e[30m Color 30 black\e[m\n" printf "\e[31m Color 31 red\e[m\n" printf "\e[32m Color 32 green\e[m\n" printf "\e[33m Color 33 yellow\e[m\n" printf "\e[34m Color 34 blue\e[m\n" printf "\e[35m Color 35 purple\e[m\n" printf "\e[36m Color 36 cyan\e[m\n" printf "\e[37m Color 37 white\e[m\n\n" printf "\e[40m Backlight 40 black \e[m\n" printf "\e[41m Backlight 41 red \e[m\n" printf "\e[34;42m Backlight 42 green \e[m\n" printf "\e[43m Backlight 43 yellow\e[m\n" printf "\e[37;44m Backlight 44 blue \e[m\n" printf "\e[45m Backlight 45 purple\e[m\n" printf "\e[30;46m Backlight 46 cyan \e[m\n" printf "\e[30;47m Backlight 47 white \e[m\n\n"
The polled I/O interfaces are only available under the following circumstances:
When you enter the OpenBoot PROM by using the L1+A keystroke sequence
When you boot the system with a standalone debugger such as kmdb(1)
When the system panics
The polled I/O interfaces only become available at a certain point in the boot process. Polled I/O requests issued from the OpenBoot PROM before the system is running are not rendered. Similarly, kmdb prompts issued before the console is configured are not rendered.
To test the polled I/O interfaces, enter the OpenBoot PROM by using the L1+A keystroke sequence. To validate that the polled I/O interfaces are being used, type the following command at the OpenBoot PROM ok prompt:
ok 1b emit ." [32m This is a test" 1b emit ." [m" |
The polled I/O interfaces are working properly if the following statements are true:
The result of the above command is that the phrase This is a test is displayed in green.
The OpenBoot PROM continues to function correctly.
Scrolling performs as expected.
The cursor displays correctly.
The system can be reentered and continued repeatedly.
To determine whether the video mode change callback function is working properly, log in to the system and use fbconfig(1M) to change the resolution and depth of the frame buffer several times. If the console continues to display text properly, the video mode change callback function is working correctly. The kernel terminal emulator might adjust the font size to accommodate different screen sizes, but that is not significant to the console frame buffer driver.
To determine whether the X Window System and the console frame buffer driver interact correctly, switch between the X Window System and the command line several times while modifying the X Window System's video resolution and the command line resolution in different ways. If the X Window System exits and the console characters are not displayed correctly, either the X Window System did not notify the driver console code that the video mode changed or the driver did not call the kernel terminal emulator's video mode change callback function.
During boot, the system sends messages to /var/adm/messages if the system fails to locate or successfully load a kernel terminal emulator compatible frame buffer driver. To monitor these messages, type the following command in a separate window:
% tail -f /var/adm/messages |
To avoid problems with USB while debugging the driver, change the EEPROM input-device NVRAM configuration parameter to use a serial port instead of the keyboard. See the eeprom(1M) man page for more information about this parameter.