gdb_server: support gdb target description
[openocd.git] / src / server / gdb_server.c
index 5e570f5d5302df3903bb81f05e347a7b4e5bb336..beeeedcce1470fc804cdd96274e650370c4c3528 100644 (file)
  *   Copyright (C) ST-Ericsson SA 2011                                     *
  *   michel.jaouen@stericsson.com : smp minimum support                    *
  *                                                                         *
+ *   Copyright (C) 2013 Andes Technology                                   *
+ *   Hsiangkai Wang <hkwang@andestech.com>                                 *
+ *                                                                         *
+ *   Copyright (C) 2013 Franck Jullien                                     *
+ *   elec4fun@gmail.com                                                    *
+ *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   it under the terms of the GNU General Public License as published by  *
  *   the Free Software Foundation; either version 2 of the License, or     *
@@ -27,7 +33,7 @@
  *   You should have received a copy of the GNU General Public License     *
  *   along with this program; if not, write to the                         *
  *   Free Software Foundation, Inc.,                                       *
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.           *
  ***************************************************************************/
 
 #ifdef HAVE_CONFIG_H
@@ -93,7 +99,6 @@ static enum breakpoint_type gdb_breakpoint_override_type;
 static int gdb_error(struct connection *connection, int retval);
 static const char *gdb_port;
 static const char *gdb_port_next;
-static const char DIGITS[16] = "0123456789abcdef";
 
 static void gdb_log_callback(void *priv, const char *file, unsigned line,
                const char *function, const char *string);
@@ -115,6 +120,11 @@ static int gdb_flash_program = 1;
  */
 static int gdb_report_data_abort;
 
+/* set if we are sending target descriptions to gdb
+ * via qXfer:features:read packet */
+/* disabled by default */
+static int gdb_use_target_description;
+
 static int gdb_last_signal(struct target *target)
 {
        switch (target->debug_reason) {
@@ -379,18 +389,14 @@ static int gdb_put_packet_inner(struct connection *connection,
                if ((size_t)len + 4 <= sizeof(local_buffer)) {
                        /* performance gain on smaller packets by only a single call to gdb_write() */
                        memcpy(local_buffer + 1, buffer, len++);
-                       local_buffer[len++] = '#';
-                       local_buffer[len++] = DIGITS[(my_checksum >> 4) & 0xf];
-                       local_buffer[len++] = DIGITS[my_checksum & 0xf];
+                       len += snprintf(local_buffer + len, sizeof(local_buffer) - len, "#%02x", my_checksum);
                        retval = gdb_write(connection, local_buffer, len);
                        if (retval != ERROR_OK)
                                return retval;
                } else {
                        /* larger packets are transmitted directly from caller supplied buffer
                         * by several calls to gdb_write() to avoid dynamic allocation */
-                       local_buffer[1] = '#';
-                       local_buffer[2] = DIGITS[(my_checksum >> 4) & 0xf];
-                       local_buffer[3] = DIGITS[my_checksum & 0xf];
+                       snprintf(local_buffer + 1, sizeof(local_buffer) - 1, "#%02x", my_checksum);
                        retval = gdb_write(connection, local_buffer, 1);
                        if (retval != ERROR_OK)
                                return retval;
@@ -724,7 +730,6 @@ static int gdb_target_callback_event_handler(struct target *target,
        int retval;
        struct connection *connection = priv;
 
-       target_handle_event(target, event);
        switch (event) {
                case TARGET_EVENT_GDB_HALT:
                        gdb_frontend_halted(target, connection);
@@ -900,7 +905,7 @@ static int gdb_last_signal_packet(struct connection *connection,
        return ERROR_OK;
 }
 
-static int gdb_reg_pos(struct target *target, int pos, int len)
+static inline int gdb_reg_pos(struct target *target, int pos, int len)
 {
        if (target->endianness == TARGET_LITTLE_ENDIAN)
                return pos;
@@ -929,22 +934,10 @@ static void gdb_str_to_target(struct target *target,
 
        for (i = 0; i < buf_len; i++) {
                int j = gdb_reg_pos(target, i, buf_len);
-               tstr[i*2]   = DIGITS[(buf[j]>>4) & 0xf];
-               tstr[i*2 + 1] = DIGITS[buf[j]&0xf];
+               tstr += sprintf(tstr, "%02x", buf[j]);
        }
 }
 
-static int hextoint(int c)
-{
-       if (c >= '0' && c <= '9')
-               return c - '0';
-       c = toupper(c);
-       if (c >= 'A' && c <= 'F')
-               return c - 'A' + 10;
-       LOG_ERROR("BUG: invalid register value %08x", c);
-       return 0;
-}
-
 /* copy over in register buffer */
 static void gdb_target_to_reg(struct target *target,
                char *tstr, int str_len, uint8_t *bin)
@@ -956,8 +949,11 @@ static void gdb_target_to_reg(struct target *target,
 
        int i;
        for (i = 0; i < str_len; i += 2) {
-               uint8_t t = hextoint(tstr[i]) << 4;
-               t |= hextoint(tstr[i + 1]);
+               unsigned t;
+               if (sscanf(tstr + i, "%02x", &t) != 1) {
+                       LOG_ERROR("BUG: unable to convert register value");
+                       exit(-1);
+               }
 
                int j = gdb_reg_pos(target, i/2, str_len/2);
                bin[j] = t;
@@ -983,7 +979,8 @@ static int gdb_get_registers_packet(struct connection *connection,
        if ((target->rtos != NULL) && (ERROR_OK == rtos_get_gdb_reg_list(connection)))
                return ERROR_OK;
 
-       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
+       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
+                       REG_CLASS_GENERAL);
        if (retval != ERROR_OK)
                return gdb_error(connection, retval);
 
@@ -992,7 +989,7 @@ static int gdb_get_registers_packet(struct connection *connection,
 
        assert(reg_packet_size > 0);
 
-       reg_packet = malloc(reg_packet_size);
+       reg_packet = malloc(reg_packet_size + 1); /* plus one for string termination null */
        reg_packet_p = reg_packet;
 
        for (i = 0; i < reg_list_size; i++) {
@@ -1042,7 +1039,8 @@ static int gdb_set_registers_packet(struct connection *connection,
                return ERROR_SERVER_REMOTE_CLOSED;
        }
 
-       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
+       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
+                       REG_CLASS_GENERAL);
        if (retval != ERROR_OK)
                return gdb_error(connection, retval);
 
@@ -1087,7 +1085,8 @@ static int gdb_get_register_packet(struct connection *connection,
        LOG_DEBUG("-");
 #endif
 
-       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
+       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
+                       REG_CLASS_ALL);
        if (retval != ERROR_OK)
                return gdb_error(connection, retval);
 
@@ -1099,7 +1098,7 @@ static int gdb_get_register_packet(struct connection *connection,
        if (!reg_list[reg_num]->valid)
                reg_list[reg_num]->type->get(reg_list[reg_num]);
 
-       reg_packet = malloc(DIV_ROUND_UP(reg_list[reg_num]->size, 8) * 2);
+       reg_packet = malloc(DIV_ROUND_UP(reg_list[reg_num]->size, 8) * 2 + 1); /* plus one for string termination null */
 
        gdb_str_to_target(target, reg_packet, reg_list[reg_num]);
 
@@ -1124,7 +1123,8 @@ static int gdb_set_register_packet(struct connection *connection,
 
        LOG_DEBUG("-");
 
-       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
+       retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
+                       REG_CLASS_ALL);
        if (retval != ERROR_OK)
                return gdb_error(connection, retval);
 
@@ -1273,7 +1273,7 @@ static int gdb_write_memory_packet(struct connection *connection,
 
        LOG_DEBUG("addr: 0x%8.8" PRIx32 ", len: 0x%8.8" PRIx32 "", addr, len);
 
-       if (unhexify((char *)buffer, separator + 2, len) != (int)len)
+       if (unhexify((char *)buffer, separator, len) != (int)len)
                LOG_ERROR("unable to decode memory packet");
 
        retval = target_write_buffer(target, addr, len, buffer);
@@ -1685,6 +1685,331 @@ static int gdb_memory_map(struct connection *connection,
        return ERROR_OK;
 }
 
+static const char *gdb_get_reg_type_name(enum reg_type type)
+{
+       switch (type) {
+               case REG_TYPE_INT8:
+                       return "int8";
+               case REG_TYPE_INT16:
+                       return "int16";
+               case REG_TYPE_INT32:
+                       return "int32";
+               case REG_TYPE_INT64:
+                       return "int64";
+               case REG_TYPE_INT128:
+                       return "int128";
+               case REG_TYPE_UINT8:
+                       return "uint8";
+               case REG_TYPE_UINT16:
+                       return "uint16";
+               case REG_TYPE_UINT32:
+                       return "uint32";
+               case REG_TYPE_UINT64:
+                       return "uint64";
+               case REG_TYPE_UINT128:
+                       return "uint128";
+               case REG_TYPE_CODE_PTR:
+                       return "code_ptr";
+               case REG_TYPE_DATA_PTR:
+                       return "data_ptr";
+               case REG_TYPE_IEEE_SINGLE:
+                       return "ieee_single";
+               case REG_TYPE_IEEE_DOUBLE:
+                       return "ieee_double";
+               case REG_TYPE_ARCH_DEFINED:
+                       return "int"; /* return arbitrary string to avoid compile warning. */
+       }
+
+       return "int"; /* "int" as default value */
+}
+
+static int gdb_generate_reg_type_description(struct target *target,
+               char **tdesc, int *pos, int *size, struct reg_data_type *type)
+{
+       int retval = ERROR_OK;
+
+       if (type->type_class == REG_TYPE_CLASS_VECTOR) {
+               /* <vector id="id" type="type" count="count"/> */
+               xml_printf(&retval, tdesc, pos, size,
+                               "<vector id=\"%s\" type=\"%s\" count=\"%d\"/>\n",
+                               type->id, type->reg_type_vector->type->id,
+                               type->reg_type_vector->count);
+
+       } else if (type->type_class == REG_TYPE_CLASS_UNION) {
+               /* <union id="id">
+                *  <field name="name" type="type"/> ...
+                * </union> */
+               xml_printf(&retval, tdesc, pos, size,
+                               "<union id=\"%s\">\n",
+                               type->id);
+
+               struct reg_data_type_union_field *field;
+               field = type->reg_type_union->fields;
+               while (field != NULL) {
+                       xml_printf(&retval, tdesc, pos, size,
+                                       "<field name=\"%s\" type=\"%s\"/>\n",
+                                       field->name, field->type->id);
+
+                       field = field->next;
+               }
+
+               xml_printf(&retval, tdesc, pos, size,
+                               "</union>\n");
+
+       } else if (type->type_class == REG_TYPE_CLASS_STRUCT) {
+               struct reg_data_type_struct_field *field;
+               field = type->reg_type_struct->fields;
+
+               if (field->use_bitfields) {
+                       /* <struct id="id" size="size">
+                        *  <field name="name" start="start" end="end"/> ...
+                        * </struct> */
+                       xml_printf(&retval, tdesc, pos, size,
+                                       "<struct id=\"%s\" size=\"%d\">\n",
+                                       type->id, type->reg_type_struct->size);
+                       while (field != NULL) {
+                               xml_printf(&retval, tdesc, pos, size,
+                                               "<field name=\"%s\" start=\"%d\" end=\"%d\"/>\n",
+                                               field->name, field->bitfield->start,
+                                               field->bitfield->end);
+
+                               field = field->next;
+                       }
+               } else {
+                       /* <struct id="id">
+                        *  <field name="name" type="type"/> ...
+                        * </struct> */
+                       xml_printf(&retval, tdesc, pos, size,
+                                       "<struct id=\"%s\">\n",
+                                       type->id);
+                       while (field != NULL) {
+                               xml_printf(&retval, tdesc, pos, size,
+                                               "<field name=\"%s\" type=\"%s\"/>\n",
+                                               field->name, field->type->id);
+
+                               field = field->next;
+                       }
+               }
+
+               xml_printf(&retval, tdesc, pos, size,
+                               "</struct>\n");
+
+       } else if (type->type_class == REG_TYPE_CLASS_FLAGS) {
+               /* <flags id="id" size="size">
+                *  <field name="name" start="start" end="end"/> ...
+                * </flags> */
+               xml_printf(&retval, tdesc, pos, size,
+                               "<flags id=\"%s\" size=\"%d\">\n",
+                               type->id, type->reg_type_flags->size);
+
+               struct reg_data_type_flags_field *field;
+               field = type->reg_type_flags->fields;
+               while (field != NULL) {
+                       xml_printf(&retval, tdesc, pos, size,
+                                       "<field name=\"%s\" start=\"%d\" end=\"%d\"/>\n",
+                                       field->name, field->bitfield->start, field->bitfield->end);
+
+                       field = field->next;
+               }
+
+               xml_printf(&retval, tdesc, pos, size,
+                               "</flags>\n");
+
+       }
+
+       return ERROR_OK;
+}
+
+/* Get a list of available target registers features. feature_list must
+ * be freed by caller.
+ */
+int get_reg_features_list(struct target *target, char **feature_list[], int *feature_list_size,
+               struct reg **reg_list, int reg_list_size)
+{
+       int tbl_sz = 0;
+
+       /* Start with only one element */
+       *feature_list = calloc(1, sizeof(char *));
+
+       for (int i = 0; i < reg_list_size; i++) {
+               if (reg_list[i]->exist == false)
+                       continue;
+
+               if ((reg_list[i]->feature->name != NULL)
+                       && (strcmp(reg_list[i]->feature->name, ""))) {
+                       /* We found a feature, check if the feature is already in the
+                        * table. If not, allocate a new entry for the table and
+                        * put the new feature in it.
+                        */
+                       for (int j = 0; j < (tbl_sz + 1); j++) {
+                               if (!((*feature_list)[j])) {
+                                       (*feature_list)[tbl_sz++] = strdup(reg_list[i]->feature->name);
+                                       *feature_list = realloc(*feature_list, sizeof(char *) * (tbl_sz + 1));
+                                       (*feature_list)[tbl_sz] = NULL;
+                                       break;
+                               } else {
+                                       if (!strcmp((*feature_list)[j], reg_list[i]->feature->name))
+                                               break;
+                               }
+                       }
+               }
+       }
+
+       if (feature_list_size)
+               *feature_list_size = tbl_sz;
+
+       return ERROR_OK;
+}
+
+static int gdb_generate_target_description(struct target *target, char **tdesc)
+{
+       int retval = ERROR_OK;
+       struct reg **reg_list;
+       int reg_list_size;
+       int pos = 0;
+       int size = 0;
+
+       xml_printf(&retval, tdesc, &pos, &size,
+                       "<?xml version=\"1.0\"?>\n"
+                       "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
+                       "<target version=\"1.0\">\n");
+
+       retval = target_get_gdb_reg_list(target, &reg_list,
+                       &reg_list_size, REG_CLASS_ALL);
+
+       if (retval != ERROR_OK) {
+               LOG_ERROR("get register list failed");
+               return ERROR_FAIL;
+       }
+
+       if (reg_list_size <= 0)
+               return ERROR_FAIL;
+
+       char **features = NULL;
+       /* Get a list of available target registers features */
+       retval = get_reg_features_list(target, &features, NULL, reg_list, reg_list_size);
+       if (retval != ERROR_OK) {
+               LOG_ERROR("Can't get the registers feature list");
+               return ERROR_FAIL;
+       }
+
+       /* If we found some features associated with registers, create sections */
+       int current_feature = 0;
+
+       /* generate target description according to register list */
+       if (features != NULL) {
+               while (features[current_feature]) {
+
+                       xml_printf(&retval, tdesc, &pos, &size,
+                                       "<feature name=\"%s\">\n",
+                                       features[current_feature]);
+
+                       int i;
+                       for (i = 0; i < reg_list_size; i++) {
+
+                               if (reg_list[i]->exist == false)
+                                       continue;
+
+                               if (strcmp(reg_list[i]->feature->name, features[current_feature]))
+                                       continue;
+
+                               const char *type_str;
+                               if (reg_list[i]->reg_data_type != NULL) {
+                                       if (reg_list[i]->reg_data_type->type == REG_TYPE_ARCH_DEFINED) {
+                                               /* generate <type... first, if there are architecture-defined types. */
+                                               gdb_generate_reg_type_description(target, tdesc, &pos, &size,
+                                                               reg_list[i]->reg_data_type);
+
+                                               type_str = reg_list[i]->reg_data_type->id;
+                                       } else {
+                                               /* predefined type */
+                                               type_str = gdb_get_reg_type_name(
+                                                               reg_list[i]->reg_data_type->type);
+                                       }
+                               } else {
+                                       /* Default type is "int" */
+                                       type_str = "int";
+                               }
+
+                               xml_printf(&retval, tdesc, &pos, &size,
+                                               "<reg name=\"%s\"", reg_list[i]->name);
+                               xml_printf(&retval, tdesc, &pos, &size,
+                                               " bitsize=\"%d\"", reg_list[i]->size);
+                               xml_printf(&retval, tdesc, &pos, &size,
+                                               " regnum=\"%d\"", reg_list[i]->number);
+                               if (reg_list[i]->caller_save)
+                                       xml_printf(&retval, tdesc, &pos, &size,
+                                                       " save-restore=\"yes\"");
+                               else
+                                       xml_printf(&retval, tdesc, &pos, &size,
+                                                       " save-restore=\"no\"");
+
+                               xml_printf(&retval, tdesc, &pos, &size,
+                                               " type=\"%s\"", type_str);
+
+                               if (reg_list[i]->group != NULL)
+                                       xml_printf(&retval, tdesc, &pos, &size,
+                                                       " group=\"%s\"", reg_list[i]->group);
+
+                               xml_printf(&retval, tdesc, &pos, &size,
+                                               "/>\n");
+                       }
+
+                       xml_printf(&retval, tdesc, &pos, &size,
+                                       "</feature>\n");
+
+                       current_feature++;
+               }
+       }
+
+       xml_printf(&retval, tdesc, &pos, &size,
+                       "</target>\n");
+
+       if (reg_list != NULL)
+               free(reg_list);
+
+       if (features != NULL)
+               free(features);
+
+       return ERROR_OK;
+}
+
+static int gdb_get_target_description_chunk(struct target *target, char **chunk,
+               int32_t offset, uint32_t length)
+{
+       static char *tdesc;
+       static uint32_t tdesc_length;
+
+       if (tdesc == NULL) {
+               gdb_generate_target_description(target, &tdesc);
+               tdesc_length = strlen(tdesc);
+       }
+
+       char transfer_type;
+
+       if (length < (tdesc_length - offset))
+               transfer_type = 'm';
+       else
+               transfer_type = 'l';
+
+       *chunk = malloc(length + 2);
+       (*chunk)[0] = transfer_type;
+       if (transfer_type == 'm') {
+               strncpy((*chunk) + 1, tdesc + offset, length);
+               (*chunk)[1 + length] = '\0';
+       } else {
+               strncpy((*chunk) + 1, tdesc + offset, tdesc_length - offset);
+               (*chunk)[1 + (tdesc_length - offset)] = '\0';
+
+               /* After gdb-server sends out last chunk, invalidate tdesc. */
+               free(tdesc);
+               tdesc = NULL;
+               tdesc_length = 0;
+       }
+
+       return ERROR_OK;
+}
+
 static int gdb_query_packet(struct connection *connection,
                char *packet, int packet_size)
 {
@@ -1759,9 +2084,10 @@ static int gdb_query_packet(struct connection *connection,
                        &buffer,
                        &pos,
                        &size,
-                       "PacketSize=%x;qXfer:memory-map:read%c;qXfer:features:read-;QStartNoAckMode+",
+                       "PacketSize=%x;qXfer:memory-map:read%c;qXfer:features:read%c;QStartNoAckMode+",
                        (GDB_BUFFER_SIZE - 1),
-                       ((gdb_use_memory_map == 1) && (flash_get_bank_count() > 0)) ? '+' : '-');
+                       ((gdb_use_memory_map == 1) && (flash_get_bank_count() > 0)) ? '+' : '-',
+                       (gdb_use_target_description == 1) ? '+' : '-');
 
                if (retval != ERROR_OK) {
                        gdb_send_error(connection, 01);
@@ -1777,8 +2103,6 @@ static int gdb_query_packet(struct connection *connection,
                return gdb_memory_map(connection, packet, packet_size);
        else if (strncmp(packet, "qXfer:features:read:", 20) == 0) {
                char *xml = NULL;
-               int size = 0;
-               int pos = 0;
                int retval = ERROR_OK;
 
                int offset;
@@ -1793,17 +2117,12 @@ static int gdb_query_packet(struct connection *connection,
                        return ERROR_OK;
                }
 
-               if (strcmp(annex, "target.xml") != 0) {
-                       gdb_send_error(connection, 01);
-                       return ERROR_OK;
-               }
-
-               xml_printf(&retval,
-                       &xml,
-                       &pos,
-                       &size, \
-                       "l < target version=\"1.0\">\n < architecture > arm</architecture>\n</target>\n");
-
+               /* Target should prepare correct target description for annex.
+                * The first character of returned xml is 'm' or 'l'. 'm' for
+                * there are *more* chunks to transfer. 'l' for it is the *last*
+                * chunk of target description.
+                */
+               retval = gdb_get_target_description_chunk(target, &xml, offset, length);
                if (retval != ERROR_OK) {
                        gdb_error(connection, retval);
                        return retval;
@@ -2387,6 +2706,54 @@ COMMAND_HANDLER(handle_gdb_breakpoint_override_command)
        return ERROR_OK;
 }
 
+COMMAND_HANDLER(handle_gdb_target_description_command)
+{
+       if (CMD_ARGC != 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_ENABLE(CMD_ARGV[0], gdb_use_target_description);
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_gdb_save_tdesc_command)
+{
+       static char *tdesc;
+       static uint32_t tdesc_length;
+       struct target *target = get_current_target(CMD_CTX);
+       char *tdesc_filename;
+
+       if (tdesc == NULL) {
+               gdb_generate_target_description(target, &tdesc);
+               tdesc_length = strlen(tdesc);
+       }
+
+       struct fileio fileio;
+       size_t size_written;
+
+       tdesc_filename = malloc(strlen(target_type_name(target)) + 5);
+       sprintf(tdesc_filename, "%s.xml", target_type_name(target));
+
+       int retval = fileio_open(&fileio, tdesc_filename, FILEIO_WRITE, FILEIO_TEXT);
+
+       free(tdesc_filename);
+
+       if (retval != ERROR_OK) {
+               LOG_WARNING("Can't open %s for writing", tdesc_filename);
+               return ERROR_FAIL;
+       }
+
+       retval = fileio_write(&fileio, tdesc_length, tdesc, &size_written);
+
+       fileio_close(&fileio);
+
+       if (retval != ERROR_OK) {
+               LOG_WARNING("Error while writing the tdesc file");
+               return ERROR_FAIL;
+       }
+
+       return ERROR_OK;
+}
+
 static const struct command_registration gdb_command_handlers[] = {
        {
                .name = "gdb_sync",
@@ -2439,6 +2806,19 @@ static const struct command_registration gdb_command_handlers[] = {
                        "to be used by gdb 'break' commands.",
                .usage = "('hard'|'soft'|'disable')"
        },
+       {
+               .name = "gdb_target_description",
+               .handler = handle_gdb_target_description_command,
+               .mode = COMMAND_CONFIG,
+               .help = "enable or disable target description",
+               .usage = "('enable'|'disable')"
+       },
+       {
+               .name = "gdb_save_tdesc",
+               .handler = handle_gdb_save_tdesc_command,
+               .mode = COMMAND_EXEC,
+               .help = "Save the target description file",
+       },
        COMMAND_REGISTRATION_DONE
 };
 

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)