split NOR and NAND flash headers
[openocd.git] / src / target / arm_dpm.c
index b8107d78549f1c91640ee4f59775f375aee14e6c..a9ce880719453024dd1856357d27f79e68bbdda6 100644 (file)
 
 #include "armv4_5.h"           /* REVISIT to become arm.h */
 #include "arm_dpm.h"
-#include "jtag.h"
+#include <jtag/jtag.h>
 #include "register.h"
+#include "breakpoints.h"
+#include "target_type.h"
 
 
 /**
  * implementation differences between cores like ARM1136 and Cortex-A8.
  */
 
+/*----------------------------------------------------------------------*/
+
+/*
+ * Coprocessor support
+ */
+
+/* Read coprocessor */
+static int dpm_mrc(struct target *target, int cpnum,
+               uint32_t op1, uint32_t op2, uint32_t CRn, uint32_t CRm,
+               uint32_t *value)
+{
+       struct arm *arm = target_to_arm(target);
+       struct arm_dpm *dpm = arm->dpm;
+       int retval;
+
+       retval = dpm->prepare(dpm);
+       if (retval != ERROR_OK)
+               return retval;
+
+       LOG_DEBUG("MRC p%d, %d, r0, c%d, c%d, %d", cpnum,
+                       (int) op1, (int) CRn,
+                       (int) CRm, (int) op2);
+
+       /* read coprocessor register into R0; return via DCC */
+       retval = dpm->instr_read_data_r0(dpm,
+                       ARMV4_5_MRC(cpnum, op1, 0, CRn, CRm, op2),
+                       value);
+
+       /* (void) */ dpm->finish(dpm);
+       return retval;
+}
+
+static int dpm_mcr(struct target *target, int cpnum,
+               uint32_t op1, uint32_t op2, uint32_t CRn, uint32_t CRm,
+               uint32_t value)
+{
+       struct arm *arm = target_to_arm(target);
+       struct arm_dpm *dpm = arm->dpm;
+       int retval;
+
+       retval = dpm->prepare(dpm);
+       if (retval != ERROR_OK)
+               return retval;
+
+       LOG_DEBUG("MCR p%d, %d, r0, c%d, c%d, %d", cpnum,
+                       (int) op1, (int) CRn,
+                       (int) CRm, (int) op2);
+
+       /* read DCC into r0; then write coprocessor register from R0 */
+       retval = dpm->instr_write_data_r0(dpm,
+                       ARMV4_5_MCR(cpnum, op1, 0, CRn, CRm, op2),
+                       value);
+
+       /* (void) */ dpm->finish(dpm);
+       return retval;
+}
+
+/*----------------------------------------------------------------------*/
+
+/*
+ * Register access utilities
+ */
+
 /* Toggles between recorded core mode (USR, SVC, etc) and a temporary one.
  * Routines *must* restore the original mode before returning!!
  */
@@ -123,7 +188,7 @@ static int dpm_write_reg(struct arm_dpm *dpm, struct reg *r, unsigned regnum)
 
        switch (regnum) {
        case 0 ... 14:
-               /* load register from DCC:  "MCR p14, 0, Rnum, c0, c5, 0" */
+               /* load register from DCC:  "MRC p14, 0, Rnum, c0, c5, 0" */
                retval = dpm->instr_write_data_dcc(dpm,
                                ARMV4_5_MRC(14, 0, regnum, 0, 5, 0),
                                value);
@@ -201,6 +266,11 @@ int arm_dpm_read_current_registers(struct arm_dpm *dpm)
 
        /* NOTE: SPSR ignored (if it's even relevant). */
 
+       /* REVISIT the debugger can trigger various exceptions.  See the
+        * ARMv7A architecture spec, section C5.7, for more info about
+        * what defenses are needed; v6 debug has the most issues.
+        */
+
 fail:
        /* (void) */ dpm->finish(dpm);
        return retval;
@@ -209,8 +279,12 @@ fail:
 /**
  * Writes all modified core registers for all processor modes.  In normal
  * operation this is called on exit from halting debug state.
+ *
+ * @param dpm: represents the processor
+ * @param bpwp: true ensures breakpoints and watchpoints are set,
+ *     false ensures they are cleared
  */
-int arm_dpm_write_dirty_registers(struct arm_dpm *dpm)
+int arm_dpm_write_dirty_registers(struct arm_dpm *dpm, bool bpwp)
 {
        struct arm *arm = dpm->arm;
        struct reg_cache *cache = arm->core_cache;
@@ -221,6 +295,53 @@ int arm_dpm_write_dirty_registers(struct arm_dpm *dpm)
        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, dwp->control);
+
+               if (retval != ERROR_OK)
+                       LOG_ERROR("%s: can't %s HW watchpoint %d",
+                                       target_name(arm->target),
+                                       disable ? "disable" : "enable",
+                                       i);
+       }
+
+       /* NOTE:  writes to breakpoint and watchpoint registers might
+        * be queued, and need (efficient/batched) flushing later.
+        */
+
        /* Scan the registers until we find one that's both dirty and
         * eligible for flushing.  Flush that and everything else that
         * shares the same core mode setting.  Typically this won't
@@ -344,6 +465,13 @@ static enum armv4_5_mode dpm_mapmode(struct arm *arm,
        return ARMV4_5_MODE_ANY;
 }
 
+
+/*
+ * Standard ARM register accessors ... there are three methods
+ * in "struct arm", to support individual read/write and bulk read
+ * of registers.
+ */
+
 static int arm_dpm_read_core_reg(struct target *target, struct reg *r,
                int regnum, enum armv4_5_mode mode)
 {
@@ -489,9 +617,194 @@ done:
        return retval;
 }
 
+
+/*----------------------------------------------------------------------*/
+
+/*
+ * Breakpoint and Watchpoint support.
+ *
+ * Hardware {break,watch}points are usually left active, to minimize
+ * debug entry/exit costs.  When they are set or cleared, it's done in
+ * batches.  Also, DPM-conformant hardware can update debug registers
+ * regardless of whether the CPU is running or halted ... though that
+ * fact isn't currently leveraged.
+ */
+
+static int dpm_watchpoint_setup(struct arm_dpm *dpm, unsigned index,
+               struct watchpoint *wp)
+{
+       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.
+        */
+       switch (wp->length) {
+       case 1:
+               control |= (1 << (addr & 3)) << 5;
+               addr &= ~3;
+               break;
+       case 2:
+               /* require 2-byte alignment */
+               if (!(addr & 1)) {
+                       control |= (3 << (addr & 2)) << 5;
+                       break;
+               }
+               /* FALL THROUGH */
+       case 4:
+               /* require 4-byte alignment */
+               if (!(addr & 3)) {
+                       control |= 0xf << 5;
+                       break;
+               }
+               /* FALL THROUGH */
+       default:
+               LOG_DEBUG("bad watchpoint length or alignment");
+               return ERROR_INVALID_ARGUMENTS;
+       }
+
+       /* other control bits:
+        * bits 9:12 == 0 ... only checking up to four byte addresses (v7 only)
+        * 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;
+
+       /* hardware is updated in write_dirty_registers() */
+       return ERROR_OK;
+}
+
+
+static int dpm_add_watchpoint(struct target *target, struct watchpoint *wp)
+{
+       struct arm *arm = target_to_arm(target);
+       struct arm_dpm *dpm = arm->dpm;
+       int retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+
+       if (dpm->bpwp_enable) {
+               for (unsigned i = 0; i < dpm->nwp; i++) {
+                       if (!dpm->dwp[i].wp) {
+                               retval = dpm_watchpoint_setup(dpm, i, wp);
+                               break;
+                       }
+               }
+       }
+
+       return retval;
+}
+
+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;
+
+       for (unsigned i = 0; i < dpm->nwp; i++) {
+               if (dpm->dwp[i].wp == wp) {
+                       dpm->dwp[i].wp = NULL;
+                       dpm->dwp[i].dirty = true;
+
+                       /* hardware is updated in write_dirty_registers() */
+                       retval = ERROR_OK;
+                       break;
+               }
+       }
+
+       return retval;
+}
+
+void arm_dpm_report_wfar(struct arm_dpm *dpm, uint32_t addr)
+{
+       switch (dpm->arm->core_state) {
+       case ARMV4_5_STATE_ARM:
+               addr -= 8;
+               break;
+       case ARMV4_5_STATE_THUMB:
+       case ARM_STATE_THUMB_EE:
+               addr -= 4;
+               break;
+       case ARMV4_5_STATE_JAZELLE:
+               /* ?? */
+               break;
+       }
+       dpm->wp_pc = addr;
+}
+
+/*----------------------------------------------------------------------*/
+
+/*
+ * Other debug and support utilities
+ */
+
+void arm_dpm_report_dscr(struct arm_dpm *dpm, uint32_t dscr)
+{
+       struct target *target = dpm->arm->target;
+
+       dpm->dscr = dscr;
+
+       /* Examine debug reason */
+       switch (DSCR_ENTRY(dscr)) {
+       case 6:         /* Data abort (v6 only) */
+       case 7:         /* Prefetch abort (v6 only) */
+               /* FALL THROUGH -- assume a v6 core in abort mode */
+       case 0:         /* HALT request from debugger */
+       case 4:         /* EDBGRQ */
+               target->debug_reason = DBG_REASON_DBGRQ;
+               break;
+       case 1:         /* HW breakpoint */
+       case 3:         /* SW BKPT */
+       case 5:         /* vector catch */
+               target->debug_reason = DBG_REASON_BREAKPOINT;
+               break;
+       case 2:         /* asynch watchpoint */
+       case 10:        /* precise watchpoint */
+               target->debug_reason = DBG_REASON_WATCHPOINT;
+               break;
+       default:
+               target->debug_reason = DBG_REASON_UNDEFINED;
+               break;
+       }
+}
+
+/*----------------------------------------------------------------------*/
+
+/*
+ * Setup and management support.
+ */
+
 /**
  * Hooks up this DPM to its associated target; call only once.
  * Initially this only covers the register cache.
+ *
+ * Oh, and watchpoints.  Yeah.
  */
 int arm_dpm_setup(struct arm_dpm *dpm)
 {
@@ -501,6 +814,7 @@ int arm_dpm_setup(struct arm_dpm *dpm)
 
        arm->dpm = dpm;
 
+       /* register access setup */
        arm->full_context = arm_dpm_full_context;
        arm->read_core_reg = arm_dpm_read_core_reg;
        arm->write_core_reg = arm_dpm_write_core_reg;
@@ -510,6 +824,37 @@ int arm_dpm_setup(struct arm_dpm *dpm)
                return ERROR_FAIL;
 
        *register_get_last_cache_p(&target->reg_cache) = cache;
+
+       /* coprocessor access setup */
+       arm->mrc = dpm_mrc;
+       arm->mcr = dpm_mcr;
+
+       /* breakpoint and 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);
+       dpm->dbp = calloc(dpm->nbp, sizeof *dpm->dbp);
+
+       dpm->nwp = 1 + ((dpm->didr >> 28) & 0xf);
+       dpm->dwp = calloc(dpm->nwp, sizeof *dpm->dwp);
+
+       if (!dpm->dbp || !dpm->dwp) {
+               free(dpm->dbp);
+               free(dpm->dwp);
+               return ERROR_FAIL;
+       }
+
+       LOG_INFO("%s: hardware has %d breakpoints, %d watchpoints",
+                       target_name(target), dpm->nbp, dpm->nwp);
+
+       /* REVISIT ... and some of those breakpoints could match
+        * execution context IDs...
+        */
+
        return ERROR_OK;
 }
 
@@ -519,6 +864,17 @@ int arm_dpm_setup(struct arm_dpm *dpm)
  */
 int arm_dpm_initialize(struct arm_dpm *dpm)
 {
-       /* FIXME -- nothing yet */
+       /* Disable all breakpoints and watchpoints at startup. */
+       if (dpm->bpwp_disable) {
+               unsigned i;
+
+               for (i = 0; i < dpm->nbp; i++)
+                       (void) dpm->bpwp_disable(dpm, i);
+               for (i = 0; i < dpm->nwp; i++)
+                       (void) dpm->bpwp_disable(dpm, 16 + i);
+       } else
+               LOG_WARNING("%s: can't disable breakpoints and watchpoints",
+                       target_name(dpm->arm->target));
+
        return ERROR_OK;
 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)