#include "command.h"
#include "general.h"
#include "target.h"
#include "target_internal.h"
#define RAM_BASE_ADDR 0x20000000U
#define FLASH_BASE_ADDR 0x00000000U
#define SIM_SRSID 0x40048000U
#define SRSID_KE04_MASK 0xff00U
#define SRSID_KE04_FAMILY 0x0400U
#define SRSID_PIN_MASK 0x000fU
#define SRSID_PIN__8 0x0000U
#define SRSID_PIN_16 0x0001U
#define SRSID_PIN_20 0x0002U
#define SRSID_PIN_24 0x0003U
#define SRSID_PIN_32 0x0004U
#define SRSID_PIN_44 0x0005U
#define SRSID_PIN_48 0x0006U
#define SRSID_PIN_64 0x0007U
#define SRSID_PIN_80 0x0008U
#define SRSID_PIN100 0x000aU
#define FTMRE_BASE 0x40020000U
#define FTMRE_FCCOBIX (FTMRE_BASE + 0x01U)
#define FTMRE_FSEC (FTMRE_BASE + 0x02U)
#define FTMRE_FCLKDIV (FTMRE_BASE + 0x03U)
#define FTMRE_FSTAT (FTMRE_BASE + 0x05U)
#define FTMRE_FCNFG (FTMRE_BASE + 0x07U)
#define FTMRE_FCCOB (FTMRE_BASE + 0x08U)
#define FTMRE_FCCOBLO (FTMRE_BASE + 0x08U)
#define FTMRE_FCCOBHI (FTMRE_BASE + 0x09U)
#define FTMRE_FPROT (FTMRE_BASE + 0x0bU)
#define FTMRE_FOPT (FTMRE_BASE + 0x0fU)
#define FTMRE_FSTAT_CCIF 0x80U
#define FTMRE_FSTAT_ACCERR 0x20U
#define FTMRE_FSTAT_FPVIOL 0x10U
#define FTMRE_FSTAT_MGBUSY 0x08U
#define FTMRE_FSTAT_MGSTAT1 0x02U
#define FTMRE_FSTAT_MGSTAT0 0x01U
#define CMD_PROGRAM_FLASH_32 0x00U
#define CMD_ERASE_VERIFY_ALL_BLOCKS 0x01U
#define CMD_ERASE_VERIFY_BLOCK 0x02U
#define CMD_ERASE_VERIFY_SECTION 0x03U
#define CMD_READ_ONCE 0x04U
#define CMD_PROGRAM_FLASH 0x06U
#define CMD_PROGRAM_ONCE 0x07U
#define CMD_ERASE_ALL_BLOCKS 0x08U
#define CMD_ERASE_FLASH_BLOCK 0x09U
#define CMD_ERASE_FLASH_SECTOR 0x0aU
#define CMD_UNSECURE_FLASH 0x0bU
#define CMD_VERIFY_BACKDOOR_ACCESS_KEY 0x0cU
#define CMD_SET_USER_MARGIN_LEVEL 0x0dU
#define CMD_SET_FACTORY_MARGIN_LEVEL 0x0eU
#define KE04_WRITE_LEN 8U
#define KE04_SECTOR_SIZE 0x200U
#define FLASH_SECURITY_BYTE_ADDRESS 0x0000040eU
#define FLASH_SECURITY_BYTE_UNSECURED 0xfeU
#define FLASH_SECURITY_WORD_ADDRESS 0x0000040cU
#define FLASH_SECURITY_WORD_UNSECURED 0xfffeffffU
static const uint8_t cmd_lens[] = {4, 1, 2, 3, 6, 0, 6, 6, 1, 2, 2, 1, 5, 3, 3};
static bool ke04_command(target_s *t, uint8_t cmd, uint32_t addr, const void *data);
static bool ke04_flash_erase(target_flash_s *f, target_addr_t addr, size_t len);
static bool ke04_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len);
static bool ke04_flash_done(target_flash_s *f);
static bool ke04_mass_erase(target_s *t);
static bool kinetis_cmd_unsafe(target_s *t, int argc, const char **argv);
static bool ke04_cmd_sector_erase(target_s *t, int argc, const char **argv);
const command_s ke_cmd_list[] = {
{"unsafe", kinetis_cmd_unsafe, "Allow programming security byte (enable|disable)"},
{"sector_erase", ke04_cmd_sector_erase, "Erase sector containing given address"},
{NULL, NULL, NULL},
};
static bool ke04_cmd_sector_erase(target_s *t, int argc, const char **argv)
{
if (argc < 2)
tc_printf(t, "usage: monitor sector_erase <addr>\n");
target_flash_s *f = t->flash;
char *eos = NULL;
uint32_t addr = strtoul(argv[1], &eos, 0);
if ((eos && eos[0] != '\0') || addr < f->start || addr >= f->start + f->length) {
tc_printf(t, "Invalid sector address\n");
return false;
}
ke04_command(t, CMD_ERASE_FLASH_SECTOR, addr, NULL);
ke04_flash_done(f);
return true;
}
static bool kinetis_cmd_unsafe(target_s *t, int argc, const char **argv)
{
if (argc == 1)
tc_printf(t, "Allow programming security byte: %s\n", t->unsafe_enabled ? "enabled" : "disabled");
else
parse_enable_or_disable(argv[1], &t->unsafe_enabled);
return true;
}
bool ke04_probe(target_s *t)
{
const uint16_t srsid = target_mem_read32(t, SIM_SRSID) >> 16U;
if ((srsid & SRSID_KE04_MASK) != SRSID_KE04_FAMILY)
return false;
uint32_t ramsize = 0;
uint32_t flashsize = 0;
switch (srsid & SRSID_PIN_MASK) {
case SRSID_PIN_16:
case SRSID_PIN_20:
case SRSID_PIN_24:
t->driver = "Kinetis KE04Z8Vxxxx";
flashsize = 0x2000U;
ramsize = 0x400U;
break;
case SRSID_PIN_44:
case SRSID_PIN_64:
case SRSID_PIN_80: {
volatile uint32_t __attribute__((unused)) dummy = target_mem_read32(t, 0x00010000U);
if (target_check_error(t)) {
t->driver = "Kinetis KE04Z64Vxxxx";
flashsize = 0x10000U;
ramsize = 0x2000U;
} else {
t->driver = "Kinetis KE04Z128Vxxxx";
flashsize = 0x20000U;
ramsize = 0x4000U;
}
break;
}
default:
return false;
}
t->mass_erase = ke04_mass_erase;
ramsize /= 4U;
target_add_ram(t, RAM_BASE_ADDR - ramsize, ramsize);
ramsize *= 3U;
target_add_ram(t, RAM_BASE_ADDR, ramsize);
target_flash_s *f = calloc(1, sizeof(*f));
if (!f) {
DEBUG_ERROR("calloc: failed in %s\n", __func__);
return false;
}
f->start = FLASH_BASE_ADDR;
f->length = flashsize;
f->blocksize = KE04_SECTOR_SIZE;
f->erase = ke04_flash_erase;
f->write = ke04_flash_write;
f->done = ke04_flash_done;
f->erased = 0xffU;
target_add_flash(t, f);
target_add_commands(t, ke_cmd_list, t->driver);
return true;
}
static bool ke04_mass_erase(target_s *t)
{
ke04_command(t, CMD_ERASE_ALL_BLOCKS, 0, NULL);
ke04_flash_done(t->flash);
return true;
}
static bool ke04_wait_complete(target_s *t)
{
uint8_t fstat = 0;
while (!(fstat & FTMRE_FSTAT_CCIF)) {
fstat = target_mem_read8(t, FTMRE_FSTAT);
if (target_check_error(t))
return false;
}
return true;
}
static bool ke04_command(target_s *t, uint8_t cmd, uint32_t addr, const void *const data)
{
uint8_t fclkdiv = target_mem_read8(t, FTMRE_FCLKDIV);
if ((fclkdiv & 0x1fU) != 0x17U) {
if (!ke04_wait_complete(t))
return false;
target_mem_write8(t, FTMRE_FCLKDIV, 0x17U);
}
target_mem_write8(t, FTMRE_FSTAT, FTMRE_FSTAT_ACCERR | FTMRE_FSTAT_FPVIOL);
if (!ke04_wait_complete(t))
return false;
uint8_t fccob_idx = 0;
addr &= 0x00ffffffU;
const uint8_t cmd_len = cmd_lens[cmd];
if (cmd == CMD_PROGRAM_FLASH_32)
cmd = CMD_PROGRAM_FLASH;
const uint16_t fccob_cmd = (cmd << 8U) | (addr >> 16U);
target_mem_write8(t, FTMRE_FCCOBIX, fccob_idx++);
target_mem_write16(t, FTMRE_FCCOB, fccob_cmd);
if (cmd_len >= 1) {
target_mem_write8(t, FTMRE_FCCOBIX, fccob_idx++);
target_mem_write16(t, FTMRE_FCCOB, addr & 0xffffU);
}
const uint16_t *const cmd_data = (const uint16_t *)data;
for (uint8_t offset = 0; fccob_idx < cmd_len; ++fccob_idx, ++offset) {
target_mem_write8(t, FTMRE_FCCOBIX, fccob_idx);
target_mem_write16(t, FTMRE_FCCOB, cmd_data[offset]);
}
target_mem_write8(t, FTMRE_FSTAT, FTMRE_FSTAT_CCIF);
platform_timeout_s timeout;
platform_timeout_set(&timeout, 500);
uint8_t fstat = 0;
while (!(fstat & FTMRE_FSTAT_CCIF)) {
fstat = target_mem_read8(t, FTMRE_FSTAT);
if (fstat & (FTMRE_FSTAT_ACCERR | FTMRE_FSTAT_FPVIOL))
return false;
if (cmd == CMD_ERASE_ALL_BLOCKS)
target_print_progress(&timeout);
}
return true;
}
static bool ke04_flash_erase(target_flash_s *const f, const target_addr_t addr, const size_t len)
{
for (size_t offset = 0; offset < len; offset += f->blocksize) {
if (!ke04_command(f->t, CMD_ERASE_FLASH_SECTOR, addr + offset, NULL))
return false;
}
return true;
}
static bool ke04_flash_write(target_flash_s *f, target_addr_t dest, const void *src, size_t len)
{
target_s *t = f->t;
const uint8_t *data = src;
if (!t->unsafe_enabled && dest <= FLASH_SECURITY_BYTE_ADDRESS && dest + len > FLASH_SECURITY_BYTE_ADDRESS)
((uint8_t *)data)[FLASH_SECURITY_BYTE_ADDRESS - dest] = FLASH_SECURITY_BYTE_UNSECURED;
for (size_t offset = 0; offset < len; offset += KE04_WRITE_LEN) {
if (!ke04_command(f->t, CMD_PROGRAM_FLASH, dest + offset, data + offset))
return false;
}
return true;
}
static bool ke04_flash_done(target_flash_s *f)
{
target_s *t = f->t;
if (t->unsafe_enabled || target_mem_read8(f->t, FLASH_SECURITY_BYTE_ADDRESS) == FLASH_SECURITY_BYTE_UNSECURED)
return true;
uint32_t vals[2] = {target_mem_read32(f->t, FLASH_SECURITY_WORD_ADDRESS), 0};
vals[0] = (vals[0] & 0xff00ffffU) | (FLASH_SECURITY_BYTE_UNSECURED << 16U);
return ke04_command(f->t, CMD_PROGRAM_FLASH_32, FLASH_SECURITY_WORD_ADDRESS, &vals);
}