Mapping Device and Kernel Memory
Some device drivers allow applications to access device or kernel memory through mmap(2). Frame buffer drivers, for example, enable the frame buffer to be mapped into a user thread. Another example would be a pseudo driver that uses a shared kernel memory pool to communicate with an application. This chapter provides information on the following subjects:
10.1. Memory Mapping Overview
The steps that a driver must take to export device or kernel memory are as follows:
-
Set the
D_DEVMAP
flag in thecb_flag
flag of the cb_ops(9S) structure. -
Define a devmap(9E) driver entry point and optional segmap(9E) entry point to export the mapping.
-
Use devmap_devmem_setup(9F) to set up user mappings to a device. To set up user mappings to kernel memory, use devmap_umem_setup(9F).
10.2. Exporting the Mapping
This section describes how to use the segmap(9E) and devmap(9E) entry points.
10.2.1. The segmap(9E) Entry Point
The segmap(9E) entry point
is responsible for setting up a memory mapping requested by an mmap(2) system call. Drivers for many
memory-mapped devices use ddi_devmap_segmap(9F) as the entry point rather
than defining their own segmap(9E) routine. By providing a segmap
entry point, a driver can take care of general tasks before or
after creating the mapping. For example, the driver can check mapping permissions
and allocate private mapping resources. The driver can also make adjustments
to the mapping to accommodate non-page-aligned device buffers. The segmap
entry point must call the ddi_devmap_segmap(9F) function before returning.
The ddi_devmap_segmap
function calls the driver's devmap(9E) entry point
to perform the actual mapping.
The segmap
function has the following syntax:
int segmap(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);
where:
- dev
-
Device whose memory is to be mapped.
- off
-
Offset within device memory at which mapping begins.
- asp
-
Pointer to the address space into which the device memory should be mapped.
Note that this argument can be either a
struct as *
, as shown in segmap(9E) Routine, or addi_as_handle_t
, as shown in Using the segmap Function to Change the Address Returned by the mmap Call. This is because ddidevmap.h includes the following declaration:typedef struct as *ddi_as_handle_t
- addrp
-
Pointer to the address in the address space to which the device memory should be mapped.
- len
-
Length (in bytes) of the memory being mapped.
- prot
-
A bit field that specifies the protections. Possible settings are PROT_READ, PROT_WRITE, PROT_EXEC, PROT_USER, and PROT_ALL. See the man page for details.
- maxprot
-
Maximum protection flag possible for attempted mapping. The PROT_WRITE bit can be masked out if the user opened the special file read-only.
- flags
-
Flags that indicate the type of mapping. Possible values include MAP_SHARED and MAP_PRIVATE.
- credp
-
Pointer to the user credentials structure.
In the following example, the driver controls a frame buffer that allows
write-only mappings. The driver returns EINVAL
if the
application tries to gain read access and then calls ddi_devmap_segmap(9F) to set up the user mapping.
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)
{
if (prot & PROT_READ)
return (EINVAL);
return (ddi_devmap_segmap(dev, off, as, addrp,
len, prot, maxprot, flags, cred));
}
The following example shows how to handle a device that has a buffer
that is not page-aligned in its register space. This example maps a buffer
that starts at offset 0x800, so that mmap(2) returns an address that corresponds to the start of
the buffer. The devmap_devmem_setup(9F) function
maps entire pages, requires the mapping to be page aligned, and returns an
address to the start of a page. If this address is passed through segmap(9E), or if no segmap
entry point is defined, mmap
returns
the address that corresponds to the start of the page, not the address that
corresponds to the start of the buffer. In this example, the buffer offset
is added to the page-aligned address that was returned by devmap_devmem_setup
so that the resulting address returned is the desired start of
the buffer.
#define BUFFER_OFFSET 0x800
int
xx_segmap(dev_t dev, off_t off, ddi_as_handle_t as, caddr_t *addrp, off_t len,
uint_t prot, uint_t maxprot, uint_t flags, cred_t *credp)
{
int rval;
unsigned long pagemask = ptob(1L) - 1L;
if ((rval = ddi_devmap_segmap(dev, off, as, addrp, len, prot, maxprot,
flags, credp)) == DDI_SUCCESS) {
/*
* The address returned by ddi_devmap_segmap is the start of the page
* that contains the buffer. Add the offset of the buffer to get the
* final address.
*/
*addrp += BUFFER_OFFSET & pagemask);
}
return (rval);
}
10.2.2. The devmap(9E) Entry Point
The devmap(9E) entry point is called from the ddi_devmap_segmap(9F) function inside the segmap(9E) entry point.
The devmap(9E) entry
point is called as a result of the mmap(2) system call. The devmap(9E) function is called to export device
memory or kernel memory to user applications. The devmap
function
is used for the following operations:
-
Validate the user mapping to the device or kernel memory
-
Translate the logical offset within the application mapping to the corresponding offset within the device or kernel memory
-
Pass the mapping information to the system for setting up the mapping
The devmap
function has the following syntax:
int devmap(dev_t dev, devmap_cookie_t handle, offset_t off,
size_t len, size_t *maplen, uint_t model);
where:
- dev
-
Device whose memory is to be mapped.
- handle
-
Device-mapping handle that the system creates and uses to describe a mapping to contiguous memory in the device or kernel.
- off
-
Logical offset within the application mapping that has to be translated by the driver to the corresponding offset within the device or kernel memory.
- len
-
Length (in bytes) of the memory being mapped.
- maplen
-
Enables driver to associate different kernel memory regions or multiple physically discontiguous memory regions with one contiguous user application mapping.
- model
-
Data model type of the current thread.
The system creates multiple mapping handles in one mmap(2) system call. For example, the mapping might contain multiple physically discontiguous memory regions.
Initially, devmap(9E) is
called with the parameters off and len.
These parameters are passed by the application to mmap(2). devmap(9E) sets *
maplen to the length from off to
the end of a contiguous memory region. The *
maplen value must be rounded up to a multiple of a page size. The *
maplen value can be set to less than
the original mapping length len
. If so, the system
uses a new mapping handle with adjusted off and len parameters to call devmap(9E) repeatedly until the initial
mapping length is satisfied.
If a driver supports multiple application data models, model must
be passed to ddi_model_convert_from(9F). The ddi_model_convert_from
function determines whether a data model mismatch exists between
the current thread and the device driver. The device driver might have to
adjust the shape of data structures before exporting the structures to a user
thread that supports a different data model. See Making a Device Driver 64-Bit Ready page for more details.
The devmap(9E) entry
point must return -1
if the logical offset, off,
is out of the range of memory exported by the driver.
10.3. Associating Device Memory With User Mappings
Call devmap_devmem_setup(9F) from the driver's devmap(9E) entry point to export device memory to user applications.
The devmap_devmem_setup(9F) function has the following syntax:
int devmap_devmem_setup(devmap_cookie_t handle, dev_info_t *dip,
struct devmap_callback_ctl *callbackops, uint_t rnumber,
offset_t roff, size_t len, uint_t maxprot, uint_t flags,
ddi_device_acc_attr_t *accattrp);
where:
- handle
-
Opaque device-mapping handle that the system uses to identify the mapping.
- dip
-
Pointer to the device's
dev_info
structure. - callbackops
-
Pointer to a devmap_callback_ctl(9S) structure that enables the driver to be notified of user events on the mapping.
- rnumber
-
Index number to the register address space set.
- roff
-
Offset into the device memory.
- len
-
Length in bytes that is exported.
- maxprot
-
Allows the driver to specify different protections for different regions within the exported device memory.
- flags
-
Must be set to
DEVMAP_DEFAULTS
. - accattrp
-
Pointer to a ddi_device_acc_attr(9S) structure.
The roff and len arguments
describe a range within the device memory specified by the register set rnumber. The register specifications that are referred to by rnumber are described by the reg
property.
For devices with only one register set, pass zero for rnumber
.
The range is defined by roff and len.
The 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_devmem_setup(9F). The return address of mmap(2) then maps to the beginning address of the register
set.
The maxprot argument enables the driver to
specify different protections for different regions within the exported device
memory. For example, to disallow write access for a region, set only PROT_READ
and PROT_USER
for that region.
The following example shows how to export device memory to an application. The driver first determines whether the requested mapping falls within the device memory region. The size of the device memory is determined using ddi_dev_regsize(9F). The length of the mapping is rounded up to a multiple of a page size using ptob(9F) and btopr(9F). Then devmap_devmem_setup(9F) is called to export the device memory to the application.
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, rnumber;
off_t regsize;
/* Set up data access attribute structure */
struct ddi_device_acc_attr xx_acc_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
xsp = ddi_get_soft_state(statep, getminor(dev));
if (xsp == NULL)
return (-1);
/* use register set 0 */
rnumber = 0;
/* get size of register set */
if (ddi_dev_regsize(xsp->dip, rnumber, ®size) != DDI_SUCCESS)
return (-1);
/* round up len to a multiple of a page size */
len = ptob(btopr(len));
if (off + len > regsize)
return (-1);
/* Set up the device mapping */
error = devmap_devmem_setup(handle, xsp->dip, NULL, rnumber,
off, len, PROT_ALL, DEVMAP_DEFAULTS, &xx_acc_attr);
/* acknowledge the entire range */
*maplen = len;
return (error);
}
10.4. 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.
10.4.1. 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 illumos systems with
zones installed, see illumos Containers: Resource Management and illumos 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.
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);
}
10.4.2. 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.
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);
}
10.4.3. 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).