Writing Device Drivers

Entry Points for Device Context Management

The following entry points are used to manage device context:

devmap_map() Entry Point

The syntax for devmap(9E) is as follows:

int xxdevmap_map(devmap_cookie_t handle, dev_t dev, uint_t flags,
    offset_t offset, size_t len, void **new-devprivate);

The devmap_map() entry point is called after the driver returns from its devmap() entry point and the system has established the user mapping to the device memory. The devmap() entry point enables a driver to perform additional processing or to allocate mapping specific private data. For example, in order to support context switching, the driver has to allocate a context structure. The driver must then associate the context structure with the mapping.

The system expects the driver to return a pointer to the allocated private data in *new-devprivate. The driver must store offset and len, which define the range of the mapping, in its private data. Later, when the system calls devmap_unmap(9E), the driver uses this information to determine how much of the mapping is being unmapped.

flags indicates whether the driver should allocate a private context for the mapping. For example, a driver can allocate a memory region to store the device context if flags is set to MAP_PRIVATE. If MAP_SHARED is set, the driver returns a pointer to a shared region.

The following example shows a devmap() entry point. The driver allocates a new context structure. The driver then saves relevant parameters passed in by the entry point. Next, the mapping is assigned a new context either through allocation or by attaching the mapping to an already existing shared context. The minimum time interval that the mapping should have access to the device is set to one millisecond.


Example 11–1 Using the devmap() Routine

static int
int xxdevmap_map(devmap_cookie_t handle, dev_t dev, uint_t flags,
    offset_t offset, size_t len, void **new_devprivate)
{
    struct xxstate *xsp = ddi_get_soft_state(statep,
                  getminor(dev));
    struct xxctx *newctx;

    /* create a new context structure */
    newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
    newctx->xsp = xsp;
    newctx->handle = handle;
    newctx->offset = offset;
    newctx->flags = flags;
    newctx->len = len;
    mutex_enter(&xsp->ctx_lock);
    if (flags & MAP_PRIVATE) {
        /* allocate a private context and initialize it */
        newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
        xxctxinit(newctx);
    } else {
        /* set a pointer to the shared context */
        newctx->context = xsp->ctx_shared;
    }
    mutex_exit(&xsp->ctx_lock);
    /* give at least 1 ms access before context switching */
    devmap_set_ctx_timeout(handle, drv_usectohz(1000));
    /* return the context structure */
    *new_devprivate = newctx;
    return(0);
}

devmap_access() Entry Point

The devmap_access(9E) entry point is called when an access is made to a mapping whose translations are invalid. Mapping translations are invalidated when the mapping is created with devmap_devmem_setup(9F) in response to mmap(2), duplicated by fork(2), or explicitly invalidated by a call to devmap_unload(9F).

The syntax for devmap_access() is as follows:

int xxdevmap_access(devmap_cookie_t handle, void *devprivate,
    offset_t offset, size_t len, uint_t type, uint_t rw);

where:

handle

Mapping handle of the mapping that was accessed by a user process.

devprivate

Pointer to the driver private data associated with the mapping.

offset

Offset within the mapping that was accessed.

len

Length in bytes of the memory being accessed.

type

Type of access operation.

rw

Specifies the direction of access.

The system expects devmap_access(9E) to call either devmap_do_ctxmgt(9F) or devmap_default_access(9F) to load the memory address translations before devmap_access() returns. For mappings that support context switching, the device driver should call devmap_do_ctxmgt(). This routine is passed all parameters from devmap_access(9E), as well as a pointer to the driver entry point devmap_contextmgt(9E), which handles the context switching. For mappings that do not support context switching, the driver should call devmap_default_access(9F). The purpose of devmap_default_access() is to call devmap_load(9F) to load the user translation.

The following example shows a devmap_access(9E) entry point. The mapping is divided into two regions. The region that starts at offset OFF_CTXMG with a length of CTXMGT_SIZE bytes supports context management. The rest of the mapping supports default access.


Example 11–2 Using the devmap_access() Routine

#define OFF_CTXMG      0
#define CTXMGT_SIZE    0x20000    
static int
xxdevmap_access(devmap_cookie_t handle, void *devprivate,
    offset_t off, size_t len, uint_t type, uint_t rw)
{
    offset_t diff;
    int    error;

    if ((diff = off - OFF_CTXMG) >= 0 && diff < CTXMGT_SIZE) {
        error = devmap_do_ctxmgt(handle, devprivate, off,
            len, type, rw, xxdevmap_contextmgt);
    } else {
        error = devmap_default_access(handle, devprivate,
            off, len, type, rw);
    }
    return (error);
}

devmap_contextmgt() Entry Point

The syntax for devmap_contextmgt(9E) is as follows:

int xxdevmap_contextmgt(devmap_cookie_t handle, void *devprivate,
    offset_t offset, size_t len, uint_t type, uint_t rw);

devmap_contextmgt() should call devmap_unload(9F) with the handle of the mapping that currently has access to the device. This approach invalidates the translations for that mapping. The approach ensures that a call to devmap_access(9E) occurs for the current mapping the next time the mapping is accessed. The mapping translations for the mapping that caused the access event to occur need to be validated. Accordingly, the driver must restore the device context for the process requesting access. Furthermore, the driver must call devmap_load(9F) on the handle of the mapping that generated the call to this entry point.

Accesses to portions of mappings that have had their mapping translations validated by a call to devmap_load() do not generate a call to devmap_access(). A subsequent call to devmap_unload() invalidates the mapping translations. This call enables devmap_access() to be called again.

If either devmap_load() or devmap_unload() returns an error, devmap_contextmgt() should immediately return that error. If the device driver encounters a hardware failure while restoring a device context, a -1 should be returned. Otherwise, after successfully handling the access request, devmap_contextmgt() should return zero. A return of other than zero from devmap_contextmgt() causes a SIGBUS or SIGSEGV to be sent to the process.

The following example shows how to manage a one-page device context.


Note –

xxctxsave() and xxctxrestore() are device-dependent context save and restore functions. xxctxsave() reads data from the registers and saves the data in the soft state structure. xxctxrestore() takes data that is saved in the soft state structure and writes the data to device registers. Note that the read, write, and save are all performed with the DDI/DKI data access routines.



Example 11–3 Using the devmap_contextmgt() Routine

static int
xxdevmap_contextmgt(devmap_cookie_t handle, void *devprivate,
    offset_t off, size_t len, uint_t type, uint_t rw)
{
    int    error;
    struct xxctx *ctxp = devprivate;
    struct xxstate *xsp = ctxp->xsp;
    mutex_enter(&xsp->ctx_lock);
    /* unload mapping for current context */
    if (xsp->current_ctx != NULL) {
        if ((error = devmap_unload(xsp->current_ctx->handle,
            off, len)) != 0) {
            xsp->current_ctx = NULL;
            mutex_exit(&xsp->ctx_lock);
            return (error);
        }
    }
    /* Switch device context - device dependent */
    if (xxctxsave(xsp->current_ctx, off, len) < 0) {
        xsp->current_ctx = NULL;
        mutex_exit(&xsp->ctx_lock);
        return (-1);
    }
    if (xxctxrestore(ctxp, off, len) < 0){
        xsp->current_ctx = NULL;
        mutex_exit(&xsp->ctx_lock);
        return (-1);
    }
    xsp->current_ctx = ctxp;
    /* establish mapping for new context and return */
    error = devmap_load(handle, off, len, type, rw);
    if (error)
        xsp->current_ctx = NULL;
    mutex_exit(&xsp->ctx_lock);
    return (error);
}

devmap_dup() Entry Point

The devmap_dup(9E) entry point is called when a device mapping is duplicated, for example, by a user process that calls fork(2). The driver is expected to generate new driver private data for the new mapping.

The syntax fordevmap_dup() is as follows:

int xxdevmap_dup(devmap_cookie_t handle, void *devprivate,
    devmap_cookie_t new-handle, void **new-devprivate);

where:

handle

Mapping handle of the mapping being duplicated.

new-handle

Mapping handle of the mapping that was duplicated.

devprivate

Pointer to the driver private data associated with the mapping being duplicated.

*new-devprivate

Should be set to point to the new driver private data for the new mapping.

Mappings that have been created with devmap_dup() by default have their mapping translations invalidated. Invalid mapping translations force a call to the devmap_access(9E) entry point the first time the mapping is accessed.

The following example shows a typical devmap_dup() routine.


Example 11–4 Using the devmap_dup() Routine

static int
xxdevmap_dup(devmap_cookie_t handle, void *devprivate,
    devmap_cookie_t new_handle, void **new_devprivate)
{
    struct xxctx *ctxp = devprivate;
    struct xxstate *xsp = ctxp->xsp;
    struct xxctx *newctx;
    /* Create a new context for the duplicated mapping */
    newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
    newctx->xsp = xsp;
    newctx->handle = new_handle;
    newctx->offset = ctxp->offset;
    newctx->flags = ctxp->flags;
    newctx->len = ctxp->len;
    mutex_enter(&xsp->ctx_lock);
    if (ctxp->flags & MAP_PRIVATE) {
        newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
        bcopy(ctxp->context, newctx->context, XXCTX_SIZE);
    } else {
        newctx->context = xsp->ctx_shared;
    }
    mutex_exit(&xsp->ctx_lock);
    *new_devprivate = newctx;
    return(0);
}

devmap_unmap() Entry Point

The devmap_unmap(9E) entry point is called when a mapping is unmapped. Unmapping can be caused by a user process exiting or by calling the munmap(2) system call.

The syntax for devmap_unmap() is as follows:

void xxdevmap_unmap(devmap_cookie_t handle, void *devprivate,
    offset_t off, size_t len, devmap_cookie_t new-handle1,
    void **new-devprivate1, devmap_cookie_t new-handle2,
    void **new-devprivate2);

where:

handle

Mapping handle of the mapping being freed.

devprivate

Pointer to the driver private data associated with the mapping.

off

Offset within the logical device memory at which the unmapping begins.

len

Length in bytes of the memory being unmapped.

new-handle1

Handle that the system uses to describe the new region that ends at off - 1. The value of new-handle1 can be NULL.

new-devprivate1

Pointer to be filled in by the driver with the private driver mapping data for the new region that ends at off -1. new-devprivate1 is ignored if new-handle1 is NULL.

new-handle2

Handle that the system uses to describe the new region that begins at off + len. The value of new-handle2 can be NULL.

new-devprivate2

Pointer to be filled in by the driver with the driver private mapping data for the new region that begins at off + len. new-devprivate2 is ignored if new-handle2 is NULL.

The devmap_unmap() routine is expected to free any driver private resources that were allocated when this mapping was created, either by devmap_map(9E) or by devmap_dup(9E). If the mapping is only partially unmapped, the driver must allocate new private data for the remaining mapping before freeing the old private data. Calling devmap_unload(9F) on the handle of the freed mapping is not necessary, even if this handle points to the mapping with the valid translations. However, to prevent future devmap_access(9E) problems, the device driver should make sure the current mapping representation is set to “no current mapping”.

The following example shows a typical devmap_unmap() routine.


Example 11–5 Using the devmap_unmap() Routine

static void
xxdevmap_unmap(devmap_cookie_t handle, void *devprivate,
    offset_t off, size_t len, devmap_cookie_t new_handle1,
    void **new_devprivate1, devmap_cookie_t new_handle2,
    void **new_devprivate2)
{
    struct xxctx *ctxp = devprivate;
    struct xxstate *xsp = ctxp->xsp;
    mutex_enter(&xsp->ctx_lock);
    /*
     * If new_handle1 is not NULL, we are unmapping
     * at the end of the mapping.
     */
    if (new_handle1 != NULL) {
        /* Create a new context structure for the mapping */
        newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
        newctx->xsp = xsp;
        if (ctxp->flags & MAP_PRIVATE) {
            /* allocate memory for the private context and copy it */
            newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
            bcopy(ctxp->context, newctx->context, XXCTX_SIZE);
        } else {
            /* point to the shared context */
            newctx->context = xsp->ctx_shared;
        }
        newctx->handle = new_handle1;
        newctx->offset = ctxp->offset;
        newctx->len = off - ctxp->offset;
        *new_devprivate1 = newctx;
    }
    /*
     * If new_handle2 is not NULL, we are unmapping
     * at the beginning of the mapping.
     */
    if (new_handle2 != NULL) {
        /* Create a new context for the mapping */
        newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
        newctx->xsp = xsp;
        if (ctxp->flags & MAP_PRIVATE) {
            newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
            bcopy(ctxp->context, newctx->context, XXCTX_SIZE);
        } else {
            newctx->context = xsp->ctx_shared;
        }
        newctx->handle = new_handle2;
        newctx->offset = off + len;
        newctx->flags = ctxp->flags;
        newctx->len = ctxp->len - (off + len - ctxp->off);
        *new_devprivate2 = newctx;
    }
    if (xsp->current_ctx == ctxp)
        xsp->current_ctx = NULL;
    mutex_exit(&xsp->ctx_lock);
    if (ctxp->flags & MAP_PRIVATE)
        kmem_free(ctxp->context, XXCTX_SIZE);
    kmem_free(ctxp, sizeof (struct xxctx));
}