Some device drivers allow applications to access device or kernel memory using mmap(2). Examples are frame buffer drivers, which allow the frame buffer to be mapped into a user thread, or a pseudo driver that communicates with an application using a shared kernel memory pool. This chapter describes how to associate device or kernel memory with user mappings.
In general, the steps a driver must take to export device or kernel memory are:
Set the D_DEVMAP flag in the cb_flag flag of the cb_ops(9S) structure.
Define a devmap(9E) driver 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).
The devmap(9E) entry point is called as a result of the mmap(2) system call. devmap(9E) is used to:
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.
int devmap(dev_t dev, devmap_cookie_t handle, offset_t off, size_t len, size_t *maplen, uint_t model);
Device whose memory is to be mapped
Device-mapping handle that the system creates and uses to describe a mapping to contiguous device or kernel memory
Logical offset within the application mapping which has to be translated by the driver to the corresponding offset within the device or kernel memory
Length (in bytes) of the memory being mapped.
Allows driver to associate different kernel memory regions or multiple physically discontiguous memory regions with one contiguous user application mapping
Data model type of the current thread
The system creates multiple mapping handles in one mmap(2) system call (for example, if the mapping contains multiple physically discontiguous memory regions).
Initially devmap(9E) is called with parameters off and len, which were passed by the application to mmap(2). devmap(9E) sets *maplen to the length from off to the end of a contiguous memory region. *maplen must be rounded up to a multiple of a page size. If *maplen is set to less than the original mapping length len, the system will repeatedly call devmap(9E) with a new mapping handle and adjusted off and len parameters until the initial mapping length is satisfied.
If a driver supports multiple application data models, model has to be passed to ddi_model_convert_from(9F) to determine whether there is a data model mismatch between the current thread and the device driver. The device driver might have to adjust the shape of data structures before exporting them to a user thread that supports a different data model. See Appendix C, Making a Device Driver 64-Bit Ready for more details.
devmap(9E) must return -1 if the logical offset, off, is out of the range of memory exported by the driver.
devmap_devmem_setup(9F) is provided to export device memory to user applications.
devmap_devmem_setup(9F) has to be called from the driver's devmap(9E) entry point.
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
Opaque device-mapping handle that the system uses to identify the mapping
Pointer to the device's dev_info structure
Pointer to a devmap_callback_ctl(9S) structure that allows the driver to be notified of user events on the mapping
Index number to the register address space set
Offset into the device memory
Length in bytes that is exported
Allows the driver to specify different protections for different regions within the exported device memory
Must be set to DEVMAP_DEFAULTS
Pointer to a ddi_device_acc_attr(9S) structure
roff and len describe a range within the device memory specified by the register set rnumber. The register specifications referred to by rnumber are described by the reg property. For devices with only one register set, pass zero for rnumber. The range described by roff and len are made accessible to the user's application mapping at the offset 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.
maxprot allows the driver to specify different protections for different regions within the exported device memory. For example, one region might not allow write access by setting only PROT_READ and PROT_USER.
Example 12-1 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), and 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); }
Some device drivers might need to allocate kernel memory that is made accessible to user programs by using mmap(2). Examples of this are setting up shared memory for communication between two applications or between driver and application.
In general, the steps for exporting kernel memory to user applications are:
Allocate kernel memory using ddi_umem_alloc(9F).
Export the memory using devmap_umem_setup(9F).
Free the memory using ddi_umem_free(9F) when no longer needed.
ddi_umem_alloc(9F) is provided to allocate kernel memory that is exported to applications:
void *ddi_umem_alloc(size_t size, int flag, ddi_umem_cookie_t *cookiep);
Number of bytes to allocate
Used to determine the sleep conditions and the memory type
Pointer to a kernel memory cookie
ddi_umem_alloc(9F) allocates page-aligned kernel memory and returns a pointer to the allocated memory. The initial contents of the memory is zero-filled. The number of bytes allocated is a multiple of the system page size (roundup of size). The allocated memory can be used in the kernel and can be exported to applications. cookiep is a pointer to the kernel memory cookie that describes the kernel memory being allocated. It 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) will block or return immediately, and whether the allocated kernel memory is pageable. Table 12-1 lists the values for flag.
Table 12-1 ddi_umem_alloc(9F) flag Values
Values |
Indicated Action |
---|---|
DDI_UMEM_NOSLEEP |
Driver does not need to wait for memory to become available. Return NULL if memory unavailable. |
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. |
Example 12-2 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 exporting memory to 64-bit and 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; #ifdef _MULTI_DATAMODEL /* 64-bit driver supports 64-bit and 32-bit applications */ mem_size = ptob(2); #else mem_size = ptob(1); #endif /* _MULTI_DATAMODEL */ 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); }
devmap_umem_setup(9F) is provided to export kernel memory to user applications. devmap_umem_setup(9F) must be called from the driver's devmap(9E) entry point:
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);
Opaque structure used to describe the mapping
Pointer to the device's dev_info structure.
Pointer to a devmap_callback_ctl(9S) structure
Kernel memory cookie returned by ddi_umem_alloc(9F)
Offset into the kernel memory specified by cookie
Length in bytes that is exported
Specifies the maximum protection possible for the exported mapping
Must be set to DEVMAP_DEFAULTS
Pointer to a ddi_device_acc_attr(9S) structure
handle is a device-mapping handle that the system uses to identify the mapping. It is passed in by the devmap(9E) entry point. dip is a pointer to the device's dev_info structure. callbackops allows 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 will be made accessible to the user's application mapping at the offset passed in by the devmap(9E) entry point. Usually the driver will pass the devmap(9E) offset directly to devmap_umem_setup(9F). The return address of mmap(2) will then map 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.
Example 12-3 shows how to export kernel memory to an application. The driver first checks if 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 ensures that only applications compiled to the same data model will 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); #ifdef _MULTI_DATAMODEL if (ddi_model_convert_from(model) == DDI_MODEL_ILP32) { /* request from 32-bit application. Skip first page */ off += ptob(1); } #endif /* _MULTI_DATAMODEL */ /* 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); }
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);
Kernel memory cookie returned by ddi_umem_alloc(9F)
cookie is the kernel memory cookie returned by ddi_umem_alloc(9F).