/*                                              -*- mode:C++; fill-column:100 -*-
  SFBBootBlock.h - Persistent boot-time information support
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA

  $Id$
*/

/**
  \file SFBBootBlock.h Support for the SFB boot block
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */
#ifndef SFBBOOTBLOCK_H
#define SFBBOOTBLOCK_H

#include "SFBChecksum.h"    /* For SFBChecksum */ 
#include "SFBTypes.h"       /* For u32 etc */
#include "SFBEeprom.h"      /* For EEPROM_RESERVED_BYTES */
#include "SFBPower.h"       /* For VOLTAGE_CALIBRATION_SIZE_BYTES */ 

#define BOOTBLOCK_MIN_VERSION 0x01  /**< Minimum legal boot block version; excludes 0. */
#define BOOTBLOCK_MAX_VERSION 0xfe  /**< Maximum legel boot block version, excludes 0xff.  */
 
/**
 * The current boot block version.  This value can never decrease, during any and all future
 * evolution of the SFB code.
 */
#define BOOTBLOCK_VERSION 6

/**
 * The exactly-64-byte-long SFB 'boot block' contents.  
 *
 * The SFBBootBlock structure is more complicated than it otherwise might be, to make it possible
 * for an older tertiary bootloader to interoperate with a newer bootblock format in a reasonably
 * sane way.  In particular, an older tertiary bootloader should be able to verify the newer format,
 * and get at the old fields that it knows about, without messing up newer fields it doesn't
 * understand.
 *
 * In addition, we reserve room in EEPROM for two #SFBBootBlock, which are written to alternately,
 * so that dying during a bootblock write will cause rollback to the prior bootblock, rather than
 * the complete loss of all bootblock information.  The value of the writeTag field determines which
 * copy is 'current' and which is 'backup'.
 *
 * This drastically reduces the chances that the (overall, redundant) boot block will be
 * accidentally corrupted, even if a power failure occurs during a boot block update.  Typically, in
 * such a case, all that will happen is that interrupted update will be lost, and the backup,
 * still-uncorrupted boot block will be treated as current instead.
 *
 * \attention 
 *
 * Although it is not documented with each method, note that all the SFBBootBlock methods have the
 * potential to:
 * \blinks #E_API_EEPROM_ADDRESS or #E_API_EEPROM_RESPONSE or  #E_API_EEPROM_DATA 
 * if there are problems accessing the EEPROM device.
 */
struct SFBBootBlock {
  u8 version;             ///< V1: The version of the bootblock goes in the first byte.  If
                          ///      bootblock-handling code of a given version accesses a bootblock
                          ///      of a \e newer version, that older code must look \e only at the
                          ///      fields it understands (while faithfully double-buffering the \e
                          ///      entire block, however).
                          ///
                          ///      On the flip side, \e all \e future \e versions of the bootblock
                          ///      code must
                          ///       -# be able to upgrade \b all \b prior bootblock versions, and
                          ///       -# during such an upgrade process must change \b nothing in any
                          ///          preexisting field, \e except for the version.  
  u8 writeTag;            ///< V1: Determines which bootblock copy is newer.  If both bootblocks
                          ///      are valid, as they typically will be, then the 'more ahead' (in
                          ///      the sense of #IS_LATER, except on 8 bits) #writeTag value wins.
  u16 flags;              ///< V1: Boot-related and miscellaneous flags
  u32 bootCount;          ///< V1: The number of times the tertiary bootloader has booted a sketch
  u32 burnCount;          ///< V1: The number of times the tertiary bootloader has burned a sketch
  u32 ownerId;            ///< V2: The owner of this board, displayed in B36, 0xffffffff illegal
  u32 boardId;            ///< V2: The name or id of this board, displayed in B36, 0xffffffff illegal
  u32 powerOnHours;       ///< V3: The count of full uptime hours on this board, excluding any
                          ///  fractions lost to reboots.  Note that maintaining this field involves
                          ///  an EEPROM write once an hour.  This does age the EEPROM, although
                          ///  with the dual bootblocks it will take over two centuries to perform a
                          ///  million write operations.  Nonetheless, if desired for whatever
                          ///  reason, incrementing of the powerOnHours can be suppressed by calling
                          ///  \code SET_REFLEX_FLAGS(RF_LESS_EEPROM); \endcode.
  u8 initialPower;        ///< V4: The bits determining which faces should provide/accept power at
                          ///  boot time.
  u8 watchdogResets;      ///< V5: Which of last eight boots (LSB: most recent) involved watchdog resets
  u8 voltageCalib[15];    ///< V6: Optional ADC calibration data for NSEW #faceVoltage, and #railVoltage
  u8 reservedPadding[15]; ///< Reserved for future use; currently undefined; should not be read.
  SFBChecksum cs;         ///< The checksum of the first 60 bytes of this block.
};

/** \cond
 * Generates compilation errors if SFBBootBlock and SFBEeprom aren't 'on the same page'.. 
 */
typedef char ensureSFBBootBlockSizeIsTheRightSize[sizeof(SFBBootBlock) == EEPROM_BYTES_PER_PAGE];
typedef char ensureCorrectNumberOfPagesAreReserved[2 == EEPROM_RESERVED_PAGES];
/** \endcond */

/** Flags that can be set in the #SFBBootBlock */
enum SFBBootBlockFlag {
  BOOTBLOCK_INVALID =      0x0001, ///< entire block invalid if set
  BOOTBLOCK_BOOT_MODE =    0x0002, ///< if 0 then GREEN_BOOT - newest code wins
  BOOTBLOCK_BLUE_NOT_RED = 0x0004, ///< if BOOT_MODE set, then if this then BLUE_BOOT else RED_BOOT
  BOOTBLOCK_UNLOCKED =     0x0008, ///< if 0 then ownerId,boardId cannot be modified

  BOOTBLOCK_NO_UPDATES =   0x0010, ///< go direct to valid sketch; don't even try to update
  BOOTBLOCK_KILL_SKETCH =  0x0020, ///< treat sketch as invalid even if checksum looks good
  BOOTBLOCK_RSRVD06 =      0x0040, ///< Reserved, value should be zero and must be ignored
  BOOTBLOCK_RSRVD07 =      0x0080, ///< Reserved, value should be zero and must be ignored

  BOOTBLOCK_RSRVD08 =      0x0100, ///< Reserved, value should be zero and must be ignored
  BOOTBLOCK_RSRVD09 =      0x0200, ///< Reserved, value should be zero and must be ignored
  BOOTBLOCK_RSRVD10 =      0x0400, ///< Reserved, value should be zero and must be ignored
  BOOTBLOCK_RSRVD11 =      0x0800, ///< Reserved, value should be zero and must be ignored

  BOOTBLOCK_RSRVD12 =      0x1000, ///< Reserved, value should be zero and must be ignored
  BOOTBLOCK_RSRVD13 =      0x2000, ///< Reserved, value should be zero and must be ignored
  BOOTBLOCK_RSRVD14 =      0x4000, ///< Reserved, value should be zero and must be ignored
  BOOTBLOCK_RSRVD15 =      0x8000, ///< Reserved, value should be zero and must be ignored

  BOOTBLOCK_DEFINED_FLAGS =   ///< Currently defined flags
    BOOTBLOCK_INVALID | BOOTBLOCK_BOOT_MODE | BOOTBLOCK_BLUE_NOT_RED | BOOTBLOCK_UNLOCKED
  | BOOTBLOCK_NO_UPDATES | BOOTBLOCK_KILL_SKETCH
};

extern u32 getBootBlockVersion() ;  ///< Get bootblock version.  \returns value from
                                    ///   #BOOTBLOCK_MIN_VERSION to #BOOTBLOCK_MAX_VERSION

extern u8 getBootBlockWatchdogResets() ;  ///< Get bootblock watchdog resets byte.

extern u32 getBootBlockOwnerId() ;  ///< Get bootblock owner id
extern u32 getBootBlockBoardId() ;  ///< Get bootblock board id
extern u32 getBootBlockBootCount() ;  ///< Get bootblock boot count
extern u32 getBootBlockBurnCount() ;  ///< Get bootblock sketch burn count
extern u32 getBootBlockPowerOnHours() ;  ///< Get bootblock total Power-On-Hours count.  Note this
                                         ///  counts only full consecutive uptime hours; all
                                         ///  fractions are dropped no matter what their cumulative
                                         ///  time.  Note also that this value can be affected by
                                         ///  #RF_LESS_EEPROM.
extern u32 getBootBlockFlags() ;         ///< Get bootblock flags
extern u32 getBootBlockBootMode() ;      ///< Get bootblock boot mode.  \returns #RED_BOOT_MODE,
                                         ///  #GREEN_BOOT_MODE, or #BLUE_BOOT_MODE, based on the
                                         ///  values of the flags #BOOTBLOCK_BOOT_MODE and
                                         ///  #BOOTBLOCK_BLUE_NOT_RED extracted from
                                         ///  #getBootBlockFlags.
extern u32 getBootBlockMergedChecksum() ; ///< Return the \c u32 XOR (exclusive-OR) of the two
                                          ///  halves of the bootblock checksum.  This value is used
                                          ///  by the library startup code to generate the random
                                          ///  number seed.
extern bool getBootBlockPowerIn(u8 face) ; ///< Return true if input power should be accepted from
                                           ///  \a face at power up.  The return value when \a
                                           ///  output equals \c false is meaningful only on boards
                                           ///  with power input controls; is a noop otherwise.
                                           ///  Note this value reflects \e only the boot time
                                           ///  setting; even if power input is subsequently
                                           ///  manipulated via #powerIn, this value does not
                                           ///  change.  See #setBootBlockPowerIn to change this
                                           ///  value.
extern bool getBootBlockPowerOut(u8 face) ; ///< Return true if output power should be provided from
                                            ///  \a face at power up.  Note this value reflects \e
                                            ///  only the boot time setting; even if power output is
                                            ///  subsequently manipulated via #powerOut, this value
                                            ///  does not change.  See #setBootBlockPowerOut to
                                            ///  change this value.
extern void setBootBlockPowerIn(u8 face, bool accept) ; ///< Specify whether input power should be
                                          ///   accepted (\a accept equals \c true) or blocked (\a
                                          ///   accept equals \c false) on \a face.  The value set
                                          ///   is meaningful only on boards with power input
                                          ///   controls; this method is a noop otherwise.
extern void setBootBlockPowerOut(u8 face, bool provide) ; ///< Specify whether output power should be
                                          ///   provided (\a provide equals \c true) or not (\a
                                          ///   provide equals \c false) on \a face at boot time.


extern const char * bootModeColorName(u32 bootMode) ;

extern u32 getBootBlockBootLoaderLength() ; ///< Access the size of the tertiary bootloader.  Even
                                            /// though strictly speaking that's not 'boot block
                                            /// business'.
extern u32 getBootBlockBootLoaderBurnCount() ; ///< Access the count of how many times the tertiary
                                               /// bootloader itself has been burned. Even though
                                               /// strictly speaking that's not 'boot block
                                               /// business'.  Also note that it is possible for
                                               /// this count to get reset if various errors occur
                                               /// during reflashing, so it should be treated as
                                               /// likely lower bound on the count.

extern void getBootBlockBootLoaderChecksum(SFBChecksum cs) ; ///< Access the checksum of the
                                                             ///tertiary bootloader itself. Even
                                                             ///though strictly speaking that's not
                                                             ///'boot block business'.  \sideeffect
                                                             ///cs is updated.


extern void setBootBlockWatchdogResetFlag(bool bootedClean) ; ///< Add another bit to the bottom of
                                                              /// the watchdog reset flag byte,
                                                              /// pushing the existing bits to the
                                                              /// left.
extern void setBootBlockOwnerId(u32 newOwnerId) ;             ///< Write the boot block owner id
                                                              /// field.  Fails silently unless the
                                                              /// boot block is unlocked.
extern void setBootBlockBoardId(u32 newBoardId) ;             ///< Write the boot block board id
                                                              /// field.  Fails silently unless the
                                                              /// boot block is unlocked.
extern void incrementBootBlockBootCount() ;         ///< Increment the boot count in the boot block.
extern void incrementBootBlockBurnCount() ;         ///< Increment the burn count in the boot block. 
extern void incrementBootBlockPowerOnHours() ;      ///< Increment the power on hours in the boot block.
extern void setBootBlockFlags(u32 newFlags) ;       ///< Write the boot block flags. 
extern void setBootBlockBootMode(u32 newBootMode) ; ///< Set the boot block boot mode. 

/** Fill \a data with the voltage calibration data (returns
    all zeros if this board was never voltage-calibrated).
 */
extern void getBootBlockVoltageCalibrationData(u8 data[VOLTAGE_CALIBRATION_SIZE_BYTES]) ;

/** Stores voltage calibration data read from \a data.
    Storing all zeros implies 'no calibration data available',
    which will cause #faceVoltage and #railVoltage to fall back
    on approximate 'datasheet defaults'.
 */
extern void setBootBlockVoltageCalibrationData(u8 data[VOLTAGE_CALIBRATION_SIZE_BYTES]) ;

/** \cond */
/* The one-time serial startup code */
extern void bootblock_startup_initialization() ;

/* Increment the bootcount at startup (unless overridden) */
extern void bootblock_bootcount_initialization() __attribute__ ((weak));  /* Allow overriding this initialization step */
/** \endcond */

#endif  /* SFBBOOTBLOCK_H */

