2005-04-17 02:20:36 +04:00
/*
* DMA region bookkeeping routines
*
* Copyright ( C ) 2002 Maas Digital LLC
*
* This code is licensed under the GPL . See the file COPYING in the root
* directory of the kernel sources for details .
*/
# include <linux/module.h>
# include <linux/vmalloc.h>
# include <linux/slab.h>
# include <linux/mm.h>
# include "dma.h"
/* dma_prog_region */
void dma_prog_region_init ( struct dma_prog_region * prog )
{
prog - > kvirt = NULL ;
prog - > dev = NULL ;
prog - > n_pages = 0 ;
prog - > bus_addr = 0 ;
}
int dma_prog_region_alloc ( struct dma_prog_region * prog , unsigned long n_bytes , struct pci_dev * dev )
{
/* round up to page size */
n_bytes = PAGE_ALIGN ( n_bytes ) ;
prog - > n_pages = n_bytes > > PAGE_SHIFT ;
prog - > kvirt = pci_alloc_consistent ( dev , n_bytes , & prog - > bus_addr ) ;
if ( ! prog - > kvirt ) {
printk ( KERN_ERR " dma_prog_region_alloc: pci_alloc_consistent() failed \n " ) ;
dma_prog_region_free ( prog ) ;
return - ENOMEM ;
}
prog - > dev = dev ;
return 0 ;
}
void dma_prog_region_free ( struct dma_prog_region * prog )
{
if ( prog - > kvirt ) {
pci_free_consistent ( prog - > dev , prog - > n_pages < < PAGE_SHIFT , prog - > kvirt , prog - > bus_addr ) ;
}
prog - > kvirt = NULL ;
prog - > dev = NULL ;
prog - > n_pages = 0 ;
prog - > bus_addr = 0 ;
}
/* dma_region */
void dma_region_init ( struct dma_region * dma )
{
dma - > kvirt = NULL ;
dma - > dev = NULL ;
dma - > n_pages = 0 ;
dma - > n_dma_pages = 0 ;
dma - > sglist = NULL ;
}
int dma_region_alloc ( struct dma_region * dma , unsigned long n_bytes , struct pci_dev * dev , int direction )
{
unsigned int i ;
/* round up to page size */
n_bytes = PAGE_ALIGN ( n_bytes ) ;
dma - > n_pages = n_bytes > > PAGE_SHIFT ;
dma - > kvirt = vmalloc_32 ( n_bytes ) ;
if ( ! dma - > kvirt ) {
printk ( KERN_ERR " dma_region_alloc: vmalloc_32() failed \n " ) ;
goto err ;
}
/* Clear the ram out, no junk to the user */
memset ( dma - > kvirt , 0 , n_bytes ) ;
/* allocate scatter/gather list */
dma - > sglist = vmalloc ( dma - > n_pages * sizeof ( * dma - > sglist ) ) ;
if ( ! dma - > sglist ) {
printk ( KERN_ERR " dma_region_alloc: vmalloc(sglist) failed \n " ) ;
goto err ;
}
/* just to be safe - this will become unnecessary once sglist->address goes away */
memset ( dma - > sglist , 0 , dma - > n_pages * sizeof ( * dma - > sglist ) ) ;
/* fill scatter/gather list with pages */
for ( i = 0 ; i < dma - > n_pages ; i + + ) {
unsigned long va = ( unsigned long ) dma - > kvirt + ( i < < PAGE_SHIFT ) ;
dma - > sglist [ i ] . page = vmalloc_to_page ( ( void * ) va ) ;
dma - > sglist [ i ] . length = PAGE_SIZE ;
}
/* map sglist to the IOMMU */
dma - > n_dma_pages = pci_map_sg ( dev , dma - > sglist , dma - > n_pages , direction ) ;
if ( dma - > n_dma_pages = = 0 ) {
printk ( KERN_ERR " dma_region_alloc: pci_map_sg() failed \n " ) ;
goto err ;
}
dma - > dev = dev ;
dma - > direction = direction ;
return 0 ;
err :
dma_region_free ( dma ) ;
return - ENOMEM ;
}
void dma_region_free ( struct dma_region * dma )
{
if ( dma - > n_dma_pages ) {
pci_unmap_sg ( dma - > dev , dma - > sglist , dma - > n_pages , dma - > direction ) ;
dma - > n_dma_pages = 0 ;
dma - > dev = NULL ;
}
vfree ( dma - > sglist ) ;
dma - > sglist = NULL ;
vfree ( dma - > kvirt ) ;
dma - > kvirt = NULL ;
dma - > n_pages = 0 ;
}
/* find the scatterlist index and remaining offset corresponding to a
given offset from the beginning of the buffer */
static inline int dma_region_find ( struct dma_region * dma , unsigned long offset , unsigned long * rem )
{
int i ;
unsigned long off = offset ;
for ( i = 0 ; i < dma - > n_dma_pages ; i + + ) {
if ( off < sg_dma_len ( & dma - > sglist [ i ] ) ) {
* rem = off ;
break ;
}
off - = sg_dma_len ( & dma - > sglist [ i ] ) ;
}
BUG_ON ( i > = dma - > n_dma_pages ) ;
return i ;
}
dma_addr_t dma_region_offset_to_bus ( struct dma_region * dma , unsigned long offset )
{
2005-07-10 04:01:23 +04:00
unsigned long rem = 0 ;
2005-04-17 02:20:36 +04:00
struct scatterlist * sg = & dma - > sglist [ dma_region_find ( dma , offset , & rem ) ] ;
return sg_dma_address ( sg ) + rem ;
}
void dma_region_sync_for_cpu ( struct dma_region * dma , unsigned long offset , unsigned long len )
{
int first , last ;
unsigned long rem ;
if ( ! len )
len = 1 ;
first = dma_region_find ( dma , offset , & rem ) ;
last = dma_region_find ( dma , offset + len - 1 , & rem ) ;
pci_dma_sync_sg_for_cpu ( dma - > dev , & dma - > sglist [ first ] , last - first + 1 , dma - > direction ) ;
}
void dma_region_sync_for_device ( struct dma_region * dma , unsigned long offset , unsigned long len )
{
int first , last ;
unsigned long rem ;
if ( ! len )
len = 1 ;
first = dma_region_find ( dma , offset , & rem ) ;
last = dma_region_find ( dma , offset + len - 1 , & rem ) ;
pci_dma_sync_sg_for_device ( dma - > dev , & dma - > sglist [ first ] , last - first + 1 , dma - > direction ) ;
}
# ifdef CONFIG_MMU
/* nopage() handler for mmap access */
static struct page *
dma_region_pagefault ( struct vm_area_struct * area , unsigned long address , int * type )
{
unsigned long offset ;
unsigned long kernel_virt_addr ;
struct page * ret = NOPAGE_SIGBUS ;
struct dma_region * dma = ( struct dma_region * ) area - > vm_private_data ;
if ( ! dma - > kvirt )
goto out ;
if ( ( address < ( unsigned long ) area - > vm_start ) | |
( address > ( unsigned long ) area - > vm_start + ( dma - > n_pages < < PAGE_SHIFT ) ) )
goto out ;
if ( type )
* type = VM_FAULT_MINOR ;
offset = address - area - > vm_start ;
kernel_virt_addr = ( unsigned long ) dma - > kvirt + offset ;
ret = vmalloc_to_page ( ( void * ) kernel_virt_addr ) ;
get_page ( ret ) ;
out :
return ret ;
}
static struct vm_operations_struct dma_region_vm_ops = {
. nopage = dma_region_pagefault ,
} ;
int dma_region_mmap ( struct dma_region * dma , struct file * file , struct vm_area_struct * vma )
{
unsigned long size ;
if ( ! dma - > kvirt )
return - EINVAL ;
/* must be page-aligned */
if ( vma - > vm_pgoff ! = 0 )
return - EINVAL ;
/* check the length */
size = vma - > vm_end - vma - > vm_start ;
if ( size > ( dma - > n_pages < < PAGE_SHIFT ) )
return - EINVAL ;
vma - > vm_ops = & dma_region_vm_ops ;
vma - > vm_private_data = dma ;
vma - > vm_file = file ;
vma - > vm_flags | = VM_RESERVED ;
return 0 ;
}
# else /* CONFIG_MMU */
int dma_region_mmap ( struct dma_region * dma , struct file * file , struct vm_area_struct * vma )
{
return - EINVAL ;
}
# endif /* CONFIG_MMU */