273 lines
6 KiB
C
273 lines
6 KiB
C
|
/**
|
||
|
** Proll (PROM replacement)
|
||
|
** iommu.c: Functions for DVMA management.
|
||
|
** Copyright 1999 Pete Zaitcev
|
||
|
** This code is licensed under GNU General Public License.
|
||
|
**/
|
||
|
#include "config.h"
|
||
|
#include "libopenbios/bindings.h"
|
||
|
#include "libopenbios/ofmem.h"
|
||
|
#include "drivers/drivers.h"
|
||
|
#include "iommu.h"
|
||
|
#include "arch/sparc32/ofmem_sparc32.h"
|
||
|
#include "arch/sparc32/asi.h"
|
||
|
#include "arch/sparc32/pgtsrmmu.h"
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_IOMMU
|
||
|
#define DPRINTF(fmt, args...) \
|
||
|
do { printk(fmt , ##args); } while (0)
|
||
|
#else
|
||
|
#define DPRINTF(fmt, args...)
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* IOMMU parameters
|
||
|
*/
|
||
|
struct iommu {
|
||
|
struct iommu_regs *regs;
|
||
|
unsigned int *page_table;
|
||
|
unsigned long plow; /* Base bus address */
|
||
|
unsigned long pphys; /* Base phys address */
|
||
|
};
|
||
|
|
||
|
static struct iommu ciommu;
|
||
|
|
||
|
static void
|
||
|
iommu_invalidate(struct iommu_regs *iregs)
|
||
|
{
|
||
|
iregs->tlbflush = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* XXX This is a problematic interface. We alloc _memory_ which is uncached.
|
||
|
* So if we ever reuse allocations somebody is going to get uncached pages.
|
||
|
* Returned address is always aligned by page.
|
||
|
* BTW, we were not going to give away anonymous storage, were we not?
|
||
|
*/
|
||
|
void *
|
||
|
dvma_alloc(int size)
|
||
|
{
|
||
|
void *va;
|
||
|
unsigned int pa, iova;
|
||
|
unsigned int npages;
|
||
|
unsigned int mva, mpa;
|
||
|
unsigned int i;
|
||
|
unsigned int *iopte;
|
||
|
struct iommu *t = &ciommu;
|
||
|
|
||
|
npages = (size + (PAGE_SIZE-1)) / PAGE_SIZE;
|
||
|
iova = (unsigned int)mem_alloc(&cdvmem, npages * PAGE_SIZE, PAGE_SIZE);
|
||
|
if (iova == 0)
|
||
|
return NULL;
|
||
|
|
||
|
pa = t->pphys + (iova - t->plow);
|
||
|
va = (void *)pa2va((unsigned long)pa);
|
||
|
|
||
|
/*
|
||
|
* Change page attributes in MMU to uncached.
|
||
|
*/
|
||
|
mva = (unsigned int) va;
|
||
|
mpa = (unsigned int) pa;
|
||
|
ofmem_arch_map_pages(mpa, mva, npages * PAGE_SIZE, ofmem_arch_io_translation_mode(mpa));
|
||
|
|
||
|
/*
|
||
|
* Map into IOMMU page table.
|
||
|
*/
|
||
|
mpa = (unsigned int) pa;
|
||
|
iopte = &t->page_table[(iova - t->plow) / PAGE_SIZE];
|
||
|
for (i = 0; i < npages; i++) {
|
||
|
*iopte++ = MKIOPTE(mpa);
|
||
|
mpa += PAGE_SIZE;
|
||
|
}
|
||
|
|
||
|
return va;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
dvma_sync(unsigned char *va, int size)
|
||
|
{
|
||
|
/* Synchronise the VA address region after DMA */
|
||
|
unsigned long virt = pointer2cell(va);
|
||
|
unsigned long page;
|
||
|
|
||
|
for (page = (unsigned long)virt; page < virt + size; page += PAGE_SIZE) {
|
||
|
srmmu_flush_tlb_page(page);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unsigned int
|
||
|
dvma_map_in(unsigned char *va)
|
||
|
{
|
||
|
/* Convert from VA to IOVA */
|
||
|
unsigned int pa, iova;
|
||
|
struct iommu *t = &ciommu;
|
||
|
|
||
|
pa = va2pa((unsigned int)va);
|
||
|
iova = t->plow + (pa - t->pphys);
|
||
|
|
||
|
return iova;
|
||
|
}
|
||
|
|
||
|
#define DVMA_SIZE 0x4000
|
||
|
|
||
|
/*
|
||
|
* Initialize IOMMU
|
||
|
* This looks like initialization of CPU MMU but
|
||
|
* the routine is higher in food chain.
|
||
|
*/
|
||
|
static struct iommu_regs *
|
||
|
iommu_init(struct iommu *t, uint64_t base)
|
||
|
{
|
||
|
unsigned int *ptab, pva;
|
||
|
int ptsize;
|
||
|
#ifdef CONFIG_DEBUG_IOMMU
|
||
|
unsigned int impl, vers;
|
||
|
#endif
|
||
|
unsigned int tmp;
|
||
|
struct iommu_regs *regs;
|
||
|
int ret;
|
||
|
unsigned long vasize;
|
||
|
|
||
|
regs = (struct iommu_regs *)ofmem_map_io(base, IOMMU_REGS);
|
||
|
if (regs == NULL) {
|
||
|
DPRINTF("Cannot map IOMMU\n");
|
||
|
for (;;) { }
|
||
|
}
|
||
|
t->regs = regs;
|
||
|
#ifdef CONFIG_DEBUG_IOMMU
|
||
|
impl = (regs->control & IOMMU_CTRL_IMPL) >> 28;
|
||
|
vers = (regs->control & IOMMU_CTRL_VERS) >> 24;
|
||
|
#endif
|
||
|
|
||
|
tmp = regs->control;
|
||
|
tmp &= ~(IOMMU_CTRL_RNGE);
|
||
|
|
||
|
tmp |= (IOMMU_RNGE_32MB | IOMMU_CTRL_ENAB);
|
||
|
t->plow = 0xfe000000; /* End - 32 MB */
|
||
|
/* Size of VA region that we manage */
|
||
|
vasize = 0x2000000; /* 32 MB */
|
||
|
|
||
|
regs->control = tmp;
|
||
|
iommu_invalidate(regs);
|
||
|
|
||
|
/* Allocate IOMMU page table */
|
||
|
/* Tremendous alignment causes great waste... */
|
||
|
ptsize = (vasize / PAGE_SIZE) * sizeof(int);
|
||
|
ret = ofmem_posix_memalign((void *)&ptab, ptsize, ptsize);
|
||
|
if (ret != 0) {
|
||
|
DPRINTF("Cannot allocate IOMMU table [0x%x]\n", ptsize);
|
||
|
for (;;) { }
|
||
|
}
|
||
|
t->page_table = ptab;
|
||
|
|
||
|
/* flush_cache_all(); */
|
||
|
/** flush_tlb_all(); **/
|
||
|
tmp = (unsigned int)va2pa((unsigned long)ptab);
|
||
|
regs->base = tmp >> 4;
|
||
|
iommu_invalidate(regs);
|
||
|
|
||
|
DPRINTF("IOMMU: impl %d vers %d page table at 0x%p (pa 0x%x) of size %d bytes\n",
|
||
|
impl, vers, t->page_table, tmp, ptsize);
|
||
|
|
||
|
mem_init(&cdvmem, (char*)t->plow, (char *)(t->plow + DVMA_SIZE));
|
||
|
ret = ofmem_posix_memalign((void *)&pva, DVMA_SIZE, PAGE_SIZE);
|
||
|
if (ret != 0) {
|
||
|
DPRINTF("Cannot allocate IOMMU phys size [0x%x]\n", DVMA_SIZE);
|
||
|
for (;;) { }
|
||
|
}
|
||
|
t->pphys = va2pa(pva);
|
||
|
return regs;
|
||
|
}
|
||
|
|
||
|
/* ( addr.lo addr.hi size -- virt ) */
|
||
|
|
||
|
static void
|
||
|
ob_iommu_map_in(void)
|
||
|
{
|
||
|
phys_addr_t phys;
|
||
|
ucell size, virt;
|
||
|
|
||
|
size = POP();
|
||
|
phys = POP();
|
||
|
phys = (phys << 32) + POP();
|
||
|
|
||
|
virt = ofmem_map_io(phys, size);
|
||
|
|
||
|
PUSH(virt);
|
||
|
}
|
||
|
|
||
|
/* ( virt size ) */
|
||
|
|
||
|
static void
|
||
|
ob_iommu_map_out(void)
|
||
|
{
|
||
|
ucell size = POP();
|
||
|
ucell virt = POP();
|
||
|
|
||
|
ofmem_release_io(virt, size);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ob_iommu_dma_alloc(void)
|
||
|
{
|
||
|
call_parent_method("dma-alloc");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ob_iommu_dma_free(void)
|
||
|
{
|
||
|
call_parent_method("dma-free");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ob_iommu_dma_map_in(void)
|
||
|
{
|
||
|
call_parent_method("dma-map-in");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ob_iommu_dma_map_out(void)
|
||
|
{
|
||
|
call_parent_method("dma-map-out");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ob_iommu_dma_sync(void)
|
||
|
{
|
||
|
call_parent_method("dma-sync");
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ob_init_iommu(uint64_t base)
|
||
|
{
|
||
|
struct iommu_regs *regs;
|
||
|
|
||
|
regs = iommu_init(&ciommu, base);
|
||
|
|
||
|
push_str("/iommu");
|
||
|
fword("find-device");
|
||
|
PUSH((unsigned long)regs);
|
||
|
fword("encode-int");
|
||
|
push_str("address");
|
||
|
fword("property");
|
||
|
|
||
|
PUSH(base >> 32);
|
||
|
fword("encode-int");
|
||
|
PUSH(base & 0xffffffff);
|
||
|
fword("encode-int");
|
||
|
fword("encode+");
|
||
|
PUSH(IOMMU_REGS);
|
||
|
fword("encode-int");
|
||
|
fword("encode+");
|
||
|
push_str("reg");
|
||
|
fword("property");
|
||
|
|
||
|
bind_func("map-in", ob_iommu_map_in);
|
||
|
bind_func("map-out", ob_iommu_map_out);
|
||
|
bind_func("dma-alloc", ob_iommu_dma_alloc);
|
||
|
bind_func("dma-free", ob_iommu_dma_free);
|
||
|
bind_func("dma-map-in", ob_iommu_dma_map_in);
|
||
|
bind_func("dma-map-out", ob_iommu_dma_map_out);
|
||
|
bind_func("dma-sync", ob_iommu_dma_sync);
|
||
|
}
|