mspsim/se/sics/mspsim/core/Flash.java

595 lines
16 KiB
Java

/*
* Copyright (c) 2009, Friedrich-Alexander University Erlangen, Germany
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of mspsim.
*
*/
/**
* @author Klaus Stengel <siklsten@informatik.stud.uni-erlangen.de>
*/
package se.sics.mspsim.core;
import java.util.Arrays;
import se.sics.mspsim.core.Memory.AccessMode;
import se.sics.mspsim.util.Utils;
public class Flash extends IOUnit {
private static final int FCTL1 = 0x00;
private static final int FCTL2 = 0x02;
private static final int FCTL3 = 0x04;
private static final int FCTL4 = 0x06;
private static final int FRKEY = 0x9600;
private static final int FWKEY = 0xA500;
private static final int KEYMASK = 0xff00;
private static final int CMDMASK = 0x00ff;
private static final int BLKWRT = 0x80;
private static final int WRT = 0x40;
private static final int ERASE_SHIFT = 1;
private static final int ERASE_MASK = 0x06;
/* Erase modes needs to be first due to usage of ordinality */
private enum WriteMode {
NONE,
ERASE_SEGMENT,
ERASE_MAIN,
ERASE_ALL,
WRITE_SINGLE,
WRITE_BLOCK,
WRITE_BLOCK_FINISH
}
private static final int EMEX = 0x20;
private static final int LOCK = 0x10;
private static final int WAIT = 0x08;
private static final int ACCVIFG = 0x04;
private static final int KEYV = 0x02;
private static final int BUSY = 0x01;
private static final int FSSEL_SHIFT = 6;
private static final int FSSEL_MASK = 0xc0;
private static final int RESET_VECTOR = 15;
private static final int NMI_VECTOR = 14;
private static final int ACCVIE = 1 << 5;
private enum ClockSource {
ACLK,
MCLK,
SMCLK
};
private static final int MASS_ERASE_TIME = 5297;
private static final int SEGMENT_ERASE_TIME = 4819;
private static final int WRITE_TIME = 35;
private static final int BLOCKWRITE_FIRST_TIME = 30;
private static final int BLOCKWRITE_TIME = 21;
private static final int BLOCKWRITE_END_TIME = 6;
private static final int FN_MASK = 0x3f;
private FlashRange main_range;
private FlashRange info_range;
private int mode; /* FCTL1 */
private int clockcfg; /* FCTL2 */
private int statusreg; /* FCTL3 */
private boolean locked;
private boolean wait;
private boolean blocked_cpu;
private WriteMode currentWriteMode;
private int blockwriteCount;
private TimeEvent end_process = new TimeEvent(0) {
public void execute(long t) {
blocked_cpu = false;
switch(currentWriteMode) {
case NONE:
break;
case ERASE_SEGMENT:
case ERASE_MAIN:
case ERASE_ALL:
// Erase flags are automatically cleared after each erase
mode = 0;
currentWriteMode = WriteMode.NONE;
cpu.isFlashBusy = false;
break;
case WRITE_SINGLE:
cpu.isFlashBusy = false;
// WRT flags are NOT automatically cleared
break;
case WRITE_BLOCK:
blockwriteCount++;
if (blockwriteCount == 64) {
// FIXME: What happens if we try to write more than 64 bytes
// on real hardware???
logw("Last access in block mode. Forced exit?");
currentWriteMode = WriteMode.WRITE_BLOCK_FINISH;
}
/* if (DEBUG) {
System.out.println("Write cycle complete, flagged WAIT.");
} */
wait = true;
break;
case WRITE_BLOCK_FINISH:
if (DEBUG) {
log("Programming voltage dropped, write mode disabled.");
}
currentWriteMode = WriteMode.NONE;
cpu.isFlashBusy = false;
wait = true;
mode = 0;
break;
}
}
};
public Flash(MSP430Core cpu, int[] memory, FlashRange main_range,
FlashRange info_range, int offset) {
super("Flash", "Internal Flash", cpu, memory, offset);
this.main_range = main_range;
this.info_range = info_range;
locked = true;
Arrays.fill(memory, main_range.start, main_range.end, 0xff);
Arrays.fill(memory, info_range.start, info_range.end, 0xff);
reset(MSP430.RESET_POR);
}
public boolean blocksCPU() {
return blocked_cpu;
}
public void interruptServiced(int vector) {
cpu.flagInterrupt(vector, this, false);
}
public boolean addressInFlash(int address) {
if (main_range.isInRange(address)) {
return true;
}
if (info_range.isInRange(address)) {
return true;
}
return false;
}
private int getFlashClockDiv() {
return (clockcfg & FN_MASK) + 1;
}
private void waitFlashProcess(int time) {
int instr_addr = cpu.getPC();
int freqdiv = getFlashClockDiv();
int myfreq;
double finish_msec;
cpu.isFlashBusy = true;
if (addressInFlash(instr_addr)) {
blocked_cpu = true;
}
switch(getClockSource()) {
case ACLK:
myfreq = cpu.aclkFrq / freqdiv;
finish_msec = ((double)time * freqdiv * 1000) / cpu.aclkFrq;
if (DEBUG)
log("Using ACLK source with f=" + myfreq + "Hz. Time required=" + finish_msec + " ms");
cpu.scheduleTimeEventMillis(end_process, finish_msec);
break;
case SMCLK:
myfreq = cpu.smclkFrq / freqdiv;
finish_msec = ((double)time * freqdiv * 1000) / cpu.smclkFrq;
/* if (DEBUG)
System.out.println("Flash: Using SMCLK source with f=" + myfreq
+ " Hz\nFlash: Time required=" + finish_msec + " ms"); */
cpu.scheduleTimeEventMillis(end_process, finish_msec);
break;
case MCLK:
if (DEBUG)
log("Using MCLK source with div=" + freqdiv);
cpu.scheduleCycleEvent(end_process, (long)time * freqdiv);
break;
}
}
public boolean needsTick() {
return false;
}
public void flashWrite(int address, int data, AccessMode dataMode) {
int wait_time = -1;
if (locked) {
if (DEBUG) {
log("Write to flash blocked because of LOCK flag.");
}
return;
}
if (cpu.isFlashBusy || wait == false) {
if (!((mode & BLKWRT) != 0 && wait)) {
triggerAccessViolation("Flash write prohbited while BUSY=1 or WAIT=0");
return;
}
}
switch(currentWriteMode) {
case ERASE_SEGMENT:
int a_area_start[] = new int[1];
int a_area_end[] = new int[1];
getSegmentRange(address, a_area_start, a_area_end);
int area_start = a_area_start[0];
int area_end = a_area_end[0];
if (DEBUG) {
log("Segment erase @" + Utils.hex(address, 4) +
": erasing area " + Utils.hex(area_start, 4) + "-" +
Utils.hex(area_end, 4));
}
for (int i = area_start; i < area_end; i++) {
memory[i] = 0xff;
}
waitFlashProcess(SEGMENT_ERASE_TIME);
break;
case ERASE_MAIN:
if (! main_range.isInRange(address)) {
return;
}
for (int i = main_range.start; i < main_range.end; i++) {
memory[i] = 0xff;
}
waitFlashProcess(MASS_ERASE_TIME);
break;
case ERASE_ALL:
for (int i = main_range.start; i < main_range.end; i++) {
memory[i] = 0xff;
}
for (int i = info_range.start; i < main_range.end; i++) {
memory[i] = 0xff;
}
waitFlashProcess(MASS_ERASE_TIME);
break;
case WRITE_SINGLE:
case WRITE_BLOCK:
if (currentWriteMode == WriteMode.WRITE_BLOCK) {
wait = false;
// TODO: Register target block and verify all writes stay in the same
// block. What does the real hardware on random writes?!?
if (blockwriteCount == 0) {
wait_time = BLOCKWRITE_FIRST_TIME;
if (DEBUG) {
log("Flash write in block mode started @" + Utils.hex(address, 4));
}
if (addressInFlash(cpu.getPC())) {
logw("Oops. Block write access only allowed when executing from RAM.");
}
} else {
wait_time = BLOCKWRITE_TIME;
}
} else {
wait_time = WRITE_TIME;
}
/* Flash memory allows clearing bits only */
memory[address] &= data & 0xff;
if (dataMode != AccessMode.BYTE) {
memory[address + 1] &= (data >> 8) & 0xff;
if (dataMode == AccessMode.WORD20) {
/* TODO should the write really write the full word? CHECK THIS */
memory[address + 2] &= (data >> 16) & 0xff;
memory[address + 3] &= (data >> 24) & 0xff;
}
}
if (DEBUG) {
log("Writing $" + Utils.hex20(data) + " to $" + Utils.hex(address, 4) + " (" + dataMode.bytes + " bytes)");
}
waitFlashProcess(wait_time);
break;
}
}
public void notifyRead(int address) {
if (cpu.isFlashBusy) {
triggerAccessViolation("Flash read not allowed while BUSY flag set");
return;
}
if (DEBUG) {
if (wait == false && currentWriteMode == WriteMode.WRITE_BLOCK) {
log("Reading flash prohibited. Would read 0x3fff!!!");
log("CPU PC=$" + Utils.hex(cpu.getPC(), 4)
+ " read address $" + Utils.hex(address, 4));
}
}
}
private FlashRange getFlashRange(int address) {
if (main_range.isInRange(address)) {
return main_range;
}
if (info_range.isInRange(address)) {
return info_range;
}
return null;
}
private void getSegmentRange(int address, int[] start, int[] end) {
FlashRange addr_type = getFlashRange(address);
int segsize, ioffset;
if (addr_type == null) {
throw new RuntimeException("Address not in flash");
}
segsize = addr_type.segment_size;
ioffset = address - addr_type.start;
ioffset /= segsize;
ioffset *= segsize;
start[0] = addr_type.start + ioffset;
end[0] = start[0] + segsize;
}
public int read(int address, boolean word, long cycles) {
address = address - offset;
if (address == FCTL1) {
return mode | FRKEY;
}
if (address == FCTL2) {
return clockcfg | FRKEY;
}
if (address == FCTL3) {
int retval = statusreg | FRKEY;
if (cpu.isFlashBusy)
retval |= BUSY;
if (locked)
retval |= LOCK;
if (wait)
retval |= WAIT;
return retval;
}
return 0;
}
private ClockSource getClockSource() {
switch((clockcfg & FSSEL_MASK) >> FSSEL_SHIFT) {
case 0:
return ClockSource.ACLK;
case 1:
return ClockSource.MCLK;
case 2:
case 3:
return ClockSource.SMCLK;
}
throw new RuntimeException("Bad clock source");
}
private boolean checkKey(int value) {
if ((value & KEYMASK) == FWKEY)
return true;
logw("Bad key accessing flash controller --> reset");
statusreg |= KEYV;
cpu.flagInterrupt(RESET_VECTOR, this, true);
return false;
}
private void triggerEmergencyExit() {
mode = 0;
cpu.isFlashBusy = false;
wait = true;
locked = true;
currentWriteMode = WriteMode.NONE;
}
private WriteMode getEraseMode(int regdata) {
int idx = (regdata & ERASE_MASK) >> ERASE_SHIFT;
for (WriteMode em : WriteMode.values()) {
if (em.ordinal() == idx)
return em;
}
throw new IllegalArgumentException("Invalid erase mode: " + regdata);
}
private void triggerErase(int newmode) {
currentWriteMode = getEraseMode(newmode);
}
private void triggerLockFlash() {
locked = true;
}
private void triggerUnlockFlash() {
locked = false;
}
private void triggerAccessViolation(String reason) {
logw("Access violation: " + reason + ". PC=$" + Utils.hex(cpu.getPC(), 4));
statusreg |= ACCVIFG;
if (cpu.getSFR().isIEBitsSet(SFR.IE1, ACCVIE)) {
cpu.flagInterrupt(NMI_VECTOR, this, true);
}
}
private void triggerSingleWrite() {
/*if (DEBUG) {
System.out.println("Single write triggered");
}*/
currentWriteMode = WriteMode.WRITE_SINGLE;
}
private void triggerBlockWrite() {
if (DEBUG) {
log("Block write triggered");
}
currentWriteMode = WriteMode.WRITE_BLOCK;
blockwriteCount = 0;
}
private void triggerEndBlockWrite() {
if (DEBUG) {
log("Got end of flash block write");
}
currentWriteMode = WriteMode.WRITE_BLOCK_FINISH;
waitFlashProcess(BLOCKWRITE_END_TIME);
}
public void write(int address, int value, boolean word, long cycles) {
address = address - offset;
if (!word) {
logw("Invalid access type to flash controller");
return;
}
if (!(address == FCTL1 || address == FCTL2 || address == FCTL3)) {
return;
}
if (!checkKey(value)) {
return;
}
int regdata = value & CMDMASK;
switch (address) {
case FCTL1:
// access violation while erase/write in progress
// exception: block write mode and WAIT==1
// if ((mode & ERASE_MASK) != 0 || (mode & WRT) != 0) {
if (cpu.isFlashBusy && ((mode & BLKWRT) == 0 || wait == false)) {
// if (!((mode & BLKWRT) != 0 && wait)) {
triggerAccessViolation("FCTL1 write not allowed while erase/write active");
return;
}
if ((mode & ERASE_MASK) != (regdata & ERASE_MASK)) {
if ((mode & ERASE_MASK) == 0) {
triggerErase(regdata);
}
mode &= ~ERASE_MASK;
mode |= regdata & ERASE_MASK;
}
if ((mode & WRT) != (regdata & WRT)) {
if ((regdata & WRT) != 0) {
if ((regdata & BLKWRT) != 0) {
triggerBlockWrite();
mode |= BLKWRT;
} else {
triggerSingleWrite();
}
}
mode &= ~WRT;
mode |= regdata & WRT;
}
if ((mode & BLKWRT) != 0 && (regdata & BLKWRT) == 0) {
triggerEndBlockWrite();
mode &= ~BLKWRT;
}
break;
case FCTL2:
// access violation if BUSY==1
if (cpu.isFlashBusy) {
triggerAccessViolation(
"Register write to FCTL2 not allowed when busy");
return;
}
clockcfg = regdata;
break;
case FCTL3:
if ((statusreg & EMEX) == 0 && (regdata & EMEX) == 1) {
triggerEmergencyExit();
}
if (locked && (regdata & LOCK) == 0) {
triggerUnlockFlash();
} else {
if (!locked && (regdata & LOCK) != 0) {
triggerLockFlash();
}
}
if (((statusreg ^ regdata) & KEYV) != 0) {
statusreg ^= KEYV;
}
if (((statusreg ^ regdata) & ACCVIFG) != 0) {
statusreg ^= ACCVIFG;
}
break;
}
}
public void reset(int type) {
if (DEBUG) {
log("Got reset!");
}
if (type == MSP430.RESET_POR)
statusreg = 0;
mode = 0;
clockcfg = 0x42;
cpu.isFlashBusy = false;
wait = true;
locked = true;
currentWriteMode = WriteMode.NONE;
}
}