/* * Copyright (c) 2007, Swedish Institute of Computer Science. * 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. * */ package se.sics.cooja.mspmote; import java.awt.Component; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.Hashtable; import org.apache.log4j.Logger; import org.jdom.Element; import se.sics.cooja.ContikiError; import se.sics.cooja.GUI; import se.sics.cooja.Mote; import se.sics.cooja.MoteInterface; import se.sics.cooja.MoteInterfaceHandler; import se.sics.cooja.MoteMemory; import se.sics.cooja.MoteType; import se.sics.cooja.Simulation; import se.sics.cooja.Watchpoint; import se.sics.cooja.WatchpointMote; import se.sics.cooja.interfaces.IPAddress; import se.sics.cooja.motes.AbstractEmulatedMote; import se.sics.cooja.mspmote.interfaces.Msp802154Radio; import se.sics.cooja.mspmote.interfaces.MspSerial; import se.sics.cooja.mspmote.plugins.CodeVisualizerSkin; import se.sics.cooja.mspmote.plugins.MspBreakpoint; import se.sics.cooja.plugins.Visualizer; import se.sics.mspsim.cli.CommandContext; import se.sics.mspsim.cli.CommandHandler; import se.sics.mspsim.cli.LineListener; import se.sics.mspsim.cli.LineOutputStream; import se.sics.mspsim.core.EmulationException; import se.sics.mspsim.core.LogListener; import se.sics.mspsim.core.Loggable; import se.sics.mspsim.core.MSP430; import se.sics.mspsim.core.EmulationLogger.WarningType; import se.sics.mspsim.platform.GenericNode; import se.sics.mspsim.ui.ManagedWindow; import se.sics.mspsim.ui.WindowManager; import se.sics.mspsim.util.ComponentRegistry; import se.sics.mspsim.util.ConfigManager; import se.sics.mspsim.util.DebugInfo; import se.sics.mspsim.util.ELF; import se.sics.mspsim.util.MapEntry; import se.sics.mspsim.util.MapTable; import se.sics.mspsim.profiler.SimpleProfiler; /** * @author Fredrik Osterlind */ public abstract class MspMote extends AbstractEmulatedMote implements Mote, WatchpointMote { private static Logger logger = Logger.getLogger(MspMote.class); private final static int EXECUTE_DURATION_US = 1; /* We always execute in 1 us steps */ { Visualizer.registerVisualizerSkin(CodeVisualizerSkin.class); } private CommandHandler commandHandler; private MSP430 myCpu = null; private MspMoteType myMoteType = null; private MspMoteMemory myMemory = null; private MoteInterfaceHandler myMoteInterfaceHandler = null; public ComponentRegistry registry = null; /* Stack monitoring variables */ private boolean stopNextInstruction = false; public GenericNode mspNode = null; public MspMote(MspMoteType moteType, Simulation simulation) { this.simulation = simulation; myMoteType = moteType; /* Schedule us immediately */ requestImmediateWakeup(); } protected void initMote() { if (myMoteType != null) { initEmulator(myMoteType.getContikiFirmwareFile()); myMoteInterfaceHandler = createMoteInterfaceHandler(); /* TODO Create COOJA-specific window manager */ registry.removeComponent("windowManager"); registry.registerComponent("windowManager", new WindowManager() { public ManagedWindow createWindow(String name) { return new ManagedWindow() { public void setVisible(boolean b) { logger.warn("setVisible() ignored"); } public void setTitle(String string) { logger.warn("setTitle() ignored"); } public void setSize(int width, int height) { logger.warn("setSize() ignored"); } public void setBounds(int x, int y, int width, int height) { logger.warn("setBounds() ignored"); } public void removeAll() { logger.warn("removeAll() ignored"); } public void pack() { logger.warn("pack() ignored"); } public boolean isVisible() { logger.warn("isVisible() return false"); return false; } public String getTitle() { logger.warn("getTitle() return \"\""); return ""; } public void add(Component component) { logger.warn("add() ignored"); } }; } }); try { debuggingInfo = ((MspMoteType)getType()).getFirmwareDebugInfo(); } catch (IOException e) { throw (RuntimeException) new RuntimeException("Error: " + e.getMessage()).initCause(e); } } } /** * Abort execution immediately. * May for example be called by a breakpoint handler. */ public void stopNextInstruction() { stopNextInstruction = true; getCPU().stop(); } protected MoteInterfaceHandler createMoteInterfaceHandler() { return new MoteInterfaceHandler(this, getType().getMoteInterfaceClasses()); } /** * @return MSP430 CPU */ public MSP430 getCPU() { return myCpu; } public void setCPU(MSP430 cpu) { myCpu = cpu; } public MoteMemory getMemory() { return myMemory; } public void setMemory(MoteMemory memory) { myMemory = (MspMoteMemory) memory; } /** * Prepares CPU, memory and ELF module. * * @param fileELF ELF file * @param cpu MSP430 cpu * @throws IOException Preparing mote failed */ protected void prepareMote(File fileELF, GenericNode node) throws IOException { this.commandHandler = new CommandHandler(System.out, System.err); this.mspNode = node; node.setCommandHandler(commandHandler); ConfigManager config = new ConfigManager(); node.setup(config); this.myCpu = node.getCPU(); this.myCpu.setMonitorExec(true); this.myCpu.setTrace(0); /* TODO Enable */ LogListener ll = new LogListener() { private Logger mlogger = Logger.getLogger("MSPSim"); @Override public void log(Loggable source, String message) { mlogger.debug("" + getID() + ": " + source.getID() + ": " + message); } @Override public void logw(Loggable source, WarningType type, String message) throws EmulationException { mlogger.warn("" + getID() +": " + "# " + source.getID() + "[" + type + "]: " + message); } }; this.myCpu.getLogger().addLogListener(ll); logger.info("Loading firmware from: " + fileELF.getAbsolutePath()); GUI.setProgressMessage("Loading " + fileELF.getName()); node.loadFirmware(((MspMoteType)getType()).getELF()); /* Throw exceptions at bad memory access */ /*myCpu.setThrowIfWarning(true);*/ /* Create mote address memory */ MapTable map = ((MspMoteType)getType()).getELF().getMap(); MapEntry[] allEntries = map.getAllEntries(); myMemory = new MspMoteMemory(this, allEntries, myCpu); myCpu.reset(); } public CommandHandler getCLICommandHandler() { return commandHandler; } /* called when moteID is updated */ public void idUpdated(int newID) { } public MoteType getType() { return myMoteType; } public void setType(MoteType type) { myMoteType = (MspMoteType) type; } public MoteInterfaceHandler getInterfaces() { return myMoteInterfaceHandler; } public void setInterfaces(MoteInterfaceHandler moteInterfaceHandler) { myMoteInterfaceHandler = moteInterfaceHandler; } /** * Initializes emulator by creating CPU, memory and node object. * * @param ELFFile ELF file * @return True if successful */ protected abstract boolean initEmulator(File ELFFile); private boolean booted = false; public void simTimeChanged(long diff) { /* Compensates for simulation time changes (without simulation execution) */ lastExecute -= diff; nextExecute -= diff; scheduleNextWakeup(nextExecute); } private long lastExecute = -1; /* Last time mote executed */ private long nextExecute; public void execute(long time) { execute(time, EXECUTE_DURATION_US); } public void execute(long t, int duration) { /* Wait until mote boots */ if (!booted && myMoteInterfaceHandler.getClock().getTime() < 0) { scheduleNextWakeup(t - myMoteInterfaceHandler.getClock().getTime()); return; } booted = true; if (stopNextInstruction) { stopNextInstruction = false; scheduleNextWakeup(t); throw new RuntimeException("MSPSim requested simulation stop"); } if (lastExecute < 0) { /* Always execute one microsecond the first time */ lastExecute = t; } if (t < lastExecute) { throw new RuntimeException("Bad event ordering: " + lastExecute + " < " + t); } /* Execute MSPSim-based mote */ /* TODO Try-catch overhead */ try { nextExecute = t + duration + myCpu.stepMicros(t - lastExecute, duration); lastExecute = t; } catch (EmulationException e) { String trace = e.getMessage() + "\n\n" + getStackTrace(); throw (ContikiError) new ContikiError(trace).initCause(e); } /* Schedule wakeup */ if (nextExecute < t) { throw new RuntimeException(t + ": MSPSim requested early wakeup: " + nextExecute); } /*logger.debug(t + ": Schedule next wakeup at " + nextExecute);*/ scheduleNextWakeup(nextExecute); if (stopNextInstruction) { stopNextInstruction = false; throw new RuntimeException("MSPSim requested simulation stop"); } /* XXX TODO Reimplement stack monitoring using MSPSim internals */ /*if (monitorStackUsage) { int newStack = cpu.reg[MSP430.SP]; if (newStack < stackPointerLow && newStack > 0) { stackPointerLow = cpu.reg[MSP430.SP]; // Check if stack is writing in memory if (stackPointerLow < heapStartAddress) { stackOverflowObservable.signalStackOverflow(); stopNextInstruction = true; getSimulation().stopSimulation(); } } }*/ } public String getStackTrace() { return executeCLICommand("stacktrace"); } public int executeCLICommand(String cmd, CommandContext context) { return commandHandler.executeCommand(cmd, context); } public String executeCLICommand(String cmd) { final StringBuilder sb = new StringBuilder(); LineListener ll = new LineListener() { public void lineRead(String line) { sb.append(line).append("\n"); } }; PrintStream po = new PrintStream(new LineOutputStream(ll)); CommandContext c = new CommandContext(commandHandler, null, "", new String[0], 1, null); c.out = po; c.err = po; if (0 != executeCLICommand(cmd, c)) { sb.append("\nWarning: command failed"); } return sb.toString(); } public int getCPUFrequency() { return myCpu.getDCOFrequency(); } public int getID() { return getInterfaces().getMoteID().getMoteID(); } public boolean setConfigXML(Simulation simulation, Collection configXML, boolean visAvailable) { setSimulation(simulation); if (myMoteInterfaceHandler == null) { myMoteInterfaceHandler = createMoteInterfaceHandler(); } try { debuggingInfo = ((MspMoteType)getType()).getFirmwareDebugInfo(); } catch (IOException e) { throw (RuntimeException) new RuntimeException("Error: " + e.getMessage()).initCause(e); } for (Element element: configXML) { String name = element.getName(); if (name.equals("motetype_identifier")) { /* Ignored: handled by simulation */ } else if ("breakpoints".equals(element.getName())) { setWatchpointConfigXML(element.getChildren(), visAvailable); } else if (name.equals("interface_config")) { String intfClass = element.getText().trim(); if (intfClass.equals("se.sics.cooja.mspmote.interfaces.MspIPAddress")) { intfClass = IPAddress.class.getName(); } if (intfClass.equals("se.sics.cooja.mspmote.interfaces.ESBLog")) { intfClass = MspSerial.class.getName(); } if (intfClass.equals("se.sics.cooja.mspmote.interfaces.SkyByteRadio")) { intfClass = Msp802154Radio.class.getName(); } if (intfClass.equals("se.sics.cooja.mspmote.interfaces.SkySerial")) { intfClass = MspSerial.class.getName(); } Class moteInterfaceClass = simulation.getGUI().tryLoadClass( this, MoteInterface.class, intfClass); if (moteInterfaceClass == null) { logger.fatal("Could not load mote interface class: " + intfClass); return false; } MoteInterface moteInterface = getInterfaces().getInterfaceOfType(moteInterfaceClass); if (moteInterface == null) { logger.fatal("Could not find mote interface of class: " + moteInterfaceClass); return false; } moteInterface.setConfigXML(element.getChildren(), visAvailable); } } /* Schedule us immediately */ requestImmediateWakeup(); return true; } public Collection getConfigXML() { ArrayList config = new ArrayList(); Element element; /* Breakpoints */ element = new Element("breakpoints"); element.addContent(getWatchpointConfigXML()); config.add(element); // Mote interfaces for (MoteInterface moteInterface: getInterfaces().getInterfaces()) { element = new Element("interface_config"); element.setText(moteInterface.getClass().getName()); Collection interfaceXML = moteInterface.getConfigXML(); if (interfaceXML != null) { element.addContent(interfaceXML); config.add(element); } } return config; } public String getExecutionDetails() { return executeCLICommand("stacktrace"); } public String getPCString() { int pc = myCpu.getPC(); ELF elf = myCpu.getRegistry().getComponent(ELF.class); DebugInfo di = elf.getDebugInfo(pc); /* Following code examples from MSPsim, DebugCommands.java */ if (di == null) { di = elf.getDebugInfo(pc + 1); } if (di == null) { /* Return PC value */ SimpleProfiler sp = (SimpleProfiler)myCpu.getProfiler(); try { MapEntry mapEntry = sp.getCallMapEntry(0); if (mapEntry != null) { String file = mapEntry.getFile(); if (file != null) { if (file.indexOf('/') >= 0) { file = file.substring(file.lastIndexOf('/')+1); } } String name = mapEntry.getName(); return file + ":?:" + name; } return String.format("*%02x", pc); } catch (Exception e) { return null; } } int lineNo = di.getLine(); String file = di.getFile(); file = file==null?"?":file; if (file.contains("/")) { /* strip path */ file = file.substring(file.lastIndexOf('/')+1, file.length()); } String function = di.getFunction(); function = function==null?"":function; if (function.contains(":")) { /* strip arguments */ function = function.substring(0, function.lastIndexOf(':')); } if (function.equals("* not available")) { function = "?"; } return file + ":" + lineNo + ":" + function; /*return executeCLICommand("line " + myCpu.getPC());*/ } /* WatchpointMote */ private ArrayList watchpointListeners = new ArrayList(); private ArrayList watchpoints = new ArrayList(); private Hashtable> debuggingInfo = null; public void addWatchpointListener(WatchpointListener listener) { watchpointListeners.add(listener); } public void removeWatchpointListener(WatchpointListener listener) { watchpointListeners.remove(listener); } public WatchpointListener[] getWatchpointListeners() { return watchpointListeners.toArray(new WatchpointListener[0]); } public Watchpoint addBreakpoint(File codeFile, int lineNr, int address) { MspBreakpoint bp = new MspBreakpoint(this, address, codeFile, new Integer(lineNr)); watchpoints.add(bp); for (WatchpointListener listener: watchpointListeners) { listener.watchpointsChanged(); } return bp; } public void removeBreakpoint(Watchpoint watchpoint) { ((MspBreakpoint)watchpoint).unregisterBreakpoint(); watchpoints.remove(watchpoint); for (WatchpointListener listener: watchpointListeners) { listener.watchpointsChanged(); } } public Watchpoint[] getBreakpoints() { return watchpoints.toArray(new Watchpoint[0]); } public boolean breakpointExists(int address) { if (address < 0) { return false; } for (Watchpoint watchpoint: watchpoints) { if (watchpoint.getExecutableAddress() == address) { return true; } } return false; } public boolean breakpointExists(File file, int lineNr) { for (Watchpoint watchpoint: watchpoints) { if (watchpoint.getCodeFile() == null) { continue; } if (watchpoint.getCodeFile().compareTo(file) != 0) { continue; } if (watchpoint.getLineNumber() != lineNr) { continue; } return true; } return false; } public int getExecutableAddressOf(File file, int lineNr) { if (file == null || lineNr < 0 || debuggingInfo == null) { return -1; } /* Match file */ Hashtable lineTable = debuggingInfo.get(file); if (lineTable == null) { for (File f: debuggingInfo.keySet()) { if (f != null && f.getName().equals(file.getName())) { lineTable = debuggingInfo.get(f); break; } } } if (lineTable == null) { return -1; } /* Match line number */ Integer address = lineTable.get(lineNr); if (address != null) { for (Integer l: lineTable.keySet()) { if (l != null && l.intValue() == lineNr) { /* Found line address */ return lineTable.get(l); } } } return -1; } private long lastBreakpointCycles = -1; public void signalBreakpointTrigger(MspBreakpoint b) { if (lastBreakpointCycles == myCpu.cycles) { return; } lastBreakpointCycles = myCpu.cycles; if (b.stopsSimulation() && getSimulation().isRunning()) { /* Stop simulation immediately */ stopNextInstruction(); } /* Notify listeners */ WatchpointListener[] listeners = getWatchpointListeners(); for (WatchpointListener listener: listeners) { listener.watchpointTriggered(b); } } public Collection getWatchpointConfigXML() { ArrayList config = new ArrayList(); Element element; for (MspBreakpoint breakpoint: watchpoints) { element = new Element("breakpoint"); element.addContent(breakpoint.getConfigXML()); config.add(element); } return config; } public boolean setWatchpointConfigXML(Collection configXML, boolean visAvailable) { for (Element element : configXML) { if (element.getName().equals("breakpoint")) { MspBreakpoint breakpoint = new MspBreakpoint(this); if (!breakpoint.setConfigXML(element.getChildren(), visAvailable)) { logger.warn("Could not restore breakpoint: " + breakpoint); } else { watchpoints.add(breakpoint); } } } return true; } }