Part IV Appendixes
The appendixes provide the following background material:
Appendix A Hardware Overview
This appendix discusses general issues about hardware that is capable
of supporting the Solaris OS. The discussion includes the processor, bus architectures,
and memory models that are supported by the Solaris OS. Various device issues
and the PROM used in Sun platforms are also covered.
Note –
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 Solaris
DDI/DKI interfaces.
This appendix provides information on the following subjects:
SPARC Processor Issues
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.
Note –
Drivers should never perform floating-point operations, because
these operations are not supported in the kernel.
SPARC Data Alignment
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.
Member Alignment in SPARC Structures
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.
SPARC Byte Ordering
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 Register Windows
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.
SPARC Multiply and Divide Instructions
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.
x86 Processor Issues
Data types have
no alignment restrictions. However, extra memory cycles might be required
for the x86 processor to properly handle misaligned data transfers.
Note –
Drivers should not perform floating-point operations, as these
operations are not supported in the kernel.
x86 Byte Ordering
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.
x86 Architecture Manuals
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/.
Endianness
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.
Figure A–1 Byte Ordering Required for Host Bus Dependency
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.
Figure A–2 Data Ordering Host Bus Dependency
Store Buffers
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.
System Memory Model
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).
Total Store Ordering (TSO)
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.
Partial Store Ordering (PSO)
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.
Bus Architectures
This section describes device identification, device addressing,
and interrupts.
Device Identification
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.
Supported Interrupt Types
The Solaris platform supports both polling and vectored interrupts.
The Solaris DDI/DKI interrupt model is the same for both types of interrupts.
See Chapter 8, Interrupt Handlers for
more information about interrupt handling.
Bus Specifics
This section covers addressing and device configuration issues specific
to the buses that the Solaris platform supports.
PCI Local Bus
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.
Figure A–3 Machine Block Diagram
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.
PCI Address Domain
The PCI address domain consists of three distinct address spaces: configuration,
memory, and I/O space.
PCI Configuration Address 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.
PCI Configuration Base Address Registers
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.
Figure A–4 Base Address Registers for
Memory and I/O
PCI Memory Address Space
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 I/O Address Space
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.
PCI Hardware Configuration Files
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.
PCI Express
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/
SBus
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.
SBus Physical Address Space
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
|
Physical SBus Addresses
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.
Note –
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.
SBus Hardware Configuration Files
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.
Device Issues
This section describes issues with special devices.
Timing-Critical Sections
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.
Delays
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.
Internal Sequencing Logic
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.
Interrupt Issues
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.
PROM on SPARC Machines
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 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 Solaris OS and do not become active until the 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
Open Boot PROM 3
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 Sun4UTM architecture. Other architectures
might require different commands to perform actions.
Note –
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.
Forth Commands
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.
To add the two top values on the stack, use the + operator.
The result remains on the stack. The stack is shown with the .s word.
The default base is hexadecimal. The hex and decimal words can be used to switch bases.
See the Forth User's Guide for more information.
Walking the PROMs Device Tree
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.
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:
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.
Mapping the Device
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
|
Reading and Writing
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.
A suffix of @ indicates a read operation. The read
operation takes the address off the stack.
A suffix of ? is used to display the value without
affecting the stack.
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
|
Appendix B Summary of Solaris DDI/DKI
Services
This appendix discusses the interfaces provided by the 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:
This appendix does not discuss STREAMS interfaces; to learn more about
network drivers, see the STREAMS Programming Guide.
Module Functions
The module functions are:
-
mod_info
-
Query a loadable module
-
mod_install
-
Add a loadable module
-
mod_remove
-
Remove a loadable module
Device Information Tree Node (dev_info_t)
Functions
The device information tree node functions are:
-
ddi_binding_name()
-
Return driver binding name
-
ddi_dev_is_sid()
-
Tell whether a device is self-identifying
-
ddi_driver_major()
-
Return driver major device number
-
ddi_driver_name()
-
Return normalized driver name
-
ddi_node_name()
-
Return the devinfo node name
-
ddi_get_devstate()
-
Check device state
-
ddi_get_instance()
-
Get device instance number
-
ddi_get_name()
-
Return driver binding name
-
ddi_get_parent()
-
Find the parent of a device information structure
-
ddi_root_node()
-
Get the root of the dev_info tree
Device (dev_t) Functions
The device functions are:
-
ddi_create_minor_node()
-
Create a minor node for a device
-
ddi_getiminor()
-
Get kernel internal minor number from an external dev_t
-
ddi_remove_minor_node()
-
Remove a minor mode for a device
-
getmajor()
-
Get major device number
-
getminor()
-
Get minor device number
-
makedevice()
-
Make device number from major and minor numbers
Property Functions
The property functions are:
-
ddi_prop_exists()
-
Check for the existence of a property
-
ddi_prop_free()
-
Free resources consumed by property lookup
-
ddi_prop_get_int()
-
Look up integer property
-
ddi_prop_get_int64()
-
Look up 64-bit integer property
-
ddi_prop_lookup_byte_array()
-
Look up byte array property
-
ddi_prop_lookup_int_array()
-
Look up integer array property
-
ddi_prop_lookup_int64_array()
-
Look up 64-bit integer array property
-
ddi_prop_lookup_string()
-
Look up string property
-
ddi_prop_lookup_string_array()
-
Look up string array property
-
ddi_prop_remove()
-
Remove a property of a device
-
ddi_prop_remove_all()
-
Remove all properties of a device
-
ddi_prop_undefine()
-
Hide a property of a device
-
ddi_prop_update_byte_array()
-
Create or update byte array property
-
ddi_prop_update_int()
-
Create or update integer property
-
ddi_prop_update_int64()
-
Create or update 64-bit integer property
-
ddi_prop_update_int_array()
-
Create or update integer array property
-
ddi_prop_update_int64_array()
-
Create or update 64-bit integer array property
-
ddi_prop_update_string()
-
Create or update string property
-
ddi_prop_update_string_array()
-
Create or update string array property
Table B–1 Deprecated Property Functions
|
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()
|
Device Software State Functions
The device software state functions are:
-
ddi_get_driver_private()
-
Get the address of the device's private data area
-
ddi_get_soft_state()
-
Get pointer to instance soft-state structure
-
ddi_set_driver_private()
-
Set the address of the device's private data area
-
ddi_soft_state_fini()
-
Destroy driver soft-state structure
-
ddi_soft_state_free()
-
Free instance soft-state structure
-
ddi_soft_state_init()
-
Initialize driver soft-state structure
-
ddi_soft_state_zalloc()
-
Allocate instance soft-state structure
Memory Allocation and Deallocation Functions
The memory allocation and deallocation functions are:
-
kmem_alloc()
-
Allocate kernel memory
-
kmem_free()
-
Free kernel memory
-
kmem_zalloc()
-
Allocate zero-filled kernel memory
The
following functions allocate and free memory intended to be used for DMA.
See Direct Memory Access (DMA) Functions.
-
ddi_dma_mem_alloc()
-
Allocate memory for DMA transfer
-
ddi_dma_mem_free()
-
Free previously allocated DMA memory
The following functions allocate and free memory intended to be exported
to user space. See User Space Access Functions.
-
ddi_umem_alloc()
-
Allocate page-aligned kernel memory
-
ddi_umem_free()
-
Free page-aligned kernel memory
Table B–2 Deprecated Memory Allocation and Deallocation
Functions
|
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()
|
Kernel Thread Control and Synchronization
Functions
The kernel thread control and synchronization functions are:
-
cv_broadcast()
-
Wake up all waiting threads
-
cv_destroy()
-
Free an allocated condition variable
-
cv_init()
-
Allocate a condition variable
-
cv_signal()
-
Wake up one waiting thread
-
cv_timedwait()
-
Await an event with timeout
-
cv_timedwait_sig()
-
Await an event or signal with timeout
-
cv_wait()
-
Await an event
-
cv_wait_sig()
-
Await an event or signal
-
ddi_can_receive_sig()
-
Determine whether the current thread can receive a signal
-
ddi_enter_critical()
-
Enter a critical region of control
-
ddi_exit_critical()
-
Exit a critical region of control
-
mutex_destroy()
-
Destroy mutual exclusion lock
-
mutex_enter()
-
Acquire mutual exclusion lock
-
mutex_exit()
-
Release mutual exclusion lock
-
mutex_init()
-
Initialize mutual exclusion lock
-
mutex_owned()
-
Determine whether current thread is holding mutual exclusion
lock
-
mutex_tryenter()
-
Attempt to acquire mutual exclusion lock without waiting
-
rw_destroy()
-
Destroy a readers/writer lock
-
rw_downgrade()
-
Downgrade a readers/writer lock holding from writer to reader
-
rw_enter()
-
Acquire a readers/writer lock
-
rw_exit()
-
Release a readers/writer lock
-
rw_init()
-
Initialize a readers/writer lock
-
rw_read_locked()
-
Determine whether readers/writer lock is held for read or
write
-
rw_tryenter()
-
Attempt to acquire a readers/writer lock without waiting
-
rw_tryupgrade()
-
Attempt to upgrade readers/writer lock holding from reader
to writer
-
sema_destroy()
-
Destroy a semaphore
-
sema_init()
-
Initialize a semaphore
-
sema_p()
-
Decrement semaphore and possibly block
-
sema_p_sig()
-
Decrement semaphore but do not block if signal is pending
-
sema_tryp()
-
Attempt to decrement semaphore but do not block
-
sema_v()
-
Increment semaphore and possibly unblock waiter
Task Queue Management Functions
The task queue management functions are listed below. See the taskq(9F) man page for more information
about these interfaces.
-
ddi_taskq_create()
-
Create a task queue
-
ddi_taskq_destroy()
-
Destroy a task queue
-
ddi_taskq_dispatch()
-
Add a task to a task queue
-
ddi_taskq_wait()
-
Wait for pending tasks to complete
-
ddi_taskq_suspend()
-
Suspend a task queue
-
ddi_taskq_suspended()
-
Check whether a task queue is suspended
-
ddi_taskq_resume()
-
Resume a suspended task queue
Interrupt Functions
The interrupt functions are:
-
ddi_intr_add_handler(9F)
-
Adds an interrupt handler.
-
ddi_intr_add_softint(9F)
-
Adds a soft interrupt handler.
-
ddi_intr_alloc(9F)
-
Allocates system resources and interrupt vectors for the specified
type of interrupt.
-
ddi_intr_block_disable(9F)
-
Disables the specified range of interrupts. For MSI only.
-
ddi_intr_block_enable(9F)
-
Enables the specified range of interrupts. For MSI only.
-
ddi_intr_clr_mask(9F)
-
Clears an interrupt mask if the specified
interrupt is enabled.
-
ddi_intr_disable(9F)
-
Disables the specified interrupt.
-
ddi_intr_dup_handler(9F)
-
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.
-
ddi_intr_enable(9F)
-
Enables the specified interrupt.
-
ddi_intr_free(9F)
-
Releases the system resources and interrupt vectors for a
specified interrupt handle.
-
ddi_intr_get_cap(9F)
-
Returns interrupt capability flags for the specified interrupt.
-
ddi_intr_get_hilevel_pri(9F)
-
Returns the minimum priority level for a high-level interrupt.
-
ddi_intr_get_navail(9F)
-
Returns the number of interrupts available for a particular
hardware device and given interrupt type.
-
ddi_intr_get_nintrs(9F)
-
Get the number of interrupts that the device supports for
the given interrupt type.
-
ddi_intr_get_pending(9F)
-
Read the interrupt pending bit if one is supported by either
the host bridge or the device.
-
ddi_intr_get_pri(9F)
-
Returns the current software priority setting for the specified
interrupt.
-
ddi_intr_get_softint_pri(9F)
-
Returns the soft interrupt priority for the specified interrupt.
-
ddi_intr_get_supported_types(9F)
-
Returns the hardware interrupt types that are supported by
both the device and the host.
-
ddi_intr_remove_handler(9F)
-
Removes the specified interrupt handler.
-
ddi_intr_remove_softint(9F)
-
Remove the specified soft interrupt handler.
-
ddi_intr_set_cap(9F)
-
Sets the DDI_INTR_FLAG_LEVEL or DDI_INTR_FLAG_EDGE flag for
the specified interrupt.
-
ddi_intr_set_mask(9F)
-
Sets an interrupt
mask if the specified interrupt is enabled.
-
ddi_intr_set_pri(9F)
-
Sets the interrupt priority level for the specified interrupt.
-
ddi_intr_set_softint_pri(9F)
-
Changes the relative soft interrupt priority for the specified
soft interrupt.
-
ddi_intr_trigger_softint(9F)
-
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
Programmed I/O Functions
The programmed I/O functions are:
-
ddi_dev_nregs()
-
Return the number of register sets a device has
-
ddi_dev_regsize()
-
Return the size of a device's register
-
ddi_regs_map_setup()
-
Set up a mapping for a register address space
-
ddi_regs_map_free()
-
Free a previously mapped register address space
-
ddi_device_copy()
-
Copy data from one device register to another device register
-
ddi_device_zero()
-
Zero fill the device
-
ddi_check_acc_handle()
-
Check data access handle
-
ddi_get8()
-
Read 8-bit data from mapped memory, device register, or DMA
memory
-
ddi_get16()
-
Read 16-bit data from mapped memory, device register, or DMA
memory
-
ddi_get32()
-
Read 32-bit data from mapped memory, device register, or DMA
memory
-
ddi_get64()
-
Read 64-bit data from mapped memory, device register, or DMA
memory
-
ddi_put8()
-
Write 8-bit data to mapped memory, device register, or DMA
memory
-
ddi_put16()
-
Write 16-bit data to mapped memory, device register, or DMA
memory
-
ddi_put32()
-
Write 32-bit data to mapped memory, device register, or DMA
memory
-
ddi_put64()
-
Write 64-bit data to mapped memory, device register, or DMA
memory
-
ddi_rep_get8()
-
Read multiple 8-bit data from mapped memory, device register,
or DMA memory
-
ddi_rep_get16()
-
Read multiple 16-bit data from mapped memory, device register,
or DMA memory
-
ddi_rep_get32()
-
Read multiple 32-bit data from mapped memory, device register,
or DMA memory
-
ddi_rep_get64()
-
Read multiple 64-bit data from mapped memory, device register,
or DMA memory
-
ddi_rep_put8()
-
Write multiple 8-bit data to mapped memory, device register,
or DMA memory
-
ddi_rep_put16()
-
Write multiple 16-bit data to mapped memory, device register,
or DMA memory
-
ddi_rep_put32()
-
Write multiple 32-bit data to mapped memory, device register,
or DMA memory
-
ddi_rep_put64()
-
Write multiple 64-bit data to mapped memory, device register,
or DMA memory
-
ddi_peek8()
-
Cautiously read an 8-bit value from a location
-
ddi_peek16()
-
Cautiously read a 16-bit value from a location
-
ddi_peek32()
-
Cautiously read a 32-bit value from a location
-
ddi_peek64()
-
Cautiously read a 64-bit value from a location
-
ddi_poke8()
-
Cautiously write an 8-bit value to a location
-
ddi_poke16()
-
Cautiously write a 16-bit value to a location
-
ddi_poke32()
-
Cautiously write a 32-bit value to a location
-
ddi_poke64()
-
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.
-
ddi_io_get8()
-
Read 8-bit data from a mapped device register in I/O space
-
ddi_io_get16()
-
Read 16-bit data from a mapped device register in I/O space
-
ddi_io_get32()
-
Read 32-bit data from a mapped device register in I/O space
-
ddi_io_put8()
-
Write 8-bit data to a mapped device register in I/O space
-
ddi_io_put16()
-
Write 16-bit data to a mapped device register in I/O space
-
ddi_io_put32()
-
Write 32-bit data to a mapped device register in I/O space
-
ddi_io_rep_get8()
-
Read multiple 8-bit data from a mapped device register in
I/O space
-
ddi_io_rep_get16()
-
Read multiple 16-bit data from a mapped device register in
I/O space
-
ddi_io_rep_get32()
-
Read multiple 32-bit data from a mapped device register in
I/O space
-
ddi_io_rep_put8()
-
Write multiple 8-bit data to a mapped device register in I/O
space
-
ddi_io_rep_put16()
-
Write multiple 16-bit data to a mapped device register in
I/O space
-
ddi_io_rep_put32()
-
Write multiple 32-bit data to a mapped device register in
I/O space
-
ddi_mem_get8()
-
Read 8-bit data from a mapped device in memory space or DMA
memory
-
ddi_mem_get16()
-
Read 16-bit data from a mapped device in memory space or DMA
memory
-
ddi_mem_get32()
-
Read 32-bit data from a mapped device in memory space or DMA
memory
-
ddi_mem_get64()
-
Read 64-bit data from a mapped device in memory space or DMA
memory
-
ddi_mem_put8()
-
Write 8-bit data to a mapped device in memory space or DMA
memory
-
ddi_mem_put16()
-
Write 16-bit data to a mapped device in memory space or DMA
memory
-
ddi_mem_put32()
-
Write 32-bit data to a mapped device in memory space or DMA
memory
-
ddi_mem_put64()
-
Write 64-bit data to a mapped device in memory space or DMA
memory
-
ddi_mem_rep_get8()
-
Read multiple 8-bit data from a mapped device in memory space
or DMA memory
-
ddi_mem_rep_get16()
-
Read multiple 16-bit data from a mapped device in memory space
or DMA memory
-
ddi_mem_rep_get32()
-
Read multiple 32-bit data from a mapped device in memory space
or DMA memory
-
ddi_mem_rep_get64()
-
Read multiple 64-bit data from a mapped device in memory space
or DMA memory
-
ddi_mem_rep_put8()
-
Write multiple 8-bit data to a mapped device in memory space
or DMA memory
-
ddi_mem_rep_put16()
-
Write multiple 16-bit data to a mapped device in memory space
or DMA memory
-
ddi_mem_rep_put32()
-
Write multiple 32-bit data to a mapped device in memory space
or DMA memory
-
ddi_mem_rep_put64()
-
Write multiple 64-bit data to a mapped device in memory space
or DMA memory
-
pci_config_setup()
-
Set up access to PCI Local Bus Configuration space
-
pci_config_teardown()
-
Tear down access to PCI Local Bus Configuration space
-
pci_config_get8()
-
Read 8-bit data from the PCI Local Bus Configuration space
-
pci_config_get16()
-
Read 16-bit data from the PCI Local Bus Configuration space
-
pci_config_get32()
-
Read 32-bit data from the PCI Local Bus Configuration space
-
pci_config_get64()
-
Read 64-bit data from the PCI Local Bus Configuration space
-
pci_config_put8()
-
Write 8-bit data to the PCI Local Bus Configuration space
-
pci_config_put16()
-
Write 16-bit data to the PCI Local Bus Configuration space
-
pci_config_put32()
-
Write 32-bit data to the PCI Local Bus Configuration space
-
pci_config_put64()
-
Write 64-bit data to the PCI Local Bus Configuration space
Table B–4 Deprecated Programmed I/O Functions
|
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()
|
Direct Memory Access (DMA) Functions
The DMA functions are:
-
ddi_dma_alloc_handle()
-
Allocate a DMA handle
-
ddi_dma_free_handle()
-
Free a DMA handle
-
ddi_dma_mem_alloc()
-
Allocate memory for a DMA transfer
-
ddi_dma_mem_free()
-
Free previously allocated DMA memory
-
ddi_dma_addr_bind_handle()
-
Bind an address to a DMA handle
-
ddi_dma_buf_bind_handle()
-
Bind a system buffer to a DMA handle
-
ddi_dma_unbind_handle()
-
Unbind the address in a DMA handle
-
ddi_dma_nextcookie()
-
Retrieve the subsequent DMA cookie
-
ddi_dma_getwin()
-
Activate a new DMA window
-
ddi_dma_numwin()
-
Retrieve number of DMA windows
-
ddi_dma_sync()
-
Synchronize CPU and I/O views of memory
-
ddi_check_dma_handle()
-
Check a DMA handle
-
ddi_dma_set_sbus64()
-
Allow 64-bit transfers on SBus
-
ddi_slaveonly()
-
Report whether a device is installed in a slave access-only
location
-
ddi_iomin()
-
Find the minimum alignment and transfer size for DMA
-
ddi_dma_burstsizes()
-
Find out the allowed burst sizes for a DMA mapping
-
ddi_dma_devalign()
-
Find DMA mapping alignment and minimum transfer size
-
ddi_dmae_alloc()
-
Acquire a DMA channel
-
ddi_dmae_release()
-
Release a DMA channel
-
ddi_dmae_getattr()
-
Get the DMA engine attributes
-
ddi_dmae_prog()
-
Program a DMA channel
-
ddi_dmae_stop()
-
Terminate a DMA engine operation
-
ddi_dmae_disable()
-
Disable a DMA channel
-
ddi_dmae_enable()
-
Enable a DMA channel
-
ddi_dmae_getcnt()
-
Get the remaining DMA engine count
-
ddi_dmae_1stparty()
-
Configure the DMA channel cascade mode
-
ddi_dma_coff()
-
Convert a DMA cookie to an offset within a DMA handle
Table B–5 Deprecated Direct Memory Access (DMA)
Functions
|
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()
|
User Space Access Functions
The user space access functions are:
-
ddi_copyin()
-
Copy data to a driver buffer
-
ddi_copyout()
-
Copy data from a driver
-
uiomove()
-
Copy kernel data using a uio structure
-
ureadc()
-
Add character to a uio structure
-
uwritec()
-
Remove a character from a uio structure
-
getminor()
-
Get minor device number.
-
ddi_model_convert_from()
-
Determine a data model type mismatch
-
IOC_CONVERT_FROM()
-
Determine whether there is a need to translate M_IOCTL contents
-
STRUCT_DECL()
-
Establish the handle to application data in a possibly differing
data model
-
STRUCT_HANDLE()
-
Establish the handle to application data in a possibly differing
data model
-
STRUCT_INIT()
-
Establish the handle to application data in a possibly differing
data model
-
STRUCT_SET_HANDLE()
-
Establish the handle to application data in a possibly differing
data model
-
SIZEOF_PTR()
-
Return the size of pointer in specified data model
-
SIZEOF_STRUCT()
-
Return the size of a structure in the specified data model
-
STRUCT_SIZE()
-
Return the size of a structure in the application data model
-
STRUCT_BUF()
-
Return a pointer to the native mode instance of the structure
-
STRUCT_FADDR()
-
Return a pointer to the specified field of a structure
-
STRUCT_FGET()
-
Return the specified field of a structure in the application
data model
-
STRUCT_FGETP()
-
Return the specified pointer field of a structure in the application
data model
-
STRUCT_FSET()
-
Set a specified field of a structure in the application data
model
-
STRUCT_FSETP()
-
Set a specified pointer field of a structure in the application
data model
Table B–6 Deprecated User Space Access Functions
|
Deprecated Function
|
Replacement
|
|
copyin()
|
ddi_copyin()
|
|
copyout()
|
ddi_copyout()
|
|
ddi_getminor()
|
getminor()
|
User Process Event Functions
The user process event functions are:
-
pollwakeup()
-
Inform a process that an event has occurred
-
proc_ref()
-
Get a handle on a process to signal
-
proc_unref()
-
Release a handle on a process to signal
-
proc_signal()
-
Send a signal to a process
User Process Information Functions
The user process information functions are:
-
ddi_get_cred()
-
Return a pointer to the credential structure of the caller
-
drv_priv()
-
Determine process credentials privilege
-
ddi_get_pid()
-
Return the process ID
Table B–7 Deprecated User Process Information
Functions
|
Deprecated Functions
|
Replacement
|
|
drv_getparm()
|
ddi_get_pid(), ddi_get_cred()
|
User Application Kernel and Device Access
Functions
The user application kernel and device access functions are:
-
ddi_dev_nregs()
-
Return the number of register sets a device has
-
ddi_dev_regsize()
-
Return the size of a device's register
-
ddi_devmap_segmap(), devmap_setup()
-
Set up a user mapping to device memory using the devmap framework
-
devmap_devmem_setup()
-
Export device memory to user space
-
devmap_load()
-
Validate memory address translations
-
devmap_unload()
-
Invalidate memory address translations
-
devmap_do_ctxmgt()
-
Perform device context switching on a mapping
-
devmap_set_ctx_timeout()
-
Set the timeout value for the context management callback
-
devmap_default_access()
-
Default driver memory access function
-
ddi_umem_alloc()
-
Allocate page-aligned kernel memory
-
ddi_umem_free()
-
Free page-aligned kernel memory
-
ddi_umem_lock()
-
Lock memory pages
-
ddi_umem_unlock()
-
Unlock memory pages
-
ddi_umem_iosetup()
-
Setup I/O requests to application memory
-
devmap_umem_setup()
-
Export kernel memory to user space
-
ddi_model_convert_from()
-
Determine data model type mismatch
Table B–8 Deprecated User Application Kernel
and Device Access Functions
|
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()
|
Time-Related Functions
The time-related functions are:
-
ddi_get_lbolt()
-
Return the number of clock ticks since reboot
-
ddi_get_time()
-
Return the current time in seconds
-
ddi_periodic_add()
-
Issue nanosecond periodic timeout requests
-
ddi_periodic_delete()
-
Cancel nanosecond periodic timeout requests
-
delay()
-
Delay execution for a specified number of clock ticks
-
drv_hztousec()
-
Convert clock ticks to microseconds
-
drv_usectohz()
-
Convert microseconds to clock ticks
-
drv_usecwait()
-
Busy-wait for specified interval
-
gethrtime()
-
Get high-resolution time
-
gethrvtime()
-
Get high-resolution LWP virtual time
-
timeout()
-
Execute a function after a specified length of time
-
untimeout()
-
Cancel the previous time out function call
-
drv_getparm()
-
ddi_get_lbolt(), ddi_get_time()
Table B–9 Deprecated Time-Related Functions
|
Deprecated Function
|
Replacement
|
|
drv_getparm()
|
ddi_get_lbolt(), ddi_get_time()
|
Power Management Functions
The power management functions are:
-
ddi_removing_power()
-
Check if device loses power with DDI_SUSPEND
-
pci_report_pmcap()
-
Report the power management capability of a PCI device
-
pm_busy_component()
-
Mark a component as busy
-
pm_idle_component()
-
Mark a component as idle
-
pm_raise_power()
-
Raise the power level of a component
-
pm_lower_power()
-
Lower the power level of a component
-
pm_power_has_changed()
-
Notify the power management framework of an autonomous power
level change
-
pm_trans_check()
-
Device power cycle advisory check
Table B–10 Deprecated Power Management Functions
|
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
|
Fault Management Functions
The fault management functions are:
-
ddi_fm_init()
-
Allocates and initializes resources based on declared fault
management capabilities
-
ddi_fm_fini()
-
Cleans up resources that were allocated for this device instance
to support fault management capabilities declared to ddi_fm_init()
-
ddi_fm_capable()
-
Returns the capability bit mask currently set for this device
instance
-
ddi_fm_handler_register()
-
Registers an error handler callback routine with the IO Fault
Management framework
-
ddi_fm_handler_unregister()
-
Removes an error handler callback routine that was registered
with ddi_fm_handler_register()
-
ddi_fm_acc_err_get()
-
Returns the error status for an access handle
-
ddi_fm_dma_err_get()
-
Returns the error status for a DMA handle
-
ddi_fm_acc_err_clear()
-
Clears the error status for an access handle
-
ddi_fm_dma_err_clear()
-
Clears the error status for a DMA handle
-
ddi_fm_ereport_post()
-
Queues an encoded fault management error report name-value
pair list for delivery to the Fault Manager daemon, fmd(1M)
-
ddi_fm_service_impact()
-
Reports the impact of an error
-
pci_ereport_setup()
-
Initializes support for error report generation and sets up
the resources for subsequent accesses to PCI, PCI/X, or PCI Express configuration
space
-
pci_ereport_teardown()
-
Releases any resources allocated and setup by pci_ereport_setup() for this device instance
-
pci_ereport_post()
-
Scans for and posts any PCI, PCI/X, or PCI Express bus errors
Kernel Statistics Functions
The kernel statistics (kstats) functions are:
-
kstat_create()
-
Create and initialize a new kstat
-
kstat_delete()
-
Remove a kstat from the system
-
kstat_install()
-
Add a fully initialized kstat to the system
-
kstat_named_init()
-
Initialize a named kstat
-
kstat_runq_back_to_waitq()
-
Record a transaction migration from run queue to the wait
queue
-
kstat_runq_enter()
-
Record a transaction addition to the run queue
-
kstat_runq_exit()
-
Record a transaction removal from the run queue
-
kstat_waitq_enter()
-
Record a transaction addition to the wait queue
-
kstat_waitq_exit()
-
Record a transaction removal from the wait queue
-
kstat_waitq_to_runq()
-
Record a transaction migration from the wait queue to the
run queue
Kernel Logging and Printing Functions
The kernel logging and printing functions are:
-
cmn_err(), vcmn_err()
-
Display an error message
-
ddi_report_dev()
-
Announce a device
-
strlog()
-
Submit messages to the log driver
-
ddi_dev_report_fault()
-
Report a hardware failure
-
scsi_errmsg()
-
Display a SCSI request sense message
-
scsi_log()
-
Display a SCSI-device-related message
-
scsi_vu_errmsg()
-
Display a SCSI request sense message
Buffered I/O Functions
The buffered I/O functions are:
-
physio()
-
Perform physical I/O
-
aphysio()
-
Perform asynchronous physical I/O
-
anocancel()
-
Prevent cancellation of an asynchronous I/O request
-
minphys()
-
Limit the physio() buffer size
-
biowait()
-
Suspend processes pending completion of block I/O
-
biodone()
-
Release the buffer after buffer I/O transfer and notify blocked
threads
-
bioerror()
-
Indicate the error in a buffer header
-
geterror()
-
Return an I/O error
-
bp_mapin()
-
Allocate virtual address space
-
bp_mapout()
-
Deallocate virtual address space
-
disksort()
-
Use a single-direction elevator seek strategy to sort for
buffers
-
getrbuf()
-
Get a raw buffer header
-
freerbuf()
-
Free a raw buffer header
-
biosize()
-
Return the size of a buffer structure
-
bioinit()
-
Initialize a buffer structure
-
biofini()
-
Uninitialize a buffer structure
-
bioreset()
-
Reuse a private buffer header after I/O is complete
-
bioclone()
-
Clone another buffer
-
biomodified()
-
Check whether a buffer is modified
-
clrbuf()
-
Erase the contents of a buffer
Virtual Memory Functions
The virtual memory functions are:
-
ddi_btop()
-
Convert device bytes to pages (round down)
-
ddi_btopr()
-
Convert device bytes to pages (round up)
-
ddi_ptob()
-
Convert device pages to bytes
-
btop()
-
Convert size in bytes to size in pages (round down)
-
btopr()
-
Convert size in bytes to size in pages (round up)
-
ptob()
-
Convert size in pages to size in bytes
Table B–11 Deprecated Virtual Memory Functions
|
Deprecated Functions
|
Replacement
|
|
hat_getkpfnum()
|
devmap(), ddi_dma_*_bind_handle(), ddi_dma_nextcookie()
|
Device ID Functions
The device ID functions are:
-
ddi_devid_init()
-
Allocate a device ID structure
-
ddi_devid_free()
-
Free a device ID structure
-
ddi_devid_register()
-
Register a device ID
-
ddi_devid_unregister()
-
Unregister a device ID
-
ddi_devid_compare()
-
Compare two device IDs
-
ddi_devid_sizeof()
-
Return the size of a device ID
-
ddi_devid_valid()
-
Validate a device ID
-
ddi_devid_str_encode()
-
Encode a device ID and minor_name into a null-terminated ASCII
string; return a pointer to that string
-
ddi_devid_str_decode()
-
Decode the device ID and minor_name from a previously encoded
string; allocate and return pointers to the extracted parts
-
ddi_devid_str_free()
-
Free all strings returned by the ddi_devid_* functions
SCSI Functions
The SCSI functions are:
-
scsi_probe()
-
Probe a SCSI device
-
scsi_unprobe()
-
Free resources allocated during initial probing
-
scsi_alloc_consistent_buf()
-
Allocate an I/O buffer for SCSI DMA
-
scsi_free_consistent_buf()
-
Free a previously allocated SCSI DMA I/O buffer
-
scsi_init_pkt()
-
Prepare a complete SCSI packet
-
scsi_destroy_pkt()
-
Free an allocated SCSI packet and its DMA resource
-
scsi_setup_cdb()
-
Set up SCSI command descriptor block (CDB)
-
scsi_transport()
-
Start a SCSI command
-
scsi_poll()
-
Run a polled SCSI command
-
scsi_ifgetcap()
-
Get SCSI transport capability
-
scsi_ifsetcap()
-
Set SCSI transport capability
-
scsi_sync_pkt()
-
Synchronize CPU and I/O views of memory
-
scsi_abort()
-
Abort a SCSI command
-
scsi_reset()
-
Reset a SCSI bus or target
-
scsi_reset_notify()
-
Notify the target driver of bus resets
-
scsi_cname()
-
Decode a SCSI command
-
scsi_dname()
-
Decode a SCSI peripheral device type
-
scsi_mname()
-
Decode a SCSI message
-
scsi_rname()
-
Decode a SCSI packet completion reason
-
scsi_sname()
-
Decode a SCSI sense key
-
scsi_errmsg()
-
Display a SCSI request sense message
-
scsi_log()
-
Display a SCSI-device-related message
-
scsi_vu_errmsg()
-
Display a SCSI request sense message
-
scsi_hba_init()
-
SCSI HBA system initialization routine
-
scsi_hba_fini()
-
SCSI HBA system completion routine
-
scsi_hba_attach_setup()
-
SCSI HBA attach routine
-
scsi_hba_detach()
-
SCSI HBA detach routine
-
scsi_hba_probe()
-
Default SCSI HBA probe function
-
scsi_hba_tran_alloc()
-
Allocate a transport structure
-
scsi_hba_tran_free()
-
Free a transport structure
-
scsi_hba_pkt_alloc()
-
Allocate a scsi_pkt structure
-
scsi_hba_pkt_free()
-
Free a scsi_pkt structure
-
scsi_hba_lookup_capstr()
-
Return an index matching capability string
Table B–12 Deprecated SCSI Functions
|
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()
|
Resource Map Management Functions
The resource map management functions are:
-
rmallocmap()
-
Allocate a resource map
-
rmallocmap_wait()
-
Allocate a resource map, wait if necessary
-
rmfreemap()
-
Free a resource map
-
rmalloc()
-
Allocate space from a resource map
-
rmalloc_wait()
-
Allocate space from a resource map, wait if necessary
-
rmfree()
-
Free space back into a resource map
System Global State
-
ddi_in_panic()
-
Determine whether the system is in panic state
Utility Functions
The utility functions are:
-
nulldev()
-
Zero return function
-
nodev()
-
Error return function
-
nochpoll()
-
Error return function for non-pollable devices
-
ASSERT()
-
Expression verification
-
bcopy()
-
Copy data between address locations in the kernel
-
bzero()
-
Clear memory for a given number of bytes
-
bcmp()
-
Compare two byte arrays
-
ddi_ffs()
-
Find the first bit set in a long integer
-
ddi_fls()
-
Find the last bit set in a long integer
-
swab()
-
Swap bytes in 16-bit halfwords
-
strcmp()
-
Compare two null-terminated strings
-
strncmp()
-
Compare two null-terminated strings, with length limit
-
strlen()
-
Determine the number of non-null bytes in a string
-
strnlen()
-
(Available starting with SXCE build 88)
Determine the number of non-null bytes in a string, with length limit
-
strcpy()
-
Copy a string from one location to another
-
strncpy()
-
Copy a string from one location to another, with length limit
-
strchr()
-
Find a character in a string
-
sprintf(), vsprintf()
-
Format characters in memory
-
numtos()
-
Convert an integer to a decimal string
-
stoi()
-
Convert a decimal string to an integer
-
max()
-
Return the larger of two integers
-
min()
-
Return the lesser of two integers
-
va_arg()
-
Finds the next value in a variable argument list
-
va_copy()
-
Copies the state of a variable argument list
-
va_end()
-
Deletes pointer to a variable argument list
-
va_start()
-
Finds the pointer to the start of a variable argument list
Appendix C Making a Device Driver 64-Bit
Ready
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:
Introduction to 64-Bit Driver Design
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. More general topics are covered in
the Solaris 64-bit Developer’s Guide.
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.
General Conversion Steps
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.
See the notes on using and interpreting the output of lint in
the Solaris 64-bit Developer’s Guide.
Note –
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.
Use Fixed-Width Types for Hardware Registers
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 */
Use Fixed-Width Common Access Functions
The 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.
Check and Extend Use of Derived Types
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.
Check Changed Fields in DDI Data Structures
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.
buf Structure Changes
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 */
ddi_dma_attr
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.
ddi_dma_cookie Structure
Changes
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.
csi_arq_status Structure
Changes
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.
scsi_pkt Structure Changes
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.
Check Changed Arguments of DDI Functions
This section describes the DDI function argument data types that have
been changed.
getrbuf() Argument Changes
struct buf *getrbuf(int sleepflag);
In previous releases, sleepflag was defined as a type long.
drv_getparm() Argument Changes
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).
delay() and timeout() Argument
Changes
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.
rmallocmap() and rmallocmap_wait() Argument Changes
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.
scsi_alloc_consistent_buf() Argument
Changes
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.
uiomove() Argument Changes
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.
cv_timedwait() and cv_timedwait_sig() Argument Changes
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.
ddi_device_copy() Argument Changes
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.
ddi_device_zero() Argument Changes
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.
ddi_dma_mem_alloc() Argument
Changes
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 *.
Modify Routines That Handle Data Sharing
If a device driver shares data structures
that contain longs 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.
Data Sharing in ioctl()
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).
Data Sharing in devmap()
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.
Data Sharing in mmap()
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_MODEL_ILP32 –
Application expects the ILP32 data model
-
DDI_MODEL_ILP64 –
Application expects the LP64 data 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.
Check Structures with 64-bit Long Data Types on x86-Based
Platforms
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.
Well Known ioctl Interfaces
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.
Device Sizes
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.
Appendix D Console Frame Buffer Drivers
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 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 Solaris kernel terminal emulator.
Solaris Consoles and the 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 Solaris console frame buffer driver, your frame buffer
driver must be compatible with the 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 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 Solaris kernel terminal emulator.
x86 Platform Console Communication
On x86 platforms, the 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 Platform Console Communication
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 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.
-
tem-support
-
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:
The following section provides detailed information.
Console Visual I/O Interfaces
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.
I/O Control Interfaces
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_DEFINI.
|
|
VIS_CONSDISPLAY
|
vis_consdisplay
|
Displays pixels as a rectangle. See VIS_CONSDISPLAY.
|
|
VIS_CONSCOPY
|
vis_conscopy
|
Copies a rectangle of pixels (scroll). See VIS_CONSCOPY.
|
|
VIS_CONSCURSOR
|
vis_conscursor
|
Displays or hides a text cursor. See VIS_CONSCURSOR.
|
|
VIS_PUTCMAP
|
vis_cmap
|
Sends the terminal emulator module color map to the frame buffer driver.
See VIS_PUTCMAP.
|
|
VIS_GETCMAP
|
vis_cmap
|
Reads the terminal emulator module color map from the frame buffer.
See VIS_GETCMAP.
|
Polled I/O Interfaces
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.
|
Video Mode Change Callback Interface
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).
|
Implementing the Visual I/O Interfaces in Console
Frame Buffer Drivers
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.
Note –
Each ioctl command should determine whether
the FKIOCTL is set in the ioctl flag
argument and return EPERM if that bit is not set.
VIS_DEVINIT
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.
Note –
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.
Implementing Polled I/O in Console Frame Buffer Drivers
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:
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.
Frame Buffer Specific Configuration Module
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.
The X Window System Frame Buffer Specific DDX Module
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.
Developing, Testing, and Debugging Console Frame
Buffer Drivers
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.
Testing the I/O Control Interfaces
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"
Testing the Polled I/O Interfaces
The polled I/O interfaces are only available under the following circumstances:
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.
Testing the Video Mode Change Callback Function
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.
Additional Suggestions for Testing Console Frame
Buffer Drivers
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.