一、芯片运行模式
1、Cortex-M:
M系列处理器仅支持2种模式,2种访问级别:
1)Thread mode(线程模式):该模式下的执行可以是非特权的或特权的。运行异常服务程序以外的程序。
2)Handler mode(中断模式):该模式下的执行始终处于特权级。异常触发,处理器会切换为特权级访问,所有异常处理都在 Handler模式下执行。运行异常服务程序。
注意:
--特权级和非特权级可以理解为内核态和用户态。非特权执行会限制对某些资源的访问,如用户级禁止访问和修改特殊寄存器组的(第三节说特殊寄存器组)。
--CONTROL
寄存器的bit0仅在特权级别
可访问修改,bit0用于切换线程模式下的访问级别。在系统复位后,处理器进入线程模式+特权级+默认使用MSP。运行在用户级的代码想要进入特权级,必须触发异常(如SVC异常),在异常的ISR中修改CONTROL的bit0。(M系列的
特殊寄存器组看第三节)。
--ARM系统的“异常”指:
打断程序顺序执行的事件都称为异常。外部中断产生或者当有指令执行了“非法操作”, 或者访问被禁的内存区间, 因各种错误触发 fault异常。还可以代码主动请求进入异常状态(利用SVC系统调用)。使用主动进入的例子:
//__svc是编译器关键字,编译器会插入svc指令,触发svc异常,0是svc编号,可以根据svc_args获取
__svc(0) uint32_t svc_service_add(uint32_t x, uint32_t y);
void SVC_Handler_C(uint32_t* svc_args) { // C语言服务函数
// ... ...
}
__asm void SVC_Handler(void) { // svc向量入口
IMPORT SVC_Handler_C // 导入外部函数
// ... ...
B SVC_Handler_C // 跳转
}
int main(void) {
uint32_t result = svc_service_add(100, 200); // 调用
}
M3跑RTOS系统的时候,一旦OS启动后开始运行任务,任务是要运行在 用户态+PSP堆栈指针,默认上电系统运行在线程模式+特权级+默认使用MSP,所以要运行系统必须完成 CPU模式切换和栈指针切换(核心就是修改CONTROL寄存器或者通过EXC_RETURN机制间接修改)。
2、Cortex-A:1,3列即是A芯片常见的工作模式(除了用户模式是用户级,其他模式都是特权级)。系统上电之后,CPU默认进入SVC模式。
二、中断系统
抢占优先级:抢占优先级高的中断发生,低优先级中断执行过程会被打断,形成中断嵌套去执行高优先级中断;
响应优先级:同时来两个中断,其抢占优先级与正在执行的一个中断相同,正在执行的中断不会被打断,等它执行完后,再执行响应优先级高的那个;
注意:若同时来两个中断,优先考虑抢占优先级,再考虑响应优先级,最后再考虑向量表顺序;抢占优先级具备打断性。
1、Cortex-M:NVIC(内嵌向量中断控制器),具备抢占优先级和响应优先级。
中断向量表:表中列举了一款芯片所有的中断向量,包括芯片外设的所有中断:
CPU收到的就是某一个具体的中断请求,自己跳转到对应的具体的某个ISR。
2、Cortex-A:GIC(general interrupt controller),具备抢占优先级和响应优先级。
中断向量表:1,2列即是A芯片的所有中断向量:
当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是 ARM 内核只提供了4个信号给 GIC 来汇报中断情况:VFIQ(虚拟快速中断)、VIRQ(虚拟外部中断)、FIQ 和 IRQ:
GIC 将众多的中断源分为3类:
中断源有很多,区分不同的中断源通过分配一个的中断ID,ID绑定具体哪个中断源半导体厂商去定义:
#define NUMBER_OF_INT_VECTORS 160 /* 中断源 160 个,SGI+PPI+SPI*/
typedef enum IRQn {
/* Auxiliary constants */
NotAvail_IRQn = -128,
/* Core interrupts */
Software0_IRQn = 0,
Software1_IRQn = 1,
...... ......
Reserved158_IRQn = 158,
PMU_IRQ2_IRQn = 159
} IRQn_Type;
三、寄存器组
1、Cortex-M:M系列处理器实现了2个堆栈指针:MSP和PSP。在 Handler 模式下,处理器使用 Main SP, 在Thread模式下,它可以使用任一堆栈指针(上电系统运行在线程模式+特权级+默认使用MSP)。使用CONTROL寄存器的bit1控制。堆栈指针的切换在异常服务的始末由硬件自动切换。
2、Cortex-A:只有用户模式处于用户级,其他模式都处于特权级!
另外,A系列还有重要的协处理器寄存器组:
CP15 协处理器:通过MRC 或者 MCR 指令访问,指令中的控制位 CRn、opc1、CRm 和 opc2 通过不同的搭配可以选择c1~c15是作为什么寄存器被设置。
四、MPU-MMU
1、 MPU(内存保护单元):Cortex-M中,同属于CPU中的一个硬件单元。只具备内存保护的作用,防止进程访问不是分配给它的内存。
2、MMU(内存管理单元):Cortex-A中,同属于CPU中的一个硬件单元。主要是虚拟内存(虚拟地址到物理地址的转换)和内存保护。MMU可以看作是MPU的超集。
五、Cortex-A裸机中断coding流程
通过中断号判断决定对应的ISR调用,ISR通用写法流程为:
1)中断处理函数形式:
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
2)中断处理结构体:
typedef struct _sys_irq_handle
{
system_irq_handler_t irqHandler; /* 中断处理函数 */
void *userParam; /* 中断处理函数参数:中断号 */
} sys_irq_handle_t;
3)系统有多少个中断ID,就定义多少个sys_irq_handle_t类型的变量:
static sys_irq_handle_t irqTable[160];
4)根据系统内部定义的IRQn_Type注册对应的ISR:
void system_register_irqhandler(IRQn_Type irq,
system_irq_handler_t handler,
void *userParam)
{
irqTable[irq].irqHandler = handler;
irqTable[irq].userParam = userParam;
}
5)根据中断ID调用对于的处理函数,中断发生时可读取相关寄存器获取对应中断号:
void system_irqhandler(unsigned int x_ID)
{
... ...
irqTable[x_ID].irqHandler(x_ID, irqTable[x_ID].userParam);
... ...
}
6)在编写的汇编文件xx.S(C语言环境的准备)中调用C编写的中断函数:在汇编调用 C 函数的时候建议形参不要超过 4 个,形参可以由 r0~r3 这四个寄存器来传递。
IRQ_Handler:
... ...
ldr r0, [r1, #0XC] // 从GICC_IAR 寄存器获取中断号,通过r0传递参数system_irqhandler
ldr r2, =system_irqhandler // 实际的中断函数
... ...