summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--exec.c18
-rw-r--r--hmp-commands.hx2
-rw-r--r--monitor.c4
-rw-r--r--target-xtensa/core-dc232b.c1
-rw-r--r--target-xtensa/core-fsf.c1
-rw-r--r--target-xtensa/cpu.h43
-rw-r--r--target-xtensa/helper.c110
-rw-r--r--target-xtensa/helpers.h7
-rw-r--r--target-xtensa/op_helper.c100
-rw-r--r--target-xtensa/overlay_tool.h23
-rw-r--r--target-xtensa/translate.c156
-rw-r--r--tests/tcg/xtensa/Makefile1
-rw-r--r--tests/tcg/xtensa/test_break.S223
13 files changed, 674 insertions, 15 deletions
diff --git a/exec.c b/exec.c
index 0c93b2691f..3ce35399d6 100644
--- a/exec.c
+++ b/exec.c
@@ -1504,7 +1504,8 @@ int cpu_watchpoint_insert(CPUState *env, target_ulong addr, target_ulong len,
     CPUWatchpoint *wp;
 
     /* sanity checks: allow power-of-2 lengths, deny unaligned watchpoints */
-    if ((len != 1 && len != 2 && len != 4 && len != 8) || (addr & ~len_mask)) {
+    if ((len & (len - 1)) || (addr & ~len_mask) ||
+            len == 0 || len > TARGET_PAGE_SIZE) {
         fprintf(stderr, "qemu: tried to set invalid watchpoint at "
                 TARGET_FMT_lx ", len=" TARGET_FMT_lu "\n", addr, len);
         return -EINVAL;
@@ -3331,11 +3332,12 @@ static void check_watchpoint(int offset, int len_mask, int flags)
                 tb_phys_invalidate(tb, -1);
                 if (wp->flags & BP_STOP_BEFORE_ACCESS) {
                     env->exception_index = EXCP_DEBUG;
+                    cpu_loop_exit(env);
                 } else {
                     cpu_get_tb_cpu_state(env, &pc, &cs_base, &cpu_flags);
                     tb_gen_code(env, pc, cs_base, cpu_flags, 1);
+                    cpu_resume_from_signal(env, NULL);
                 }
-                cpu_resume_from_signal(env, NULL);
             }
         } else {
             wp->flags &= ~BP_WATCHPOINT_HIT;
@@ -3363,9 +3365,15 @@ static void watch_mem_write(void *opaque, target_phys_addr_t addr,
 {
     check_watchpoint(addr & ~TARGET_PAGE_MASK, ~(size - 1), BP_MEM_WRITE);
     switch (size) {
-    case 1: stb_phys(addr, val);
-    case 2: stw_phys(addr, val);
-    case 4: stl_phys(addr, val);
+    case 1:
+        stb_phys(addr, val);
+        break;
+    case 2:
+        stw_phys(addr, val);
+        break;
+    case 4:
+        stl_phys(addr, val);
+        break;
     default: abort();
     }
 }
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 64b3656d8e..ed88877230 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1355,7 +1355,7 @@ show i8259 (PIC) state
 @item info pci
 show emulated PCI device info
 @item info tlb
-show virtual to physical memory mappings (i386, SH4, SPARC, and PPC only)
+show virtual to physical memory mappings (i386, SH4, SPARC, PPC, and Xtensa only)
 @item info mem
 show the active virtual memory mappings (i386 only)
 @item info jit
diff --git a/monitor.c b/monitor.c
index 953e7482b7..cbdfbad768 100644
--- a/monitor.c
+++ b/monitor.c
@@ -1949,7 +1949,7 @@ static void tlb_info(Monitor *mon)
 
 #endif
 
-#if defined(TARGET_SPARC) || defined(TARGET_PPC)
+#if defined(TARGET_SPARC) || defined(TARGET_PPC) || defined(TARGET_XTENSA)
 static void tlb_info(Monitor *mon)
 {
     CPUState *env1 = mon_get_cpu();
@@ -2396,7 +2396,7 @@ static mon_cmd_t info_cmds[] = {
         .mhandler.info = hmp_info_pci,
     },
 #if defined(TARGET_I386) || defined(TARGET_SH4) || defined(TARGET_SPARC) || \
-    defined(TARGET_PPC)
+    defined(TARGET_PPC) || defined(TARGET_XTENSA)
     {
         .name       = "tlb",
         .args_type  = "",
diff --git a/target-xtensa/core-dc232b.c b/target-xtensa/core-dc232b.c
index 4d9bd559d4..b723c4ca7b 100644
--- a/target-xtensa/core-dc232b.c
+++ b/target-xtensa/core-dc232b.c
@@ -22,6 +22,7 @@ static const XtensaConfig dc232b = {
     EXCEPTIONS_SECTION,
     INTERRUPTS_SECTION,
     TLB_SECTION,
+    DEBUG_SECTION,
     .clock_freq_khz = 10000,
 };
 
diff --git a/target-xtensa/core-fsf.c b/target-xtensa/core-fsf.c
index 7650462f2f..88de4dd4ba 100644
--- a/target-xtensa/core-fsf.c
+++ b/target-xtensa/core-fsf.c
@@ -16,6 +16,7 @@ static const XtensaConfig fsf = {
     EXCEPTIONS_SECTION,
     INTERRUPTS_SECTION,
     TLB_SECTION,
+    DEBUG_SECTION,
     .clock_freq_khz = 10000,
 };
 
diff --git a/target-xtensa/cpu.h b/target-xtensa/cpu.h
index 0db83a6fd7..fb8a727c66 100644
--- a/target-xtensa/cpu.h
+++ b/target-xtensa/cpu.h
@@ -126,6 +126,10 @@ enum {
     RASID = 90,
     ITLBCFG = 91,
     DTLBCFG = 92,
+    IBREAKENABLE = 96,
+    IBREAKA = 128,
+    DBREAKA = 144,
+    DBREAKC = 160,
     EPC1 = 177,
     DEPC = 192,
     EPS2 = 194,
@@ -137,8 +141,11 @@ enum {
     PS = 230,
     VECBASE = 231,
     EXCCAUSE = 232,
+    DEBUGCAUSE = 233,
     CCOUNT = 234,
     PRID = 235,
+    ICOUNT = 236,
+    ICOUNTLEVEL = 237,
     EXCVADDR = 238,
     CCOMPARE = 240,
 };
@@ -161,12 +168,27 @@ enum {
 
 #define PS_WOE 0x40000
 
+#define DEBUGCAUSE_IC 0x1
+#define DEBUGCAUSE_IB 0x2
+#define DEBUGCAUSE_DB 0x4
+#define DEBUGCAUSE_BI 0x8
+#define DEBUGCAUSE_BN 0x10
+#define DEBUGCAUSE_DI 0x20
+#define DEBUGCAUSE_DBNUM 0xf00
+#define DEBUGCAUSE_DBNUM_SHIFT 8
+
+#define DBREAKC_SB 0x80000000
+#define DBREAKC_LB 0x40000000
+#define DBREAKC_SB_LB (DBREAKC_SB | DBREAKC_LB)
+#define DBREAKC_MASK 0x3f
+
 #define MAX_NAREG 64
 #define MAX_NINTERRUPT 32
 #define MAX_NLEVEL 6
 #define MAX_NNMI 1
 #define MAX_NCCOMPARE 3
 #define MAX_TLB_WAY_SIZE 8
+#define MAX_NDBREAK 2
 
 #define REGION_PAGE_MASK 0xe0000000
 
@@ -186,6 +208,7 @@ enum {
     EXC_KERNEL,
     EXC_USER,
     EXC_DOUBLE,
+    EXC_DEBUG,
     EXC_MAX
 };
 
@@ -279,6 +302,11 @@ typedef struct XtensaConfig {
     uint32_t timerint[MAX_NCCOMPARE];
     unsigned nextint;
     unsigned extint[MAX_NINTERRUPT];
+
+    unsigned debug_level;
+    unsigned nibreak;
+    unsigned ndbreak;
+
     uint32_t clock_freq_khz;
 
     xtensa_tlb itlb;
@@ -310,6 +338,9 @@ typedef struct CPUXtensaState {
 
     int exception_taken;
 
+    /* Watchpoints for DBREAK registers */
+    CPUWatchpoint *cpu_watchpoint[MAX_NDBREAK];
+
     CPU_COMMON
 } CPUXtensaState;
 
@@ -344,6 +375,8 @@ void xtensa_tlb_set_entry(CPUState *env, bool dtlb,
 int xtensa_get_physical_addr(CPUState *env,
         uint32_t vaddr, int is_write, int mmu_idx,
         uint32_t *paddr, uint32_t *page_size, unsigned *access);
+void dump_mmu(FILE *f, fprintf_function cpu_fprintf, CPUState *env);
+void debug_exception_env(CPUState *new_env, uint32_t cause);
 
 
 #define XTENSA_OPTION_BIT(opt) (((uint64_t)1) << (opt))
@@ -409,6 +442,8 @@ static inline int cpu_mmu_index(CPUState *env)
 #define XTENSA_TBFLAG_RING_MASK 0x3
 #define XTENSA_TBFLAG_EXCM 0x4
 #define XTENSA_TBFLAG_LITBASE 0x8
+#define XTENSA_TBFLAG_DEBUG 0x10
+#define XTENSA_TBFLAG_ICOUNT 0x20
 
 static inline void cpu_get_tb_cpu_state(CPUState *env, target_ulong *pc,
         target_ulong *cs_base, int *flags)
@@ -424,6 +459,14 @@ static inline void cpu_get_tb_cpu_state(CPUState *env, target_ulong *pc,
             (env->sregs[LITBASE] & 1)) {
         *flags |= XTENSA_TBFLAG_LITBASE;
     }
+    if (xtensa_option_enabled(env->config, XTENSA_OPTION_DEBUG)) {
+        if (xtensa_get_cintlevel(env) < env->config->debug_level) {
+            *flags |= XTENSA_TBFLAG_DEBUG;
+        }
+        if (xtensa_get_cintlevel(env) < env->sregs[ICOUNTLEVEL]) {
+            *flags |= XTENSA_TBFLAG_ICOUNT;
+        }
+    }
 }
 
 #include "cpu-all.h"
diff --git a/target-xtensa/helper.c b/target-xtensa/helper.c
index 2a0cb1a562..2ef50d656e 100644
--- a/target-xtensa/helper.c
+++ b/target-xtensa/helper.c
@@ -44,6 +44,7 @@ void cpu_reset(CPUXtensaState *env)
     env->sregs[PS] = xtensa_option_enabled(env->config,
             XTENSA_OPTION_INTERRUPT) ? 0x1f : 0x10;
     env->sregs[VECBASE] = env->config->vecbase;
+    env->sregs[IBREAKENABLE] = 0;
 
     env->pending_irq_level = 0;
     reset_mmu(env);
@@ -57,9 +58,44 @@ void xtensa_register_core(XtensaConfigList *node)
     xtensa_cores = node;
 }
 
+static uint32_t check_hw_breakpoints(CPUState *env)
+{
+    unsigned i;
+
+    for (i = 0; i < env->config->ndbreak; ++i) {
+        if (env->cpu_watchpoint[i] &&
+                env->cpu_watchpoint[i]->flags & BP_WATCHPOINT_HIT) {
+            return DEBUGCAUSE_DB | (i << DEBUGCAUSE_DBNUM_SHIFT);
+        }
+    }
+    return 0;
+}
+
+static CPUDebugExcpHandler *prev_debug_excp_handler;
+
+static void breakpoint_handler(CPUState *env)
+{
+    if (env->watchpoint_hit) {
+        if (env->watchpoint_hit->flags & BP_CPU) {
+            uint32_t cause;
+
+            env->watchpoint_hit = NULL;
+            cause = check_hw_breakpoints(env);
+            if (cause) {
+                debug_exception_env(env, cause);
+            }
+            cpu_resume_from_signal(env, NULL);
+        }
+    }
+    if (prev_debug_excp_handler) {
+        prev_debug_excp_handler(env);
+    }
+}
+
 CPUXtensaState *cpu_xtensa_init(const char *cpu_model)
 {
     static int tcg_inited;
+    static int debug_handler_inited;
     CPUXtensaState *env;
     const XtensaConfig *config = NULL;
     XtensaConfigList *core = xtensa_cores;
@@ -83,6 +119,12 @@ CPUXtensaState *cpu_xtensa_init(const char *cpu_model)
         xtensa_translate_init();
     }
 
+    if (!debug_handler_inited && tcg_enabled()) {
+        debug_handler_inited = 1;
+        prev_debug_excp_handler =
+            cpu_set_debug_excp_handler(breakpoint_handler);
+    }
+
     xtensa_irq_init(env);
     qemu_init_vcpu(env);
     return env;
@@ -193,6 +235,7 @@ void do_interrupt(CPUState *env)
     case EXC_KERNEL:
     case EXC_USER:
     case EXC_DOUBLE:
+    case EXC_DEBUG:
         qemu_log_mask(CPU_LOG_INT, "%s(%d) "
                 "pc = %08x, a0 = %08x, ps = %08x, ccount = %08x\n",
                 __func__, env->exception_index,
@@ -540,3 +583,70 @@ int xtensa_get_physical_addr(CPUState *env,
         return 0;
     }
 }
+
+static void dump_tlb(FILE *f, fprintf_function cpu_fprintf,
+        CPUState *env, bool dtlb)
+{
+    unsigned wi, ei;
+    const xtensa_tlb *conf =
+        dtlb ? &env->config->dtlb : &env->config->itlb;
+    unsigned (*attr_to_access)(uint32_t) =
+        xtensa_option_enabled(env->config, XTENSA_OPTION_MMU) ?
+        mmu_attr_to_access : region_attr_to_access;
+
+    for (wi = 0; wi < conf->nways; ++wi) {
+        uint32_t sz = ~xtensa_tlb_get_addr_mask(env, dtlb, wi) + 1;
+        const char *sz_text;
+        bool print_header = true;
+
+        if (sz >= 0x100000) {
+            sz >>= 20;
+            sz_text = "MB";
+        } else {
+            sz >>= 10;
+            sz_text = "KB";
+        }
+
+        for (ei = 0; ei < conf->way_size[wi]; ++ei) {
+            const xtensa_tlb_entry *entry =
+                xtensa_tlb_get_entry(env, dtlb, wi, ei);
+
+            if (entry->asid) {
+                unsigned access = attr_to_access(entry->attr);
+
+                if (print_header) {
+                    print_header = false;
+                    cpu_fprintf(f, "Way %u (%d %s)\n", wi, sz, sz_text);
+                    cpu_fprintf(f,
+                            "\tVaddr       Paddr       ASID  Attr RWX\n"
+                            "\t----------  ----------  ----  ---- ---\n");
+                }
+                cpu_fprintf(f,
+                        "\t0x%08x  0x%08x  0x%02x  0x%02x %c%c%c\n",
+                        entry->vaddr,
+                        entry->paddr,
+                        entry->asid,
+                        entry->attr,
+                        (access & PAGE_READ) ? 'R' : '-',
+                        (access & PAGE_WRITE) ? 'W' : '-',
+                        (access & PAGE_EXEC) ? 'X' : '-');
+            }
+        }
+    }
+}
+
+void dump_mmu(FILE *f, fprintf_function cpu_fprintf, CPUState *env)
+{
+    if (xtensa_option_bits_enabled(env->config,
+                XTENSA_OPTION_BIT(XTENSA_OPTION_REGION_PROTECTION) |
+                XTENSA_OPTION_BIT(XTENSA_OPTION_REGION_TRANSLATION) |
+                XTENSA_OPTION_BIT(XTENSA_OPTION_MMU))) {
+
+        cpu_fprintf(f, "ITLB:\n");
+        dump_tlb(f, cpu_fprintf, env, false);
+        cpu_fprintf(f, "\nDTLB:\n");
+        dump_tlb(f, cpu_fprintf, env, true);
+    } else {
+        cpu_fprintf(f, "No TLB for this CPU core\n");
+    }
+}
diff --git a/target-xtensa/helpers.h b/target-xtensa/helpers.h
index 09ab3325c9..48a741e46d 100644
--- a/target-xtensa/helpers.h
+++ b/target-xtensa/helpers.h
@@ -3,6 +3,8 @@
 DEF_HELPER_1(exception, void, i32)
 DEF_HELPER_2(exception_cause, void, i32, i32)
 DEF_HELPER_3(exception_cause_vaddr, void, i32, i32, i32)
+DEF_HELPER_2(debug_exception, void, i32, i32)
+
 DEF_HELPER_1(nsa, i32, i32)
 DEF_HELPER_1(nsau, i32, i32)
 DEF_HELPER_1(wsr_windowbase, void, i32)
@@ -29,4 +31,9 @@ DEF_HELPER_2(itlb, void, i32, i32)
 DEF_HELPER_2(ptlb, i32, i32, i32)
 DEF_HELPER_3(wtlb, void, i32, i32, i32)
 
+DEF_HELPER_1(wsr_ibreakenable, void, i32)
+DEF_HELPER_2(wsr_ibreaka, void, i32, i32)
+DEF_HELPER_2(wsr_dbreaka, void, i32, i32)
+DEF_HELPER_2(wsr_dbreakc, void, i32, i32)
+
 #include "def-helper.h"
diff --git a/target-xtensa/op_helper.c b/target-xtensa/op_helper.c
index 0605611031..e184cf64f0 100644
--- a/target-xtensa/op_helper.c
+++ b/target-xtensa/op_helper.c
@@ -134,6 +134,27 @@ void HELPER(exception_cause_vaddr)(uint32_t pc, uint32_t cause, uint32_t vaddr)
     HELPER(exception_cause)(pc, cause);
 }
 
+void debug_exception_env(CPUState *new_env, uint32_t cause)
+{
+    if (xtensa_get_cintlevel(new_env) < new_env->config->debug_level) {
+        env = new_env;
+        HELPER(debug_exception)(env->pc, cause);
+    }
+}
+
+void HELPER(debug_exception)(uint32_t pc, uint32_t cause)
+{
+    unsigned level = env->config->debug_level;
+
+    env->pc = pc;
+    env->sregs[DEBUGCAUSE] = cause;
+    env->sregs[EPC1 + level - 1] = pc;
+    env->sregs[EPS2 + level - 2] = env->sregs[PS];
+    env->sregs[PS] = (env->sregs[PS] & ~PS_INTLEVEL) | PS_EXCM |
+        (level << PS_INTLEVEL_SHIFT);
+    HELPER(exception)(EXC_DEBUG);
+}
+
 uint32_t HELPER(nsa)(uint32_t v)
 {
     if (v & 0x80000000) {
@@ -662,3 +683,82 @@ void HELPER(wtlb)(uint32_t p, uint32_t v, uint32_t dtlb)
     split_tlb_entry_spec(v, dtlb, &vpn, &wi, &ei);
     xtensa_tlb_set_entry(env, dtlb, wi, ei, vpn, p);
 }
+
+
+void HELPER(wsr_ibreakenable)(uint32_t v)
+{
+    uint32_t change = v ^ env->sregs[IBREAKENABLE];
+    unsigned i;
+
+    for (i = 0; i < env->config->nibreak; ++i) {
+        if (change & (1 << i)) {
+            tb_invalidate_phys_page_range(
+                    env->sregs[IBREAKA + i], env->sregs[IBREAKA + i] + 1, 0);
+        }
+    }
+    env->sregs[IBREAKENABLE] = v & ((1 << env->config->nibreak) - 1);
+}
+
+void HELPER(wsr_ibreaka)(uint32_t i, uint32_t v)
+{
+    if (env->sregs[IBREAKENABLE] & (1 << i) && env->sregs[IBREAKA + i] != v) {
+        tb_invalidate_phys_page_range(
+                env->sregs[IBREAKA + i], env->sregs[IBREAKA + i] + 1, 0);
+        tb_invalidate_phys_page_range(v, v + 1, 0);
+    }
+    env->sregs[IBREAKA + i] = v;
+}
+
+static void set_dbreak(unsigned i, uint32_t dbreaka, uint32_t dbreakc)
+{
+    int flags = BP_CPU | BP_STOP_BEFORE_ACCESS;
+    uint32_t mask = dbreakc | ~DBREAKC_MASK;
+
+    if (env->cpu_watchpoint[i]) {
+        cpu_watchpoint_remove_by_ref(env, env->cpu_watchpoint[i]);
+    }
+    if (dbreakc & DBREAKC_SB) {
+        flags |= BP_MEM_WRITE;
+    }
+    if (dbreakc & DBREAKC_LB) {
+        flags |= BP_MEM_READ;
+    }
+    /* contiguous mask after inversion is one less than some power of 2 */
+    if ((~mask + 1) & ~mask) {
+        qemu_log("DBREAKC mask is not contiguous: 0x%08x\n", dbreakc);
+        /* cut mask after the first zero bit */
+        mask = 0xffffffff << (32 - clo32(mask));
+    }
+    if (cpu_watchpoint_insert(env, dbreaka & mask, ~mask + 1,
+            flags, &env->cpu_watchpoint[i])) {
+        env->cpu_watchpoint[i] = NULL;
+        qemu_log("Failed to set data breakpoint at 0x%08x/%d\n",
+                dbreaka & mask, ~mask + 1);
+    }
+}
+
+void HELPER(wsr_dbreaka)(uint32_t i, uint32_t v)
+{
+    uint32_t dbreakc = env->sregs[DBREAKC + i];
+
+    if ((dbreakc & DBREAKC_SB_LB) &&
+            env->sregs[DBREAKA + i] != v) {
+        set_dbreak(i, v, dbreakc);
+    }
+    env->sregs[DBREAKA + i] = v;
+}
+
+void HELPER(wsr_dbreakc)(uint32_t i, uint32_t v)
+{
+    if ((env->sregs[DBREAKC + i] ^ v) & (DBREAKC_SB_LB | DBREAKC_MASK)) {
+        if (v & DBREAKC_SB_LB) {
+            set_dbreak(i, env->sregs[DBREAKA + i], v);
+        } else {
+            if (env->cpu_watchpoint[i]) {
+                cpu_watchpoint_remove_by_ref(env, env->cpu_watchpoint[i]);
+                env->cpu_watchpoint[i] = NULL;
+            }
+        }
+    }
+    env->sregs[DBREAKC + i] = v;
+}
diff --git a/target-xtensa/overlay_tool.h b/target-xtensa/overlay_tool.h
index df19cc96ea..a3a5650fb0 100644
--- a/target-xtensa/overlay_tool.h
+++ b/target-xtensa/overlay_tool.h
@@ -114,6 +114,7 @@
         [EXC_KERNEL] = XCHAL_KERNEL_VECTOR_VADDR, \
         [EXC_USER] = XCHAL_USER_VECTOR_VADDR, \
         [EXC_DOUBLE] = XCHAL_DOUBLEEXC_VECTOR_VADDR, \
+        [EXC_DEBUG] = XCHAL_DEBUG_VECTOR_VADDR, \
     }
 
 #define INTERRUPT_VECTORS { \
@@ -251,6 +252,8 @@
     .nextint = XCHAL_NUM_EXTINTERRUPTS, \
     .extint = EXTINTS
 
+#if XCHAL_HAVE_PTP_MMU
+
 #define TLB_TEMPLATE(ways, refill_way_size, way56) { \
         .nways = ways, \
         .way_size = { \
@@ -268,11 +271,23 @@
 #define DTLB(varway56) \
     TLB_TEMPLATE(10, 1 << XCHAL_DTLB_ARF_ENTRIES_LOG2, varway56)
 
-#if XCHAL_HAVE_PTP_MMU
 #define TLB_SECTION \
     .itlb = ITLB(XCHAL_HAVE_SPANNING_WAY), \
     .dtlb = DTLB(XCHAL_HAVE_SPANNING_WAY)
-#else
+
+#elif XCHAL_HAVE_XLT_CACHEATTR || XCHAL_HAVE_MIMIC_CACHEATTR
+
+#define TLB_TEMPLATE { \
+        .nways = 1, \
+        .way_size = { \
+            8, \
+        } \
+    }
+
+#define TLB_SECTION \
+    .itlb = TLB_TEMPLATE, \
+    .dtlb = TLB_TEMPLATE
+
 #endif
 
 #if (defined(TARGET_WORDS_BIGENDIAN) != 0) == (XCHAL_HAVE_BE != 0)
@@ -288,6 +303,10 @@
 #define REGISTER_CORE(core)
 #endif
 
+#define DEBUG_SECTION \
+    .debug_level = XCHAL_DEBUGLEVEL, \
+    .nibreak = XCHAL_NUM_IBREAK, \
+    .ndbreak = XCHAL_NUM_DBREAK
 
 #if XCHAL_NUM_INTLEVELS + XCHAL_HAVE_NMI + 1 <= 2
 #define XCHAL_INTLEVEL2_VECTOR_VADDR 0
diff --git a/target-xtensa/translate.c b/target-xtensa/translate.c
index c81450d1a5..9e8e20a904 100644
--- a/target-xtensa/translate.c
+++ b/target-xtensa/translate.c
@@ -61,6 +61,10 @@ typedef struct DisasContext {
 
     uint32_t ccount_delta;
     unsigned used_window;
+
+    bool debug;
+    bool icount;
+    TCGv_i32 next_icount;
 } DisasContext;
 
 static TCGv_ptr cpu_env;
@@ -91,6 +95,13 @@ static const char * const sregnames[256] = {
     [RASID] = "RASID",
     [ITLBCFG] = "ITLBCFG",
     [DTLBCFG] = "DTLBCFG",
+    [IBREAKENABLE] = "IBREAKENABLE",
+    [IBREAKA] = "IBREAKA0",
+    [IBREAKA + 1] = "IBREAKA1",
+    [DBREAKA] = "DBREAKA0",
+    [DBREAKA + 1] = "DBREAKA1",
+    [DBREAKC] = "DBREAKC0",
+    [DBREAKC + 1] = "DBREAKC1",
     [EPC1] = "EPC1",
     [EPC1 + 1] = "EPC2",
     [EPC1 + 2] = "EPC3",
@@ -119,8 +130,11 @@ static const char * const sregnames[256] = {
     [PS] = "PS",
     [VECBASE] = "VECBASE",
     [EXCCAUSE] = "EXCCAUSE",
+    [DEBUGCAUSE] = "DEBUGCAUSE",
     [CCOUNT] = "CCOUNT",
     [PRID] = "PRID",
+    [ICOUNT] = "ICOUNT",
+    [ICOUNTLEVEL] = "ICOUNTLEVEL",
     [EXCVADDR] = "EXCVADDR",
     [CCOMPARE] = "CCOMPARE0",
     [CCOMPARE + 1] = "CCOMPARE1",
@@ -283,6 +297,19 @@ static void gen_exception_cause_vaddr(DisasContext *dc, uint32_t cause,
     tcg_temp_free(tcause);
 }
 
+static void gen_debug_exception(DisasContext *dc, uint32_t cause)
+{
+    TCGv_i32 tpc = tcg_const_i32(dc->pc);
+    TCGv_i32 tcause = tcg_const_i32(cause);
+    gen_advance_ccount(dc);
+    gen_helper_debug_exception(tpc, tcause);
+    tcg_temp_free(tpc);
+    tcg_temp_free(tcause);
+    if (cause & (DEBUGCAUSE_IB | DEBUGCAUSE_BI | DEBUGCAUSE_BN)) {
+        dc->is_jmp = DISAS_UPDATE;
+    }
+}
+
 static void gen_check_privilege(DisasContext *dc)
 {
     if (dc->cring) {
@@ -294,10 +321,13 @@ static void gen_check_privilege(DisasContext *dc)
 static void gen_jump_slot(DisasContext *dc, TCGv dest, int slot)
 {
     tcg_gen_mov_i32(cpu_pc, dest);
+    gen_advance_ccount(dc);
+    if (dc->icount) {
+        tcg_gen_mov_i32(cpu_SR[ICOUNT], dc->next_icount);
+    }
     if (dc->singlestep_enabled) {
         gen_exception(dc, EXCP_DEBUG);
     } else {
-        gen_advance_ccount(dc);
         if (slot >= 0) {
             tcg_gen_goto_tb(slot);
             tcg_gen_exit_tb((tcg_target_long)dc->tb + slot);
@@ -492,6 +522,46 @@ static void gen_wsr_tlbcfg(DisasContext *dc, uint32_t sr, TCGv_i32 v)
     tcg_gen_andi_i32(cpu_SR[sr], v, 0x01130000);
 }
 
+static void gen_wsr_ibreakenable(DisasContext *dc, uint32_t sr, TCGv_i32 v)
+{
+    gen_helper_wsr_ibreakenable(v);
+    gen_jumpi_check_loop_end(dc, 0);
+}
+
+static void gen_wsr_ibreaka(DisasContext *dc, uint32_t sr, TCGv_i32 v)
+{
+    unsigned id = sr - IBREAKA;
+
+    if (id < dc->config->nibreak) {
+        TCGv_i32 tmp = tcg_const_i32(id);
+        gen_helper_wsr_ibreaka(tmp, v);
+        tcg_temp_free(tmp);
+        gen_jumpi_check_loop_end(dc, 0);
+    }
+}
+
+static void gen_wsr_dbreaka(DisasContext *dc, uint32_t sr, TCGv_i32 v)
+{
+    unsigned id = sr - DBREAKA;
+
+    if (id < dc->config->ndbreak) {
+        TCGv_i32 tmp = tcg_const_i32(id);
+        gen_helper_wsr_dbreaka(tmp, v);
+        tcg_temp_free(tmp);
+    }
+}
+
+static void gen_wsr_dbreakc(DisasContext *dc, uint32_t sr, TCGv_i32 v)
+{
+    unsigned id = sr - DBREAKC;
+
+    if (id < dc->config->ndbreak) {
+        TCGv_i32 tmp = tcg_const_i32(id);
+        gen_helper_wsr_dbreakc(tmp, v);
+        tcg_temp_free(tmp);
+    }
+}
+
 static void gen_wsr_intset(DisasContext *dc, uint32_t sr, TCGv_i32 v)
 {
     tcg_gen_andi_i32(cpu_SR[sr], v,
@@ -535,10 +605,30 @@ static void gen_wsr_ps(DisasContext *dc, uint32_t sr, TCGv_i32 v)
     gen_jumpi_check_loop_end(dc, -1);
 }
 
+static void gen_wsr_debugcause(DisasContext *dc, uint32_t sr, TCGv_i32 v)
+{
+}
+
 static void gen_wsr_prid(DisasContext *dc, uint32_t sr, TCGv_i32 v)
 {
 }
 
+static void gen_wsr_icount(DisasContext *dc, uint32_t sr, TCGv_i32 v)
+{
+    if (dc->icount) {
+        tcg_gen_mov_i32(dc->next_icount, v);
+    } else {
+        tcg_gen_mov_i32(cpu_SR[sr], v);
+    }
+}
+
+static void gen_wsr_icountlevel(DisasContext *dc, uint32_t sr, TCGv_i32 v)
+{
+    tcg_gen_andi_i32(cpu_SR[sr], v, 0xf);
+    /* This can change tb->flags, so exit tb */
+    gen_jumpi_check_loop_end(dc, -1);
+}
+
 static void gen_wsr_ccompare(DisasContext *dc, uint32_t sr, TCGv_i32 v)
 {
     uint32_t id = sr - CCOMPARE;
@@ -567,11 +657,21 @@ static void gen_wsr(DisasContext *dc, uint32_t sr, TCGv_i32 s)
         [RASID] = gen_wsr_rasid,
         [ITLBCFG] = gen_wsr_tlbcfg,
         [DTLBCFG] = gen_wsr_tlbcfg,
+        [IBREAKENABLE] = gen_wsr_ibreakenable,
+        [IBREAKA] = gen_wsr_ibreaka,
+        [IBREAKA + 1] = gen_wsr_ibreaka,
+        [DBREAKA] = gen_wsr_dbreaka,
+        [DBREAKA + 1] = gen_wsr_dbreaka,
+        [DBREAKC] = gen_wsr_dbreakc,
+        [DBREAKC + 1] = gen_wsr_dbreakc,
         [INTSET] = gen_wsr_intset,
         [INTCLEAR] = gen_wsr_intclear,
         [INTENABLE] = gen_wsr_intenable,
         [PS] = gen_wsr_ps,
+        [DEBUGCAUSE] = gen_wsr_debugcause,
         [PRID] = gen_wsr_prid,
+        [ICOUNT] = gen_wsr_icount,
+        [ICOUNTLEVEL] = gen_wsr_icountlevel,
         [CCOMPARE] = gen_wsr_ccompare,
         [CCOMPARE + 1] = gen_wsr_ccompare,
         [CCOMPARE + 2] = gen_wsr_ccompare,
@@ -749,7 +849,7 @@ static void disas_xtensa_insn(DisasContext *dc)
 
     uint8_t b0 = ldub_code(dc->pc);
     uint8_t b1 = ldub_code(dc->pc + 1);
-    uint8_t b2 = ldub_code(dc->pc + 2);
+    uint8_t b2 = 0;
 
     static const uint32_t B4CONST[] = {
         0xffffffff, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16, 32, 64, 128, 256
@@ -764,6 +864,7 @@ static void disas_xtensa_insn(DisasContext *dc)
         HAS_OPTION(XTENSA_OPTION_CODE_DENSITY);
     } else {
         dc->next_pc = dc->pc + 3;
+        b2 = ldub_code(dc->pc + 2);
     }
 
     switch (OP0) {
@@ -968,8 +1069,10 @@ static void disas_xtensa_insn(DisasContext *dc)
                     break;
 
                 case 4: /*BREAKx*/
-                    HAS_OPTION(XTENSA_OPTION_EXCEPTION);
-                    TBD();
+                    HAS_OPTION(XTENSA_OPTION_DEBUG);
+                    if (dc->debug) {
+                        gen_debug_exception(dc, DEBUGCAUSE_BI);
+                    }
                     break;
 
                 case 5: /*SYSCALLx*/
@@ -2349,7 +2452,10 @@ static void disas_xtensa_insn(DisasContext *dc)
                 break;
 
             case 2: /*BREAK.Nn*/
-                TBD();
+                HAS_OPTION(XTENSA_OPTION_DEBUG);
+                if (dc->debug) {
+                    gen_debug_exception(dc, DEBUGCAUSE_BN);
+                }
                 break;
 
             case 3: /*NOP.Nn*/
@@ -2402,6 +2508,19 @@ static void check_breakpoint(CPUState *env, DisasContext *dc)
     }
 }
 
+static void gen_ibreak_check(CPUState *env, DisasContext *dc)
+{
+    unsigned i;
+
+    for (i = 0; i < dc->config->nibreak; ++i) {
+        if ((env->sregs[IBREAKENABLE] & (1 << i)) &&
+                env->sregs[IBREAKA + i] == dc->pc) {
+            gen_debug_exception(dc, DEBUGCAUSE_IB);
+            break;
+        }
+    }
+}
+
 static void gen_intermediate_code_internal(
         CPUState *env, TranslationBlock *tb, int search_pc)
 {
@@ -2428,10 +2547,15 @@ static void gen_intermediate_code_internal(
     dc.lend = env->sregs[LEND];
     dc.is_jmp = DISAS_NEXT;
     dc.ccount_delta = 0;
+    dc.debug = tb->flags & XTENSA_TBFLAG_DEBUG;
+    dc.icount = tb->flags & XTENSA_TBFLAG_ICOUNT;
 
     init_litbase(&dc);
     init_sar_tracker(&dc);
     reset_used_window(&dc);
+    if (dc.icount) {
+        dc.next_icount = tcg_temp_local_new_i32();
+    }
 
     gen_icount_start();
 
@@ -2467,8 +2591,27 @@ static void gen_intermediate_code_internal(
             gen_io_start();
         }
 
+        if (dc.icount) {
+            int label = gen_new_label();
+
+            tcg_gen_addi_i32(dc.next_icount, cpu_SR[ICOUNT], 1);
+            tcg_gen_brcondi_i32(TCG_COND_NE, dc.next_icount, 0, label);
+            tcg_gen_mov_i32(dc.next_icount, cpu_SR[ICOUNT]);
+            if (dc.debug) {
+                gen_debug_exception(&dc, DEBUGCAUSE_IC);
+            }
+            gen_set_label(label);
+        }
+
+        if (dc.debug) {
+            gen_ibreak_check(env, &dc);
+        }
+
         disas_xtensa_insn(&dc);
         ++insn_count;
+        if (dc.icount) {
+            tcg_gen_mov_i32(cpu_SR[ICOUNT], dc.next_icount);
+        }
         if (env->singlestep_enabled) {
             tcg_gen_movi_i32(cpu_pc, dc.pc);
             gen_exception(&dc, EXCP_DEBUG);
@@ -2481,6 +2624,9 @@ static void gen_intermediate_code_internal(
 
     reset_litbase(&dc);
     reset_sar_tracker(&dc);
+    if (dc.icount) {
+        tcg_temp_free(dc.next_icount);
+    }
 
     if (tb->cflags & CF_LAST_IO) {
         gen_io_end();
diff --git a/tests/tcg/xtensa/Makefile b/tests/tcg/xtensa/Makefile
index 8713af16eb..7e1e619d23 100644
--- a/tests/tcg/xtensa/Makefile
+++ b/tests/tcg/xtensa/Makefile
@@ -23,6 +23,7 @@ CRT        = crt.o vectors.o
 TESTCASES += test_b.tst
 TESTCASES += test_bi.tst
 #TESTCASES += test_boolean.tst
+TESTCASES += test_break.tst
 TESTCASES += test_bz.tst
 TESTCASES += test_clamps.tst
 TESTCASES += test_fail.tst
diff --git a/tests/tcg/xtensa/test_break.S b/tests/tcg/xtensa/test_break.S
new file mode 100644
index 0000000000..8a8db8033b
--- /dev/null
+++ b/tests/tcg/xtensa/test_break.S
@@ -0,0 +1,223 @@
+.include "macros.inc"
+
+#define debug_level 6
+#define debug_vector level6
+
+test_suite break
+
+test break
+    set_vector debug_vector, 0
+    rsil    a2, debug_level
+    _break  0, 0
+
+    set_vector debug_vector, 2f
+    rsil    a2, debug_level - 1
+1:
+    _break  0, 0
+    test_fail
+2:
+    rsr     a2, ps
+    movi    a3, 0x1f
+    and     a2, a2, a3
+    movi    a3, 0x10 | debug_level
+    assert  eq, a2, a3
+    rsr     a2, epc6
+    movi    a3, 1b
+    assert  eq, a2, a3
+    rsr     a2, debugcause
+    movi    a3, 0x8
+    assert  eq, a2, a3
+test_end
+
+test breakn
+    set_vector debug_vector, 0
+    rsil    a2, debug_level
+    _break.n  0
+
+    set_vector debug_vector, 2f
+    rsil    a2, debug_level - 1
+1:
+    _break.n  0
+    test_fail
+2:
+    rsr     a2, ps
+    movi    a3, 0x1f
+    and     a2, a2, a3
+    movi    a3, 0x10 | debug_level
+    assert  eq, a2, a3
+    rsr     a2, epc6
+    movi    a3, 1b
+    assert  eq, a2, a3
+    rsr     a2, debugcause
+    movi    a3, 0x10
+    assert  eq, a2, a3
+test_end
+
+test ibreak
+    set_vector debug_vector, 0
+    rsil    a2, debug_level
+    movi    a2, 1f
+    wsr     a2, ibreaka0
+    movi    a2, 1
+    wsr     a2, ibreakenable
+    isync
+1:
+    rsil    a2, debug_level - 1
+    movi    a2, 1f
+    wsr     a2, ibreaka0
+    movi    a2, 0
+    wsr     a2, ibreakenable
+    isync
+1:
+    set_vector debug_vector, 2f
+    movi    a2, 1f
+    wsr     a2, ibreaka0
+    movi    a2, 1
+    wsr     a2, ibreakenable
+    isync
+1:
+    test_fail
+2:
+    rsr     a2, ps
+    movi    a3, 0x1f
+    and     a2, a2, a3
+    movi    a3, 0x10 | debug_level
+    assert  eq, a2, a3
+    rsr     a2, epc6
+    movi    a3, 1b
+    assert  eq, a2, a3
+    rsr     a2, debugcause
+    movi    a3, 0x2
+    assert  eq, a2, a3
+test_end
+
+test ibreak_priority
+    set_vector debug_vector, 2f
+    rsil    a2, debug_level - 1
+    movi    a2, 1f
+    wsr     a2, ibreaka0
+    movi    a2, 1
+    wsr     a2, ibreakenable
+    isync
+1:
+    break   0, 0
+    test_fail
+2:
+    rsr     a2, debugcause
+    movi    a3, 0x2
+    assert  eq, a2, a3
+test_end
+
+test icount
+    set_vector debug_vector, 2f
+    rsil    a2, debug_level - 1
+    movi    a2, -2
+    wsr     a2, icount
+    movi    a2, 1
+    wsr     a2, icountlevel
+    isync
+    rsil    a2, 0
+    nop
+1:
+    break   0, 0
+    test_fail
+2:
+    movi    a2, 0
+    wsr     a2, icountlevel
+    rsr     a2, epc6
+    movi    a3, 1b
+    assert  eq, a2, a3
+    rsr     a2, debugcause
+    movi    a3, 0x1
+    assert  eq, a2, a3
+test_end
+
+.macro check_dbreak dr
+    rsr     a2, epc6
+    movi    a3, 1b
+    assert  eq, a2, a3
+    rsr     a2, debugcause
+    movi    a3, 0x4 | (\dr << 8)
+    assert  eq, a2, a3
+    movi    a2, 0
+    wsr     a2, dbreakc\dr
+.endm
+
+.macro dbreak_test dr, ctl, break, access, op
+    set_vector debug_vector, 2f
+    rsil    a2, debug_level - 1
+    movi    a2, \ctl
+    wsr     a2, dbreakc\dr
+    movi    a2, \break
+    wsr     a2, dbreaka\dr
+    movi    a2, \access
+    isync
+1:
+    \op     a3, a2, 0
+    test_fail
+2:
+    check_dbreak \dr
+    reset_ps
+.endm
+
+test dbreak_exact
+    dbreak_test 0, 0x4000003f, 0xd000007f, 0xd000007f, l8ui
+    dbreak_test 1, 0x4000003e, 0xd000007e, 0xd000007e, l16ui
+    dbreak_test 0, 0x4000003c, 0xd000007c, 0xd000007c, l32i
+
+    dbreak_test 1, 0x8000003f, 0xd000007f, 0xd000007f, s8i
+    dbreak_test 0, 0x8000003e, 0xd000007e, 0xd000007e, s16i
+    dbreak_test 1, 0x8000003c, 0xd000007c, 0xd000007c, s32i
+test_end
+
+test dbreak_overlap
+    dbreak_test 0, 0x4000003f, 0xd000007d, 0xd000007c, l16ui
+    dbreak_test 1, 0x4000003f, 0xd000007d, 0xd000007c, l32i
+
+    dbreak_test 0, 0x4000003e, 0xd000007e, 0xd000007f, l8ui
+    dbreak_test 1, 0x4000003e, 0xd000007e, 0xd000007c, l32i
+
+    dbreak_test 0, 0x4000003c, 0xd000007c, 0xd000007d, l8ui
+    dbreak_test 1, 0x4000003c, 0xd000007c, 0xd000007c, l16ui
+
+    dbreak_test 0, 0x40000038, 0xd0000078, 0xd000007b, l8ui
+    dbreak_test 1, 0x40000038, 0xd0000078, 0xd000007a, l16ui
+    dbreak_test 0, 0x40000038, 0xd0000078, 0xd000007c, l32i
+
+    dbreak_test 1, 0x40000030, 0xd0000070, 0xd0000075, l8ui
+    dbreak_test 0, 0x40000030, 0xd0000070, 0xd0000076, l16ui
+    dbreak_test 1, 0x40000030, 0xd0000070, 0xd0000078, l32i
+
+    dbreak_test 0, 0x40000020, 0xd0000060, 0xd000006f, l8ui
+    dbreak_test 1, 0x40000020, 0xd0000060, 0xd0000070, l16ui
+    dbreak_test 0, 0x40000020, 0xd0000060, 0xd0000074, l32i
+
+
+    dbreak_test 0, 0x8000003f, 0xd000007d, 0xd000007c, s16i
+    dbreak_test 1, 0x8000003f, 0xd000007d, 0xd000007c, s32i
+
+    dbreak_test 0, 0x8000003e, 0xd000007e, 0xd000007f, s8i
+    dbreak_test 1, 0x8000003e, 0xd000007e, 0xd000007c, s32i
+
+    dbreak_test 0, 0x8000003c, 0xd000007c, 0xd000007d, s8i
+    dbreak_test 1, 0x8000003c, 0xd000007c, 0xd000007c, s16i
+
+    dbreak_test 0, 0x80000038, 0xd0000078, 0xd000007b, s8i
+    dbreak_test 1, 0x80000038, 0xd0000078, 0xd000007a, s16i
+    dbreak_test 0, 0x80000038, 0xd0000078, 0xd000007c, s32i
+
+    dbreak_test 1, 0x80000030, 0xd0000070, 0xd0000075, s8i
+    dbreak_test 0, 0x80000030, 0xd0000070, 0xd0000076, s16i
+    dbreak_test 1, 0x80000030, 0xd0000070, 0xd0000078, s32i
+
+    dbreak_test 0, 0x80000020, 0xd0000060, 0xd000006f, s8i
+    dbreak_test 1, 0x80000020, 0xd0000060, 0xd0000070, s16i
+    dbreak_test 0, 0x80000020, 0xd0000060, 0xd0000074, s32i
+test_end
+
+test dbreak_invalid
+    dbreak_test 0, 0x40000030, 0xd0000071, 0xd0000070, l16ui
+    dbreak_test 1, 0x40000035, 0xd0000072, 0xd0000070, l32i
+test_end
+
+test_suite_end