target/arc: Introduce Actionpoints support 63/5763/5
authorEvgeniy Didin <didin@synopsys.com>
Fri, 10 Jul 2020 11:52:35 +0000 (14:52 +0300)
committerAntonio Borneo <borneo.antonio@gmail.com>
Sun, 26 Jul 2020 19:08:21 +0000 (20:08 +0100)
Actionpoint mechanism allows to setup HW breakpoints and watchpoints on Synopsys ARC CPUs.
This mechanism is controlled by DEBUG register and by a set of auxilary registers.
Each actionpoint is controlled by 3 aux registers: Actionpoint(AP) match mask(AP_AMM),
AP match value(AP_AMV) and AP control(AC).

Note: some fields of actionpoint_t structure will be used in further
support of watchpoints.

Change-Id: I4efb24675f247cc19d9122501c9e63c3126fcab4
Signed-off-by: Evgeniy Didin <didin@synopsys.com>
Reviewed-on: http://openocd.zylin.com/5763
Tested-by: jenkins
Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com>
src/target/arc.c
src/target/arc.h
src/target/arc_cmd.c
tcl/cpu/arc/v2.tcl

index 1ac4a43355419ed756e9ed9df9ee4f89a4f2786d..db338031f704d03a23102b66cf217f0968045fbe 100644 (file)
@@ -603,6 +603,27 @@ static int arc_get_register_value(struct target *target, const char *reg_name,
        return ERROR_OK;
 }
 
+static int arc_set_register_value(struct target *target, const char *reg_name,
+               uint32_t value)
+{
+       LOG_DEBUG("reg_name=%s value=0x%08" PRIx32, reg_name, value);
+
+       if (!(target && reg_name)) {
+               LOG_ERROR("Arguments cannot be NULL.");
+               return ERROR_FAIL;
+       }
+
+       struct reg *reg = arc_reg_get_by_name(target->reg_cache, reg_name, true);
+
+       if (!reg)
+               return ERROR_ARC_REGISTER_NOT_FOUND;
+
+       uint8_t value_buf[4];
+       buf_set_u32(value_buf, 0, 32, value);
+       CHECK_RETVAL(reg->type->set(reg, value_buf));
+
+       return ERROR_OK;
+}
 
 /* Configure DCCM's */
 static int arc_configure_dccm(struct target  *target)
@@ -897,6 +918,44 @@ exit:
        return retval;
 }
 
+/**
+ * Finds an actionpoint that triggered last actionpoint event, as specified by
+ * DEBUG.ASR.
+ *
+ * @param actionpoint Pointer to be set to last active actionpoint. Pointer
+ *                    will be set to NULL if DEBUG.AH is 0.
+ */
+static int get_current_actionpoint(struct target *target,
+               struct arc_actionpoint **actionpoint)
+{
+       assert(target != NULL);
+       assert(actionpoint != NULL);
+
+       uint32_t debug_ah;
+       /* Check if actionpoint caused halt */
+       CHECK_RETVAL(arc_reg_get_field(target, "debug", "ah",
+                               &debug_ah));
+
+       if (debug_ah) {
+               struct arc_common *arc = target_to_arc(target);
+               unsigned int ap;
+               uint32_t debug_asr;
+               CHECK_RETVAL(arc_reg_get_field(target, "debug",
+                                       "asr", &debug_asr));
+
+               for (ap = 0; debug_asr > 1; debug_asr >>= 1)
+                       ap += 1;
+
+               assert(ap < arc->actionpoints_num);
+
+               *actionpoint = &(arc->actionpoints_list[ap]);
+       } else {
+               *actionpoint = NULL;
+       }
+
+       return ERROR_OK;
+}
+
 static int arc_examine_debug_reason(struct target *target)
 {
        uint32_t debug_bh;
@@ -916,8 +975,20 @@ static int arc_examine_debug_reason(struct target *target)
                /* DEBUG.BH is set if core halted due to BRK instruction.  */
                target->debug_reason = DBG_REASON_BREAKPOINT;
        } else {
-               /* TODO: Add Actionpoint check when AP support will be introduced*/
-               LOG_WARNING("Unknown debug reason");
+               struct arc_actionpoint *actionpoint = NULL;
+               CHECK_RETVAL(get_current_actionpoint(target, &actionpoint));
+
+               if (actionpoint != NULL) {
+                       if (!actionpoint->used)
+                               LOG_WARNING("Target halted by an unused actionpoint.");
+
+                       if (actionpoint->type == ARC_AP_BREAKPOINT)
+                               target->debug_reason = DBG_REASON_BREAKPOINT;
+                       else if (actionpoint->type == ARC_AP_WATCHPOINT)
+                               target->debug_reason = DBG_REASON_WATCHPOINT;
+                       else
+                               LOG_WARNING("Unknown type of actionpoint.");
+               }
        }
 
        return ERROR_OK;
@@ -1301,6 +1372,7 @@ static void arc_deinit_target(struct target *target)
        list_for_each_entry_safe(desc, k, &arc->bcr_reg_descriptions, list)
                free_reg_desc(desc);
 
+       free(arc->actionpoints_list);
        free(arc);
 }
 
@@ -1377,10 +1449,54 @@ int arc_read_instruction_u32(struct target *target, uint32_t address,
        return ERROR_OK;
 }
 
+/* Actionpoint mechanism allows to setup HW breakpoints
+ * and watchpoints. Each actionpoint is controlled by
+ * 3 aux registers: Actionpoint(AP) match mask(AP_AMM), AP match value(AP_AMV)
+ * and AP control(AC).
+ * This function is for setting/unsetting actionpoints:
+ * at - actionpoint target: trigger on mem/reg access
+ * tt - transaction type : trigger on r/w. */
+static int arc_configure_actionpoint(struct target *target, uint32_t ap_num,
+       uint32_t match_value, uint32_t control_tt, uint32_t control_at)
+{
+       struct arc_common *arc = target_to_arc(target);
+
+       if (control_tt != AP_AC_TT_DISABLE) {
+
+               if (arc->actionpoints_num_avail < 1) {
+                       LOG_ERROR("No free actionpoints, maximim amount is %" PRIu32,
+                                       arc->actionpoints_num);
+                       return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+               }
+
+               /* Names of register to set - 24 chars should be enough. Looks a little
+                * bit out-of-place for C code, but makes it aligned to the bigger
+                * concept of "ARC registers are defined in TCL" as far as possible.
+                */
+               char ap_amv_reg_name[24], ap_amm_reg_name[24], ap_ac_reg_name[24];
+               snprintf(ap_amv_reg_name, 24, "ap_amv%" PRIu32, ap_num);
+               snprintf(ap_amm_reg_name, 24, "ap_amm%" PRIu32, ap_num);
+               snprintf(ap_ac_reg_name, 24, "ap_ac%" PRIu32, ap_num);
+               CHECK_RETVAL(arc_set_register_value(target, ap_amv_reg_name,
+                                        match_value));
+               CHECK_RETVAL(arc_set_register_value(target, ap_amm_reg_name, 0));
+               CHECK_RETVAL(arc_set_register_value(target, ap_ac_reg_name,
+                                        control_tt | control_at));
+               arc->actionpoints_num_avail--;
+       } else {
+               char ap_ac_reg_name[24];
+               snprintf(ap_ac_reg_name, 24, "ap_ac%" PRIu32, ap_num);
+               CHECK_RETVAL(arc_set_register_value(target, ap_ac_reg_name,
+                                        AP_AC_TT_DISABLE));
+               arc->actionpoints_num_avail++;
+       }
+
+       return ERROR_OK;
+}
+
 static int arc_set_breakpoint(struct target *target,
                struct breakpoint *breakpoint)
 {
-
        if (breakpoint->set) {
                LOG_WARNING("breakpoint already set");
                return ERROR_OK;
@@ -1425,8 +1541,34 @@ static int arc_set_breakpoint(struct target *target,
 
                breakpoint->set = 64; /* Any nice value but 0 */
        } else if (breakpoint->type == BKPT_HARD) {
-               LOG_DEBUG("Hardware breakpoints are not supported yet!");
-               return ERROR_FAIL;
+               struct arc_common *arc = target_to_arc(target);
+               struct arc_actionpoint *ap_list = arc->actionpoints_list;
+               unsigned int bp_num;
+
+               for (bp_num = 0; bp_num < arc->actionpoints_num; bp_num++) {
+                       if (!ap_list[bp_num].used)
+                               break;
+               }
+
+               if (bp_num >= arc->actionpoints_num) {
+                       LOG_ERROR("No free actionpoints, maximum amount is %" PRIu32,
+                                       arc->actionpoints_num);
+                       return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+               }
+
+               int retval = arc_configure_actionpoint(target, bp_num,
+                               breakpoint->address, AP_AC_TT_READWRITE, AP_AC_AT_INST_ADDR);
+
+               if (retval == ERROR_OK) {
+                       breakpoint->set = bp_num + 1;
+                       ap_list[bp_num].used = 1;
+                       ap_list[bp_num].bp_value = breakpoint->address;
+                       ap_list[bp_num].type = ARC_AP_BREAKPOINT;
+
+                       LOG_DEBUG("bpid: %" PRIu32 ", bp_num %u bp_value 0x%" PRIx32,
+                                       breakpoint->unique_id, bp_num, ap_list[bp_num].bp_value);
+               }
+
        } else {
                LOG_DEBUG("ERROR: setting unknown breakpoint type");
                return ERROR_FAIL;
@@ -1491,8 +1633,27 @@ static int arc_unset_breakpoint(struct target *target,
                breakpoint->set = 0;
 
        }       else if (breakpoint->type == BKPT_HARD) {
-                       LOG_WARNING("Hardware breakpoints are not supported yet!");
-                       return ERROR_FAIL;
+               struct arc_common *arc = target_to_arc(target);
+               struct arc_actionpoint *ap_list = arc->actionpoints_list;
+               unsigned int bp_num = breakpoint->set - 1;
+
+               if ((breakpoint->set == 0) || (bp_num >= arc->actionpoints_num)) {
+                       LOG_DEBUG("Invalid actionpoint ID: %u in breakpoint: %" PRIu32,
+                                         bp_num, breakpoint->unique_id);
+                       return ERROR_OK;
+               }
+
+               retval = arc_configure_actionpoint(target, bp_num,
+                                               breakpoint->address, AP_AC_TT_DISABLE, AP_AC_AT_INST_ADDR);
+
+               if (retval == ERROR_OK) {
+                       breakpoint->set = 0;
+                       ap_list[bp_num].used = 0;
+                       ap_list[bp_num].bp_value = 0;
+
+                       LOG_DEBUG("bpid: %" PRIu32 " - released actionpoint ID: %i",
+                                       breakpoint->unique_id, bp_num);
+               }
        } else {
                        LOG_DEBUG("ERROR: unsetting unknown breakpoint type");
                        return ERROR_FAIL;
@@ -1530,6 +1691,115 @@ static int arc_remove_breakpoint(struct target *target,
        return ERROR_OK;
 }
 
+void arc_reset_actionpoints(struct target *target)
+{
+       struct arc_common *arc = target_to_arc(target);
+       struct arc_actionpoint *ap_list = arc->actionpoints_list;
+       struct breakpoint *next_b;
+
+       while (target->breakpoints) {
+               next_b = target->breakpoints->next;
+               arc_remove_breakpoint(target, target->breakpoints);
+               free(target->breakpoints->orig_instr);
+               free(target->breakpoints);
+               target->breakpoints = next_b;
+       }
+       for (unsigned int i = 0; i < arc->actionpoints_num; i++) {
+               if ((ap_list[i].used) && (ap_list[i].reg_address))
+                       arc_remove_auxreg_actionpoint(target, ap_list[i].reg_address);
+       }
+}
+
+int arc_set_actionpoints_num(struct target *target, uint32_t ap_num)
+{
+       LOG_DEBUG("target=%s actionpoints=%" PRIu32, target_name(target), ap_num);
+       struct arc_common *arc = target_to_arc(target);
+
+       /* Make sure that there are no enabled actionpoints in target. */
+       arc_reset_actionpoints(target);
+
+       /* Assume that all points have been removed from target.  */
+       free(arc->actionpoints_list);
+
+       arc->actionpoints_num_avail = ap_num;
+       arc->actionpoints_num = ap_num;
+       /* calloc can be safely called when ncount == 0.  */
+       arc->actionpoints_list = calloc(ap_num, sizeof(struct arc_actionpoint));
+
+       if (!arc->actionpoints_list) {
+               LOG_ERROR("Unable to allocate memory");
+               return ERROR_FAIL;
+       }
+       return ERROR_OK;
+}
+
+
+int arc_add_auxreg_actionpoint(struct target *target,
+       uint32_t auxreg_addr, uint32_t transaction)
+{
+       unsigned int ap_num = 0;
+       int retval = ERROR_OK;
+
+       if (target->state != TARGET_HALTED)
+               return ERROR_TARGET_NOT_HALTED;
+
+       struct arc_common *arc = target_to_arc(target);
+       struct arc_actionpoint *ap_list = arc->actionpoints_list;
+
+       while (ap_list[ap_num].used)
+               ap_num++;
+
+       if (ap_num >= arc->actionpoints_num) {
+               LOG_ERROR("No actionpoint free, maximum amount is %u",
+                               arc->actionpoints_num);
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       retval =  arc_configure_actionpoint(target, ap_num,
+                       auxreg_addr, transaction, AP_AC_AT_AUXREG_ADDR);
+
+       if (retval == ERROR_OK) {
+               ap_list[ap_num].used = 1;
+               ap_list[ap_num].reg_address = auxreg_addr;
+       }
+
+       return retval;
+}
+
+int arc_remove_auxreg_actionpoint(struct target *target, uint32_t auxreg_addr)
+{
+       int retval = ERROR_OK;
+       bool ap_found = false;
+       unsigned int ap_num = 0;
+
+       if (target->state != TARGET_HALTED)
+               return ERROR_TARGET_NOT_HALTED;
+
+       struct arc_common *arc = target_to_arc(target);
+       struct arc_actionpoint *ap_list = arc->actionpoints_list;
+
+       while ((ap_list[ap_num].used) && (ap_num < arc->actionpoints_num)) {
+               if (ap_list[ap_num].reg_address == auxreg_addr) {
+                       ap_found = true;
+                       break;
+               }
+               ap_num++;
+       }
+
+       if (ap_found) {
+               retval =  arc_configure_actionpoint(target, ap_num,
+                               auxreg_addr, AP_AC_TT_DISABLE, AP_AC_AT_AUXREG_ADDR);
+
+               if (retval == ERROR_OK) {
+                       ap_list[ap_num].used = 0;
+                       ap_list[ap_num].bp_value = 0;
+               }
+       } else {
+               LOG_ERROR("Register actionpoint not found");
+       }
+       return retval;
+}
+
 /* Helper function which swiches core to single_step mode by
  * doing aux r/w operations.  */
 int arc_config_step(struct target *target, int enable_step)
index 66414115976cb71dc48db6281cd98238a03d3bf7..f9ee5b45e1f447b473254c2c8cce98ca738cc3f9 100644 (file)
 #define SLC_AUX_CACHE_INV              0x905
 #define L2_INV_IV                      BIT(0)
 
+ /* Action Point */
+#define AP_AC_AT_INST_ADDR             0x0
+#define AP_AC_AT_MEMORY_ADDR   0x2
+#define AP_AC_AT_AUXREG_ADDR   0x4
+
+#define AP_AC_TT_DISABLE               0x00
+#define AP_AC_TT_WRITE                 0x10
+#define AP_AC_TT_READ                  0x20
+#define AP_AC_TT_READWRITE             0x30
+
 struct arc_reg_bitfield {
        struct reg_data_type_bitfield bitfield;
        char name[REG_TYPE_MAX_NAME_LENGTH];
@@ -96,8 +106,6 @@ struct arc_reg_data_type {
        };
 };
 
-
-
 /* Standard GDB register types */
 static const struct reg_data_type standard_gdb_types[] = {
        { .type = REG_TYPE_INT,         .id = "int" },
@@ -118,6 +126,18 @@ static const struct reg_data_type standard_gdb_types[] = {
        { .type = REG_TYPE_IEEE_DOUBLE, .id = "ieee_double" },
 };
 
+enum arc_actionpointype {
+       ARC_AP_BREAKPOINT,
+       ARC_AP_WATCHPOINT,
+};
+
+/* Actionpoint related fields  */
+struct arc_actionpoint {
+       int used;
+       uint32_t bp_value;
+       uint32_t reg_address;
+       enum arc_actionpointype type;
+};
 
 struct arc_common {
        uint32_t common_magic;
@@ -172,6 +192,11 @@ struct arc_common {
        unsigned long pc_index_in_cache;
        /* DEBUG register location in register cache. */
        unsigned long debug_index_in_cache;
+
+       /* Actionpoints */
+       unsigned int actionpoints_num;
+       unsigned int actionpoints_num_avail;
+       struct arc_actionpoint *actionpoints_list;
 };
 
 /* Borrowed from nds32.h */
@@ -284,4 +309,9 @@ int arc_reg_get_field(struct target *target, const char *reg_name,
 int arc_cache_flush(struct target *target);
 int arc_cache_invalidate(struct target *target);
 
+int arc_add_auxreg_actionpoint(struct target *target,
+       uint32_t auxreg_addr, uint32_t transaction);
+int arc_remove_auxreg_actionpoint(struct target *target, uint32_t auxreg_addr);
+int arc_set_actionpoints_num(struct target *target, uint32_t ap_num);
+
 #endif /* OPENOCD_TARGET_ARC_H */
index 59e1645d67cbfebf9c1db1caa8e8f2fc29fef834..a1d5a0936a238a7c41473b6749bea16fe4d246e1 100644 (file)
@@ -929,6 +929,50 @@ COMMAND_HANDLER(arc_l2_cache_disable_auto_cmd)
                &arc->has_l2cache, "target has l2 cache enabled");
 }
 
+static int jim_handle_actionpoints_num(Jim_Interp *interp, int argc,
+       Jim_Obj * const *argv)
+{
+       Jim_GetOptInfo goi;
+       Jim_GetOpt_Setup(&goi, interp, argc - 1, argv + 1);
+
+       LOG_DEBUG("-");
+
+       if (goi.argc >= 2) {
+               Jim_WrongNumArgs(interp, goi.argc, goi.argv, "[<unsigned integer>]");
+               return JIM_ERR;
+       }
+
+       struct command_context *context = current_command_context(interp);
+       assert(context);
+
+       struct target *target = get_current_target(context);
+
+       if (!target) {
+               Jim_SetResultFormatted(goi.interp, "No current target");
+               return JIM_ERR;
+       }
+
+       struct arc_common *arc = target_to_arc(target);
+       /* It is not possible to pass &arc->actionpoints_num directly to
+        * handle_command_parse_uint, because this value should be valid during
+        * "actionpoint reset, initiated by arc_set_actionpoints_num.  */
+       uint32_t ap_num = arc->actionpoints_num;
+
+       if (goi.argc == 1) {
+               JIM_CHECK_RETVAL(arc_cmd_jim_get_uint32(&goi, &ap_num));
+               int e = arc_set_actionpoints_num(target, ap_num);
+               if (e != ERROR_OK) {
+                       Jim_SetResultFormatted(goi.interp,
+                               "Failed to set number of actionpoints");
+                       return JIM_ERR;
+               }
+       }
+
+       Jim_SetResultInt(interp, ap_num);
+
+       return JIM_OK;
+}
+
 /* ----- Exported target commands ------------------------------------------ */
 
 const struct command_registration arc_l2_cache_group_handlers[] = {
@@ -1024,6 +1068,13 @@ static const struct command_registration arc_core_command_handlers[] = {
                .usage = "",
                .chain = arc_cache_group_handlers,
        },
+       {
+               .name = "num-actionpoints",
+               .jim_handler = jim_handle_actionpoints_num,
+               .mode = COMMAND_ANY,
+               .usage = "[<unsigned integer>]",
+               .help = "Prints or sets amount of actionpoints in the processor.",
+       },
        COMMAND_REGISTRATION_DONE
 };
 
index ad55361a5482dcb65a0ae26da7ddf3ee607d5b76..a3172c220a1d918b20aae8286db775eaa9b98bfb 100644 (file)
@@ -30,6 +30,32 @@ proc arc_v2_examine_target { {target ""} } {
                r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 \
                gp fp sp ilink r30 blink lp_count pcl
 
+       # Actionpoints
+       if { [arc get-reg-field ap_build version] == 5 } {
+               set ap_build_type [arc get-reg-field ap_build type]
+               # AP_BUILD.TYPE > 0b0110 is reserved in current ISA.
+               # Current ISA supports up to 8 actionpoints.
+               if { $ap_build_type < 8 } {
+                       # Two LSB bits of AP_BUILD.TYPE define amount of actionpoints:
+                       # 0b00 - 2 actionpoints
+                       # 0b01 - 4 actionpoints
+                       # 0b10 - 8 actionpoints
+                       # 0b11 - reserved.
+                       set ap_num [expr 0x2 << ($ap_build_type & 3)]
+                       # Expression on top may produce 16 action points - which is a
+                       # reserved value for now.
+                       if { $ap_num < 16 } {
+                               # Enable actionpoint registers
+                               for {set i 0} {$i < $ap_num} {incr i} {
+                                       arc set-reg-exists ap_amv$i ap_amm$i ap_ac$i
+                               }
+
+                               # Set amount of actionpoints
+                               arc num-actionpoints $ap_num
+                       }
+               }
+       }
+
        # DCCM
        set dccm_version [arc get-reg-field dccm_build version]
        if { $dccm_version == 3 || $dccm_version == 4 } {
@@ -213,6 +239,30 @@ proc arc_v2_init_regs { } {
                0x018 aux_dccm  int
                0x208 aux_iccm  int
 
+               0x220 ap_amv0   uint32
+               0x221 ap_amm0   uint32
+               0x222 ap_ac0    ap_control_t
+               0x223 ap_amv1   uint32
+               0x224 ap_amm1   uint32
+               0x225 ap_ac1    ap_control_t
+               0x226 ap_amv2   uint32
+               0x227 ap_amm2   uint32
+               0x228 ap_ac2    ap_control_t
+               0x229 ap_amv3   uint32
+               0x22A ap_amm3   uint32
+               0x22B ap_ac3    ap_control_t
+               0x22C ap_amv4   uint32
+               0x22D ap_amm4   uint32
+               0x22E ap_ac4    ap_control_t
+               0x22F ap_amv5   uint32
+               0x230 ap_amm5   uint32
+               0x231 ap_ac5    ap_control_t
+               0x232 ap_amv6   uint32
+               0x233 ap_amm6   uint32
+               0x234 ap_ac6    ap_control_t
+               0x235 ap_amv7   uint32
+               0x236 ap_amm7   uint32
+               0x237 ap_ac7    ap_control_t
 
                0x400 eret              code_ptr
                0x401 erbta             code_ptr
@@ -285,4 +335,12 @@ proc arc_v2_init_regs { } {
 
 proc arc_v2_reset { {target ""} } {
        arc_common_reset $target
+
+       # Disable all actionpoints.  Cannot write via regcache yet, because it will
+       # not be flushed and all changes to registers will get lost.  Therefore has
+       # to write directly via JTAG layer...
+       set num_ap [arc num-actionpoints]
+       for {set i 0} {$i < $num_ap} {incr i} {
+               arc jtag set-aux-reg [expr 0x222 + $i * 3] 0
+       }
 }

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)