/*
* linux/arch/alpha/kernel/pci_iommu.c
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/bootmem.h>
#include <linux/scatterlist.h>
#include <linux/log2.h>
#include <linux/dma-mapping.h>
#include <linux/iommu-helper.h>
#include <asm/io.h>
#include <asm/hwrpb.h>
#include "proto.h"
#include "pci_impl.h"
#define DEBUG_ALLOC 0
#if DEBUG_ALLOC > 0
# define DBGA(args...) printk(KERN_DEBUG args)
#else
# define DBGA(args...)
#endif
#if DEBUG_ALLOC > 1
# define DBGA2(args...) printk(KERN_DEBUG args)
#else
# define DBGA2(args...)
#endif
#define DEBUG_NODIRECT 0
#define ISA_DMA_MASK 0x00ffffff
static inline unsigned long
mk_iommu_pte(unsigned long paddr)
{
return (paddr >> (PAGE_SHIFT-1)) | 1;
}
/* Return the minimum of MAX or the first power of two larger
than main memory. */
unsigned long
size_for_memory(unsigned long max)
{
unsigned long mem = max_low_pfn << PAGE_SHIFT;
if (mem < max)
max = roundup_pow_of_two(mem);
return max;
}
struct pci_iommu_arena * __init
iommu_arena_new_node(int nid, struct pci_controller *hose, dma_addr_t base,
unsigned long window_size, unsigned long align)
{
unsigned long mem_size;
struct pci_iommu_arena *arena;
mem_size = window_size / (PAGE_SIZE / sizeof(unsigned long));
/* Note that the TLB lookup logic uses bitwise concatenation,
not addition, so the required arena alignment is based on
the size of the window. Retain the align parameter so that
particular systems can over-align the arena. */
if (align < mem_size)
align = mem_size;
#ifdef CONFIG_DISCONTIGMEM
arena = alloc_bootmem_node(NODE_DATA(nid), sizeof(*arena));
if (!NODE_DATA(nid) || !arena) {
printk("%s: couldn't allocate arena from node %d\n"
" falling back to system-wide allocation\n",
__func__, nid);
arena = alloc_bootmem(sizeof(*arena));
}
arena->ptes = __alloc_bootmem_node(NODE_DATA(nid), mem_size, align, 0);
if (!NODE_DATA(nid) || !arena->ptes) {
printk("%s: couldn't allocate arena ptes from node %d\n"
" falling back to system-wide allocation\n",
__func__, nid);
arena->ptes = __alloc_bootmem(mem_size, align, 0);
}
#else /* CONFIG_DISCONTIGMEM */
arena = alloc_bootmem(sizeof(*arena));
arena->ptes = __alloc_bootmem(mem_size, align, 0);
#endif /* CONFIG_DISCONTIGMEM */
spin_lock_init(&arena->lock);
arena->hose = hose;
arena->dma_base = base;
arena->size = window_size;
arena->next_entry = 0;
/* Align allocations to a multiple of a page size. Not needed
unless there are chip bugs. */
arena->align_entry = 1;
return arena;
}
struct pci_iommu_arena * __init
iommu_arena_new(struct pci_controller *hose, dma_addr_t base,
unsigned long window_size, unsigned long align)
{
return iommu_arena_new_node(0, hose, base, window_size, align);
}
/* Must be called with the arena lock held */
static long
iommu_arena_find_pages(struct device *dev, struct pci_iommu_arena *arena,
long n, long mask)
{
unsigned long *ptes;
long i, p, nent;
int pass = 0;
unsigned long base;
unsigned long boundary_size;
base = arena->dma_base >> PAGE_SHIFT;
if (dev) {
boundary_size = dma_get_seg_boundary(dev) + 1;
boundary_size >>= PAGE_SHIFT;
} else {
boundary_size = 1UL << (32 - PAGE_SHIFT);
}
/* Search forward for the first mask-aligned sequence of N free ptes */
ptes = arena->ptes;
nent = arena->size >> PAGE_SHIFT;
p = ALIGN(arena->next_entry, mask + 1);
i = 0;
again:
while (i < n && p+i < nent) {
if (!i && iommu_is_span_boundary(p, n, base, boundary_size)) {
p = ALIGN(p + 1, mask + 1);
goto again;
}
if (ptes[p+i])
p = ALIGN(p + i + 1, mask + 1), i = 0;
else