Optional dual-boot support in iAudio X5 and M5 bootloader, based on FS#5289.

In order to enable it, #define HAVE_DUALBOOT when building the bootloader.
Do not use the automatically created x5_fw.bin or m5_fw.bin, but use mkboot
to create a new firmware file from an OF x5_fw.bin resp. m5_fw.bin and
bootloader.bin.

The dual-boot bootloader boots the OF when pressing Play (main or remote) for
more than 3 seconds. Hold it a bit longer because the OF also checks buttons.
Short press boots rockbox.

As a bonus, the Play button read (for hold check) is done a bit earlier for
single-boot mode as well.



git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30018 a1c6a512-1295-4272-9138-f99709370657
diff --git a/bootloader/bootloader.make b/bootloader/bootloader.make
index 0118235..78b8b43 100644
--- a/bootloader/bootloader.make
+++ b/bootloader/bootloader.make
@@ -10,6 +10,7 @@
 INCLUDES += -I$(APPSDIR)
 SRC += $(call preprocess, $(APPSDIR)/SOURCES)
 
+CONFIGFILE := $(FIRMDIR)/export/config/$(MODELNAME).h
 BOOTLDS := $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/boot.lds
 BOOTLINK := $(BUILDDIR)/boot.link
 
@@ -17,7 +18,7 @@
 
 .SECONDEXPANSION:
 
-$(BOOTLINK): $(BOOTLDS)
+$(BOOTLINK): $(BOOTLDS) $(CONFIGFILE)
 	$(call PRINTS,PP $(@F))
 	$(call preprocess2file,$<,$@,-DLOADADDRESS=$(LOADADDRESS))
 
diff --git a/bootloader/iaudio_coldfire.c b/bootloader/iaudio_coldfire.c
index 5639a0e..9c829c4 100644
--- a/bootloader/iaudio_coldfire.c
+++ b/bootloader/iaudio_coldfire.c
@@ -123,6 +123,10 @@
     }
 }
 
+#if defined(IAUDIO_M5) || defined(IAUDIO_X5)
+int initial_gpio_read;
+#endif
+
 void main(void)
 {
     int i;
@@ -144,17 +148,13 @@
     if ((GPIO_READ & 0x80000000) == 0)
         rc_on_button = true;
 #elif defined(IAUDIO_M5) || defined(IAUDIO_X5)
-    int data;
-
     or_l(0x0e000000, &GPIO_FUNCTION); /* main Hold & Power, remote Play */
     and_l(~0x0e000000, &GPIO_ENABLE);
-    
-    data = GPIO_READ;
-    
-    if ((data & 0x04000000) == 0)
+
+    if ((initial_gpio_read & 0x04000000) == 0)
         on_button = true;
-        
-    if ((data & 0x02000000) == 0)
+
+    if ((initial_gpio_read & 0x02000000) == 0)
         rc_on_button = true;
 #endif
 
diff --git a/firmware/target/coldfire/crt0.S b/firmware/target/coldfire/crt0.S
index e671771..881fcf9 100644
--- a/firmware/target/coldfire/crt0.S
+++ b/firmware/target/coldfire/crt0.S
@@ -26,11 +26,51 @@
     .global    start
 start:
 
+#if defined(BOOTLOADER) && defined(HAVE_DUALBOOT) \
+    && (defined(IAUDIO_X5) || defined(IAUDIO_M5))
+
+    /* 8 byte dualboot signature */
+    bra.b   1f           /* 0x6006 */
+    .short  0x4442      /* DB */
+#if defined(IAUDIO_X5)
+    .long   0x69617835  /* iax5 */
+#elif defined(IAUDIO_M5)
+    .long   0x69616d35  /* iam5 */
+#else
+#error Dualboot signature not defined
+#endif
+1:
+    /* As the control registers are write-only, we're relying on MBAR2 being */
+    /* set up correctly by the preloader for button check */
+    /* Only use scratch regs until we're sure that we will boot rockbox */
+    lea     MBAR2, %a1
+    move.l  (%a1), %a0        /* store GPIO_READ result for button check in main() */
+
+    /* Wait ~3 seconds for ON-button release. We need roughly 300ns per
+       iteration, so we check 10000000 times to reach the desired delay */
+    move.l  #10000000, %d0
+.on_button_test:
+    move.l  (%a1), %d1        /* GPIO_READ */
+    and.l   #0x06000000, %d1  /* Check main (bit 25=0) and remote (bit 26=0) */
+    cmp.l   #0x06000000, %d1  /* ON buttons simultaneously */
+    beq.b   .loadrockbox
+    subq.l  #1, %d0
+    bne.b   .on_button_test
+
+.loadoriginal:
+    jmp     0x10010
+
+.loadrockbox:
+    move.l  %a0, %d7          /* keep initial GPIO_READ value in %d7 for now */
+
+#endif /* defined(BOOTLOADER && defined(HAVE_DUALBOOT)
+          && (defined(IAUDIO_X5) || defined(IAUDIO_M5)) */
+
     move.w  #0x2700,%sr
 
     move.l  #vectors,%d0
     movec.l %d0,%vbr
-        
+
     move.l  #MBAR+1,%d0
     movec.l %d0,%mbar
         
@@ -39,7 +79,12 @@
 
     lea     MBAR,%a0
     lea     MBAR2,%a1
-        
+
+#if defined(BOOTLOADER) && !defined(HAVE_DUALBOOT) \
+    && (defined(IAUDIO_X5) || defined(IAUDIO_M5))
+    move.l  (%a1), %d7        /* store GPIO_READ result for button check in main() */
+#endif
+
     clr.l  (0x180,%a1)  /* PLLCR = 0 */
 
     /* 64K DMA-capable SRAM at 0x10000000
@@ -318,6 +363,10 @@
     move.l  %d0,(%a2)+
     cmp.l   %a2,%a4
     bhi.b   .mungeloop
+    
+#if defined(BOOTLOADER) && (defined(IAUDIO_X5) || defined(IAUDIO_M5))
+    move.l  %d7, initial_gpio_read
+#endif
 
     jsr     main
 .hoo:
diff --git a/firmware/target/coldfire/iaudio/boot.lds b/firmware/target/coldfire/iaudio/boot.lds
index 4cd0e8e..9288a6c 100644
--- a/firmware/target/coldfire/iaudio/boot.lds
+++ b/firmware/target/coldfire/iaudio/boot.lds
@@ -14,8 +14,12 @@
 #define IRAMSIZE 0x18000
 #endif
 #define DRAMORIG 0x31000000
+#ifdef HAVE_DUALBOOT
+#define FLASHORIG 0x00150000
+#else
 #define FLASHORIG 0x00010000
-#define FLASHSIZE 4M
+#endif
+#define FLASHSIZE 4M - FLASHORIG
 
 MEMORY
 {
diff --git a/rbutil/rbutilqt/base/bootloaderinstallhex.cpp b/rbutil/rbutilqt/base/bootloaderinstallhex.cpp
index 84d60ed..5118aab 100644
--- a/rbutil/rbutilqt/base/bootloaderinstallhex.cpp
+++ b/rbutil/rbutilqt/base/bootloaderinstallhex.cpp
@@ -162,7 +162,7 @@
 
     // iriver decode already done in stage 1
     int result;
-    if((result = mkboot(descrambledName.toLocal8Bit().constData(),
+    if((result = mkboot_iriver(descrambledName.toLocal8Bit().constData(),
                     tempfileName.toLocal8Bit().constData(),
                     tempbinName.toLocal8Bit().constData(), origin)) < 0)
     {
diff --git a/tools/mkboot.c b/tools/mkboot.c
index 49d6452..f7c7b46 100644
--- a/tools/mkboot.c
+++ b/tools/mkboot.c
@@ -26,42 +26,46 @@
 #ifndef RBUTIL
 static void usage(void)
 {
-    printf("usage: mkboot [-h300] <firmware file> <boot file> <output file>\n");
+    printf("usage: mkboot <target> <firmware file> <boot file> <output file>\n");
+    printf("available targets:\n"
+           "\t-h100     Iriver H1x0\n"
+           "\t-h300     Iriver H3x0\n"
+           "\t-iax5     iAudio X5\n"
+           "\t-iam5     iAudio M5\n");
 
     exit(1);
 }
 #endif
 
-static unsigned char image[0x400000 + 0x220 + 0x400000/0x200];
-
 #ifndef RBUTIL
 int main(int argc, char *argv[])
 {
-    char *infile, *bootfile, *outfile;
-    int origin = 0x1f0000;   /* H1x0 bootloader address */
-
-    if(argc < 3) {
-        usage();
-    }
-
-    if(!strcmp(argv[1], "-h300")) {
-        infile = argv[2];
-        bootfile = argv[3];
-        outfile = argv[4];
-
-        origin = 0x3f0000;   /* H3x0 bootloader address */
-    }
-    else
+    if(argc != 5)
     {
-        infile = argv[1];
-        bootfile = argv[2];
-        outfile = argv[3];
+        usage();
+        return 1;
     }
-    return mkboot(infile, bootfile, outfile, origin);
+
+    if ( ! strcmp(argv[1], "-h100"))
+        return mkboot_iriver(argv[2], argv[3], argv[4], 0x1f0000);
+    
+    if ( ! strcmp(argv[1], "-h300"))
+        return mkboot_iriver(argv[2], argv[3], argv[4], 0x3f0000);
+    
+    if ( ! strcmp(argv[1], "-iax5"))
+        return mkboot_iaudio(argv[2], argv[3], argv[4], 0);
+        
+    if ( ! strcmp(argv[1], "-iam5"))
+        return mkboot_iaudio(argv[2], argv[3], argv[4], 1);
+
+    usage();
+    return 1;
 }
 #endif
 
-int mkboot(const char* infile, const char* bootfile, const char* outfile, int origin)
+static unsigned char image[0x400000 + 0x220 + 0x400000/0x200];
+
+int mkboot_iriver(const char* infile, const char* bootfile, const char* outfile, int origin)
 {
     FILE *f;
     int i;
@@ -187,3 +191,129 @@
     
     return 0;
 }
+
+/* iAudio firmware update file header size */
+#define HEADER_SIZE 0x1030
+/* Address of flash contents that get overwritten by a firmware update.
+ * Contents before this address contain the preloader and are not affected
+ * by a firmware update.
+ * -> Firmware update file contents starting at offset HEADER_SIZE end up
+ * in flash at address FLASH_START
+ */
+#define FLASH_START        0x00010000
+/* Start of unused space in original firmware (flash address, not file
+ * offset!) where we patch in the Rockbox loader */
+#define ROCKBOX_BOOTLOADER 0x00150000
+/* End of unused space in original firmware */
+#define BOOTLOADER_LIMIT   0x00170000 
+
+/* Patch the Rockbox bootloader into free space in the original firmware
+ * (starting at 0x150000). The preloader starts execution of the OF at
+ * 0x10000 which normally contains a jsr 0x10010. We also patch this to
+ * do a jsr 0x150000 to the Rockbox dual boot loader instead. If it then
+ * decides to start the OF instead of Rockbox, it simply does a jmp
+ * 0x10010 instead of loading Rockbox from disk.
+ */
+int mkboot_iaudio(const char* infile, const char* bootfile, const char* outfile, int model_nr)
+{
+    size_t flength, blength;
+    unsigned char *bbuf, *fbuf, *p;
+    const unsigned char fsig[] = {
+        0x4e, 0xb9, 0x00, 0x01, 0x00, 0x10 };               /* jsr 0x10010 */
+    unsigned char bsig[2][8] = {
+        /* dualboot signatures */
+        { 0x60, 0x06, 0x44, 0x42, 0x69, 0x61, 0x78, 0x35 }, /* X5 */
+        { 0x60, 0x06, 0x44, 0x42, 0x69, 0x61, 0x6d, 0x35 }, /* M5 */ };
+    FILE *ffile, *bfile, *ofile;
+    unsigned char sum = 0;
+    int i;
+
+    /* read input files */
+    if ((bfile = fopen(bootfile, "rb")) == NULL) {
+        perror("Cannot open Rockbox bootloader file.\n");
+        return 1;
+    }
+
+    fseek(bfile, 0, SEEK_END);
+    blength = ftell(bfile);
+    fseek(bfile, 0, SEEK_SET);
+
+    if (blength + ROCKBOX_BOOTLOADER >= BOOTLOADER_LIMIT) {
+        fprintf(stderr, "Rockbox bootloader is too big.\n");
+        return 1;
+    }
+ 
+    if ((ffile = fopen(infile, "rb")) == NULL) {
+        perror("Cannot open original firmware file.");
+        return 1;
+    }
+  
+    fseek(ffile, 0, SEEK_END);
+    flength = ftell(ffile);
+    fseek(ffile, 0, SEEK_SET);
+
+    bbuf = malloc(blength);
+    fbuf = malloc(flength);
+
+    if (!bbuf || !fbuf) {
+        fprintf(stderr, "Out of memory.\n");
+        return 1;
+    }
+
+    if (   fread(bbuf, 1, blength, bfile) < blength
+        || fread(fbuf, 1, flength, ffile) < flength) {
+        fprintf(stderr, "Read error.\n");
+        return 1;
+    }
+    fclose(bfile);
+    fclose(ffile);
+
+    /* verify format of input files */
+    if (blength < 0x10 || memcmp(bbuf, bsig[model_nr], sizeof(bsig[0]))) {
+        fprintf(stderr, "Rockbox bootloader format error (is it bootloader.bin?).\n");
+        return 1;
+    }
+    if (flength < HEADER_SIZE-FLASH_START+BOOTLOADER_LIMIT
+        || memcmp(fbuf+HEADER_SIZE, fsig, sizeof(fsig))) {
+        fprintf(stderr, "Original firmware format error.\n");
+        return 1;
+    }
+
+    /* verify firmware is not overrun */
+    for (i = ROCKBOX_BOOTLOADER; i < BOOTLOADER_LIMIT; i++) {
+        if (fbuf[HEADER_SIZE-FLASH_START+i] != 0xff) {
+            fprintf(stderr, "Original firmware has grown too much.\n");
+            return 1;
+        }
+    }
+
+    /* change jsr 0x10010 to jsr DUAL_BOOTLOADER */
+    p = fbuf + HEADER_SIZE + 2;
+    *p++ = (ROCKBOX_BOOTLOADER >> 24) & 0xff;
+    *p++ = (ROCKBOX_BOOTLOADER >> 16) & 0xff;
+    *p++ = (ROCKBOX_BOOTLOADER >>  8) & 0xff;
+    *p++ = (ROCKBOX_BOOTLOADER      ) & 0xff;
+
+    p = fbuf + HEADER_SIZE + ROCKBOX_BOOTLOADER - FLASH_START;
+    memcpy(p, bbuf, blength);
+
+    /* recalc checksum */
+    for (i = HEADER_SIZE; (size_t)i < flength; i++)
+        sum += fbuf[i];
+    fbuf[0x102b] = sum;
+
+    /* write output */
+    if ((ofile = fopen(outfile, "wb")) == NULL) {
+        perror("Cannot open output file");
+        return 1;
+    }
+    if (fwrite(fbuf, 1, flength, ofile) < flength) {
+        fprintf(stderr, "Write error.\n");
+        return 1;
+    }
+    fclose(ofile);
+    free(bbuf);
+    free(fbuf);
+
+    return 0;
+}
\ No newline at end of file
diff --git a/tools/mkboot.h b/tools/mkboot.h
index 980e469..ba12d1b 100644
--- a/tools/mkboot.h
+++ b/tools/mkboot.h
@@ -26,7 +26,8 @@
 extern "C" {
 #endif
 
-int mkboot(const char* infile, const char* bootfile, const char* outfile, int origin);
+int mkboot_iriver(const char* infile, const char* bootfile, const char* outfile, int origin);
+int mkboot_iaudio(const char* infile, const char* bootfile, const char* outfile, int model_nr);
 
 #ifdef __cplusplus
 }