/* * Creation Date: <1999/11/07 19:02:11 samuel> * Time-stamp: <2004/01/07 19:42:36 samuel> * * * * 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); }