owned this note
owned this note
Published
Linked with GitHub
# 期末考筆記:
---
## 中斷 Interrupt
當周邊(peripheral)或是硬體需要處理器(processor)做某些特殊處理時,會發現以下事件:
- 周邊觸發一個中斷請求給處理器
- 處理器把目前處理的`task `停滯起來
- 處理器執行中斷的ISR並且將中斷請求清空
- 處理器回復成之前的`Task`
在任何的Cortex M上面都有`NVIC ` Nested Vectored Interrupt Controller ,其目的是為了對中斷進行管理。
以下圖來說:


- `NVIC ` 會接受來自周邊的中斷請求。
- 為了回復到被中斷的程式碼中,`the exception sequence `中斷程序會把進入目前階段的程式碼儲存起來,以便用來在完成中斷服務程序後,系統可以回復到執行中斷程序前。
- 在Cortex M的處理器中某些暫存器會把目前的資料存到`stack `裡面,當`exception `發生時,處理器會根據`exception return sequence `的訊息回到相對應的程式碼中。
## exception return
+ 為了在中斷後 return 回原先的 function 必須有儲存原先執行狀態的機制
+ 當中斷發生時系統會備份caller saved register:
+ `r0`~`r3`, `r12`, `LR`, `PSR`,還有目前程式備份的address,此稱作stack frame。stack pointer便會開始更新
- 功能:
EXC_RETURN的功能是,他要記錄你在發生exception前你的sp是位在psp還是msp,且你是在handler mode 還是 thread mode,以便得知exception return時應該有的行為。
exception return的條件:
- 在Handler mode發生,且要使PC被設為EXC_RETURN。
將PC設為EXC_RETURN是為了要讓exception得的機制知道exception完成了, 藉由偵測31~4bits, 當全為 f 時,processor會detect到他不是一個一般的branch,並且會開始執行exception return的部分。
這張圖很清楚的說明exception return做了甚麼事:


- ****List of System Exceptions****

- List of Interrupts

# Vector table :
[中斷向量表](https://blog.xuite.net/embeddedsystem_book/twblog/174144583-%E4%B8%AD%E6%96%B7%E5%90%91%E9%87%8F%E8%A1%A8%E6%B7%BA%E8%AB%87)

CPU(或中斷控制器)會為每一個中斷編一個序列號,而中斷可以分為兩個種類,一種是CPU自行產生的內部中斷(又稱例外 - exception),以及由周邊設備產生的外部中斷。而vector table便是紀錄在發生不同的中斷時,所要執行的函式位址。
**基本上中斷向量表的實作就是一個C的陣列,當然也可以用組合語言寫,但意義是一樣的。實際上CPU只有記憶體位址的觀念,當然不知道C的陣列是什麼;但說穿了所謂的中斷向量表,就是從某個位址開始,每4個byte為一個單位(entry),每一個entry記錄一個函數的位址**
- 當`exceptions `發生時,處理器必須將開始發生的位址記錄在相對應`exception handler `
- 以一個ARM處理器來說,ARM7TDMI是負責中斷主要的部份
- 簡單來說,每個中斷(16~255)或者外部事件(exception: 1~15)都對應一個中斷號碼(exception numbers)
- 而系統程式(program)必須告訴嵌入式系統的CPU,Vector table在哪裡(位於哪個位址)。
- 系統程式必須maintain一個中斷向量表,這是一個很單純的表格,每一個entry紀錄一個位址,這個位址指到一段程式(或說一個函數),稱為中斷處理程式(或ISR,Interrupt Service Routine)
- 如下圖所示
- 
- 當外部事件或exception產生時,CPU或中斷處理器會知道第幾號中斷產生了,並參考中斷向量表,再將CPU“跳”到中斷向量表相對entry裡紀錄的位址
- 中斷處理程式執行完畢後,會返回被中斷的程式

#### Fault Handling(dummy)
當系統在執行中斷服務時,出現bug或者是未定義的行為時,便會return到一個錯誤的中斷服務函式裡,例如(除以0)

- 除了`hard fault`是總是啟動的以外,其他都是事件發生才觸發啟動。
---
## Exception handling in details :

- AAPCS: Procedure Call Standard for ARM Architecture
- a C function can modify R0 to R3, R12, R14 (LR), and PSR
- R0~R3, R12, LR, and PSR : caller saved registers在執行function call之前需要將這些暫存器裡的資料搬移到stack(內存)中
- R4~R11 are called “callee-saved registers” 這幾個暫存器內存的位址或是資料是不能改變的。若真的需要改變的話,就需要將R4~R11裡的資料暫存到stack裡面然後等中斷服務執行完後把這些值搬移回去。
## stack frame :
上述需要被push到stack的資料會被放到一個資料表裡面,這個資料表我們就稱為`stack frame `
without float point unit:

with float point unit :

#### Exception Sequences Example

------
# Usart :
## USART Configuration
- Enable the USART by writing the UE bit in USART_CR1 register to 1.
- Select the desired baud rate using the USART_BRR register.
- Set the TE bit in USART_CR1 to enable the transmitter
- Set the RE bit in USART_CR1 to enable the receiver
## character transmission :
1. the USART_DR register consists of Transmit data register (TDR).
2. The TXE flag generates an interrupt if the TXEIE bit is set
## Character Reception
1. When a character is received, the RXNE bit is set.
2. Read to the USART_DR register (this clears the RXNE bit)
### RXNE :
1. 由硬體設備控制
2. 當RXNE設定為1的時候,表示資料已被傳送完成而且已經可以讀取到
3. 一旦RXNE被設定為1,則中斷程序便會產生
4. RXNE可以藉由軟體重設為0
5. RXNE在完成接收後且在接收下一段資料之前一定要清空,否則會出現Overrun error
## Baud rate :

# Porting Newlib: `malloc ` & `printf `
自己找重點XD
-------
# OS: User Mode and Kernel Mode
[Cortex M4 暫存器介紹](https://www.itread01.com/content/1541967072.html)
## Privileged and Non-privileged Operation Modes
- 在Cortex M4/M3中,其權限可以分為兩種=> privileged and non-privileged operation modes
- 在一般的情況下coding,我們是處於`user mode `並且操作在unprivileged 的操作模式。
- 在non-privileged operation modes的情況下是不能使用一些`NVIC`的暫存器的
- MPU也可以用來防止在non-privileged operation modes下不會assess到不該assess的記憶體,而這種分為兩種模式的系統可以防止即使在non-privileged的執行權限下失敗了,系統也不會崩潰。
- 多數運行的模式只需要在non-privileged mode
- 
- Operation states and modes:

- processor在thread mode中有兩個LEVEL的權限下運行(privileged access level or unprivileged access level)
- thread mode可以透過SP()在彼此之間互換
- Non-privileged operation mode restrictions
- Limited access to the MSR and MRS instructions
- Cannot access the system timer, NVIC, or system control block
- Might have restricted access to memory or peripherals
## MSP和PSP的差別
- MSP :在handler mode下進行時是使用MSP,當然在thread mode下也可以使用PSP但這就需要特殊呼叫,在邏輯位址上為R13的暫存器
- PSP :在thread mode下進行時是使用PSP,在邏輯上的位址是R13的暫存器
- MSP和PSP的概念就像USART中的BUFF概念是相通的,在USART中我們傳送訊息時是用TXBUFF而接收訊息時是用RXBUFF,但實際上MCU的的UART就只有一個,而TXBUFF和RXBUFF都是在同一個位址。
- 目的: 是為了在進行模式轉換間,減少Stack的占用,同時也可以為不同權限的工作模式設置不同的stack
## Switch Between Privileged and Non-privileged Operation Modes
- 透過SVC可以從Non-privileged 轉換到 Privileged
- 透過software設定可以從Privileged 轉換到 Non-privileged
## Special register(特殊功能暫存器組):

- 程式狀態暫存器組(PSRs/xPSR) 3個
- 中斷遮蔽暫存器組(PRIMASK, FAULTMASK,以及BASEPRI)
- 控制暫存器(CONTROL)
這7個暫存器只能被專用的MSR和MRS指令訪問,而且它們也沒有儲存器地址。
### 程式狀態暫存器

[詳細狀態暫存器說明](http://b8807053.pixnet.net/blog/post/3612679-arm%E5%9F%BA%E7%A4%8E%E7%9F%A5%E8%AD%98%E6%95%99%E7%A8%8B%E4%BA%8C)
### 中斷遮蔽暫存器:
- PRIMASK : 這個暫存器只有一位,當該位為1時,就遮蔽所有可遮蔽的異常,除NMI和硬體fault可以響應。預設是0;
- FAULTMASK: 這個暫存器也只有一位,置1時,只有NMI才能響應,所有其他異常均遮蔽。預設為0
- BASEPRI: 這個暫存器的有效位由表達優先順序的位數決定,最多9位。它定義了被遮蔽優先順序的閾值,當優先順序號大於等於此值的中斷均被關閉。預設為0,則不關閉任何中斷
- **簡單來說: 想要暫時關閉中斷 : 要處理PRIMASK和BASEPRI。**
- **FAULTMASK: 被OS用於暫時關閉fault處理機能。**
:::info
PRIMASK=1;關中斷
PRIMASK=0; 開中斷
FAULTMASK=1; 關異常
FAULTMASK=0; 開異常
:::
### 控制暫存器:
- **用於定義特權級別和堆疊指標的使用(MSP/PSP)**
- M4: 特有,當處理異常時是否儲存浮點環境
- 只能在 privileged access level 才是可讀可寫,而 unprivileged access levels則是可以讀取不能修改

**對映下圖:**

- 在嵌入式系統被執行時,控制暫存器可以控制不同的Task運行在privileged或是unprivileged的情況下。
:::info
### 切換access level
**處理器一開始一定是在優先級的執行緒模式**,這時候可以將其切換到非優先級(Control[0]=1)。 但是在執行緒模式下,非優先級不能直接變成優先級。 非優先級的執行緒模式要是想回到優先級,必須使用例外處理(exception handler),當處理器處在處理模式時會變成優先級(Control[0]=0)。 然後再跳回執行緒模式即可。
RTOS或嵌入式作業系統可以用這個技巧去設計更安全的應用程式。
```css=
[Kernel] Control: 0x0
[Kernel] SP: 0x2001ffc8
[Kernel] MSP: 0x2001ffc8
[Kernel] PSP: 0x0
[Kernel] Switch to unprivileged thread mode & start user task (psp_init = 0x20018000).
[User] Start in unprivileged thread mode.
[User] Control: 0x3
[User] SP: 0x20017ff8
[User] MSP: 0x0
[User] PSP: 0x0
[User] Switch to privileged thread mode by writing the control register directly. (forbidden)
[User] Control: 0x3
[User] SP: 0x20017ff8
[User] MSP: 0x0
[User] PSP: 0x0
```
:::
#### 結論:
- 在Handler mode中執行必定是Privileged的權限
- 無法直接從unprivilege轉換成Privileged的權限,其用意是在保護系統和核心。
----
## 11 - OS: System Call
### SVC (Supervisor Call) Exception
SVC的機制可以當作是一個API負責處理:是否允許Tasks可以assess到系統資源。

透過SVC-instruction我們可以產生SVC-中斷,根據其給的參數 決定要做什麼事(SVC numbers)
共有8個位元,也就是256個api
```css=
SVC #0x3 ; Call SVC function 3
```

## 為什麼要多花8bits給SVC來做儲存?
因為我們可能在執行程式時,會有需要不一樣的權限,若是使用register可能會造成值不正確。
## 例外處理程序的實例:

:::success
## 背這張圖:

有沒有發現, 當進入 user thread mode 狀態時, 並沒有一條路直接到 privileged thread ,
而是要透過 exception 進到 privileged handler. 且 user thread 並沒有權限直接去改
program CONTROL register. ( user thread mode 是 unprivileged 的 )
那怎麼去進到 所謂的 privileged kernel mode 去管理資源呢.
這時候靠的就是 SVC exception.
- SVC Sequence:

## LAB11-2 SVC Handler: Extract the SVC Parameter
1.

透過SVC來幫我們做事情,程式透過`sys_call()`中的svc instruction進入到SVC_Hanler中,而在進入`handler`之前,硬體機制幫我們作了以下的事情:
- 把r0~r3 LR PSR和return address push 到stack裡
- 把exc_return 讀取到LR中
- 讀取Handler_entry中的exception number給PC,已進入所對應的例外處理
### SVC_handler :
2.
- 組語:
```cpp=
.type svc_handler, %function
.global svc_handler
svc_handler:
mov r0, lr //把exc_return(在lr裡面)給r0
mrs r1, msp //把MSP(Main stack pointer)給r1 (main_back_up)因為在handler中或許會用到MSP,所以要先把之前的值處理起來
b svc_handler_c // 回到svc_handler_c函式裡
```
3.
- c語言:
```cpp=
if (handler&0b100) //Test bit 2 of EXC_RETURN
{
stack_frame_ptr = (uint32_t* )read_psp(); //if 1, stacking used PSP
printf("[SVC Handler] Stacking used PSP: 0x%X \r\n\n", (unsigned int)stack_frame_ptr);
}
else
{
stack_frame_ptr = (uint32_t* )main_backup;//if 0, stacking used MSP
printf("[SVC Handler] Stacking used MSP: 0x%X \r\n\n", (unsigned int)stack_frame_ptr);
}
```
透過暫存起來的exc_return判斷是用PSP還是MSP,並且把該Pointer給stack_frame_ptr,再透過`stack_frame_ptr `去找到`RETURN_ADDRESS`(stack_frame_ptr+6)即為return_address(LR)。
```css=
uint32_t stacked_r0 =(*(stack_frame_ptr));// (*(stack_frame_ptr));
uint32_t stacked_r1 =(*(stack_frame_ptr+1));;
uint32_t stacked_return_addr = (*(stack_frame_ptr+6));
uint16_t svc_instruction = *((uint16_t *)stacked_return_addr - 1); //透過找到return_address 我們也可以找到SVC_instruction的值: 如下圖
uint8_t svc_num =(uint8_t)svc_instruction;
```

找到`SVC_instruction`後我們就可以找到對應的instruction number。
## 為甚麼要找到SVC parameter?
The SVC exception handler needs to extract the SVC parameter and determine what action it needs to perform.
以本例子來說,我們根據SVC parameter判斷為需要將r0和r1做相加,於是我們就做相加的動作:
```css=
if (svc_num == 0xA)
//return r0 + r1
*stack_frame_ptr = stacked_r0 + stacked_r1;
else
*stack_frame_ptr = 0;//return 0
```
:::
## 12 - OS: Context Switch
+ 簡單的 context switch 可使用 systick 產生 exception 達成
+ 流程如圖所示
1. 當前的 task 被 sytick exception 中斷後,**進入 systick handler**
+ 此時,caller saved register 會被 push 至當前 task 的 user stack 中
2. 在 systick handler 中,是使用 msp
+ 先把 LR 存好
+ 把 callee saved register 存好到 stack
+ 再 branch 至 sw_task (以 c 實作) 以切換成新的 psp
+ 最後把存好的 LR 取回
3. **離開 systick handler**後,因為 psp 已更新,就會到下一個 user stack 中

+ 每個 user stack 結構如下圖


`systick`倒數,時間到了就進入到`systick handler`裡面先存取`r4~r11`還有`LR(excreturn )`,接著`branch`到`scheduler`裡面,`scheduler`再`return`下一個task的`psp`給`systick handler`,`systick-hanlder`回復`r4~r11`還有`lr(exc_return)`後就結束中斷,再回到task裡面時,硬體會回復`stack-frame`,就會發現所有的register變成下一個task。 這樣子就變成一個context switch
## Ivan, Joe ICB