/* pci_sun4v.c: SUN4V specific PCI controller support.
*
* Copyright (C) 2006 David S. Miller (davem@davemloft.net)
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/percpu.h>
#include <asm/pbm.h>
#include <asm/iommu.h>
#include <asm/irq.h>
#include <asm/upa.h>
#include <asm/pstate.h>
#include <asm/oplib.h>
#include <asm/hypervisor.h>
#include "pci_impl.h"
#include "iommu_common.h"
#include "pci_sun4v.h"
#define PGLIST_NENTS (PAGE_SIZE / sizeof(u64))
struct pci_iommu_batch {
struct pci_dev *pdev; /* Device mapping is for. */
unsigned long prot; /* IOMMU page protections */
unsigned long entry; /* Index into IOTSB. */
u64 *pglist; /* List of physical pages */
unsigned long npages; /* Number of pages in list. */
};
static DEFINE_PER_CPU(struct pci_iommu_batch, pci_iommu_batch);
/* Interrupts must be disabled. */
static inline void pci_iommu_batch_start(struct pci_dev *pdev, unsigned long prot, unsigned long entry)
{
struct pci_iommu_batch *p = &__get_cpu_var(pci_iommu_batch);
p->pdev = pdev;
p->prot = prot;
p->entry = entry;
p->npages = 0;
}
/* Interrupts must be disabled. */
static long pci_iommu_batch_flush(struct pci_iommu_batch *p)
{
struct pcidev_cookie *pcp = p->pdev->sysdata;
unsigned long devhandle = pcp->pbm->devhandle;
unsigned long prot = p->prot;
unsigned long entry = p->entry;
u64 *pglist = p->pglist;
unsigned long npages = p->npages;
while (npages != 0) {
long num;
num = pci_sun4v_iommu_map(devhandle, HV_PCI_TSBID(0, entry),
npages, prot, __pa(pglist));
if (unlikely(num < 0)) {
if (printk_ratelimit())
printk("pci_iommu_batch_flush: IOMMU map of "
"[%08lx:%08lx:%lx:%lx:%lx] failed with "
"status %ld\n",
devhandle, HV_PCI_TSBID(0, entry),
npages, prot, __pa(pglist), num);
return -1;
}
entry += num;
npages -= num;
pglist += num;
}
p->entry = entry;
p->npages = 0;
return 0;
}
/* Interrupts must be disabled. */
static inline long pci_iommu_batch_add(u64 phys_page)
{
struct pci_iommu_batch *p = &__get_cpu_var(pci_iommu_batch);
BUG_ON(p->npages >= PGLIST_NENTS);
p->pglist[p->npages++] = phys_page;
if (p->npages == PGLIST_NENTS)
return pci_iommu_batch_flush(p);
return 0;
}
/* Interrupts must be disabled. */
static inline long pci_iommu_batch_end(void)
{
struct pci_iommu_batch *p = &__get_cpu_var(pci_iommu_batch);
BUG_ON(p->npages >= PGLIST_NENTS);
return pci_iommu_batch_flush(p);
}
static long pci_arena_alloc(struct pci_iommu_arena *arena, unsigned long npages)
{
unsigned long n, i, start, end, limit;
int pass;
limit = arena-