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

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值