// SPDX-License-Identifier: GPL-2.0-or-later
/*
* OpenOCD STM8 target driver
* Copyright (C) 2017 Ake Rehnman
* ake.rehnman(at)gmail.com
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <helper/log.h>
#include "target.h"
#include "target_type.h"
#include "hello.h"
#include "jtag/interface.h"
#include "jtag/jtag.h"
#include "jtag/swim.h"
#include "register.h"
#include "breakpoints.h"
#include "algorithm.h"
#include "stm8.h"
static struct reg_cache *stm8_build_reg_cache(struct target *target);
static int stm8_read_core_reg(struct target *target, unsigned int num);
static int stm8_write_core_reg(struct target *target, unsigned int num);
static int stm8_save_context(struct target *target);
static void stm8_enable_breakpoints(struct target *target);
static int stm8_unset_breakpoint(struct target *target,
struct breakpoint *breakpoint);
static int stm8_set_breakpoint(struct target *target,
struct breakpoint *breakpoint);
static void stm8_enable_watchpoints(struct target *target);
static int stm8_unset_watchpoint(struct target *target,
struct watchpoint *watchpoint);
static int (*adapter_speed)(int speed);
extern struct adapter_driver *adapter_driver;
static const struct {
unsigned id;
const char *name;
const uint8_t bits;
enum reg_type type;
const char *group;
const char *feature;
int flag;
} stm8_regs[] = {
{ 0, "pc", 32, REG_TYPE_UINT32, "general", "org.gnu.gdb.stm8.core", 0 },
{ 1, "a", 8, REG_TYPE_UINT8, "general", "org.gnu.gdb.stm8.core", 0 },
{ 2, "x", 16, REG_TYPE_UINT16, "general", "org.gnu.gdb.stm8.core", 0 },
{ 3, "y", 16, REG_TYPE_UINT16, "general", "org.gnu.gdb.stm8.core", 0 },
{ 4, "sp", 16, REG_TYPE_UINT16, "general", "org.gnu.gdb.stm8.core", 0 },
{ 5, "cc", 8, REG_TYPE_UINT8, "general", "org.gnu.gdb.stm8.core", 0 },
};
#define STM8_NUM_REGS ARRAY_SIZE(stm8_regs)
#define STM8_PC 0
#define STM8_A 1
#define STM8_X 2
#define STM8_Y 3
#define STM8_SP 4
#define STM8_CC 5
#define CC_I0 0x8
#define CC_I1 0x20
#define DM_REGS 0x7f00
#define DM_REG_A 0x7f00
#define DM_REG_PC 0x7f01
#define DM_REG_X 0x7f04
#define DM_REG_Y 0x7f06
#define DM_REG_SP 0x7f08
#define DM_REG_CC 0x7f0a
#define DM_BKR1E 0x7f90
#define DM_BKR2E 0x7f93
#define DM_CR1 0x7f96
#define DM_CR2 0x7f97
#define DM_CSR1 0x7f98
#define DM_CSR2 0x7f99
#define STE 0x40
#define STF 0x20
#define RST 0x10
#define BRW 0x08
#define BK2F 0x04
#define BK1F 0x02
#define SWBRK 0x20
#define SWBKF 0x10
#define STALL 0x08
#define FLUSH 0x01
#define FLASH_CR1_STM8S 0x505A
#define FLASH_CR2_STM8S 0x505B
#define FLASH_NCR2_STM8S 0x505C
#define FLASH_IAPSR_STM8S 0x505F
#define FLASH_PUKR_STM8S 0x5062
#define FLASH_DUKR_STM8S 0x5064
#define FLASH_CR1_STM8L 0x5050
#define FLASH_CR2_STM8L 0x5051
#define FLASH_NCR2_STM8L 0
#define FLASH_PUKR_STM8L 0x5052
#define FLASH_DUKR_STM8L 0x5053
#define FLASH_IAPSR_STM8L 0x5054
/* FLASH_IAPSR */
#define HVOFF 0x40
#define DUL 0x08
#define EOP 0x04
#define PUL 0x02
#define WR_PG_DIS 0x01
/* FLASH_CR2 */
#define OPT 0x80
#define WPRG 0x40
#define ERASE 0x20
#define FPRG 0x10
#define PRG 0x01
/* SWIM_CSR */
#define SAFE_MASK 0x80
#define NO_ACCESS 0x40
#define SWIM_DM 0x20
#define HS 0x10
#define OSCOFF 0x08
#define SWIM_RST 0x04
#define HSIT 0x02
#define PRI 0x01
#define SWIM_CSR 0x7f80
#define STM8_BREAK 0x8B
enum mem_type {
RAM,
FLASH,
EEPROM,
OPTION
};
struct stm8_algorithm {
int common_magic;
};
struct stm8_core_reg {
uint32_t num;
struct target *target;
};
enum hw_break_type {
/* break on execute */
HWBRK_EXEC,
/* break on read */
HWBRK_RD,
/* break on write */
HWBRK_WR,
/* break on read, write and execute */
HWBRK_ACC
};
struct stm8_comparator {
bool used;
uint32_t bp_value;
uint32_t reg_address;
enum hw_break_type type;
};
static int stm8_adapter_read_memory(struct target *target,
uint32_t addr, int size, int count, void *buf)
{
return swim_read_mem(addr, size, count, buf);
}
static int stm8_adapter_write_memory(struct target *target,
uint32_t addr, int size, int count, const void *buf)
{
return swim_write_mem(addr, size, count, buf);
}
static int stm8_write_u8(struct target *target,
uint32_t addr, uint8_t val)
{
uint8_t buf[1];
buf[0] = val;
return swim_write_mem(addr, 1, 1, buf);
}
static int stm8_read_u8(struct target *target,
uint32_t addr, uint8_t *val)
{
return swim_read_mem(addr, 1, 1, val);
}
/*
<enable == 0> Disables interrupts.
If interrupts are enabled they are masked and the cc register
is saved.
<enable == 1> Enables interrupts.
Enable interrupts is actually restoring I1 I0 state from previous
call with enable == 0. Note that if stepping and breaking on a sim
instruction will NOT work since the interrupt flags are restored on
debug_entry. We don't have any way for the debugger to exclusively
disable the interrupts
*/
static int stm8_enable_interrupts(struct target *target, int enable)
{
struct stm8_common *stm8 = target_to_stm8(target);
uint8_t cc;
if (enable) {
if (!stm8->cc_valid)
return ERROR_OK; /* cc was not stashed */
/* fetch current cc */
stm8_read_u8(target, DM_REG_CC, &cc);
/* clear I1 I0 */
cc &= ~(CC_I0 + CC_I1);
/* restore I1 & I0 from stash*/
cc |= (stm8->cc & (CC_I0+CC_I1));
/* update current cc */
stm8_write_u8(target, DM_REG_CC, cc);
stm8->cc_valid = false;
} else {
stm8_read_u8(target, DM_REG_CC, &cc);
if ((cc & CC_I0) && (cc & CC_I1))
return ERROR_OK; /* interrupts already masked */
/* stash cc */
stm8->cc = cc;
stm8->cc_valid = true;
/* mask interrupts (disable) */
cc |= (CC_I0 + CC_I1);
stm8_write_u8(target, DM_REG_CC, cc);
}
return ERROR_OK;
}
static int stm8_set_hwbreak(struct target *target,
struct stm8_comparator comparator_list[])
{
uint8_t buf[3];
int i, ret;
/* Refer to Table 4 in UM0470 */
uint8_t bc = 0x5;
uint8_t bir = 0;
uint8_t biw = 0;
uint32_t data;
uint32_t addr;
if (!comparator_list[0].used) {
comparator_list[0].type = HWBRK_EXEC;
comparator_list[0].bp_value = -1;
}
if (!comparator_list[1].used) {
comparator_list[1].type = HWBRK_EXEC;
comparator_list[1].bp_value = -1;
}
if ((comparator_list[0].type == HWBRK_EXEC)
&& (comparator_list[1].type == HWBRK_EXEC)) {
comparator_list[0].reg_address = 0;
comparator_list[1].reg_address = 1;
}
if ((comparator_list[0].type == HWBRK_EXEC)
&& (comparator_list[1].type != HWBRK_EXEC)) {
comparator_list[0].reg_address = 0;
comparator_list[1].reg_address = 1;
switch (comparator_list[1].type) {
case HWBRK_RD:
bir = 1;
break;
case HWBRK_WR:
biw = 1;
break;
default:
bir = 1;
biw = 1;
break;
}
}
if ((comparator_list[1].type == HWBRK_EXEC)
&& (comparator_list[0].type != HWBRK_EXEC)) {
comparator_list[0].reg_address = 1;
comparator_list[1].reg_address = 0;
switch (comparator_list[0].type) {
case HWBRK_RD:
bir = 1;
break;
case HWBRK_WR:
biw = 1;
break;
default:
bir = 1;
biw = 1;
break;
}
}
if ((comparator_list[0].type != HWBRK_EXEC)
&& (comparator_list[1].type != HWBRK_EXEC)) {
if (comparator_list[0].type != comparator_list[1].type) {
LOG_ERROR("data hw breakpoints must be of same type");
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
}
}
for (i = 0; i < 2; i++) {
data = comparator_list[i].bp_value;
addr = comparator_list[i].reg_address;
buf[0] = data >> 16;
buf[1] = data >> 8;
buf[2] = data;
if (addr == 0) {
ret = stm8_adapter_write_memory(target, DM_BKR1E, 1, 3, buf);
LOG_DEBUG("DM_BKR1E=%" PRIx32, data);
} else if (addr == 1) {
ret = stm8_adapter_write_memory(target, DM_BKR2E, 1, 3, buf);
LOG_DEBUG("DM_BKR2E=%" PRIx32, data);
} else {
LOG_DEBUG("addr=%" PRIu32, addr);
return ERROR_FAIL;
}
if (ret != ERROR_OK)
return ret;
ret = stm8_write_u8(target, DM_CR1,
(bc << 3) + (bir << 2) + (biw << 1));
LOG_DEBUG("DM_CR1=%" PRIx8, buf[0]);
if (ret != ERROR_OK)
return ret;
}
return ERROR_OK;
}
/* read DM control and status regs */
static int stm8_read_dm_csrx(struct target *target, uint8_t *csr1,
uint8_t *csr2)
{
int ret;
uint8_t buf[2];
ret = stm8_adapter_read_memory(target, DM_CSR1, 1, sizeof(buf), buf);
if (ret != ERROR_OK)
return ret;
if (csr1)
*csr1 = buf[0];
if (csr2)
*csr2 = buf[1];
return ERROR_OK;
}
/* set or clear the single step flag in DM */
static int stm8_config_step(struct target *target, int enable)
{
int ret;
uint8_t csr1, csr2;
ret = stm8_read_dm_csrx(target, &csr1, &csr2);
if (ret != ERROR_OK)
return ret;
if (enable)
csr1 |= STE;
else
csr1 &= ~STE;
ret = stm8_write_u8(target, DM_CSR1, csr1);
if (ret != ERROR_OK)
return ret;
return ERROR_OK;
}
/* set the stall flag in DM */
static int stm8_debug_stall(struct target *target)
{
int ret;
uint8_t csr1, csr2;
ret = stm8_read_dm_csrx(target, &csr1, &csr2);
if (ret != ERROR_OK)
return ret;
csr2 |= STALL;
ret = stm8_write_u8(target, DM_CSR2, csr2);
if (ret != ERROR_OK)
return ret;
return ERROR_OK;
}
static int stm8_configure_break_unit(struct target *target)
{
/* get pointers to arch-specific information */
struct stm8_common *stm8 = target_to_stm8(target);
if (stm8->bp_scanned)
return ERROR_OK;
stm8->num_hw_bpoints = 2;
stm8->num_hw_bpoints_avail = stm8->num_hw_bpoints;
stm8->hw_break_list = calloc(stm8->num_hw_bpoints,
sizeof(struct stm8_comparator));
stm8->hw_break_list[0].reg_address = 0;
stm8->hw_break_list[1].reg_address = 1;
LOG_DEBUG("hw breakpoints: numinst %i numdata %i", stm8->num_hw_bpoints,
stm8->num_hw_bpoints);
stm8->bp_scanned = true;
return ERROR_OK;
}
static int stm8_examine_debug_reason(struct target *target)
{
int retval;
uint8_t csr1, csr2;
retval = stm8_read_dm_csrx(target, &csr1, &csr2);
if (retval == ERROR_OK)
LOG_DEBUG("csr1 = 0x%02X csr2 = 0x%02X", csr1, csr2);
if ((target->debug_reason != DBG_REASON_DBGRQ)
&& (target->debug_reason != DBG_REASON_SINGLESTEP)) {
if (retval != ERROR_OK)
return retval;
if (csr1 & RST)
/* halted on reset */
target->debug_reason = DBG_REASON_UNDEFINED;
if (csr1 & (BK1F+BK2F))
/* we have halted on a breakpoint (or wp)*/
target->debug_reason = DBG_REASON_BREAKPOINT;
if (csr2 & SWBKF)
/* we have halted on a breakpoint */
target->debug_reason = DBG_REASON_BREAKPOINT;
}
return ERROR_OK;
}
static int stm8_debug_entry(struct target *target)
{
struct stm8_common *stm8 = target_to_stm8(target);
/* restore interrupts */
stm8_enable_interrupts(target, 1);
stm8_save_context(target);
/* make sure stepping disabled STE bit in CSR1 cleared */
stm8_config_step(target, 0);
/* attempt to find halt reason */
stm8_examine_debug_reason(target);
LOG_DEBUG("entered debug state at PC 0x%" PRIx32 ", target->state: %s",
buf_get_u32(stm8->core_cache->reg_list[STM8_PC].value, 0, 32),
target_state_name(target));
return ERROR_OK;
}
/* clear stall flag in DM and flush instruction pipe */
static int stm8_exit_debug(struct target *target)
{
int ret;
uint8_t csr1, csr2;
ret = stm8_read_dm_csrx(target, &csr1, &csr2);
|