4.1 SWI中斷處理
前面我們學習ARM工作模式中,處理器模式切換可以通過軟體控制進行切換,即修改CPSR模式位,但這是在特權模式下,當我們處于使用者模式下,是沒有權限實作模式轉換的。若想實作模式切換,隻能由另一種方法來實作,即通過外部中斷或是異常處理過程進行切換。于是ARM指令集中提供了兩條産生異常的指令,通過這兩條指令可以用軟體的方法實作異常,其中一個就是中斷指令SWI 。
4.1.1軟體中斷
軟中斷是利用硬體中斷的概念,用軟體方式進行模拟,實作從使用者模式切換到特權模式并執行特權程式的機制。
硬體中斷是由電平的實體特性決定,在電平變化時引發中斷操作,而軟中斷是通過一條具體指令SWI,引發中斷操作,也就是說使用者程式裡可以通過寫入SWI指令來切換到特權模式,當CPU執行到SWI指令時會從使用者模式切換到管理模式下,執行軟體中斷處理。由于SWI指令由作業系統提供的API封裝起來,并且軟體中斷處理程式也是作業系統編寫者提前寫好的,是以使用者程式調用API時就是将操作權限交給了作業系統,是以使用者程式還是不能随意通路硬體。
軟體中斷指令(Software Interrupt, SWI)用于産生軟中斷,實作從使用者模式變換到管理模式,CPSR儲存到管理模式的SPSR中,執行轉移到SWI向量。在其他模式下也可以使用SWI指令,處理器同樣切換到管理模式。
1、SWI指令格式如下:
SWI{cond} immed_24
其中:
immed_24 24位立即數,值為從0――16777215之間的整數。
SWI指令後面的24立即數是幹什麼用的呢?使用者程式通過SWI指令切換到特權模式,進入軟中斷處理程式,但是軟中斷處理程式不知道使用者程式到底想要做什麼?SWI指令後面的24位用來做使用者程式和軟中斷處理程式之間的接頭暗号。通過該軟中斷立即數來區分使用者不同操作,執行不同核心函數。如果使用者程式調用系統調用時傳遞參數,根據ATPCSC語言與彙編混合程式設計規則将參數放入R0~R4即可。
2、指令舉例
使用SWI指令時,通常使用以下兩種方法進行參數傳遞,SWI異常處理程式可以提供相關的服務,這兩種方法均是使用者軟體協定。SWI異常中斷處理程式要通過讀取引起軟體中斷的SWI指令,以取得24為立即數。
1)、指令中24位的立即數指定了使用者請求的服務類型,中斷服務的參數通過通用寄存器傳遞。
如下面這個程式産生一個中斷号位12 的軟中斷:
MOV R0,#34 ;設定功能号為34
SWI 12 ;産生軟中斷,中斷号為12
2)、指令中的24位立即數被忽略,使用者請求的服務類型有寄存器R0的值決定,參數通過其他的通用寄存器傳遞。
如下面的例子通過R0傳遞中斷号,R1傳遞中斷的子功能号:
MOV R0, #12 ;設定12号軟中斷
MOV R1, #34 ;設定功能号為34
SWI 0
下面的例子通過系統調用函數int led_on(int led_no)實作點亮第led_no 個LED燈,由于C語言裡沒有SWI 指令對應的語句,是以這兒要用到C語言與彙編混合程式設計,led_on函數裡将參數led_no的值傳遞給R0,通過軟中斷SWI指令切換到軟中斷管理模式,同時R0 軟中斷方式點亮LED燈,使用者通過SWI #1指令可以點燈,具體點亮哪個燈,通過R0儲存參數傳遞,如果亮燈成功傳回對應LED号。
#define __led_on_swi_no 1 // 軟中斷号1,調用管理模式下的do_led_on函數
int led_on(int led_no)
{
int ret; // 傳回值
_asm
{
// 由于C程式中沒有SWI對應表達式,是以使用混合程式設計
mov r0, led_no // 根據ATPCS規則,r0存放第一個參數
swi __led_on_swi_no // 産生SWI軟中斷,中斷号為__led_on_swi_no
mov ret, r0 // 軟中斷處理結束,取得中斷處理傳回值,傳遞給ret變量
}
return ret; // 将ret傳回給調用led_on的語句
}
4.1.2軟中斷處理
CPU執行到swi xxx執行後,産生軟體中斷,由異常處理部分知識可知,軟中斷産生後CPU将強制将PC的值置為異常向量表位址0x08,在異常向量表0x08處安放跳轉指令b HandleSWI,這樣CPU就跳往我們自己定義的HandleSWI處執行。
1–保護現場
軟中斷進行中通過STMFD SP!, {R0-R12, LR} 要儲存程式執行現場,将R0~R12通用寄存器資料儲存在管理模式下SP棧内,LR由硬體自動儲存軟中斷指令下一條指令的位址(後面利用LR的位址取得SWI指令編碼),該寄存器值也儲存在SP棧内,将來處理完畢之後傳回;
2–擷取SWI指令編碼
由SWI指令編碼知識可知,SWI指令低24位儲存有軟中斷号,通過LDR R4, [LR, #-4]指令,取得SWI指令編碼(LR為硬體自動儲存SWI xxx指令的下一條指令位址,LR – 4就是SWI指令位址),将其儲存在R4寄存器中。通過BIC R4, R4, #0xFF000000 指令将SWI指令高8位清除掉,隻保留低24位立即數,取得SWI指令編碼;
3–根據SWI指令做出相應操作
根據24位立即數中的軟中斷号判斷使用者程式的請求操作。如果24位立即數為1,表示led_on系統調用産生的軟中斷,則在管理模式下調用對應的亮燈操作do_led_on。如果24位立即數為2,表示led_off系統調用産生的軟中斷,則調用滅燈操作do_led_on,根據ATPCS調用規則,R0~R3做為參數傳遞寄存器,在軟中斷進行中沒有使用這4個寄存器,而是使用R4作為操作寄存器的。
4–傳回并恢複現場
執行完系統調用操作之後,傳回到swi_return(在調用對應系統操作時,通過LDREQ LR, =swi_return設定了傳回位址),執行傳回處理,通過LDMIA SP!, {R0-R12, PC}^ 指令将使用者寄存器資料恢複到R0~R12,将進入軟中斷處理時儲存的傳回位址LR的值恢複給PC,實作程式傳回,同時還恢複了狀态寄存器。切換回使用者模式下程式中繼續執行。
; 異常向量表開始
; 0x00: 複位Reset異常
b Reset
; 0x04: 未定義異常(未處理)
HandleUndef
b HandleUndef
; 0x08: 軟體中斷異常,跳往軟體中斷處理函數HandleSWI
b HandleSWI
… …
; 省略其它異常向量和對應處理
… …
;***********************************************************************
; 軟中斷處理
;***********************************************************************
IMPORT do_led_on
IMPORT do_led_off
HandleSWI
STMFD SP!, {R0-R12, LR} ; 儲存程式執行現場
LDR R4, [LR, #-4] ; LR - 4 為指令" swi xxx" 的位址,低24位是軟體中斷号
BIC R4, R4, #0xFF000000 ; 取得ARM指令24位立即數
CMP R4, #1 ; 判斷24位立即數,如果為1,調用do_led_on系統調用
LDREQ LR, =swi_return ; 軟中斷處理傳回位址
LDREQ PC, = do_led_on ; 軟中斷号1對應系統調用處理
CMP R4, #2 ; 判斷24位立即數,如果為2,調用do_led_off系統調用
LDREQ LR, =swi_return ; 軟中斷處理傳回位址
LDREQ PC, = do_led_off ; 軟中斷号2對應系統調用處理
MOVNE R0, #-1 ; 沒有該軟中斷号對應函數,出錯傳回-1
swi_return
LDMIA SP!, {R0-R12, PC}^ ; 中斷傳回, ^表示将spsr的值複制到cpsr
其實講到這,會産生一個疑問,什麼時候需要我們從使用者模式切換到管理模式?我們應該記得系統調用,就是使用者态向核心态的切換。
4.1.3系統調用
作業系統的主要功能是為應用程式的運作建立良好的環境,保障每個程式都可以最大化利用硬體資源,防止非法程式破壞其它應用程式執行環境,為了達到這個目的,作業系統會将硬體的操作權限交給核心來管理,使用者程式不能随意使用硬體,使用硬體(對硬體寄存器進行讀寫)時要先向作業系統送出請求,作業系統核心幫助使用者程式實作其操作,也就是說使用者程式不會直接操作硬體,而是提供給使用者程式一些具備預定功能的核心函數,通過一組稱為系統調用的(system call)的接口呈現給使用者,系統調用把應用程式的請求傳給核心,調用相應的核心函數完成所需的處理,将處理結果傳回給應用程式。
這好比我們去銀行取款,使用者自己的銀行帳戶不可能随意操作,必須要有一個安全的操作流程和規範,銀行裡的布局通常被分成兩部分,中間用透明玻璃分隔開,隻留一個小視窗,面向使用者的是使用者服務區,從業人員所在區域為内部業務操作區,取款時,将銀行卡或存折通過小視窗交給業務員,并且告訴他要取多少錢,具體取錢的操作你是不會直接接觸的,業務員會将銀行帳戶裡減掉取款金額,将現金給你。上述操作流程可以很好保護銀行系統,銀行系統的操作全部由業務員來實作,使用者隻能向業務員提出自己的服務請求。銀行裡的小視窗就類似與作業系統的系統調用接口,是将使用者請求傳遞給核心的接口。
圖1
作業系統裡将使用者程式運作在使用者模式下,并且為其配置設定可以使用記憶體空間,其它記憶體空間不能通路,核心态運作在特權模式下,對系統所有硬體進行統一管理和控制。從前面所學知識可以了解到,使用者模式下沒有權限進行模式切換,這也就意味着使用者程式不可能直接通過切換模式去通路硬體寄存器,如果使用者程式試圖通路沒有權限的硬體,會産生異常。這樣使用者程式被限制起來,如果使用者程式想要使用硬體時怎麼辦呢?使用者程式使用硬體時,必須調用作業系統提供的API接口才可以,而作業系統API接口通過軟體中斷方式切換到管理模式下,實作從使用者模式下進入特權模式。
At91rm9200處理器對應的linux2.4.19核心系統調用對應的軟中斷定義如下:
#if defined(__thumb__) //thumb模式
#define __syscall(name) \
"push {r7}\n\t" \
"mov r7, #" __sys1(__NR_##name) "\n\t" \
"swi 0\n\t" \
"pop {r7}"
#else //arm模式
#define __syscall(name) "swi\t" __sys1(__NR_##name) "\n\t"
#endif
#define __sys2(x) #x
#define __sys1(x) __sys2(x)
#define __NR_SYSCALL_BASE 0x900000 //此為OS_NUMBER << 20運算值
#define __NR_open (__NR_SYSCALL_BASE+ 5) //0x900005
舉一個例子來說:open系統調用,庫函數最終會調用__syscall(open),宏展開之後為swi #__NR_open,即,swi #0x900005觸發中斷,中斷号0x900005存放在[lr,#-4]位址中,處理器跳轉到arch/arm/kernel/entry-common.S中vector_swi讀取[lr,#-4]位址中的中斷号,之後查詢arch/arm/kernel/entry-common.S中的sys_call_table系統調用表,該表内容在arch/arm/kernel/calls.S中定義,__NR_open在表中對應的順序号為
__syscall_start:
.long SYMBOL_NAME(sys_open) //第5個
…
将sys_call_table[5]中内容傳給pc,系統進入sys_open函數,處理實質的open動作
注:用到的一些函數資料所在檔案,如下所示
arch/arm/kernel/calls.S聲明了系統調用函數
include/asm-arm/unistd.h定義了系統調用的調用号規則
vector_swi定義在arch/arm/kernel/entry-common.S
vector_IRQ定義在arch/arm/kernel/entry-armv.S
vector_FIQ定義在arch/arm/kernel/entry-armv.S
arch/arm/kernel/entry-common.S中對sys_call_table進行了定義:
.type sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S" //将calls.S中的内容順序連結到這裡
源程式:
ENTRY(vector_swi)
save_user_regs
zero_fp
get_scno //将[lr,#-4]中的中斷号轉儲到scno(r7)
arm710_bug_check scno, ip
#ifdef CONFIG_ALIGNMENT_TRAP
ldr ip, __cr_alignment
ldr ip, [ip]
mcr p15, 0, ip, c1, c0 @ update control register
#endif
enable_irq ip
str r4, [sp, #-S_OFF]! @ push fifth arg
get_current_task tsk
ldr ip, [tsk, #TSK_PTRACE] @ check for syscall tracing
bic scno, scno, #0xff000000 @ mask off SWI op-code
//#define OS_NUMBER 9[entry-header.S]
//是以對于上面示例中open系統調用号scno=0x900005
//eor scno,scno,#0x900000
//之後scno=0x05
eor scno, scno, #OS_NUMBER << 20 @ check OS number
//sys_call_table項為calls.S的内容
adr tbl, sys_call_table @ load syscall table pointer
tst ip, #PT_TRACESYS @ are we tracing syscalls?
bne __sys_trace
adrsvc al, lr, ret_fast_syscall @ return address
cmp scno, #NR_syscalls @ check upper syscall limit
//執行sys_open函數
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
add r1, sp, #S_OFF
2: mov why, #0 @ no longer a real syscall
cmp scno, #ARMSWI_OFFSET
eor r0, scno, #OS_NUMBER << 20 @ put OS number back
bcs SYMBOL_NAME(arm_syscall)
b SYMBOL_NAME(sys_ni_syscall) @ not private func
/*
* This is the really slow path. We're going to be doing
* context switches, and waiting for our parent to respond.
*/
__sys_trace:
add r1, sp, #S_OFF
mov r0, #0 @ trace entry [IP = 0]
bl SYMBOL_NAME(syscall_trace)
/*
//2007-07-01 gliethttp [entry-header.S]
//Like adr, but force SVC mode (if required)
.macro adrsvc, cond, reg, label
adr\cond \reg, \label
.endm
//對應反彙編:
//add lr, pc, #16 ; lr = __sys_trace_return
*/
adrsvc al, lr, __sys_trace_return @ return address
add r1, sp, #S_R0 + S_OFF @ pointer to regs
cmp scno, #NR_syscalls @ check upper syscall limit
ldmccia r1, {r0 - r3} @ have to reload r0 - r3
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
b 2b
__sys_trace_return:
str r0, [sp, #S_R0 + S_OFF]! @ save returned r0
mov r1, sp
mov r0, #1 @ trace exit [IP = 1]
bl SYMBOL_NAME(syscall_trace)
b ret_disable_irq
.align 5
#ifdef CONFIG_ALIGNMENT_TRAP
.type __cr_alignment, #object
__cr_alignment:
.word SYMBOL_NAME(cr_alignment)
#endif
.type sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"
4.2輪詢方式
以KEY2控制LED3亮滅為例:
圖2
【0】檢測按鍵k2,按鍵k2按下一次,燈LED2閃一次。
【1】檢視原理圖,連接配接引腳和控制邏輯
(1)按鍵k2 連接配接在GPX1_1引腳
(2)控制邏輯
k2 按下 ---- K2閉合 ---- GPX1_1 低電壓
k2 常态 ---- K2打開 ---- GPX1_1 高電壓
【2】檢視相應的晶片手冊
【2-1】循環檢測GPX1_1引腳輸入的電平,為低電壓時,按鍵按下
(1)配置GPX1_1引腳功能為輸入,設定内部上拉下拉禁止。
GPX1.CON = GPX1.CON &(~(0xf<<4)) ;
GPX1.PUD = GPX1.PUD & ~(0x3 << 2);
(2)循環檢測:
while(1)
{
if(!(GPX1.DAT & (0x1<<1))) // 傳回為真,按鍵按下
{
msdelay(10);
if(!(GPX1.DAT & (0x1<<1))) //二次檢測,去抖
{
GPX2.DAT |= 0x1 << 7; //Turn on LED2
mydelay_ms(500);
GPX2.DAT &= ~(0x1<<7); //Turn off LED2
mydelay_ms(500);
while(!(GPX1.DAT & (0x1<<1)));
}
}
}
這種輪詢方式始終占着CPU,不利于操作。
4.3 IRQ中斷方式
将K2按下時,GPX1_1引腳獲得的電平,作為異常事件。使能異常處理,k2每按下一次,響應一次異常處理。SPI 傳遞流程如下示:
圖3
注:
Exynos4412中斷控制器包括160個中斷控制源,這些中斷源來自軟中斷(SGI),私有外部中斷(PPI),公共外部中斷(SPI)。
Exynos4412采用GIC中斷控制器,主要是因為Contex-A9 是多核處理器,GIC(Generic Interrupt Controller)通用中斷控制器用來選擇使用哪個CPU接口,具體主要有兩個功能:
1)配置設定器:設定一個開關,是否接收外部中斷源;為該中斷源選擇CPU接口;
2)CPU接口:設定一個開發,是否接受該中斷源請求;
具體實作如下:
1、外設一級 —設定 GPIO控制器
1-- 将GPX1_1引腳的上拉和下拉禁止
GPX1PUD[3:2]= 0b00;
2 – 将GPX1_1引腳功能設定為中斷功能 WAKEUP_INT1[1] — EXT_INT41[1]
GPX1CON[7:4] = 0xf
3 – EXT_INT41CON 配置觸發電平
目前配置成下降沿觸發:
EXT_INT41CON[6:4] = 0x2
4 – EXT_INT41_FLTCON0 配置中斷引腳濾波
預設就是打開的,不需要配置
5 – EXT_INT41_MASK 中斷使能寄存器
使能INT41[1]
EXT_INT41_MASK[1] = 0b0
6 – EXT_INT41_PEND 中斷狀态寄存器
當GPX1_1引腳接收到中斷信号,中斷發生,中斷狀态寄存器EXT_INT41_PEND 相應位會自動置1
注意:中斷處理完成的時候,需要清除相應狀态位。置1清0.
EXT_INT41_PEND[1] =0b1
2、中斷控制器
1-- 找到外設中斷名稱和GIC中斷控制器對應的名稱
檢視晶片手冊(本例:Exynos_4412 – 9.2表)
WAKEUP_INT1[1] — EXT_INT41[1] — INT[9] — SPI[25]/ID[57]
其對應INT[9],中斷ID為57,這是非常重要的,在後面的寄存器設定中起很大作用;
下面是外設與中斷控制器處理具體流程:
圖4
2 – GIC使能
ICDDCR =1;
使能配置設定器。
3 – 使能相應中斷到配置設定器
ICDISER.ICDISER1 |= (0x1 << 25); //57/32 =1…25 取整數(那個寄存器) 和餘數(哪位)
ICDISER用于使能相應中斷到配置設定器,一個bit控制一個中斷源,一個ICDISER可以控制32個中斷源,這裡INT[9] 對應的中斷ID為57,是以在ICDSER1中進行設定,57/32 =1餘25,是以這裡在ICDISER1第25位置一。
4 – 選擇CPU接口
設定SPI[25]/ID[57]由那個cpu處理,目前設定為cpu0的irq中斷
ICDIPTR.ICDIPTR14 |= 0x01<<8; //SPI25 interrupts are sent to processor 0 //57/4 = 14…1 14号寄存器的[15:8]
ICDIPTR寄存器每8個bit 控制一個中斷源
5 – 全局使能cpu0中斷處理
CPU0.ICCICR |= 0x1;
使能中斷到CPU。
6 – 優先級屏蔽寄存器,設定cpu0能處理所有的中斷。
CPU0.ICCPMR = 0xFF;
3、ARM核心(cpu0)
前面兩步設定好,就可以等待中斷的發生了,當中斷發生時,ARM核心的處理過程如下:
1-- 四大步三小步 — 硬體
圖5
(1)拷貝 CPSR 到 SPSR _ < mode>
(2)設定适當的 CPSR 位:
(2-1)–改變處理器狀态進入 ARM 态
(2-2)–改變處理器模式進入相應的異常模式
(2-3)–設定中斷禁止位禁止相應中斷 (如果需要)
(3)儲存傳回位址到 LR_
(4)設定 PC 為相應的異常向量
2 – 中斷服務程式 — start.S 彙編
.arch armv7-a
.arm
.globl _start
_start:
B reset
NOP
NOP
NOP
NOP
NOP
@@@@ B irq_handler
@@@@ LDR PC, [Rx]
LDR PC, __irq_handler
NOP
__irq_handler: .word irq_handler
reset:
MOV R0, #0x40000000
mcr p15,0,R0,c12,c0,0
/* Switch Into SVC Mode */
MRS R0, CPSR
BIC R0, R0, #0x1F
ORR R0, R0, #0x0D3 @@ Disable IRQ and Disable FIQ
MSR CPSR_c, R0
/* svc Mode Stack Initialization */
LDR SP, =svc_stack_top
/* Switch Into IRQ Mode */
MSR CPSR_c, #0x0D2
/* irq Mode Stack Initialization */
LDR SP, =irq_stack_top
/* Switch Into USR Mode and Enable IRQ */
MSR CPSR_c, #0x50 @@ 0b0(I)1(F)0(T)_10000
LDR SP, =usr_stack_top
BL main
__die:
B __die
.align 4
/* void do_irq(void) */
irq_handler:
STMFD SP!, {R0-R12, LR}
BL do_irq
LDMFD SP!, {R0-R12, PC}^
/** void __delay(void)*/
.global __delay
__delay:
PUSH {R2, LR}
MOV R2, #0x20000000
loop1:
SUBS R2, R2, #0x1
BNE loop1
POP {R2, LR}
MOV PC, LR
.data
.space 8192
usr_stack_top:
.space 1024
__reserved:
.space 4096
svc_stack_top:
.space 4096
irq_stack_top:
.end
3–中斷處理程式 — do_irq函數 c語言(函數原型void name(void))
(1) 讀取正在處理的中斷ID寄存器(ICCIAR)
irq_num = (CPU0.ICCIAR & 0x1FF);
(2)根據irq_num,分支進行中斷
switch(irq_num)
{
case 57:
break;
....
}
#include "exynos_4412.h"
#include "led.h"
/*
* 裸機代碼,不同于LINUX 應用層, 一定加循環控制
*/
int main (void)
{
GPX1.CON =GPX1.CON & (~(0xf << 4)) |(0xf << 4); //配置引腳功能為外部中斷
GPX1.PUD = GPX1.PUD & (~(0x3 << 2)); //關閉上下拉電阻
EXT_INT41_CON = EXT_INT41_CON &(~(0xf << 4))|(0x2 << 4); //外部中斷觸發方式
EXT_INT41_MASK = EXT_INT41_MASK & (~(0x1 << 1)); //使能中斷
ICDDCR = 1; //使能配置設定器
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (0x1 << 25); //使能相應中斷到配置設定器
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xff << 8))|(0x1 << 8); //選擇CPU接口
CPU0.ICCPMR = 255; //中斷屏蔽優先級
CPU0.ICCICR = 1; //使能中斷到CPU
led_init();
while(1)
{
}
return 0;
}