Writing Device Drivers

Appendix A Converting a SunOS 4.1 Device Driver to SunOS 5.7

This chapter is a guide to the differences between SunOS 4.1 and SunOS 5.7 device drivers. The information in this chapter can be used to update relatively simple drivers intended to operate on the same platform under the SunOS 5.7 system that they operated on under the SunOS 4.1 system.

Note that drivers that need to operate on multiple platforms, or drivers that need to take advantage of features such as multithreading must be rethought and rewritten along the guidelines specified in this manual.

Before Starting the Conversion

Before starting to convert a driver to the SunOS 5.7 system, take the preliminary steps listed below.

Review Existing Functionality

Make sure that the driver's current functionality is well understood: the way it manages the hardware, and the interfaces it provides to applications ( ioctl(2) states the device is put in for example). Maintain this functionality in the new driver.

Read the Manual

This chapter is not a substitute for the rest of this book. Make sure that you have access to the SunOS 5.7 reference manuals.

ANSI C Compliance

The unbundled Sun C compiler is now ANSI C compliant. Most ANSI C changes are beyond the scope of this book. A number of ANSI C books are available in local bookstores; in particular, the following books are good references:

Development Environment

DDI/DKI

The device driver interface/driver-kernel interface (DDI/DKI) is a new name for the routines formerly called "kernel support routines" in the SunOS 4.1 Writing Device Drivers manual, and for the "well-known" entry points in the SunOS 4.1 cdevsw and bdevsw structures. The intent is to specify a set of interfaces for drivers that provide a binary and source code interface. If a driver uses only kernel routines and structures described in Section 9 of the Solaris 2.6 Reference Manual, it is called Solaris 7 DDI/DKI-compliant. A Solaris 7 DDI/DKI-compliant driver is likely to be binary compatible across Sun Solaris platforms with the same processor, and binary compatible with future releases of Solaris on platforms the driver works on.

Avoid Using Non-DDI/DKI Interfaces

Many architecture-specific features have been hidden from driver writers behind DDI/DKI interfaces. Specific examples are elements of the dev_info structure, user structure, proc structure, and page tables. If the driver has been using unadvertised interfaces, it must be changed to use DDI/DKI interfaces that provide the required functionality. If the driver continues to use unadvertised interfaces, it loses all the source and binary compatibility features of the DDI/DKI. For example, previous releases had an undocumented routine called as_fault( ) that could be used to lock down user pages in memory. This routine still exists, but is not part of the DDI/DKI, so it should not be used. The only documented way to lock down user memory is to use physio(9F).

Do not use any undocumented fields of structures. Documented fields are in Section 9S of the Solaris 2.6 Reference Manual. Do not use fields, structures, variables, or macros just because they are in a header file.

Dynamically allocate structures whenever possible. If buf(9S) structure is needed, do not declare one. Instead, declare a pointer to one, and call getrbuf(9F) to allocate it.


Note -

Even using kmem_alloc(sizeof(struct buf)) is not allowed, because the size of a buf(9S) structure might change in future releases.


UNIX System V Release 4

The SunOS 5.7 system is the Sun version of AT&T's System V Release 4 (SVR4). The system administration model is different from those in previous SunOS releases, which were more like 4.3 BSD. Differences important to device driver writers are:

For general SVR4 system administration information, see the Solaris Transition Guide.

Development Tools

The only compiler that should be used to compile SunOS 5.7 device drivers is the unbundled Sun C compiler, Sun WorkShop Compiler C 4.2. See Chapter 15, Loading and Unloading Drivers for information on how to compile and load a driver. Note that the compiler's bin directory (possibly /opt/SUNWspro/bin) and the supporting tools directory (/usr/ccs/bin) should be prepended to the PATH. When compiling a driver, use the -Xt and -D_KERNEL options.

When building a loadable driver module from the object modules, use ld(1) with the -r flag.

Debugging Tools

adb(1), kadb(1M), and crash(1M) are essentially the same as they were in the SunOS 4.1 system, though there are new macros. To debug a live kernel, use /dev/ksyms (see ksyms(7)) instead of the kernel name (which used to be /vmunix):

# adb -k /dev/ksyms /dev/mem

See "Debugging Tools" for more information.

ANSI C Features

The unbundled Sun C compiler is now ANSI C compliant. Two important ANSI C features device driver writers should use are the volatile keyword and function prototyping.

volatile

volatile is an ANSI C keyword that is used to prevent the optimizer from removing what it thinks are unnecessary accesses to objects. As an example, if the device has a control register that requires two consecutive writes to get it to take action, the optimizer could decide that the first write is unnecessary since the value is unused if there is no intervening read access.

If a device driver does not use the DDI data access functions to access device registers, device registers should be declared volatile. However, if the DDI data access functions are used to access device registers, it is not necessary to use volatile.


Note -

It is not an error to declare a variable volatile unnecessarily, but it might impact performance.


Function Prototypes

ANSI C provides function prototypes. This allows the compiler to check the type and number of arguments to functions, and avoids default argument promotions. To prototype functions, declare the type and name of each function in the function definition. Then provide a prototype declaration (including at least the types) before the function is called.

Prototypes are provided for most DDI/DKI functions, so many potentially fatal errors are now caught at compile time.

Header Files

For Solaris 7 DDI/DKI compliance, drivers are allowed to include only the kernel header files listed in the synopsis sections of Section 9 of the Solaris 2.6 Reference Manual. All allowed kernel header files are now located in the /usr/include/sys directory.

New header files all drivers must include are <sys/ddi.h> and <sys/sunddi.h>. These two headers must appear last in the list of kernel header include files.

Summary of Changes

Autoconfiguration Changes

Starting with the SunOS 4.1.2 system, the framework initialized all the drivers in the system before starting init(8). The advent of loadable module technology enabled some device drivers to be added and removed manually at later times in the life of the system.

The SunOS 5.7 system extends this idea to make every driver loadable, and to allow the system to automatically configure itself continually in response to the needs of applications. This, plus the unification of the "mb" style and Open Boot style autoconfiguration, has meant some significant changes to the probe(9E) and attach(9E) routines, and has added detach(9E).

Because all device drivers are loadable, the kernel no longer needs to be recompiled and relinked to add a driver. The config(8) program has been replaced by Open Boot PROM information and supplemented by information in hardware configuration files (see driver.conf(4)).

Changes to Routines

The reason the rules are so stringent is that the implementation will change. If driver routines follow these rules, they will not be affected by changes to the implementation. If, however, they assume that the autoconfiguration routines are called only in a certain order (first probe(9E), then attach(9E), for example), these drivers will break in some future release.

Instance Numbers

In the SunOS 4.1 system, drivers counted the number of devices that they found, and assigned a unit number to each (in the range 0 to the number of units found less one). Now, these unit numbers are called instance numbers, and the system assigns the numbers to devices.

Instances can be thought of as a shorthand name for a particular instance of a device (foo0 could name instance 0 of device foo). The system assigns and retrieves the instance numbers, even after any number of reboots. This is because at open(2) time all the system has is a dev_t. To determine which device is needed (as it may need to be attached), the system needs to get the instance number (which the driver retrieves from the minor number).

The mapping between instance numbers and minor numbers (see getinfo(9E)) should be static. The driver should not require any state information to do the translation, since that information might not be available (the device might not be attached).

Changes to /devices

All devices in the system are represented by a data structure in the kernel called the device tree. The /devices hierarchy is a representation of this tree in the file system.

In the SunOS 4.1 system,the administrator created special device files using mknod (or an installation script running mknod). Now, device drivers notify the kernel of entries by calling ddi_create_minor_node(9F) once they have determined a particular device exists. drvconfig(1M) actually maintains the file system nodes. This results in names that completely identify the device.

Changes to/dev

In the SunOS 4.1 system, device special files were located (by convention) in /dev. Now that the /devices directory is used for special files, /dev is used for logical device names. Usually, these are symbolic links to the real names in /devices.

Logical names can be used for backward compatibility with SunOS 4.1 applications, a short name for the real /devices name, or a way to identify a device without having to know where it is in the /devices tree. For example,/dev/fb could refer to a cgsix, cgthree, or bwtwo framebuffer, but the application does not need to know this.

See disks(1M), tapes(1M), ports(1M), devlinks(1M), and /etc/devlink.tab for system-supported ways of creating these links. See also Chapter 5, Autoconfiguration, for more information.

Multithreading Changes

The SunOS 5.7 system supports multiple threads in the kernel, and multiple CPUs. A thread is a sequence of instructions being executed by a program. In the SunOS 5.7 system, there are application threads, and there are kernel threads. Kernel threads are used to execute kernel code, and are the threads of concern to the driver writer.

Interrupts are also handled as threads. Because of this, there is less of a distinction between the tophalf and bottomhalf of a driver than there was in the SunOS 4.1 system. All driver code is executed by a thread, which may be running in parallel with threads in other (or the same) part of a driver. The distinction now is whether these threads have user context.

See Chapter 4, Multithreading, for more information.

Locking Changes

Starting with the SunOS 4.1.2 system, only one processor can be in the kernel at any one time. This is accomplished by using a master lock around the entire kernel. When a processor needs to execute kernel code, it needs to acquire the lock (this excludes other processors from running the code protected by the lock) and then release the lock when it is through. Because of this master lock, drivers written for uniprocessor systems did not change for multiprocessor systems. Two processors could not execute driver code at the same time.

In the SunOS 5.7 system, instead of one master lock, there are many smaller locks that protect smaller regions of code. For example, there may be a kernel lock that protects access to a particular vnode, and one that protects an inode. Only one processor can be running code dealing with that vnode at a time, but another could be accessing an inode. This allows a greater degree of concurrency.

However, because the kernel is multithreaded, it is possible that two (or more) threads are in driver code at the same time.

  1. One thread could be in an entry point, and another in the interrupt routine. The driver had to handle this in the SunOS 4.1 system, but with the restriction that the interrupt routine blocked the user context routine while it ran.

  2. Two threads could be in a routine at the same time. This could not happen in the SunOS 4.1 system.

Both of these cases are similar to situations present in the SunOS 4.1 system, but now these threads could run at the same time on different CPUs. The driver must be prepared to handle these types of occurrences.

Mutual Exclusion Locks

In the SunOS 4.1 system, a driver had to be careful when accessing data shared between the tophalf and the interrupt routine. Because the interrupt could occur asynchronously, the interrupt routine could corrupt data or simply hang. To prevent this, portions of the top half of the driver would raise, using the various spl routines, the interrupt priority level of the CPU to block the interrupt from being handled:

	s = splr(pritospl(6));
 	/* access shared data */
 	(void)splx(s);

In the SunOS 5.7 system, this no longer works. Changing the interrupt priority level of one CPU does not necessarily prevent another CPU from handling the interrupt. Also, two top-half routines may be running simultaneously with the interrupt running on a third CPU.

To solve this problem, the SunOS 5.7 system provides:

  1. A uniform module of execution--even interrupts run as threads. This blurs the distinction between the tophalf and the bottomhalf, as effectively every routine is a bottomhalf routine.

  2. A number of locking mechanisms-a common mechanism is to use mutual exclusion locks (mutexes):

	mutex_enter(&mu);
 	/* access shared data */
 	mutex_exit(&mu);

A subtle difference from the SunOS 4.1 system is that, because everything is run by kernel threads, the interrupt routine needs to explicitly acquire and release the mutex. In the SunOS 4.1 system, this was implicit since the interrupt handler automatically ran at an elevated priority.

See "Multithreading Additions to the State Structure" for more information on locking.

Condition Variables

In the SunOS 4.1 system, when the driver needed the current process to wait for something (such as a data transfer to complete), it called sleep()(), specifying a channel and a dispatch priority. The interrupt routine then called wakeup()( ) on that channel to notify all processes waiting on that channel that something happened. Because the interrupt could occur at any time, the interrupt priority was usually raised to ensure that the wakeup could not occur until the process was asleep.


Example A-1 SunOS 4.1 Synchronization Method

int		busy; /* global device busy flag */
int xxread(dev, uio)
dev_t		dev;
struct uio *uio;
{
	int		s;
	s = splr(pritospl(6));
	while (busy)
	    	sleep(&busy, PRIBIO + 1);
	busy = 1;
	(void)splx(s);
	/* do the read */
}
int xxintr()
{
	busy = 0;
	wakeup(&busy);
}

The SunOS 5.7 system provides similar functionality with condition variables. Threads are blocked on condition variables until they are notified that the condition has occurred. The driver must acquire a mutex that protects the condition variable before blocking the thread. The mutex is then released before the thread is blocked (similar to blocking/unblocking interrupts in the SunOS 4.1 system).


Example A-2 Synchronization in SunOS 5.7 Similar to SunOS 4.1

int			busy; 			/* global device busy flag */
kmutex_t 			busy_mu;			/* mutex protecting busy flag */
kcondvar_t			busy_cv;			/* condition variable for busy flag */
static int
xxread(dev_t dev, struct uio *uiop, cred_t *credp)
{
	mutex_enter(&busy_mu);
	while (busy)
	    	cv_wait(&busy_cv, &busy_mu);
	busy = 1;
	mutex_exit(&busy_mu);
	/* do the read */
}
static u_int
xxintr(caddr_t arg)
{
	mutex_enter(&busy_mu);
	busy = 0;
	cv_broadcast(&busy_cv);
	mutex_exit(&busy_mu);
}

Like wakeup(), cv_broadcast(9F) unblocks all threads waiting on the condition variable. To wake up one thread, use cv_signal(9F) (there was no documented equivalent for cv_signal(9F) in the SunOS 4.1 system).


Note -

There is no equivalent to the dispatch priority passed to sleep()( ).


Though the sleep()() and wakeup()() calls exist, do not use them, since the result would be an MT-unsafe driver.

See "Thread Synchronization" for more information.

Catching Signals

The driver could accidentally wait for an event that will never occur, or the event might not happen for a long time. In either case, the user might want to abort the process by sending it a signal (or typing a character that causes a signal to be sent to the process). Whether the signal causes the driver to wake up depends upon the driver.

In the SunOS 4.1 system, whether the sleep()() was signal-interruptible depended upon the dispatch priority passed to sleep()(). If the priority was greater than PZERO, the driver was signal-interruptible, otherwise the driver would not be awakened by a signal. Normally, a signal interrupt caused sleep( ) to return to the user, without notifying the driver that the signal had occurred. Drivers that needed to release resources before returning to the user passed the PCATCH flag to sleep( ), then looked at the return value of sleep() to determine why they awoke:

while (busy) {
 	if (sleep(&busy, PCATCH | (PRIBIO + 1))) {
 		/* awakened because of a signal */
 		/* free resources */
 		return (EINTR);
 	}
 }

In the SunOS 5.7 system, the driver can use cv_wait_sig(9F) to wait on the condition variable, but be signal interruptible. Note that cv_wait_sig(9F) returns zero to indicate the return was due to a signal, but sleep( ) in the SunOS 4.1 system returned a nonzero value:

while (busy) {
 	if (cv_wait_sig(&busy_cv, &busy_mu) == 0) {
 		/* returned because of signal */
 		/* free resources */
 		return (EINTR);
 	}
 }

cv_timedwait()()

Another solution drivers used to avoid blocking on events that would not occur was to set a timeout before the call to sleep. This timeout would occur far enough in the future that the event should have happened, and if it did run, it would awaken the blocked process. The driver would then see if the timeout function had run, and return some sort of error.

This can still be done in the SunOS 5.7 system, but the same thing may be accomplished with cv_timedwait(9F). An absolute time to wait is passed to cv_timedwait(9F), which will return zero if the time is reached and the event has not occurred. See Example 4-3 for an example usage of cv_timedwait(9F). Also see "cv_wait_sig()" for information on cv_timedwait_sig(9F).

Other Locks

Semaphores and readers/writers locks are also available. See semaphore(9F) and rwlock(9F).

Lock Granularity

Generally, start with one lock, and add more depending upon the abilities of the device. See "Choosing a Locking Scheme" and Appendix G, Advanced Topics, for more information.

Interrupt Changes

In the SunOS 4.1 system, two distinct methods were used for handling interrupts.

In the SunOS 5.7 system, the interrupt handler model has been unified. The device driver registers an interrupt handler for each device instance, and the system either polls all the handlers for the currently active interrupt level, or calls that handler directly (if it is vectored). The driver no longer needs to care which type of interrupt mechanism is in use (in the handler).

ddi_add_intr(9F) is used to register a handler with the system. A driver-defined argument of type caddr_t to pass to the interrupt handler. The address of the state structure is a good choice. The handler can then cast the caddr_t to whatever was passed. See "Registering Interrupts" and "Responsibilities of an Interrupt Handler" for more information.

DMA Changes

In the SunOS 4.1 system, to do a DMA transfer the driver mapped a buffer into the DMA space, retrieved the DMA address and programed the device, did the transfer, and freed the mapping. This was accomplished in this sequence:

  1. mb_mapalloc()( ) - Map buffer into DMA space

  2. MBI_ADDR()( ) - Retrieve address from returned cookie

  3. Program the device and start the DMA

  4. mb_mapfree()( ) - Free mapping when DMA is complete

The first three usually occurred in a start()( ) routine, and the last in the interrupt routine.

The SunOS 5.7 DMA model is similar, but it has been extended. The goal of the new DMA model is to abstract the platform-dependent details of DMA away from the driver. A sliding DMA window has been added for drivers that need to do DMA to large objects, and the DMA routines can be informed of device limitations (such as 24-bit addressing).

The sequence for DMA is as follows: The driver allocates a DMA handle using ddi_dma_alloc_handle(9F). The DMA handle can be reused for subsequent DMA transfers. Then the driver commits DMA resources using either ddi_dma_buf_bind_handle(9F) or ddi_dma_addr_bind_handle(9F), retrieves the DMA address from the DMA cookie to do the DMA, and frees the mapping with ddi_dma_unbind_handle(9F). The new sequence is something like this:

  1. ddi_dma_alloc_handle(9F) - Allocate a DMA handle

  2. ddi_dma_buf_bind_handle(9F) - Allocate DMA resources and retrieve address from the returned cookie

  3. Program the device and start the DMA

  4. Perform the transfer.


Note -

If the transfer involves several windows, you can call ddi_dma_getwin(9F) to move to subsequent windows.


  1. ddi_dma_unbind_handle(9F) - Free mapping when DMA is complete

  2. ddi_dma_free_handle(9F) - Free DMA handle when no longer needed

Additional routines have been added to synchronize any underlying caches and buffers, and handle IOPB memory. See Chapter 7, DMA, for details.

In addition, in the SunOS 4.1 system, the driver had to inform the system that it might do DMA, either through the mb_driver structure or with a call to adddma()( ). This was needed because the kernel might need to block interrupts to prevent DMA, but needed to know the highest interrupt level to block. Because the new implementation uses mutexes, this is no longer needed.

Conversion Notes

identify(9E)

identify(9E) is obsolete and no longer required. identify(9E) was used to determine whether a driver drove the device pointed to by dip. identify(9E) is currently supported only to provide backward compatibility with older drivers and should not be implemented. Set this entry point to nulldev(9F).


Note -

The framework now handles unit counting. To get the unit number in any routine, call ddi_get_instance(9F). Do not count units anywhere.


probe(9E)

SunOS 4.1 system:

	int xxprobe(reg, unit)
 	caddr_t reg;
 	int		unit;

SunOS 5.7 system:

	int xxprobe(dev_info_t *dip)

probe(9E) is still expected to determine if a device exists, but now the routine might be called any number of times, so it must be stateless (free anything it allocates).

attach(9E)

SunOS 4.1 system:

	int xxattach(devinfo)
 							struct dev_info *devinfo;

SunOS 5.7 system

	int xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)

Drivers are not allowed to count instances anywhere. Use ddi_get_instance(9F) to get the assigned instance number.

new_kmem_alloc( ) and new_kmem_zalloc( ) have become kmem_alloc(9F) and kmem_zalloc(9F). In SunOS 4.1 sleep flags were KMEM_SLEEP and KMEM_NOSLEEP; now they are KM_SLEEP and KM_NOSLEEP. Consider using KM_SLEEP only on small requests, as larger requests could deadlock the driver if there is not (or there will not be) enough memory. Instead, use KM_NOSLEEP, possibly shrink the request, and try again.

Any required memory should be dynamically allocated, as the driver should handle all occurrences of its device rather than a fixed number of them (if possible). Instead of statically allocating an array of controller state structures, each should now be allocated dynamically.

Remember to call ddi_create_minor_node(9F) for each minor device name that should be visible to applications.

The module loading process turns the information in any driver.conf(4) file into properties. Information that used to pass in the config file (such as flags) should now be passed as properties.

getinfo(9E)

SunOS 5.7 system:

	int xxgetinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
 		void *arg, void **resultp)

Make sure that the minor number to instance number and the reverse translation is static, since getinfo(9E) may be called when the device is not attached. For example:

	#define XXINST(dev) (getminor(dev) >> 3)

This is a required entry point; it cannot be replaced with nulldev(9F) or nodev(9F).

open(9E)

SunOS 4.1 system:

	int xxopen(dev, flag)
 	dev_t		dev;
 	int		flag;

SunOS 5.7 system:

	int xxopen(dev_t *devp, int flag, int otyp, cred_t *credp)

The first argument to open(9E) is a pointer to a dev_t. The rest of the cb_ops(9S) routines receive a dev_t.

Verify that the open type is one that the driver actually supports. This is normally OTYP_CHR for character devices, or OTYP_BLK for block devices. This prevents the driver from allowing future open types that it does not support.

If the driver used to check for root privileges using suser(), it should now use driv_priv(9F) instead on the passed credential pointer.

psize()

This entry point does not exist. Instead, block devices should support the nblocks property. This property may be created in attach(9E) if its value will not change. A prop_op(9E) entry point may be required if the value cannot be determined at attach time (such as if the device supports removable media). See "Properties" for more information.

read(9E) and write(9E)

SunOS 4.1 system:

	int xxread(dev, uio)
 	int xxwrite(dev, uio)
 	dev_t		dev;
 	struct uio *uio;

SunOS 5.7 system:

	int xxread(dev_t dev, uio_t *uiop, cred_t *credp);
 	int xxwrite(dev_t dev, uio_t *uiop, cred_t *credp);

physio(9F) should no longer be called with the address of a statically allocated buf(9S) structure. Instead, pass a NULL pointer as the second argument, which causes physio(9F) to allocate a buf structure. The address of the allocated buf structure should always be saved in strategy(9E), as it is needed to call biodone(9F). An alternative is to use getrbuf(9F) to allocate the buf(9S) structure, and freerbuf(9F) to free it.

ioctl(9E)

SunOS 4.1 system:

	int xxioctl(dev, cmd, data, flag)
 	dev_t		dev;
 	int		cmd, flag;
 	caddr_t		data;

SunOS 5.7 system:

	int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode,
 		cred_t *credp, int *rvalp);

In the SunOS 4.1 system, ioctl(9E) command arguments were defined as follows:

	#define XXIOCTL1  _IOR(m, 1, u_int)

The _IOR( ), _IOW( ), and _IOWR( ) macros were used to encode the direction and size of the data transfer. The kernel would then automatically copy the data into or out of the kernel. This is no longer the case. To do a data transfer, the driver is now required to use ddi_copyin(9F) and ddi_copyout(9F) explicitly. Do not dereference arg directly.

In addition, use the new method of a left-shifted letter OR'ed with number:

	#define XXIOC     (`x'<<8)
 	#define XXIOCTL1  (XXIOC | 1)

The credential pointer can be used to check credentials on the call (with drv_priv(9F)), and the return value pointer can be used to return a value that has meaning (as opposed to the old method of always getting zero back for success). This number should be positive to avoid confusion with applications that check for ioctl(2) returning a negative value for failure.

strategy(9E)

SunOS 4.1 system:

	int xxstrategy(buf)
 	struct buf *bp;

SunOS 5.7 system;

	int xxstrategy(struct buf *bp);

Retrieving the minor number from the b_dev field of the buf(9S) structure no longer works (or will work occasionally, and fail in new and notable ways at other times). Use the b_edev field instead.

If the driver allocated buffers uncached, it should now use ddi_dma_sync(9F) whenever consistent view of the buffer is required.

mmap(9E)

SunOS 4.1 system:

	int xxmmap(dev, off, prot)
 	dev_t		dev;
 	off_t		off;
 	int		prot;

SunOS 5.7 system:

	int xxdevmap(dev_t dev, devmap_cookie_t handle, offset_t off,
 	size_t len, size_t *maplen, u_int model);

In Solaris 7 and subsequent releases, the recommended way for applications to map kernel or device memory is with the devmap(9E) interface. For information on devmap(9E), see Chapter 11, Mapping Device or Kernel Memory.

If the driver checked for root privileges using suser()(), it should now use drv_priv(9F). Because there is no credential pointer passed to devmap(9E), the driver must use ddi_get_cred(9F) to retrieve the credential pointer.

chpoll(9E)

chpoll(9E) is similar in operation to select()( ), but there are more conditions that can be examined. See "Multiplexing I/O on File Descriptors " for details.

SunOS 4.1 to SunOS 5.7 Differences

Table A-1 compares device driver routines on the SunOS 4.1 system versus the SunOS 5.7 system. It is not a table of equivalences. That is, simply changing from the function in column one to the function (or group of functions) in column two is not always sufficient. If the 4.1 driver used a function in column one, read about the function in column two before changing any code.

Table A-1 SunOS 4.1 and SunOS 5.7 Kernel Support Routines

SunOS 4.1.x 

SunOS 5.6 

Description 

ASSERT()

ASSERT()

Expression verification 

CDELAY() 

Conditional busy-wait 

DELAY()

drv_usecwait()

Busy-wait for specified interval 

OTHERQ()

OTHERQ()

Gets pointer to queue's partner queue 

RD()

RD()

Gets pointer to the read queue 

WR()

WR()

Gets pointer to the write queue 

add_intr()

ddi_add_intr()

Adds an interrupt handler 

adjmsg()

adjmsg()

Trims bytes from a message 

allocb()

allocb()

Allocates a message block 

backq()

backq()

Gets pointer to queue behind the current queue 

bcmp()

bcmp()

Compares two byte arrays 

bcopy()

bcopy()

Copies data between address locations in kernel 

biodone() iodone()

biodone()

Indicates I/O is complete 

biowait() iowait()

b iowait()

Wait sfor I/O to complete 

bp_mapin()

bp_mapin()

Allocates virtual address space 

bp_mapout()

bp_mapout()

Deallocates virtual address space 

brelse()

-

Returns buffer to the free list 

btodb() 

Converts bytes to disk sectors 

btop()

btop() ddi_btop()

Converts size in bytes to size in pages (round down) 

btopr()

btopr() ddi_btopr()

Converts size in bytes to size in pages (round up) 

bufcall()

bufcall()

Calls a function when a buffer becomes available 

bzero()

bzero()

Zeros out memory 

canput()

canput()

Tests for room in a message queue 

clrbuf()

clrbuf()

Erases the contents of a buffer 

copyb()

copyb()

Copies a message block 

copyin()

ddi_copyin()

Copies data from a user program to a driver buffer 

copymsg()

copymsg()

Copies a message 

copyout()

ddi_copyout()

Copies data from a driver to a user program 

datamsg()

datamsg()

Tests whether a message is a data message 

delay() 

delay() 

Delays execution for a specified number of clock ticks 

disksort()

disksort()

Single direction elevator seek-sort for buffers 

dupb()

dupb()

Duplicates a message block descriptor 

dupmsg()

dupmsg()

Duplicates a message 

enableok()

enableok()

Reschedules a queue for service 

esballoc()

esballoc()

Allocates a message block using caller-supplied buffer 

esbbcall()

esbbcall()

Call sfunction when buffer is available 

ffs()

ddi_ffs()

Finds first bit set in a long integer 

fls()

ddi_fls()

Finds last bit set in a long integer 

flushq()

flushq()

Removes messages from a queue 

free_pktiopb()

scsi_free_consistent_buf()

Frees a SCSI packet in the iopb map

freeb()

freeb()

Frees a message block 

freemsg()

freemsg()

Frees all message blocks in a message 

get_pktiopb()

scsi_alloc_consistent_buf()

Allocates a SCSI packet in the iopb map

geterror()

geterror()

Gets buffer's error number 

getlongprop()

ddi_getlongprop()

Gets arbitrary size property information 

getprop()

ddi_getprop()

Gets boolean and integer property information 

getproplen()

ddi_getproplen()

Gets property information length 

getq()

getq()

Gets the next message from a queue 

gsignal() 

Sends signal to process group 

hat_getpkfnum()

hat_getkpfnum()

Gets page frame number for kernel address 

index()

strchr()

Returns pointer to first occurrence of character in string 

insq()

insq()

Inserts a message into a queue 

kmem_alloc()

kmem_alloc()

Allocates space from kernel free memory 

kmem_free()

kmem_free()

Frees previously allocated kernel memory 

kmem_zalloc()

kmem_zalloc()

Allocates and clears space from kernel free memory 

linkb()

linkb()

Concatenates two message blocks 

log() 

strlog() 

Logs kernel errors 

machineid() 

Gets host ID from EPROM 

major()

getmajor()

Gets major device number 

makecom_g0()

makecom_g0()

Makes packet for SCSI group 0 commands 

makecom_g0_s()

makecom_g0_s()

Makes packet for SCSI group 0 sequential commands 

makecom_g1()

makecom_g1()

Makes packet for SCSI group 1 commands 

makecom_g5()

makecom_g5()

Makes packet for SCSI group 5 commands 

mapin() map_regs()

ddi_regs_map_setup()

Maps physical-to-virtual space 

mapout() unmap_regs()

ddi_regs_map_free()

Removes physical-to-virtual mappings 

max()

max()

Returns the larger of two integers 

mb_mapalloc()

ddi_dma_buf_bind_handle()

Sets up system DMA resources and retrieves DMA address 

mb_mapfree()

ddi_dma_unbind_handle()

Releases system DMA resources 

mballoc() 

Allocates a main bus buffer 

mbrelse() 

Frees main bus resources 

mbsetup() 

Sets up use of main bus resources 

min()

min()

Returns the lesser of two integers 

minor()

getminor()

Gets minor device number 

minphys()

minphys()

Limits transfer request size to system maximum 

mp_nbmapalloc() 

ddi_dma_addr_bind_handle() 

Sets up system DMA resources and retrieves DMA address 

MBI_ADDR() 

Retrieves DMA address 

msgdsize()

msgdsize()

Returns the number of bytes in a message 

nodev()

nodev()

Error function returning ENXIO 

noenable()

noenable()

Prevents a queue from being scheduled 

nulldev()

nulldev()

Function returning zero 

ovbcopy() 

Copies overlapping byte memory regions 

panic() 

cmn_err() 

Reboots at fatal error 

peek()

ddi_peek16()

Reads a 16-bit value from a location 

peekc()

ddi_peek8()

Reads a 8-bit value from a location 

peekl()

ddi_peek32()

Reads a 32-bit value from a location 

physio()

physio()

Limits transfer request size 

pkt_transport()

scsi_transport()

Request by a SCSI target driver to start a command 

poke()

ddi_poke16()

Writes a 16-bit value to a location 

pokec()

ddi_poke8()

Writes a 8-bit value to a location 

pokel()

ddi_poke32()

Writes a 32-bit value to a location 

printf()

cmn_err()

Displays an error message or panics the system 

pritospl() 

Converts priority level 

psignal()

-

Sends a signal to a process 

ptob()

ptob() ddi_ptob()

Converts size in pages to size in bytes 

pullupmsg()

pullupmsg()

Concatenates bytes in a message 

put()

put()

Calls a STREAMS put procedure 

putbq()

putbq()

Places a message at the head of a queue 

putctl()

putctl()

Sends a control message to a queue 

putctl1()

putctl1()

Sends a control message with one-byte parameter to a queue 

putnext()

putnext()

Sends a message to the next queue 

putq()

putq()

Puts a message on a queue 

qenable()

qenable()

Enables a queue 

qreply()

qreply()

Sends a message on a stream in the reverse direction 

qsize()

qsize()

Finds the number of messages on a queue 

remintr()

ddi_remove_intr()

Removes an interrupt handler 

report_dev()

ddi_report_dev()

Announces a device 

rmalloc()

rmallocmap() rmalloc()

Allocates resource map allocates space from a resource map 

rmalloc(iopbmap)

ddi_dma_mem_alloc()

Allocates consistent memory 

rmfree()

rmfreemap() rmfree()

Frees resource map frees space back into a resource map 

rmfree(iopbmap)

ddi_dma_mem_free()

Free consistent memory 

rmvb()

rmvb()

Removes a message block from a message 

rmvq()

rmvq()

Removes a message from a queue 

scsi_abort()

scsi_abort()

Aborts a SCSI command 

scsi_dmafree()

scsi_dmafree()

Frees DMA resources for SCSI command 

scsi_dmaget()

scsi_init_pkt()

Allocates DMA resources for SCSI command 

scsi_ifgetcap()

scsi_ifgetcap()

Gets SCSI transport capability 

scsi_ifsetcap()

scsi_ifsetcap()

Sets SCSI transport capability 

scsi_pktalloc()

scsi_init_pkt()

Allocates packet resources for SCSI command 

scsi_pktfree()

scsi_destroy_pkt()

Frees packet resources for SCSI command 

scsi_poll()

scsi_poll()

Runs a polled SCSI command 

scsi_resalloc()

scsi_init_pkt()

Prepares a complete SCSI packet 

scsi_reset()

scsi_reset()

Resets a SCSI bus or target 

scsi_resfree()

scsi_destroy_pkt()

Frees an allocated SCSI packet 

scsi_slave()

scsi_probe()

Probes for a SCSI target 

selwakeup()

pollwakeup()

Informs a process that an event has occurred 

slaveslot()

ddi_slaveonly()

Tells if device is installed in a slave-only slot 

sleep()

cv_wait()

Supends calling thread and exit mutex atomically 

spln() 

mutex_enter() 

Sets CPU priority level 

splr() splx() 

mutex_exit() 

Resets priority level 

splstr() 

Sets processor level for STREAMS 

strcmp()

strcmp()

Compares two null-terminated strings 

strcpy()

strcmp()

Copies a string from one location to another 

suser() 

drv_priv() 

Verifies superuser 

swab()

swab()

Swaps bytes in 16-bit halfwords 

testb()

testb()

Checks for an available buffer 

timeout()

timeout()

Executes a function after a specified length of time 

uiomove()

uiomove()

Copies kernel data using uio(9S) structure

unbufcall() 

unbufcall() 

Cancels an outstanding bufcall request

unlinkb()

unlinkb()

Removes a message block from the head of a message 

untimeout()

untimeout()

Cancels previous timeout function call 

uprintf() 

cmn_err() 

Kernel print to controlling terminal 

ureadc()

ureadc()

Adds character to a uio structure

useracc()

useracc()

Verifies whether user has access to memory 

usleep() 

drv_usecwait 

Busy-waits for specified interval 

uwritec()

uwritec()

Removes a character from a uio structure

wakeup()

cv_broadcast()

Signals condition and wakes all blocked threads