945c4b875c59fd05f9ccd8c68dd8c7f49924d034
[openocd.git] / src / rtos / FreeRTOS.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 /***************************************************************************
4 * Copyright (C) 2011 by Broadcom Corporation *
5 * Evan Hunter - ehunter@broadcom.com *
6 ***************************************************************************/
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11
12 #include <helper/time_support.h>
13 #include <jtag/jtag.h>
14 #include "target/target.h"
15 #include "target/target_type.h"
16 #include "rtos.h"
17 #include "helper/log.h"
18 #include "helper/types.h"
19 #include "rtos_standard_stackings.h"
20 #include "target/armv7m.h"
21 #include "target/cortex_m.h"
22
23 #define FREERTOS_MAX_PRIORITIES 63
24
25 /* FIXME: none of the _width parameters are actually observed properly!
26 * you WILL need to edit more if you actually attempt to target a 8/16/64
27 * bit target!
28 */
29
30 struct freertos_params {
31 const char *target_name;
32 const unsigned char thread_count_width;
33 const unsigned char pointer_width;
34 const unsigned char list_next_offset;
35 const unsigned char list_width;
36 const unsigned char list_elem_next_offset;
37 const unsigned char list_elem_content_offset;
38 const unsigned char thread_stack_offset;
39 const unsigned char thread_name_offset;
40 const struct rtos_register_stacking *stacking_info_cm3;
41 const struct rtos_register_stacking *stacking_info_cm4f;
42 const struct rtos_register_stacking *stacking_info_cm4f_fpu;
43 };
44
45 static const struct freertos_params freertos_params_list[] = {
46 {
47 "cortex_m", /* target_name */
48 4, /* thread_count_width; */
49 4, /* pointer_width; */
50 16, /* list_next_offset; */
51 20, /* list_width; */
52 8, /* list_elem_next_offset; */
53 12, /* list_elem_content_offset */
54 0, /* thread_stack_offset; */
55 52, /* thread_name_offset; */
56 &rtos_standard_cortex_m3_stacking, /* stacking_info */
57 &rtos_standard_cortex_m4f_stacking,
58 &rtos_standard_cortex_m4f_fpu_stacking,
59 },
60 {
61 "hla_target", /* target_name */
62 4, /* thread_count_width; */
63 4, /* pointer_width; */
64 16, /* list_next_offset; */
65 20, /* list_width; */
66 8, /* list_elem_next_offset; */
67 12, /* list_elem_content_offset */
68 0, /* thread_stack_offset; */
69 52, /* thread_name_offset; */
70 &rtos_standard_cortex_m3_stacking, /* stacking_info */
71 &rtos_standard_cortex_m4f_stacking,
72 &rtos_standard_cortex_m4f_fpu_stacking,
73 },
74 };
75
76 static bool freertos_detect_rtos(struct target *target);
77 static int freertos_create(struct target *target);
78 static int freertos_update_threads(struct rtos *rtos);
79 static int freertos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
80 struct rtos_reg **reg_list, int *num_regs);
81 static int freertos_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[]);
82
83 struct rtos_type freertos_rtos = {
84 .name = "FreeRTOS",
85
86 .detect_rtos = freertos_detect_rtos,
87 .create = freertos_create,
88 .update_threads = freertos_update_threads,
89 .get_thread_reg_list = freertos_get_thread_reg_list,
90 .get_symbol_list_to_lookup = freertos_get_symbol_list_to_lookup,
91 };
92
93 enum freertos_symbol_values {
94 FREERTOS_VAL_PX_CURRENT_TCB = 0,
95 FREERTOS_VAL_PX_READY_TASKS_LISTS = 1,
96 FREERTOS_VAL_X_DELAYED_TASK_LIST1 = 2,
97 FREERTOS_VAL_X_DELAYED_TASK_LIST2 = 3,
98 FREERTOS_VAL_PX_DELAYED_TASK_LIST = 4,
99 FREERTOS_VAL_PX_OVERFLOW_DELAYED_TASK_LIST = 5,
100 FREERTOS_VAL_X_PENDING_READY_LIST = 6,
101 FREERTOS_VAL_X_TASKS_WAITING_TERMINATION = 7,
102 FREERTOS_VAL_X_SUSPENDED_TASK_LIST = 8,
103 FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS = 9,
104 FREERTOS_VAL_UX_TOP_USED_PRIORITY = 10,
105 FREERTOS_VAL_X_SCHEDULER_RUNNING = 11,
106 };
107
108 struct symbols {
109 const char *name;
110 bool optional;
111 };
112
113 static const struct symbols freertos_symbol_list[] = {
114 { "pxCurrentTCB", false },
115 { "pxReadyTasksLists", false },
116 { "xDelayedTaskList1", false },
117 { "xDelayedTaskList2", false },
118 { "pxDelayedTaskList", false },
119 { "pxOverflowDelayedTaskList", false },
120 { "xPendingReadyList", false },
121 { "xTasksWaitingTermination", true }, /* Only if INCLUDE_vTaskDelete */
122 { "xSuspendedTaskList", true }, /* Only if INCLUDE_vTaskSuspend */
123 { "uxCurrentNumberOfTasks", false },
124 { "uxTopUsedPriority", true }, /* Unavailable since v7.5.3 */
125 { "xSchedulerRunning", false },
126 { NULL, false }
127 };
128
129 /* TODO: */
130 /* this is not safe for little endian yet */
131 /* may be problems reading if sizes are not 32 bit long integers. */
132 /* test mallocs for failure */
133
134 static int freertos_update_threads(struct rtos *rtos)
135 {
136 int retval;
137 unsigned int tasks_found = 0;
138 const struct freertos_params *param;
139
140 if (!rtos->rtos_specific_params)
141 return -1;
142
143 param = (const struct freertos_params *) rtos->rtos_specific_params;
144
145 if (!rtos->symbols) {
146 LOG_ERROR("No symbols for FreeRTOS");
147 return -3;
148 }
149
150 if (rtos->symbols[FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS].address == 0) {
151 LOG_ERROR("Don't have the number of threads in FreeRTOS");
152 return -2;
153 }
154
155 uint32_t thread_list_size = 0;
156 retval = target_read_u32(rtos->target,
157 rtos->symbols[FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS].address,
158 &thread_list_size);
159 LOG_DEBUG("FreeRTOS: Read uxCurrentNumberOfTasks at 0x%" PRIx64 ", value %" PRIu32,
160 rtos->symbols[FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS].address,
161 thread_list_size);
162
163 if (retval != ERROR_OK) {
164 LOG_ERROR("Could not read FreeRTOS thread count from target");
165 return retval;
166 }
167
168 /* wipe out previous thread details if any */
169 rtos_free_threadlist(rtos);
170
171 /* read the current thread */
172 uint32_t pointer_casts_are_bad;
173 retval = target_read_u32(rtos->target,
174 rtos->symbols[FREERTOS_VAL_PX_CURRENT_TCB].address,
175 &pointer_casts_are_bad);
176 if (retval != ERROR_OK) {
177 LOG_ERROR("Error reading current thread in FreeRTOS thread list");
178 return retval;
179 }
180 rtos->current_thread = pointer_casts_are_bad;
181 LOG_DEBUG("FreeRTOS: Read pxCurrentTCB at 0x%" PRIx64 ", value 0x%" PRIx64,
182 rtos->symbols[FREERTOS_VAL_PX_CURRENT_TCB].address,
183 rtos->current_thread);
184
185 /* read scheduler running */
186 uint32_t scheduler_running;
187 retval = target_read_u32(rtos->target,
188 rtos->symbols[FREERTOS_VAL_X_SCHEDULER_RUNNING].address,
189 &scheduler_running);
190 if (retval != ERROR_OK) {
191 LOG_ERROR("Error reading FreeRTOS scheduler state");
192 return retval;
193 }
194 LOG_DEBUG("FreeRTOS: Read xSchedulerRunning at 0x%" PRIx64 ", value 0x%" PRIx32,
195 rtos->symbols[FREERTOS_VAL_X_SCHEDULER_RUNNING].address,
196 scheduler_running);
197
198 if ((thread_list_size == 0) || (rtos->current_thread == 0) || (scheduler_running != 1)) {
199 /* Either : No RTOS threads - there is always at least the current execution though */
200 /* OR : No current thread - all threads suspended - show the current execution
201 * of idling */
202 char tmp_str[] = "Current Execution";
203 thread_list_size++;
204 tasks_found++;
205 rtos->thread_details = malloc(
206 sizeof(struct thread_detail) * thread_list_size);
207 if (!rtos->thread_details) {
208 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
209 return ERROR_FAIL;
210 }
211 rtos->current_thread = 1;
212 rtos->thread_details->threadid = rtos->current_thread;
213 rtos->thread_details->exists = true;
214 rtos->thread_details->extra_info_str = NULL;
215 rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str));
216 strcpy(rtos->thread_details->thread_name_str, tmp_str);
217
218 if (thread_list_size == 1) {
219 rtos->thread_count = 1;
220 return ERROR_OK;
221 }
222 } else {
223 /* create space for new thread details */
224 rtos->thread_details = malloc(
225 sizeof(struct thread_detail) * thread_list_size);
226 if (!rtos->thread_details) {
227 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
228 return ERROR_FAIL;
229 }
230 }
231
232 /* Find out how many lists are needed to be read from pxReadyTasksLists, */
233 if (rtos->symbols[FREERTOS_VAL_UX_TOP_USED_PRIORITY].address == 0) {
234 LOG_ERROR("FreeRTOS: uxTopUsedPriority is not defined, consult the OpenOCD manual for a work-around");
235 return ERROR_FAIL;
236 }
237 uint32_t top_used_priority = 0;
238 retval = target_read_u32(rtos->target,
239 rtos->symbols[FREERTOS_VAL_UX_TOP_USED_PRIORITY].address,
240 &top_used_priority);
241 if (retval != ERROR_OK)
242 return retval;
243 LOG_DEBUG("FreeRTOS: Read uxTopUsedPriority at 0x%" PRIx64 ", value %" PRIu32,
244 rtos->symbols[FREERTOS_VAL_UX_TOP_USED_PRIORITY].address,
245 top_used_priority);
246 if (top_used_priority > FREERTOS_MAX_PRIORITIES) {
247 LOG_ERROR("FreeRTOS top used priority is unreasonably big, not proceeding: %" PRIu32,
248 top_used_priority);
249 return ERROR_FAIL;
250 }
251
252 /* uxTopUsedPriority was defined as configMAX_PRIORITIES - 1
253 * in old FreeRTOS versions (before V7.5.3)
254 * Use contrib/rtos-helpers/FreeRTOS-openocd.c to get compatible symbol
255 * in newer FreeRTOS versions.
256 * Here we restore the original configMAX_PRIORITIES value */
257 unsigned int config_max_priorities = top_used_priority + 1;
258
259 symbol_address_t *list_of_lists =
260 malloc(sizeof(symbol_address_t) * (config_max_priorities + 5));
261 if (!list_of_lists) {
262 LOG_ERROR("Error allocating memory for %u priorities", config_max_priorities);
263 return ERROR_FAIL;
264 }
265
266 unsigned int num_lists;
267 for (num_lists = 0; num_lists < config_max_priorities; num_lists++)
268 list_of_lists[num_lists] = rtos->symbols[FREERTOS_VAL_PX_READY_TASKS_LISTS].address +
269 num_lists * param->list_width;
270
271 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_DELAYED_TASK_LIST1].address;
272 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_DELAYED_TASK_LIST2].address;
273 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_PENDING_READY_LIST].address;
274 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_SUSPENDED_TASK_LIST].address;
275 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_TASKS_WAITING_TERMINATION].address;
276
277 for (unsigned int i = 0; i < num_lists; i++) {
278 if (list_of_lists[i] == 0)
279 continue;
280
281 /* Read the number of threads in this list */
282 uint32_t list_thread_count = 0;
283 retval = target_read_u32(rtos->target,
284 list_of_lists[i],
285 &list_thread_count);
286 if (retval != ERROR_OK) {
287 LOG_ERROR("Error reading number of threads in FreeRTOS thread list");
288 free(list_of_lists);
289 return retval;
290 }
291 LOG_DEBUG("FreeRTOS: Read thread count for list %u at 0x%" PRIx64 ", value %" PRIu32,
292 i, list_of_lists[i], list_thread_count);
293
294 if (list_thread_count == 0)
295 continue;
296
297 /* Read the location of first list item */
298 uint32_t prev_list_elem_ptr = -1;
299 uint32_t list_elem_ptr = 0;
300 retval = target_read_u32(rtos->target,
301 list_of_lists[i] + param->list_next_offset,
302 &list_elem_ptr);
303 if (retval != ERROR_OK) {
304 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
305 free(list_of_lists);
306 return retval;
307 }
308 LOG_DEBUG("FreeRTOS: Read first item for list %u at 0x%" PRIx64 ", value 0x%" PRIx32,
309 i, list_of_lists[i] + param->list_next_offset, list_elem_ptr);
310
311 while ((list_thread_count > 0) && (list_elem_ptr != 0) &&
312 (list_elem_ptr != prev_list_elem_ptr) &&
313 (tasks_found < thread_list_size)) {
314 /* Get the location of the thread structure. */
315 rtos->thread_details[tasks_found].threadid = 0;
316 retval = target_read_u32(rtos->target,
317 list_elem_ptr + param->list_elem_content_offset,
318 &pointer_casts_are_bad);
319 if (retval != ERROR_OK) {
320 LOG_ERROR("Error reading thread list item object in FreeRTOS thread list");
321 free(list_of_lists);
322 return retval;
323 }
324 rtos->thread_details[tasks_found].threadid = pointer_casts_are_bad;
325 LOG_DEBUG("FreeRTOS: Read Thread ID at 0x%" PRIx32 ", value 0x%" PRIx64,
326 list_elem_ptr + param->list_elem_content_offset,
327 rtos->thread_details[tasks_found].threadid);
328
329 /* get thread name */
330
331 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
332 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
333
334 /* Read the thread name */
335 retval = target_read_buffer(rtos->target,
336 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
337 FREERTOS_THREAD_NAME_STR_SIZE,
338 (uint8_t *)&tmp_str);
339 if (retval != ERROR_OK) {
340 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
341 free(list_of_lists);
342 return retval;
343 }
344 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
345 LOG_DEBUG("FreeRTOS: Read Thread Name at 0x%" PRIx64 ", value '%s'",
346 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
347 tmp_str);
348
349 if (tmp_str[0] == '\x00')
350 strcpy(tmp_str, "No Name");
351
352 rtos->thread_details[tasks_found].thread_name_str =
353 malloc(strlen(tmp_str)+1);
354 strcpy(rtos->thread_details[tasks_found].thread_name_str, tmp_str);
355 rtos->thread_details[tasks_found].exists = true;
356
357 if (rtos->thread_details[tasks_found].threadid == rtos->current_thread) {
358 char running_str[] = "State: Running";
359 rtos->thread_details[tasks_found].extra_info_str = malloc(
360 sizeof(running_str));
361 strcpy(rtos->thread_details[tasks_found].extra_info_str,
362 running_str);
363 } else
364 rtos->thread_details[tasks_found].extra_info_str = NULL;
365
366 tasks_found++;
367 list_thread_count--;
368
369 prev_list_elem_ptr = list_elem_ptr;
370 list_elem_ptr = 0;
371 retval = target_read_u32(rtos->target,
372 prev_list_elem_ptr + param->list_elem_next_offset,
373 &list_elem_ptr);
374 if (retval != ERROR_OK) {
375 LOG_ERROR("Error reading next thread item location in FreeRTOS thread list");
376 free(list_of_lists);
377 return retval;
378 }
379 LOG_DEBUG("FreeRTOS: Read next thread location at 0x%" PRIx32 ", value 0x%" PRIx32,
380 prev_list_elem_ptr + param->list_elem_next_offset,
381 list_elem_ptr);
382 }
383 }
384
385 free(list_of_lists);
386 rtos->thread_count = tasks_found;
387 return 0;
388 }
389
390 static int freertos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
391 struct rtos_reg **reg_list, int *num_regs)
392 {
393 int retval;
394 const struct freertos_params *param;
395 int64_t stack_ptr = 0;
396
397 if (!rtos)
398 return -1;
399
400 if (thread_id == 0)
401 return -2;
402
403 if (!rtos->rtos_specific_params)
404 return -1;
405
406 param = (const struct freertos_params *) rtos->rtos_specific_params;
407
408 /* Read the stack pointer */
409 uint32_t pointer_casts_are_bad;
410 retval = target_read_u32(rtos->target,
411 thread_id + param->thread_stack_offset,
412 &pointer_casts_are_bad);
413 if (retval != ERROR_OK) {
414 LOG_ERROR("Error reading stack frame from FreeRTOS thread");
415 return retval;
416 }
417 stack_ptr = pointer_casts_are_bad;
418 LOG_DEBUG("FreeRTOS: Read stack pointer at 0x%" PRIx64 ", value 0x%" PRIx64,
419 thread_id + param->thread_stack_offset,
420 stack_ptr);
421
422 /* Check for armv7m with *enabled* FPU, i.e. a Cortex-M4F */
423 int cm4_fpu_enabled = 0;
424 struct armv7m_common *armv7m_target = target_to_armv7m(rtos->target);
425 if (is_armv7m(armv7m_target)) {
426 if ((armv7m_target->fp_feature == FPV4_SP) || (armv7m_target->fp_feature == FPV5_SP) ||
427 (armv7m_target->fp_feature == FPV5_DP)) {
428 /* Found ARM v7m target which includes a FPU */
429 uint32_t cpacr;
430
431 retval = target_read_u32(rtos->target, FPU_CPACR, &cpacr);
432 if (retval != ERROR_OK) {
433 LOG_ERROR("Could not read CPACR register to check FPU state");
434 return -1;
435 }
436
437 /* Check if CP10 and CP11 are set to full access. */
438 if (cpacr & 0x00F00000) {
439 /* Found target with enabled FPU */
440 cm4_fpu_enabled = 1;
441 }
442 }
443 }
444
445 if (cm4_fpu_enabled == 1) {
446 /* Read the LR to decide between stacking with or without FPU */
447 uint32_t lr_svc = 0;
448 retval = target_read_u32(rtos->target,
449 stack_ptr + 0x20,
450 &lr_svc);
451 if (retval != ERROR_OK) {
452 LOG_OUTPUT("Error reading stack frame from FreeRTOS thread");
453 return retval;
454 }
455 if ((lr_svc & 0x10) == 0)
456 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f_fpu, stack_ptr, reg_list, num_regs);
457 else
458 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f, stack_ptr, reg_list, num_regs);
459 } else
460 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm3, stack_ptr, reg_list, num_regs);
461 }
462
463 static int freertos_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[])
464 {
465 unsigned int i;
466 *symbol_list = calloc(
467 ARRAY_SIZE(freertos_symbol_list), sizeof(struct symbol_table_elem));
468
469 for (i = 0; i < ARRAY_SIZE(freertos_symbol_list); i++) {
470 (*symbol_list)[i].symbol_name = freertos_symbol_list[i].name;
471 (*symbol_list)[i].optional = freertos_symbol_list[i].optional;
472 }
473
474 return 0;
475 }
476
477 #if 0
478
479 static int freertos_set_current_thread(struct rtos *rtos, threadid_t thread_id)
480 {
481 return 0;
482 }
483
484 static int freertos_get_thread_ascii_info(struct rtos *rtos, threadid_t thread_id, char **info)
485 {
486 int retval;
487 const struct freertos_params *param;
488
489 if (!rtos)
490 return -1;
491
492 if (thread_id == 0)
493 return -2;
494
495 if (!rtos->rtos_specific_params)
496 return -3;
497
498 param = (const struct freertos_params *) rtos->rtos_specific_params;
499
500 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
501 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
502
503 /* Read the thread name */
504 retval = target_read_buffer(rtos->target,
505 thread_id + param->thread_name_offset,
506 FREERTOS_THREAD_NAME_STR_SIZE,
507 (uint8_t *)&tmp_str);
508 if (retval != ERROR_OK) {
509 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
510 return retval;
511 }
512 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
513
514 if (tmp_str[0] == '\x00')
515 strcpy(tmp_str, "No Name");
516
517 *info = malloc(strlen(tmp_str)+1);
518 strcpy(*info, tmp_str);
519 return 0;
520 }
521
522 #endif
523
524 static bool freertos_detect_rtos(struct target *target)
525 {
526 if ((target->rtos->symbols) &&
527 (target->rtos->symbols[FREERTOS_VAL_PX_READY_TASKS_LISTS].address != 0)) {
528 /* looks like FreeRTOS */
529 return true;
530 }
531 return false;
532 }
533
534 static int freertos_create(struct target *target)
535 {
536 for (unsigned int i = 0; i < ARRAY_SIZE(freertos_params_list); i++)
537 if (strcmp(freertos_params_list[i].target_name, target->type->name) == 0) {
538 target->rtos->rtos_specific_params = (void *)&freertos_params_list[i];
539 return 0;
540 }
541
542 LOG_ERROR("Could not find target in FreeRTOS compatibility list");
543 return -1;
544 }

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)