package lejos.pc.tools;
import lejos.pc.comm.*;
import lejos.nxt.remote.*;
import java.io.*;
/**
* Class to allow the updating and verification of the leJOS firmware.
*
* @author andy
*/
public class NXJFlashUpdate {
private static final int MAX_FIRMWARE_PAGES = 368;
private static final int TOTAL_PAGES = 1024;
private static final int SETTINGS_PAGES = 1;
private static final int DIRECTORY_PAGES = 2;
private static final int MENU_ADDRESS_LOC = 0x40;
private static final int MENU_LENGTH_LOC = MENU_ADDRESS_LOC + 4;
private static final int FLASH_START_PAGE_LOC = MENU_LENGTH_LOC + 4;
private static final String VM = "lejos_nxt_rom.bin";
private static final String MENU = "StartUpText.bin";
private NXJFlashUI ui;
public NXJFlashUpdate(NXJFlashUI ui) {
this.ui = ui;
}
/**
* Format and store a 32 bit value into a memory image.
*
* @param mem
* The image in which to store the value
* @param offset
* The location in bytes in the image
* @param val
* The value to be stored.
*/
void storeWord(byte[] mem, int offset, int val) {
mem[offset++] = (byte) (val & 0xff);
mem[offset++] = (byte) ((val >> 8) & 0xff);
mem[offset++] = (byte) ((val >> 16) & 0xff);
mem[offset++] = (byte) ((val >> 24) & 0xff);
}
/**
* Create the memory image ready to be flashed to the device. Load the
* firmware and menu images into memory ready for flashing. The command line
* provides details of the location of the image files to be used.
*
* @param commandLine
* Options for the location of the firmware and menu.
* @return Memory image ready to be flashed to the device.
*/
public byte[] createFirmwareImage(String vmName, String menuName,
String leJOSHomeDir) throws IOException, FileNotFoundException {
ui.message("Building firmware image.");
byte[] memoryImage = new byte[MAX_FIRMWARE_PAGES * NXTSamba.PAGE_SIZE];
String home = leJOSHomeDir;
// String home = System.getProperty("nxj.home");
// if (home == null)
// home = System.getenv("NXJ_HOME");
if (home == null)
home = "";
String SEP = System.getProperty("file.separator");
if (vmName == null)
vmName = home + SEP + "bin" + SEP + VM;
if (menuName == null)
menuName = home + SEP + "bin" + SEP + MENU;
ui.message("VM file: " + vmName);
ui.message("Menu file: " + menuName);
FileInputStream vm = new FileInputStream(vmName);
FileInputStream menu = new FileInputStream(menuName);
int vmLen = vm.read(memoryImage, 0, memoryImage.length);
// Round up to page and use as base for the menu location
int menuStart = ((vmLen + NXTSamba.PAGE_SIZE - 1) / NXTSamba.PAGE_SIZE)
* NXTSamba.PAGE_SIZE;
// Read the menu. Note we may read less than the full size of the menu.
// If so this will be caught by the overall size check below.
int menuLen = menu.read(memoryImage, menuStart, memoryImage.length
- menuStart);
// We store the length and location of the Menu in special locations
// that are known to the firmware.
storeWord(memoryImage, MENU_LENGTH_LOC, menuLen);
storeWord(memoryImage, MENU_ADDRESS_LOC, menuStart
+ NXTSamba.FLASH_BASE);
storeWord(memoryImage, FLASH_START_PAGE_LOC, MAX_FIRMWARE_PAGES);
// Check overall size allow for size/length markers in last block.
if (menuStart + menuLen >= memoryImage.length) {
throw new IOException("Combined size of VM and Menu > "
+ memoryImage.length);
}
ui.message("VM size: " + vmLen + " bytes.");
ui.message("Menu size: " + menuLen + " bytes.");
ui.message("Total image size " + (menuStart + menuLen) + "/"
+ memoryImage.length + " bytes.");
return memoryImage;
}
/**
* Create a memory image for the leJOS file system. We create an in memory
* image for the settinigs, and directory pages. We then fill the remainder
* of the space with a test pattern to help detect any flash memory problems
*
* @return byte array containing the file system data
*/
public byte[] createFilesystemImage() {
ui.message("Building filesystem image.");
byte[] fs = new byte[NXTSamba.PAGE_SIZE
* (TOTAL_PAGES - MAX_FIRMWARE_PAGES)];
// First few pages are settings are directory, these must all be
// cleared to zero. After that we fill with a test pattern, to help
// spot any flash problems
int addr = (SETTINGS_PAGES + DIRECTORY_PAGES) * NXTSamba.PAGE_SIZE;
while (addr <= (fs.length - 32)) {
storeWord(fs, addr, addr);
addr += 4;
storeWord(fs, addr, ~addr);
addr += 4;
storeWord(fs, addr, 0xf0f0f0f0);
addr += 4;
storeWord(fs, addr, 0x0f0f0f0f);
addr += 4;
storeWord(fs, addr, 0xaaaaaaaa);
addr += 4;
storeWord(fs, addr, 0x55555555);
addr += 4;
storeWord(fs, addr, 0x00000000);
addr += 4;
storeWord(fs, addr, 0xffffffff);
addr += 4;
}
return fs;
}
/**
* Locate and open an nxt device in SAM-BA mode. If none are present wait up
* to timeout ms checking to see if one has become available.
*
* @return
*/
public NXTSamba openSambaDevice(int timeout) throws NXTCommException,
IOException {
NXTSamba samba = new NXTSamba();
ui.message("Locating device in firmware update mode.");
// Look for devices in SAM-BA mode
NXTInfo[] nxts = samba.search();
if (nxts.length == 0) {
for (int i = 0; i < timeout / 1000; i++) {
nxts = samba.search();
if (nxts.length > 0)
break;
try {
ui.progress("Searching", (i * 100) / (timeout / 1000));
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
if (nxts.length > 1) {
throw new NXTCommException(
"Too many devices in firmware update mode.");
}
if (nxts.length == 0) {
return null;
}
// Must be just the one. Try and open it!
if (!samba.open(nxts[0])) {
throw new NXTCommException("Failed to open device in SAM-BA mode.");
}
ui.message("Opened device in firmware update mode.");
return samba;
}
/**
* Attempt to restart the nxt in SAM-BA mode.
*
* @param nxt
* The device to reset
* @throws lejos.pc.comm.NXTCommException
* @throws java.io.IOException
*/
public void resetDevice(NXTInfo nxt) throws NXTCommException, IOException {
ui.message("Attempting to reboot the device.");
NXTComm nxtComm = NXTCommFactory.createNXTComm(nxt.protocol);
NXTCommand cmd = NXTCommand.getSingleton();
if (!nxtComm.open(nxt, NXTComm.LCP)) {
throw new NXTCommException("Failed to open device in command mode.");
}
cmd.setNXTComm(nxtComm);
// Force into firmware update mode.
cmd.boot();
cmd.close();
}
private static int getPageAddr(int page)
{
return NXTSamba.FLASH_BASE + page * NXTSamba.PAGE_SIZE;
}
/**
* Verify that the contents of the nxt flash memory match the supplied
* image.
*
* @param nxt
* device to verify
* @param first
* starting address
* @param memoryImage
* memory address to compare with
* @return number of mismatched bytes found
* @throws java.io.IOException
*/
public int verifyPages(NXTSamba nxt, int first, byte[] memoryImage) throws IOException {
int failCnt = 0;
int len = memoryImage.length;
InputStream is = nxt.createInputStream(getPageAddr(first), len);
try
{
int p = -1;
for (int i = 0; i < len; i++)
{
int np = i * 100 / len;
if (np > p)
{
p = np;
ui.progress("Verifying", np);
}
int b = is.read();
if (b < 0)
throw new IOException("EOF came too soon");
if ((byte)b != memoryImage[i]) {
ui.message(String.format(
"Verify failed at address 0x%08X: expected 0x%02X, found 0x%02X\n",
i, memoryImage[i] & 0xff, b));
failCnt++;
}
}
}
finally
{
is.close();
}
ui.progress("", 0);
if (failCnt == 0)
ui.message("Verified " + memoryImage.length + " bytes ok.");
else
ui.message("Failed to verify " + failCnt + " of " + memoryImage.length + " bytes.");
return failCnt;
}
public void writePages(NXTSamba nxt, int first, byte[] memoryImage)
throws IOException {
int pages = memoryImage.length / NXTSamba.PAGE_SIZE;
int p = -1;
for (int page = 0; page < pages; page++) {
int np = page * 100 / pages;
if (np > p)
{
p = np;
ui.progress("Writing", np);
}
nxt.writePage(first + page, memoryImage, page * NXTSamba.PAGE_SIZE);
}
//workaround the problem, that verification and rebooting fails directly after write
nxt.readWord(getPageAddr(first));
ui.progress("", 0);
}
/**
* Update the NXT with the new memory image.
*
* @param nxt
* Device to update, must be open in SAM-BA mode.
* @param memoryImage
* New image for the device
* @param commandLine
* Update options
*/
public void writeFirmware(NXTSamba nxt, byte[] memoryImage)
throws IOException {
ui.message("Unlocking pages.");
nxt.unlockAllPages();
ui.message("Writing firmware image.");
writePages(nxt, 0, memoryImage);
}
/**
* Format the nxt file system.
*
* @param nxt
* Device to format
* @param fs
* File system image to use
* @throws java.io.IOException
*/
public void writeFilesystem(NXTSamba nxt, byte[] fs) throws IOException {
ui.message("Unlocking pages.");
nxt.unlockAllPages();
ui.message("Writing filesystem image.");
writePages(nxt, MAX_FIRMWARE_PAGES, fs);
}
/**
* Verify the firware downloaded to the device.
*
* @param nxt
* device to verify
* @param image
* firmware image to compare against
* @return the number of mismatched bytes.
* @throws java.io.IOException
*/
public int verifyFirmware(NXTSamba nxt, byte[] image) throws IOException {
ui.message("Verifying firmware.");
return verifyPages(nxt, 0, image);
}
public int verifyFilesystem(NXTSamba nxt, byte[] fs) throws IOException {
ui.message("Verifying filesystem.");
return verifyPages(nxt, MAX_FIRMWARE_PAGES, fs);
}
public void rebootDevice(NXTSamba nxt) throws IOException {
ui.message("Restarting the device.");
nxt.reboot();
nxt.close();
}
/**
* Update the NXT with the new memory image.
*
* @param nxt
* Device to update, must be open in SAM-BA mode.
* @param memoryImage
* New firmware image for the device
* @param fs
* File system image.
* @param verify
* Should we verify the updates?
*/
public void updateDevice(NXTSamba nxt, byte[] memoryImage, byte[] fs,
boolean verify) throws IOException {
updateDevice(nxt, memoryImage, fs, verify, verify, true);
}
public void updateDevice(NXTSamba nxt, byte[] memoryImage, byte[] fs,
boolean verifyFirm, boolean verifyFS, boolean reboot) throws IOException {
if (memoryImage != null) {
writeFirmware(nxt, memoryImage);
if (verifyFirm)
verifyFirmware(nxt, memoryImage);
}
if (fs != null) {
writeFilesystem(nxt, fs);
if (verifyFS)
verifyFilesystem(nxt, fs);
}
if (reboot) {
rebootDevice(nxt);
}
}
}