본문 바로가기

시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리/5장: Armv7 - 동작 모드

[ARM][ARMv7] 리눅스 커널: ARM 모드를 설정하는 어셈블리 코드 분석

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 아키텍처의 구조와 원리' 저자>