Associating Kernel Memory With User Mappings
Some device drivers might need to allocate kernel memory that is made accessible
to user programs through mmap(2). One example is setting up shared memory
for communication between two applications. Another example is sharing memory between a driver and
an application.
When exporting kernel memory to user applications, follow these steps:
Use ddi_umem_alloc(9F) to allocate kernel memory.
Use devmap_umem_setup(9F) to export the memory.
Use ddi_umem_free(9F) to free the memory when the memory is no longer needed.
Allocating Kernel Memory for User Access
Use ddi_umem_alloc(9F) to allocate kernel memory that is exported to applications. ddi_umem_alloc() uses the
following syntax:
void *ddi_umem_alloc(size_t size, int flag, ddi_umem_cookie_t
*cookiep);
where:
- size
Number of bytes to allocate.
- flag
Used to determine the sleep conditions and the memory type.
- cookiep
Pointer to a kernel memory cookie.
ddi_umem_alloc(9F) allocates page-aligned kernel memory. ddi_umem_alloc() returns a pointer to the allocated
memory. Initially, the memory is filled with zeroes. The number of bytes that
are allocated is a multiple of the system page size, which is rounded
up from the size parameter. The allocated memory can be used in
the kernel. This memory can be exported to applications as well. cookiep is
a pointer to the kernel memory cookie that describes the kernel memory being
allocated. cookiep is used in devmap_umem_setup(9F) when the driver exports the kernel memory
to a user application.
The flag argument indicates whether ddi_umem_alloc(9F) blocks or returns immediately, and whether
the allocated kernel memory is pageable. The values for the flag argument as follows:
- DDI_UMEM_NOSLEEP
Driver does not need to wait for memory to become available. Return NULL if memory is not available.
- DDI_UMEM_SLEEP
Driver can wait indefinitely for memory to become available.
- DDI_UMEM_PAGEABLE
Driver allows memory to be paged out. If not set, the memory is locked down.
The ddi_umem_lock() function can perform device-locked-memory checks. The function checks against the limit
value that is specified in project.max-locked-memory. If the current project locked-memory usage is
below the limit, the project's locked-memory byte count is increased. After the limit
check, the memory is locked. The ddi_umem_unlock() function unlocks the memory, and the
project's locked-memory byte count is decremented.
The accounting method that is used is an imprecise full price model. For
example, two callers of umem_lockmemory() within the same project with overlapping memory
regions are charged twice.
For information about the project.max-locked-memory and zone.max-locked_memory resource controls on Solaris systems
with zones installed, see Solaris Containers: Resource Management and Solaris Zones Developer’s Guide and see resource_controls(5).
The following example shows how to allocate kernel memory for application access. The
driver exports one page of kernel memory, which is used by multiple applications
as a shared memory area. The memory is allocated in segmap(9E) when
an application maps the shared page the first time. An additional page is
allocated if the driver has to support multiple application data models. For example,
a 64-bit driver might export memory both to 64-bit applications and to 32-bit
applications. 64-bit applications share the first page, and 32-bit applications share the second page.
Example 10-4 Using the ddi_umem_alloc() Routine
static int
xxsegmap(dev_t dev, off_t off, struct as *asp, caddr_t *addrp, off_t len,
unsigned int prot, unsigned int maxprot, unsigned int flags,
cred_t *credp)
{
int error;
minor_t instance = getminor(dev);
struct xxstate *xsp = ddi_get_soft_state(statep, instance);
size_t mem_size;
/* 64-bit driver supports 64-bit and 32-bit applications */
switch (ddi_mmap_get_model()) {
case DDI_MODEL_LP64:
mem_size = ptob(2);
break;
case DDI_MODEL_ILP32:
mem_size = ptob(1);
break;
}
mutex_enter(&xsp->mu);
if (xsp->umem == NULL) {
/* allocate the shared area as kernel pageable memory */
xsp->umem = ddi_umem_alloc(mem_size,
DDI_UMEM_SLEEP | DDI_UMEM_PAGEABLE, &xsp->ucookie);
}
mutex_exit(&xsp->mu);
/* Set up the user mapping */
error = devmap_setup(dev, (offset_t)off, asp, addrp, len,
prot, maxprot, flags, credp);
return (error);
}
Exporting Kernel Memory to Applications
Use devmap_umem_setup(9F) to export kernel memory to user applications. devmap_umem_setup() must be
called from the driver's devmap(9E) entry point. The syntax for devmap_umem_setup() is
as follows:
int devmap_umem_setup(devmap_cookie_t handle, dev_info_t *dip,
struct devmap_callback_ctl *callbackops, ddi_umem_cookie_t cookie,
offset_t koff, size_t len, uint_t maxprot, uint_t flags,
ddi_device_acc_attr_t *accattrp);
where:
- handle
Opaque structure used to describe the mapping.
- dip
Pointer to the device's dev_info structure.
- callbackops
Pointer to a devmap_callback_ctl(9S) structure.
- cookie
Kernel memory cookie returned by ddi_umem_alloc(9F).
- koff
Offset into the kernel memory specified by cookie.
- len
Length in bytes that is exported.
- maxprot
Specifies the maximum protection possible for the exported mapping.
- flags
Must be set to DEVMAP_DEFAULTS.
- accattrp
Pointer to a ddi_device_acc_attr(9S) structure.
handle is a device-mapping handle that the system uses to identify the mapping.
handle is passed in by the devmap(9E) entry point. dip is a pointer to
the device's dev_info structure. callbackops enables the driver to be notified of user
events on the mapping. Most drivers set callbackops to NULL when kernel
memory is exported.
koff and len specify a range within the kernel memory allocated by ddi_umem_alloc(9F).
This range is made accessible to the user's application mapping at the offset
that is passed in by the devmap(9E) entry point. Usually, the driver
passes the devmap(9E) offset directly to devmap_umem_setup(9F). The return address of mmap(2)
then maps to the kernel address returned by ddi_umem_alloc(9F). koff and len
must be page-aligned.
maxprot enables the driver to specify different protections for different regions within the
exported kernel memory. For example, one region might not allow write access by
only setting PROT_READ and PROT_USER.
The following example shows how to export kernel memory to an application. The
driver first checks whether the requested mapping falls within the allocated kernel memory
region. If a 64-bit driver receives a mapping request from a 32-bit application,
the request is redirected to the second page of the kernel memory area.
This redirection ensures that only applications compiled to the same data model share
the same page.
Example 10-5 devmap_umem_setup(9F) Routine
static int
xxdevmap(dev_t dev, devmap_cookie_t handle, offset_t off, size_t len,
size_t *maplen, uint_t model)
{
struct xxstate *xsp;
int error;
/* round up len to a multiple of a page size */
len = ptob(btopr(len));
/* check if the requested range is ok */
if (off + len > ptob(1))
return (ENXIO);
xsp = ddi_get_soft_state(statep, getminor(dev));
if (xsp == NULL)
return (ENXIO);
if (ddi_model_convert_from(model) == DDI_MODEL_ILP32)
/* request from 32-bit application. Skip first page */
off += ptob(1);
/* export the memory to the application */
error = devmap_umem_setup(handle, xsp->dip, NULL, xsp->ucookie,
off, len, PROT_ALL, DEVMAP_DEFAULTS, NULL);
*maplen = len;
return (error);
}
Freeing Kernel Memory Exported for User Access
When the driver is unloaded, the memory that was allocated by ddi_umem_alloc(9F) must
be freed by calling ddi_umem_free(9F).
void ddi_umem_free(ddi_umem_cookie_t cookie);
cookie is the kernel memory cookie returned by ddi_umem_alloc(9F).