stlink: skip rw-misc commands with TCP server
[openocd.git] / src / jtag / drivers / stlink_usb.c
index 818fccd333b7e1004aef7faebff77373060533d3..b51a0c2bacd23d51232657e510ed3c0848c54d48 100644 (file)
@@ -33,6 +33,7 @@
 #endif
 
 /* project specific includes */
+#include <helper/align.h>
 #include <helper/binarybuffer.h>
 #include <helper/bits.h>
 #include <helper/system.h>
@@ -227,6 +228,7 @@ struct dap_queue {
                        unsigned int reg;
                        struct adiv5_ap *ap;
                        uint32_t data;
+                       bool changes_csw_default;
                } ap_w;
                struct mem_ap {
                        uint32_t addr;
@@ -434,6 +436,12 @@ static inline int stlink_usb_xfer_noerrcheck(void *handle, const uint8_t *buf, i
 #define STLINK_DEBUG_APIV2_INIT_AP         0x4B
 #define STLINK_DEBUG_APIV2_CLOSE_AP_DBG    0x4C
 
+#define STLINK_DEBUG_WRITEMEM_32BIT_NO_ADDR_INC         0x50
+#define STLINK_DEBUG_APIV2_RW_MISC_OUT     0x51
+#define STLINK_DEBUG_APIV2_RW_MISC_IN      0x52
+
+#define STLINK_DEBUG_READMEM_32BIT_NO_ADDR_INC          0x54
+
 #define STLINK_APIV3_SET_COM_FREQ           0x61
 #define STLINK_APIV3_GET_COM_FREQ           0x62
 
@@ -506,6 +514,9 @@ static inline int stlink_usb_xfer_noerrcheck(void *handle, const uint8_t *buf, i
 /* aliases */
 #define STLINK_F_HAS_TARGET_VOLT        STLINK_F_HAS_TRACE
 #define STLINK_F_HAS_FPU_REG            STLINK_F_HAS_GETLASTRWSTATUS2
+#define STLINK_F_HAS_MEM_WR_NO_INC      STLINK_F_HAS_MEM_16BIT
+#define STLINK_F_HAS_MEM_RD_NO_INC      STLINK_F_HAS_DPBANKSEL
+#define STLINK_F_HAS_RW_MISC            STLINK_F_HAS_DPBANKSEL
 #define STLINK_F_HAS_CSW                STLINK_F_HAS_DPBANKSEL
 
 #define STLINK_REGSEL_IS_FPU(x)         ((x) > 0x1F)
@@ -1323,6 +1334,7 @@ static int stlink_usb_version(void *handle)
                        flags |= STLINK_F_QUIRK_JTAG_DP_READ;
 
                /* API to read/write memory at 16 bit from J26 */
+               /* API to write memory without address increment from J26 */
                if (h->version.jtag >= 26)
                        flags |= STLINK_F_HAS_MEM_16BIT;
 
@@ -1335,6 +1347,7 @@ static int stlink_usb_version(void *handle)
                        flags |= STLINK_F_FIX_CLOSE_AP;
 
                /* Banked regs (DPv1 & DPv2) support from V2J32 */
+               /* API to read memory without address increment from V2J32 */
                /* Memory R/W supports CSW from V2J32 */
                if (h->version.jtag >= 32)
                        flags |= STLINK_F_HAS_DPBANKSEL;
@@ -1357,6 +1370,7 @@ static int stlink_usb_version(void *handle)
                flags |= STLINK_F_HAS_DAP_REG;
 
                /* API to read/write memory at 16 bit */
+               /* API to write memory without address increment */
                flags |= STLINK_F_HAS_MEM_16BIT;
 
                /* API required to init AP before any AP access */
@@ -1366,6 +1380,7 @@ static int stlink_usb_version(void *handle)
                flags |= STLINK_F_FIX_CLOSE_AP;
 
                /* Banked regs (DPv1 & DPv2) support from V3J2 */
+               /* API to read memory without address increment from V3J2 */
                /* Memory R/W supports CSW from V3J2 */
                if (h->version.jtag >= 2)
                        flags |= STLINK_F_HAS_DPBANKSEL;
@@ -2672,6 +2687,88 @@ static int stlink_usb_write_mem32(void *handle, uint8_t ap_num, uint32_t csw,
        return stlink_usb_get_rw_status(handle);
 }
 
+static int stlink_usb_read_mem32_noaddrinc(void *handle, uint8_t ap_num, uint32_t csw,
+               uint32_t addr, uint16_t len, uint8_t *buffer)
+{
+       struct stlink_usb_handle_s *h = handle;
+
+       assert(handle != NULL);
+
+       if (!(h->version.flags & STLINK_F_HAS_MEM_RD_NO_INC))
+               return ERROR_COMMAND_NOTFOUND;
+
+       if (len > STLINK_MAX_RW16_32) {
+               LOG_DEBUG("max buffer (%d) length exceeded", STLINK_MAX_RW16_32);
+               return ERROR_FAIL;
+       }
+
+       /* data must be a multiple of 4 and word aligned */
+       if (len % 4 || addr % 4) {
+               LOG_DEBUG("Invalid data alignment");
+               return ERROR_TARGET_UNALIGNED_ACCESS;
+       }
+
+       stlink_usb_init_buffer(handle, h->rx_ep, len);
+
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_COMMAND;
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_READMEM_32BIT_NO_ADDR_INC;
+       h_u32_to_le(h->cmdbuf + h->cmdidx, addr);
+       h->cmdidx += 4;
+       h_u16_to_le(h->cmdbuf + h->cmdidx, len);
+       h->cmdidx += 2;
+       h->cmdbuf[h->cmdidx++] = ap_num;
+       h_u24_to_le(h->cmdbuf + h->cmdidx, csw >> 8);
+       h->cmdidx += 3;
+
+       int retval = stlink_usb_xfer_noerrcheck(handle, h->databuf, len);
+       if (retval != ERROR_OK)
+               return retval;
+
+       memcpy(buffer, h->databuf, len);
+
+       return stlink_usb_get_rw_status(handle);
+}
+
+static int stlink_usb_write_mem32_noaddrinc(void *handle, uint8_t ap_num, uint32_t csw,
+               uint32_t addr, uint16_t len, const uint8_t *buffer)
+{
+       struct stlink_usb_handle_s *h = handle;
+
+       assert(handle != NULL);
+
+       if (!(h->version.flags & STLINK_F_HAS_MEM_WR_NO_INC))
+               return ERROR_COMMAND_NOTFOUND;
+
+       if (len > STLINK_MAX_RW16_32) {
+               LOG_DEBUG("max buffer (%d) length exceeded", STLINK_MAX_RW16_32);
+               return ERROR_FAIL;
+       }
+
+       /* data must be a multiple of 4 and word aligned */
+       if (len % 4 || addr % 4) {
+               LOG_DEBUG("Invalid data alignment");
+               return ERROR_TARGET_UNALIGNED_ACCESS;
+       }
+
+       stlink_usb_init_buffer(handle, h->tx_ep, len);
+
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_COMMAND;
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_WRITEMEM_32BIT_NO_ADDR_INC;
+       h_u32_to_le(h->cmdbuf + h->cmdidx, addr);
+       h->cmdidx += 4;
+       h_u16_to_le(h->cmdbuf + h->cmdidx, len);
+       h->cmdidx += 2;
+       h->cmdbuf[h->cmdidx++] = ap_num;
+       h_u24_to_le(h->cmdbuf + h->cmdidx, csw >> 8);
+       h->cmdidx += 3;
+
+       int retval = stlink_usb_xfer_noerrcheck(handle, buffer, len);
+       if (retval != ERROR_OK)
+               return retval;
+
+       return stlink_usb_get_rw_status(handle);
+}
+
 static uint32_t stlink_max_block_size(uint32_t tar_autoincr_block, uint32_t address)
 {
        uint32_t max_tar_block = (tar_autoincr_block - ((tar_autoincr_block - 1) & address));
@@ -3774,6 +3871,53 @@ static int stlink_usb_close_access_port(void *handle, unsigned char ap_num)
 
 }
 
+static int stlink_usb_rw_misc_out(void *handle, uint32_t items, const uint8_t *buffer)
+{
+       struct stlink_usb_handle_s *h = handle;
+       unsigned int buflen = ALIGN_UP(items, 4) + 4 * items;
+
+       LOG_DEBUG_IO("%s(%" PRIu32 ")", __func__, items);
+
+       assert(handle != NULL);
+
+       if (!(h->version.flags & STLINK_F_HAS_RW_MISC))
+               return ERROR_COMMAND_NOTFOUND;
+
+       stlink_usb_init_buffer(handle, h->tx_ep, buflen);
+
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_COMMAND;
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_APIV2_RW_MISC_OUT;
+       h_u32_to_le(&h->cmdbuf[2], items);
+
+       return stlink_usb_xfer_noerrcheck(handle, buffer, buflen);
+}
+
+static int stlink_usb_rw_misc_in(void *handle, uint32_t items, uint8_t *buffer)
+{
+       struct stlink_usb_handle_s *h = handle;
+       unsigned int buflen = 2 * 4 * items;
+
+       LOG_DEBUG_IO("%s(%" PRIu32 ")", __func__, items);
+
+       assert(handle != NULL);
+
+       if (!(h->version.flags & STLINK_F_HAS_RW_MISC))
+               return ERROR_COMMAND_NOTFOUND;
+
+       stlink_usb_init_buffer(handle, h->rx_ep, buflen);
+
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_COMMAND;
+       h->cmdbuf[h->cmdidx++] = STLINK_DEBUG_APIV2_RW_MISC_IN;
+
+       int res = stlink_usb_xfer_noerrcheck(handle, h->databuf, buflen);
+       if (res != ERROR_OK)
+               return res;
+
+       memcpy(buffer, h->databuf, buflen);
+
+       return ERROR_OK;
+}
+
 /** */
 static int stlink_read_dap_register(void *handle, unsigned short dap_port,
                        unsigned short addr, uint32_t *val)
@@ -3868,6 +4012,7 @@ struct hl_layout_api_s stlink_usb_layout_api = {
 static struct stlink_usb_handle_s *stlink_dap_handle;
 static struct hl_interface_param_s stlink_dap_param;
 static DECLARE_BITMAP(opened_ap, DP_APSEL_MAX + 1);
+static uint32_t last_csw_default[DP_APSEL_MAX + 1];
 static int stlink_dap_error = ERROR_OK;
 
 /** */
@@ -3912,6 +4057,7 @@ static int stlink_usb_open_ap(void *handle, unsigned short apsel)
 
        LOG_DEBUG("AP %d enabled", apsel);
        set_bit(apsel, opened_ap);
+       last_csw_default[apsel] = 0;
        return ERROR_OK;
 }
 
@@ -3995,6 +4141,8 @@ static int stlink_dap_op_connect(struct adiv5_dap *dap)
 
        dap->do_reconnect = false;
        dap_invalidate_cache(dap);
+       for (unsigned int i = 0; i <= DP_APSEL_MAX; i++)
+               last_csw_default[i] = 0;
 
        retval = dap_dp_init(dap);
        if (retval != ERROR_OK) {
@@ -4132,6 +4280,97 @@ static int stlink_dap_op_queue_ap_abort(struct adiv5_dap *dap, uint8_t *ack)
        return ERROR_OK;
 }
 
+#define RW_MISC_CMD_ADDRESS     1
+#define RW_MISC_CMD_WRITE       2
+#define RW_MISC_CMD_READ        3
+#define RW_MISC_CMD_APNUM       5
+
+static int stlink_usb_misc_rw_segment(void *handle, const struct dap_queue *q, unsigned int len, unsigned int items)
+{
+       uint8_t buf[2 * 4 * items];
+
+       LOG_DEBUG("Queue: %u commands in %u items", len, items);
+
+       int ap_num = DP_APSEL_INVALID;
+       unsigned int cmd_index = 0;
+       unsigned int val_index = ALIGN_UP(items, 4);
+       for (unsigned int i = 0; i < len; i++) {
+               if (ap_num != q[i].mem_ap.ap->ap_num) {
+                       ap_num = q[i].mem_ap.ap->ap_num;
+                       buf[cmd_index++] = RW_MISC_CMD_APNUM;
+                       h_u32_to_le(&buf[val_index], ap_num);
+                       val_index += 4;
+               }
+
+               switch (q[i].cmd) {
+               case CMD_MEM_AP_READ32:
+                       buf[cmd_index++] = RW_MISC_CMD_READ;
+                       h_u32_to_le(&buf[val_index], q[i].mem_ap.addr);
+                       val_index += 4;
+                       break;
+               case CMD_MEM_AP_WRITE32:
+                       buf[cmd_index++] = RW_MISC_CMD_ADDRESS;
+                       h_u32_to_le(&buf[val_index], q[i].mem_ap.addr);
+                       val_index += 4;
+                       buf[cmd_index++] = RW_MISC_CMD_WRITE;
+                       h_u32_to_le(&buf[val_index], q[i].mem_ap.data);
+                       val_index += 4;
+                       break;
+               default:
+                       /* Not supposed to happen */
+                       return ERROR_FAIL;
+               }
+       }
+       /* pad after last command */
+       while (!IS_ALIGNED(cmd_index, 4))
+               buf[cmd_index++] = 0;
+
+       int retval = stlink_usb_rw_misc_out(handle, items, buf);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = stlink_usb_rw_misc_in(handle, items, buf);
+       if (retval != ERROR_OK)
+               return retval;
+
+       ap_num = DP_APSEL_INVALID;
+       val_index = 0;
+       unsigned int err_index = 4 * items;
+       for (unsigned int i = 0; i < len; i++) {
+               uint32_t errcode = le_to_h_u32(&buf[err_index]);
+               if (errcode != STLINK_DEBUG_ERR_OK) {
+                       LOG_ERROR("unknown/unexpected STLINK status code 0x%x", errcode);
+                       return ERROR_FAIL;
+               }
+               if (ap_num != q[i].mem_ap.ap->ap_num) {
+                       ap_num = q[i].mem_ap.ap->ap_num;
+                       err_index += 4;
+                       val_index += 4;
+                       errcode = le_to_h_u32(&buf[err_index]);
+                       if (errcode != STLINK_DEBUG_ERR_OK) {
+                               LOG_ERROR("unknown/unexpected STLINK status code 0x%x", errcode);
+                               return ERROR_FAIL;
+                       }
+               }
+
+               if (q[i].cmd == CMD_MEM_AP_READ32) {
+                       *q[i].mem_ap.p_data = le_to_h_u32(&buf[val_index]);
+               } else { /* q[i]->cmd == CMD_MEM_AP_WRITE32 */
+                       err_index += 4;
+                       val_index += 4;
+                       errcode = le_to_h_u32(&buf[err_index]);
+                       if (errcode != STLINK_DEBUG_ERR_OK) {
+                               LOG_ERROR("unknown/unexpected STLINK status code 0x%x", errcode);
+                               return ERROR_FAIL;
+                       }
+               }
+               err_index += 4;
+               val_index += 4;
+       }
+
+       return ERROR_OK;
+}
+
 static int stlink_usb_buf_rw_segment(void *handle, const struct dap_queue *q, unsigned int count)
 {
        uint32_t bufsize = count * CMD_MEM_AP_2_SIZE(q[0].cmd);
@@ -4158,7 +4397,10 @@ static int stlink_usb_buf_rw_segment(void *handle, const struct dap_queue *q, un
        case CMD_MEM_AP_WRITE32:
                for (unsigned int i = 0; i < count; i++)
                        h_u32_to_le(&buf[4 * i], q[i].mem_ap.data);
-               return stlink_usb_write_mem32(stlink_dap_handle, ap_num, csw, addr, bufsize, buf);
+               if (count > 1 && q[0].mem_ap.addr == q[1].mem_ap.addr)
+                       return stlink_usb_write_mem32_noaddrinc(stlink_dap_handle, ap_num, csw, addr, bufsize, buf);
+               else
+                       return stlink_usb_write_mem32(stlink_dap_handle, ap_num, csw, addr, bufsize, buf);
 
        case CMD_MEM_AP_READ8:
                retval = stlink_usb_read_mem8(stlink_dap_handle, ap_num, csw, addr, bufsize, buf);
@@ -4175,7 +4417,10 @@ static int stlink_usb_buf_rw_segment(void *handle, const struct dap_queue *q, un
                return retval;
 
        case CMD_MEM_AP_READ32:
-               retval = stlink_usb_read_mem32(stlink_dap_handle, ap_num, csw, addr, bufsize, buf);
+               if (count > 1 && q[0].mem_ap.addr == q[1].mem_ap.addr)
+                       retval = stlink_usb_read_mem32_noaddrinc(stlink_dap_handle, ap_num, csw, addr, bufsize, buf);
+               else
+                       retval = stlink_usb_read_mem32(stlink_dap_handle, ap_num, csw, addr, bufsize, buf);
                if (retval == ERROR_OK)
                        for (unsigned int i = 0; i < count; i++)
                                *q[i].mem_ap.p_data = le_to_h_u32(&buf[4 * i]);
@@ -4186,6 +4431,48 @@ static int stlink_usb_buf_rw_segment(void *handle, const struct dap_queue *q, un
        };
 }
 
+/* TODO: recover these values with cmd STLINK_DEBUG_APIV2_RW_MISC_GET_MAX (0x53) */
+#define STLINK_V2_RW_MISC_SIZE (64)
+#define STLINK_V3_RW_MISC_SIZE (1227)
+
+static int stlink_usb_count_misc_rw_queue(void *handle, const struct dap_queue *q, unsigned int len,
+               unsigned int *pkt_items)
+{
+       struct stlink_usb_handle_s *h = handle;
+       unsigned int i, items = 0;
+       int ap_num = DP_APSEL_INVALID;
+       unsigned int misc_max_items = (h->version.stlink == 2) ? STLINK_V2_RW_MISC_SIZE : STLINK_V3_RW_MISC_SIZE;
+
+       if (!(h->version.flags & STLINK_F_HAS_RW_MISC))
+               return 0;
+       /*
+        * RW_MISC sequence doesn't lock the st-link, so are not safe in shared mode.
+        * Don't use it with TCP backend to prevent any issue in case of sharing.
+        * This further degrades the performance, on top of TCP server overhead.
+        */
+       if (h->backend == &stlink_tcp_backend)
+               return 0;
+
+       for (i = 0; i < len; i++) {
+               if (q[i].cmd != CMD_MEM_AP_READ32 && q[i].cmd != CMD_MEM_AP_WRITE32)
+                       break;
+               unsigned int count = 1;
+               if (ap_num != q[i].mem_ap.ap->ap_num) {
+                       count++;
+                       ap_num = q[i].mem_ap.ap->ap_num;
+               }
+               if (q[i].cmd == CMD_MEM_AP_WRITE32)
+                       count++;
+               if (items + count > misc_max_items)
+                       break;
+               items += count;
+       }
+
+       *pkt_items = items;
+
+       return i;
+}
+
 static int stlink_usb_count_buf_rw_queue(const struct dap_queue *q, unsigned int len)
 {
        uint32_t incr = CMD_MEM_AP_2_SIZE(q[0].cmd);
@@ -4196,6 +4483,10 @@ static int stlink_usb_count_buf_rw_queue(const struct dap_queue *q, unsigned int
        else
                len_max = STLINK_MAX_RW16_32 / incr;
 
+       /* check for no address increment, 32 bits only */
+       if (len > 1 && incr == 4 && q[0].mem_ap.addr == q[1].mem_ap.addr)
+               incr = 0;
+
        if (len > len_max)
                len = len_max;
 
@@ -4211,9 +4502,19 @@ static int stlink_usb_count_buf_rw_queue(const struct dap_queue *q, unsigned int
 
 static int stlink_usb_mem_rw_queue(void *handle, const struct dap_queue *q, unsigned int len, unsigned int *skip)
 {
-       unsigned int count = stlink_usb_count_buf_rw_queue(q, len);
+       unsigned int count, misc_items = 0;
+       int retval;
 
-       int retval = stlink_usb_buf_rw_segment(handle, q, count);
+       unsigned int count_misc = stlink_usb_count_misc_rw_queue(handle, q, len, &misc_items);
+       unsigned int count_buf = stlink_usb_count_buf_rw_queue(q, len);
+
+       if (count_misc > count_buf) {
+               count = count_misc;
+               retval = stlink_usb_misc_rw_segment(handle, q, count, misc_items);
+       } else {
+               count = count_buf;
+               retval = stlink_usb_buf_rw_segment(handle, q, count_buf);
+       }
        if (retval != ERROR_OK)
                return retval;
 
@@ -4390,6 +4691,8 @@ static int stlink_dap_op_queue_ap_read(struct adiv5_ap *ap, unsigned int reg,
        unsigned int i = stlink_dap_handle->queue_index++;
        struct dap_queue *q = &stlink_dap_handle->queue[i];
 
+       /* test STLINK_F_HAS_CSW implicitly tests STLINK_F_HAS_MEM_16BIT, STLINK_F_HAS_MEM_RD_NO_INC
+        * and STLINK_F_HAS_RW_MISC */
        if ((stlink_dap_handle->version.flags & STLINK_F_HAS_CSW) &&
                        (reg == MEM_AP_REG_DRW || reg == MEM_AP_REG_BD0 || reg == MEM_AP_REG_BD1 ||
                         reg == MEM_AP_REG_BD2 || reg == MEM_AP_REG_BD3)) {
@@ -4401,8 +4704,9 @@ static int stlink_dap_op_queue_ap_read(struct adiv5_ap *ap, unsigned int reg,
                        q = prev_q;
                        prev_q--;
                }
-               /* de-queue previous write-CSW */
-               if (i && prev_q->cmd == CMD_AP_WRITE && prev_q->ap_w.ap == ap && prev_q->ap_w.reg == MEM_AP_REG_CSW) {
+               /* de-queue previous write-CSW if it didn't changed ap->csw_default */
+               if (i && prev_q->cmd == CMD_AP_WRITE && prev_q->ap_w.ap == ap && prev_q->ap_w.reg == MEM_AP_REG_CSW &&
+                               !prev_q->ap_w.changes_csw_default) {
                        stlink_dap_handle->queue_index = i;
                        q = prev_q;
                }
@@ -4453,6 +4757,8 @@ static int stlink_dap_op_queue_ap_write(struct adiv5_ap *ap, unsigned int reg,
        unsigned int i = stlink_dap_handle->queue_index++;
        struct dap_queue *q = &stlink_dap_handle->queue[i];
 
+       /* test STLINK_F_HAS_CSW implicitly tests STLINK_F_HAS_MEM_16BIT, STLINK_F_HAS_MEM_WR_NO_INC
+        * and STLINK_F_HAS_RW_MISC */
        if ((stlink_dap_handle->version.flags & STLINK_F_HAS_CSW) &&
                        (reg == MEM_AP_REG_DRW || reg == MEM_AP_REG_BD0 || reg == MEM_AP_REG_BD1 ||
                         reg == MEM_AP_REG_BD2 || reg == MEM_AP_REG_BD3)) {
@@ -4464,8 +4770,9 @@ static int stlink_dap_op_queue_ap_write(struct adiv5_ap *ap, unsigned int reg,
                        q = prev_q;
                        prev_q--;
                }
-               /* de-queue previous write-CSW */
-               if (i && prev_q->cmd == CMD_AP_WRITE && prev_q->ap_w.ap == ap && prev_q->ap_w.reg == MEM_AP_REG_CSW) {
+               /* de-queue previous write-CSW if it didn't changed ap->csw_default */
+               if (i && prev_q->cmd == CMD_AP_WRITE && prev_q->ap_w.ap == ap && prev_q->ap_w.reg == MEM_AP_REG_CSW &&
+                               !prev_q->ap_w.changes_csw_default) {
                        stlink_dap_handle->queue_index = i;
                        q = prev_q;
                }
@@ -4499,6 +4806,12 @@ static int stlink_dap_op_queue_ap_write(struct adiv5_ap *ap, unsigned int reg,
                q->ap_w.reg = reg;
                q->ap_w.ap = ap;
                q->ap_w.data = data;
+               if (reg == MEM_AP_REG_CSW && ap->csw_default != last_csw_default[ap->ap_num]) {
+                       q->ap_w.changes_csw_default = true;
+                       last_csw_default[ap->ap_num] = ap->csw_default;
+               } else {
+                       q->ap_w.changes_csw_default = false;
+               }
        }
 
        if (i == MAX_QUEUE_DEPTH - 1)

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)