flash: increase stellaris flash loader buffer
[fw/openocd] / src / flash / nor / stellaris.c
index 0ae65dc8ac195242cfc79f42ecebf1e21111d450..287be12d7021ee20974fe208c512ca37a433e2b2 100644 (file)
@@ -22,7 +22,7 @@
  ***************************************************************************/
 
 /***************************************************************************
-* STELLARIS is tested on LM3S811, LM3S6965
+* STELLARIS flash is tested on LM3S811, LM3S6965, LM3s3748, more.
 ***************************************************************************/
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -36,8 +36,7 @@
 
 #define DID0_VER(did0) ((did0 >> 28)&0x07)
 
-static int stellaris_read_part_info(struct flash_bank *bank);
-
+static void stellaris_read_clock_info(struct flash_bank *bank);
 static int stellaris_mass_erase(struct flash_bank *bank);
 
 static struct {
@@ -256,20 +255,16 @@ FLASH_BANK_COMMAND_HANDLER(stellaris_flash_bank_command)
        return ERROR_OK;
 }
 
-static int stellaris_info(struct flash_bank *bank, char *buf, int buf_size)
+static int get_stellaris_info(struct flash_bank *bank, char *buf, int buf_size)
 {
        int printed, device_class;
        struct stellaris_flash_bank *stellaris_info = bank->driver_priv;
 
-       stellaris_read_part_info(bank);
-
        if (stellaris_info->did1 == 0)
-       {
-               printed = snprintf(buf, buf_size, "Cannot identify target as a Stellaris\n");
-               buf += printed;
-               buf_size -= printed;
-               return ERROR_FLASH_OPERATION_FAILED;
-       }
+               return ERROR_FLASH_BANK_NOT_PROBED;
+
+       /* Read main and master clock freqency register */
+       stellaris_read_clock_info(bank);
 
        if (DID0_VER(stellaris_info->did0) > 0)
        {
@@ -495,7 +490,8 @@ static int stellaris_read_part_info(struct flash_bank *bank)
        fam = (did1 >> 24) & 0xF;
        if (((ver != 0) && (ver != 1)) || (fam != 0))
        {
-               LOG_WARNING("Unknown did1 version/family, cannot positively identify target as a Stellaris");
+               LOG_WARNING("Unknown did1 version/family.");
+               return ERROR_FLASH_OPERATION_FAILED;
        }
 
        /* For Sandstorm, Fury, DustDevil:  current data sheets say IOSC
@@ -535,9 +531,10 @@ static int stellaris_read_part_info(struct flash_bank *bank)
                default:
                        LOG_WARNING("Unknown did0 class");
                }
-       default:
                break;
+       default:
                LOG_WARNING("Unknown did0 version");
+               break;
        }
 
        for (i = 0; StellarisParts[i].partno; i++)
@@ -554,22 +551,12 @@ static int stellaris_read_part_info(struct flash_bank *bank)
        stellaris_info->num_lockbits = 1 + (stellaris_info->dc0 & 0xFFFF);
        stellaris_info->num_pages = 2 *(1 + (stellaris_info->dc0 & 0xFFFF));
        stellaris_info->pagesize = 1024;
-       bank->size = 1024 * stellaris_info->num_pages;
        stellaris_info->pages_in_lockregion = 2;
 
-       /* provide this for the benefit of the higher flash driver layers */
-       bank->num_sectors = stellaris_info->num_pages;
-       bank->sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
-       for (i = 0; i < bank->num_sectors; i++)
-       {
-               bank->sectors[i].offset = i * stellaris_info->pagesize;
-               bank->sectors[i].size = stellaris_info->pagesize;
-               bank->sectors[i].is_erased = -1;
-               bank->sectors[i].is_protected = -1;
-       }
-
-       /* Read main and master clock freqency register */
-       stellaris_read_clock_info(bank);
+       /* REVISIT for at least Tempest parts, read NVMSTAT.FWB too.
+        * That exposes a 32-word Flash Write Buffer ... enabling
+        * writes of more than one word at a time.
+        */
 
        return ERROR_OK;
 }
@@ -586,11 +573,7 @@ static int stellaris_protect_check(struct flash_bank *bank)
        unsigned page;
 
        if (stellaris->did1 == 0)
-       {
-               status = stellaris_read_part_info(bank);
-               if (status < 0)
-                       return status;
-       }
+               return ERROR_FLASH_BANK_NOT_PROBED;
 
        for (i = 0; i < (unsigned) bank->num_sectors; i++)
                bank->sectors[i].is_protected = -1;
@@ -642,15 +625,7 @@ static int stellaris_erase(struct flash_bank *bank, int first, int last)
        }
 
        if (stellaris_info->did1 == 0)
-       {
-               stellaris_read_part_info(bank);
-       }
-
-       if (stellaris_info->did1 == 0)
-       {
-               LOG_WARNING("Cannot identify target as Stellaris");
-               return ERROR_FLASH_OPERATION_FAILED;
-       }
+               return ERROR_FLASH_BANK_NOT_PROBED;
 
        if ((first < 0) || (last < first) || (last >= (int)stellaris_info->num_pages))
        {
@@ -670,6 +645,10 @@ static int stellaris_erase(struct flash_bank *bank, int first, int last)
        target_write_u32(target, FLASH_CIM, 0);
        target_write_u32(target, FLASH_MISC, PMISC | AMISC);
 
+       /* REVISIT this clobbers state set by any halted firmware ...
+        * it might want to process those IRQs.
+        */
+
        for (banknr = first; banknr <= last; banknr++)
        {
                /* Address is first word in page */
@@ -714,31 +693,23 @@ static int stellaris_protect(struct flash_bank *bank, int set, int first, int la
 
        if (!set)
        {
-               LOG_ERROR("Can't unprotect write-protected pages.");
-               /* except by the "recover locked device" procedure ... */
+               LOG_ERROR("Hardware doesn't suppport page-level unprotect. "
+                       "Try the 'recover' command.");
                return ERROR_INVALID_ARGUMENTS;
        }
 
+       if (stellaris_info->did1 == 0)
+               return ERROR_FLASH_BANK_NOT_PROBED;
+
        /* lockregions are 2 pages ... must protect [even..odd] */
        if ((first < 0) || (first & 1)
                        || (last < first) || !(last & 1)
                        || (last >= 2 * stellaris_info->num_lockbits))
        {
-               LOG_ERROR("Can't protect unaligned or out-of-range sectors.");
+               LOG_ERROR("Can't protect unaligned or out-of-range pages.");
                return ERROR_FLASH_SECTOR_INVALID;
        }
 
-       if (stellaris_info->did1 == 0)
-       {
-               stellaris_read_part_info(bank);
-       }
-
-       if (stellaris_info->did1 == 0)
-       {
-               LOG_WARNING("Cannot identify target as an Stellaris MCU");
-               return ERROR_FLASH_OPERATION_FAILED;
-       }
-
        /* Refresh flash controller timing */
        stellaris_read_clock_info(bank);
        stellaris_set_flash_timing(bank);
@@ -764,6 +735,10 @@ static int stellaris_protect(struct flash_bank *bank, int set, int first, int la
        target_write_u32(target, FLASH_CIM, 0);
        target_write_u32(target, FLASH_MISC, PMISC | AMISC);
 
+       /* REVISIT this clobbers state set by any halted firmware ...
+        * it might want to process those IRQs.
+        */
+
        LOG_DEBUG("fmppe 0x%" PRIx32 "",fmppe);
        target_write_u32(target, SCB_BASE | FMPPE, fmppe);
 
@@ -773,6 +748,8 @@ static int stellaris_protect(struct flash_bank *bank, int set, int first, int la
        /* Write commit command */
        /* REVISIT safety check, since this cannot be undone
         * except by the "Recover a locked device" procedure.
+        * REVISIT DustDevil-A0 parts have an erratum making FMPPE commits
+        * inadvisable ... it makes future mass erase operations fail.
         */
        LOG_WARNING("Flash protection cannot be removed once commited, commit is NOT executed !");
        /* target_write_u32(target, FLASH_FMC, FMC_WRKEY | FMC_COMT); */
@@ -828,8 +805,7 @@ static const uint8_t stellaris_write_code[] =
        0x04,0x36,                      /* adds r6, r6, #4 */
        0x96,0x42,                      /* cmp  r6, r2 */
        0xF4,0xD1,                      /* bne  mainloop */
-                                               /* exit: */
-       0xFE,0xE7,                      /* b exit */
+       0x00,0xBE,              /* bkpt #0 */
 /* pFLASH_CTRL_BASE: */
        0x00,0xD0,0x0F,0x40,    /* .word        0x400FD000 */
 /* FLASHWRITECMD: */
@@ -840,7 +816,7 @@ static int stellaris_write_block(struct flash_bank *bank,
                uint8_t *buffer, uint32_t offset, uint32_t wcount)
 {
        struct target *target = bank->target;
-       uint32_t buffer_size = 8192;
+       uint32_t buffer_size = 16384;
        struct working_area *source;
        struct working_area *write_algorithm;
        uint32_t address = bank->base + offset;
@@ -848,37 +824,44 @@ static int stellaris_write_block(struct flash_bank *bank,
        struct armv7m_algorithm armv7m_info;
        int retval = ERROR_OK;
 
+       /* power of two, and multiple of word size */
+       static const unsigned buf_min = 128;
+
+       /* for small buffers it's faster not to download an algorithm */
+       if (wcount * 4 < buf_min)
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+
        LOG_DEBUG("(bank=%p buffer=%p offset=%08" PRIx32 " wcount=%08" PRIx32 "",
                        bank, buffer, offset, wcount);
 
        /* flash write code */
        if (target_alloc_working_area(target, sizeof(stellaris_write_code), &write_algorithm) != ERROR_OK)
        {
-               LOG_WARNING("no working area available, can't do block memory writes");
+               LOG_DEBUG("no working area for block memory writes");
                return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
        };
 
-       target_write_buffer(target, write_algorithm->address,
-                       sizeof(stellaris_write_code),
-                       (uint8_t *) stellaris_write_code);
+       /* plus a buffer big enough for this data */
+       if (wcount * 4 < buffer_size)
+               buffer_size = wcount * 4;
 
        /* memory buffer */
-       while (target_alloc_working_area(target, buffer_size, &source) != ERROR_OK)
+       while (target_alloc_working_area_try(target, buffer_size, &source) != ERROR_OK)
        {
-               LOG_DEBUG("called target_alloc_working_area(target=%p buffer_size=%08" PRIx32 " source=%p)",
-                               target, buffer_size, source);
                buffer_size /= 2;
-               if (buffer_size <= 256)
+               if (buffer_size <= buf_min)
                {
-                       /* if we already allocated the writing code, but failed to get a buffer, free the algorithm */
-                       if (write_algorithm)
-                               target_free_working_area(target, write_algorithm);
-
-                       LOG_WARNING("no large enough working area available, can't do block memory writes");
+                       target_free_working_area(target, write_algorithm);
                        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
                }
+               LOG_DEBUG("retry target_alloc_working_area(%s, size=%u)",
+                               target_name(target), (unsigned) buffer_size);
        };
 
+       retval = target_write_buffer(target, write_algorithm->address,
+                       sizeof(stellaris_write_code),
+                       (uint8_t *) stellaris_write_code);
+
        armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
        armv7m_info.core_mode = ARMV7M_MODE_ANY;
 
@@ -895,11 +878,19 @@ static int stellaris_write_block(struct flash_bank *bank,
                buf_set_u32(reg_params[0].value, 0, 32, source->address);
                buf_set_u32(reg_params[1].value, 0, 32, address);
                buf_set_u32(reg_params[2].value, 0, 32, 4*thisrun_count);
-               LOG_INFO("Algorithm flash write %" PRIi32 " words to 0x%" PRIx32 ", %" PRIi32 " remaining", thisrun_count, address, (wcount - thisrun_count));
-               LOG_DEBUG("Algorithm flash write %" PRIi32 " words to 0x%" PRIx32 ", %" PRIi32 " remaining", thisrun_count, address, (wcount - thisrun_count));
-               if ((retval = target_run_algorithm(target, 0, NULL, 3, reg_params, write_algorithm->address, write_algorithm->address + sizeof(stellaris_write_code)-10, 10000, &armv7m_info)) != ERROR_OK)
+               LOG_DEBUG("Algorithm flash write %u words to 0x%" PRIx32
+                               ", %u remaining",
+                               (unsigned) thisrun_count, address,
+                               (unsigned) (wcount - thisrun_count));
+               retval = target_run_algorithm(target, 0, NULL, 3, reg_params,
+                               write_algorithm->address,
+                               0,
+                               10000, &armv7m_info);
+               if (retval != ERROR_OK)
                {
-                       LOG_ERROR("error executing stellaris flash write algorithm");
+                       LOG_ERROR("error %d executing stellaris "
+                                       "flash write algorithm",
+                                       retval);
                        retval = ERROR_FLASH_OPERATION_FAILED;
                        break;
                }
@@ -909,6 +900,10 @@ static int stellaris_write_block(struct flash_bank *bank,
                wcount -= thisrun_count;
        }
 
+       /* REVISIT we could speed up writing multi-section images by
+        * not freeing the initialized write_algorithm this way.
+        */
+
        target_free_working_area(target, write_algorithm);
        target_free_working_area(target, source);
 
@@ -940,15 +935,7 @@ static int stellaris_write(struct flash_bank *bank, uint8_t *buffer, uint32_t of
                        bank, buffer, offset, count);
 
        if (stellaris_info->did1 == 0)
-       {
-               stellaris_read_part_info(bank);
-       }
-
-       if (stellaris_info->did1 == 0)
-       {
-               LOG_WARNING("Cannot identify target as a Stellaris processor");
-               return ERROR_FLASH_OPERATION_FAILED;
-       }
+               return ERROR_FLASH_BANK_NOT_PROBED;
 
        if (offset & 0x3)
        {
@@ -967,17 +954,21 @@ static int stellaris_write(struct flash_bank *bank, uint8_t *buffer, uint32_t of
        target_write_u32(target, FLASH_CIM, 0);
        target_write_u32(target, FLASH_MISC, PMISC | AMISC);
 
+       /* REVISIT this clobbers state set by any halted firmware ...
+        * it might want to process those IRQs.
+        */
+
        /* multiple words to be programmed? */
        if (words_remaining > 0)
        {
                /* try using a block write */
-               if ((retval = stellaris_write_block(bank, buffer, offset, words_remaining)) != ERROR_OK)
+               retval = stellaris_write_block(bank, buffer, offset,
+                               words_remaining);
+               if (retval != ERROR_OK)
                {
                        if (retval == ERROR_TARGET_RESOURCE_NOT_AVAILABLE)
                        {
-                               /* if block write failed (no sufficient working area),
-                                * we use normal (slow) single dword accesses */
-                               LOG_WARNING("couldn't use block writes, falling back to single memory accesses");
+                               LOG_DEBUG("writing flash word-at-a-time");
                        }
                        else if (retval == ERROR_FLASH_OPERATION_FAILED)
                        {
@@ -1056,26 +1047,42 @@ static int stellaris_write(struct flash_bank *bank, uint8_t *buffer, uint32_t of
 
 static int stellaris_probe(struct flash_bank *bank)
 {
-       /* we can't probe on an stellaris
-        * if this is an stellaris, it has the configured flash
+       struct stellaris_flash_bank *stellaris_info = bank->driver_priv;
+       int retval;
+
+       /* If this is a stellaris chip, it has flash; probe() is just
+        * to figure out how much is present.  Only do it once.
         */
+       if (stellaris_info->did1 != 0)
+               return ERROR_OK;
 
-       if (bank->target->state != TARGET_HALTED)
+       /* stellaris_read_part_info() already handled error checking and
+        * reporting.  Note that it doesn't write, so we don't care about
+        * whether the target is halted or not.
+        */
+       retval = stellaris_read_part_info(bank);
+       if (retval != ERROR_OK)
+               return retval;
+
+       if (bank->sectors)
        {
-               LOG_ERROR("Target not halted");
-               return ERROR_TARGET_NOT_HALTED;
+               free(bank->sectors);
+               bank->sectors = NULL;
        }
 
-       /* stellaris_read_part_info() already takes care about error checking and reporting */
-       return stellaris_read_part_info(bank);
-}
+       /* provide this for the benefit of the NOR flash framework */
+       bank->size = 1024 * stellaris_info->num_pages;
+       bank->num_sectors = stellaris_info->num_pages;
+       bank->sectors = calloc(bank->num_sectors, sizeof(struct flash_sector));
+       for (int i = 0; i < bank->num_sectors; i++)
+       {
+               bank->sectors[i].offset = i * stellaris_info->pagesize;
+               bank->sectors[i].size = stellaris_info->pagesize;
+               bank->sectors[i].is_erased = -1;
+               bank->sectors[i].is_protected = -1;
+       }
 
-static int stellaris_auto_probe(struct flash_bank *bank)
-{
-       struct stellaris_flash_bank *stellaris_info = bank->driver_priv;
-       if (stellaris_info->did1)
-               return ERROR_OK;
-       return stellaris_probe(bank);
+       return retval;
 }
 
 static int stellaris_mass_erase(struct flash_bank *bank)
@@ -1094,15 +1101,7 @@ static int stellaris_mass_erase(struct flash_bank *bank)
        }
 
        if (stellaris_info->did1 == 0)
-       {
-               stellaris_read_part_info(bank);
-       }
-
-       if (stellaris_info->did1 == 0)
-       {
-               LOG_WARNING("Cannot identify target as Stellaris");
-               return ERROR_FLASH_OPERATION_FAILED;
-       }
+               return ERROR_FLASH_BANK_NOT_PROBED;
 
        /* Refresh flash controller timing */
        stellaris_read_clock_info(bank);
@@ -1112,6 +1111,10 @@ static int stellaris_mass_erase(struct flash_bank *bank)
        target_write_u32(target, FLASH_CIM, 0);
        target_write_u32(target, FLASH_MISC, PMISC | AMISC);
 
+       /* REVISIT this clobbers state set by any halted firmware ...
+        * it might want to process those IRQs.
+        */
+
        target_write_u32(target, FLASH_FMA, 0);
        target_write_u32(target, FLASH_FMC, FMC_WRKEY | FMC_MERASE);
        /* Wait until erase complete */
@@ -1171,19 +1174,85 @@ COMMAND_HANDLER(stellaris_handle_mass_erase_command)
        return ERROR_OK;
 }
 
+/**
+ * Perform the Stellaris "Recovering a 'Locked' Device procedure.
+ * This performs a mass erase and then restores all nonvolatile registers
+ * (including USER_* registers and flash lock bits) to their defaults.
+ * Accordingly, flash can be reprogrammed, and JTAG can be used.
+ *
+ * NOTE that DustDevil parts (at least rev A0 silicon) have errata which
+ * can affect this operation if flash protection has been enabled.
+ */
+COMMAND_HANDLER(stellaris_handle_recover_command)
+{
+       struct flash_bank *bank;
+       int retval;
+
+       retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* REVISIT ... it may be worth sanity checking that the AP is
+        * inactive before we start.  ARM documents that switching a DP's
+        * mode while it's active can cause fault modes that need a power
+        * cycle to recover.
+        */
+
+       /* assert SRST */
+       if (!(jtag_get_reset_config() & RESET_HAS_SRST)) {
+               LOG_ERROR("Can't recover Stellaris flash without SRST");
+               return ERROR_FAIL;
+       }
+       jtag_add_reset(0, 1);
+
+       for (int i = 0; i < 5; i++) {
+               retval = dap_to_swd(bank->target);
+               if (retval != ERROR_OK)
+                       goto done;
+
+               retval = dap_to_jtag(bank->target);
+               if (retval != ERROR_OK)
+                       goto done;
+       }
+
+       /* de-assert SRST */
+       jtag_add_reset(0, 0);
+       retval = jtag_execute_queue();
+
+       /* wait 400+ msec ... OK, "1+ second" is simpler */
+       usleep(1000);
+
+       /* USER INTERVENTION required for the power cycle
+        * Restarting OpenOCD is likely needed because of mode switching.
+        */
+       LOG_INFO("USER ACTION:  "
+               "power cycle Stellaris chip, then restart OpenOCD.");
+
+done:
+       return retval;
+}
+
 static const struct command_registration stellaris_exec_command_handlers[] = {
        {
                .name = "mass_erase",
-               .handler = &stellaris_handle_mass_erase_command,
+               .handler = stellaris_handle_mass_erase_command,
                .mode = COMMAND_EXEC,
+               .usage = "bank_id",
                .help = "erase entire device",
        },
+       {
+               .name = "recover",
+               .handler = stellaris_handle_recover_command,
+               .mode = COMMAND_EXEC,
+               .usage = "bank_id",
+               .help = "recover (and erase) locked device",
+       },
        COMMAND_REGISTRATION_DONE
 };
 static const struct command_registration stellaris_command_handlers[] = {
        {
                .name = "stellaris",
-               .mode = COMMAND_ANY,
+               .mode = COMMAND_EXEC,
                .help = "Stellaris flash command group",
                .chain = stellaris_exec_command_handlers,
        },
@@ -1197,9 +1266,10 @@ struct flash_driver stellaris_flash = {
        .erase = stellaris_erase,
        .protect = stellaris_protect,
        .write = stellaris_write,
+       .read = default_flash_read,
        .probe = stellaris_probe,
-       .auto_probe = stellaris_auto_probe,
+       .auto_probe = stellaris_probe,
        .erase_check = default_flash_mem_blank_check,
        .protect_check = stellaris_protect_check,
-       .info = stellaris_info,
+       .info = get_stellaris_info,
 };