Contents: uio-dma.c code review (V1.3)
Date: 11-02-2011
Last modified: 11-02-2011
-----------------------------------------------------------------------------------------------------------
Distributed and Embedded System Lab (分布式嵌入式系统实验室,兰州大学)
===============================================================
1. function list
2. function analysis
3. related structures
4. the source code
===============
1. function list
|- function
|| uio_dma_device_lock +
|| uio_dma_device_unlock +
|| __drop_dev_mappings +
|| uio_dma_device_free +
|| uio_dma_device_get +
|| uio_dma_device_put +
|| __device_lookup +
|| uio_dma_device_lookup +
|| uio_dma_device_open ======= ++++
|| uio_dma_device_close ====== ++++
|| __last_page +
|| uio_dma_area_free +
|| uio_dma_area_alloc +
|| uio_dma_area_get +
|| uio_dma_area_put +
|| uio_dma_area_lookup +
|| uio_dma_mapping_del +
|| uio_dma_mapping_add +
|| uio_dma_mapping_lookup +
|| uio_dma_context_lock +
|| uio_dma_context_unlock +
|| append_to +
|| uio_dma_cmd_alloc +
|| uio_dma_cmd_free +
|| uio_dma_cmd_map +
|| uio_dma_cmd_unmap +
|| uio_dma_ioctl +
|| __drop_ctx_areas +
|| uio_dma_close +
|| uio_dma_open +
|| uio_dma_poll +
|| uio_dma_read +
|| uio_dma_write +
|| uio_dma_vm_open +
|| uio_dma_vm_close +
|| uio_dma_vm_fault +
|| uio_dma_mmap +
|| uio_dma_init_module +
|| uio_dma_exit_module +
==============================
2. function analysis
1.
F: Register the misc device "uio-dma"
static int __init uio_dma_init_module(void)
Init device lists //INIT_LIST_HEAD
Init mutex used to protect uio_dma_dev_list//mutex_init
Register our misc device //misc_register[misc device is a special char dev]
2.
F: Unregister the misc device "uio-dma"
static void __exit uio_dma_exit_module(void)
//misc_deregister
3.
F: allocate a new uio-dma context and attached to the fd.
static int uio_dma_open(struct inode *inode, struct file * file)
/* Allocate new uio-dma context */ //kzalloc
Attach the uio-dma context with a fd.
4.
F: Try to free all the memory allocate under the context attached with
the fd which @inode belong to.
static int uio_dma_close(struct inode *inode, struct file *file)
As to the areas in context @uc, decrease their ref. counter.
If the counter become 0, "free" the areas. //__drop_ctx_areas
5.
F: As to the areas in context @uc, decrease their ref. counter.
If the counter become 0, "free" the areas.
static void __drop_ctx_areas(struct uio_dma_context *uc)
As to the areas in context @uc, decrease their ref. counter.
If the counter become 0, "free" the areas. //uio_dma_area_put
6.
F: Decrease @area's ref. counter, if counter become 0, "Free" the area.
static void uio_dma_area_put(struct uio_dma_area *area)
atomicly decrease @area's ref. counter.
if no one use it, then Release the area and related memory chunks.
//atomic_dec_and_test, uio_dma_area_free
7.
F:Release DMA area.
/*
* Release DMA area.
* Called only after all references to this area have been dropped.
*/
static void uio_dma_area_free(struct uio_dma_area *area)
Clear page Resered flag of all pages of all chunk belong to @area
and free the pages //__last_page, virt_to_page, ClearPageReserved
free the @area.
8.
F:get last page of the region dercribed by @addr and @size
static inline struct page *__last_page(void *addr, unsigned long size)
get last page of the region dercribed by @addr and @size//virt_to_page
9.
F:Fina a DMA area, map it to userspace.
static int uio_dma_mmap(struct file *file, struct vm_area_struct *vma)
Find a DMA area by offset(!We have allocate DMA area in uio_dma_ioctl()) //uio_dma_area_lookup
If this map is a partial mappings, reject it.// You can only map the whole dma area in u space.
Set the @vma's cache property indicated by struct uio_dma_area's cache member.
Create page table for every chunk in the dma area //page_to_pfn, virt_to_page, remap_pfn_range
Override the @vma's operations(do nothing here!) //vma->vm_ops = &uio_dma_mmap_ops;
NOTE:
In userspace:
mmap(NULL, da->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, da->mmap_offset);
In uio_dma_mmap():
unsigned long offset = vma->vm_pgoff * PAGE_SIZE;
...
// Find an area that matches the offset and mmap it.
area = uio_dma_area_lookup(uc, offset);
So we can draw a conclusion:
Member of struct uio_dma_area 'mmap_offset' is in page unit.
10.
F:Look up DMA area by offset.
/*
* Look up DMA area by offset.
* Must be called under context mutex.
*/
static struct uio_dma_area *uio_dma_area_lookup(struct uio_dma_context *uc, uint64_t offset)
11.
F:Do differenct opration depends on the @cmd
(UIO_DMA_ALLOC, UIO_DMA_FREE, UIO_DMA_MAP, UIO_DMA_UNMAP)
static long uio_dma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
Get uio_dma_context lock//uio_dma_context_lock
Do differenct opration depends on the @cmd // @cmd is passed from userspace ioctl()
Free uio_dma_context lock//uio_dma_context_unlock
12.
F:
static int uio_dma_cmd_alloc(struct uio_dma_context *uc, void __user *argp)
Copy the userspace's parameter(a uio_dma_alloc_req)//copy_from_user
Round up the chunk size to PAGE unit //roundup
Find list_head to append new area to //append_to
Allocate new DMA area, save every chunk's first page's kernel virtual address
in @area->addr[i] //uio_dma_area_alloc
/* Add to the context */
copy the @area's contents to userspace //copy_to_user
13.
F: return list_head to append new area to
/*
* Make sure new area (offset & size) does not everlap with
* the existing areas and return list_head to append new area to.
* Must be called under context mutex.
*/
static struct list_head *append_to(struct uio_dma_context *uc,
uint64_t *offset, unsigned int size)
14.
F:Allocate new DMA area, save every chunk's first page's kernel virtual address in @area->addr[i]
/*
* Allocate new DMA area.
*/
static struct uio_dma_area *uio_dma_area_alloc(uint64_t dma_mask, unsigned int memnode,
unsigned int cache, unsigned int chunk_size, unsigned int chunk_count)
allocate a uio_dma_area varible //kzalloc()
init the uio_dma_area varible
allocate pages for every chunk and save the (kernel virtual) address of
the first page in area->addr[i], set the last page's 'Reserved' flag//alloc_pages_node(),page_address()
return the uio_dma_area varible's pointer
15.
F: Delete dma area from @uc.area list and release the DMA area
static int uio_dma_cmd_free(struct uio_dma_context *uc, void __user *argp)
copy the uio_dma_free_req type variable from the u space//copy_from_user
Look up DMA area by offset //uio_dma_area_lookup
delete the dma area from the @uc.area list //list_del
Release DMA area // uio_dma_area_put
16.
F: Release DMA area
static void uio_dma_area_put(struct uio_dma_area *area)
Release DMA area //uio_dma_area_free, atomic_dec_and_test
17.
F: Create DMA mapping (it attaches uio dma area @argp.mmap_offset and uio dma device @argp.devid)
static int uio_dma_cmd_map(struct uio_dma_context *uc, void __user *argp)
copy the contents of the uio_dma_map_req variable from U space to @req //copy_from_user
find the uio dma area by @req.mmap_offset //uio_dma_area_lookup
Lookup UIO DMA device based by id, increments device refcount if found
and return pointer to it. //uio_dma_device_lookup
get the device's lock //uio_dma_device_lock
Look up DMA mapping by area and direction.//uio_dma_mapping_lookup
If the DMA mapping is not created before,
then, create a new DMA mapping attached to device @ud//uio_dma_mapping_add
//req.chunk_count = area->chunk_count; NOTE: the request dma map's chunk size and chunk_count may be changed
//req.chunk_size = area->chunk_size;
Copy the @req's contents to U space //copy_to_user
18.
F: Lookup UIO DMA device based by id, increments device refcount if found
and return pointer to it.
/*
* Lookup device by uio dma id.
* Caller must drop the reference to the returned
* device when it's done with it.
*/
static struct uio_dma_device* uio_dma_device_lookup(uint32_t id)
get lock uio_dma_dev_mutex //mutex_lock
Lookup UIO DMA device based by id, increments device refcount if found.//__device_lookup
free lock uio_dma_dev_mutex //mutex_unlock
19.
F:Lookup UIO DMA device based by id, increments device refcount if found.
/*
* Lookup UIO DMA device based by id.
* Must be called under uio_dma_dev_mutex.
* Increments device refcount if found.
*/
static struct uio_dma_device* __device_lookup(uint32_t id)
increments the device's reference counter by 1 and return pointer
to the uio dma device//uio_dma_device_get
20.
F:Look up DMA mapping by area and direction.
/*
* Look up DMA mapping by area and direction.
* Must be called under device mutex.
*/
static struct uio_dma_mapping *uio_dma_mapping_lookup(
struct uio_dma_device *ud, struct uio_dma_area *area,
unsigned int dir)
21.
F: Create a new DMA mapping attached to device @ud.
/*
* Add new DMA mapping.
* Must be called under device mutex.
*/
static int uio_dma_mapping_add(struct uio_dma_device *ud, struct uio_dma_area *area,
unsigned int dir, struct uio_dma_mapping **map)
allocate a new uio_dma_mapping variable @m//kzalloc
Atomicly increase the uio dma area's reference counter //uio_dma_area_get
create stream DMA mapping for every chunks in @area, save the bus address in @m->dmaddr[i] //dma_map_single
Add the new uio_dma_mapping to the @ud's mapping list //list_add
save the uio dma mapping's pointer in *@map
22.
F: Delete DMA mapping indicated by @argp->mmap_offset which belongs to uio dma
device @argp->devid, if no one reference the uio dma device, drop
all mappings belong to it and free the device.
static int uio_dma_cmd_unmap(struct uio_dma_context *uc, void __user *argp)
copy content of typeof uio_dma_unmap_req variable from U space //copy_from_user
get the pointer to uio dma area indicated by @argp->mmap_offset //uio_dma_area_lookup
get the pointer to uio dma device indicated by @argp->devid //uio_dma_device_lookup
get lock of the uio dma device //uio_dma_device_lock
Look up DMA mapping by area and direction. //uio_dma_mapping_lookup
If find the corresponding DMA mapping,
Delete DMA mapping //uio_dma_mapping_del
free the lock of the uio dma device //uio_dma_device_unlock
decrease the reference counter of @ud, if no one using it, drop
all mappings belong to it and free @ud. //uio_dma_device_put
23.
F: Delete DMA mapping
/*
* Delete DMA mapping.
* Must be called under device mutex.
*/
static void uio_dma_mapping_del(struct uio_dma_device *ud, struct uio_dma_mapping *m)
delete DMA mapping @m //dma_unmap_single
delete @m from the @ud's mapping list //list_del
Decrease @area's ref. counter, if counter become 0, "Free" the area. //uio_dma_area_put
free the @m // kfree
24.
F: decrease the reference counter of @ud, if no one using it, drop
all mappings belong to it and free @ud.
static void uio_dma_device_put(struct uio_dma_device *ud)
decrease the uio dma device's reference counter //atomic_dec_and_test
if no one using it then Free the last reference to the UIO DMA device,
and Drops all mappings and releases @ud . //uio_dma_device_free
25.
F:
/*
* Free the last reference to the UIO DMA device.
* Drops all mappings and releases 'struct device'.
*/
static void uio_dma_device_free(struct uio_dma_device *ud)
Delete DMA mapping belongs to the @ud//__drop_dev_mappings
26.
F:Delete DMA mapping belongs to the @ud
static void __drop_dev_mappings(struct uio_dma_device *ud)
Delete DMA mapping belongs to the @ud //uio_dma_mapping_del
++++++++++++++ Interfaces
27.
F:
/**
* Open UIO DMA device (UIO driver interface).
* UIO driver calls this function to allocate new device id
* which can then be used by user space to create DMA mappings.
*/
int uio_dma_device_open(struct device *dev, uint32_t *id)
{
struct uio_dma_device *ud = kzalloc(sizeof(*ud), GFP_KERNEL);
if (!ud)
return -ENOMEM;
INIT_LIST_HEAD(&ud->mappings);
mutex_init(&ud->mutex);
atomic_set(&ud->refcount, 1);
ud->device = get_device(dev);//increment reference count for device.
if (!ud->device) {
kfree(ud);
return -ENODEV;
}
mutex_lock(&uio_dma_dev_mutex);
ud->id = uio_dma_dev_nextid++; // allocate the device id
list_add(&ud->list, &uio_dma_dev_list);//add the @ud to the global uio dma device
mutex_unlock(&uio_dma_dev_mutex);
*id = ud->id;// return the uio dma device id to the caller
UIO_DMA_DBG("added device. id %u %s\n", *id, dev_name(dev));
return 0;
}
EXPORT_SYMBOL(uio_dma_device_open);
28.
F:
/**
* Close UIO DMA device (UIO driver interface).
* UIO driver calls this function when the device is closed.
* All current mappings are destroyed.
*/
int uio_dma_device_close(uint32_t id)
{
struct uio_dma_device *ud;
// This can race with uio_dma_mapping_add(), which is perfectly save.
// Mappings will be cleaned up when uio_dma_mapping_add() releases
// the reference.
mutex_lock(&uio_dma_dev_mutex);
ud = __device_lookup(id);
if (!ud) {
UIO_DMA_DBG("removing bad device. id %u\n", id);
mutex_unlock(&uio_dma_dev_mutex);
return -ENOENT;
}
list_del(&ud->list);
uio_dma_device_put(ud);
mutex_unlock(&uio_dma_dev_mutex);
UIO_DMA_DBG("removed device. id %u %s\n", id, dev_name(ud->device));
uio_dma_device_put(ud);
return 0;
}
EXPORT_SYMBOL(uio_dma_device_close);
++++++++++++++
3. Important structures
/*
* DMA device.
* Holds a reference to 'struct device' and a list of DMA mappings
*/
struct uio_dma_device {
struct list_head list;
struct list_head mappings;
struct mutex mutex;
atomic_t refcount;
struct device *device;
uint32_t id;
};
struct uio_dma_unmap_req {
uint64_t mmap_offset;//used to find the uio dma area to free
uint32_t devid; // which uio dma device the uio dma area belongs to
uint32_t flags; //
uint8_t direction;
};
/*
* DMA mapping.
* Attached to a device.
* Holds a reference to an area.
*/
struct uio_dma_mapping {
struct list_head list;
struct uio_dma_area *area;
unsigned int direction;
uint64_t dmaddr[0];//Bus addresses of the DMA buffer
};
struct uio_dma_map_req {
uint64_t mmap_offset;
uint32_t flags;
uint32_t devid;
uint8_t direction;
uint32_t chunk_count;
uint32_t chunk_size;
uint64_t dmaddr[0];
};
struct uio_dma_alloc_req {
uint64_t dma_mask; //Indicate the device's address ability
uint16_t memnode;//?????? in uio_dma_area_alloc() calls alloc_pages_node()...
uint16_t cache;// cache mode
uint32_t flags;
uint32_t chunk_size;
uint32_t chunk_count;
uint64_t mmap_offset;// !!! This can be changed when overlap happens
// after calling of the append_to()
};
/*
* DMA area.
* Describes a chunk of DMAable memory.
* Attached to a DMA context.
*/
NOTE: You can only map the whole dma area in u space.
struct uio_dma_area {
struct list_head list;
atomic_t refcount;//[k:rw] ref counter of this structure
unsigned long mmap_offset;//*[k:rw] In kernel part find uio_dma_area by 'mmap_offset',
// then map the area to the userspace when userspace
// called mmap() within API uio_dma_alloc()
//* mmap_offset is in page unit.
unsigned long size;
unsigned int chunk_size;// bytes size of every chunk
unsigned int chunk_count;// quantity of the chunks
uint8_t cache;//[u:w] Indicates the area's cache property when mapped to the u space
void *addr[0];// beginning address of every chunk(virtual address of the process where the dma area mapped to is
// returned by mmap() )
//!!!! uio_dma_area_alloc(): should be kernel virtual address.
//As to the low memory the kernel virtual is just the kernel logical address
//there just a offset between kernel logical address and its physical address.
//----> the virtual addresses are used to create DMA mapping(uio_dma_mapping_add())
};
/*
* DMA context.
* Attached to a fd.
*/
struct uio_dma_context {
struct mutex mutex; //
struct list_head areas; // Used to arrange the uio_dma_area <????? The last allocated uio
dma area is placed at the beginning of the list>
};
/* List of active devices */
static struct list_head uio_dma_dev_list; //devices list allocated to usersapce
static struct mutex uio_dma_dev_mutex;
static uint32_t uio_dma_dev_nextid;
4. source code
uio-dma.h
/*
UIO-DMA kernel back-end
Copyright (C) 2009 Qualcomm Inc. All rights reserved.
Written by Max Krasnyansky <maxk@qualcommm.com>
The UIO-DMA is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The UIO-DMA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
*/
#ifndef UIO_DMA_H
#define UIO_DMA_H
#include <linux/types.h>
#include <linux/device.h>
/* kernel interfaces */
int uio_dma_device_open(struct device *dev, uint32_t *devid);
int uio_dma_device_close(uint32_t devid);
/* ioctl defines */
/* Caching modes */
enum {
UIO_DMA_CACHE_DEFAULT = 0,
UIO_DMA_CACHE_DISABLE,
UIO_DMA_CACHE_WRITECOMBINE
};
/* DMA mapping direction */
enum {
UIO_DMA_BIDIRECTIONAL = 0,
UIO_DMA_TODEVICE,
UIO_DMA_FROMDEVICE
};
#define UIO_DMA_ALLOC _IOW('U', 200, int)
struct uio_dma_alloc_req {
uint64_t dma_mask;
uint16_t memnode;
uint16_t cache;
uint32_t flags;
uint32_t chunk_size;
uint32_t chunk_count;
uint64_t mmap_offset;
};
#define UIO_DMA_FREE _IOW('U', 201, int)
struct uio_dma_free_req {
uint64_t mmap_offset;
};
#define UIO_DMA_MAP _IOW('U', 202, int)
struct uio_dma_map_req {
uint64_t mmap_offset;
uint32_t flags;
uint32_t devid;
uint8_t direction;
uint32_t chunk_count;
uint32_t chunk_size;
uint64_t dmaddr[0];
};
#define UIO_DMA_UNMAP _IOW('U', 203, int)
struct uio_dma_unmap_req {
uint64_t mmap_offset;
uint32_t devid;
uint32_t flags;
uint8_t direction;
};
#endif /* UIO_DMA_H */
uio-dma.c
/*
UIO-DMA kernel backend
Copyright (C) 2009 Qualcomm Inc. All rights reserved.
Written by Max Krasnyansky <maxk@qualcommm.com>
The UIO-DMA is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The UIO-DMA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/sysctl.h>
#include <linux/wait.h>
#include <linux/miscdevice.h>
#include <linux/ioport.h>
#include <linux/pci.h>
#include <linux/file.h>
#include <asm/io.h>
#include "uio-dma.h"
#ifdef DBG
#define UIO_DMA_DBG(args...) printk(KERN_DEBUG "uio-dma: " args)
#else
#define UIO_DMA_DBG(args...)
#endif
#define UIO_DMA_INFO(args...) printk(KERN_INFO "uio-dma: " args)
#define UIO_DMA_ERR(args...) printk(KERN_ERR "uio-dma: " args)
#define VERSION "2.0"
char uio_dma_driver_name[] = "uio-dma";
char uio_dma_driver_string[] = "UIO DMA kernel backend";
char uio_dma_driver_version[] = VERSION;
char uio_dma_copyright[] = "Copyright (c) 2009 Qualcomm Inc. Written by Max Krasnyansky <maxk@qualcomm.com>";
/* List of active devices */
static struct list_head uio_dma_dev_list;
static struct mutex uio_dma_dev_mutex;
static uint32_t uio_dma_dev_nextid;
/*
* DMA device.
* Holds a reference to 'struct device' and a list of DMA mappings
*/
struct uio_dma_device {
struct list_head list;
struct list_head mappings;
struct mutex mutex;
atomic_t refcount;
struct device *device;
uint32_t id;
};
/*
* DMA area.
* Describes a chunk of DMAable memory.
* Attached to a DMA context.
*/
struct uio_dma_area {
struct list_head list;
atomic_t refcount;
unsigned long mmap_offset;
unsigned long size;
unsigned int chunk_size;
unsigned int chunk_count;
uint8_t cache;
void *addr[0];
};
/*
* DMA mapping.
* Attached to a device.
* Holds a reference to an area.
*/
struct uio_dma_mapping {
struct list_head list;
struct uio_dma_area *area;
unsigned int direction;
uint64_t dmaddr[0];
};
/*
* DMA context.
* Attached to a fd.
*/
struct uio_dma_context {
struct mutex mutex;
struct list_head areas;
};
static void uio_dma_mapping_del(struct uio_dma_device *ud, struct uio_dma_mapping *m);
/* ---- Devices ---- */
static void uio_dma_device_lock(struct uio_dma_device *ud)
{
mutex_lock(&ud->mutex);
}
static void uio_dma_device_unlock(struct uio_dma_device *ud)
{
mutex_unlock(&ud->mutex);
}
/*
* Drop all mappings on this device
*/
static void __drop_dev_mappings(struct uio_dma_device *ud)
{
struct uio_dma_mapping *m, *n;
list_for_each_entry_safe(m, n, &ud->mappings, list)
uio_dma_mapping_del(ud, m);
}
/*
* Free the last reference to the UIO DMA device.
* Drops all mappings and releases 'struct device'.
*/
static void uio_dma_device_free(struct uio_dma_device *ud)
{
__drop_dev_mappings(ud);
UIO_DMA_DBG("freed device. %s\n", dev_name(ud->device));
put_device(ud->device);
kfree(ud);
}
static struct uio_dma_device *uio_dma_device_get(struct uio_dma_device *ud)
{
atomic_inc(&ud->refcount);
return ud;
}
static void uio_dma_device_put(struct uio_dma_device *ud)
{
if (atomic_dec_and_test(&ud->refcount))
uio_dma_device_free(ud);
}
/*
* Lookup UIO DMA device based by id.
* Must be called under uio_dma_dev_mutex.
* Increments device refcount if found.
*/
static struct uio_dma_device* __device_lookup(uint32_t id)
{
struct uio_dma_device *ud;
list_for_each_entry(ud, &uio_dma_dev_list, list) {
if (ud->id == id)
return uio_dma_device_get(ud);
}
return NULL;
}
/*
* Lookup device by uio dma id.
* Caller must drop the reference to the returned
* device when it's done with it.
*/
static struct uio_dma_device* uio_dma_device_lookup(uint32_t id)
{
struct uio_dma_device *ud;
mutex_lock(&uio_dma_dev_mutex);
ud = __device_lookup(id);
mutex_unlock(&uio_dma_dev_mutex);
return ud;
}
/**
* Open UIO DMA device (UIO driver interface).
* UIO driver calls this function to allocate new device id
* which can then be used by user space to create DMA mappings.
*/
int uio_dma_device_open(struct device *dev, uint32_t *id)
{
struct uio_dma_device *ud = kzalloc(sizeof(*ud), GFP_KERNEL);
if (!ud)
return -ENOMEM;
INIT_LIST_HEAD(&ud->mappings);
mutex_init(&ud->mutex);
atomic_set(&ud->refcount, 1);
ud->device = get_device(dev);
if (!ud->device) {
kfree(ud);
return -ENODEV;
}
mutex_lock(&uio_dma_dev_mutex);
ud->id = uio_dma_dev_nextid++;
list_add(&ud->list, &uio_dma_dev_list);
mutex_unlock(&uio_dma_dev_mutex);
*id = ud->id;
UIO_DMA_DBG("added device. id %u %s\n", *id, dev_name(dev));
return 0;
}
EXPORT_SYMBOL(uio_dma_device_open);
/**
* Close UIO DMA device (UIO driver interface).
* UIO driver calls this function when the device is closed.
* All current mappings are destroyed.
*/
int uio_dma_device_close(uint32_t id)
{
struct uio_dma_device *ud;
// This can race with uio_dma_mapping_add(), which is perfectly save.
// Mappings will be cleaned up when uio_dma_mapping_add() releases
// the reference.
mutex_lock(&uio_dma_dev_mutex);
ud = __device_lookup(id);
if (!ud) {
UIO_DMA_DBG("removing bad device. id %u\n", id);
mutex_unlock(&uio_dma_dev_mutex);
return -ENOENT;
}
list_del(&ud->list);
uio_dma_device_put(ud);
mutex_unlock(&uio_dma_dev_mutex);
UIO_DMA_DBG("removed device. id %u %s\n", id, dev_name(ud->device));
uio_dma_device_put(ud);
return 0;
}
EXPORT_SYMBOL(uio_dma_device_close);
/* ---- Areas ---- */
static inline struct page *__last_page(void *addr, unsigned long size)
{
return virt_to_page(addr + (PAGE_SIZE << get_order(size)) - 1);
}
/*
* Release DMA area.
* Called only after all references to this area have been dropped.
*/
static void uio_dma_area_free(struct uio_dma_area *area)
{
struct page *page, *last;
int i;
UIO_DMA_DBG("area free. %p mmap_offset %lu\n", area, area->mmap_offset);
for (i=0; i < area->chunk_count; i++) {
last = __last_page(area->addr[i], area->chunk_size);
for (page = virt_to_page(area->addr[i]); page <= last; page++)
ClearPageReserved(page);
free_pages((unsigned long) area->addr[i], get_order(area->chunk_size));
}
kfree(area);
}
/*
* Allocate new DMA area.
*/
static struct uio_dma_area *uio_dma_area_alloc(uint64_t dma_mask, unsigned int memnode,
unsigned int cache, unsigned int chunk_size, unsigned int chunk_count)
{
struct uio_dma_area *area;
struct page *page, *last;
int i, gfp;
area = kzalloc(sizeof(*area) + sizeof(void *) * chunk_count, GFP_KERNEL);
if (!area)
return NULL;
UIO_DMA_DBG("area alloc. area %p chunk_size %u chunk_count %u\n",
area, chunk_size, chunk_count);
gfp = GFP_KERNEL | __GFP_NOWARN;
if (dma_mask < DMA_64BIT_MASK) {
if (dma_mask < DMA_32BIT_MASK)
gfp |= GFP_DMA;
else
gfp |= GFP_DMA32;
}
atomic_set(&area->refcount, 1);
area->chunk_size = chunk_size;
area->chunk_count = chunk_count;
area->size = chunk_size * chunk_count;
area->cache = cache;
for (i=0; i < chunk_count; i++) {
page = alloc_pages_node(memnode, gfp, get_order(chunk_size));
if (!page) {
area->chunk_count = i;
uio_dma_area_free(area);
return NULL;
}
area->addr[i] = page_address(page);
last = __last_page(area->addr[i], chunk_size);
for (; page <= last; page++)
SetPageReserved(page);
}
return area;
}
static struct uio_dma_area *uio_dma_area_get(struct uio_dma_area *area)
{
atomic_inc(&area->refcount);
return area;
}
static void uio_dma_area_put(struct uio_dma_area *area)
{
if (atomic_dec_and_test(&area->refcount))
uio_dma_area_free(area);
}
/*
* Look up DMA area by offset.
* Must be called under context mutex.
*/
static struct uio_dma_area *uio_dma_area_lookup(struct uio_dma_context *uc, uint64_t offset)
{
struct uio_dma_area *area;
UIO_DMA_DBG("area lookup. context %p offset %llu\n", uc, (unsigned long long) offset);
list_for_each_entry(area, &uc->areas, list) {
if (area->mmap_offset == offset)
return area;
}
return NULL;
}
/* ---- Mappings ---- */
/*
* Delete DMA mapping.
* Must be called under device mutex.
*/
static void uio_dma_mapping_del(struct uio_dma_device *ud, struct uio_dma_mapping *m)
{
unsigned int i;
UIO_DMA_DBG("mapping del. device %s mapping %p area %p\n",
dev_name(ud->device), m, m->area);
for (i=0; i < m->area->chunk_count; i++)
dma_unmap_single(ud->device, m->dmaddr[i], m->area->chunk_size, m->direction);
list_del(&m->list);
uio_dma_area_put(m->area);
kfree(m);
}
/*
* Add new DMA mapping.
* Must be called under device mutex.
*/
static int uio_dma_mapping_add(struct uio_dma_device *ud, struct uio_dma_area *area,
unsigned int dir, struct uio_dma_mapping **map)
{
struct uio_dma_mapping *m;
int i, n, err;
m = kzalloc(sizeof(*m) + sizeof(dma_addr_t) * area->chunk_count, GFP_KERNEL);
if (!m)
return -ENOMEM;
UIO_DMA_DBG("maping add. device %s area %p chunk_size %u chunk_count %u\n",
dev_name(ud->device), area, area->chunk_size, area->chunk_count);
m->area = uio_dma_area_get(area);
m->direction = dir;
for (i=0; i < area->chunk_count; i++) {
m->dmaddr[i] = dma_map_single(ud->device, area->addr[i], area->chunk_size, dir);
if (!m->dmaddr[i]) {
err = -EBADSLT;
goto failed;
}
UIO_DMA_DBG("maped. device %s area %p chunk #%u dmaddr %llx\n",
dev_name(ud->device), area, i,
(unsigned long long) m->dmaddr[i]);
}
list_add(&m->list, &ud->mappings);
*map = m;
return 0;
failed:
for (n = 0; n < i; n++)
dma_unmap_single(ud->device, m->dmaddr[n], m->area->chunk_size, dir);
uio_dma_area_put(m->area);
kfree(m);
return err;
}
/*
* Look up DMA mapping by area and direction.
* Must be called under device mutex.
*/
static struct uio_dma_mapping *uio_dma_mapping_lookup(
struct uio_dma_device *ud, struct uio_dma_area *area,
unsigned int dir)
{
struct uio_dma_mapping *m;
UIO_DMA_DBG("mapping lookup. device %s area %p dir %u\n",
dev_name(ud->device), area, dir);
list_for_each_entry(m, &ud->mappings, list) {
if (m->area == area && m->direction == dir)
return m;
}
return NULL;
}
/* ---- Context ---- */
static void uio_dma_context_lock(struct uio_dma_context *uc)
{
mutex_lock(&uc->mutex);
}
static void uio_dma_context_unlock(struct uio_dma_context *uc)
{
mutex_unlock(&uc->mutex);
}
/* ---- User interface ---- */
/*
* Make sure new area (offset & size) does not everlap with
* the existing areas and return list_head to append new area to.
* Must be called under context mutex.
*/
static struct list_head *append_to(struct uio_dma_context *uc,
uint64_t *offset, unsigned int size)
{
unsigned long start, end, astart, aend;
struct uio_dma_area *area;
struct list_head *last;
start = *offset;
end = start + size;
UIO_DMA_DBG("adding area. context %p start %lu end %lu\n", uc, start, end);
last = &uc->areas;
list_for_each_entry(area, &uc->areas, list) {
astart = area->mmap_offset;
aend = astart + area->size;
UIO_DMA_DBG("checking area. context %p start %lu end %lu\n", uc, astart, aend);
/* Since the list is sorted we know at this point that
* new area goes before this one. */
if (end <= astart)
break;
last = &area->list;
if ((start >= astart && start < aend) ||
(end > astart && end <= aend)) {
/* Found overlap. Set start to the end of the current
* area and keep looking. */
start = aend;
end = start + size;
continue;
}
}
*offset = start;
return last;
}
static int uio_dma_cmd_alloc(struct uio_dma_context *uc, void __user *argp)
{
struct uio_dma_alloc_req req;
struct uio_dma_area *area;
struct list_head *where;
unsigned long size;
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
if (!req.chunk_size || !req.chunk_count)
return -EINVAL;
req.chunk_size = roundup(req.chunk_size, PAGE_SIZE);
size = req.chunk_size * req.chunk_count;
UIO_DMA_DBG("alloc req enter. context %p offset %llu chunk_size %u chunk_count %u (total %lu)\n",
uc, (unsigned long long) req.mmap_offset, req.chunk_size, req.chunk_count, size);
where = append_to(uc, &req.mmap_offset, size);
if (!where)
return -EBUSY;
area = uio_dma_area_alloc(req.dma_mask, req.memnode, req.cache, req.chunk_size, req.chunk_count);
if (!area)
return -ENOMEM;
/* Add to the context */
area->mmap_offset = req.mmap_offset;
list_add(&area->list, where);
if (copy_to_user(argp, &req, sizeof(req))) {
list_del(&area->list);
uio_dma_area_put(area);
return EFAULT;
}
UIO_DMA_DBG("alloc req exit. context %p offset %llu size %lu mask %llx node %u\n", uc,
(unsigned long long) area->mmap_offset, area->size,
(unsigned long long) req.dma_mask, req.memnode);
return 0;
}
static int uio_dma_cmd_free(struct uio_dma_context *uc, void __user *argp)
{
struct uio_dma_free_req req;
struct uio_dma_area *area;
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
UIO_DMA_DBG("free req. context %p offset %llu\n", uc, req.mmap_offset);
area = uio_dma_area_lookup(uc, req.mmap_offset);
if (!area)
return -ENOENT;
list_del(&area->list);
uio_dma_area_put(area);
return 0;
}
static int uio_dma_cmd_map(struct uio_dma_context *uc, void __user *argp)
{
struct uio_dma_map_req req;
struct uio_dma_mapping *m;
struct uio_dma_area *area;
struct uio_dma_device *ud;
int err;
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
UIO_DMA_DBG("map req. context %p offset %llu devid %u\n", uc, req.mmap_offset, req.devid);
area = uio_dma_area_lookup(uc, req.mmap_offset);
if (!area)
return -ENOENT;
if (req.chunk_count < area->chunk_count)
return -EINVAL;
ud = uio_dma_device_lookup(req.devid);
if (!ud)
return -ENODEV;
uio_dma_device_lock(ud);
m = uio_dma_mapping_lookup(ud, area, req.direction);
if (m) {
err = -EALREADY;
goto out;
}
err = uio_dma_mapping_add(ud, area, req.direction, &m);
if (err)
goto out;
req.chunk_count = area->chunk_count;
req.chunk_size = area->chunk_size;
if (copy_to_user(argp, &req, sizeof(req)))
goto fault;
/* Copy dma addresses */
if (copy_to_user(argp + sizeof(req), m->dmaddr, sizeof(uint64_t) * area->chunk_count))
goto fault;
err = 0;
goto out;
fault:
err = EFAULT;
uio_dma_mapping_del(ud, m);
out:
uio_dma_device_unlock(ud);
uio_dma_device_put(ud);
return err;
}
static int uio_dma_cmd_unmap(struct uio_dma_context *uc, void __user *argp)
{
struct uio_dma_unmap_req req;
struct uio_dma_area *area;
struct uio_dma_mapping *m;
struct uio_dma_device *ud;
int err;
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
UIO_DMA_DBG("map req. context %p offset %llu devid %u\n", uc, req.mmap_offset, req.devid);
area = uio_dma_area_lookup(uc, req.mmap_offset);
if (!area)
return -ENOENT;
ud = uio_dma_device_lookup(req.devid);
if (!ud)
return -ENODEV;
uio_dma_device_lock(ud);
err = -ENOENT;
m = uio_dma_mapping_lookup(ud, area, req.direction);
if (m) {
uio_dma_mapping_del(ud, m);
err = 0;
}
uio_dma_device_unlock(ud);
uio_dma_device_put(ud);
return err;
}
static long uio_dma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct uio_dma_context *uc = file->private_data;
void __user * argp = (void __user *) arg;
int err;
UIO_DMA_DBG("ioctl. context %p cmd %d arg %lu\n", uc, cmd, arg);
if (!uc)
return -EBADFD;
uio_dma_context_lock(uc);
switch (cmd) {
case UIO_DMA_ALLOC:
err = uio_dma_cmd_alloc(uc, argp);
break;
case UIO_DMA_MAP:
err = uio_dma_cmd_map(uc, argp);
break;
case UIO_DMA_UNMAP:
err = uio_dma_cmd_unmap(uc, argp);
break;
case UIO_DMA_FREE:
err = uio_dma_cmd_free(uc, argp);
break;
default:
err = -EINVAL;
break;
};
uio_dma_context_unlock(uc);
return err;
}
static void __drop_ctx_areas(struct uio_dma_context *uc)
{
struct uio_dma_area *area, *n;
list_for_each_entry_safe(area, n, &uc->areas, list)
uio_dma_area_put(area);
}
static int uio_dma_close(struct inode *inode, struct file *file)
{
struct uio_dma_context *uc = file->private_data;
if (!uc)
return 0;
UIO_DMA_DBG("closed context %p\n", uc);
__drop_ctx_areas(uc);
file->private_data = NULL;
kfree(uc);
return 0;
}
static int uio_dma_open(struct inode *inode, struct file * file)
{
struct uio_dma_context *uc;
/* Allocate new context */
uc = kzalloc(sizeof(*uc), GFP_KERNEL);
if (!uc)
return -ENOMEM;
mutex_init(&uc->mutex);
INIT_LIST_HEAD(&uc->areas);
file->private_data = uc;
UIO_DMA_DBG("created context %p\n", uc);
return 0;
}
static unsigned int uio_dma_poll(struct file *file, poll_table *wait)
{
return -ENOSYS;
}
static ssize_t uio_dma_read(struct file * file, char __user * buf,
size_t count, loff_t *pos)
{
return -ENOSYS;
}
static ssize_t uio_dma_write(struct file * file, const char __user * buf,
size_t count, loff_t *pos)
{
return -ENOSYS;
}
static void uio_dma_vm_open(struct vm_area_struct *vma)
{
}
static void uio_dma_vm_close(struct vm_area_struct *vma)
{
}
static int uio_dma_vm_fault(struct vm_area_struct *area,
struct vm_fault *fdata)
{
return VM_FAULT_SIGBUS;
}
static struct vm_operations_struct uio_dma_mmap_ops = {
.open = uio_dma_vm_open,
.close = uio_dma_vm_close,
.fault = uio_dma_vm_fault
};
static int uio_dma_mmap(struct file *file, struct vm_area_struct *vma)
{
struct uio_dma_context *uc = file->private_data;
struct uio_dma_area *area;
unsigned long start = vma->vm_start;
unsigned long size = vma->vm_end - vma->vm_start;
unsigned long offset = vma->vm_pgoff * PAGE_SIZE;
unsigned long pfn;
int i;
if (!uc)
return -EBADFD;
UIO_DMA_DBG("mmap. context %p start %lu size %lu offset %lu\n", uc, start, size, offset);
// Find an area that matches the offset and mmap it.
area = uio_dma_area_lookup(uc, offset);
if (!area)
return -ENOENT;
// We do not do partial mappings, sorry
if (area->size != size)
return -EOVERFLOW;
switch (area->cache) {
case UIO_DMA_CACHE_DISABLE:
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
break;
case UIO_DMA_CACHE_WRITECOMBINE:
#ifdef pgprot_writecombine
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
#endif
break;
default:
/* Leave as is */
break;
}
for (i=0; i < area->chunk_count; i++) {
pfn = page_to_pfn(virt_to_page(area->addr[i]));
if (remap_pfn_range(vma, start, pfn, area->chunk_size, vma->vm_page_prot))
return -EIO;
start += area->chunk_size;
}
vma->vm_ops = &uio_dma_mmap_ops;
return 0;
}
static struct file_operations uio_dma_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = uio_dma_read,
.write = uio_dma_write,
.poll = uio_dma_poll,
.open = uio_dma_open,
.release = uio_dma_close,
.mmap = uio_dma_mmap,
.unlocked_ioctl = uio_dma_ioctl,
};
static struct miscdevice uio_dma_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "uio-dma",
.fops = &uio_dma_fops,
};
static int __init uio_dma_init_module(void)
{
int err;
INIT_LIST_HEAD(&uio_dma_dev_list);
mutex_init(&uio_dma_dev_mutex);
printk(KERN_INFO "%s - version %s\n", uio_dma_driver_string, uio_dma_driver_version);
printk(KERN_INFO "%s\n", uio_dma_copyright);
err = misc_register(&uio_dma_miscdev);
if (err) {
UIO_DMA_ERR("failed to register misc device\n");
return err;
}
return err;
}
static void __exit uio_dma_exit_module(void)
{
misc_deregister(&uio_dma_miscdev);
}
module_init(uio_dma_init_module);
module_exit(uio_dma_exit_module);
/* ---- */
MODULE_AUTHOR("Max Krasnyansky <maxk@qualcomm.com>");
MODULE_DESCRIPTION("uio-dma kernel backend");
MODULE_LICENSE("GPL");
MODULE_VERSION(VERSION);
ref: