blob: 6f346a3d4d241e83271a97fdb62156419aa36f25 [file] [log] [blame]
/*
* Creation Date: <1999/11/07 19:02:11 samuel>
* Time-stamp: <2004/01/07 19:42:36 samuel>
*
* <ofmem.c>
*
* OF Memory manager
*
* Copyright (C) 1999-2004 Samuel Rydh (samuel@ibrium.se)
* Copyright (C) 2004 Stefan Reinauer
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation
*
*/
#include "config.h"
#include "libopenbios/bindings.h"
#include "libc/string.h"
#include "libopenbios/ofmem.h"
#include "kernel.h"
#include "mmutypes.h"
#include "asm/processor.h"
#define BIT(n) (1U << (31 - (n)))
#define SLB_VSID_SHIFT 12
/* called from assembly */
extern void dsi_exception(void);
extern void isi_exception(void);
extern void setup_mmu(unsigned long code_base);
/*
* From Apple's BootX source comments:
*
* 96 MB map (currently unused - 4363357 tracks re-adoption)
* 00000000 - 00003FFF : Exception Vectors
* 00004000 - 03FFFFFF : Kernel Image, Boot Struct and Drivers (~64 MB)
* 04000000 - 04FFFFFF : File Load Area (16 MB) [80 MB]
* 05000000 - 053FFFFF : FS Cache (4 MB) [84 MB]
* 05400000 - 055FFFFF : Malloc Zone (2 MB) [86 MB]
* 05600000 - 057FFFFF : BootX Image (2 MB) [88 MB]
* 05800000 - 05FFFFFF : Unused/OF (8 MB) [96 MB]
*
*/
#define FREE_BASE 0x00004000UL
#define OF_CODE_START 0xfff00000UL
#define OF_CODE_SIZE 0x00100000
#define IO_BASE 0x80000000UL
#ifdef __powerpc64__
#define HASH_BITS 18
#else
#define HASH_BITS 15
#endif
#define HASH_SIZE (2 << HASH_BITS)
#define OFMEM_SIZE (1 * 1024 * 1024 + 512 * 1024)
#define SEGR_USER BIT(2)
#define SEGR_BASE 0x0400
static inline unsigned long
get_hash_base(void)
{
return (mfsdr1() & SDR1_HTABORG_MASK);
}
static inline unsigned long
get_rom_base(void)
{
ofmem_t *ofmem = ofmem_arch_get_private();
return ofmem->ramsize - OF_CODE_SIZE;
}
static unsigned long
get_ram_top(void)
{
return get_hash_base() - (32 + 64 + 64) * 1024 - OFMEM_SIZE;
}
static unsigned long
get_ram_bottom(void)
{
return FREE_BASE;
}
static unsigned long get_heap_top(void)
{
return get_hash_base() - (32 + 64 + 64) * 1024;
}
static inline size_t ALIGN_SIZE(size_t x, size_t a)
{
return (x + a - 1) & ~(a - 1);
}
ofmem_t* ofmem_arch_get_private(void)
{
return (ofmem_t*)cell2pointer(get_heap_top() - OFMEM_SIZE);
}
void* ofmem_arch_get_malloc_base(void)
{
return (char*)ofmem_arch_get_private() + ALIGN_SIZE(sizeof(ofmem_t), 4);
}
ucell ofmem_arch_get_heap_top(void)
{
return get_heap_top();
}
ucell ofmem_arch_get_virt_top(void)
{
return IO_BASE;
}
void ofmem_arch_unmap_pages(ucell virt, ucell size)
{
/* kill page mappings in provided range */
}
void ofmem_arch_map_pages(phys_addr_t phys, ucell virt, ucell size, ucell mode)
{
/* none yet */
}
ucell ofmem_arch_get_iomem_base(void)
{
/* Currently unused */
return 0;
}
ucell ofmem_arch_get_iomem_top(void)
{
/* Currently unused */
return 0;
}
retain_t *ofmem_arch_get_retained(void)
{
/* not implemented */
return NULL;
}
int ofmem_arch_get_physaddr_cellsize(void)
{
#ifdef CONFIG_PPC64
return 2;
#else
return 1;
#endif
}
int ofmem_arch_encode_physaddr(ucell *p, phys_addr_t value)
{
int n = 0;
#ifdef CONFIG_PPC64
p[n++] = value >> 32;
#endif
p[n++] = value;
return n;
}
/* Return size of a single MMU package translation property entry in cells */
int ofmem_arch_get_translation_entry_size(void)
{
return 3 + ofmem_arch_get_physaddr_cellsize();
}
/* Generate translation property entry for PPC.
* According to the platform bindings for PPC
* (http://www.openfirmware.org/1275/bindings/ppc/release/ppc-2_1.html#REF34579)
* a translation property entry has the following layout:
*
* virtual address
* length
* physical address
* mode
*/
void ofmem_arch_create_translation_entry(ucell *transentry, translation_t *t)
{
int i = 0;
transentry[i++] = t->virt;
transentry[i++] = t->size;
i += ofmem_arch_encode_physaddr(&transentry[i], t->phys);
transentry[i++] = t->mode;
}
/* Return the size of a memory available entry given the phandle in cells */
int ofmem_arch_get_available_entry_size(phandle_t ph)
{
if (ph == s_phandle_memory) {
return 1 + ofmem_arch_get_physaddr_cellsize();
} else {
return 1 + 1;
}
}
/* Generate memory available property entry for PPC */
void ofmem_arch_create_available_entry(phandle_t ph, ucell *availentry, phys_addr_t start, ucell size)
{
int i = 0;
if (ph == s_phandle_memory) {
i += ofmem_arch_encode_physaddr(availentry, start);
} else {
availentry[i++] = start;
}
availentry[i] = size;
}
/************************************************************************/
/* OF private allocations */
/************************************************************************/
/* Private functions for mapping between physical/virtual addresses */
phys_addr_t
va2pa(unsigned long va)
{
if (va >= OF_CODE_START && va < OF_CODE_START + OF_CODE_SIZE) {
return (phys_addr_t)get_rom_base() - OF_CODE_START + va;
} else {
return (phys_addr_t)va;
}
}
unsigned long
pa2va(phys_addr_t pa)
{
if ((pa - get_rom_base() + OF_CODE_START >= OF_CODE_START) &&
(pa - get_rom_base() + OF_CODE_START < OF_CODE_START + OF_CODE_SIZE))
return (unsigned long)pa - get_rom_base() + OF_CODE_START;
else
return (unsigned long)pa;
}
void *
malloc(int size)
{
return ofmem_malloc(size);
}
void
free(void *ptr)
{
ofmem_free(ptr);
}
void *
realloc(void *ptr, size_t size)
{
return ofmem_realloc(ptr, size);
}
/************************************************************************/
/* misc */
/************************************************************************/
ucell ofmem_arch_default_translation_mode(phys_addr_t phys)
{
/* XXX: Guard bit not set as it should! */
if (phys < IO_BASE)
return 0x02; /*0xa*/ /* wim GxPp */
return 0x6a; /* WIm GxPp, I/O */
}
ucell ofmem_arch_io_translation_mode(phys_addr_t phys)
{
return 0x6a; /* WIm GxPp, I/O */
}
/************************************************************************/
/* page fault handler */
/************************************************************************/
static phys_addr_t
ea_to_phys(unsigned long ea, ucell *mode)
{
phys_addr_t phys;
if (ea >= OF_CODE_START && ea <= 0xffffffffUL) {
/* ROM into RAM */
ea -= OF_CODE_START;
phys = get_rom_base() + ea;
*mode = 0x02;
return phys;
}
phys = ofmem_translate(ea, mode);
if (phys == -1) {
phys = ea;
*mode = ofmem_arch_default_translation_mode(phys);
/* print_virt_range(); */
/* print_phys_range(); */
/* print_trans(); */
}
return phys;
}
/* Converts a global variable (from .data or .bss) into a pointer that
can be accessed from real mode */
static void *
global_ptr_real(void *p)
{
return (void*)((uintptr_t)p - OF_CODE_START + get_rom_base());
}
/* Return the next slot to evict, in the range of [0..7] */
static int
next_evicted_slot(void)
{
static int next_grab_slot;
int *next_grab_slot_va;
int r;
next_grab_slot_va = global_ptr_real(&next_grab_slot);
r = *next_grab_slot_va;
*next_grab_slot_va = (r + 1) % 8;
return r;
}
static void
hash_page_64(unsigned long ea, phys_addr_t phys, ucell mode)
{
uint64_t vsid_mask, page_mask, pgidx, hash;
uint64_t htab_mask, mask, avpn;
unsigned long pgaddr;
int i, found;
unsigned int vsid, vsid_sh, sdr, sdr_sh, sdr_mask;
mPTE_64_t *pp;
vsid = (ea >> 28) + SEGR_BASE;
vsid_sh = 7;
vsid_mask = 0x00003FFFFFFFFF80ULL;
sdr = mfsdr1();
sdr_sh = 18;
sdr_mask = 0x3FF80;
page_mask = 0x0FFFFFFF; // XXX correct?
pgidx = (ea & page_mask) >> PAGE_SHIFT;
avpn = (vsid << 12) | ((pgidx >> 4) & 0x0F80);;
hash = ((vsid ^ pgidx) << vsid_sh) & vsid_mask;
htab_mask = 0x0FFFFFFF >> (28 - (sdr & 0x1F));
mask = (htab_mask << sdr_sh) | sdr_mask;
pgaddr = sdr | (hash & mask);
pp = (mPTE_64_t *)pgaddr;
/* replace old translation */
for (found = 0, i = 0; !found && i < 8; i++)
if (pp[i].avpn == avpn)
found = 1;
/* otherwise use a free slot */
for (i = 0; !found && i < 8; i++)
if (!pp[i].v)
found = 1;
/* out of slots, just evict one */
if (!found)
i = next_evicted_slot() + 1;
i--;
{
mPTE_64_t p = {
// .avpn_low = avpn,
.avpn = avpn >> 7,
.h = 0,
.v = 1,
.rpn = (phys & ~0xfffUL) >> 12,
.r = mode & (1 << 8) ? 1 : 0,
.c = mode & (1 << 7) ? 1 : 0,
.w = mode & (1 << 6) ? 1 : 0,
.i = mode & (1 << 5) ? 1 : 0,
.m = mode & (1 << 4) ? 1 : 0,
.g = mode & (1 << 3) ? 1 : 0,
.n = mode & (1 << 2) ? 1 : 0,
.pp = mode & 3,
};
pp[i] = p;
}
asm volatile("tlbie %0" :: "r"(ea));
}
static void
hash_page_32(unsigned long ea, phys_addr_t phys, ucell mode)
{
#ifndef __powerpc64__
unsigned long *upte, cmp, hash1;
int i, vsid, found;
mPTE_t *pp;
vsid = (ea >> 28) + SEGR_BASE;
cmp = BIT(0) | (vsid << 7) | ((ea & 0x0fffffff) >> 22);
hash1 = vsid;
hash1 ^= (ea >> 12) & 0xffff;
hash1 &= (((mfsdr1() & 0x1ff) << 16) | 0xffff) >> 6;
pp = (mPTE_t*)(get_hash_base() + (hash1 << 6));
upte = (unsigned long*)pp;
/* replace old translation */
for (found = 0, i = 0; !found && i < 8; i++)
if (cmp == upte[i*2])
found = 1;
/* otherwise use a free slot */
for (i = 0; !found && i < 8; i++)
if (!pp[i].v)
found = 1;
/* out of slots, just evict one */
if (!found)
i = next_evicted_slot() + 1;
i--;
upte[i * 2] = cmp;
upte[i * 2 + 1] = (phys & ~0xfff) | mode;
asm volatile("tlbie %0" :: "r"(ea));
#endif
}
static int is_ppc64(void)
{
#ifdef __powerpc64__
return 1;
#elif defined(CONFIG_PPC_64BITSUPPORT)
unsigned int pvr = mfpvr();
return ((pvr >= 0x330000) && (pvr < 0x70330000));
#else
return 0;
#endif
}
/* XXX Remove these ugly constructs when legacy 64-bit support is dropped. */
static void hash_page(unsigned long ea, phys_addr_t phys, ucell mode)
{
if (is_ppc64())
hash_page_64(ea, phys, mode);
else
hash_page_32(ea, phys, mode);
}
void
dsi_exception(void)
{
unsigned long dar, dsisr;
ucell mode;
phys_addr_t phys;
asm volatile("mfdar %0" : "=r" (dar) : );
asm volatile("mfdsisr %0" : "=r" (dsisr) : );
phys = ea_to_phys(dar, &mode);
hash_page(dar, phys, mode);
}
void
isi_exception(void)
{
unsigned long nip, srr1;
ucell mode;
phys_addr_t phys;
asm volatile("mfsrr0 %0" : "=r" (nip) : );
asm volatile("mfsrr1 %0" : "=r" (srr1) : );
phys = ea_to_phys(nip, &mode);
hash_page(nip, phys, mode);
}
/************************************************************************/
/* init / cleanup */
/************************************************************************/
void
setup_mmu(unsigned long ramsize)
{
ofmem_t *ofmem;
#ifndef __powerpc64__
unsigned long sr_base;
#endif
unsigned long hash_base;
unsigned long hash_mask = ~0x000fffffUL; /* alignment for ppc64 */
int i;
/* SDR1: Storage Description Register 1 */
hash_base = (ramsize - OF_CODE_SIZE - HASH_SIZE) & hash_mask;
memset((void *)hash_base, 0, HASH_SIZE);
if (is_ppc64())
mtsdr1(hash_base | MAX(HASH_BITS - 18, 0));
else
mtsdr1(hash_base | ((HASH_SIZE - 1) >> 16));
#ifdef __powerpc64__
/* Segment Lookaside Buffer */
slbia(); /* Invalidate all SLBs except SLB 0 */
for (i = 0; i < 16; i++) {
unsigned long rs = (0x400 + i) << SLB_VSID_SHIFT;
unsigned long rb = ((unsigned long)i << 28) | (1 << 27) | i;
slbmte(rs, rb);
}
#else
/* Segment Register */
sr_base = SEGR_USER | SEGR_BASE ;
for (i = 0; i < 16; i++) {
int j = i << 28;
asm volatile("mtsrin %0,%1" :: "r" (sr_base + i), "r" (j));
}
#endif
ofmem = ofmem_arch_get_private();
memset(ofmem, 0, sizeof(ofmem_t));
ofmem->ramsize = ramsize;
memcpy((void *)get_rom_base(), (void *)OF_CODE_START, OF_CODE_SIZE);
/* Enable MMU */
mtmsr(mfmsr() | MSR_IR | MSR_DR);
}
void
ofmem_init(void)
{
ofmem_t *ofmem = ofmem_arch_get_private();
/* Map the memory (don't map page 0 to allow catching of NULL dereferences) */
ofmem_claim_phys(PAGE_SIZE, get_ram_bottom() - PAGE_SIZE, 0);
ofmem_claim_virt(PAGE_SIZE, get_ram_bottom() - PAGE_SIZE, 0);
ofmem_map(PAGE_SIZE, PAGE_SIZE, get_ram_bottom() - PAGE_SIZE, 0);
/* Mark the first page as non-free */
ofmem_claim_phys(0, PAGE_SIZE, 0);
ofmem_claim_virt(0, PAGE_SIZE, 0);
/* Map everything at the top of physical RAM 1:1, minus the OpenBIOS ROM in RAM copy */
ofmem_claim_phys(get_ram_top(), get_hash_base() + HASH_SIZE - get_ram_top(), 0);
ofmem_claim_virt(get_ram_top(), get_hash_base() + HASH_SIZE - get_ram_top(), 0);
ofmem_map(get_ram_top(), get_ram_top(), get_hash_base() + HASH_SIZE - get_ram_top(), 0);
/* Map the OpenBIOS ROM in RAM copy */
ofmem_claim_phys(ofmem->ramsize - OF_CODE_SIZE, OF_CODE_SIZE, 0);
ofmem_claim_virt(OF_CODE_START, OF_CODE_SIZE, 0);
ofmem_map(ofmem->ramsize - OF_CODE_SIZE, OF_CODE_START, OF_CODE_SIZE, 0);
}