code: /**
   * Copyright (c) 2004-2005, Regents of the University of California
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * Redistributions of source code must retain the above copyright notice,
   * this list of conditions and the following disclaimer.
   *
   * 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.
   *
   * Neither the name of the University of California, Los Angeles 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 COPYRIGHT HOLDERS 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 COPYRIGHT
   * OWNER 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 avrora.sim.mcu;
  
  import avrora.arch.legacy.*;
  import avrora.core.Program;
  import avrora.sim.*;
  import avrora.sim.clock.MainClock;
  import avrora.sim.output.SimPrinter;
  import cck.text.StringUtil;
  import cck.util.Arithmetic;
  import cck.util.Util;
  
  /**
   * The <code>ReprogrammableCodeSegment</code> class represents a flash segment that stores code. This segment
   * can be updated during execution. It supports probing instructions in the flash segment and updating them.
   * This implementation uses special instructions that automatically disassemble themselves from the machine
   * code representation when they are accessed, allowing dynamic update and execution of machine code.
   *
   * @author Ben L. Titzer
   */
  public class ReprogrammableCodeSegment extends CodeSegment {
  
      @SuppressWarnings("unused")
      private static final double ERASE_MS_MIN = 3.7;
      @SuppressWarnings("unused")
      private static final double WRITE_MS_MIN = 3.7;
      private static final double ERASE_MS_MAX = 4.5;
      private static final double WRITE_MS_MAX = 4.5;
      private static final int SPM_TIMEOUT = 4;
  
      private static final int STATE_NONE = 0;
      private static final int STATE_PGERASE = 1 << 1 | 1;
      private static final int STATE_RWWSRE  = 1 << 4 | 1;
      private static final int STATE_BLBSET  = 1 << 3 | 1;
      private static final int STATE_FILL    = 1;
      private static final int STATE_PGWRITE = 1 << 2 | 1;
  
      private static final int SPM_READY = 35;
      private static final int SPMCSR_LOWERBITS = 0x1f;
  
      private static final byte DEFAULT_VALUE = (byte)0xff;
  
      final SimPrinter flashPrinter;
  
      /**
       * The <code>ReprogrammableCodeSegment.Factory</code> class represents a class capable of creating a new
       * code segment for a new interpreter.
       */
      public static class Factory implements CodeSegment.Factory {
          final int pagesize;
          final int size;
  
          Factory(int size, int pagesize) {
              this.size = size;
              this.pagesize = pagesize;
          }
  
          public CodeSegment newCodeSegment(String name, AtmelInterpreter bi, Program p) {
              CodeSegment cs;
              if ( p != null ) {
                  cs = new ReprogrammableCodeSegment(name, p.program_end, bi, pagesize);
                  cs.load(p);
              } else {
                  cs = new ReprogrammableCodeSegment(name, size, bi, pagesize);
              }
              return cs;
          }
      }
  
      /**
       * The <code>SPMCSR_reg</code> class represents an instanceof the <code>ActiveRegister</code> interface
       * that is used to represent the SPMCSR register on the ATMega family microcontrollers. This register
       * is used in reprogramming the flash memory from within the program.
       */
      private class SPMCSR_reg extends RWRegister {
          ResetEvent reset = new ResetEvent();
  
          public void write(byte val) {
  
              int lower = val & SPMCSR_LOWERBITS;
              switch ( lower ) {
                  case STATE_PGERASE:
                  case STATE_RWWSRE:
                  case STATE_BLBSET:
                  case STATE_FILL:
                  case STATE_PGWRITE:
                      mainClock.removeEvent(reset);
                      mainClock.insertEvent(reset, SPM_TIMEOUT+2);
                      break;
                  default:
                      lower = STATE_NONE;
              }
  
              this.value = (byte)(val & (~ SPMCSR_LOWERBITS) | lower);
  
              interpreter.setEnabled(SPM_READY, Arithmetic.getBit(value, 7));
              interpreter.setPosted(SPM_READY, !Arithmetic.getBit(value, 0));
          }
  
          class ResetEvent implements Simulator.Event {
              public void fire() {
                  if ( flashPrinter != null )
                      flashPrinter.println("FLASH: write to SPMCSR timed out after 4 cycles");
                  reset();
              }
  
          }
  
          int getState() {
              return value & SPMCSR_LOWERBITS;
          }
  
          void reset() {
              write((byte)(value & (~ SPMCSR_LOWERBITS)));
          }
  
          boolean isBusy() {
              return Arithmetic.getBit(value, 6);
          }
  
          void setBusy() {
              value = Arithmetic.setBit(value, 6);
          }
  
          void clearBusy() {
              value = Arithmetic.clearBit(value, 6);
          }
      }
  
      /**
       * The <code>disassembler</code> field stores a reference to a disassembler for this segment.
       * This is needed because disassemblers are currently not re-entrant.
       */
      LegacyDisassembler disassembler = new LegacyDisassembler();
  
      /**
       * The <code>buffer</code> field stores a reference to the bytes in the temporary page buffer
       * which is used to rewrite the flash memory.
       */
      byte[] buffer;
  
      /**
       * The <code>SPMCSR</code> field stores a reference to the SPMCSR register which is an IO register
       * that the program uses to select which flash operations to perform.
       */
      final SPMCSR_reg SPMCSR;
  
      /**
       * The <code>ERASE_CYCLES</code> field stores the number of cycles needed to complete an erase operation.
       */
      final int ERASE_CYCLES; // from hardware manual
  
      /**
       * The <code>WRITE_CYCLES</code> field stores the number of cycles needed to complete a write operation.
       */
      final int WRITE_CYCLES; // from hardware manual
  
      /**
       * The <code>pagesize</code> field stores the number of bits in the page offset field of an address; i.e.
       * it is the log of the size of a page in words.
       */
      final int pagesize;
  
      /**
       * The <code>addressMask</code> field stores an integer used to mask out the page offset of an address.
       */
      final int addressMask;
  
      /**
       * The <code>mainClock</code> method stores a reference to the main clock signal of the chip.
       */
      final MainClock mainClock;
  
      /**
       * The constructor for the <code>ReprogrammableCodeSegment</code> creates a new instance with the specified
       * name, with the specified size, connected to the specified microcontroller, with the given page size.
       * @param name the name of the segment as a string
       * @param size the size of the segment in bytes
       * @param bi the the interpreter the code segment is attached to
       * @param pagesize the size of the page offset field of an address into the flash
       */
      public ReprogrammableCodeSegment(String name, int size, AtmelInterpreter bi, int pagesize) {
          super(name, size, bi);
          SPMCSR = new SPMCSR_reg();
          mainClock = bi.getMainClock();
          this.pagesize = pagesize;
          this.addressMask = Arithmetic.getBitRangeMask(1, pagesize + 1);
          resetBuffer();
          MCUProperties props = bi.getSimulator().getMicrocontroller().getProperties();
          bi.installIOReg(props.getIOReg("SPMCSR"), SPMCSR);
  
          ERASE_CYCLES = (int)((mainClock.getHZ() * ERASE_MS_MAX / 1000));
          WRITE_CYCLES = (int)((mainClock.getHZ() * WRITE_MS_MAX / 1000));
  
          flashPrinter = bi.getSimulator().getPrinter("atmel.flash");
      }
  
      /**
       * The <code>update()</code> method is called by the interpreter when the program executes an instruction
       * that updates the program memory. For example, the SPM instruction.
       */
      public void update() {
          // TODO: check that PC is in the bootloader section
          //int pc = interpreter.getState().getPC();
          int Z = interpreter.getRegisterWord(LegacyRegister.Z);
          int pageoffset = (Z & addressMask);
          int pagenum = Z >> (pagesize + 1);
          // for models with more than 128k flash, we need to use RAMPZ
          if (interpreter.RAMPZ > 0) {
              pagenum += interpreter.getIORegisterByte(interpreter.RAMPZ) << (16 - pagesize - 1);
          }
          // do not update the ReprogrammableCodeSegment register yet
          int state = SPMCSR.getState();
          switch (state) {
              case STATE_PGERASE:
                  if (flashPrinter != null) flashPrinter.println("FLASH: page erase of page " + pagenum);
                  pageErase(pagenum, pageoffset);
                  break;
              case STATE_RWWSRE:
                  if (flashPrinter != null) flashPrinter.println("FLASH: reset RWW section ");
                  resetRWW();
                  break;
              case STATE_BLBSET:
                  if (flashPrinter != null) flashPrinter.println("FLASH: boot lock bits set");
                  mainClock.removeEvent(SPMCSR.reset);
                  break;
              case STATE_FILL:
                  if (flashPrinter != null) flashPrinter.println("FLASH: fill buffer @ " + pageoffset);
                  fillBuffer(pagenum, pageoffset);
                  break;
              case STATE_PGWRITE:
                  if (flashPrinter != null) flashPrinter.println("FLASH: page write to page " + pagenum);
                  pageWrite(pagenum, pageoffset);
                  break;
              default:
          }
      }
  
      private void pageErase(int pagenum, int pageoffset) {
          mainClock.removeEvent(SPMCSR.reset);
          SPMCSR.setBusy();
          mainClock.insertEvent(new EraseEvent(pagenum), ERASE_CYCLES);
      }
  
      private void pageWrite(int pagenum, int pageoffset) {
          mainClock.removeEvent(SPMCSR.reset);
          SPMCSR.setBusy();
          mainClock.insertEvent(new WriteEvent(pagenum, buffer), WRITE_CYCLES);
          resetBuffer();
      }
  
      private void resetRWW() {
          mainClock.removeEvent(SPMCSR.reset);
          if ( !SPMCSR.isBusy() ) {
              SPMCSR.clearBusy();
              SPMCSR.reset();
          }
  	    resetBuffer();
      }
  
      private void fillBuffer(int pagenum, int pageoffset) {
          // write the word in R0:R1 into the buffer
          byte r0 = interpreter.getRegisterByte(LegacyRegister.R0);
          byte r1 = interpreter.getRegisterByte(LegacyRegister.R1);
          SPMCSR.reset();
          buffer[pageoffset] = r0;
          buffer[pageoffset+1] = r1;
          mainClock.removeEvent(SPMCSR.reset);
      }
  
      /**
       * The <code>EraseEvent</code> class is used as an event to schedule the timing of erasing a page.
       * When a page erase operation is begun, this event is inserted into the queue of the simulator
       * so that when it fires, the page in the code segment is erased.
       */
      class EraseEvent implements Simulator.Event {
          int pagenum;
  
          EraseEvent(int pagenum) {
              this.pagenum = pagenum;
          }
  
          public void fire() {
              // erase the page
              if ( flashPrinter != null )
                  flashPrinter.println("FLASH: page erase completed for page "+pagenum);
              int size = bufferSize();
              int addr = pagenum * size;
              for ( int offset = 0; offset < size; offset++) {
                  int baddr = addr + offset;
                  write(baddr, DEFAULT_VALUE);
                  if ( (offset & 1) == 0)
                      replaceInstr(baddr, new DisassembleLegacyInstr(baddr));
              }
              SPMCSR.reset();
          }
      }
  
      /**
       * The <code>WriteEvent</code> class is used as an event to schedule the timing of writing a page.
       * When a page write operation is begun, this event is inserted into the queue of the simulator so
       * that when it fires, the page in the code segment is written with the contents of the temporary
       * buffer.
        */
      class WriteEvent implements Simulator.Event {
          int pagenum;
          byte[] buffer;
  
          WriteEvent(int pagenum, byte[] buf) {
              this.pagenum = pagenum;
              this.buffer = buf;
          }
  
          public void fire() {
              // write the page
              if ( flashPrinter != null )
                  flashPrinter.println("FLASH: page write completed for page "+pagenum);
              int size = bufferSize();
              int addr = pagenum * size;
              for ( int offset = 0; offset < size; offset++) {
                  int baddr = addr + offset;
                  write(baddr, buffer[offset]);
                  if ( (offset & 1) == 0)
                      replaceInstr(baddr, new DisassembleLegacyInstr(baddr));
              }
              SPMCSR.reset();
          }
      }
  
      /**
       * The <code>resetBuffer()</code> method resets the temporary buffer used for the SPM instruction
       * to its default value.
       */
      protected void resetBuffer() {
          buffer = new byte[bufferSize()];
          for ( int cntr = 0; cntr < buffer.length; cntr++) {
              buffer[cntr] = DEFAULT_VALUE;
          }
      }
  
      private int bufferSize() {
          // pagesize stores the number of bits representing the word offset in an address
          return 2 << pagesize;
      }
  
      /**
       * The <code>DisasssembleInstr</code> class represents an instruction that is used by the
       * interpreter to support dynamic code update. Whenever machine code is altered, this
       * instruction will replace the instruction(s) at that location so that when the program
       * attempts to execute the instruction, it will first be disassembled and then it will
       * be executed.
       */
      public class DisassembleLegacyInstr extends LegacyInstr {
  
          protected final int address;
  
          DisassembleLegacyInstr(int addr) {
              super(null);
              address = addr;
          }
  
          public void accept(LegacyInstrVisitor v) {
              LegacyInstr i = disassembler.disassembleLegacy(segment_data, 0, address);
              if ( i == null ) throw Util.failure("invalid instruction at "+ StringUtil.addrToString(address));
              replaceInstr(address, i);
              i.accept(v);
          }
  
          public LegacyInstr build(int address, LegacyOperand[] ops) {
              throw Util.failure("DisassembleLegacyInstr should be confined to BaseInterpreter");
          }
  
          public String getOperands() {
              throw Util.failure("DisassembleLegacyInstr has no operands");
          }
  
          public LegacyInstr asInstr() {
              LegacyInstr i = disassembler.disassembleLegacy(segment_data, 0, address);
              if ( i == null ) return null;
              replaceInstr(address, i);
              return i;
          }
      }
  
  }
  