Home | Projects | Notes > ARM Cortex-M3/M4 Processor > Exceptions for System-Level Services
ARM Cortex Mx processors support 2 important system-level service exceptions.
SVC (Supervisor Call)
PendSV(Pendable Service)
Supervisory calls are typically used to request privileged operations or access to system resources from an operating system.
SVC exception is mainly used in an OS environment. For example, a less privileged user task can trigger SVC exception to get system-level services (e.g., accessing device drivers, peripherals) from the kernel of the Os.
PendSV is mainly used in an OS environment to carry out context switching between two or more tasks when no other exceptions are active in the system.
Summary of ARM Cortex Mx system exceptions:
svc
is a thumb ISA instruction which causes SVC exception.
In an RTOS scenario, user tasks can execute SVC instruction with an associated argument to make supervisory calls to seek privileged resources from the kernel code.
Unprivileged user tasks use the svc
instruction to change the processor mode to privileged mode to access privileged resources such as peripherals, etc.
svc
instruction is always used along with a number (i.e., SVC number or argument), which an be used to identify the request type by the kernel code.
The SVC handler executes right after the svc
instruction (i.e., no delay unless a higher priority exception arrives at the same time).
Example:
xxxxxxxxxx
61// user-level code
2// access level: unprivileged
3// processor mode: thread
4...
5__asm volatile("svc #0x01"); // SVC call
6...
xxxxxxxxxx
171// kernel-level code
2// access level: privileged
3// processor mode: handler
4...
5__SVC_Request_Handler(int svc_number)
6{
7case 1:
8 __write();
9 // __write() will be invoked to perform the write operation to an MPU protected register
10 // which will allow privileged access only (user task cannot directly access such
11 // resources)
12case 2:
13 __read();
14case 3:
15 __timer_start();
16 ...
17}
A user task attempting to directly access an MPU protected memory address (resource) will cause MemManage fault exception and the kernel will kill that user task.
Two methods to trigger SVC exception:
Direct execution of SVC instruction with an immediate value.
e.g., svc #0x04
in assembly (using svc
instruction is very efficient in terms of latency because it will execute as soon as it is called.)
Setting the exception pending bit in System Handler Control and State Register of the processor. (Uncommon method)
The svc
instruction has a number embedded within it, often referred to as the SVC number.
In the SVC handler, you should fetch the opcode of the svc
instruction and then extract the SVC number.
To fetch the opcode of svc
instruction from program memory, we need the value of PC (return address) where the user code had interrupted while triggering the SVC exception.
The value of the PC (return address) where the user code had interrupted is stored in the stack as a part of exception entry sequence by the processor.
The key is to get the MSP value from the SVC handler, MSP+6 to get PC, and then from that return address get the SVC number.
It is an exception type 14 and has a programmable priority level.
This exception is triggered by setting its pending status by writing to the Interrupt control and State Register of processor. (There is no instruction to trigger PendSV exception.)
Triggering a pendSV system exception is a way of invoking the preemptive kernel to carry out the context switch in an OS environment.
In an OS environment, PendSV handler is set to the lowest priority level, and the PendSV handler carries out the context switch operation.
Typically, this exception is triggered inside a higher priority exception handler, and it gets executed when the higher priority handler finishes.
Using this characteristic, we can schedule the PendSV exception handler to be executed after all the other interrupt processing tasks are done. (Generally, PendSV's priority is configured to lowest possible value.)
This way, using PendSV in context switching will be more efficient in an interrupt noisy environment.
In an interrupt noisy environment, we need to delay the context switching until all active IRQs are executed.
Scheduler is nothing but a SysTick handler that is called every time the SysTick timer expires. What the scheduler (which is generally part of the kernel of an OS) does is that it will simply set the pending status of PendSV every time slot. It is NOT the scheduler that actually performs the context switching. When there is no IRQ active, the PendSV will be called and it will carry out the context switching.
Context switching means storing on the stack the context of the currently executing task and restoring the context of the next task to be executed from the stack.
What would happen when the scheduler is called (i.e., SysTick timer expired) while a user task was being interrupted handled (ISR)?
No matter what, scheduler will do its job (i.e., pends the PendSV), preempting the currently running ISR. When the scheduler returns, ISR will pick up and continue to do what it was doing. PendSV will wait till there is no more active IRQs. So, only after the ISR is finished, the PendSV will start doing its job (i.e., context switching).
This model helps context switch to take place after all the previously triggered ISRs are executed.
If we didn't have PendSV and scheduler performed the context switching:
(ARM Coretex M4 processor does not allow to transition back to Thread mode with any unfinished ISR.)
Offloading interrupt processing
If a higher priority handler is doing time-consuming work, then the other lower priority interrupts will suffer, and systems responsiveness may reduce. This can be solved using a combination of ISR and PendSV handler.
Interrupts may be serviced in two havles:
The first half is the time critical part that needs to be executed as a part of ISR.
The second half (also called as bottom half), is basically "delayed execution" where rest of the time-consuming work will be done.
So, PendSV can be used in these cases, to handle the second half execution by triggering it in the first half.
In the following diagram, not time critical portion of the ISR0 is stretched out using PendSV.
Write a program to execute an SVC instruction from thread mode, implement the SVC handler to print the SVC number used. Also, increment the SVC number by 4 and return it to the thread mode code and print it.
Instructions:
Write a main() function where you should execute the SVC instruction with an argument. e.g., svc #0x05
Implement the SVC handler
In the SVC handler extract the SVC number and print it using printf()
.
Increment the SVC number by 4 and return it to the thread mode.
Program
xxxxxxxxxx
601
2
3
4
5
6
7
8int main(void)
9{
10 // SVC call
11 __asm("svc #8");
12
13 // Not recommended!
14 //register uint32_t data __asm("r0"); // Instead of storing data on stack use register
15 // inside the processor
16
17 // Recommended!
18 // This is how you get the value from the handler.
19 // The way return value of the SVC handler was passed to the thread mode code was via using
20 // stack!
21 uint32_t data;
22 __asm volatile("mov %0, r0": "=r"(data)::);
23 printf(" data = %ld\n", data); // data = 0x8 + 0x4 = 0x0C
24
25 /* Loop forever */
26 for(;;);
27}
28
29// This naked function is to help secure MSP (stack pointer) immediately after the svc call.
30__attribute__((naked)) void SVC_Handler(void)
31{
32 // 1. get the value of the MSP
33 __asm("mrs r0, msp");
34 __asm("b SVC_Handler_c");
35}
36
37void SVC_Handler_c(uint32_t * pBaseStackFrame)
38{
39 // __asm("mrs r0, msp"); // writing this code inside the regular C function will not get us
40 // the correct value of the MSP we want because of the prologue
41 // asm code the compiler generates
42
43 printf("SVC Handler Called!\n");
44 uint8_t *pReturnAddr = (uint8_t *)pBaseStackFrame[6];
45
46 // 2. decrement the return address by 2 to point to opcode of the SVC instruction
47 // in the program memory
48 pReturnAddr -= 2;
49
50 // 3. extract the SVC number (LSB of the opcode)
51 uint8_t svc_number = *pReturnAddr;
52 printf("svc_number = %d\n", svc_number);
53
54 // 4. Increment the SVC number by 4 and return it to the thread mode.
55 svc_number += 4;
56
57 pBaseStackFrame[0] = svc_number; // since according to AAPCS, return value is store in r0
58 // we'll just use r0 to store the value we want to pass
59 // to the caller
60}
L14: This method is not recommended since you cannot guarantee that the register you want to use will be used for some other reason during the process. Safer way is to use GCC inline assembly syntax of mixing processor register and C variable using input/output arguments discussed earlier.
xxxxxxxxxx
31/* variable window */
2pBaseStackFrame uint32_t * 0x2001ffd8
3pReturnAddr uint8_t * 0x80001ee <main+6> "þçïó\b\200"
xxxxxxxxxx
161/* disassembly window */
2 main:
3080001e8: 80 b4 push {r7}
4080001ea: 00 af add r7, sp, #0
5080001ec: 08 df svc 8
632 for(;;);
7080001ee: fe e7 b.n 0x80001ee <main+6>
838 __asm("mrs r0, msp"); // writing this code inside the regular C function will not get us
9 SVC_Handler:
10080001f0: ef f3 08 80 mrs r0, MSP
1140 __asm("b SVC_Handler_c");
12080001f4: 00 f0 02 b8 b.w 0x80001fc <SVC_Handler_c>
1342 }
14080001f8: 00 bf nop
15080001fa: 00 00 movs r0, r0
1645 {
Notice that the return address we obtained is
0x80001ee
and this subtracted by 2 is0x080001ec
which is the address of the instructionsvc 8
.
Write a program to add subtract, multiply, and divide 2 operands using SVC handler and return the result to the thread mode code and print the result. Thread mode code should pass 2 operands via stack frame. Use the following SVC number to decide the operation.
36 - Addition
37 - Subtraction
38 - Multiplication
39 - Division
Program
xxxxxxxxxx
1061
2
3
4
5
6
7
8void SVC_Handler_c(uint32_t *pBaseStackFrame);
9
10int32_t add(int32_t x, int32_t y)
11{
12 int32_t res;
13 __asm volatile("SVC #36");
14 __asm volatile("mov %0, r0": "=r"(res)::);
15 return res;
16}
17
18int32_t sub(int32_t x, int32_t y)
19{
20 int32_t res;
21 __asm volatile("SVC #37");
22 __asm volatile("mov %0, r0": "=r"(res)::); // store handler return value to res
23 return res;
24}
25
26int32_t mul(int32_t x, int32_t y)
27{
28 int32_t res;
29 __asm volatile("SVC #38");
30 __asm volatile("mov %0, r0": "=r"(res)::);
31 return res;
32}
33
34int32_t div(int32_t x, int32_t y)
35{
36 int32_t res;
37 __asm volatile("SVC #39");
38 __asm volatile("mov %0, r0": "=r"(res)::);
39 return res;
40}
41
42int main(void)
43{
44 int32_t res;
45
46 res = add(40, -90);
47 printf("Add result = %ld\n", res);
48
49 res = sub(40, -90);
50 printf("Sub result = %ld\n", res);
51
52 res = mul(40, -90);
53 printf("Mul result = %ld\n", res);
54
55 res = div(40, -90);
56 printf("Div result = %ld\n", res);
57
58 /* Loop forever */
59 for(;;);
60}
61
62__attribute__((naked)) void SVC_Handler(void)
63{
64 __asm("mrs r0, msp");
65 __asm("b SVC_Handler_c");
66}
67
68void SVC_Handler_c(uint32_t *pBaseStackFrame)
69{
70 printf("SVC Handler\n");
71
72 int32_t arg0, arg1, res;
73
74 // typecast to (uint8_t *) is so that we can decrement pReturnAddr by 2
75 uint8_t *pReturnAddr = (uint8_t *)pBaseStackFrame[6];
76
77 // 2. Decrement the return address by 2 to point to opcode of the SVC instruction
78 // in the program memory
79 pReturnAddr -= 2;
80
81 // 3. Extract the SVC number (LSByte of the opcode)
82 uint8_t svc_number = *pReturnAddr;
83
84 arg0 = pBaseStackFrame[0];
85 arg1 = pBaseStackFrame[1];
86
87 switch(svc_number)
88 {
89 case 36:
90 res = arg0 + arg1;
91 break;
92 case 37:
93 res = arg0 - arg1;
94 break;
95 case 38:
96 res = arg0 * arg1;
97 break;
98 case 39:
99 res = arg0 / arg1;
100 break;
101 default:
102 printf("Invalid SVC number\n");
103 }
104
105 pBaseStackFrame[0] = res;
106}
Following is the program written by myself. Analyze why this is not working!
After L64, the code jumps to the default handler(?) which is basically an infinite loop. The description says this handler is called when the processor has received an unexpected interrupt.
xxxxxxxxxx
871
2
3
4
5
6
7
8int main(void)
9{
10 uint32_t data;
11
12 // SVC 36: addition
13 // SVC 37: subtraction
14 // SVC 38: multiplication
15 // SVC 39: division
16
17 __asm volatile("mov r0, #5"); // 1st operand
18 __asm volatile("mov r1, #3"); // 2nd operand
19 __asm("svc 36");
20 __asm volatile("mov %0, r0": "=r"(data)::);
21 printf("5 + 3 = %ld\n", data);
22
23 __asm volatile("mov r0, #5"); // 1st operand
24 __asm volatile("mov r1, #3"); // 2nd operand
25 __asm("svc 37");
26 __asm volatile("mov %0, r0": "=r"(data)::);
27 printf("5 - 3 = %ld\n", data);
28
29 __asm volatile("mov r0, #5"); // 1st operand
30 __asm volatile("mov r1, #3"); // 2nd operand
31 __asm("svc 38");
32 __asm volatile("mov %0, r0": "=r"(data)::);
33 printf("5 * 3 = %ld\n", data);
34
35 __asm volatile("mov r0, #5"); // 1st operand
36 __asm volatile("mov r1, #3"); // 2nd operand
37 __asm("svc 39");
38 __asm volatile("mov %0, r0": "=r"(data)::);
39 printf("5 / 3 = %ld\n", data);
40
41 /* Loop forever */
42 for(;;);
43}
44
45// r0 and r1 contents will be passed to n1 and n2, respectively
46__attribute__((naked)) void SVC_Handler(uint32_t n1, uint32_t n2)
47{
48 // following commented out code will cause assembler errors. naked function does not
49 // work with regular C variables or expressions
50 /*
51 __asm volatile("mov r1, , %0": : "r"(n1)); // store n1 into r1
52 __asm volatile("mov r2, , %0": : "r"(n2)); // store n2 into r2
53 */
54 __asm volatile("mrs r3, msp"); // secure msp (stack frame base address)
55 __asm("b SVC_Handler_c");
56}
57
58void SVC_Handler_c(uint32_t n1, uint32_t n2, uint32_t * pBaseStackFrame)
59{
60 uint8_t *pReturnAddr = (uint8_t *)pBaseStackFrame[6]; // pReturnAddr-2 points to svc instruction
61 pReturnAddr -= 2;
62
63 // extract SVC number
64 uint8_t svc_number = *pReturnAddr;
65
66 switch(svc_number)
67 {
68 case 36:
69 pBaseStackFrame[0] = n1 + n2;
70 break;
71 case 37:
72 pBaseStackFrame[0] = n1 - n2;
73 break;
74 case 38:
75 pBaseStackFrame[0] = n1 * n2;
76 break;
77 case 39:
78 pBaseStackFrame[0] = n1 / n2;
79 break;
80 default:
81 printf("Invalid SVC number!\n");
82 break;
83 }
84
85 // at this point pBaseStackFrame[0], which is basically r0, will contain
86 // the calculation result to be passed to the caller (i.e., Thread mode code)
87}
Nayak, K. (2022). Embedded Systems Programming on ARM Cortex-M3/M4 Processor [Video file]. Retrieved from https://www.udemy.com/course/embedded-system-programming-on-arm-cortex-m3m4/