mini/memory.c

381 lines
8.7 KiB
C
Raw Normal View History

/*
mini - a Free Software replacement for the Nintendo/BroadOn IOS.
memory management, MMU, caches, and flushing
Copyright (C) 2008, 2009 Hector Martin "marcan" <marcan@marcansoft.com>
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, version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
2008-12-28 14:35:37 +01:00
#include "types.h"
#include "start.h"
#include "memory.h"
#include "utils.h"
#include "gecko.h"
#include "hollywood.h"
#include "irq.h"
2008-12-28 14:35:37 +01:00
void _dc_inval_entries(void *start, int count);
2009-01-16 09:07:33 +01:00
void _dc_flush_entries(const void *start, int count);
2008-12-28 14:35:37 +01:00
void _dc_flush(void);
void _dc_inval(void);
2008-12-28 14:35:37 +01:00
void _ic_inval(void);
void _drain_write_buffer(void);
void _tlb_inval(void);
extern u32 __page_table[4096];
2008-12-28 14:35:37 +01:00
#define LINESIZE 0x20
#define CACHESIZE 0x4000
#define CR_MMU (1 << 0)
#define CR_DCACHE (1 << 2)
#define CR_ICACHE (1 << 12)
2008-12-28 14:35:37 +01:00
// TODO: move to hollywood.h once we figure out WTF
#define HW_100 (HW_REG_BASE + 0x100)
#define HW_104 (HW_REG_BASE + 0x104)
#define HW_108 (HW_REG_BASE + 0x108)
#define HW_10c (HW_REG_BASE + 0x10c)
#define HW_110 (HW_REG_BASE + 0x110)
#define HW_114 (HW_REG_BASE + 0x114)
#define HW_118 (HW_REG_BASE + 0x118)
#define HW_11c (HW_REG_BASE + 0x11c)
#define HW_120 (HW_REG_BASE + 0x120)
#define HW_124 (HW_REG_BASE + 0x124)
#define HW_130 (HW_REG_BASE + 0x130)
#define HW_134 (HW_REG_BASE + 0x134)
#define HW_138 (HW_REG_BASE + 0x138)
#define HW_188 (HW_REG_BASE + 0x188)
#define HW_18C (HW_REG_BASE + 0x18c)
// what is this thing doing anyway?
// and why only on reads?
u32 _mc_read32(u32 addr)
{
u32 data;
u32 tmp130 = 0;
// this seems to be a bug workaround
2009-02-20 13:24:48 +01:00
if(!(read32(HW_VERSION) & 0xF0))
2008-12-28 14:35:37 +01:00
{
tmp130 = read32(HW_130);
write32(HW_130, tmp130 | 0x400);
// Dummy reads?
read32(HW_138);
read32(HW_138);
read32(HW_138);
read32(HW_138);
}
data = read32(addr);
2009-02-20 13:24:48 +01:00
read32(HW_VERSION); //???
2009-02-20 13:24:48 +01:00
if(!(read32(HW_VERSION) & 0xF0))
2008-12-28 14:35:37 +01:00
write32(HW_130, tmp130);
return data;
}
// this is ripped from IOS, because no one can figure out just WTF this thing is doing
2009-03-07 20:15:07 +01:00
void _ahb_flush_to(enum AHBDEV dev) {
2008-12-28 14:35:37 +01:00
u32 mask = 10;
2009-03-07 20:15:07 +01:00
switch(dev) {
case AHB_STARLET: mask = 0x8000; break;
case AHB_1: mask = 0x4000; break;
//case 2: mask = 0x0001; break;
case AHB_NAND: mask = 0x0002; break;
case AHB_AES: mask = 0x0004; break;
case AHB_SHA1: mask = 0x0008; break;
2009-03-07 20:15:07 +01:00
//case 6: mask = 0x0010; break;
//case 7: mask = 0x0020; break;
//case 8: mask = 0x0040; break;
2009-03-07 20:44:54 +01:00
case AHB_SDHC: mask = 0x0080; break;
2009-03-07 20:15:07 +01:00
//case 10: mask = 0x0100; break;
//case 11: mask = 0x1000; break;
//case 12: mask = 0x0000; break;
default:
gecko_printf("ahb_invalidate(%d): Invalid device\n", dev);
return;
2008-12-28 14:35:37 +01:00
}
//NOTE: 0xd8b000x, not 0xd8b400x!
u32 val = _mc_read32(0xd8b0008);
if(val & mask) {
2009-03-07 20:15:07 +01:00
switch(dev) {
// 2 to 10 in IOS, add more
case AHB_NAND:
case AHB_AES:
case AHB_SHA1:
2009-03-07 20:44:54 +01:00
case AHB_SDHC:
2009-03-07 20:15:07 +01:00
while((read32(HW_18C) & 0xF) == 9)
set32(HW_188, 0x10000);
clear32(HW_188, 0x10000);
set32(HW_188, 0x2000000);
mask32(HW_124, 0x7c0, 0x280);
set32(HW_134, 0x400);
while((read32(HW_18C) & 0xF) != 9);
set32(HW_100, 0x400);
set32(HW_104, 0x400);
set32(HW_108, 0x400);
set32(HW_10c, 0x400);
set32(HW_110, 0x400);
set32(HW_114, 0x400);
set32(HW_118, 0x400);
set32(HW_11c, 0x400);
set32(HW_120, 0x400);
write32(0xd8b0008, _mc_read32(0xd8b0008) & (~mask));
write32(0xd8b0008, _mc_read32(0xd8b0008) | mask);
clear32(HW_134, 0x400);
clear32(HW_100, 0x400);
clear32(HW_104, 0x400);
clear32(HW_108, 0x400);
clear32(HW_10c, 0x400);
clear32(HW_110, 0x400);
clear32(HW_114, 0x400);
clear32(HW_118, 0x400);
clear32(HW_11c, 0x400);
clear32(HW_120, 0x400);
clear32(HW_188, 0x2000000);
mask32(HW_124, 0x7c0, 0xc0);
//0, 1, 11 in IOS, add more
case AHB_STARLET:
case AHB_1:
2008-12-28 14:35:37 +01:00
write32(0xd8b0008, val & (~mask));
// wtfux
write32(0xd8b0008, val | mask);
write32(0xd8b0008, val | mask);
write32(0xd8b0008, val | mask);
}
}
}
2009-03-07 20:15:07 +01:00
// invalidate device and then starlet
void ahb_flush_to(enum AHBDEV type)
2008-12-28 14:35:37 +01:00
{
u32 cookie = irq_kill();
2009-03-07 20:15:07 +01:00
_ahb_flush_to(type);
2009-03-18 18:21:25 +01:00
if(type != AHB_STARLET)
2009-03-07 20:15:07 +01:00
_ahb_flush_to(AHB_STARLET);
irq_restore(cookie);
2008-12-28 14:35:37 +01:00
}
2009-03-07 20:15:07 +01:00
// flush device and also invalidate memory
void ahb_flush_from(enum AHBDEV dev)
2008-12-28 14:35:37 +01:00
{
u32 cookie = irq_kill();
2008-12-28 14:35:37 +01:00
u16 req = 0;
u16 ack;
int i;
2008-12-28 14:35:37 +01:00
switch(dev)
{
2009-03-07 20:15:07 +01:00
case AHB_STARLET:
case AHB_1:
2008-12-28 14:35:37 +01:00
req = 1;
break;
case AHB_AES:
case AHB_SHA1:
req = 2;
break;
2009-03-07 20:15:07 +01:00
case AHB_NAND:
2009-03-07 20:44:54 +01:00
case AHB_SDHC:
2009-03-07 06:42:07 +01:00
req = 8;
break;
2008-12-28 14:35:37 +01:00
default:
2009-03-07 20:15:07 +01:00
gecko_printf("ahb_flush(%d): Invalid device\n", dev);
irq_restore(cookie);
2009-03-07 20:15:07 +01:00
return;
2008-12-28 14:35:37 +01:00
}
2009-01-15 16:46:50 +01:00
write16(MEM_FLUSHREQ, req);
2008-12-28 14:35:37 +01:00
for(i=0;i<1000000;i++) {
ack = read16(MEM_FLUSHACK);
2009-03-07 20:15:07 +01:00
_ahb_flush_to(AHB_STARLET);
2008-12-28 14:35:37 +01:00
if(ack == req)
break;
}
2009-01-15 16:46:50 +01:00
write16(MEM_FLUSHREQ, 0);
2008-12-28 14:35:37 +01:00
if(i>=1000000) {
2009-03-07 20:15:07 +01:00
gecko_printf("ahb_flush(%d): Flush (0x%x) did not ack!\n", dev, req);
2008-12-28 14:35:37 +01:00
}
irq_restore(cookie);
2008-12-28 14:35:37 +01:00
}
2009-01-16 09:07:33 +01:00
void dc_flushrange(const void *start, u32 size)
2008-12-28 14:35:37 +01:00
{
u32 cookie = irq_kill();
2008-12-28 14:35:37 +01:00
if(size > 0x4000) {
_dc_flush();
} else {
void *end = ALIGN_FORWARD(((u8*)start) + size, LINESIZE);
start = ALIGN_BACKWARD(start, LINESIZE);
_dc_flush_entries(start, (end - start) / LINESIZE);
}
_drain_write_buffer();
2009-03-07 20:15:07 +01:00
ahb_flush_from(AHB_1);
irq_restore(cookie);
2008-12-28 14:35:37 +01:00
}
void dc_invalidaterange(void *start, u32 size)
{
u32 cookie = irq_kill();
2008-12-28 14:35:37 +01:00
void *end = ALIGN_FORWARD(((u8*)start) + size, LINESIZE);
start = ALIGN_BACKWARD(start, LINESIZE);
_dc_inval_entries(start, (end - start) / LINESIZE);
2009-03-07 20:15:07 +01:00
ahb_flush_to(AHB_STARLET);
irq_restore(cookie);
2008-12-28 14:35:37 +01:00
}
void dc_flushall(void)
{
u32 cookie = irq_kill();
2008-12-28 14:35:37 +01:00
_dc_flush();
_drain_write_buffer();
2009-03-07 20:15:07 +01:00
ahb_flush_from(AHB_1);
irq_restore(cookie);
2008-12-28 14:35:37 +01:00
}
void ic_invalidateall(void)
{
u32 cookie = irq_kill();
2008-12-28 14:35:37 +01:00
_ic_inval();
2009-03-07 20:15:07 +01:00
ahb_flush_to(AHB_STARLET);
irq_restore(cookie);
2008-12-28 14:35:37 +01:00
}
void mem_protect(int enable, void *start, void *end)
{
write16(MEM_PROT, enable?1:0);
write16(MEM_PROT_START, (((u32)start) & 0xFFFFFFF) >> 12);
write16(MEM_PROT_END, (((u32)end) & 0xFFFFFFF) >> 12);
udelay(10);
}
void mem_setswap(int enable)
{
u32 d = read32(HW_MEMMIRR);
2008-12-28 14:35:37 +01:00
if((d & 0x20) && !enable)
write32(HW_MEMMIRR, d & ~0x20);
if((!(d & 0x20)) && enable)
write32(HW_MEMMIRR, d | 0x20);
}
u32 dma_addr(void *p)
{
u32 addr = (u32)p;
switch(addr>>20) {
case 0xfff:
case 0x0d4:
case 0x0dc:
if(read32(HW_MEMMIRR) & 0x20) {
addr ^= 0x10000;
}
addr &= 0x0001FFFF;
addr |= 0x0d400000;
break;
}
//gecko_printf("DMA to %p: address %08x\n", p, addr);
return addr;
}
#define SECTION 0x012
#define NONBUFFERABLE 0x000
#define BUFFERABLE 0x004
#define WRITETHROUGH_CACHE 0x008
#define WRITEBACK_CACHE 0x00C
#define DOMAIN(x) ((x)<<5)
#define AP_ROM 0x000
#define AP_NOUSER 0x400
#define AP_ROUSER 0x800
#define AP_RWUSER 0xC00
// from, to, size: units of 1MB
2009-01-25 04:03:00 +01:00
void map_section(u32 from, u32 to, u32 size, u32 attributes)
{
attributes |= SECTION;
while(size--) {
__page_table[from++] = (to++<<20) | attributes;
}
}
//#define NO_CACHES
void mem_initialize(void)
{
u32 cr;
u32 cookie = irq_kill();
gecko_printf("MEM: cleaning up\n");
_ic_inval();
_dc_inval();
_tlb_inval();
2009-01-25 04:03:00 +01:00
gecko_printf("MEM: unprotecting memory\n");
mem_protect(0,NULL,NULL);
gecko_printf("MEM: mapping sections\n");
memset32(__page_table, 0, 16384);
map_section(0x000, 0x000, 0x018, WRITEBACK_CACHE | DOMAIN(0) | AP_RWUSER);
map_section(0x100, 0x100, 0x040, WRITEBACK_CACHE | DOMAIN(0) | AP_RWUSER);
map_section(0x0d0, 0x0d0, 0x001, NONBUFFERABLE | DOMAIN(0) | AP_RWUSER);
map_section(0x0d8, 0x0d8, 0x001, NONBUFFERABLE | DOMAIN(0) | AP_RWUSER);
map_section(0xfff, 0xfff, 0x001, WRITEBACK_CACHE | DOMAIN(0) | AP_RWUSER);
set_dacr(0xFFFFFFFF); //manager access for all domains, ignore AP
2009-01-16 09:07:33 +01:00
set_ttbr((u32)__page_table); //configure translation table
_drain_write_buffer();
cr = get_cr();
#ifndef NO_CACHES
gecko_printf("MEM: enabling caches\n");
cr |= CR_DCACHE | CR_ICACHE;
set_cr(cr);
gecko_printf("MEM: enabling MMU\n");
cr |= CR_MMU;
set_cr(cr);
#endif
gecko_printf("MEM: init done\n");
irq_restore(cookie);
}
void mem_shutdown(void)
{
u32 cookie = irq_kill();
_dc_flush();
_drain_write_buffer();
u32 cr = get_cr();
cr &= ~(CR_MMU | CR_DCACHE | CR_ICACHE); //disable ICACHE, DCACHE, MMU
set_cr(cr);
_ic_inval();
_dc_inval();
_tlb_inval();
irq_restore(cookie);
}