ARM 匯編啟動代碼詳解:從中斷向量表到中斷處理
引言
在嵌入式系統開發中,ARM 處理器(如 Cortex-A 系列)的啟動代碼是系統初始化和運行的基礎。啟動代碼通常包括中斷向量表的創建、初始化硬件狀態(如關閉緩存和 MMU)、設置棧指針以及處理各種中斷(如 IRQ、FIQ 等)。本文將詳細解析一段典型的 ARM 匯編啟動代碼,涵蓋 _start
函數、中斷向量表、復位處理程序(Reset_Handler
)以及 IRQ 中斷處理程序(IRQ_Handler
)。代碼參考了 ARM Cortex-A(armV7)編程手冊和 Cortex-A7 技術參考手冊,確保內容準確且實用。
1. 代碼概述
以下是完整代碼的結構:
.global _start /* 全局標號 *//** 描述:_start函數,首先是中斷向量表的創建* 參考文檔:ARM Cortex-A(armV7)編程手冊V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM處理器模型和寄存器)* ARM Cortex-A(armV7)編程手冊V4.0.pdf P165 11.1.1 Exception priorities(異常)*/
_start:ldr pc, =Reset_Handler /* 復位中斷 */ ldr pc, =Undefined_Handler /* 未定義中斷 */ ldr pc, =SVC_Handler /* SVC(Supervisor)中斷 */ ldr pc, =PrefAbort_Handler /* 預取終止中斷 */ ldr pc, =DataAbort_Handler /* 數據終止中斷 */ ldr pc, =NotUsed_Handler /* 未使用中斷 */ ldr pc, =IRQ_Handler /* IRQ中斷 */ ldr pc, =FIQ_Handler /* FIQ(快速中斷)未定義中斷 *//* 復位中斷 */
Reset_Handler:cpsid i /* 關閉全局中斷 *//* 關閉 I、D Cache 和 MMU */mrc p15, 0, r0, c1, c0, 0 /* 讀取 CP15 的 C1 寄存器到 R0 中 */ bic r0, r0, #(0x1 << 12) /* 清除 C1 寄存器的 bit12 位 (I 位),關閉 I Cache */ bic r0, r0, #(0x1 << 2) /* 清除 C1 寄存器的 bit2 (C 位),關閉 D Cache */ bic r0, r0, #0x2 /* 清除 C1 寄存器的 bit1 (A 位),關閉對齊 */ bic r0, r0, #(0x1 << 11) /* 清除 C1 寄存器的 bit11 (Z 位),關閉分支預測 */ bic r0, r0, #0x1 /* 清除 C1 寄存器的 bit0 (M 位),關閉 MMU */ mcr p15, 0, r0, c1, c0, 0 /* 將 r0 寄存器中的值寫入到 CP15 的 C1 寄存器中 */#if 0/* 匯編版本設置中斷向量表偏移 */ldr r0, =0X87800000dsbisbmcr p15, 0, r0, c12, c0, 0dsbisb
#endif/* 設置各個模式下的棧指針 *//* 進入 IRQ 模式 */mrs r0, cpsrbic r0, r0, #0x1f /* 將 r0 寄存器中的低 5 位清零,也就是 cpsr 的 M0~M4 */ orr r0, r0, #0x12 /* r0 或上 0x12, 表示使用 IRQ 模式 */ msr cpsr, r0 /* 將 r0 的數據寫入到 cpsr_c 中 */ ldr sp, =0x80600000 /* 設置 IRQ 模式下的棧首地址為 0X80600000, 大小為 2MB *//* 進入 SYS 模式 */mrs r0, cpsrbic r0, r0, #0x1f /* 將 r0 寄存器中的低 5 位清零 */ orr r0, r0, #0x1f /* r0 或上 0x1f, 表示使用 SYS 模式 */ msr cpsr, r0 /* 將 r0 的數據寫入到 cpsr_c 中 */ ldr sp, =0x80400000 /* 設置 SYS 模式下的棧首地址為 0X80400000, 大小為 2MB *//* 進入 SVC 模式 */mrs r0, cpsrbic r0, r0, #0x1f /* 將 r0 寄存器中的低 5 位清零 */ orr r0, r0, #0x13 /* r0 或上 0x13, 表示使用 SVC 模式 */ msr cpsr, r0 /* 將 r0 的數據寫入到 cpsr_c 中 */ ldr sp, =0X80200000 /* 設置 SVC 模式下的棧首地址為 0X80200000, 大小為 2MB */cpsie i /* 打開全局中斷 */#if 0/* 使能 IRQ 中斷 */mrs r0, cpsr /* 讀取 cpsr 寄存器值到 r0 中 */ bic r0, r0, #0x80 /* 將 r0 寄存器中 bit7 清零,也就是 CPSR 中的 I 位清零,表示允許 IRQ 中斷 */ msr cpsr, r0 /* 將 r0 重新寫入到 cpsr 中 */
#endifb main /* 跳轉到 main 函數 *//* 未定義中斷 */
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC 中斷 */
SVC_Handler:ldr r0, =SVC_Handlerbx r0/* 預取終止中斷 */
PrefAbort_Handler:ldr r0, =PrefAbort_Handler bx r0/* 數據終止中斷 */
DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0/* 未使用的中斷 */
NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0/* IRQ 中斷!重點!!!!! */
IRQ_Handler:push {lr} /* 保存 lr 地址 */ push {r0-r3, r12} /* 保存 r0-r3,r12 寄存器 */mrs r0, spsr /* 讀取 spsr 寄存器 */ push {r0} /* 保存 spsr 寄存器 */mrc p15, 4, r1, c15, c0, 0 /* 從 CP15 的 C0 寄存器內的值到 R1 寄存器中 */ add r1, r1, #0X2000 /* GIC 基地址加 0X2000,也就是 GIC 的 CPU 接口端基地址 */ ldr r0, [r1, #0XC] /* GIC 的 CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器 */ push {r0, r1} /* 保存 r0, r1 */cps #0x13 /* 進入 SVC 模式,允許其他中斷再次進去 */push {lr} /* 保存 SVC 模式的 lr 寄存器 */ ldr r2, =system_irqhandler /* 加載 C 語言中斷處理函數到 r2 寄存器中 */ blx r2 /* 運行 C 語言中斷處理函數,帶有一個參數,保存在 R0 寄存器中 */pop {lr} /* 執行完 C 語言中斷服務函數,lr 出棧 */ cps #0x12 /* 進入 IRQ 模式 */ pop {r0, r1} str r0, [r1, #0X10] /* 中斷執行完成,寫 EOIR */pop {r0} msr spsr_cxsf, r0 /* 恢復 spsr */pop {r0-r3, r12} /* r0-r3, r12 出棧 */ pop {lr} /* lr 出棧 */ subs pc, lr, #4 /* 將 lr-4 賦給 pc *//* FIQ 中斷 */
FIQ_Handler:ldr r0, =FIQ_Handler bx r0
2. 代碼功能詳解
2.1 _start
:程序入口和中斷向量表
_start
是程序的入口點,它首先創建中斷向量表。ARM 處理器在啟動或發生異常時會根據向量表跳轉到對應的處理程序。向量表包含 8 個條目,每個條目對應一種異常或中斷:
- 復位中斷(Reset):系統上電或復位后執行。
- 未定義中斷(Undefined):執行了未定義的指令。
- SVC 中斷(Supervisor Call):軟件觸發系統調用。
- 預取終止中斷(Prefetch Abort):指令預取失敗。
- 數據終止中斷(Data Abort):數據訪問失敗。
- 未使用中斷(Not Used):保留,未定義。
- IRQ 中斷(Interrupt Request):外部設備請求的中斷。
- FIQ 中斷(Fast Interrupt Request):快速中斷,通常用于高優先級任務。
代碼使用 ldr pc, =handler
將每個處理程序的地址加載到程序計數器 pc
,實現跳轉。例如:
ldr pc, =Reset_Handler /* 復位中斷 */
這表示當發生復位時,處理器會跳轉到 Reset_Handler
執行。
2.2 Reset_Handler
:復位處理程序
Reset_Handler
是系統啟動后的第一個執行函數,負責初始化硬件狀態。以下是其主要步驟:
(1) 關閉全局中斷
cpsid i /* 關閉全局中斷 */
- 使用
cpsid i
關閉所有 IRQ 中斷,確保初始化過程中不受干擾。
(2) 關閉緩存和 MMU
mrc p15, 0, r0, c1, c0, 0 /* 讀取 CP15 的 C1 寄存器到 R0 */
bic r0, r0, #(0x1 << 12) /* 關閉 I Cache */
bic r0, r0, #(0x1 << 2) /* 關閉 D Cache */
bic r0, r0, #0x2 /* 關閉對齊 */
bic r0, r0, #(0x1 << 11) /* 關閉分支預測 */
bic r0, r0, #0x1 /* 關閉 MMU */
mcr p15, 0, r0, c1, c0, 0 /* 將修改寫入 CP15 C1 */
- CP15 協處理器:CP15 負責系統配置,這里讀取其控制寄存器 C1。
- 位操作:使用
bic
清除特定位,分別關閉指令緩存(I Cache)、數據緩存(D Cache)、內存管理單元(MMU)等功能。這是“讀-改-寫”模式,確保初始狀態干凈。
(3) 設置中斷向量表偏移(可選)
#if 0
ldr r0, =0X87800000
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
#endif
- 這部分被禁用(
#if 0
),用于將中斷向量表基址設置為0x87800000
,常見于需要調整向量表位置的場景(如從 Flash 移動到 SRAM)。dsb
和isb
確保操作同步。
(4) 設置不同模式的棧指針
ARM 處理器有多種工作模式(如 IRQ、SVC、SYS),每個模式需要獨立的棧。代碼為 IRQ、SYS 和 SVC 模式設置棧指針:
/* 進入 IRQ 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 清零模式位 */
orr r0, r0, #0x12 /* 設置為 IRQ 模式 */
msr cpsr, r0
ldr sp, =0x80600000 /* 設置棧頂為 0x80600000,大小 2MB *//* 進入 SYS 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x1f /* 設置為 SYS 模式 */
msr cpsr, r0
ldr sp, =0x80400000 /* 設置棧頂為 0x80400000 *//* 進入 SVC 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13 /* 設置為 SVC 模式 */
msr cpsr, r0
ldr sp, =0x80200000 /* 設置棧頂為 0x80200000 */
- 模式切換:使用
mrs
讀取當前程序狀態寄存器(CPSR),bic
清零模式位(低 5 位),orr
設置新模式,msr
寫入回 CPSR。 - 棧設置:棧指針
sp
指向內存區域(這里是 DDR 范圍 0x80000000~0x9FFFFFFF),棧向下增長,必須 4 字節對齊。
(5) 打開全局中斷并跳轉
cpsie i /* 打開全局中斷 */
b main /* 跳轉到 main 函數 */
cpsie i
重新啟用中斷。b main
跳轉到 C 語言的main
函數,標志著初始化完成。
2.3 其他中斷處理程序
除了 Reset_Handler
,代碼還定義了其他中斷處理程序,但大多數只是簡單地進入死循環:
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0SVC_Handler:ldr r0, =SVC_Handlerbx r0PrefAbort_Handler:ldr r0, =PrefAbort_Handler bx r0DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0FIQ_Handler:ldr r0, =FIQ_Handler bx r0
- 這些處理程序使用
ldr
加載自身地址到r0
,然后bx r0
跳轉回去,形成死循環。這是一種簡單處理方式,實際應用中可能需要更復雜的邏輯。
2.4 IRQ_Handler
:IRQ 中斷處理
IRQ_Handler
是代碼中的重點,負責處理外部設備的中斷。以下是詳細解析:
(1) 保存上下文
push {lr} /* 保存返回地址 */
push {r0-r3, r12} /* 保存通用寄存器 */
- 保存中斷前的
lr
(返回地址)和r0-r3, r12
(可能被使用的寄存器)。
(2) 保存狀態
mrs r0, spsr /* 讀取 spsr */
push {r0} /* 保存 spsr */
spsr
存儲中斷前的處理器狀態,需保存以便返回。
(3) 獲取 GIC 中斷號
mrc p15, 4, r1, c15, c0, 0 /* 從 CP15 讀取 GIC 基址 */
add r1, r1, #0X2000 /* 計算 GIC CPU 接口地址 */
ldr r0, [r1, #0XC] /* 從 GICC_IAR 讀取中斷號 */
push {r0, r1} /* 保存中斷號和基址 */
- 通過 CP15 獲取 GIC 基址,偏移
0x2000
得到 CPU 接口地址,從GICC_IAR
寄存器讀取當前中斷號。
(4) 模式切換和調用 C 函數
cps #0x13 /* 切換到 SVC 模式 */
push {lr} /* 保存 SVC 模式下的 lr */
ldr r2, =system_irqhandler /* 加載 C 函數地址 */
blx r2 /* 調用 C 中斷處理函數 */
- 切換到 SVC 模式,調用 C 語言的
system_irqhandler
函數,r0
作為參數傳遞中斷號。
(5) 清理和返回
pop {lr} /* 恢復 SVC 模式 lr */
cps #0x12 /* 切換回 IRQ 模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 寫 EOIR 標記中斷結束 */
pop {r0}
msr spsr_cxsf, r0 /* 恢復 spsr */
pop {r0-r3, r12} /* 恢復寄存器 */
pop {lr} /* 恢復 lr */
subs pc, lr, #4 /* 返回 */
- 恢復所有狀態,通知 GIC 中斷處理完成(寫
GICC_EOIR
),返回到中斷前的位置。
3. 總結與應用
3.1 關鍵點回顧
- 中斷向量表:定義了 8 種異常的入口,啟動時跳轉到
Reset_Handler
。 - 復位初始化:關閉中斷、緩存和 MMU,設置棧指針,跳轉到
main
。 - IRQ 處理:通過 GIC 獲取中斷號,調用 C 函數處理,恢復現場返回。
3.2 應用場景
這段代碼適用于嵌入式系統(如基于 Cortex-A7 的開發板),用于啟動操作系統或裸機程序。理解這些內容有助于調試硬件初始化問題、優化中斷響應以及開發低級驅動。
3.3 擴展閱讀
- 參考文檔:ARM Cortex-A(armV7)編程手冊、Cortex-A7 技術參考手冊。
- 相關知識:CP15 協處理器、GIC 中斷控制器、ARM 模式切換。
4. 附錄:常見問題解答
- 為什么關閉緩存和 MMU? 為了確保啟動時硬件狀態干凈,避免緩存或虛擬內存的殘留影響。
- 棧指針為何向下增長? ARM 棧通常向下增長,方便壓棧和出棧操作。
- GIC 是什么? 通用中斷控制器,管理多個設備的中斷請求。