|  | /* | 
|  | * 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/mm.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/pci.h> | 
|  | #include <linux/vmalloc.h> | 
|  | #include <linux/scatterlist.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 */ | 
|  |  | 
|  | /** | 
|  | * dma_region_init - clear out all fields but do not allocate anything | 
|  | */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * dma_region_alloc - allocate the buffer and map it to the IOMMU | 
|  | */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | sg_init_table(dma->sglist, dma->n_pages); | 
|  |  | 
|  | /* fill scatter/gather list with pages */ | 
|  | for (i = 0; i < dma->n_pages; i++) { | 
|  | unsigned long va = | 
|  | (unsigned long)dma->kvirt + (i << PAGE_SHIFT); | 
|  |  | 
|  | sg_set_page(&dma->sglist[i], vmalloc_to_page((void *)va), | 
|  | PAGE_SIZE, 0); | 
|  | } | 
|  |  | 
|  | /* 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; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * dma_region_free - unmap and free the buffer | 
|  | */ | 
|  | 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 int start, unsigned long *rem) | 
|  | { | 
|  | int i; | 
|  | unsigned long off = offset; | 
|  |  | 
|  | for (i = start; 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_region_offset_to_bus - get bus address of an offset within a DMA region | 
|  | * | 
|  | * Returns the DMA bus address of the byte with the given @offset relative to | 
|  | * the beginning of the @dma. | 
|  | */ | 
|  | 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, 0, &rem)]; | 
|  | return sg_dma_address(sg) + rem; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * dma_region_sync_for_cpu - sync the CPU's view of the buffer | 
|  | */ | 
|  | void dma_region_sync_for_cpu(struct dma_region *dma, unsigned long offset, | 
|  | unsigned long len) | 
|  | { | 
|  | int first, last; | 
|  | unsigned long rem = 0; | 
|  |  | 
|  | if (!len) | 
|  | len = 1; | 
|  |  | 
|  | first = dma_region_find(dma, offset, 0, &rem); | 
|  | last = dma_region_find(dma, rem + len - 1, first, &rem); | 
|  |  | 
|  | pci_dma_sync_sg_for_cpu(dma->dev, &dma->sglist[first], last - first + 1, | 
|  | dma->direction); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * dma_region_sync_for_device - sync the IO bus' view of the buffer | 
|  | */ | 
|  | void dma_region_sync_for_device(struct dma_region *dma, unsigned long offset, | 
|  | unsigned long len) | 
|  | { | 
|  | int first, last; | 
|  | unsigned long rem = 0; | 
|  |  | 
|  | if (!len) | 
|  | len = 1; | 
|  |  | 
|  | first = dma_region_find(dma, offset, 0, &rem); | 
|  | last = dma_region_find(dma, rem + len - 1, first, &rem); | 
|  |  | 
|  | pci_dma_sync_sg_for_device(dma->dev, &dma->sglist[first], | 
|  | last - first + 1, dma->direction); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_MMU | 
|  |  | 
|  | static int dma_region_pagefault(struct vm_area_struct *vma, | 
|  | struct vm_fault *vmf) | 
|  | { | 
|  | struct dma_region *dma = (struct dma_region *)vma->vm_private_data; | 
|  |  | 
|  | if (!dma->kvirt) | 
|  | return VM_FAULT_SIGBUS; | 
|  |  | 
|  | if (vmf->pgoff >= dma->n_pages) | 
|  | return VM_FAULT_SIGBUS; | 
|  |  | 
|  | vmf->page = vmalloc_to_page(dma->kvirt + (vmf->pgoff << PAGE_SHIFT)); | 
|  | get_page(vmf->page); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct vm_operations_struct dma_region_vm_ops = { | 
|  | .fault = dma_region_pagefault, | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * dma_region_mmap - map the buffer into a user space process | 
|  | */ | 
|  | 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 (XXX: comment is wrong, we could allow pgoff) */ | 
|  | 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 | VM_ALWAYSDUMP; | 
|  |  | 
|  | 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 */ |