Skip to content

Firmware

Travis Goodspeed edited this page Nov 8, 2019 · 1 revision

GoodTag firmware is implemented as self-contained C applications, each of which builds to the TI-TXT format for flashing from the GoodV app.

Below are some example firmware applications for the tag. Reading through them ought to help show you how to develop for the tag

hello.c

/* This is a bare bones application for the rf430frl152h, which you
   might use as an example for your own projects and patches.  It
   hooks the vendor-proprietary A3 command to provide for a raw-read
   function, and places that command into the command table.

   Remember that we only have 2kB of FRAM, so you must be *very*
   conservative in how your code works.  If you call printf("hello"),
   you will overflow all of available code memory three or four times!
   Every byte counts, and two kilobytes are your entire budget.
   
   There are some saving graces, though.  The chip includes an 8kB ROM
   region, whose functions we can reuse and call back into.

   There is also a patch table, which allows us to add new NFC
   commands or even to patch the ROM functions.
   
   Caveats: 
   
   In this example, we have no main() and therefore static and global
   variables are not initialized.
   
 */


#include <rf430frl152h.h>
#include <stdint.h>
#include "rom.h"


/* We need to specify the Firmware Control Byte, which decides such
   things as the block size and whether the ROM is handling the sensor
   acquisition, or just the networking stack. */
__attribute__((section(".firmwarecontrolbyte")))
const uint8_t firmwarecontrolbyte = 0x7F; //8-byte pages, no ROM sensor support.


/* This vendor function allows for a raw read of words from an
   address.  It is roughly like the A3 command found in the
   RF430TAL152H devices.
   
   To try it, 'make hello.txt' and then flash it using GoodV.  Then
   use NFC Tools to send "02:a3:07:FEFF:01" by NfcV, which ought to
   result in "00:12:50".  The value at FFFE is 5012.
 */
uint16_t __attribute__ ((noinline)) cmd_a3(){
  uint16_t *adr;
  uint8_t words;

  //First we grab a 16-bit address, then an 8-bit length.
  adr=(uint16_t*) RF13MRXF;
  words=RF13MRXF_L;

  if(words<16){
    //Zero byte for success.
    RF13MTXF_L = 0;

    //Then the data.
    while(words){
      RF13MTXF = *adr;
      adr++;
      words--;
    }
  }else{
    //One for failure.
    RF13MRXF=1;
  }
  
  return 0;
}



/* And we need a patch table, and it has very strict rules about
   placement and alignment.

   1) The patch table is read backward from the start marker CECE at 0xFFCE.
   2) It must end with CECE after all valid records. (Lower address.)
   3) Entries  < 0x0100 are command hooks.
   4) Entries >= 0x0100 are patches to the ROM hook table.
*/

__attribute__((section(".rompatch")))
const uint16_t patchtable[0x12] =
  {
   0xCECE, 
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   
   (uint16_t) cmd_a3, 0x00A3, //Handler address and command number.
   0xCECE,  //This ABSOLUTELY MUST be at 0xFFCE or your patch won't load.
  };

gcmemu.c

/* This is an attempt at an emulator for a commercial gluecose tag, re-implementing
   their protocol well enough to work with a reader.  It doesn't yet work.
   
   Caveats: 
   
   In this example, we have no main() and therefore static and global
   variables are not initialized.
   
   Our tag is not write protected, and we do not forge the serial number
   of a real tag.

 */


#include <rf430frl152h.h>
#include <stdint.h>
#include "rom.h"



/* We need to specify the Firmware Control Byte, which decides such
   things as the block size and whether the ROM is handling the sensor
   acquisition, or just the networking stack. */
__attribute__((section(".firmwarecontrolbyte")))
const uint8_t firmwarecontrolbyte = 0x7f; //8-byte pages, no ROM sensor support.


/* And we need to load the NDEF tag to early FRAM. */
__attribute__((section(".earlyrom")))
const uint8_t earlydata[] = {
  /* In the future, we'll fake specific values here, but for now we
     just duplicate those of a real tag.
   */
  0x3d, 0xc7, 0x88, 0x13, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x62, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};


/* And we need to load the NDEF tag to early FRAM. */
__attribute__((section(".gcmdata")))
const uint8_t gcmdata[] = {
  /* In the future, we'll fake specific values here, but for now we
     just duplicate those of a real tag.
   */
  0x83, 0x4C, 0x00, 0x08, 0x33, 0x08, 0x44, 0x51,
  0x14, 0x07, 0x96, 0x80, 0x5A, 0x00, 0xED, 0xA6,
  0x12, 0x89, 0x1A, 0xC8, 0x04, 0x5D, 0x99, 0x6F
};

/* We need to calculate and correct checkums, but there is a hardware
   accelerator to take care of this.
 */
uint16_t crc_calculate(uint16_t *src, uint16_t wordcount){
  uint16_t count;

  //Initialize the CRC engine.
  CRCINIRES=0xFFFF;

  //Feed each 16-bit word to the engine.
  for(count=0; count<wordcount; count++){
    CRCDI=src[count];
  }

  //Return the result.
  return CRCINIRES;
}

/* Fixes the checksums expected by the reader. */
void crc_fixup(){
  /* The real tag expects these checksums, but our address space is
     different in the FRL152.

  f860 = crc(0xF862, 0xB);
  f878 = crc(0xF87A, 0x93);
  */

  SYSCNF_H &= 0xF0;//Unlock FRAM

  //Calculate new checksums.
  *((uint16_t*) (0xF860-8)) = crc_calculate((uint16_t*) (0xF862-8), 0xB);
  *((uint16_t*) (0xF878-8)) = crc_calculate((uint16_t*) (0xF87A-8), 0x93);
  
  SYSCNF_H |= 0x0F;//Lock FRAM
}


/* The A1 vendor function is called first, and it takes no parameters.
   Test it by sending "02A107" as the reader does.
 */
uint16_t __attribute__ ((noinline)) cmd_a1(){
  /* For now, just send back what a real tag would.  We'll figure out
     what these bytes mean later. */
  
  RF13MTXF_L=0;    //Success
  RF13MTXF=0x00DF;
  RF13MTXF=0x0800;
  RF13MTXF=0x0000;

  crc_fixup(); //Fix the CRCs.
  
  return 0;
}

/* After some stuff has been initialized and both earlydata and
   gcmdata have been read, the A0 command is called to initialize the sensor.
 */
uint16_t __attribute__ ((noinline)) cmd_a0(){
  //These two parameters are a 32-bit password.
  volatile uint16_t param0, param1;
  param0=RF13MRXF; //First 16-bits of password.
  param1=RF13MRXF; //Second half the password.
  
  /* For now, just send back what a real tag would.  We'll figure out
     what these bytes mean later. */
  RF13MTXF_L=0;      //Success
  RF13MTXF=0x00DC;
  RF13MTXF=0x0003;
  
  return 0;
}

/* This vendor function allows for a raw read of words from an
   address.  It is roughly like the A3 command found in the
   RF430TAL152H devices.
   
   To try it, 'make hello.txt' and then flash it using GoodV.  Then
   use NFC Tools to send "02:a3:07:FEFF:01" by NfcV, which ought to
   result in "00:12:50".  The value at FFFE is 5012.
 */
uint16_t __attribute__ ((noinline)) cmd_a3(){
  uint16_t *adr;
  uint8_t words;

  //These two parameters are a 32-bit password.
  volatile uint16_t param0, param1;
  param0=RF13MRXF; //First half of the password.
  param1=RF13MRXF; //Second half.


  //Then we grab a 16-bit address, and finally an 8-bit length.
  adr=(uint16_t*) RF13MRXF;
  words=RF13MRXF_L;

  if(words<16){
    //Zero byte for success.
    RF13MTXF_L = 0;

    //Then the data.
    while(words){
      RF13MTXF = *adr;
      adr++;
      words--;
    }
  }else{
    //One for failure.
    RF13MRXF=1;
  }
  
  return 0;
}


/* And we need a patch table, and it has very strict rules about
   placement and alignment.

   1) The patch table is read backward from the start marker CECE at 0xFFCE.
   2) It must end with CECE after all valid records. (Lower address.)
   3) Entries  < 0x0100 are command hooks.
   4) Entries >= 0x0100 are patches to the ROM hook table.
*/

__attribute__((section(".rompatch")))
const uint16_t patchtable[0x12] =
  {
   0xCECE, 
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   0xCECE, 0xCECE,
   (uint16_t) cmd_a3, 0x00A3, //Handler address and command number.
   (uint16_t) cmd_a0, 0x00A0, //Handler address and command number.
   (uint16_t) cmd_a1, 0x00A1, //Handler address and command number.
   0xCECE,  //This ABSOLUTELY MUST be at 0xFFCE or your patch won't load.
  };

ndef.c

/* This handly little application is an example NDEF RFID tag,
   implemented by disabling the ROM sensor, choosing 4-byte pages, and
   placing the NDEF bytes at the beginning of the contents.

   The data functions as a wrapper for the NDEF tag data that can be
   produced by ndeftool or libndef's ndef-encode, but the length *must*
   be correct or the reader will reject it.
   
   If you also needed the advanced ROM functions, you could enable
   them by hooking the read functions to keep the NDEF data away from
   the ROM's control registers.
   
   Caveats: 
   
   In this example, we have no main() and therefore static and global
   variables are not initialized.
   
   The NDEF data isn't write protected.  That could be added when
   flashing by JTAG, but can't yet fit within the TI-TXT format for
   OTA flashing.

 */


#include <rf430frl152h.h>
#include <stdint.h>
#include "rom.h"


/* We need to specify the Firmware Control Byte, which decides such
   things as the block size and whether the ROM is handling the sensor
   acquisition, or just the networking stack. */
__attribute__((section(".firmwarecontrolbyte")))
const uint8_t firmwarecontrolbyte = 0x42; //4-byte pages, no ROM sensor support.


/* And we need to load the NDEF tag to early FRAM. */
__attribute__((section(".earlyrom")))
const uint8_t ndefdata[] = {
  
  // Block 0
  0xE1,           // NDEF Magic Number, E1 means no fancy features.
  0x40,           // Version Number, read/write access conditions.
  0x40,           // Total formattable size, I think.
  0x00,           // Keep this zeroed.
  
  // Block 1 begins.
  0x03,           // NDEF Message present
  0x1F,           // Length of the NDEF message.
  

  //The actual NDEF message begins here:
  //ndeftool uri https://github.com/travisgoodspeed | hexdump
  0xd1, 0x01, 0x1b, 0x55, 0x04, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
  0x74, 0x72, 0x61, 0x76, 0x69, 0x73, 0x67, 0x6f, 0x6f, 0x64, 0x73, 0x70, 0x65, 0x65, 0x64,
  

  
  // We follow with a terminator, which is not a part of the length or
  // the data above.
  0xFE,           // TLV terminator
};


/* We could place more code into later sections, of course.  Quickly
   hooking the read functions would allow the NDEF to change with each
   read.
 */
Clone this wiki locally