kinetis: Revise CPU un-securing code

Old version of the code had several problems, among them are:
 * Located in a generic ADI source file instead of some Kinetis
   specific location
 * Incorrect MCU detection code that would read generic ARM ID
   registers
 * Presence of SRST line was mandatory
 * There didn't seem to be any place where after SRST line assertion
   it would be de-asserted.
 * Reset was asserted after waiting for "Flash Controller Ready" bit
   to be set, which contradicts official programming guide AN4835
 * Mass erase algorithm implemented by that code was very strange:
   ** After mass erase was initiated instead of just polling for the
      state of "Mass Erase Acknowledged" bit the code would repeatedly
      initiate mass erase AND poll the state of the "Mass Erase
      Acknowledged"
   ** Instead of just polling for the state of "Flash Mass Erase in
      Progress"(bit 0 in Control register) to wait for the end of the
      mass erase operation the code would: write 0 to Control
      register, read out Status register ignoring the result and then
      read Control register again and see if it is zero.
 * dap_syssec_kinetis_mdmap assumed that previously selected(before
   it was called) AP was 0.

This commit moves all of the code to kinetis flash driver and
introduces three new commands:

o "kinetis mdm check_security" -- the intent of that function is to be used as
  'examine-end' hook for any Kinetis target that has that kind of
  JTAG/SWD security mechanism.

o "kinetis mdm mass_erase""  -- This function removes secure status from
  MCU be performing special version of flash mass erase.

o "kinetis mdm test_securing" -- Function that allows to test securing
  fucntionality. All it does is erase the page with flash security settings thus
  making MCU 'secured'.

New version of the code implements the algorithms specified in AN4835
"Production Flash Programming Best Practices for Kinetis K-
and L-series MCUs", specifically sections 4.1.1 and 4.2.1.
It also adds KL26 MCU to the list of devices for which this security
check is performed. Implementing that algorithm also allowed to simplify
mass command in kinetis driver, since we no longer need to write security
bytes. The result that the old version of mass erase code can now be
acheived using 'kinetis mdm mass_erase'

Tested on accidentally locked FRDM-KL26Z with KL26 Kinetis MCU.

Change-Id: Ic085195edfd963dda9d3d4d8acd1e40cc366b16b
Signed-off-by: Andrey Smrinov <andrew.smirnov@gmail.com>
Reviewed-on: http://openocd.zylin.com/2034
Tested-by: jenkins
Reviewed-by: Paul Fertser <fercerpav@gmail.com>
Reviewed-by: Andreas Fritiofson <andreas.fritiofson@gmail.com>
__archive__
Andrey Smirnov 2014-03-08 14:42:28 -08:00 committed by Andreas Fritiofson
parent 6cadbadb37
commit 46101959a6
4 changed files with 348 additions and 198 deletions

View File

@ -35,6 +35,7 @@
#include <helper/binarybuffer.h> #include <helper/binarybuffer.h>
#include <target/algorithm.h> #include <target/algorithm.h>
#include <target/armv7m.h> #include <target/armv7m.h>
#include <target/cortex_m.h>
/* /*
* Implementation Notes * Implementation Notes
@ -196,6 +197,267 @@ struct kinetis_flash_bank {
} flash_class; } flash_class;
}; };
#define MDM_REG_STAT 0x00
#define MDM_REG_CTRL 0x04
#define MDM_REG_ID 0xfc
#define MDM_STAT_FMEACK (1<<0)
#define MDM_STAT_FREADY (1<<1)
#define MDM_STAT_SYSSEC (1<<2)
#define MDM_STAT_SYSRES (1<<3)
#define MDM_STAT_FMEEN (1<<5)
#define MDM_STAT_BACKDOOREN (1<<6)
#define MDM_STAT_LPEN (1<<7)
#define MDM_STAT_VLPEN (1<<8)
#define MDM_STAT_LLSMODEXIT (1<<9)
#define MDM_STAT_VLLSXMODEXIT (1<<10)
#define MDM_STAT_CORE_HALTED (1<<16)
#define MDM_STAT_CORE_SLEEPDEEP (1<<17)
#define MDM_STAT_CORESLEEPING (1<<18)
#define MEM_CTRL_FMEIP (1<<0)
#define MEM_CTRL_DBG_DIS (1<<1)
#define MEM_CTRL_DBG_REQ (1<<2)
#define MEM_CTRL_SYS_RES_REQ (1<<3)
#define MEM_CTRL_CORE_HOLD_RES (1<<4)
#define MEM_CTRL_VLLSX_DBG_REQ (1<<5)
#define MEM_CTRL_VLLSX_DBG_ACK (1<<6)
#define MEM_CTRL_VLLSX_STAT_ACK (1<<7)
#define MDM_ACCESS_TIMEOUT 3000 /* iterations */
static int kinetis_mdm_write_register(struct adiv5_dap *dap, unsigned reg, uint32_t value)
{
int retval;
LOG_DEBUG("MDM_REG[0x%02x] <- %08" PRIX32, reg, value);
retval = dap_queue_ap_write(dap, reg, value);
if (retval != ERROR_OK) {
LOG_DEBUG("MDM: failed to queue a write request");
return retval;
}
retval = dap_run(dap);
if (retval != ERROR_OK) {
LOG_DEBUG("MDM: dap_run failed");
return retval;
}
return ERROR_OK;
}
static int kinetis_mdm_read_register(struct adiv5_dap *dap, unsigned reg, uint32_t *result)
{
int retval;
retval = dap_queue_ap_read(dap, reg, result);
if (retval != ERROR_OK) {
LOG_DEBUG("MDM: failed to queue a read request");
return retval;
}
retval = dap_run(dap);
if (retval != ERROR_OK) {
LOG_DEBUG("MDM: dap_run failed");
return retval;
}
LOG_DEBUG("MDM_REG[0x%02x]: %08" PRIX32, reg, *result);
return ERROR_OK;
}
static int kinetis_mdm_poll_register(struct adiv5_dap *dap, unsigned reg, uint32_t mask, uint32_t value)
{
uint32_t val;
int retval;
int timeout = MDM_ACCESS_TIMEOUT;
do {
retval = kinetis_mdm_read_register(dap, reg, &val);
if (retval != ERROR_OK || (val & mask) == value)
return retval;
alive_sleep(1);
} while (timeout--);
LOG_DEBUG("MDM: polling timed out");
return ERROR_FAIL;
}
/*
* This function implements the procedure to mass erase the flash via
* SWD/JTAG on Kinetis K and L series of devices as it is described in
* AN4835 "Production Flash Programming Best Practices for Kinetis K-
* and L-series MCUs" Section 4.2.1
*/
COMMAND_HANDLER(kinetis_mdm_mass_erase)
{
struct target *target = get_current_target(CMD_CTX);
struct cortex_m_common *cortex_m = target_to_cm(target);
struct adiv5_dap *dap = cortex_m->armv7m.arm.dap;
int retval;
const uint8_t original_ap = dap->ap_current;
/*
* ... Power on the processor, or if power has already been
* applied, assert the RESET pin to reset the processor. For
* devices that do not have a RESET pin, write the System
* Reset Request bit in the MDM-AP control register after
* establishing communication...
*/
dap_ap_select(dap, 1);
retval = kinetis_mdm_write_register(dap, MDM_REG_CTRL, MEM_CTRL_SYS_RES_REQ);
if (retval != ERROR_OK)
return retval;
/*
* ... Read the MDM-AP status register until the Flash Ready bit sets...
*/
retval = kinetis_mdm_poll_register(dap, MDM_REG_STAT,
MDM_STAT_FREADY | MDM_STAT_SYSRES,
MDM_STAT_FREADY);
if (retval != ERROR_OK) {
LOG_ERROR("MDM : flash ready timeout");
return retval;
}
/*
* ... Write the MDM-AP control register to set the Flash Mass
* Erase in Progress bit. This will start the mass erase
* process...
*/
retval = kinetis_mdm_write_register(dap, MDM_REG_CTRL,
MEM_CTRL_SYS_RES_REQ | MEM_CTRL_FMEIP);
if (retval != ERROR_OK)
return retval;
/* As a sanity check make sure that device started mass erase procedure */
retval = kinetis_mdm_poll_register(dap, MDM_REG_STAT,
MDM_STAT_FMEACK, MDM_STAT_FMEACK);
if (retval != ERROR_OK)
return retval;
/*
* ... Read the MDM-AP control register until the Flash Mass
* Erase in Progress bit clears...
*/
retval = kinetis_mdm_poll_register(dap, MDM_REG_CTRL,
MEM_CTRL_FMEIP,
0);
if (retval != ERROR_OK)
return retval;
/*
* ... Negate the RESET signal or clear the System Reset Request
* bit in the MDM-AP control register...
*/
retval = kinetis_mdm_write_register(dap, MDM_REG_CTRL, 0);
if (retval != ERROR_OK)
return retval;
dap_ap_select(dap, original_ap);
return ERROR_OK;
}
static const uint32_t kinetis_known_mdm_ids[] = {
0x001C0020, /* KL26Z */
};
/*
* This function implements the procedure to connect to
* SWD/JTAG on Kinetis K and L series of devices as it is described in
* AN4835 "Production Flash Programming Best Practices for Kinetis K-
* and L-series MCUs" Section 4.1.1
*/
COMMAND_HANDLER(kinetis_check_flash_security_status)
{
struct target *target = get_current_target(CMD_CTX);
struct cortex_m_common *cortex_m = target_to_cm(target);
struct adiv5_dap *dap = cortex_m->armv7m.arm.dap;
uint32_t val;
int retval;
const uint8_t origninal_ap = dap->ap_current;
dap_ap_select(dap, 1);
/*
* ... The MDM-AP ID register can be read to verify that the
* connection is working correctly...
*/
retval = kinetis_mdm_read_register(dap, MDM_REG_ID, &val);
if (retval != ERROR_OK) {
LOG_ERROR("MDM: failed to read ID register");
goto fail;
}
bool found = false;
for (size_t i = 0; i < ARRAY_SIZE(kinetis_known_mdm_ids); i++) {
if (val == kinetis_known_mdm_ids[i]) {
found = true;
break;
}
}
if (!found)
LOG_WARNING("MDM: unknown ID %08" PRIX32, val);
/*
* ... Read the MDM-AP status register until the Flash Ready bit sets...
*/
retval = kinetis_mdm_poll_register(dap, MDM_REG_STAT,
MDM_STAT_FREADY,
MDM_STAT_FREADY);
if (retval != ERROR_OK) {
LOG_ERROR("MDM: flash ready timeout");
goto fail;
}
/*
* ... Read the System Security bit to determine if security is enabled.
* If System Security = 0, then proceed. If System Security = 1, then
* communication with the internals of the processor, including the
* flash, will not be possible without issuing a mass erase command or
* unsecuring the part through other means (backdoor key unlock)...
*/
retval = kinetis_mdm_read_register(dap, MDM_REG_STAT, &val);
if (retval != ERROR_OK) {
LOG_ERROR("MDM: failed to read MDM_REG_STAT");
goto fail;
}
if (val & MDM_STAT_SYSSEC) {
jtag_poll_set_enabled(false);
LOG_WARNING("*********** ATTENTION! ATTENTION! ATTENTION! ATTENTION! **********");
LOG_WARNING("**** ****");
LOG_WARNING("**** Your Kinetis MCU is in secured state, which means that, ****");
LOG_WARNING("**** with exeption for very basic communication, JTAG/SWD ****");
LOG_WARNING("**** interface will NOT work. In order to restore its ****");
LOG_WARNING("**** functionality please issue 'kinetis mdm mass_erase' ****");
LOG_WARNING("**** command, power cycle the MCU and restart openocd. ****");
LOG_WARNING("**** ****");
LOG_WARNING("*********** ATTENTION! ATTENTION! ATTENTION! ATTENTION! **********");
} else {
LOG_INFO("MDM: Chip is unsecured. Continuing.");
jtag_poll_set_enabled(true);
}
dap_ap_select(dap, origninal_ap);
return ERROR_OK;
fail:
LOG_ERROR("MDM: Failed to check security status of the MCU. Cannot proceed further");
jtag_poll_set_enabled(false);
return retval;
}
FLASH_BANK_COMMAND_HANDLER(kinetis_flash_bank_command) FLASH_BANK_COMMAND_HANDLER(kinetis_flash_bank_command)
{ {
struct kinetis_flash_bank *bank_info; struct kinetis_flash_bank *bank_info;
@ -514,7 +776,6 @@ static int kinetis_ftfx_command(struct flash_bank *bank, uint8_t fcmd, uint32_t
static int kinetis_mass_erase(struct flash_bank *bank) static int kinetis_mass_erase(struct flash_bank *bank)
{ {
int result;
uint8_t ftfx_fstat; uint8_t ftfx_fstat;
if (bank->target->state != TARGET_HALTED) { if (bank->target->state != TARGET_HALTED) {
@ -522,26 +783,31 @@ static int kinetis_mass_erase(struct flash_bank *bank)
return ERROR_TARGET_NOT_HALTED; return ERROR_TARGET_NOT_HALTED;
} }
/* check if whole bank is blank */
LOG_INFO("Execute Erase All Blocks"); LOG_INFO("Execute Erase All Blocks");
/* set command and sector address */ return kinetis_ftfx_command(bank, FTFx_CMD_MASSERASE, 0,
result = kinetis_ftfx_command(bank, FTFx_CMD_MASSERASE, 0,
0, 0, 0, 0, 0, 0, 0, 0, &ftfx_fstat); 0, 0, 0, 0, 0, 0, 0, 0, &ftfx_fstat);
/* Anyway Result, write FSEC to unsecure forcely */ }
/* if (result != ERROR_OK)
return result;*/
/* Write to MCU security status unsecure in Flash security byte(for Kinetis-L need) */ COMMAND_HANDLER(kinetis_securing_test)
LOG_INFO("Write to MCU security status unsecure Anyway!"); {
uint8_t padding[4] = {0xFE, 0xFF, 0xFF, 0xFF}; /* Write 0xFFFFFFFE */ int result;
uint8_t ftfx_fstat;
struct target *target = get_current_target(CMD_CTX);
struct flash_bank *bank = NULL;
result = kinetis_ftfx_command(bank, FTFx_CMD_LWORDPROG, (bank->base + 0x0000040C), result = get_flash_bank_by_addr(target, 0x00000000, true, &bank);
padding[3], padding[2], padding[1], padding[0],
0, 0, 0, 0, &ftfx_fstat);
if (result != ERROR_OK) if (result != ERROR_OK)
return ERROR_FLASH_OPERATION_FAILED; return result;
return ERROR_OK; assert(bank != NULL);
if (target->state != TARGET_HALTED) {
LOG_ERROR("Target not halted");
return ERROR_TARGET_NOT_HALTED;
}
return kinetis_ftfx_command(bank, FTFx_CMD_SECTERASE, bank->base + 0x00000400,
0, 0, 0, 0, 0, 0, 0, 0, &ftfx_fstat);
} }
static int kinetis_erase(struct flash_bank *bank, int first, int last) static int kinetis_erase(struct flash_bank *bank, int first, int last)
@ -1150,8 +1416,58 @@ static int kinetis_blank_check(struct flash_bank *bank)
return ERROR_OK; return ERROR_OK;
} }
static const struct command_registration kinetis_securtiy_command_handlers[] = {
{
.name = "check_security",
.mode = COMMAND_EXEC,
.help = "",
.usage = "",
.handler = kinetis_check_flash_security_status,
},
{
.name = "mass_erase",
.mode = COMMAND_EXEC,
.help = "",
.usage = "",
.handler = kinetis_mdm_mass_erase,
},
{
.name = "test_securing",
.mode = COMMAND_EXEC,
.help = "",
.usage = "",
.handler = kinetis_securing_test,
},
COMMAND_REGISTRATION_DONE
};
static const struct command_registration kinetis_exec_command_handlers[] = {
{
.name = "mdm",
.mode = COMMAND_ANY,
.help = "",
.usage = "",
.chain = kinetis_securtiy_command_handlers,
},
COMMAND_REGISTRATION_DONE
};
static const struct command_registration kinetis_command_handler[] = {
{
.name = "kinetis",
.mode = COMMAND_ANY,
.help = "kinetis NAND flash controller commands",
.usage = "",
.chain = kinetis_exec_command_handlers,
},
COMMAND_REGISTRATION_DONE
};
struct flash_driver kinetis_flash = { struct flash_driver kinetis_flash = {
.name = "kinetis", .name = "kinetis",
.commands = kinetis_command_handler,
.flash_bank_command = kinetis_flash_bank_command, .flash_bank_command = kinetis_flash_bank_command,
.erase = kinetis_erase, .erase = kinetis_erase,
.protect = kinetis_protect, .protect = kinetis_protect,

View File

@ -616,186 +616,6 @@ int mem_ap_sel_write_buf_noincr(struct adiv5_dap *swjdp, uint8_t ap,
return mem_ap_write(swjdp, buffer, size, count, address, false); return mem_ap_write(swjdp, buffer, size, count, address, false);
} }
#define MDM_REG_STAT 0x00
#define MDM_REG_CTRL 0x04
#define MDM_REG_ID 0xfc
#define MDM_STAT_FMEACK (1<<0)
#define MDM_STAT_FREADY (1<<1)
#define MDM_STAT_SYSSEC (1<<2)
#define MDM_STAT_SYSRES (1<<3)
#define MDM_STAT_FMEEN (1<<5)
#define MDM_STAT_BACKDOOREN (1<<6)
#define MDM_STAT_LPEN (1<<7)
#define MDM_STAT_VLPEN (1<<8)
#define MDM_STAT_LLSMODEXIT (1<<9)
#define MDM_STAT_VLLSXMODEXIT (1<<10)
#define MDM_STAT_CORE_HALTED (1<<16)
#define MDM_STAT_CORE_SLEEPDEEP (1<<17)
#define MDM_STAT_CORESLEEPING (1<<18)
#define MEM_CTRL_FMEIP (1<<0)
#define MEM_CTRL_DBG_DIS (1<<1)
#define MEM_CTRL_DBG_REQ (1<<2)
#define MEM_CTRL_SYS_RES_REQ (1<<3)
#define MEM_CTRL_CORE_HOLD_RES (1<<4)
#define MEM_CTRL_VLLSX_DBG_REQ (1<<5)
#define MEM_CTRL_VLLSX_DBG_ACK (1<<6)
#define MEM_CTRL_VLLSX_STAT_ACK (1<<7)
#define MDM_ACCESS_TIMEOUT 3000 /* ms */
/**
*
*/
int dap_syssec_kinetis_mdmap(struct adiv5_dap *dap)
{
uint32_t val;
int retval;
int timeout = 0;
enum reset_types jtag_reset_config = jtag_get_reset_config();
dap_ap_select(dap, 1);
/* first check mdm-ap id register */
retval = dap_queue_ap_read(dap, MDM_REG_ID, &val);
if (retval != ERROR_OK)
return retval;
dap_run(dap);
if (val != 0x001C0000) {
LOG_DEBUG("id doesn't match %08" PRIX32 " != 0x001C0000", val);
dap_ap_select(dap, 0);
return ERROR_FAIL;
}
/* read and parse status register
* it's important that the device is out of
* reset here
*/
while (1) {
if (timeout++ > MDM_ACCESS_TIMEOUT) {
LOG_DEBUG("MDMAP : flash ready timeout");
return ERROR_FAIL;
}
retval = dap_queue_ap_read(dap, MDM_REG_STAT, &val);
if (retval != ERROR_OK)
return retval;
dap_run(dap);
LOG_DEBUG("MDM_REG_STAT %08" PRIX32, val);
if (val & MDM_STAT_FREADY)
break;
alive_sleep(1);
}
if ((val & MDM_STAT_SYSSEC)) {
LOG_DEBUG("MDMAP: system is secured, masserase needed");
if (!(val & MDM_STAT_FMEEN))
LOG_DEBUG("MDMAP: masserase is disabled");
else {
/* we need to assert reset */
if (jtag_reset_config & RESET_HAS_SRST) {
/* default to asserting srst */
adapter_assert_reset();
} else {
LOG_DEBUG("SRST not configured");
dap_ap_select(dap, 0);
return ERROR_FAIL;
}
timeout = 0;
while (1) {
if (timeout++ > MDM_ACCESS_TIMEOUT) {
LOG_DEBUG("MDMAP : flash ready timeout");
return ERROR_FAIL;
}
retval = dap_queue_ap_write(dap, MDM_REG_CTRL, MEM_CTRL_FMEIP);
if (retval != ERROR_OK)
return retval;
dap_run(dap);
/* read status register and wait for ready */
retval = dap_queue_ap_read(dap, MDM_REG_STAT, &val);
if (retval != ERROR_OK)
return retval;
dap_run(dap);
LOG_DEBUG("MDM_REG_STAT %08" PRIX32, val);
if ((val & 1))
break;
alive_sleep(1);
}
timeout = 0;
while (1) {
if (timeout++ > MDM_ACCESS_TIMEOUT) {
LOG_DEBUG("MDMAP : flash ready timeout");
return ERROR_FAIL;
}
retval = dap_queue_ap_write(dap, MDM_REG_CTRL, 0);
if (retval != ERROR_OK)
return retval;
dap_run(dap);
/* read status register */
retval = dap_queue_ap_read(dap, MDM_REG_STAT, &val);
if (retval != ERROR_OK)
return retval;
dap_run(dap);
LOG_DEBUG("MDM_REG_STAT %08" PRIX32, val);
/* read control register and wait for ready */
retval = dap_queue_ap_read(dap, MDM_REG_CTRL, &val);
if (retval != ERROR_OK)
return retval;
dap_run(dap);
LOG_DEBUG("MDM_REG_CTRL %08" PRIX32, val);
if (val == 0x00)
break;
alive_sleep(1);
}
}
}
dap_ap_select(dap, 0);
return ERROR_OK;
}
/** */
struct dap_syssec_filter {
/** */
uint32_t idcode;
/** */
int (*dap_init)(struct adiv5_dap *dap);
};
/** */
static struct dap_syssec_filter dap_syssec_filter_data[] = {
{ 0x4BA00477, dap_syssec_kinetis_mdmap }
};
/**
*
*/
int dap_syssec(struct adiv5_dap *dap)
{
unsigned int i;
struct jtag_tap *tap;
for (i = 0; i < sizeof(dap_syssec_filter_data); i++) {
tap = dap->jtag_info->tap;
while (tap != NULL) {
if (tap->hasidcode && (dap_syssec_filter_data[i].idcode == tap->idcode)) {
LOG_DEBUG("DAP: mdmap_init for idcode: %08" PRIx32, tap->idcode);
dap_syssec_filter_data[i].dap_init(dap);
}
tap = tap->next_tap;
}
}
return ERROR_OK;
}
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@ -903,8 +723,6 @@ int ahbap_debugport_init(struct adiv5_dap *dap)
if (retval != ERROR_OK) if (retval != ERROR_OK)
return retval; return retval;
dap_syssec(dap);
/* check that we support packed transfers */ /* check that we support packed transfers */
uint32_t csw, cfg; uint32_t csw, cfg;

View File

@ -38,6 +38,14 @@ swj_newdap $_CHIPNAME cpu -irlen 4 -expected-id $_CPUTAPID
set _TARGETNAME $_CHIPNAME.cpu set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME cortex_m -endian $_ENDIAN -chain-position $_CHIPNAME.cpu target create $_TARGETNAME cortex_m -endian $_ENDIAN -chain-position $_CHIPNAME.cpu
# It is important that "kinetis mdm check_security" is called for
# 'examine-end' event and not 'eximine-start'. Calling it in 'examine-start'
# causes "kinetis mdm check_security" to fail the first time openocd
# calls it when it tries to connect after the CPU has been power-cycled.
$_CHIPNAME.cpu configure -event examine-end {
kinetis mdm check_security
}
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 $_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
set _FLASHNAME $_CHIPNAME.flash set _FLASHNAME $_CHIPNAME.flash

View File

@ -24,6 +24,14 @@ hla newtap $_CHIPNAME cpu -expected-id $_CPUTAPID
set _TARGETNAME $_CHIPNAME.cpu set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME hla_target -chain-position $_TARGETNAME target create $_TARGETNAME hla_target -chain-position $_TARGETNAME
# It is important that "kinetis mdm check_security" is called for
# 'examine-end' event and not 'eximine-start'. Calling it in 'examine-start'
# causes "kinetis mdm check_security" to fail the first time openocd
# calls it when it tries to connect after the CPU has been power-cycled.
$_CHIPNAME.cpu configure -event examine-end {
kinetis mdm check_security
}
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 $_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
flash bank pflash kinetis 0x00000000 0x20000 0 4 $_TARGETNAME flash bank pflash kinetis 0x00000000 0x20000 0 4 $_TARGETNAME