| function MemoryView(memory, offset) { |
| this.inherit(); |
| this.buffer = memory; |
| this.view = new DataView(this.buffer, typeof(offset) === "number" ? offset : 0); |
| this.mask = memory.byteLength - 1; |
| this.resetMask(); |
| }; |
|
|
| MemoryView.prototype.resetMask = function() { |
| this.mask8 = this.mask & 0xFFFFFFFF; |
| this.mask16 = this.mask & 0xFFFFFFFE; |
| this.mask32 = this.mask & 0xFFFFFFFC; |
| }; |
|
|
| MemoryView.prototype.load8 = function(offset) { |
| return this.view.getInt8(offset & this.mask8); |
| }; |
|
|
| MemoryView.prototype.load16 = function(offset) { |
| |
| return this.view.getInt16(offset & this.mask, true); |
| }; |
|
|
| MemoryView.prototype.loadU8 = function(offset) { |
| return this.view.getUint8(offset & this.mask8); |
| }; |
|
|
| MemoryView.prototype.loadU16 = function(offset) { |
| |
| return this.view.getUint16(offset & this.mask, true); |
| }; |
|
|
| MemoryView.prototype.load32 = function(offset) { |
| |
| var rotate = (offset & 3) << 3; |
| var mem = this.view.getInt32(offset & this.mask32, true); |
| return (mem >>> rotate) | (mem << (32 - rotate)); |
| }; |
|
|
| MemoryView.prototype.store8 = function(offset, value) { |
| this.view.setInt8(offset & this.mask8, value); |
| }; |
|
|
| MemoryView.prototype.store16 = function(offset, value) { |
| this.view.setInt16(offset & this.mask16, value, true); |
| }; |
|
|
| MemoryView.prototype.store32 = function(offset, value) { |
| this.view.setInt32(offset & this.mask32, value, true); |
| }; |
|
|
| MemoryView.prototype.invalidatePage = function(address) {}; |
|
|
| MemoryView.prototype.replaceData = function(memory, offset) { |
| this.buffer = memory; |
| this.view = new DataView(this.buffer, typeof(offset) === "number" ? offset : 0); |
| if (this.icache) { |
| this.icache = new Array(this.icache.length); |
| } |
| }; |
|
|
| function MemoryBlock(size, cacheBits) { |
| MemoryView.call(this, new ArrayBuffer(size)); |
| this.ICACHE_PAGE_BITS = cacheBits; |
| this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1; |
| this.icache = new Array(size >> (this.ICACHE_PAGE_BITS + 1)); |
| }; |
|
|
| MemoryBlock.prototype = Object.create(MemoryView.prototype); |
|
|
| MemoryBlock.prototype.invalidatePage = function(address) { |
| var page = this.icache[(address & this.mask) >> this.ICACHE_PAGE_BITS]; |
| if (page) { |
| page.invalid = true; |
| } |
| }; |
|
|
| function ROMView(rom, offset) { |
| MemoryView.call(this, rom, offset); |
| this.ICACHE_PAGE_BITS = 10; |
| this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1; |
| this.icache = new Array(rom.byteLength >> (this.ICACHE_PAGE_BITS + 1)); |
| this.mask = 0x01FFFFFF; |
| this.resetMask(); |
| }; |
|
|
| ROMView.prototype = Object.create(MemoryView.prototype); |
|
|
| ROMView.prototype.store8 = function(offset, value) {}; |
|
|
| ROMView.prototype.store16 = function(offset, value) { |
| if (offset < 0xCA && offset >= 0xC4) { |
| if (!this.gpio) { |
| this.gpio = this.mmu.allocGPIO(this); |
| } |
| this.gpio.store16(offset, value); |
| } |
| }; |
|
|
| ROMView.prototype.store32 = function(offset, value) { |
| if (offset < 0xCA && offset >= 0xC4) { |
| if (!this.gpio) { |
| this.gpio = this.mmu.allocGPIO(this); |
| } |
| this.gpio.store32(offset, value); |
| } |
| }; |
|
|
| function BIOSView(rom, offset) { |
| MemoryView.call(this, rom, offset); |
| this.ICACHE_PAGE_BITS = 16; |
| this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1; |
| this.icache = new Array(1); |
| }; |
|
|
| BIOSView.prototype = Object.create(MemoryView.prototype); |
|
|
| BIOSView.prototype.load8 = function(offset) { |
| if (offset >= this.buffer.byteLength) { |
| return -1; |
| } |
| return this.view.getInt8(offset); |
| }; |
|
|
| BIOSView.prototype.load16 = function(offset) { |
| if (offset >= this.buffer.byteLength) { |
| return -1; |
| } |
| return this.view.getInt16(offset, true); |
| }; |
|
|
| BIOSView.prototype.loadU8 = function(offset) { |
| if (offset >= this.buffer.byteLength) { |
| return -1; |
| } |
| return this.view.getUint8(offset); |
| }; |
|
|
| BIOSView.prototype.loadU16 = function(offset) { |
| if (offset >= this.buffer.byteLength) { |
| return -1; |
| } |
| return this.view.getUint16(offset, true); |
| }; |
|
|
| BIOSView.prototype.load32 = function(offset) { |
| if (offset >= this.buffer.byteLength) { |
| return -1; |
| } |
| return this.view.getInt32(offset, true); |
| }; |
|
|
| BIOSView.prototype.store8 = function(offset, value) {}; |
|
|
| BIOSView.prototype.store16 = function(offset, value) {}; |
|
|
| BIOSView.prototype.store32 = function(offset, value) {}; |
|
|
| function BadMemory(mmu, cpu) { |
| this.inherit(); |
| this.cpu = cpu; |
| this.mmu = mmu |
| }; |
|
|
| BadMemory.prototype.load8 = function(offset) { |
| return this.mmu.load8(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x3)); |
| }; |
|
|
| BadMemory.prototype.load16 = function(offset) { |
| return this.mmu.load16(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x2)); |
| }; |
|
|
| BadMemory.prototype.loadU8 = function(offset) { |
| return this.mmu.loadU8(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x3)); |
| }; |
|
|
| BadMemory.prototype.loadU16 = function(offset) { |
| return this.mmu.loadU16(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x2)); |
| }; |
|
|
| BadMemory.prototype.load32 = function(offset) { |
| if (this.cpu.execMode == this.cpu.MODE_ARM) { |
| return this.mmu.load32(this.cpu.gprs[this.cpu.gprs.PC] - this.cpu.instructionWidth); |
| } else { |
| var halfword = this.mmu.loadU16(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth); |
| return halfword | (halfword << 16); |
| } |
| }; |
|
|
| BadMemory.prototype.store8 = function(offset, value) {}; |
|
|
| BadMemory.prototype.store16 = function(offset, value) {}; |
|
|
| BadMemory.prototype.store32 = function(offset, value) {}; |
|
|
| BadMemory.prototype.invalidatePage = function(address) {}; |
|
|
| function GameBoyAdvanceMMU() { |
| this.inherit(); |
| this.REGION_BIOS = 0x0; |
| this.REGION_WORKING_RAM = 0x2; |
| this.REGION_WORKING_IRAM = 0x3; |
| this.REGION_IO = 0x4; |
| this.REGION_PALETTE_RAM = 0x5; |
| this.REGION_VRAM = 0x6; |
| this.REGION_OAM = 0x7; |
| this.REGION_CART0 = 0x8; |
| this.REGION_CART1 = 0xA; |
| this.REGION_CART2 = 0xC; |
| this.REGION_CART_SRAM = 0xE; |
|
|
| this.BASE_BIOS = 0x00000000; |
| this.BASE_WORKING_RAM = 0x02000000; |
| this.BASE_WORKING_IRAM = 0x03000000; |
| this.BASE_IO = 0x04000000; |
| this.BASE_PALETTE_RAM = 0x05000000; |
| this.BASE_VRAM = 0x06000000; |
| this.BASE_OAM = 0x07000000; |
| this.BASE_CART0 = 0x08000000; |
| this.BASE_CART1 = 0x0A000000; |
| this.BASE_CART2 = 0x0C000000; |
| this.BASE_CART_SRAM = 0x0E000000; |
|
|
| this.BASE_MASK = 0x0F000000; |
| this.BASE_OFFSET = 24; |
| this.OFFSET_MASK = 0x00FFFFFF; |
|
|
| this.SIZE_BIOS = 0x00004000; |
| this.SIZE_WORKING_RAM = 0x00040000; |
| this.SIZE_WORKING_IRAM = 0x00008000; |
| this.SIZE_IO = 0x00000400; |
| this.SIZE_PALETTE_RAM = 0x00000400; |
| this.SIZE_VRAM = 0x00018000; |
| this.SIZE_OAM = 0x00000400; |
| this.SIZE_CART0 = 0x02000000; |
| this.SIZE_CART1 = 0x02000000; |
| this.SIZE_CART2 = 0x02000000; |
| this.SIZE_CART_SRAM = 0x00008000; |
| this.SIZE_CART_FLASH512 = 0x00010000; |
| this.SIZE_CART_FLASH1M = 0x00020000; |
| this.SIZE_CART_EEPROM = 0x00002000; |
|
|
| this.DMA_TIMING_NOW = 0; |
| this.DMA_TIMING_VBLANK = 1; |
| this.DMA_TIMING_HBLANK = 2; |
| this.DMA_TIMING_CUSTOM = 3; |
|
|
| this.DMA_INCREMENT = 0; |
| this.DMA_DECREMENT = 1; |
| this.DMA_FIXED = 2; |
| this.DMA_INCREMENT_RELOAD = 3; |
|
|
| this.DMA_OFFSET = [ 1, -1, 0, 1 ]; |
|
|
| this.WAITSTATES = [ 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 ]; |
| this.WAITSTATES_32 = [ 0, 0, 5, 0, 0, 1, 0, 1, 7, 7, 9, 9, 13, 13, 8 ]; |
| this.WAITSTATES_SEQ = [ 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4 ]; |
| this.WAITSTATES_SEQ_32 = [ 0, 0, 5, 0, 0, 1, 0, 1, 5, 5, 9, 9, 17, 17, 8 ]; |
| this.NULLWAIT = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; |
|
|
| for (var i = 15; i < 256; ++i) { |
| this.WAITSTATES[i] = 0; |
| this.WAITSTATES_32[i] = 0; |
| this.WAITSTATES_SEQ[i] = 0; |
| this.WAITSTATES_SEQ_32[i] = 0; |
| this.NULLWAIT[i] = 0; |
| } |
|
|
| this.ROM_WS = [ 4, 3, 2, 8 ]; |
| this.ROM_WS_SEQ = [ |
| [ 2, 1 ], |
| [ 4, 1 ], |
| [ 8, 1 ] |
| ]; |
|
|
| this.ICACHE_PAGE_BITS = 8; |
| this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1; |
|
|
| this.bios = null; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.mmap = function(region, object) { |
| this.memory[region] = object; |
| } |
|
|
| GameBoyAdvanceMMU.prototype.clear = function() { |
| this.badMemory = new BadMemory(this, this.cpu); |
| this.memory = [ |
| this.bios, |
| this.badMemory, |
| new MemoryBlock(this.SIZE_WORKING_RAM, 9), |
| new MemoryBlock(this.SIZE_WORKING_IRAM, 7), |
| null, |
| null, |
| null, |
| null, |
| this.badMemory, |
| this.badMemory, |
| this.badMemory, |
| this.badMemory, |
| this.badMemory, |
| this.badMemory, |
| this.badMemory, |
| this.badMemory |
| ]; |
| for (var i = 16; i < 256; ++i) { |
| this.memory[i] = this.badMemory; |
| } |
|
|
| this.waitstates = this.WAITSTATES.slice(0); |
| this.waitstatesSeq = this.WAITSTATES_SEQ.slice(0); |
| this.waitstates32 = this.WAITSTATES_32.slice(0); |
| this.waitstatesSeq32 = this.WAITSTATES_SEQ_32.slice(0); |
| this.waitstatesPrefetch = this.WAITSTATES_SEQ.slice(0); |
| this.waitstatesPrefetch32 = this.WAITSTATES_SEQ_32.slice(0); |
|
|
| this.cart = null; |
| this.save = null; |
|
|
| this.DMA_REGISTER = [ |
| this.core.io.DMA0CNT_HI >> 1, |
| this.core.io.DMA1CNT_HI >> 1, |
| this.core.io.DMA2CNT_HI >> 1, |
| this.core.io.DMA3CNT_HI >> 1 |
| ]; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.freeze = function() { |
| return { |
| 'ram': Serializer.prefix(this.memory[this.REGION_WORKING_RAM].buffer), |
| 'iram': Serializer.prefix(this.memory[this.REGION_WORKING_IRAM].buffer), |
| }; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.defrost = function(frost) { |
| this.memory[this.REGION_WORKING_RAM].replaceData(frost.ram); |
| this.memory[this.REGION_WORKING_IRAM].replaceData(frost.iram); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.loadBios = function(bios, real) { |
| this.bios = new BIOSView(bios); |
| this.bios.real = !!real; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.loadRom = function(rom, process) { |
| var cart = { |
| title: null, |
| code: null, |
| maker: null, |
| memory: rom, |
| saveType: null, |
| }; |
|
|
| var lo = new ROMView(rom); |
| if (lo.view.getUint8(0xB2) != 0x96) { |
| |
| return null; |
| } |
| lo.mmu = this; |
| this.memory[this.REGION_CART0] = lo; |
| this.memory[this.REGION_CART1] = lo; |
| this.memory[this.REGION_CART2] = lo; |
|
|
| if (rom.byteLength > 0x01000000) { |
| var hi = new ROMView(rom, 0x01000000); |
| this.memory[this.REGION_CART0 + 1] = hi; |
| this.memory[this.REGION_CART1 + 1] = hi; |
| this.memory[this.REGION_CART2 + 1] = hi; |
| } |
|
|
| if (process) { |
| var name = ''; |
| for (var i = 0; i < 12; ++i) { |
| var c = lo.loadU8(i + 0xA0); |
| if (!c) { |
| break; |
| } |
| name += String.fromCharCode(c); |
| } |
| cart.title = name; |
|
|
| var code = ''; |
| for (var i = 0; i < 4; ++i) { |
| var c = lo.loadU8(i + 0xAC); |
| if (!c) { |
| break; |
| } |
| code += String.fromCharCode(c); |
| } |
| cart.code = code; |
|
|
| var maker = ''; |
| for (var i = 0; i < 2; ++i) { |
| var c = lo.loadU8(i + 0xB0); |
| if (!c) { |
| break; |
| } |
| maker += String.fromCharCode(c); |
| } |
| cart.maker = maker; |
|
|
| |
| var state = ''; |
| var next; |
| var terminal = false; |
| for (var i = 0xE4; i < rom.byteLength && !terminal; ++i) { |
| next = String.fromCharCode(lo.loadU8(i)); |
| state += next; |
| switch (state) { |
| case 'F': |
| case 'FL': |
| case 'FLA': |
| case 'FLAS': |
| case 'FLASH': |
| case 'FLASH_': |
| case 'FLASH5': |
| case 'FLASH51': |
| case 'FLASH512': |
| case 'FLASH512_': |
| case 'FLASH1': |
| case 'FLASH1M': |
| case 'FLASH1M_': |
| case 'S': |
| case 'SR': |
| case 'SRA': |
| case 'SRAM': |
| case 'SRAM_': |
| case 'E': |
| case 'EE': |
| case 'EEP': |
| case 'EEPR': |
| case 'EEPRO': |
| case 'EEPROM': |
| case 'EEPROM_': |
| break; |
| case 'FLASH_V': |
| case 'FLASH512_V': |
| case 'FLASH1M_V': |
| case 'SRAM_V': |
| case 'EEPROM_V': |
| terminal = true; |
| break; |
| default: |
| state = next; |
| break; |
| } |
| } |
| if (terminal) { |
| cart.saveType = state; |
| switch (state) { |
| case 'FLASH_V': |
| case 'FLASH512_V': |
| this.save = this.memory[this.REGION_CART_SRAM] = new FlashSavedata(this.SIZE_CART_FLASH512); |
| break; |
| case 'FLASH1M_V': |
| this.save = this.memory[this.REGION_CART_SRAM] = new FlashSavedata(this.SIZE_CART_FLASH1M); |
| break; |
| case 'SRAM_V': |
| this.save = this.memory[this.REGION_CART_SRAM] = new SRAMSavedata(this.SIZE_CART_SRAM); |
| break; |
| case 'EEPROM_V': |
| this.save = this.memory[this.REGION_CART2 + 1] = new EEPROMSavedata(this.SIZE_CART_EEPROM, this); |
| break; |
| } |
| } |
| if (!this.save) { |
| |
| this.save = this.memory[this.REGION_CART_SRAM] = new SRAMSavedata(this.SIZE_CART_SRAM); |
| } |
| } |
|
|
| this.cart = cart; |
| return cart; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.loadSavedata = function(save) { |
| this.save.replaceData(save); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.load8 = function(offset) { |
| return this.memory[offset >>> this.BASE_OFFSET].load8(offset & 0x00FFFFFF); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.load16 = function(offset) { |
| return this.memory[offset >>> this.BASE_OFFSET].load16(offset & 0x00FFFFFF); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.load32 = function(offset) { |
| return this.memory[offset >>> this.BASE_OFFSET].load32(offset & 0x00FFFFFF); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.loadU8 = function(offset) { |
| return this.memory[offset >>> this.BASE_OFFSET].loadU8(offset & 0x00FFFFFF); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.loadU16 = function(offset) { |
| return this.memory[offset >>> this.BASE_OFFSET].loadU16(offset & 0x00FFFFFF); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.store8 = function(offset, value) { |
| var maskedOffset = offset & 0x00FFFFFF; |
| var memory = this.memory[offset >>> this.BASE_OFFSET]; |
| memory.store8(maskedOffset, value); |
| memory.invalidatePage(maskedOffset); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.store16 = function(offset, value) { |
| var maskedOffset = offset & 0x00FFFFFE; |
| var memory = this.memory[offset >>> this.BASE_OFFSET]; |
| memory.store16(maskedOffset, value); |
| memory.invalidatePage(maskedOffset); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.store32 = function(offset, value) { |
| var maskedOffset = offset & 0x00FFFFFC; |
| var memory = this.memory[offset >>> this.BASE_OFFSET]; |
| memory.store32(maskedOffset, value); |
| memory.invalidatePage(maskedOffset); |
| memory.invalidatePage(maskedOffset + 2); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.waitPrefetch = function(memory) { |
| this.cpu.cycles += 1 + this.waitstatesPrefetch[memory >>> this.BASE_OFFSET]; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.waitPrefetch32 = function(memory) { |
| this.cpu.cycles += 1 + this.waitstatesPrefetch32[memory >>> this.BASE_OFFSET]; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.wait = function(memory) { |
| this.cpu.cycles += 1 + this.waitstates[memory >>> this.BASE_OFFSET]; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.wait32 = function(memory) { |
| this.cpu.cycles += 1 + this.waitstates32[memory >>> this.BASE_OFFSET]; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.waitSeq = function(memory) { |
| this.cpu.cycles += 1 + this.waitstatesSeq[memory >>> this.BASE_OFFSET]; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.waitSeq32 = function(memory) { |
| this.cpu.cycles += 1 + this.waitstatesSeq32[memory >>> this.BASE_OFFSET]; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.waitMul = function(rs) { |
| if ((rs & 0xFFFFFF00 == 0xFFFFFF00) || !(rs & 0xFFFFFF00)) { |
| this.cpu.cycles += 1; |
| } else if ((rs & 0xFFFF0000 == 0xFFFF0000) || !(rs & 0xFFFF0000)) { |
| this.cpu.cycles += 2; |
| } else if ((rs & 0xFF000000 == 0xFF000000) || !(rs & 0xFF000000)) { |
| this.cpu.cycles += 3; |
| } else { |
| this.cpu.cycles += 4; |
| } |
| } |
|
|
| GameBoyAdvanceMMU.prototype.waitMulti32 = function(memory, seq) { |
| this.cpu.cycles += 1 + this.waitstates32[memory >>> this.BASE_OFFSET]; |
| this.cpu.cycles += (1 + this.waitstatesSeq32[memory >>> this.BASE_OFFSET]) * (seq - 1); |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.addressToPage = function(region, address) { |
| return address >> this.memory[region].ICACHE_PAGE_BITS; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.accessPage = function(region, pageId) { |
| var memory = this.memory[region]; |
| var page = memory.icache[pageId]; |
| if (!page || page.invalid) { |
| page = { |
| thumb: new Array(1 << (memory.ICACHE_PAGE_BITS)), |
| arm: new Array(1 << memory.ICACHE_PAGE_BITS - 1), |
| invalid: false |
| } |
| memory.icache[pageId] = page; |
| } |
| return page; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.scheduleDma = function(number, info) { |
| switch (info.timing) { |
| case this.DMA_TIMING_NOW: |
| this.serviceDma(number, info); |
| break; |
| case this.DMA_TIMING_HBLANK: |
| |
| break; |
| case this.DMA_TIMING_VBLANK: |
| |
| break; |
| case this.DMA_TIMING_CUSTOM: |
| switch (number) { |
| case 0: |
| this.core.WARN('Discarding invalid DMA0 scheduling'); |
| break; |
| case 1: |
| case 2: |
| this.cpu.irq.audio.scheduleFIFODma(number, info); |
| break; |
| case 3: |
| this.cpu.irq.video.scheduleVCaptureDma(dma, info); |
| break; |
| } |
| } |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.runHblankDmas = function() { |
| var dma; |
| for (var i = 0; i < this.cpu.irq.dma.length; ++i) { |
| dma = this.cpu.irq.dma[i]; |
| if (dma.enable && dma.timing == this.DMA_TIMING_HBLANK) { |
| this.serviceDma(i, dma); |
| } |
| } |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.runVblankDmas = function() { |
| var dma; |
| for (var i = 0; i < this.cpu.irq.dma.length; ++i) { |
| dma = this.cpu.irq.dma[i]; |
| if (dma.enable && dma.timing == this.DMA_TIMING_VBLANK) { |
| this.serviceDma(i, dma); |
| } |
| } |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.serviceDma = function(number, info) { |
| if (!info.enable) { |
| |
| return; |
| } |
|
|
| var width = info.width; |
| var sourceOffset = this.DMA_OFFSET[info.srcControl] * width; |
| var destOffset = this.DMA_OFFSET[info.dstControl] * width; |
| var wordsRemaining = info.nextCount; |
| var source = info.nextSource & this.OFFSET_MASK; |
| var dest = info.nextDest & this.OFFSET_MASK; |
| var sourceRegion = info.nextSource >>> this.BASE_OFFSET; |
| var destRegion = info.nextDest >>> this.BASE_OFFSET; |
| var sourceBlock = this.memory[sourceRegion]; |
| var destBlock = this.memory[destRegion]; |
| var sourceView = null; |
| var destView = null; |
| var sourceMask = 0xFFFFFFFF; |
| var destMask = 0xFFFFFFFF; |
| var word; |
|
|
| if (destBlock.ICACHE_PAGE_BITS) { |
| var endPage = (dest + wordsRemaining * width) >> destBlock.ICACHE_PAGE_BITS; |
| for (var i = dest >> destBlock.ICACHE_PAGE_BITS; i <= endPage; ++i) { |
| destBlock.invalidatePage(i << destBlock.ICACHE_PAGE_BITS); |
| } |
| } |
|
|
| if (destRegion == this.REGION_WORKING_RAM || destRegion == this.REGION_WORKING_IRAM) { |
| destView = destBlock.view; |
| destMask = destBlock.mask; |
| } |
|
|
| if (sourceRegion == this.REGION_WORKING_RAM || sourceRegion == this.REGION_WORKING_IRAM || sourceRegion == this.REGION_CART0 || sourceRegion == this.REGION_CART1) { |
| sourceView = sourceBlock.view; |
| sourceMask = sourceBlock.mask; |
| } |
|
|
| if (sourceBlock && destBlock) { |
| if (sourceView && destView) { |
| if (width == 4) { |
| source &= 0xFFFFFFFC; |
| dest &= 0xFFFFFFFC; |
| while (wordsRemaining--) { |
| word = sourceView.getInt32(source & sourceMask); |
| destView.setInt32(dest & destMask, word); |
| source += sourceOffset; |
| dest += destOffset; |
| } |
| } else { |
| while (wordsRemaining--) { |
| word = sourceView.getUint16(source & sourceMask); |
| destView.setUint16(dest & destMask, word); |
| source += sourceOffset; |
| dest += destOffset; |
| } |
| } |
| } else if (sourceView) { |
| if (width == 4) { |
| source &= 0xFFFFFFFC; |
| dest &= 0xFFFFFFFC; |
| while (wordsRemaining--) { |
| word = sourceView.getInt32(source & sourceMask, true); |
| destBlock.store32(dest, word); |
| source += sourceOffset; |
| dest += destOffset; |
| } |
| } else { |
| while (wordsRemaining--) { |
| word = sourceView.getUint16(source & sourceMask, true); |
| destBlock.store16(dest, word); |
| source += sourceOffset; |
| dest += destOffset; |
| } |
| } |
| } else { |
| if (width == 4) { |
| source &= 0xFFFFFFFC; |
| dest &= 0xFFFFFFFC; |
| while (wordsRemaining--) { |
| word = sourceBlock.load32(source); |
| destBlock.store32(dest, word); |
| source += sourceOffset; |
| dest += destOffset; |
| } |
| } else { |
| while (wordsRemaining--) { |
| word = sourceBlock.loadU16(source); |
| destBlock.store16(dest, word); |
| source += sourceOffset; |
| dest += destOffset; |
| } |
| } |
| } |
| } else { |
| this.core.WARN('Invalid DMA'); |
| } |
|
|
| if (info.doIrq) { |
| info.nextIRQ = this.cpu.cycles + 2; |
| info.nextIRQ += (width == 4 ? this.waitstates32[sourceRegion] + this.waitstates32[destRegion] |
| : this.waitstates[sourceRegion] + this.waitstates[destRegion]); |
| info.nextIRQ += (info.count - 1) * (width == 4 ? this.waitstatesSeq32[sourceRegion] + this.waitstatesSeq32[destRegion] |
| : this.waitstatesSeq[sourceRegion] + this.waitstatesSeq[destRegion]); |
| } |
|
|
| info.nextSource = source | (sourceRegion << this.BASE_OFFSET); |
| info.nextDest = dest | (destRegion << this.BASE_OFFSET); |
| info.nextCount = wordsRemaining; |
|
|
| if (!info.repeat) { |
| info.enable = false; |
|
|
| |
| var io = this.memory[this.REGION_IO]; |
| io.registers[this.DMA_REGISTER[number]] &= 0x7FE0; |
| } else { |
| info.nextCount = info.count; |
| if (info.dstControl == this.DMA_INCREMENT_RELOAD) { |
| info.nextDest = info.dest; |
| } |
| this.scheduleDma(number, info); |
| } |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.adjustTimings = function(word) { |
| var sram = word & 0x0003; |
| var ws0 = (word & 0x000C) >> 2; |
| var ws0seq = (word & 0x0010) >> 4; |
| var ws1 = (word & 0x0060) >> 5; |
| var ws1seq = (word & 0x0080) >> 7; |
| var ws2 = (word & 0x0300) >> 8; |
| var ws2seq = (word & 0x0400) >> 10; |
| var prefetch = word & 0x4000; |
|
|
| this.waitstates[this.REGION_CART_SRAM] = this.ROM_WS[sram]; |
| this.waitstatesSeq[this.REGION_CART_SRAM] = this.ROM_WS[sram]; |
| this.waitstates32[this.REGION_CART_SRAM] = this.ROM_WS[sram]; |
| this.waitstatesSeq32[this.REGION_CART_SRAM] = this.ROM_WS[sram]; |
|
|
| this.waitstates[this.REGION_CART0] = this.waitstates[this.REGION_CART0 + 1] = this.ROM_WS[ws0]; |
| this.waitstates[this.REGION_CART1] = this.waitstates[this.REGION_CART1 + 1] = this.ROM_WS[ws1]; |
| this.waitstates[this.REGION_CART2] = this.waitstates[this.REGION_CART2 + 1] = this.ROM_WS[ws2]; |
|
|
| this.waitstatesSeq[this.REGION_CART0] = this.waitstatesSeq[this.REGION_CART0 + 1] = this.ROM_WS_SEQ[0][ws0seq]; |
| this.waitstatesSeq[this.REGION_CART1] = this.waitstatesSeq[this.REGION_CART1 + 1] = this.ROM_WS_SEQ[1][ws1seq]; |
| this.waitstatesSeq[this.REGION_CART2] = this.waitstatesSeq[this.REGION_CART2 + 1] = this.ROM_WS_SEQ[2][ws2seq]; |
|
|
| this.waitstates32[this.REGION_CART0] = this.waitstates32[this.REGION_CART0 + 1] = this.waitstates[this.REGION_CART0] + 1 + this.waitstatesSeq[this.REGION_CART0]; |
| this.waitstates32[this.REGION_CART1] = this.waitstates32[this.REGION_CART1 + 1] = this.waitstates[this.REGION_CART1] + 1 + this.waitstatesSeq[this.REGION_CART1]; |
| this.waitstates32[this.REGION_CART2] = this.waitstates32[this.REGION_CART2 + 1] = this.waitstates[this.REGION_CART2] + 1 + this.waitstatesSeq[this.REGION_CART2]; |
|
|
| this.waitstatesSeq32[this.REGION_CART0] = this.waitstatesSeq32[this.REGION_CART0 + 1] = 2 * this.waitstatesSeq[this.REGION_CART0] + 1; |
| this.waitstatesSeq32[this.REGION_CART1] = this.waitstatesSeq32[this.REGION_CART1 + 1] = 2 * this.waitstatesSeq[this.REGION_CART1] + 1; |
| this.waitstatesSeq32[this.REGION_CART2] = this.waitstatesSeq32[this.REGION_CART2 + 1] = 2 * this.waitstatesSeq[this.REGION_CART2] + 1; |
|
|
| if (prefetch) { |
| this.waitstatesPrefetch[this.REGION_CART0] = this.waitstatesPrefetch[this.REGION_CART0 + 1] = 0; |
| this.waitstatesPrefetch[this.REGION_CART1] = this.waitstatesPrefetch[this.REGION_CART1 + 1] = 0; |
| this.waitstatesPrefetch[this.REGION_CART2] = this.waitstatesPrefetch[this.REGION_CART2 + 1] = 0; |
|
|
| this.waitstatesPrefetch32[this.REGION_CART0] = this.waitstatesPrefetch32[this.REGION_CART0 + 1] = 0; |
| this.waitstatesPrefetch32[this.REGION_CART1] = this.waitstatesPrefetch32[this.REGION_CART1 + 1] = 0; |
| this.waitstatesPrefetch32[this.REGION_CART2] = this.waitstatesPrefetch32[this.REGION_CART2 + 1] = 0; |
| } else { |
| this.waitstatesPrefetch[this.REGION_CART0] = this.waitstatesPrefetch[this.REGION_CART0 + 1] = this.waitstatesSeq[this.REGION_CART0]; |
| this.waitstatesPrefetch[this.REGION_CART1] = this.waitstatesPrefetch[this.REGION_CART1 + 1] = this.waitstatesSeq[this.REGION_CART1]; |
| this.waitstatesPrefetch[this.REGION_CART2] = this.waitstatesPrefetch[this.REGION_CART2 + 1] = this.waitstatesSeq[this.REGION_CART2]; |
|
|
| this.waitstatesPrefetch32[this.REGION_CART0] = this.waitstatesPrefetch32[this.REGION_CART0 + 1] = this.waitstatesSeq32[this.REGION_CART0]; |
| this.waitstatesPrefetch32[this.REGION_CART1] = this.waitstatesPrefetch32[this.REGION_CART1 + 1] = this.waitstatesSeq32[this.REGION_CART1]; |
| this.waitstatesPrefetch32[this.REGION_CART2] = this.waitstatesPrefetch32[this.REGION_CART2 + 1] = this.waitstatesSeq32[this.REGION_CART2]; |
| } |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.saveNeedsFlush = function() { |
| return this.save.writePending; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.flushSave = function() { |
| this.save.writePending = false; |
| }; |
|
|
| GameBoyAdvanceMMU.prototype.allocGPIO = function(rom) { |
| return new GameBoyAdvanceGPIO(this.core, rom); |
| }; |
|
|