This chapter explains how to write kernel-level general memory-mapping device drivers.
It contains the following sections:
This chapter describes how a kernel-level device driver can map VME device hardware or main (kernel) memory to user space. It introduces two system calls, mmap(2) and munmap(2), as well as describing two IRIX driver functions, drvmap() and drvunmap()[16] . Because your driver allows the user to map the device user space, your driver may not need to include drvread() and drvwrite() functions.
For a VME device driver, refer to Chapter 3, “Writing a VME Device Driver,” for details on adding a VME device driver to the kernel.
For a EISA device driver, see Chapter 4, “Writing an EISA Device Driver,” for details on including this type of driver.
For a SCSI device driver, refer to Chapter 5, “Writing a SCSI Device Driver,” for details on including this type of driver to the kernel.
For a GIO device driver, see Chapter 6, “Writing Kernel-level GIO Device Drivers,” for details.
The functions for mapping memory and registers are mmap(), drvmmap(), munmap(), and v_mapphys().
When a user-level program wants to map device memory into its address space, the user program opens the special file corresponding to the particular device and uses the mmap() system call. Your driver needs to call drvmmap() — which you need to write — when a mmap call maps into a user's address space. drvmmap is logically similar to a drvopen routine; make sure it does whatever work your device requires. (See Chapter 2, “Writing a Device Driver,” and the mmap(D2) man page for more details.)
The section of a user-level program that maps device memory into its own addressing space could look like:
#include "fcntl.h"
#include "sys/mman.h"
fd = open(special_file, O_RDWR);
addr = mmap(0, len, PROT_READ|PROT_WRITE,
MAP_PRIVATE, fd, off);
|
After the kernel performs basic sanity checking on the system call arguments, if the file descriptor passed to the mmap(2) system call represents a special file, the kernel looks in your driver object module and calls that device mapping function.
Synopsis
drvmap(dev, vt, off, len, prot)
dev_t dev; /* device number*/
vhandl_t *vt; /* handle to caller's
/* virtual address space */
off_t off; /* offset into device */
int len; /* # of bytes to map */
int prot; /* protections */
|
Arguments
To unmap a device, the user program calls the munmap(2) system call:
munmap (addr, len); |
where addr is the device virtual address returned by the mmap(2) function and len is the length of the mapped area. After performing device-independent unmapping in the user's space, the munmap(2) system call calls your driver's drvunmap() function if it is defined as an entry in the device switch table.
Synopsis
drvunmap(dev, vt);
dev_t dev; /* device number */
vhandl_t *vt; /* handle to caller's virtual
address space */
|
The vt and dev parameters are the same as for drvmap(), above.
![]() | Note: If a driver provides a mapping function but does not provide an unmapping function, the munmap() system call returns the ENODEV error condition to the user. Therefore, it is a good idea for your driver to provide a dummy unmapping function even if your driver does not need to perform any action to unmap the device. |
Your driver can allow the user to map device registers and local memory from the user's virtual space to physical memory if your driver's drvmap() function calls v_mapphys().
Synopsis
int v_mapphys(vt, addr, len);
vhandl_t *vt;
caddr_t addr;
int len;
|
Arguments
| vt | Your drvmap() function must give this parameter the opaque handle to the user's virtual address space. (The internals of the structure for the opaque handle are likely to change from release to release.) | |
| addr | Your drvmap() function must give this parameter the kernel virtual address by which the device is accessed. (See “VME Slave Addressing” in Appendix A.) The system takes this address from the user's call to mmap(2) and hands it to the off parameter of your drvmap() function. | |
| len | Your drvmap() function must give this parameter the number of bytes to be mapped. The system takes this value from the user's call to mmap(2) and hands it to the len parameter of your drvmap() function. |
If successful, v_mapphys() returns 0. If v_mapphys() fails, it sets errno and returns -1. After a successful call to v_mapphys(), the device's registers at addr are mapped into the user's address space as designated by vt. You do not need any special unmapping, so the drvunmap() function does not need actions specified within it.
![]() | Caution: Your driver must be very careful when it maps device registers to a user process. It must carefully check the range of addresses that the user requests and make sure that the request references only the requested device. Because protection is available only up to a page boundary, configure the addresses of I/O cards so that they do not overlap a page. If they are allowed to overlap, an application process may be able to access more than one device, possibly a system device (for example, a disk controller or Ethernet). This can cause system secuity problems or other problems that are hard to diagnose. |
To allocate memory that is shared between the driver and the application process, your drvmap() function must do steps 1 and 2 of the procedure below. To free that memory later, your drvunmap() function must do step 3.
Use the kmem_alloc() kernel function to allocate some memory pages in the kernel:
caddr_t *kaddr = kmem_alloc (len , KM_CACHEALIGN); |
Map the memory pages into the user's address space by calling v_mapphys():
v_mapphys (vt, kaddr, nbytes) |
To free the memory, your driver's unmapping function, drvunmap(), must call kmem_free():
kmem_free(kaddr, len); |
kmem_alloc() allocates physical memory and returns a kernel virtual address associated with that memory. The physical memory is not subject to paging. v_mapphys() returns 0 upon success and -1 upon failure. The parameters for these calls are:
| vt | Your drvmap() function must give this parameter the opaque handle to the user virtual address space. (The internals of this structure are likely to change from release to release.) | |
| kaddr | Your drvmap() function must give this parameter the virtual address returned by kmem_alloc(). Your drvunmap() function must give this parameter the kernel virtual address returned by kmem_alloc(). | |
| len | Your drvmap() function must give this parameter the number of bytes to be mapped. (This value must not be greater than the number of pages allocated in step 1.) |
Suppose the mythical VT device wants to share memory with a user program. Its drvmap() and drvunmap() functions would look something like this:
#include <sys/sysmacros.h>
struct mpd {
unsigned int d_id; /* id of memory segment */
caddr_t d_addr; /* address of allocated memory */
int d_npages; /* number of pages allocated */
struct mpd *d_last, *d_next; /* links */
};
struct mpd vdk_list; /* at init, this becomes a doubly
/*linked ring */
int vdkmap(dev_t dev, vhandl_t *vt, off_t off, int len,
int prot)
{
struct mpd *d;
/* initial sanity checking (not shown) */
...
/* allocate some temporary storage */
if( (d = kmem_alloc(sizeof(struct mpd)), 0 )
== NULL )
return ENOMEM;
d->d_npages = btoc(len);
if( (d->d_addr = kmem_alloc(ctob(d->d_npages))KM_CACHEALIGN) == NULL ) {
kmem_free(d,sizeof(struct mpd));
return ENOMEM;
}
/* map it into the user's address space */
if( v_mapphys(vt,d->d_addr,len) ) {
kmem_free(d->d_addr,ctob(d->d_npages));
kmem_free(d,sizeof(struct mpd));
return ENOMEM;
}
d->d_id = v_gethandle(vt);
/* initialize the memory */
bzero(d->d_addr,ctob(d->d_npages));
/* add to the list */
d->d_next = vdk_list.d_next;
d->d_last = &vdk_list;
d->d_next->d_last = d->d_last->d_next = d;
return 0;
}
|
See “Returning Opaque Handle Data,” for a description of the v_gethandle() system function. In the vdkunmap() function, you can find the piece of memory to be deallocated by searching the above list. The driver can then call the kmem_free() kernel function with the address and length of this section of memory:
int vdkunmap(dev_t dev, vhandl_t *vt)
{
struct mpd *d;
int id;
id = v_gethandle(vt);
/* Find chunk of memory corresponding to it. */
for(d = vdk_list.d_next; d != &vdk_list ; d = d->d_next )
if( d->d_id == id )
break;
/* Make sure we found it. */
if( d == &vdk_list )
return 0;
/* remove from list */
d->d_next->d_last = d->d_last;
d->d_last->d_next = d->d_next;
/* free up resources */
kmem_free(d->d_addr,ctob(d->d_npages));
kmem_free(d,sizeof(struct mpd));
return 0;
}
|
Use the v_gethandle macro to get the unique identifier associated with vt, the opaque handle to the user's virtual address space. (The term “opaque” indicates that your code does not directly deal with the members of this structure, which is likely to change from release to release.)
#include "sys/region.h" unsigned v_gethandle(vt); vhandl_t *vt; |
Because the virtual handle points into the kernel stack, it is likely to be overridden. Use v_gethandle if your driver must “remember” several virtual handles. Various other macros are also defined in sys/region.h, including macros that get the user virtual address to which the device space is mapped; macros that get the inode associated with the special file; and macros that get the length (in pages) of the user's mapped space.