RTOS: ThreadX support on ARM926E-JS
[openocd.git] / src / rtos / ThreadX.c
1 /***************************************************************************
2 * Copyright (C) 2011 by Broadcom Corporation *
3 * Evan Hunter - ehunter@broadcom.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
19 ***************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <helper/time_support.h>
26 #include <jtag/jtag.h>
27 #include "target/target.h"
28 #include "target/target_type.h"
29 #include "rtos.h"
30 #include "helper/log.h"
31 #include "helper/types.h"
32 #include "rtos_standard_stackings.h"
33
34 static const struct rtos_register_stacking *get_stacking_info(const struct rtos *rtos, int64_t stack_ptr);
35 static const struct rtos_register_stacking *get_stacking_info_arm926ejs(const struct rtos *rtos, int64_t stack_ptr);
36
37 static int is_thread_id_valid(const struct rtos *rtos, int64_t thread_id);
38 static int is_thread_id_valid_arm926ejs(const struct rtos *rtos, int64_t thread_id);
39
40 static int ThreadX_detect_rtos(struct target *target);
41 static int ThreadX_create(struct target *target);
42 static int ThreadX_update_threads(struct rtos *rtos);
43 static int ThreadX_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, char **hex_reg_list);
44 static int ThreadX_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[]);
45
46
47
48 struct ThreadX_thread_state {
49 int value;
50 const char *desc;
51 };
52
53 static const struct ThreadX_thread_state ThreadX_thread_states[] = {
54 { 0, "Ready" },
55 { 1, "Completed" },
56 { 2, "Terminated" },
57 { 3, "Suspended" },
58 { 4, "Sleeping" },
59 { 5, "Waiting - Queue" },
60 { 6, "Waiting - Semaphore" },
61 { 7, "Waiting - Event flag" },
62 { 8, "Waiting - Memory" },
63 { 9, "Waiting - Memory" },
64 { 10, "Waiting - I/O" },
65 { 11, "Waiting - Filesystem" },
66 { 12, "Waiting - Network" },
67 { 13, "Waiting - Mutex" },
68 };
69
70 #define THREADX_NUM_STATES (sizeof(ThreadX_thread_states)/sizeof(struct ThreadX_thread_state))
71
72 #define ARM926EJS_REGISTERS_SIZE_SOLICITED (11 * 4)
73 static const struct stack_register_offset rtos_threadx_arm926ejs_stack_offsets_solicited[] = {
74 { -1, 32 }, /* r0 */
75 { -1, 32 }, /* r1 */
76 { -1, 32 }, /* r2 */
77 { -1, 32 }, /* r3 */
78 { 0x08, 32 }, /* r4 */
79 { 0x0C, 32 }, /* r5 */
80 { 0x10, 32 }, /* r6 */
81 { 0x14, 32 }, /* r7 */
82 { 0x18, 32 }, /* r8 */
83 { 0x1C, 32 }, /* r9 */
84 { 0x20, 32 }, /* r10 */
85 { 0x24, 32 }, /* r11 */
86 { -1, 32 }, /* r12 */
87 { -2, 32 }, /* sp (r13) */
88 { 0x28, 32 }, /* lr (r14) */
89 { -1, 32 }, /* pc (r15) */
90 /*{ -1, 32 },*/ /* lr (r14) */
91 /*{ 0x28, 32 },*/ /* pc (r15) */
92 { 0x04, 32 }, /* xPSR */
93 };
94 #define ARM926EJS_REGISTERS_SIZE_INTERRUPT (17 * 4)
95 static const struct stack_register_offset rtos_threadx_arm926ejs_stack_offsets_interrupt[] = {
96 { 0x08, 32 }, /* r0 */
97 { 0x0C, 32 }, /* r1 */
98 { 0x10, 32 }, /* r2 */
99 { 0x14, 32 }, /* r3 */
100 { 0x18, 32 }, /* r4 */
101 { 0x1C, 32 }, /* r5 */
102 { 0x20, 32 }, /* r6 */
103 { 0x24, 32 }, /* r7 */
104 { 0x28, 32 }, /* r8 */
105 { 0x2C, 32 }, /* r9 */
106 { 0x30, 32 }, /* r10 */
107 { 0x34, 32 }, /* r11 */
108 { 0x38, 32 }, /* r12 */
109 { -2, 32 }, /* sp (r13) */
110 { 0x3C, 32 }, /* lr (r14) */
111 { 0x40, 32 }, /* pc (r15) */
112 { 0x04, 32 }, /* xPSR */
113 };
114
115 const struct rtos_register_stacking rtos_threadx_arm926ejs_stacking[] = {
116 {
117 ARM926EJS_REGISTERS_SIZE_SOLICITED, /* stack_registers_size */
118 -1, /* stack_growth_direction */
119 17, /* num_output_registers */
120 0, /* stack_alignment */
121 rtos_threadx_arm926ejs_stack_offsets_solicited /* register_offsets */
122 },
123 {
124 ARM926EJS_REGISTERS_SIZE_INTERRUPT, /* stack_registers_size */
125 -1, /* stack_growth_direction */
126 17, /* num_output_registers */
127 0, /* stack_alignment */
128 rtos_threadx_arm926ejs_stack_offsets_interrupt /* register_offsets */
129 },
130 };
131
132 struct ThreadX_params {
133 const char *target_name;
134 unsigned char pointer_width;
135 unsigned char thread_stack_offset;
136 unsigned char thread_name_offset;
137 unsigned char thread_state_offset;
138 unsigned char thread_next_offset;
139 const struct rtos_register_stacking *stacking_info;
140 size_t stacking_info_nb;
141 const struct rtos_register_stacking* (*fn_get_stacking_info)(const struct rtos *rtos, int64_t stack_ptr);
142 int (*fn_is_thread_id_valid)(const struct rtos *rtos, int64_t thread_id);
143 };
144
145 static const struct ThreadX_params ThreadX_params_list[] = {
146 {
147 "cortex_m", /* target_name */
148 4, /* pointer_width; */
149 8, /* thread_stack_offset; */
150 40, /* thread_name_offset; */
151 48, /* thread_state_offset; */
152 136, /* thread_next_offset */
153 &rtos_standard_Cortex_M3_stacking, /* stacking_info */
154 1, /* stacking_info_nb */
155 NULL, /* fn_get_stacking_info */
156 NULL, /* fn_is_thread_id_valid */
157 },
158 {
159 "cortex_r4", /* target_name */
160 4, /* pointer_width; */
161 8, /* thread_stack_offset; */
162 40, /* thread_name_offset; */
163 48, /* thread_state_offset; */
164 136, /* thread_next_offset */
165 &rtos_standard_Cortex_R4_stacking, /* stacking_info */
166 1, /* stacking_info_nb */
167 NULL, /* fn_get_stacking_info */
168 NULL, /* fn_is_thread_id_valid */
169 },
170 {
171 "arm926ejs", /* target_name */
172 4, /* pointer_width; */
173 8, /* thread_stack_offset; */
174 40, /* thread_name_offset; */
175 48, /* thread_state_offset; */
176 136, /* thread_next_offset */
177 rtos_threadx_arm926ejs_stacking, /* stacking_info */
178 2, /* stacking_info_nb */
179 get_stacking_info_arm926ejs, /* fn_get_stacking_info */
180 is_thread_id_valid_arm926ejs, /* fn_is_thread_id_valid */
181 },
182 };
183
184 #define THREADX_NUM_PARAMS ((int)(sizeof(ThreadX_params_list)/sizeof(struct ThreadX_params)))
185
186 enum ThreadX_symbol_values {
187 ThreadX_VAL_tx_thread_current_ptr = 0,
188 ThreadX_VAL_tx_thread_created_ptr = 1,
189 ThreadX_VAL_tx_thread_created_count = 2,
190 };
191
192 static const char * const ThreadX_symbol_list[] = {
193 "_tx_thread_current_ptr",
194 "_tx_thread_created_ptr",
195 "_tx_thread_created_count",
196 NULL
197 };
198
199 const struct rtos_type ThreadX_rtos = {
200 .name = "ThreadX",
201
202 .detect_rtos = ThreadX_detect_rtos,
203 .create = ThreadX_create,
204 .update_threads = ThreadX_update_threads,
205 .get_thread_reg_list = ThreadX_get_thread_reg_list,
206 .get_symbol_list_to_lookup = ThreadX_get_symbol_list_to_lookup,
207 };
208
209 static const struct rtos_register_stacking *get_stacking_info(const struct rtos *rtos, int64_t stack_ptr)
210 {
211 const struct ThreadX_params *param = (const struct ThreadX_params *) rtos->rtos_specific_params;
212
213 if (param->fn_get_stacking_info != NULL)
214 return param->fn_get_stacking_info(rtos, stack_ptr);
215
216 return param->stacking_info + 0;
217 }
218
219 static int is_thread_id_valid(const struct rtos *rtos, int64_t thread_id)
220 {
221 const struct ThreadX_params *param;
222
223 if (rtos->rtos_specific_params == NULL)
224 return 0; /* invalid */
225
226 param = (const struct ThreadX_params *) rtos->rtos_specific_params;
227
228 if (param->fn_is_thread_id_valid != NULL)
229 return param->fn_is_thread_id_valid(rtos, thread_id);
230
231 return (thread_id != 0);
232 }
233
234 static const struct rtos_register_stacking *get_stacking_info_arm926ejs(const struct rtos *rtos, int64_t stack_ptr)
235 {
236 const struct ThreadX_params *param = (const struct ThreadX_params *) rtos->rtos_specific_params;
237 int retval;
238 uint32_t flag;
239
240 retval = target_read_buffer(rtos->target,
241 stack_ptr,
242 sizeof(flag),
243 (uint8_t *)&flag);
244 if (retval != ERROR_OK) {
245 LOG_ERROR("Error reading stack data from ThreadX thread: stack_ptr=0x%" PRIx64, stack_ptr);
246 return NULL;
247 }
248
249 if (flag == 0) {
250 LOG_DEBUG(" solicited stack");
251 return param->stacking_info + 0;
252 } else {
253 LOG_DEBUG(" interrupt stack: %u", flag);
254 return param->stacking_info + 1;
255 }
256 }
257
258 static int is_thread_id_valid_arm926ejs(const struct rtos *rtos, int64_t thread_id)
259 {
260 return (thread_id != 0 && thread_id != 1);
261 }
262
263 static int ThreadX_update_threads(struct rtos *rtos)
264 {
265 int retval;
266 int tasks_found = 0;
267 int thread_list_size = 0;
268 const struct ThreadX_params *param;
269
270 if (rtos == NULL)
271 return -1;
272
273 if (rtos->rtos_specific_params == NULL)
274 return -3;
275
276 param = (const struct ThreadX_params *) rtos->rtos_specific_params;
277
278 if (rtos->symbols == NULL) {
279 LOG_ERROR("No symbols for ThreadX");
280 return -4;
281 }
282
283 if (rtos->symbols[ThreadX_VAL_tx_thread_created_count].address == 0) {
284 LOG_ERROR("Don't have the number of threads in ThreadX");
285 return -2;
286 }
287
288 /* read the number of threads */
289 retval = target_read_buffer(rtos->target,
290 rtos->symbols[ThreadX_VAL_tx_thread_created_count].address,
291 4,
292 (uint8_t *)&thread_list_size);
293
294 if (retval != ERROR_OK) {
295 LOG_ERROR("Could not read ThreadX thread count from target");
296 return retval;
297 }
298
299 /* wipe out previous thread details if any */
300 rtos_free_threadlist(rtos);
301
302 /* read the current thread id */
303 retval = target_read_buffer(rtos->target,
304 rtos->symbols[ThreadX_VAL_tx_thread_current_ptr].address,
305 4,
306 (uint8_t *)&rtos->current_thread);
307
308 if (retval != ERROR_OK) {
309 LOG_ERROR("Could not read ThreadX current thread from target");
310 return retval;
311 }
312
313 if ((thread_list_size == 0) || (rtos->current_thread == 0)) {
314 /* Either : No RTOS threads - there is always at least the current execution though */
315 /* OR : No current thread - all threads suspended - show the current execution
316 * of idling */
317 char tmp_str[] = "Current Execution";
318 thread_list_size++;
319 tasks_found++;
320 rtos->thread_details = malloc(
321 sizeof(struct thread_detail) * thread_list_size);
322 rtos->thread_details->threadid = 1;
323 rtos->thread_details->exists = true;
324 rtos->thread_details->display_str = NULL;
325 rtos->thread_details->extra_info_str = NULL;
326 rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str));
327 strcpy(rtos->thread_details->thread_name_str, tmp_str);
328
329 if (thread_list_size == 0) {
330 rtos->thread_count = 1;
331 return ERROR_OK;
332 }
333 } else {
334 /* create space for new thread details */
335 rtos->thread_details = malloc(
336 sizeof(struct thread_detail) * thread_list_size);
337 }
338
339 /* Read the pointer to the first thread */
340 int64_t thread_ptr = 0;
341 retval = target_read_buffer(rtos->target,
342 rtos->symbols[ThreadX_VAL_tx_thread_created_ptr].address,
343 param->pointer_width,
344 (uint8_t *)&thread_ptr);
345 if (retval != ERROR_OK) {
346 LOG_ERROR("Could not read ThreadX thread location from target");
347 return retval;
348 }
349
350 /* loop over all threads */
351 int64_t prev_thread_ptr = 0;
352 while ((thread_ptr != prev_thread_ptr) && (tasks_found < thread_list_size)) {
353
354 #define THREADX_THREAD_NAME_STR_SIZE (200)
355 char tmp_str[THREADX_THREAD_NAME_STR_SIZE];
356 unsigned int i = 0;
357 int64_t name_ptr = 0;
358
359 /* Save the thread pointer */
360 rtos->thread_details[tasks_found].threadid = thread_ptr;
361
362 /* read the name pointer */
363 retval = target_read_buffer(rtos->target,
364 thread_ptr + param->thread_name_offset,
365 param->pointer_width,
366 (uint8_t *)&name_ptr);
367 if (retval != ERROR_OK) {
368 LOG_ERROR("Could not read ThreadX thread name pointer from target");
369 return retval;
370 }
371
372 /* Read the thread name */
373 retval =
374 target_read_buffer(rtos->target,
375 name_ptr,
376 THREADX_THREAD_NAME_STR_SIZE,
377 (uint8_t *)&tmp_str);
378 if (retval != ERROR_OK) {
379 LOG_ERROR("Error reading thread name from ThreadX target");
380 return retval;
381 }
382 tmp_str[THREADX_THREAD_NAME_STR_SIZE-1] = '\x00';
383
384 if (tmp_str[0] == '\x00')
385 strcpy(tmp_str, "No Name");
386
387 rtos->thread_details[tasks_found].thread_name_str =
388 malloc(strlen(tmp_str)+1);
389 strcpy(rtos->thread_details[tasks_found].thread_name_str, tmp_str);
390
391 /* Read the thread status */
392 int64_t thread_status = 0;
393 retval = target_read_buffer(rtos->target,
394 thread_ptr + param->thread_state_offset,
395 4,
396 (uint8_t *)&thread_status);
397 if (retval != ERROR_OK) {
398 LOG_ERROR("Error reading thread state from ThreadX target");
399 return retval;
400 }
401
402 for (i = 0; (i < THREADX_NUM_STATES) &&
403 (ThreadX_thread_states[i].value != thread_status); i++) {
404 /* empty */
405 }
406
407 const char *state_desc;
408 if (i < THREADX_NUM_STATES)
409 state_desc = ThreadX_thread_states[i].desc;
410 else
411 state_desc = "Unknown state";
412
413 rtos->thread_details[tasks_found].extra_info_str = malloc(strlen(
414 state_desc)+1);
415 strcpy(rtos->thread_details[tasks_found].extra_info_str, state_desc);
416
417 rtos->thread_details[tasks_found].exists = true;
418
419 rtos->thread_details[tasks_found].display_str = NULL;
420
421 tasks_found++;
422 prev_thread_ptr = thread_ptr;
423
424 /* Get the location of the next thread structure. */
425 thread_ptr = 0;
426 retval = target_read_buffer(rtos->target,
427 prev_thread_ptr + param->thread_next_offset,
428 param->pointer_width,
429 (uint8_t *) &thread_ptr);
430 if (retval != ERROR_OK) {
431 LOG_ERROR("Error reading next thread pointer in ThreadX thread list");
432 return retval;
433 }
434 }
435
436 rtos->thread_count = tasks_found;
437
438 return 0;
439 }
440
441 static int ThreadX_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, char **hex_reg_list)
442 {
443 int retval;
444 const struct ThreadX_params *param;
445
446 *hex_reg_list = NULL;
447
448 if (rtos == NULL)
449 return -1;
450
451 if (!is_thread_id_valid(rtos, thread_id))
452 return -2;
453
454 if (rtos->rtos_specific_params == NULL)
455 return -3;
456
457 param = (const struct ThreadX_params *) rtos->rtos_specific_params;
458
459 /* Read the stack pointer */
460 int64_t stack_ptr = 0;
461 retval = target_read_buffer(rtos->target,
462 thread_id + param->thread_stack_offset,
463 param->pointer_width,
464 (uint8_t *)&stack_ptr);
465 if (retval != ERROR_OK) {
466 LOG_ERROR("Error reading stack frame from ThreadX thread");
467 return retval;
468 }
469
470 LOG_INFO("thread: 0x%" PRIx64 ", stack_ptr=0x%" PRIx64, (uint64_t)thread_id, (uint64_t)stack_ptr);
471
472 if (stack_ptr == 0) {
473 LOG_ERROR("null stack pointer in thread");
474 return -5;
475 }
476
477 const struct rtos_register_stacking *stacking_info =
478 get_stacking_info(rtos, stack_ptr);
479
480 if (stacking_info == NULL) {
481 LOG_ERROR("Unknown stacking info for thread id=0x%" PRIx64, (uint64_t)thread_id);
482 return -6;
483 }
484
485 return rtos_generic_stack_read(rtos->target, stacking_info, stack_ptr, hex_reg_list);
486 }
487
488 static int ThreadX_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
489 {
490 unsigned int i;
491 *symbol_list = calloc(
492 ARRAY_SIZE(ThreadX_symbol_list), sizeof(symbol_table_elem_t));
493
494 for (i = 0; i < ARRAY_SIZE(ThreadX_symbol_list); i++)
495 (*symbol_list)[i].symbol_name = ThreadX_symbol_list[i];
496
497 return 0;
498 }
499
500 static int ThreadX_detect_rtos(struct target *target)
501 {
502 if ((target->rtos->symbols != NULL) &&
503 (target->rtos->symbols[ThreadX_VAL_tx_thread_created_ptr].address != 0)) {
504 /* looks like ThreadX */
505 return 1;
506 }
507 return 0;
508 }
509
510 #if 0
511
512 static int ThreadX_set_current_thread(struct rtos *rtos, threadid_t thread_id)
513 {
514 return 0;
515 }
516
517 static int ThreadX_get_thread_detail(struct rtos *rtos,
518 threadid_t thread_id,
519 struct thread_detail *detail)
520 {
521 unsigned int i = 0;
522 int retval;
523
524 #define THREADX_THREAD_NAME_STR_SIZE (200)
525 char tmp_str[THREADX_THREAD_NAME_STR_SIZE];
526
527 const struct ThreadX_params *param;
528
529 if (rtos == NULL)
530 return -1;
531
532 if (thread_id == 0)
533 return -2;
534
535 if (rtos->rtos_specific_params == NULL)
536 return -3;
537
538 param = (const struct ThreadX_params *) rtos->rtos_specific_params;
539
540 if (rtos->symbols == NULL) {
541 LOG_ERROR("No symbols for ThreadX");
542 return -3;
543 }
544
545 detail->threadid = thread_id;
546
547 int64_t name_ptr = 0;
548 /* read the name pointer */
549 retval = target_read_buffer(rtos->target,
550 thread_id + param->thread_name_offset,
551 param->pointer_width,
552 (uint8_t *)&name_ptr);
553 if (retval != ERROR_OK) {
554 LOG_ERROR("Could not read ThreadX thread name pointer from target");
555 return retval;
556 }
557
558 /* Read the thread name */
559 retval = target_read_buffer(rtos->target,
560 name_ptr,
561 THREADX_THREAD_NAME_STR_SIZE,
562 (uint8_t *)&tmp_str);
563 if (retval != ERROR_OK) {
564 LOG_ERROR("Error reading thread name from ThreadX target");
565 return retval;
566 }
567 tmp_str[THREADX_THREAD_NAME_STR_SIZE-1] = '\x00';
568
569 if (tmp_str[0] == '\x00')
570 strcpy(tmp_str, "No Name");
571
572 detail->thread_name_str = malloc(strlen(tmp_str)+1);
573
574 /* Read the thread status */
575 int64_t thread_status = 0;
576 retval =
577 target_read_buffer(rtos->target,
578 thread_id + param->thread_state_offset,
579 4,
580 (uint8_t *)&thread_status);
581 if (retval != ERROR_OK) {
582 LOG_ERROR("Error reading thread state from ThreadX target");
583 return retval;
584 }
585
586 for (i = 0; (i < THREADX_NUM_STATES) &&
587 (ThreadX_thread_states[i].value != thread_status); i++) {
588 /* empty */
589 }
590
591 char *state_desc;
592 if (i < THREADX_NUM_STATES)
593 state_desc = ThreadX_thread_states[i].desc;
594 else
595 state_desc = "Unknown state";
596
597 detail->extra_info_str = malloc(strlen(state_desc)+1);
598
599 detail->exists = true;
600
601 detail->display_str = NULL;
602
603 return 0;
604 }
605
606 #endif
607
608 static int ThreadX_create(struct target *target)
609 {
610 int i = 0;
611 while ((i < THREADX_NUM_PARAMS) &&
612 (0 != strcmp(ThreadX_params_list[i].target_name, target->type->name))) {
613 i++;
614 }
615 if (i >= THREADX_NUM_PARAMS) {
616 LOG_ERROR("Could not find target in ThreadX compatibility list");
617 return -1;
618 }
619
620 target->rtos->rtos_specific_params = (void *) &ThreadX_params_list[i];
621 target->rtos->current_thread = 0;
622 target->rtos->thread_details = NULL;
623 return 0;
624 }

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)