| /* | 
 |  * 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) | 
 | { | 
 | 	unsigned long rem = 0; | 
 |  | 
 | 	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 */ |