How to boot from the 2nd half of a 24C512?

As mentioned here, an FM24C512 or BL24C512 can be very useful for storing a unique serial number or MAC address in the "hidden" 128 byte ID page. But it can also be very useful for storing a second boot image because it has twice as much memory as the P1 needs. This could be used to make field software updates fail safe. If the download process gets interrupted by a power failure or cable disconnect the boot loader in the first half of the memory stays operable and can be used to retry the update later.
If the bootloader detects a valid boot image in the second half it can load it into RAM and execute it. But how can this be done? A REBOOT command is not what I want because it would look for serial boot and the boot image in the first half of the EEPROM, again. I should start cog0 with the Spin interpreter as it's done after loading the EEPROM contents to RAM by the ROM bootloader. Is there a defined entry point where I can jump to?
Comments
This ASM snippet will boot from upper. ($8000- or change the start address with the mem_start variable)
' Execute the program stored in upper part of 64kB eeprom. ' ' NOTE: mem_start variable (at end of code) determines memory location ASM ' reset pins mov dira,#0 mov outa,#0 ':delay djnz time_xtal,#:delay 'allow 20ms @20MHz for pins to settle ' led high 'or dira,LED0 ' HIGH LED0 'or outa,LED0 ' set clock to 12MHz clkset clkmode12 wrlong clkfreq12, #0 wrbyte clkmode12, #4 ' configure pins shl mask_scl,pin_num shl mask_sda,pin_num ' configure chip address shl chip_address,#1 or cs_write,chip_address or cs_read,chip_address ' set temp variable to zero mov addr,#0 ' Load ram from eeprom and launch boot mov smode,#0 'clear mode in case error call #ee_read 'send read command :loop call #ee_receive 'get eeprom byte wrbyte eedata,addr 'write to ram add addr,#1 cmp count,first_byte wz 'calculate the file length if_z mov low_byte,eedata 'from the word value in cmp count,second_byte wz 'bytes at address $0008 and $0009 if_z call #getcount djnz count,#:loop 'loop until done call #ee_stop 'end read (followed by launch) ' Launch program in ram launch rdword addr,#$0004+2 'if pbase address invalid, shutdown cmp addr,#$0010 wz if_nz jmp #shutdown rdbyte addr,#$0004 'if xtal/pll enabled, start up now and addr,#$F8 '..while remaining in rcfast mode clkset addr :delay djnz time_xtal,#:delay 'allow 20ms @20MHz for xtal/pll to settle rdbyte addr,#$0004 'switch to selected clock clkset addr coginit interpreter 'reboot cog with interpreter ' Shutdown shutdown mov dira,#0 'cancel any outputs ':delay djnz time_xtal,#:delay 'allow 20ms @20MHz for pins to settle mov smode,#$02 'stop clock ':delay djnz time_xtal,#:delay 'allow 20ms @20MHz for pins to settle clkset 128 'reboot now! '************************************** '* I2C routines for 24x256/512 EEPROM * '* assumes fastest RC timing - 20MHz * '* SCL low time = 8 inst, >1.6us * '* SCL high time = 4 inst, >0.8us * '* SCL period = 12 inst, >2.4us * '************************************** ' Begin eeprom read ee_read call #ee_write 'begin write (sets address) mov eedata,cs_read 'send read command call #ee_start if_c jmp #shutdown 'if no ack, shutdown mov count,byte_count 'initialize count at $8000 ee_read_ret ret ' Begin eeprom write ee_write call #ee_wait 'wait for ack and begin write mov addr,mem_start 'load program start address mov eedata,addr 'send high address shr eedata,#8 call #ee_transmit if_c jmp #shutdown 'if no ack, shutdown mov eedata,addr 'send low address call #ee_transmit mov addr,#$0 'reset address if_c jmp #shutdown 'if no ack, shutdown ee_write_ret ret ' Wait for eeprom ack ee_wait mov count,#400 ' 400 attempts > 10ms @20MHz :loop mov eedata,cs_write '1 send write command call #ee_start '132+ if_c djnz count,#:loop '1 if no ack, loop until done if_c jmp #shutdown ' if no ack, shutdown ee_wait_ret ret ' Start + transmit ee_start mov bits,#9 '1 ready 9 start attempts :loop andn outa,mask_scl '1(!) ready scl low or dira,mask_scl '1! scl low nop '1 andn dira,mask_sda '1! sda float call #delay3 '5 or outa,mask_scl '1! scl high nop '1 test mask_sda,ina wc 'h?h sample sda if_nc djnz bits,#:loop '1,2 if sda not high, loop until done if_nc jmp #shutdown '1 if sda still not high, shutdown or dira,mask_sda '1! sda low ' Transmit/receive ee_transmit shl eedata,#1 '1 ready to transmit byte and receive ack or eedata,#%00000000_1 '1 jmp #ee_tr '1 ee_receive mov eedata,#%11111111_0 '1 ready to receive byte and transmit ack ee_tr mov bits,#9 '1 transmit/receive byte and ack :loop test eedata,#$100 wz '1 get next sda output state andn outa,mask_scl '1! scl low rcl eedata,#1 '1 shift in prior sda input state muxz dira,mask_sda '1! sda low/float call #delay2 '4 test mask_sda,ina wc 'h?h sample sda or outa,mask_scl '1! scl high nop '1 djnz bits,#:loop '1,2 if another bit, loop and eedata,#$FF '1 isolate byte received ee_receive_ret ee_transmit_ret ee_start_ret ret '1 nc=ack ' Stop ee_stop mov bits,#9 '1 ready 9 stop attempts :loop andn outa,mask_scl '1! scl low nop '1 or dira,mask_sda '1! sda low call #delay3 '5 or outa,mask_scl '1! scl high call #delay1 '3 andn dira,mask_sda '1! sda float call #delay2 '4 test mask_sda,ina wc 'h?h sample sda if_nc djnz bits,#:loop '1,2 if sda not high, loop until done ee_jmp if_nc jmp #shutdown '1 if sda still not high, shutdown ee_stop_ret ret '1 getcount 'calculate the binary files length mov high_byte,eedata shl high_byte,#8 or high_byte,low_byte mov count,high_byte sub count,#9 getcount_ret ret '-----------[ delays ]-------------------------------------------------------- delay100ms mov bit_time,_100ms jmp #delay delay10ms mov bit_time,_10ms jmp #delay delay1300 mov bit_time,_1300ns jmp #delay delay650 mov bit_time,_650ns jmp #delay delay add bit_time,cnt waitcnt bit_time,#0 delay_ret delay100ms_ret delay10ms_ret delay650_ret delay1300_ret ret ' Cycle delays delay3 nop '1 delay2 nop '1 delay1 nop '1 delay1_ret delay2_ret delay3_ret ret '1 mask_sda long $2 mask_scl long $1 pin_num long $1C mem_start long $8000 ' IMPORTANT - Where we load the new program from chip_address long 0 cs_write long $A0 cs_read long $A1 time_xtal long 20 * 20000 / 4 / 1 '20ms (@20MHz, 1 inst/loop) smode long 0 interpreter long $0001 << 18 + $3C01 << 4 + %0000 first_byte long $7ff8 second_byte long $7ff7 byte_count long $8000 h8000 long $8000 clkmode12 long %0000_0000 clkfreq12 long 12_000_000 _100ms long 0 _10ms long 0 _650ns long 0 _1300ns long 0 'LED0 long 1 << 20 command res 1 addr res 1 count res 1 bits res 1 eedata res 1 low_byte res 1 high_byte res 1 bit_time res 1 ENDASM
Code that boots upper half of 512 EEprom was in OBEX.
I can't remember the exact name of the code.
For this to work, you would always store new firmware to upper half of EEprom.
And lower half of EEprom always hold the code that loads from upper half
Thanks @VonSzarvas
So the whole boot process can be summarized like this:
1. load the first 10 bytes from EEPROM address $8000 to RAM address $0000
2. take the length of the boot image from byte 8 and 9
3. load the rest of the image to RAM
4. check if code is valid (checksum or whatever)
5. take XTAL/PLL mode from byte 4
6. switch clock mode, wait 20ms for PLL
7. execute a
COGINT interpreter
command.. where
<interpreter>
points to the somehow magic address $7C010. Ah, I just looked up the P1 COGINT instruction. $7C010 = 1<<18 + $3C01<<4 + 0 means PAR=$0004 and code adr=$F004 for cog #0. This is the entry point for the spin interpreter.The code above assumes that it is launched in cog 0, right? So
coginit interpreter
restarts its own cog and does not fall into the shutdown procedure. I have to do it differently. If no valid code is found in the upper half of the EEPROM I have to go back and startup the network interface to accept firmware updates from the PC. No problem... I just have to start the ASM code in a different cog (say 1) and wait with cog 0 if it reports an error. If there's no error it will never come back but restarts cog 0 with the interpreter and the main program in RAM and shuts down cog 1.Correct.
In a product it worked like this...
The lower EEPROM held a bootloader that ran each time the P1 reset.
The bootloader pinged a server (over GPRS) for firmware updates, and if existed then downloaded and copied the update to upper EEPROM, verified the checksum and repeated if failed. The repeat delay got longer each attempt, etc..
Finally the ASM code snippet was executed with
COGINIT BootUpper, 0
Jippee! It actually seems to work. Although it's a lot more complicated than I first thought.
I'd like to protect the firmware image in the second half of the EEPROM with a checksum to be able to detect incomplete or corrupted downloads. But I can tell whether the checksum is correct or not only after I have read the complete firmware image and I don't want to do that twice. So if I detect an error I have to re-load the bootloader from the first half of the EEPROM and launch it in network mode so it can wait for downloads from the PC.
To detect if the bootloader comes fresh from a cold start or if the firmware boot already failed I patch a global variable after loading the bootloader from EEPROM to RAM. The address of the variable is passed to the bootstrap ASM code in the PAR register.
Cunning
Btw... One way I handled the checksum in the past was by downloading an update in 128 long blocks, saving to ram and verifying a checksum before writing to EE. Then repeat until the full image stored.
Trouble was it didn't help if a powerfail occured on the last write, so the bootloader being able to re-try the update is still important.
I also zero'd the first few longs of EE when starting the update, and then downloaded the image backwards. It means the checksum bytes close to $8000 are the last written, so quick to verify if a valid image exists.