jtagspi/pld: add support from intel driver
[openocd.git] / src / pld / intel.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 /***************************************************************************
4 * Copyright (C) 2022 by Daniel Anselmi *
5 * danselmi@gmx.ch *
6 ***************************************************************************/
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11
12 #include <jtag/jtag.h>
13 #include <jtag/adapter.h>
14 #include <helper/system.h>
15 #include <helper/log.h>
16
17 #include "pld.h"
18 #include "raw_bit.h"
19
20 #define BYPASS 0x3FF
21 #define USER0 0x00C
22 #define USER1 0x00E
23
24 enum intel_family_e {
25 INTEL_CYCLONEIII,
26 INTEL_CYCLONEIV,
27 INTEL_CYCLONEV,
28 INTEL_CYCLONE10,
29 INTEL_ARRIAII,
30 INTEL_UNKNOWN
31 };
32
33 struct intel_pld_device {
34 struct jtag_tap *tap;
35 unsigned int boundary_scan_length;
36 int checkpos;
37 enum intel_family_e family;
38 };
39
40 struct intel_device_parameters_elem {
41 uint32_t id;
42 unsigned int boundary_scan_length;
43 int checkpos;
44 enum intel_family_e family;
45 };
46
47 static const struct intel_device_parameters_elem intel_device_parameters[] = {
48 {0x020f10dd, 603, 226, INTEL_CYCLONEIII}, /* EP3C5 EP3C10 */
49 {0x020f20dd, 1080, 409, INTEL_CYCLONEIII}, /* EP3C16 */
50 {0x020f30dd, 732, 286, INTEL_CYCLONEIII}, /* EP3C25 */
51 {0x020f40dd, 1632, 604, INTEL_CYCLONEIII}, /* EP3C40 */
52 {0x020f50dd, 1164, 442, INTEL_CYCLONEIII}, /* EP3C55 */
53 {0x020f60dd, 1314, 502, INTEL_CYCLONEIII}, /* EP3C80 */
54 {0x020f70dd, 1620, 613, INTEL_CYCLONEIII}, /* EP3C120*/
55 {0x027010dd, 1314, 226, INTEL_CYCLONEIII}, /* EP3CLS70 */
56 {0x027000dd, 1314, 226, INTEL_CYCLONEIII}, /* EP3CLS100 */
57 {0x027030dd, 1314, 409, INTEL_CYCLONEIII}, /* EP3CLS150 */
58 {0x027020dd, 1314, 409, INTEL_CYCLONEIII}, /* EP3CLS200 */
59
60 {0x020f10dd, 603, 226, INTEL_CYCLONEIV}, /* EP4CE6 EP4CE10 */
61 {0x020f20dd, 1080, 409, INTEL_CYCLONEIV}, /* EP4CE15 */
62 {0x020f30dd, 732, 286, INTEL_CYCLONEIV}, /* EP4CE22 */
63 {0x020f40dd, 1632, 604, INTEL_CYCLONEIV}, /* EP4CE30 EP4CE40 */
64 {0x020f50dd, 1164, 442, INTEL_CYCLONEIV}, /* EP4CE55 */
65 {0x020f60dd, 1314, 502, INTEL_CYCLONEIV}, /* EP4CE75 */
66 {0x020f70dd, 1620, 613, INTEL_CYCLONEIV}, /* EP4CE115 */
67 {0x028010dd, 260, 229, INTEL_CYCLONEIV}, /* EP4CGX15 */
68 {0x028120dd, 494, 463, INTEL_CYCLONEIV}, /* EP4CGX22 */
69 {0x028020dd, 494, 463, INTEL_CYCLONEIV}, /* EP4CGX30 */
70 {0x028230dd, 1006, 943, INTEL_CYCLONEIV}, /* EP4CGX30 */
71 {0x028130dd, 1006, 943, INTEL_CYCLONEIV}, /* EP4CGX50 */
72 {0x028030dd, 1006, 943, INTEL_CYCLONEIV}, /* EP4CGX75 */
73 {0x028140dd, 1495, 1438, INTEL_CYCLONEIV}, /* EP4CGX110 */
74 {0x028040dd, 1495, 1438, INTEL_CYCLONEIV}, /* EP4CGX150 */
75
76 {0x02b150dd, 864, 163, INTEL_CYCLONEV}, /* 5CEBA2F23 5CEBA2F17 5CEFA2M13 5CEFA2F23 5CEBA2U15 5CEFA2U19 5CEBA2U19 */
77 {0x02d020dd, 1485, 19, INTEL_CYCLONEV}, /* 5CSXFC6D6F31 5CSTFD6D5F31 5CSEBA6U23 5CSEMA6U23 5CSEBA6U19 5CSEBA6U23
78 5CSEBA6U19 5CSEMA6F31 5CSXFC6C6U23 */
79 {0x02b040dd, 1728, -1, INTEL_CYCLONEV}, /* 5CGXFC9EF35 5CGXBC9AU19 5CGXBC9CF23 5CGTFD9CF23 5CGXFC9AU19 5CGXFC9CF23
80 5CGXFC9EF31 5CGXFC9DF27 5CGXBC9DF27 5CGXBC9EF31 5CGTFD9EF31 5CGTFD9EF35
81 5CGTFD9AU19 5CGXBC9EF35 5CGTFD9DF27 */
82 {0x02b050dd, 864, 163, INTEL_CYCLONEV}, /* 5CEFA4U19 5CEFA4F23 5CEFA4M13 5CEBA4F17 5CEBA4U15 5CEBA4U19 5CEBA4F23 */
83 {0x02b030dd, 1488, 19, INTEL_CYCLONEV}, /* 5CGXBC7CU19 5CGTFD7CU19 5CGTFD7DF27 5CGXFC7BM15 5CGXFC7DF27 5CGXFC7DF31
84 5CGTFD7CF23 5CGXBC7CF23 5CGXBC7DF31 5CGTFD7BM15 5CGXFC7CU19 5CGTFD7DF31
85 5CGXBC7BM15 5CGXFC7CF23 5CGXBC7DF27 */
86 {0x02d120dd, 1485, -1, INTEL_CYCLONEV}, /* 5CSEBA5U23 5CSEBA5U23 5CSTFD5D5F31 5CSEBA5U19 5CSXFC5D6F31 5CSEMA5U23
87 5CSEMA5F31 5CSXFC5C6U23 5CSEBA5U19 */
88 {0x02b220dd, 1104, 19, INTEL_CYCLONEV}, /* 5CEBA5U19 5CEFA5U19 5CEFA5M13 5CEBA5F23 5CEFA5F23 */
89 {0x02b020dd, 1104, 19, INTEL_CYCLONEV}, /* 5CGXBC5CU19 5CGXFC5F6M11 5CGXFC5CM13 5CGTFD5CF23 5CGXBC5CF23 5CGTFD5CF27
90 5CGTFD5F5M11 5CGXFC5CF27 5CGXFC5CU19 5CGTFD5CM13 5CGXFC5CF23 5CGXBC5CF27
91 5CGTFD5CU19 */
92 {0x02d010dd, 1197, -1, INTEL_CYCLONEV}, /* 5CSEBA4U23 5CSXFC4C6U23 5CSEMA4U23 5CSEBA4U23 5CSEBA4U19 5CSEBA4U19
93 5CSXFC2C6U23 */
94 {0x02b120dd, 1104, 19, INTEL_CYCLONEV}, /* 5CGXFC4CM13 5CGXFC4CU19 5CGXFC4F6M11 5CGXBC4CU19 5CGXFC4CF27 5CGXBC4CF23
95 5CGXBC4CF27 5CGXFC4CF23 */
96 {0x02b140dd, 1728, -1, INTEL_CYCLONEV}, /* 5CEFA9F31 5CEBA9F31 5CEFA9F27 5CEBA9U19 5CEBA9F27 5CEFA9U19 5CEBA9F23
97 5CEFA9F23 */
98 {0x02b010dd, 720, 19, INTEL_CYCLONEV}, /* 5CGXFC3U15 5CGXBC3U15 5CGXFC3F23 5CGXFC3U19 5CGXBC3U19 5CGXBC3F23 */
99 {0x02b130dd, 1488, 19, INTEL_CYCLONEV}, /* 5CEFA7F31 5CEBA7F27 5CEBA7M15 5CEFA7U19 5CEBA7F23 5CEFA7F23 5CEFA7F27
100 5CEFA7M15 5CEBA7U19 5CEBA7F31 */
101 {0x02d110dd, 1197, -1, INTEL_CYCLONEV}, /* 5CSEBA2U23 5CSEMA2U23 5CSEBA2U23 5CSEBA2U19 5CSEBA2U19 */
102
103 {0x020f10dd, 603, 226, INTEL_CYCLONE10}, /* 10CL006E144 10CL006U256 10CL010M164 10CL010U256 10CL010E144 */
104 {0x020f20dd, 1080, 409, INTEL_CYCLONE10}, /* 10CL016U256 10CL016E144 10CL016U484 10CL016F484 10CL016M164 */
105 {0x020f30dd, 732, 286, INTEL_CYCLONE10}, /* 10CL025U256 10CL025E144 */
106 {0x020f40dd, 1632, 604, INTEL_CYCLONE10}, /* 10CL040F484 10CL040U484 */
107 {0x020f50dd, 1164, 442, INTEL_CYCLONE10}, /* 10CL055F484 10CL055U484 */
108 {0x020f60dd, 1314, 502, INTEL_CYCLONE10}, /* 10CL080F484 10CL080F780 10CL080U484 */
109 {0x020f70dd, 1620, 613, INTEL_CYCLONE10}, /* 10CL120F484 10CL120F780 */
110
111 {0x02e120dd, 1339, -1, INTEL_CYCLONE10}, /* 10CX085U484 10CX085F672 */
112 {0x02e320dd, 1339, -1, INTEL_CYCLONE10}, /* 10CX105F780 10CX105U484 10CX105F672 */
113 {0x02e720dd, 1339, -1, INTEL_CYCLONE10}, /* 10CX150F672 10CX150F780 10CX150U484 */
114 {0x02ef20dd, 1339, -1, INTEL_CYCLONE10}, /* 10CX220F672 10CX220F780 10CX220U484 */
115
116 {0x025120dd, 1227, 1174, INTEL_ARRIAII}, /* EP2AGX45 */
117 {0x025020dd, 1227, -1, INTEL_ARRIAII}, /* EP2AGX65 */
118 {0x025130dd, 1467, -1, INTEL_ARRIAII}, /* EP2AGX95 */
119 {0x025030dd, 1467, -1, INTEL_ARRIAII}, /* EP2AGX125 */
120 {0x025140dd, 1971, -1, INTEL_ARRIAII}, /* EP2AGX190 */
121 {0x025040dd, 1971, -1, INTEL_ARRIAII}, /* EP2AGX260 */
122 {0x024810dd, 2274, -1, INTEL_ARRIAII}, /* EP2AGZ225 */
123 {0x0240a0dd, 2682, -1, INTEL_ARRIAII}, /* EP2AGZ300 */
124 {0x024820dd, 2682, -1, INTEL_ARRIAII}, /* EP2AGZ350 */
125 };
126
127 static int intel_fill_device_parameters(struct intel_pld_device *intel_info)
128 {
129 for (size_t i = 0; i < ARRAY_SIZE(intel_device_parameters); ++i) {
130 if (intel_device_parameters[i].id == intel_info->tap->idcode &&
131 intel_info->family == intel_device_parameters[i].family) {
132 if (intel_info->boundary_scan_length == 0)
133 intel_info->boundary_scan_length = intel_device_parameters[i].boundary_scan_length;
134
135 if (intel_info->checkpos == -1)
136 intel_info->checkpos = intel_device_parameters[i].checkpos;
137
138 return ERROR_OK;
139 }
140 }
141
142 return ERROR_FAIL;
143 }
144
145 static int intel_check_for_unique_id(struct intel_pld_device *intel_info)
146 {
147 int found = 0;
148 for (size_t i = 0; i < ARRAY_SIZE(intel_device_parameters); ++i) {
149 if (intel_device_parameters[i].id == intel_info->tap->idcode) {
150 ++found;
151 intel_info->family = intel_device_parameters[i].family;
152 }
153 }
154
155 return (found == 1) ? ERROR_OK : ERROR_FAIL;
156 }
157
158 static int intel_check_config(struct intel_pld_device *intel_info)
159 {
160 if (!intel_info->tap->hasidcode) {
161 LOG_ERROR("no IDCODE");
162 return ERROR_FAIL;
163 }
164
165 if (intel_info->family == INTEL_UNKNOWN) {
166 if (intel_check_for_unique_id(intel_info) != ERROR_OK) {
167 LOG_ERROR("id is ambiguous, please specify family");
168 return ERROR_FAIL;
169 }
170 }
171
172 if (intel_info->boundary_scan_length == 0 || intel_info->checkpos == -1) {
173 int ret = intel_fill_device_parameters(intel_info);
174 if (ret != ERROR_OK)
175 return ret;
176 }
177
178 if (intel_info->checkpos >= 0 && (unsigned int)intel_info->checkpos >= intel_info->boundary_scan_length) {
179 LOG_ERROR("checkpos has to be smaller than scan length %d < %u",
180 intel_info->checkpos, intel_info->boundary_scan_length);
181 return ERROR_FAIL;
182 }
183
184 return ERROR_OK;
185 }
186
187 static int intel_read_file(struct raw_bit_file *bit_file, const char *filename)
188 {
189 if (!filename || !bit_file)
190 return ERROR_COMMAND_SYNTAX_ERROR;
191
192 /* check if binary .bin or ascii .bit/.hex */
193 const char *file_ending_pos = strrchr(filename, '.');
194 if (!file_ending_pos) {
195 LOG_ERROR("Unable to detect filename suffix");
196 return ERROR_PLD_FILE_LOAD_FAILED;
197 }
198
199 if (strcasecmp(file_ending_pos, ".rbf") == 0)
200 return cpld_read_raw_bit_file(bit_file, filename);
201
202 LOG_ERROR("Unable to detect filetype");
203 return ERROR_PLD_FILE_LOAD_FAILED;
204 }
205
206 static int intel_set_instr(struct jtag_tap *tap, uint16_t new_instr)
207 {
208 struct scan_field field;
209 field.num_bits = tap->ir_length;
210 void *t = calloc(DIV_ROUND_UP(field.num_bits, 8), 1);
211 if (!t) {
212 LOG_ERROR("Out of memory");
213 return ERROR_FAIL;
214 }
215 field.out_value = t;
216 buf_set_u32(t, 0, field.num_bits, new_instr);
217 field.in_value = NULL;
218 jtag_add_ir_scan(tap, &field, TAP_IDLE);
219 free(t);
220 return ERROR_OK;
221 }
222
223
224 static int intel_load(struct pld_device *pld_device, const char *filename)
225 {
226 unsigned int speed = adapter_get_speed_khz();
227 if (speed < 1)
228 speed = 1;
229
230 unsigned int cycles = DIV_ROUND_UP(speed, 200);
231 if (cycles < 1)
232 cycles = 1;
233
234 if (!pld_device || !pld_device->driver_priv)
235 return ERROR_FAIL;
236
237 struct intel_pld_device *intel_info = pld_device->driver_priv;
238 if (!intel_info || !intel_info->tap)
239 return ERROR_FAIL;
240 struct jtag_tap *tap = intel_info->tap;
241
242 int retval = intel_check_config(intel_info);
243 if (retval != ERROR_OK)
244 return retval;
245
246 struct raw_bit_file bit_file;
247 retval = intel_read_file(&bit_file, filename);
248 if (retval != ERROR_OK)
249 return retval;
250
251 if (retval != ERROR_OK)
252 return retval;
253
254 retval = intel_set_instr(tap, 0x002);
255 if (retval != ERROR_OK) {
256 free(bit_file.data);
257 return retval;
258 }
259 jtag_add_runtest(speed, TAP_IDLE);
260 retval = jtag_execute_queue();
261 if (retval != ERROR_OK) {
262 free(bit_file.data);
263 return retval;
264 }
265
266 /* shift in the bitstream */
267 struct scan_field field;
268 field.num_bits = bit_file.length * 8;
269 field.out_value = bit_file.data;
270 field.in_value = NULL;
271
272 jtag_add_dr_scan(tap, 1, &field, TAP_DRPAUSE);
273 retval = jtag_execute_queue();
274 free(bit_file.data);
275 if (retval != ERROR_OK)
276 return retval;
277
278 retval = intel_set_instr(tap, 0x004);
279 if (retval != ERROR_OK)
280 return retval;
281 jtag_add_runtest(cycles, TAP_IDLE);
282 retval = jtag_execute_queue();
283 if (retval != ERROR_OK)
284 return retval;
285
286 if (intel_info->boundary_scan_length != 0) {
287 uint8_t *buf = calloc(DIV_ROUND_UP(intel_info->boundary_scan_length, 8), 1);
288 if (!buf) {
289 LOG_ERROR("Out of memory");
290 return ERROR_FAIL;
291 }
292
293 field.num_bits = intel_info->boundary_scan_length;
294 field.out_value = buf;
295 field.in_value = buf;
296 jtag_add_dr_scan(tap, 1, &field, TAP_DRPAUSE);
297 retval = jtag_execute_queue();
298 if (retval != ERROR_OK) {
299 free(buf);
300 return retval;
301 }
302
303 if (intel_info->checkpos != -1)
304 retval = ((buf[intel_info->checkpos / 8] & (1 << (intel_info->checkpos % 8)))) ?
305 ERROR_OK : ERROR_FAIL;
306 free(buf);
307 if (retval != ERROR_OK) {
308 LOG_ERROR("Check failed");
309 return ERROR_FAIL;
310 }
311 }
312
313 retval = intel_set_instr(tap, 0x003);
314 if (retval != ERROR_OK)
315 return retval;
316 switch (intel_info->family) {
317 case INTEL_CYCLONEIII:
318 case INTEL_CYCLONEIV:
319 jtag_add_runtest(5 * speed + 512, TAP_IDLE);
320 break;
321 case INTEL_CYCLONEV:
322 jtag_add_runtest(5 * speed + 512, TAP_IDLE);
323 break;
324 case INTEL_CYCLONE10:
325 jtag_add_runtest(DIV_ROUND_UP(512ul * speed, 125ul) + 512, TAP_IDLE);
326 break;
327 case INTEL_ARRIAII:
328 jtag_add_runtest(DIV_ROUND_UP(64ul * speed, 125ul) + 512, TAP_IDLE);
329 break;
330 case INTEL_UNKNOWN:
331 LOG_ERROR("unknown family");
332 return ERROR_FAIL;
333 }
334
335 retval = intel_set_instr(tap, BYPASS);
336 if (retval != ERROR_OK)
337 return retval;
338 jtag_add_runtest(speed, TAP_IDLE);
339 return jtag_execute_queue();
340 }
341
342 static int intel_get_ipdbg_hub(int user_num, struct pld_device *pld_device, struct pld_ipdbg_hub *hub)
343 {
344 if (!pld_device)
345 return ERROR_FAIL;
346
347 struct intel_pld_device *pld_device_info = pld_device->driver_priv;
348
349 if (!pld_device_info || !pld_device_info->tap)
350 return ERROR_FAIL;
351
352 hub->tap = pld_device_info->tap;
353
354 if (user_num == 0) {
355 hub->user_ir_code = USER0;
356 } else if (user_num == 1) {
357 hub->user_ir_code = USER1;
358 } else {
359 LOG_ERROR("intel devices only have user register 0 & 1");
360 return ERROR_FAIL;
361 }
362 return ERROR_OK;
363 }
364
365 static int intel_get_jtagspi_userircode(struct pld_device *pld_device, unsigned int *ir)
366 {
367 *ir = USER1;
368 return ERROR_OK;
369 }
370
371 COMMAND_HANDLER(intel_set_bscan_command_handler)
372 {
373 unsigned int boundary_scan_length;
374
375 if (CMD_ARGC != 2)
376 return ERROR_COMMAND_SYNTAX_ERROR;
377
378 struct pld_device *pld_device = get_pld_device_by_name_or_numstr(CMD_ARGV[0]);
379 if (!pld_device) {
380 command_print(CMD, "pld device '#%s' is out of bounds or unknown", CMD_ARGV[0]);
381 return ERROR_FAIL;
382 }
383
384 COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], boundary_scan_length);
385
386 struct intel_pld_device *intel_info = pld_device->driver_priv;
387
388 if (!intel_info)
389 return ERROR_FAIL;
390
391 intel_info->boundary_scan_length = boundary_scan_length;
392
393 return ERROR_OK;
394 }
395
396 COMMAND_HANDLER(intel_set_check_pos_command_handler)
397 {
398 int checkpos;
399
400 if (CMD_ARGC != 2)
401 return ERROR_COMMAND_SYNTAX_ERROR;
402
403 struct pld_device *pld_device = get_pld_device_by_name_or_numstr(CMD_ARGV[0]);
404 if (!pld_device) {
405 command_print(CMD, "pld device '#%s' is out of bounds or unknown", CMD_ARGV[0]);
406 return ERROR_FAIL;
407 }
408
409 COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], checkpos);
410
411 struct intel_pld_device *intel_info = pld_device->driver_priv;
412
413 if (!intel_info)
414 return ERROR_FAIL;
415
416 intel_info->checkpos = checkpos;
417
418 return ERROR_OK;
419 }
420
421 PLD_CREATE_COMMAND_HANDLER(intel_pld_create_command)
422 {
423 if (CMD_ARGC != 4 && CMD_ARGC != 6)
424 return ERROR_COMMAND_SYNTAX_ERROR;
425
426 if (strcmp(CMD_ARGV[2], "-chain-position") != 0)
427 return ERROR_COMMAND_SYNTAX_ERROR;
428
429 struct jtag_tap *tap = jtag_tap_by_string(CMD_ARGV[3]);
430 if (!tap) {
431 command_print(CMD, "Tap: %s does not exist", CMD_ARGV[3]);
432 return ERROR_FAIL;
433 }
434
435 enum intel_family_e family = INTEL_UNKNOWN;
436 if (CMD_ARGC == 6) {
437 if (strcmp(CMD_ARGV[4], "-family") != 0)
438 return ERROR_COMMAND_SYNTAX_ERROR;
439
440 if (strcmp(CMD_ARGV[5], "cycloneiii") == 0) {
441 family = INTEL_CYCLONEIII;
442 } else if (strcmp(CMD_ARGV[5], "cycloneiv") == 0) {
443 family = INTEL_CYCLONEIV;
444 } else if (strcmp(CMD_ARGV[5], "cyclonev") == 0) {
445 family = INTEL_CYCLONEV;
446 } else if (strcmp(CMD_ARGV[5], "cyclone10") == 0) {
447 family = INTEL_CYCLONE10;
448 } else if (strcmp(CMD_ARGV[5], "arriaii") == 0) {
449 family = INTEL_ARRIAII;
450 } else {
451 command_print(CMD, "unknown family");
452 return ERROR_FAIL;
453 }
454 }
455
456 struct intel_pld_device *intel_info = malloc(sizeof(struct intel_pld_device));
457 if (!intel_info) {
458 LOG_ERROR("Out of memory");
459 return ERROR_FAIL;
460 }
461
462 intel_info->tap = tap;
463 intel_info->boundary_scan_length = 0;
464 intel_info->checkpos = -1;
465 intel_info->family = family;
466
467 pld->driver_priv = intel_info;
468
469 return ERROR_OK;
470 }
471
472 static const struct command_registration intel_exec_command_handlers[] = {
473 {
474 .name = "set_bscan",
475 .mode = COMMAND_ANY,
476 .handler = intel_set_bscan_command_handler,
477 .help = "set boundary scan register length of FPGA",
478 .usage = "pld_name len",
479 }, {
480 .name = "set_check_pos",
481 .mode = COMMAND_ANY,
482 .handler = intel_set_check_pos_command_handler,
483 .help = "set check_pos of FPGA",
484 .usage = "pld_name pos",
485 },
486 COMMAND_REGISTRATION_DONE
487 };
488
489 static const struct command_registration intel_command_handler[] = {
490 {
491 .name = "intel",
492 .mode = COMMAND_ANY,
493 .help = "intel specific commands",
494 .usage = "",
495 .chain = intel_exec_command_handlers,
496 },
497 COMMAND_REGISTRATION_DONE
498 };
499
500 struct pld_driver intel_pld = {
501 .name = "intel",
502 .commands = intel_command_handler,
503 .pld_create_command = &intel_pld_create_command,
504 .load = &intel_load,
505 .get_ipdbg_hub = intel_get_ipdbg_hub,
506 .get_jtagspi_userircode = intel_get_jtagspi_userircode,
507 };

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)