target: increase chunk size in dump_image
[fw/openocd] / src / target / arm_dpm.c
index 406e30a289f5f4a4c995e60b64a676ef1eadd476..9f7af4e79ae28027f0ed9f0351e76bb5a223a542 100644 (file)
@@ -21,7 +21,7 @@
 #include "config.h"
 #endif
 
-#include "armv4_5.h"           /* REVISIT to become arm.h */
+#include "arm.h"
 #include "arm_dpm.h"
 #include <jtag/jtag.h>
 #include "register.h"
  * Implements various ARM DPM operations using architectural debug registers.
  * These routines layer over core-specific communication methods to cope with
  * implementation differences between cores like ARM1136 and Cortex-A8.
+ *
+ * The "Debug Programmers' Model" (DPM) for ARMv6 and ARMv7 is defined by
+ * Part C (Debug Architecture) of the ARM Architecture Reference Manual,
+ * ARMv7-A and ARMv7-R edition (ARM DDI 0406B).  In OpenOCD, DPM operations
+ * are abstracted through internal programming interfaces to share code and
+ * to minimize needless differences in debug behavior between cores.
  */
 
 /*----------------------------------------------------------------------*/
@@ -103,7 +109,7 @@ static int dpm_mcr(struct target *target, int cpnum,
 /* Toggles between recorded core mode (USR, SVC, etc) and a temporary one.
  * Routines *must* restore the original mode before returning!!
  */
-static int dpm_modeswitch(struct arm_dpm *dpm, enum arm_mode mode)
+int dpm_modeswitch(struct arm_dpm *dpm, enum arm_mode mode)
 {
        int retval;
        uint32_t cpsr;
@@ -117,6 +123,8 @@ static int dpm_modeswitch(struct arm_dpm *dpm, enum arm_mode mode)
                cpsr = mode;
 
        retval = dpm->instr_write_data_r0(dpm, ARMV4_5_MSR_GP(0, 0xf, 0), cpsr);
+       if (retval != ERROR_OK)
+               return retval;
 
        if (dpm->instr_cpsr_sync)
                retval = dpm->instr_cpsr_sync(dpm);
@@ -205,6 +213,8 @@ static int dpm_write_reg(struct arm_dpm *dpm, struct reg *r, unsigned regnum)
                retval = dpm->instr_write_data_r0(dpm,
                                ARMV4_5_MSR_GP(0, 0xf, regnum & 1),
                                value);
+               if (retval != ERROR_OK)
+                       return retval;
 
                if (regnum == 16 && dpm->instr_cpsr_sync)
                        retval = dpm->instr_cpsr_sync(dpm);
@@ -277,6 +287,54 @@ fail:
        return retval;
 }
 
+/* Avoid needless I/O ... leave breakpoints and watchpoints alone
+ * unless they're removed, or need updating because of single-stepping
+ * or running debugger code.
+ */
+static int dpm_maybe_update_bpwp(struct arm_dpm *dpm, bool bpwp,
+               struct dpm_bpwp *xp, int *set_p)
+{
+       int retval = ERROR_OK;
+       bool disable;
+
+       if (!set_p) {
+               if (!xp->dirty)
+                       goto done;
+               xp->dirty = false;
+               /* removed or startup; we must disable it */
+               disable = true;
+       } else if (bpwp) {
+               if (!xp->dirty)
+                       goto done;
+               /* disabled, but we must set it */
+               xp->dirty = disable = false;
+               *set_p = true;
+       } else {
+               if (!*set_p)
+                       goto done;
+               /* set, but we must temporarily disable it */
+               xp->dirty = disable = true;
+               *set_p = false;
+       }
+
+       if (disable)
+               retval = dpm->bpwp_disable(dpm, xp->number);
+       else
+               retval = dpm->bpwp_enable(dpm, xp->number,
+                               xp->address, xp->control);
+
+       if (retval != ERROR_OK)
+               LOG_ERROR("%s: can't %s HW %spoint %d",
+                               disable ? "disable" : "enable",
+                               target_name(dpm->arm->target),
+                               (xp->number < 16) ? "break" : "watch",
+                               xp->number & 0xf);
+done:
+       return retval;
+}
+
+static int dpm_add_breakpoint(struct target *target, struct breakpoint *bp);
+
 /**
  * Writes all modified core registers for all processor modes.  In normal
  * operation this is called on exit from halting debug state.
@@ -296,47 +354,34 @@ int arm_dpm_write_dirty_registers(struct arm_dpm *dpm, bool bpwp)
        if (retval != ERROR_OK)
                goto done;
 
+       /* If we're managing hardware breakpoints for this core, enable
+        * or disable them as requested.
+        *
+        * REVISIT We don't yet manage them for ANY cores.  Eventually
+        * we should be able to assume we handle them; but until then,
+        * cope with the hand-crafted breakpoint code.
+        */
+       if (arm->target->type->add_breakpoint == dpm_add_breakpoint) {
+               for (unsigned i = 0; i < dpm->nbp; i++) {
+                       struct dpm_bp *dbp = dpm->dbp + i;
+                       struct breakpoint *bp = dbp->bp;
+
+                       retval = dpm_maybe_update_bpwp(dpm, bpwp, &dbp->bpwp,
+                                       bp ? &bp->set : NULL);
+                       if (retval != ERROR_OK)
+                               goto done;
+               }
+       }
+
        /* enable/disable watchpoints */
        for (unsigned i = 0; i < dpm->nwp; i++) {
                struct dpm_wp *dwp = dpm->dwp + i;
                struct watchpoint *wp = dwp->wp;
-               bool disable;
-
-               /* Avoid needless I/O ... leave watchpoints alone
-                * unless they're removed, or need updating because
-                * of single-stepping or running debugger code.
-                */
-               if (!wp) {
-                       if (!dwp->dirty)
-                               continue;
-                       dwp->dirty = false;
-                       /* removed or startup; we must disable it */
-                       disable = true;
-               } else if (bpwp) {
-                       if (!dwp->dirty)
-                               continue;
-                       /* disabled, but we must set it */
-                       dwp->dirty = disable = false;
-                       wp->set = true;
-               } else {
-                       if (!wp->set)
-                               continue;
-                       /* set, but we must temporarily disable it */
-                       dwp->dirty = disable = true;
-                       wp->set = false;
-               }
-
-               if (disable)
-                       retval = dpm->bpwp_disable(dpm, 16 + i);
-               else
-                       retval = dpm->bpwp_enable(dpm, 16 + i,
-                                       wp->address & ~3, dwp->control);
 
+               retval = dpm_maybe_update_bpwp(dpm, bpwp, &dwp->bpwp,
+                               wp ? &wp->set : NULL);
                if (retval != ERROR_OK)
-                       LOG_ERROR("%s: can't %s HW watchpoint %d",
-                                       target_name(arm->target),
-                                       disable ? "disable" : "enable",
-                                       i);
+                       goto done;
        }
 
        /* NOTE:  writes to breakpoint and watchpoint registers might
@@ -396,7 +441,11 @@ int arm_dpm_write_dirty_registers(struct arm_dpm *dpm, bool bpwp)
 
                                /* REVISIT error checks */
                                if (tmode != ARM_MODE_ANY)
+                               {
                                        retval = dpm_modeswitch(dpm, tmode);
+                                       if (retval != ERROR_OK)
+                                               goto done;
+                               }
                        }
                        if (r->mode != mode)
                                continue;
@@ -404,7 +453,8 @@ int arm_dpm_write_dirty_registers(struct arm_dpm *dpm, bool bpwp)
                        retval = dpm_write_reg(dpm,
                                        &cache->reg_list[i],
                                        regnum);
-
+                       if (retval != ERROR_OK)
+                               goto done;
                }
 
        } while (did_write);
@@ -414,13 +464,19 @@ int arm_dpm_write_dirty_registers(struct arm_dpm *dpm, bool bpwp)
         * defined, and must not write it before CPSR.
         */
        retval = dpm_modeswitch(dpm, ARM_MODE_ANY);
+       if (retval != ERROR_OK)
+               goto done;
        arm->cpsr->dirty = false;
 
-       retval = dpm_write_reg(dpm, &cache->reg_list[15], 15);
-       cache->reg_list[15].dirty = false;
+       retval = dpm_write_reg(dpm, arm->pc, 15);
+       if (retval != ERROR_OK)
+               goto done;
+       arm->pc->dirty = false;
 
        /* flush R0 -- it's *very* dirty by now */
        retval = dpm_write_reg(dpm, &cache->reg_list[0], 0);
+       if (retval != ERROR_OK)
+               goto done;
        cache->reg_list[0].dirty = false;
 
        /* (void) */ dpm->finish(dpm);
@@ -480,7 +536,7 @@ static int arm_dpm_read_core_reg(struct target *target, struct reg *r,
        int retval;
 
        if (regnum < 0 || regnum > 16)
-               return ERROR_INVALID_ARGUMENTS;
+               return ERROR_COMMAND_SYNTAX_ERROR;
 
        if (regnum == 16) {
                if (mode != ARM_MODE_ANY)
@@ -503,6 +559,8 @@ static int arm_dpm_read_core_reg(struct target *target, struct reg *r,
        }
 
        retval = dpm_read_reg(dpm, r, regnum);
+       if (retval != ERROR_OK)
+               goto fail;
        /* always clean up, regardless of error */
 
        if (mode != ARM_MODE_ANY)
@@ -521,7 +579,7 @@ static int arm_dpm_write_core_reg(struct target *target, struct reg *r,
 
 
        if (regnum < 0 || regnum > 16)
-               return ERROR_INVALID_ARGUMENTS;
+               return ERROR_COMMAND_SYNTAX_ERROR;
 
        if (regnum == 16) {
                if (mode != ARM_MODE_ANY)
@@ -599,6 +657,8 @@ static int arm_dpm_full_context(struct target *target)
 
                                /* REVISIT error checks */
                                retval = dpm_modeswitch(dpm, mode);
+                               if (retval != ERROR_OK)
+                                       goto done;
                        }
                        if (r->mode != mode)
                                continue;
@@ -607,7 +667,8 @@ static int arm_dpm_full_context(struct target *target)
                        retval = dpm_read_reg(dpm,
                                        &cache->reg_list[i],
                                        (r->num == 16) ? 17 : r->num);
-
+                       if (retval != ERROR_OK)
+                               goto done;
                }
 
        } while (did_read);
@@ -631,43 +692,26 @@ done:
  * fact isn't currently leveraged.
  */
 
-static int dpm_watchpoint_setup(struct arm_dpm *dpm, unsigned index,
-               struct watchpoint *wp)
+static int dpm_bpwp_setup(struct arm_dpm *dpm, struct dpm_bpwp *xp,
+               uint32_t addr, uint32_t length)
 {
-       uint32_t addr = wp->address;
        uint32_t control;
 
-       /* this hardware doesn't support data value matching or masking */
-       if (wp->value || wp->mask != ~(uint32_t)0) {
-               LOG_DEBUG("watchpoint values and masking not supported");
-               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
-       }
-
        control = (1 << 0)      /* enable */
                | (3 << 1);     /* both user and privileged access */
 
-       switch (wp->rw) {
-       case WPT_READ:
-               control |= 1 << 3;
-               break;
-       case WPT_WRITE:
-               control |= 2 << 3;
-               break;
-       case WPT_ACCESS:
-               control |= 3 << 3;
-               break;
-       }
-
        /* Match 1, 2, or all 4 byte addresses in this word.
         *
-        * FIXME:  v7 hardware allows lengths up to 2 GB, and has eight
-        * byte address select bits.  Support larger wp->length, if addr
-        * is suitably aligned.
+        * FIXME:  v7 hardware allows lengths up to 2 GB for BP and WP.
+        * Support larger length, when addr is suitably aligned.  In
+        * particular, allow watchpoints on 8 byte "double" values.
+        *
+        * REVISIT allow watchpoints on unaligned 2-bit values; and on
+        * v7 hardware, unaligned 4-byte ones too.
         */
-       switch (wp->length) {
+       switch (length) {
        case 1:
                control |= (1 << (addr & 3)) << 5;
-               addr &= ~3;
                break;
        case 2:
                /* require 2-byte alignment */
@@ -684,25 +728,110 @@ static int dpm_watchpoint_setup(struct arm_dpm *dpm, unsigned index,
                }
                /* FALL THROUGH */
        default:
-               LOG_DEBUG("bad watchpoint length or alignment");
-               return ERROR_INVALID_ARGUMENTS;
+               LOG_ERROR("unsupported {break,watch}point length/alignment");
+               return ERROR_COMMAND_SYNTAX_ERROR;
        }
 
-       /* other control bits:
-        * bits 9:12 == 0 ... only checking up to four byte addresses (v7 only)
+       /* other shared control bits:
         * bits 15:14 == 0 ... both secure and nonsecure states (v6.1+ only)
         * bit 20 == 0 ... not linked to a context ID
         * bit 28:24 == 0 ... not ignoring N LSBs (v7 only)
         */
 
-       dpm->dwp[index].wp = wp;
-       dpm->dwp[index].control = control;
-       dpm->dwp[index].dirty = true;
+       xp->address = addr & ~3;
+       xp->control = control;
+       xp->dirty = true;
+
+       LOG_DEBUG("BPWP: addr %8.8" PRIx32 ", control %" PRIx32 ", number %d",
+                       xp->address, control, xp->number);
 
        /* hardware is updated in write_dirty_registers() */
        return ERROR_OK;
 }
 
+static int dpm_add_breakpoint(struct target *target, struct breakpoint *bp)
+{
+       struct arm *arm = target_to_arm(target);
+       struct arm_dpm *dpm = arm->dpm;
+       int retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+
+       if (bp->length < 2)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       if (!dpm->bpwp_enable)
+               return retval;
+
+       /* FIXME we need a generic solution for software breakpoints. */
+       if (bp->type == BKPT_SOFT)
+               LOG_DEBUG("using HW bkpt, not SW...");
+
+       for (unsigned i = 0; i < dpm->nbp; i++) {
+               if (!dpm->dbp[i].bp) {
+                       retval = dpm_bpwp_setup(dpm, &dpm->dbp[i].bpwp,
+                                       bp->address, bp->length);
+                       if (retval == ERROR_OK)
+                               dpm->dbp[i].bp = bp;
+                       break;
+               }
+       }
+
+       return retval;
+}
+
+static int dpm_remove_breakpoint(struct target *target, struct breakpoint *bp)
+{
+       struct arm *arm = target_to_arm(target);
+       struct arm_dpm *dpm = arm->dpm;
+       int retval = ERROR_COMMAND_SYNTAX_ERROR;
+
+       for (unsigned i = 0; i < dpm->nbp; i++) {
+               if (dpm->dbp[i].bp == bp) {
+                       dpm->dbp[i].bp = NULL;
+                       dpm->dbp[i].bpwp.dirty = true;
+
+                       /* hardware is updated in write_dirty_registers() */
+                       retval = ERROR_OK;
+                       break;
+               }
+       }
+
+       return retval;
+}
+
+static int dpm_watchpoint_setup(struct arm_dpm *dpm, unsigned index_t,
+               struct watchpoint *wp)
+{
+       int retval;
+       struct dpm_wp *dwp = dpm->dwp + index_t;
+       uint32_t control;
+
+       /* this hardware doesn't support data value matching or masking */
+       if (wp->value || wp->mask != ~(uint32_t)0) {
+               LOG_DEBUG("watchpoint values and masking not supported");
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       retval = dpm_bpwp_setup(dpm, &dwp->bpwp, wp->address, wp->length);
+       if (retval != ERROR_OK)
+               return retval;
+
+       control = dwp->bpwp.control;
+       switch (wp->rw) {
+       case WPT_READ:
+               control |= 1 << 3;
+               break;
+       case WPT_WRITE:
+               control |= 2 << 3;
+               break;
+       case WPT_ACCESS:
+               control |= 3 << 3;
+               break;
+       }
+       dwp->bpwp.control = control;
+
+       dpm->dwp[index_t].wp = wp;
+
+       return retval;
+}
 
 static int dpm_add_watchpoint(struct target *target, struct watchpoint *wp)
 {
@@ -726,12 +855,12 @@ static int dpm_remove_watchpoint(struct target *target, struct watchpoint *wp)
 {
        struct arm *arm = target_to_arm(target);
        struct arm_dpm *dpm = arm->dpm;
-       int retval = ERROR_INVALID_ARGUMENTS;
+       int retval = ERROR_COMMAND_SYNTAX_ERROR;
 
        for (unsigned i = 0; i < dpm->nwp; i++) {
                if (dpm->dwp[i].wp == wp) {
                        dpm->dwp[i].wp = NULL;
-                       dpm->dwp[i].dirty = true;
+                       dpm->dwp[i].bpwp.dirty = true;
 
                        /* hardware is updated in write_dirty_registers() */
                        retval = ERROR_OK;
@@ -830,11 +959,16 @@ int arm_dpm_setup(struct arm_dpm *dpm)
        arm->mrc = dpm_mrc;
        arm->mcr = dpm_mcr;
 
-       /* breakpoint and watchpoint setup */
+       /* breakpoint setup -- optional until it works everywhere */
+       if (!target->type->add_breakpoint) {
+               target->type->add_breakpoint = dpm_add_breakpoint;
+               target->type->remove_breakpoint = dpm_remove_breakpoint;
+       }
+
+       /* watchpoint setup */
        target->type->add_watchpoint = dpm_add_watchpoint;
        target->type->remove_watchpoint = dpm_remove_watchpoint;
 
-       /* FIXME add breakpoint support */
        /* FIXME add vector catch support */
 
        dpm->nbp = 1 + ((dpm->didr >> 24) & 0xf);
@@ -869,10 +1003,14 @@ int arm_dpm_initialize(struct arm_dpm *dpm)
        if (dpm->bpwp_disable) {
                unsigned i;
 
-               for (i = 0; i < dpm->nbp; i++)
+               for (i = 0; i < dpm->nbp; i++) {
+                       dpm->dbp[i].bpwp.number = i;
                        (void) dpm->bpwp_disable(dpm, i);
-               for (i = 0; i < dpm->nwp; i++)
+               }
+               for (i = 0; i < dpm->nwp; i++) {
+                       dpm->dwp[i].bpwp.number = 16 + i;
                        (void) dpm->bpwp_disable(dpm, 16 + i);
+               }
        } else
                LOG_WARNING("%s: can't disable breakpoints and watchpoints",
                        target_name(dpm->arm->target));