This page should describe how "maps" and mmaping are handled in the DRM.
Maps were originally areas set up by the X Server for DRI clients to mmap into their space, using the DRM to provide the privelege to do so and handling of physical offsets. Maps have grown since then, and are sometimes areas in the kernel not intended for userland consumption (DRMRESTRICTED maps), and ares sometimes set up by the kernel with out the X Server's awareness.
But originally, the X Server did an addmap of a certain offset, creating a map in its list, and returning the handle. The handle identified the map for future use by userland, for example for removing the map or mmapping it. The DRM internally maintained an offset, which was not necessarily equal to the handle, for its own use.
Here's a listing of what handles and offsets are for various mapping types:
type | handle | offset | handle passed to user space |
REGISTERS | kernel virtual address | bus address | offset |
FRAMEBUFFER | 0 | bus address | offset |
SHM | kernel virtual address | kernel virtual address | handle |
AGP | 0 | bus address | offset |
SCATTER_GATHER | kernel virtual address | kernel virtual address | offset |
CONSISTENT | kernel virtual address | bus address | n/a |
Typically, the X Server communicates the handle returned from addmap to the client driver through its driver private record (XF86DRI protocol).
In all of the following cases, the handle value is used as an offset to mmap:
- The r128 client driver maps the register, gart texture handles.
- The radeon client driver maps the register, status, and gart texture handles.
- The sis client driver maps the register and agp handles. Separate from maps, but also involved in the process, are bufs, which are used for DMA handling. At least radeon and r128 use this. These are set up with the drmAddBufs call in the server, and are either type AGP, SHM, or PCI (= consistent memory). Client drivers call drmMapBufs on the file descriptor. drmMapBufs calls into the kernel to get how many buffers there are, what their offsets are, and then actually map them.
So far map handles are of a machine dependent size. On 32-bit platform they are 32 bit wide while on 64-bit platforms their width is 64 bit. This creates problems on bi-arch architectures that can both run 32 and 64 bit binaries:
- The driver private rec that's passed between the Xserver and the DRI client has a machine dependent size making it impossible to pass data between different machine size binaries (64-bit Xserver and 32-bit DRI client for example).
- 64-bit handle that does not fit into 32-bit cannot be used as offset in a 32-bit binary (unless the code is compiled with -D_FILE_OFFSET_BITS=64).
From the table above we can see the following:
handle
contains either the kernel virtual address or 0 whileoffset
contains either the kernel virtual address or the bus base. Furthermore the kernel virtual address is meaningless in user space. Theoffset
value of the drm_handle_t structure is not read back to user space. Insteadhandle
contains the value ofoffset
which is used to identify the map from user space. We could therefore usehandle
to pass a 32-bit value back to user space. If we assume that bus base addresses always fit into 32-bit. This can be done by:- finding a value range that is guranteed to never conflict with bus base addresses and storing the result in
offset
instead of the kernel virtual address or - adding another element to the drm_map_t struct that uniquly identifies the map and passing this back to user space. We can pass the bus base to user space if feasable (ie. it fits in 32 bit and the value has not been used to identify another range). This would require creating a new struct for in kernel use, so that nothing changes for user space.
This can be done by changing the name of every occurance of drm_map_t (except where copying from and to user space) or by using a different name for the drm_map_t in the kernel. The latter will produce a smaller patch, the former may be less confusing. Inside of the DRM, the ?MapBufs ioctl does an mmap on behalf of the client, using the offset from the "agp_buffer_map," which is set by the driver to be the map that its buffers come from -- added as above as type AGP or SCATTER_GATHER or CONSISTENT.
- finding a value range that is guranteed to never conflict with bus base addresses and storing the result in
Now, in the mmap handler, the map to mmap from is decided using the offset of the map, not the handle. This makes ?MapBufs work, and SHM, but I don't see how the register mmaping actually functions, since the kernel virtual address is going to be different from the bus address. And, in the framebuffer case, passing 0 in for the handle seems like it's going to trigger the "drm_mmap_dma" case and fail. So I'm clearly missing something.
Actually I don't see how the SHM case will work either:
In drm_mmap()
int drm_mmap(struct file *filp, struct vm_area_struct *vma)
does:
off = dev->driver->get_map_ofs(map);
if (off == VM_OFFSET(vma))
break;
to find the map region that should be mapped.
In the case of DRM_SHM drm_addmap()
executes the following code:
#ifdef CONFIG_COMPAT
/* Assign a 32-bit handle for _DRM_SHM mappings */
/* We do it here so that dev->struct_sem protects the increment */
if (map->type == _DRM_SHM)
map->offset = map32_handle += PAGE_SIZE;
#endif
Therefore map->offset
isn't a real virtual address any more but more or less a 'tag' identifying the map.
After identifying the correct map drm_mmap() goes on doing:
offset = dev->driver->get_reg_ofs(dev);
#ifdef __sparc__
if (io_remap_page_range(DRM_RPR_ARG(vma) vma->vm_start,
VM_OFFSET(vma) + offset,
vma->vm_end - vma->vm_start,
vma->vm_page_prot, 0))
#else
if (remap_pfn_range(vma, vma->vm_start,
(VM_OFFSET(vma) + offset) >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot))
#endif`
This doesn't seem to work as offset
is just a tag, not a virtual address.