ARM 프로세서의 주요 용법을 배우기 위해 리눅스 커널만큼 좋은 리퍼런스 자료가 없는 것 같다.
ARM 사의 개발자들이 리눅스 커널에 자신의 코드를 메인라인 시키기 때문이다.
이번에는 리눅스 커널에서 32비트 ARMv7 아키텍처에서 ARM의 모드를 읽어 제어하는 루틴을 살펴보자.
먼저 다음 코드를 보자.
arch/arm/include/uapi/asm/ptrace.h
#define USR_MODE 0x00000010
#define SVC_MODE 0x00000013
#define FIQ_MODE 0x00000011
#define IRQ_MODE 0x00000012
#define MON_MODE 0x00000016
#define ABT_MODE 0x00000017
#define HYP_MODE 0x0000001a
#define UND_MODE 0x0000001b
#define SYSTEM_MODE 0x0000001f
#define MODE32_BIT 0x00000010
#define MODE_MASK 0x0000001f
각 매크로를 선언해 놨는데, 그 동안 ARM 아키텍처에서 소개한 ARM 모드를 나타내는 매크로로 보인다.
위에서 선언된 매크로가 실제 코드에서 어떤 방식으로 사용되는지 확인해보자.
arch/arm/kvm/emulate.c
unsigned long *__vcpu_spsr(struct kvm_vcpu *vcpu)
{
unsigned long mode = *vcpu_cpsr(vcpu) & MODE_MASK;
switch (mode) {
case SVC_MODE:
return &vcpu->arch.ctxt.gp_regs.KVM_ARM_SVC_spsr;
case ABT_MODE:
return &vcpu->arch.ctxt.gp_regs.KVM_ARM_ABT_spsr;
case UND_MODE:
return &vcpu->arch.ctxt.gp_regs.KVM_ARM_UND_spsr;
case IRQ_MODE:
return &vcpu->arch.ctxt.gp_regs.KVM_ARM_IRQ_spsr;
case FIQ_MODE:
return &vcpu->arch.ctxt.gp_regs.KVM_ARM_FIQ_spsr;
default:
BUG();
}
}
위 코드에서 핵심 루틴은 다음과 같다.
*vcpu_cpsr(vcpu) & MODE_MASK;
이를 해석하면 ARM 명령어로 레지스터 CPSR를 읽어서 MODE_MASK(0x1f)와 AND 비트 연산을 하면 ARM의 모드를 알 수 있다는 것이다.
위 내용을 참고해서 패치를 작성해보자.
diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c
index 8448613..ac43177 100644
--- a/arch/arm/kernel/irq.c
+++ b/arch/arm/kernel/irq.c
@@ -47,8 +47,23 @@
unsigned long irq_err_count;
+
+#define MODE_MASK 0x0000001f
+
+static inline unsigned long arch_arm_mode(void)
+{
+ unsigned long cpsr_value;
+
+ asm volatile(
+ " mrs %0, cpsr"
+ : "=r" (cpsr_value) : : "memory", "cc");
+ return (cpsr_value & MODE_MASK) ;
+}
+
+
int arch_show_interrupts(struct seq_file *p, int prec)
{
+ unsigned long arm_mode = 0;
#ifdef CONFIG_FIQ
show_fiq_list(p, prec);
#endif
@@ -56,6 +71,10 @@ int arch_show_interrupts(struct seq_file *p, int prec)
show_ipi_list(p, prec);
#endif
seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
+
+ arm_mode = arch_arm_mode();
+
+ printk(" arm mode = %lx \n", arm_mode);
return 0;
}
@@ -67,6 +86,11 @@ int arch_show_interrupts(struct seq_file *p, int prec)
*/
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
+ unsigned long arm_mode = 0;
+
+ arm_mode = arch_arm_mode();
+ printk(" arm mode = %lx \n", arm_mode);
+
__handle_domain_irq(NULL, irq, false, regs);
}
위 패치 코드의 핵심은 아래 함수이다.
static inline unsigned long arch_arm_mode(void)
{
unsigned long cpsr_value;
asm volatile(
" mrs %0, cpsr"
: "=r" (cpsr_value) : : "memory", "cc");
return (cpsr_value & MODE_MASK) ;
}
" mrs %0, cpsr" 명령어를 실행해 cpsr_value 지역 변수에 레지스터 cpsr를 읽어서,
MODE_MASK(0x1f)와 AND 비트 연산을 한 결과를 반환하는 동작이다.
이번에는 이 코드를 빌드하면 실제 어셈블리 코드로 어떻게 변환됐는지 확인해보자.
c0209284 <arch_show_interrupts>:
c0209284: e1a0c00d mov ip, sp
c0209288: e92dd830 push {r4, r5, fp, ip, lr, pc}
...
c02092d0: eb077061 bl c03e545c <seq_printf>
c02092d4: e10f1000 mrs r1, CPSR
c02092d8: e59f001c ldr r0, [pc, #28] ; c02092fc <arch_show_interrupts+0x78>
c02092dc: e201101f and r1, r1, #31
c02092e0: eb01c3d8 bl c027a248 <printk>
위 코드에서 핵심 구문을 분석해보자.
c02092d4: e10f1000 mrs r1, CPSR
레지스터 CPSR값을 r1 레지스터로 읽어 오는 동작이다.
c02092d8: e59f001c ldr r0, [pc, #28] ; c02092fc <arch_show_interrupts+0x78>
c02092dc: e201101f and r1, r1, #31
r1 레지스터와 31(0x1f)와 AND 비트 연산을 수행한 결과를 r1 레지스터에 저장한다.
ARM 어셈블리 명령어로 ARM 모드를 직접 설정할 수 있는 명령어를 제공한다.
cpsr 레지스터에서 ARM의 모드가 있는 [4:0] 비트만 설정할 수 있는 기능이다.
이를 활용해 다시 패치를 작성해봤다.
diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c
index 8448613..4a1b55c 100644
--- a/arch/arm/kernel/irq.c
+++ b/arch/arm/kernel/irq.c
@@ -47,8 +47,31 @@
unsigned long irq_err_count;
+
+#define MODE_MASK 0x0000001f
+
+static inline unsigned long arch_arm_mode(void)
+{
+ unsigned long cpsr_value;
+
+ asm volatile(
+ " mrs %0, cpsr"
+ : "=r" (cpsr_value) : : "memory", "cc");
+ return (cpsr_value & MODE_MASK) ;
+}
+
+static inline void arch_arm_set_svc_mode(void)
+{
+ unsigned long flags = SVC_MODE | PSR_I_BIT | PSR_F_BIT;
+
+ asm volatile(
+ " msr cpsr_c, %0"
+ : "=r" (flags) : : "memory", "cc");
+}
+
int arch_show_interrupts(struct seq_file *p, int prec)
{
+ unsigned long arm_mode = 0;
#ifdef CONFIG_FIQ
show_fiq_list(p, prec);
#endif
@@ -56,6 +79,12 @@ int arch_show_interrupts(struct seq_file *p, int prec)
show_ipi_list(p, prec);
#endif
seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
+
+ arm_mode = arch_arm_mode();
+ printk(" arm mode = %lx \n", arm_mode);
+
+ arch_arm_set_svc_mode();
+
return 0;
}
@@ -67,6 +96,11 @@ int arch_show_interrupts(struct seq_file *p, int prec)
*/
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
+ unsigned long arm_mode = 0;
+
+ arm_mode = arch_arm_mode();
+ printk(" arm mode = %lx \n", arm_mode);
+
__handle_domain_irq(NULL, irq, false, regs);
}
이 패치의 핵심 코드는 다음 함수이다.
static inline void arch_arm_set_svc_mode(void)
{
unsigned long flags = SVC_MODE | PSR_I_BIT | PSR_F_BIT;
asm volatile(
" msr cpsr_c, %0"
: "=r" (flags) : : "memory", "cc");
}
위 코드를 빌드한 후 어셈블리 코드를 확인한 결과는 다음과 같다.
c0209284 <arch_show_interrupts>:
c0209284: e1a0c00d mov ip, sp
c0209288: e92dd830 push {r4, r5, fp, ip, lr, pc}
c020928c: e24cb004 sub fp, ip, #4
...
c02092d0: eb077061 bl c03e545c <seq_printf>
c02092d4: e10f1000 mrs r1, CPSR
c02092d8: e59f0020 ldr r0, [pc, #32] ; c0209300 <arch_show_interrupts+0x7c>
c02092dc: e201101f and r1, r1, #31
c02092e0: eb01c3d8 bl c027a248 <printk>
c02092e4: e121f003 msr CPSR_c, r3
'msr CPSR_c, r3' 명령어가 실행되서 ARM 프로세서의 모드를 SVC로 업데이트 한다.
(주의)
이 코드는 실험용으로 쓸만하다. 실제 시스템에 적용되면 무슨 일이 일어날 지 모른다.
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
'시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리 > 5장: Armv7 - 동작 모드' 카테고리의 다른 글
[Arm프로세서] Armv7: 어떤 동작 모드를 선택해야 할까? (0) | 2024.01.01 |
---|---|
[Arm프로세서] Armv7 동작 모드: PL와 Arm 동작 모드 소개 (0) | 2023.06.10 |
[Arm프로세서] Armv7 아키텍처의 Arm 동작 모드 소개 (0) | 2023.06.10 |
[ARM프로세서] ARM 모드에 대한 소개 (0) | 2023.06.09 |