您的位置:首页 > 产品设计 > UI/UE

DMA in user space (uio dma) //code analysis

2011-11-02 19:35 393 查看
Joseph (Honggang Yang)<ganggexiongqi@gmail.com>

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
, 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:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: