/*
* 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 <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 DEBUG_FORCEDAC 0
#define ISA_DMA_MASK 0x00ffffff
static inline unsigned long
mk_iommu_pte(unsigned long paddr)
{
return (paddr >> (PAGE_SHIFT-1)) | 1;
}
static inline long
calc_npages(long bytes)
{
return (bytes + PAGE_SIZE - 1) >> PAGE_SHIFT;
}
/* 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 = 1UL << ceil_log2(mem);
return max;
}
struct pci_iommu_arena *
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
if (!NODE_DATA(nid) ||
(NULL == (arena = alloc_bootmem_node(NODE_DATA(nid),
sizeof(*arena))))) {
printk("%s: couldn't allocate arena from node %d\n"
" falling back to system-wide allocation\n",
__FUNCTION__, nid);
arena = alloc_bootmem(sizeof(*arena));
}
if (!NODE_DATA(nid) ||
(NULL == (arena->ptes = __alloc_bootmem_node(NODE_DATA(nid),
mem_size,
align,
0)))) {
printk("%s: couldn't allocate arena ptes from node %d\n"
" falling back to system-wide allocation\n",
__FUNCTION__, 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 *
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 pci_iommu_arena *arena, long n, long mask)
{
unsigned long *ptes;
long i, p, nent;
/* Search forward for the first mask-aligned sequence of N free ptes */
ptes = arena->ptes;
nent = arena->size >> PAGE_SHIFT;
p = (arena->next_entry + mask) & ~mask;
i = 0;
while (i < n && p+i < nent) {
if (ptes[p+i])
p = (p + i + 1 + mask) & ~mask, i = 0;
else
i = i + 1;
}
if (i < n) {
/* Reached the end. Flush the TLB and restart the
search from the beginning. */
alpha_mv.mv_pci_tbi(arena->hose, 0, -1);
p = 0, i = 0;
while (i < n && p+i < nent) {
if (ptes[p+i])
p = (p + i + 1 + mask) & ~mask, i = 0;
else
i =