Contained WithinFind More DocumentationFeatured Support Resources | PDF로 이 문서 다운로드 (1004 KB)
Appendix A Converting a SunOS 4.1 Device Driver to SunOS 5.7This 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 ConversionBefore starting to convert a driver to the SunOS 5.7 system, take the preliminary steps listed below. Review Existing FunctionalityMake 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 ManualThis 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 ComplianceThe 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 EnvironmentDDI/DKIThe 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 InterfacesMany 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 4The 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 ToolsThe 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 Toolsadb(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 FeaturesThe 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. volatilevolatile 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 PrototypesANSI 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 FilesFor 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 ChangesAutoconfiguration ChangesStarting 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 NumbersIn 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 /devicesAll 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/devIn 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 ChangesThe 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 ChangesStarting 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.
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 LocksIn 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:
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 VariablesIn 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 Methodint 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.1int 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 SignalsThe 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 LocksSemaphores and readers/writers locks are also available. See semaphore(9F) and rwlock(9F). Lock GranularityGenerally, 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 ChangesIn 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 ChangesIn 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:
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:
Note - If the transfer involves several windows, you can call ddi_dma_getwin(9F) to move to subsequent windows.
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 Notesidentify(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 DifferencesTable 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
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||