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

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)