mirror of https://github.com/contiki-ng/mspsim
595 lines
16 KiB
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;
|
|
}
|
|
}
|