Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

38120
1703
402253


유데미에 '리눅스 시스템 소프트웨어 강좌'를 오픈했습니다. 한번만 도와주세요!(feat: 목표는 300명 등록) 임베디드 에세이

제가 유데미에 '초보 시스템 소프트웨어 개발자 벗어나기'라는 강좌 만들어서 오픈했습니다. 링크는 아래와 같습니다;




여러분 중에 시스템 소프트웨어 분야를 진출하기를 희망 하시거나 혹은 실전 프로젝트에서 어떤 일을 하는지 궁금 하신다면이 강의를 꼭 등록해서 들으셨으면 좋겠습니다. 

클래스의 목차를 보시면 아시겠지만 시스템 소프트웨어 개발자로 실전 프로젝트에서 일을 하는지에 대해 대해서 굉장히 자세하게 설명을 하고요. 특히 시스템 반도체와 전기 자동차 업계에서 리눅스 시스템 소프트웨어 개발자가 어떤 일을 하는지 자세히 설명합니다.



클래스의 내용을 보시면 아시겠지만 리눅스 커널이나 Arm 아키텍처에 대한 소개와 컨셉 그리고 실전 프로젝트에서 활용이 될만한 실용적인 내용을 잘 정리했습니다. 



시스템 소프트웨어 개발자가 되기 위해서 어떤 주제를 공부해야 하는지 궁금해하시는 분에게 큰 도움이 될 것이라 생각됩니다.  

여러분 중에 시스템 소프트웨어 분야를 진출하기를 희망 하시거나 혹은 실전 프로젝트에서 어떤 일을 하는지 궁금 하신다면 이 강의를 꼭 들어 주셨으면 좋겠습니다.

감사합니다.






질문: current 매크로를 귀찮게(?) 두 번 캐스팅하는 이유 Question_Announcement

질문;

4.10.1절 중에
#define get_current() (current_thread_info()->task)
#define current get_current()
에서 왜 굳이 한줄로 사용할 수 있는 매크로를 두 줄로 정의하는 건가요? 

답신;

current 매크로를 귀찮게(?) 두 번 캐스팅하는 이유는,

#define get_current() (current_thread_info()->task)
#define current get_current()

current_thread_info()의 세부 구현부가 CPU 아키텍처 별로 다르기 때문입니다. 

예를 들어 32비트 기반의 Arm 아키텍처에서 current_thread_info 선언부는 다음과 같은데요.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h#L86
static inline struct thread_info *current_thread_info(void) __attribute_const__;

static inline struct thread_info *current_thread_info(void)
{
return (struct thread_info *)
(current_stack_pointer & ~(THREAD_SIZE - 1));
}

64비트 기반의 Arm 아키텍처는 아래와 같습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/thread_info.h#L22
#define current_thread_info() ((struct thread_info *)current)

유데미 강사부트캠프 2기 - 8주차 후기 임베디드 에세이

*본 포스팅은 유데미 강사부트캠프 2기 SNS 챌린지 참여 후기로 작성되었습니다.

유데미 2차 부트 캠프에 참가해 '멘토'로부터 티칭(Teaching)에 대한 고급 노하우를 전수 받았다. 마지막 세션에서 진행된 '임동준'님은 세미나를 통해 너무나도 유익한 정보를 전수했다. 티칭 뿐만 아니라 개발자로써 새로운 지식을 배울 때 적용하면 좋을 만한 내용이 많았다. 

1. 난해한 내용은 1가지만 설명하자

낯선 개념을 1가지 이상 설명하면 입문자는 더욱더 혼돈에 빠진다. 어려운 개념은 1가지 씩만 설명하자. 새로운 기능을 배울 때 이 규칙을 적용하면 좋겠다.

2. 소프트웨어를 배울 때 방향

자전거를 배울 때 자전저를 학습하는 사람이 있다. 기어나 브레이크의 깊이 있는 동작 원리를 배운다. 문제는 쉽게 지루해진다는 것이고 자전거를 못 탈 수도 있다. 자전거를 배울 때는; 일단 평지에서 자전거를 타는 방법을 익힌 다음에, 경사가 있는 곳을 올라갈 때 만나는 문제점(기어 사용)에 대해 고민하는게 좋다. 

이는 소프트웨어에도 적용될 수 있다. 일단 뭐라도 간단히 만들어보고 문제를 만나면 차근 차근 해결하는 방식으로 강의를 구성하면 좋겠다.

3. 핵심을 찌를 수 있는 간단한 예시

아주 간단한 예제 코드나 실습을 통해 입문자가 배우려는 내용의 핵심 원리를 파악할 수 있다. 입문자가 무엇인가를 따라하면서 자연스럽게 소프트웨어의 핵심 원리를 체득하는 방식이다. 세미나나 강의를 구성할 때 꼭 참고하면 좋은 내용이다.

4. 아무리 설명해도 못 알아들을 때

이런 상황에서는 내가 설명하려는 내용을 내가 어떻게 체득했는지는 생각해 볼 필요가 있다. 내가 체득했던 방식을 입문자가 실습으로 따라하게 강의를 구성하거나 내가 실습으로 보여줄 수도 있다. 

5. 잦은 피드백

농구에서 슛을 쏜 다음에 골이 안 들어갔다는 것을 1시간 후에 확인하면 어떨까? 농구 실력이 늘지 않을 것이다. 피드백은 자주 받는게 좋다. 강사는 학생이 제대로 이해를 했는지, 학생은 본인이 제대로 이해했는지 자주 확인하는 과정이 필요하다. 

세미나 도중에 실시간으로 질문을 받고 바로 피드백을 받으니 정말 유익했다. 
<아래는 스틸 것>


2022년 최고의 세미나였다고 생각된다.

#유데미, #유데미코리아, #유데미강사되기, #유데미부트캠프, #강사부트캠프, #유데미강사부트캠프, #강사되기, #강사양성

[Arm프로세서] GIC: ICC_SRE_EL1 레지스터 Arm: GIC

ICC_SRE_EL1(Interrupt Controller System Register Enable register) 레지스터는 CPU Interface와 연관된 시스템 레지스터 인터페이스와 메모리 맵드 인터페이스를 설정하는 기능을 제공합니다.

다음 그림을 보면서 ICC_SRE_EL1 레지스터의 비트 맵을 알아봅시다.

그림 16.21 ICC_SRE_EL1 레지스터의 비트 맵

비트 맵 중에 DIB를 봅시다.

DIB, bit [2]

IRQ 바이패스를 비활성화합니다. 비트 값에 따라 다음과 같이 설정됩니다.

0b0: IRQ 바이패스 활성화 
0b1: IRQ 바이패스 비활성화

DFB, bit [1]

FIQ 바이패스를 비활성화하는 비트입니다. 

0b0: FIQ 바이패스 활성화
0b1: FIQ 바이패스 비활성화

SRE, bit [0]

시스템 레지스터에 접근하는 방식을 설정하는 레지스터입니다.

0b0: 메모리 맵드 인터페이스가 반드시 사용되야 합니다. ICC_SRE_EL1를 제외한 CPU Interface와 관련된 레지스터(ICC_*)를 EL1에서 접근하면 EL1로 트랩됩니다.
 
0b1: 현재 시큐리티 상태와 관련된 시스템 레지스터 인터페이스가 활성화됩니다.

[Arm프로세서] GIC: ICC_CTLR_EL1 레지스터 Arm: GIC

ICC_CTLR_EL1는 Interrupt Controller Control Register (EL1)로 CPU Interface의 세부 동작을 설정하는 레지스터입니다. CPU Interface의 세부 속성을 설정하는 가장 중요한 레지스터 중 하나이므로 GIC를 사용하는 시스템에서는 부팅 할 때 ICC_CTLR_EL1 레지스터를 설정해야 합니다.

다음 그림은 ICC_CTLR_EL1 레지스터의 비트 필드입니다.


그림 16.20 ICC_CTLR_EL1 레지스터의 비트 맵

먼저 ExtRange 비트를 봅시다. 

ExtRange, bit [19]

추가 인터럽트 아이디 범위를 설정할 수 있습니다. 비트에 따라 다음과 같이 설정할 수 있습니다.

0b0: CPU interface는 1024~8191 범위의 인터럽트 아이디를 지원하지 않음
0b1: CPU interface는 1024~8191 범위의 인터럽트 아이디를 지원함

ExtRange는 보통 부팅할 때 설정되는 비트입니다.

RSS, bit [18]

Range Selector를 지원하는 비트로 다음과 같이 설정할 수 있습니다.

0b0: SGI에 대한 affinity level 0의 값의 범위를 0~15로 지정  
0b1: SGI에 대한 affinity level 0의 값의 범위를 0~255로 지정 

A3V, bit [15]

Affinity 3가 유효한지 나타내는 비트입니다.

0b0: CPU interface 로직이 Affinity 3에 대한 0값을 지원 
0b1: CPU interface 로직이 Affinity 3에 대한 논-제로값을 지원 

SEIS, bit [14]

SEI(asynchronous SError interrupt)를 지원하는 비트입니다.

0b0: CPU interface는 SEI를 지원  
0b1: CPU interface는 SEI를 지원 

IDbits, bits [13:11]

ID를 저장하는데 읽기 전용입니다.

0b000: 16 bits.
0b001: 24 bits.

PRIbits, bits [10:8]

우선 순위 비트의 사이즈를 지정하는 비트입니다. 이 비트는 2개 시큐어 상태에서 32 레벨의 피지컬한 우선 순위가 구현돼야 사용됩니다.

PMHE, bit [6]

우선 순위 마스크 힌트를 활성화하는 비트입니다. 인터럽트를 분배하기 위해 PMR 레지스터를 제어하는 용도로 사용됩니다.

0b0: ICC_PMR_EL1를 비활성화   
0b1: ICC_PMR_EL1를 활성화 

EOImode, bit [1]

현재 시큐어 상태의 EOI(End of Interrupt) 모드를 설정합니다. EOI(End of Interrupt) 레지스터의 값을 쓰면 인터럽트를 비활성화할 수 있는 레지스터입니다.

0b0: ICC_EOIR0_EL1와 ICC_EOIR1_EL1 가 우선 순위 드랍과 인터럽트 비활성화 기능을 지원 
0b1: ICC_EOIR0_EL1와 ICC_EOIR1_EL1는 우선 순위 드랍만 지원합니다. 대신 ICC_DIR_EL1 레지스터가 인터럽트 비활성화를 지원 

CBPR, bit [0]

Common Binary Point Register입니다. 인터럽트 선점을 virtual Group 0 혹은 virtual Group 1으로 설정하는 기능입니다.


주제: 초보 시스템 소프트웨어 개발자 벗어나기 임베디드 리눅스 학습 방법


대상자:

* 리눅스 시스템 소프트웨어 개발자(신입~3년차): 시스템 반도체 개발자, 전기자동차 개발자, 임베디드 개발자
* 시스템 소프트웨어 개발자를 지망하는 취준생(대학원, 대학생)

커리큘럼:

1. 시스템 반도체와 관련된 동향과 테크트리

  - 시스템 반도체란 무엇인가
  - 시스템 반도체에 적용되는 소프트웨어 스택
  - 시스템 반도체 관련 업체 - 1: SoC 벤더
  - 시스템 반도체 관련 업체 - 2: 전기자동차
  - 시스템 반도체 관련 업체 - 3: OEM 업체 

2. 임베디드 시스템의 기본 원리

  - 부트로더란 무엇인가
  - RTOS란 무엇인가
  - 페리페럴이란 무엇인가
  - 회로도란 무엇인가

3. 리눅스 커널 드라이버

  - 리눅스 커널 드라이버의 기본 구조
  - 리눅스 커널 드라이버는 어디에 쓰일까
  - 캐릭터 디바이스 드라이버의 구조
  - 디바이스 트리의 구조
  - 플렛폼 드라이버 모델
  - 주요 디버깅 기능

4. Arm 프로세서

  - Arm 아키텍처와 프로세서란
  - 어셈블리 명령어이란
  - 레지스터이란
  - 익셉션 레벨이란
  - 익셉션이란

5. 트러블 슈팅

  - 시스템 소프트웨어 개발에서 트러블 슈팅이란
  - 트러블 슈팅을 할 때 마음가짐
  - 리셋, 크래시 문제 디버깅
  - 하드웨어 이슈 디버깅

[Arm프로세서] GIC: ICC_BPR0_EL1 레지스터 Arm: GIC

ICC_BPR0_EL1는 Interrupt Controller Binary Point Register로 우선 순위의 단계를 설정하는 레지스터입니다. ICC_BPR0_EL1 레지스터를 설정하면 인터럽트 우선 순위를 나타내는 비트 필드를 2가지 부분으로 나눠서 처리할 수 있습니다. 

다음 그림을 보면서 ICC_BPR0_EL1 레지스터의 비트 맵을 알아 봅시다.

그림 16.20 ICC_BPR0_EL1 레지스터의 비트 맵

BinaryPoint 비트를 제외한 RES0는 Reserved입니다. 먼저 BinaryPoint 비트를 봅시다.

BinaryPoint, bits [2:0]

이 필드 값으로 8비트로 구성된 우선 순위 필드를 2개 부분으로 분류하는 규칙을 설정할 수 있습니다. 'BinaryPoint'는 3비트로 구성돼 있으며, 이 값에 따라 우선 순위 필드는 다음과 같이 구성됩니다. 

표 16.6 바이너리 포인트 값에 따른 그룹 우선순위와 서브 우선 순위 
 >>> g와 s의 의미를 기입하자.

인터럽트가 선점(Preemption)되려면 우선 순위 레벨과 ICC_BPR0_EL1 레지스터를 통해 그룹 우선 순위와 서브 그룹 우선 순위를 설정해야 합니다.


[Arm프로세서] GIC: ICC_RPR_EL1 레지스터 Arm: GIC

ICC_RPR_EL1는 Interrupt Controller Running Priority Register로 CPU Interface의 우선 순위를 담고 있는 레지스터입니다. 

다음 그림을 보면서 ICC_RPR_EL1 레지스터의 비트 맵을 알아 봅시다. 

 
그림 16.19 ICC_RPR_EL1 레지스터의 비트 맵

먼저 NMI 비트부터 봅시다.

NMI, bit [63]

NMI 인터럽트로 발생한 우선 순위인지 나타냅니다. 
 
NMI_NS, bit [62]

논 시큐어 NMI 인터럽트로 발생한 인터럽트인지를 설정합니다. 

Priority, bits [7:0]

Priority 비트는 CPU Interface의 Running Priority를 저장하며, 현재 활성화된 인터럽트의 그룹 우선순위를 나타냅니다. 만약 CPU Interface에서 활성화된 인터럽트가 없으면 모든 활성화된 인터럽트들은 '우선 순위 드랍'으로 처리됩니다.


[Arm프로세서] GIC: ICC_PMR_EL1 레지스터 Arm: GIC

ICC_PMR_EL1는 Interrupt Controller Interrupt Priority Mask Register로 CPU Interface로 전달되는 인터럽트 ‘우선 순위’의 필터를 저장하는 레지스터입니다.

ICC_PMR_EL1 레지스터보다 낮은 우선 순위로 설정된 인터럽트는 CPU Interface로 전달되지 않습니다.

[정보] Priority Mask Register이란 무엇인가?

Priority Mask Register를 우선 순위 문턱 레지스터라고 명시합니다.


이어서 다음 그림을 보면서 ICC_PMR_EL1 레지스터의 비트 맵을 알아 봅시다. 

 
그림 16.18 ICC_PMR_EL1 레지스터의 비트 맵

ICC_PMR_EL1 레지스터의 비트 [7:0]은 인터럽트 필터 값을 담고 있습니다. 이 비트 값에 따라 인터럽트 우선순위 레벨은 다음 표와 같이 분류할 수 있습니다.


표 16.5 ICC_PMR_EL1 레지스터: 우선 순위 값의 레벨과 범위 

GIC에서 관리하는 인터럽트는 대부분 우선 순위와 함께 설정됩니다. ICC_PMR_EL1 레지스터 값이 우선 순위의 기준이므로, 부팅 할 때 제대로 ICC_PMR_EL1 레지스터를 설정해야 인터럽트가 제대로 처리됩니다. 인터럽트가 GIC에서 Arm 코어로 라우팅되지 않을 때 먼저 ICC_PMR_EL1 레지스터를 체크해야 합니다.


[Arm프로세서] GIC: ICC_EOIR0_EL1 레지스터 Arm: GIC

ICC_EOIR0_EL1는 'Interrupt Controller End Of Interrupt Register 0'의 약자입니다. ICC_EOIR0_EL1 레지스터에 어떤 값을 써주면 GIC를 구성하는 CPU Interface에게 "명시된 인터럽트에 대한 처리를 끝냈다"라고 알려줍니다.

여기서 ICC_EOIR0_EL1 레지스터 이름에서 EOIR0를 눈겨여 봅시다. EOIR0의 가장 마지막에 있는 숫자인 0은 'Group 0' 인터럽트를 의미합니다. 이름이 유사한 ICC_EOIR1_EL1 레지스터의 EOIR1는 무엇을 의미할까요? EOIR1의 마지막에 있는 숫자 1은 'Group 1' 인터럽트 그룹을 뜻합니다.

ICC_EOIR0_EL1 레지스터에 대한 비트 맵은 다음 그림에서 확인할 수 있습니다.
 

그림 16.17 ICC_EOIR0_EL1 레지스터의 비트 맵

ICC_EOIR0_EL1 레지스터의 비트 맵은 EOIINTID로 구성돼 있습니다. 이 필드는 인터럽트 아이디(INTID)를 저장합니다.

EOIINTID, bits [23:0]

EOIINTID는 ICC_IAR0_EL1 레지스터를 통해 읽은 값입니다. 이 값을 bits [23:0] 비트에 써주면 “CPU Interface에게 인터럽트에 대한 처리를 마무리했다”라고 알립니다.

나머지 Bits [31:24]은 Reserved된 영역인데, 이름 그대로 예약된 비트입니다. 

[정보] ICC_IAR1_EL1, ICC_EOIR0_EL1 레지스터의 중요성

ICC_IAR1_EL1와 ICC_EOIR0_EL1는 인터럽트 서비스 루틴을 실행하기 전에 반드시 설정해야 하는 레지스터이니 잘 기억합시다. 


[Arm프로세서] GIC: ICC_IAR1_EL1 레지스터 Arm: GIC

ICC_IAR1_EL1는 'Interrupt Controller Interrupt Acknowledge Register 1'의 약자로 인터럽트에 대한 Acknowledge를 수행하는 레지스터입니다.

MRS 명령어를 통해 ICC_IAR1_EL1 레지스터의 값을 읽기만 하면 CPU Interface에 ACK를 보냅니다. 이를 통해 "CPI Interface에게 인터럽트를 확인했다"라고 알립니다. ICC_IAR1_EL1 레지스터를 읽으면 ‘Group 1’에서 관리하는 인터럽트 아이디(Interrupt ID)를 반환합니다. 

인터럽트 아이디(Interrupt ID)는 인터럽트를 나타내는 식별자이며 이 값을 저장한 다음에 ICC_EOIR1_EL1 레지스터에게 다시 써줍니다.

ICC_IAR1_EL1 레지스터에 대한 비트 맵은 다음 그림에서 확인할 수 있습니다.

그림 16.16 ICC_IAR1_EL1 레지스터의 비트 맵

그림과 같이 ICC_IAR1_EL1 레지스터의 비트 맵은 InterruptID 비트로 구성돼 있습니다.  

InterruptID, bits [23:0]

InterruptID는 ‘Group 1’ 인터럽트에 대한 인터럽트 아이디를 뜻합니다. 이 값을 읽기만 하면 "CPU Interface'에게 인터럽트를 확인했다"는 ACK를 보냅니다.

나머지 Bits [31:24]은 Reserved된 영역으로 사용되지는 않습니다.

InterruptID 비트, ICC_IAR1_EL1 레지스터는 언제 접근할까?

Arm 코어는 IRQ 인터럽트가 유발되면 이를 익셉션의 한 종류로 처리합니다. 그래서 어떤 명령어를 실행다가 인터럽트를 감지하면 ‘IRQ 인터럽트’ 익셉션으로 정의된 익셉션 벡터 주소로 프로그램 카운터를 브랜치합니다. 특정 주소에 존재하는 명령어를 익셉션 핸들러라고 하는데 익셉션 핸들러에서 먼저 인터럽트 서비스 루틴으로 분기합니다.
 
인터럽트 서비스 루틴에서 가장 먼저 어떤 처리를 할까요? 바로 ICC_IAR1_EL1 레지스터를 읽습니다. 
 
이어서 리눅스 커널에서 ICC_IAR1_EL1 레지스터를 읽어 처리하는 코드를 분석하면서 더 자세히 알아 봅시다.

https://elixir.bootlin.com/linux/v5.4.61/source/drivers/irqchip/irq-gic-v3.c
01 static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
02 {
03 u32 irqnr;
04
05 irqnr = gic_read_iar();

05번째 줄을 보면 gic_read_iar() 함수를 호출한 결과를 irqnr 지역 변수에 저장합니다. 여기서 irqnr는 ‘Group 1’ 인터럽트 그룹에서 관리되는 인터럽트 아이디 정보(정수 값)를 담고 있습니다.

이 내용은 gic_read_iar() 함수 구현부를 보면 더 자세히 알 수 있습니다.

https://elixir.bootlin.com/linux/v5.4.61/source/arch/arm/include/asm/arch_gicv3.h 
01 static inline u32 gic_read_iar(void)
02 {
03 u32 irqstat = read_sysreg(ICC_IAR1);
04
05 dsb(sy);
06
07 return irqstat;
08 }

03번째 줄을 보면 ICC_IAR1_EL1 시스템 레지스터를 읽어 로컬 변수인 irqstat에 저장합니다. 그 다음 07번째 줄에서 ICC_IAR1_EL1 시스템 레지스터의 값을 담고 있는 irqstat을 반환합니다.  

[Arm프로세서] GIC: CPU interfaces 소개 Arm: GIC

Arm 코어와 가장 근접한 위치에 있는 CPU interfaces는 Arm 코어에게 인터럽트를 관리해 라우팅하는 기능을 수행합니다. CPU 코어 별로 CPU interface가 존재하며 다음과 같은 기능을 지원합니다.

각각 SGI, PPI 인터럽트의 우선 순위를 설정  
각각 SGI, PPI 인터럽트를 비활성화 및 활성화
각각 SGI, PPI 인터럽트를 라우팅하는 정보 설정
각각 PPI 인터럽트의 level-sensitive or edge-triggered 방식 설정
각각 SGI, PPI 인터럽트를 인터럽트 그룹에 등록

CPU interface는 시스템 레지스터를 통해 속성을 설정할 수 있으며, CPU interface에서 제공하는 시스템 레지스터의 목록은 다음과 같습니다.

ICC_PMR_EL1
ICC_IAR1_EL1
ICC_EOIR1_EL1
ICC_RPR_EL1
ICC_BPRn_EL1
ICC_CTLR_EL1 
ICC_SRE_EL1 
ICC_IGRPEN1_EL1

CPU interfae에서 제공된 레지스터는 GIC에서 정의된 레지스터 중에서 반드시 알아야 하니 잘 익혀둡시다. 위에서 소개한 레지스터 중에서 먼저 ICC_IAR1_EL1를 소개합니다.


[Arm프로세서] GIC: GICR_IGRPMODR0 - 'Interrupt Group Modifier Register 0' 레지스터 Arm: GIC

GICR_IGRPMODR0는 'Interrupt Group Modifier Register 0'로 인터럽트 그룹을 설정하는 레지스터입니다. GICD_CTLR 레지스터의 DS 비트가 0인 경우(GICD_CTLR.DS==0), GICR_IGROUPR0와 함께 인터럽트를 다음과 같이 설정합니다. 

시큐어 그룹 0
논시큐어 그룹 0
시스템 레지스터에 접근할 수 있으면, 시큐어 그룹 1

GICR_IGRPMODR<n> 레지스터의 비트 맵은 Group_modifier_bit으로 구성돼 있는데, Group_modifier_bit 비트를 통해 인터럽트를 시큐어로 설정할 수 있습니다. 
 
Group_modifier_bit<x>

Group_modifier_bit<x> 비트는 이름과 같이 Group Modifier를 설정하는 비트입니다. 인터럽트를 시큐어 혹은 논시큐어로 설정합니다. GICD_IGROUPR 레지스터를 구성하는 Group status 비트와 함께 인터럽트를 표 16.4와 같은 조합으로 설정할 수 있습니다.


[Arm프로세서] GIC: GICR_IGROUPR0, Interrupt Group Register 0 레지스터 Arm: GIC

GICR_IGROUPR0는 Interrupt Group Register의 약자로 SGI나 PPI를 그룹 Group 0 혹은 Group 1으로 설정하는 레지스터입니다. Arm 아키텍처에서 인터럽트를 시큐어 혹은 논시큐어 모드에서 처리하도록 설정할 수 있는데, 이를 위해 인터럽트를 Group 1, Group 0으로 분류해 관리합니다.

GICR_IGROUPR0 레지스터의 비트 맵은 다음과 같습니다.

 
그림 16.15 GICR_IGROUPR0 레지스터의 비트 맵 

GICR_IGROUPR0 레지스터를 구성하는 비트 맵은 Redistributor_group_status_bit<x>으로 구성돼 있습니다. 각 비트 설정은 다음과 같습니다.

0b0: 지정된 인터럽트를 Group 0으로 설정합니다. 만약 GICD_CTLR 레지스터의 DS 비트가 0이면 SGI나 PPI를 시큐어로 지정합니다. 시큐어 상태에서만 시큐어 인터럽트를 처리할 수 있습니다.

0b1: 지정된 인터럽트를 Group 1으로 설정합니다. 만약 GICD_CTLR 레지스터의 DS 비트가 1이면 SGI나 PPI는 Group 1의 논 시큐어로 설정됩니다.

인터럽트를 논 시큐어와 시큐어로 설정할 때 사용되는 레지스터이니 잘 익혀 둡시다.


[Arm프로세서] GIC: GICR_IPRIORITYR<n> 레지스터 Arm: GIC

GICR_IPRIORITYR는 리디스트리뷰터에 존재하는 Interrupt Priority Registers의 약자입니다. 레지스터의 이름과 같이 리디스트리뷰터에서 관리하는 SGI나 PPI 인터럽트의 우선 순위를 정할 수 있습니다. 

레지스터 이름의 접미사인 <n>의 범위는 0~7입니다. 이 정보로 GICR_IPRIORITYR0~GICR_IPRIORITYR7 범위의 레지스터가 존재한다는 사실을 알 수 있습니다.

다음은 GICR_IPRIORITYR0<n> 레지스터에 설정되는 정보입니다.

GICR_IPRIORITYR0~GICR_IPRIORITYR3: SGI 인터럽트 우선순위 
GICR_IPRIORITYR4~GICR_IPRIORITYR7: PPI 인터럽트 우선순위

인터럽트의 우선 순위를 잘못 설정하면 인터럽트가 CPU interface로 포워딩되지 않으니, 이 점 유념합시다. 

[정보] 인터럽트 아이디와 인터럽트 소스

GIC에서는 인터럽트 아이디로 인터럽트의 소스를 관리합니다. 인터럽트 아이디 별 인터럽트의 소스는 다음과 같습니다.

0~15: SGI 인터럽트
16~31: PPI 인터럽트

GIC에서 제공하는 시스템 레지스터에서 인터럽트 아이디와 관련된 내용이 확인되면 위 내용을 떠올립시다.


GICR_IPRIORITYR<n> 레지스터의 비트 맵

이어서 GICR_IPRIORITYR<n> 레지스터의 비트 맵을 보겠습니다. 
 

그림 16.14 GICR_IPRIORITYR<n> 레지스터의 비트 맵

비트 맵을 보면 Priority_offset_0B~Priority_offset_3B가 확인됩니다. 각각 비트 필드는 8비트 단위로 구성됐습니다. GICR_IPRIORITYR<n>에서 n의 범위가 0~7 이므로 32(32 = 8 x 4)개 인터럽트 아이디에 해당되는 인터럽트의 우선 순위를 설정할 수 있습니다.

GICR_IPRIORITYR0~GICR_IPRIORITYR3 레지스터: 0~15 인터럽트 아이디에 해당되는 인터럽트를 설정
GICR_IPRIORITYR4~GICR_IPRIORITYR7 레지스터: 16~31 인터럽트 아이디에 해당되는 인터럽트를 설정

이번에는 비트맵의 의미를 알아 보기 전에 비트 맵의 사이즈를 먼저 점검하겠습니다. 비트 맵의 사이즈는 2진수로 8비트이니 10진수로 128입니다. 이 정보로 0~128 범위로 인터럽트의 우선순위를 정할 수 있습니다.


[Arm프로세서] GIC: GICR_ICFGR0 레지스터 Arm: GIC

GICR_ICFGR0는 'Interrupt Configuration Register 0' 약자입니다. GICR_ICFGR0 레지스터를 통해 지정된 SGI 인터럽트를 에지 트리거(edge-triggered) 혹은 레벨 센시티브(level-sensitive)으로 설정할 수 있습니다. 

SGI의 인터럽트 아이디(INTID)의 범위는 0~15이므로 GICR_ICFGR0 레지스터의 2개 비트를 사용해 각각 SGI의 속성을 설정할 수 있습니다.

GICR_ICFGR0 레지스터의 비트 맵

다음 그림을 보면서 GICR_ICFGR0 레지스터의 비트 맵을 알아 봅시다.


그림 16.13 GICR_ICFGR0 레지스터의 비트 맵

GICR_ICFGR0 레지스터는 Int_config<x> 비트 맵으로만 구성돼 있으며, 2개의 비트로 Int_config<x>를 설정합니다. 비트 값에 따라 인터럽트를 다음과 같이 설정합니다.

0b00: 레벨 센시티브(level-sensitive)
0b10: 에지 트리거(edge-triggered)

대부분의 경우 SGI는 에지 트리거로 설정합니다.


[Arm프로세서] GIC: GICR_ISENABLER0, Interrupt Set-Enable Register 0 레지스터 Arm: GIC

GICR_ISENABLER0(Interrupt Set-Enable Register 0)는 SGI나 PPI를 어떤 방식으로 CPU Interfaces에 포워딩하는지 설정하는 레지스터입니다.

다음 그림을 보면서 GICR_ISENABLER0 레지스터의 비트 맵을 알아봅시다. 

 
그림 16.12 GICR_ISENABLER0 레지스터의 비트 맵

GICR_ISENABLER0 레지스터는 그림 16.12와 같이 Set_enable_bit<x>로 구성돼 있으며 x의 범위는 31~0입니다.

PPIs와 SGIs를 나타내는 인터럽트 아이디인 x에 대응되는 CPU interface로 포워딩하는 동작을 설정하는 비트입니다. 비트 값에 따라 다음과 같은 동작이 설정됩니다.

0b0: 이 비트를 읽으면 지정된 레지스터의 CPU interface 포워딩은 비활성화
0b1: 이 비트를 읽으면 지정된 레지스터의 CPU interface 포워딩은 활성화

부팅할 때 GICR_ISENABLER0 레지스터를 설정하는 경우가 많습니다.


[Arm프로세서] GIC: 리디스트리뷰터(Redistributor) 소개 Arm: GIC

GIC는 구성하는 하드웨어 블록 중에 리디스트리뷰터는 중요한 기능을 수행합니다. CPU 코어 별로 리디스트리뷰터가 존재하는데 다음과 같은 기능을 지원합니다.

   * 각각 SGI, PPI 인터럽트의 우선 순위를 설정 
   * 각각 SGI, PPI 인터럽트를 비활성화 및 활성화
   * 각각 SGI, PPI 인터럽트를 라우팅하는 정보 설정
   * 각각 PPI 인터럽트의 level-sensitive or edge-triggered 방식 설정
   * 각각 SGI, PPI 인터럽트를 인터럽트 그룹에 등록

리디스트리뷰터에서 제공하는 시스템 레지스터의 목록은 다음과 같습니다.

   * GICR_ISENABLER0 
   * GICR_ICFGR0 
   * GICR_IPRIORITYR<n> 
   * GICR_IGROUPR0 
   * GICR_IGRPMODR0

리디스트리뷰터를 구성하는 시스템 레지스터를 통해 리디스트리뷰터의 동작을 설정할 수 있습니다. 이제부터 리드스트리뷰터에서 제공하는 시스템 레지스터를 알아 봅시다.


[Arm프로세서] GIC: GICD_IGRPMODR<n> 레지스터 Arm: GIC

GICD_IGRPMODR<n>는 Interrupt Group Modifier Registers으로 인터럽트 그룹을 설정할 때 사용되는 레지스터됩니다. 여기서 <n>의 범위는 0~31입니다. 

GICD_IGRPMODR<n> 레지스터는 1개의 비트로 인터럽트의 그룹 속성을 설정하므로, 하나의 레지스터로 32개의 인터럽트의 속성을 설정할 수 있습니다. <n>의 범위가 0~31이므로 1024(1024 = 32 x 32)개의 인터럽트의 설정할 수 있습니다.

GICD_CTLR.DS이면 GICD_IGRPMODR<n> 는 지정된 인터럽트를 다음과 같이 설정합니다.

   * Secure Group 0.
   * Non-secure Group 1.
   * Secure Group 1.

GICD_IGRPMODR<n> 레지스터의 비트 맵은 다음과 같습니다.

 
그림 16.11 GICD_IGRPMODR<n> 레지스터의 비트 맵

GICD_IGRPMODR<n> 레지스터를 구성하는 비트 맵은 Group_modifier_bit으로 구성돼 있습니다. Group_modifier_bit은 인터럽트를 시큐어로 설정하는 기능입니다.
 
Group_modifier_bit는 용어 그대로 Group Modifier를 설정하는 비트로 인터럽트를 시큐어 혹은 논 시큐어 중 하나로 설정합니다. GICD_IGROUPR 레지스터를 구성하는 Group status 비트와의 조합으로 인터럽트를 다음과 같은 그룹으로 설정할 수 있습니다.

표 16.4 GICD_IGRPMODR와 GICD_IGROUPR로 인터럽트 그룹을 설정하는 방식

GICD_IGRPMODR와 GICD_IGROUPR 레지스터를 통해 인터럽트를 논시큐어 혹은 시큐어로 설정할 수 있습니다. 표 16.4를 보면 알 수 있듯이 GICD_IGRPMODR를 설정하면 인터럽트를 시큐어로 설정합니다. 그래서 Trusted OS에서 GICD_IGRPMODR 레지스터를 설정하는 경우가 많습니다.


[Arm프로세서] GIC: GICD_IGROUPR<n> 레지스터 Arm: GIC

GICD_IGROUPR은 Interrupt Group Registers의 약자로 인터럽트를 인터럽트 그룹 Group 0 혹은 Group 1으로 설정하는 레지스터입니다. GICD_IGROUPR은 GICD_IGROUPR<n>으로 표기되며 n의 범위는 0~31입니다.

Arm 아키텍처에서 지정된 인터럽트를 시큐어 혹은 논시큐어 월드에서 처리하도록 설정할 수 있습니다. 이를 위해 인터럽트를 Group 1, Group 0으로 분류해 관리하며 Group 0에 속한 인터럽트를 시큐어 인터럽트, Group 1은 논 시큐어 인터럽트로 명시합니다.

GICD_IGROUPR<n> 레지스터는 1개 비트 맵으로 인터럽트의 그룹 속성을 설정하므로, 하나의 레지스터로 32개의 인터럽트의 속성을 설정할 수 있습니다. <n>의 범위가 0~31이므로 1024(1024 = 32 x 32)개의 인터럽트 속성을 설정할 수 있습니다.

GICD_IGROUPR<n> 레지스터의 비트 맵

GICD_IGROUPR<n> 레지스터의 비트 맵은 다음과 같습니다.
 
그림 16.10 GICD_IGROUPR<n> 레지스터의 비트 맵

GICD_IGROUPR<n> 레지스터의 비트 맵은 Group_status_bit<x>으로 구성돼 있습니다. 각 비트 설정은 다음과 같습니다.

   * 0b0: 지정된 인터럽트를 Group 0으로 지정합니다. 만약 GICD_CTLR 레지스터의 DS 비트가 0이면 인터럽트를 시큐어로 지정합니다. 이 경우, 시큐어 상태에서만 해당 인터럽트를 설정해 처리할 수 있습니다.

   * 0b1: 지정된 인터럽트를 Group 1으로 지정합니다. 만약 GICD_CTLR 레지스터의 DS 비트가 1이면 인터럽트는 Group 1의 논 시큐어로 설정됩니다.
 
대부분 ‘Group 0’으로 지정된 인터럽트는 시큐어 상태에서 실행되는 Trusted 커널이 받아 처리합니다.

GICD_IGROUPR<n> 레지스터에 엑세스하는 예제 코드

이번에는 XEN 하이퍼바이저에서 GICD_IGROUPR<n> 레지스터에 엑세스하는 예제 코드를 확인해 봅시다.

https://github.com/xen-project/xen/blob/stable-4.15/xen/arch/arm/gic-v3.c
01 static void __init gicv3_dist_init(void)
02 {
...
03    /*
04     * Configure SPIs as non-secure Group-1. This will only matter
05     * if the GIC only has a single security state.
06     */
07    for ( i = NR_GIC_LOCAL_IRQS; i < nr_lines; i += 32 )
08        writel_relaxed(GENMASK(31, 0), GICD + GICD_IGROUPR + (i / 32) * 4);

08번째 줄에서 GICD_IGROUPR<n> 레지스터에 인터럽트 그룹을 설정하는 루틴을 확인할 수 있습니다. 04~05번째 줄에 있는 주석을 보면 XEN 하이퍼바이저는 SPI를 논시큐어 그룹 1로 지정한다는 사실을 알 수 있습니다.


[Arm프로세서] GIC: GICD_ICFGR<n> 레지스터 Arm: GIC

GICD_ICFGR(Interrupt Configuration Registers) 는 'Interrupt Configuration Registers' 약자로 GICD_ICFGR<n> 레지스터를 통해 SPI 인터럽트를 에지 트리거(edge-triggered) 혹은 레벨 센시티브(level-sensitive)으로 설정할 수 있습니다. 

GICD_ICFGR은 GICD_ICFGR<n>으로 표기되며 n의 범위는 0~63입니다. 

GICD_ICFGR<n> 레지스터는 2개의 비트로 SPI 인터럽트 속성을 설정하므로, 하나의 레지스터로 16개의 인터럽트의 속성을 설정할 수 있습니다. <n>의 범위가 0~63이므로 1024개 인터럽트(1024 = 16 x 64)의 속성을 설정할 수 있습니다.

GICD_ICFGR 레지스터의 비트 맵

이어서 다음 그림을 보면서 GICD_ICFGR 레지스터의 비트 맵을 알아봅시다. 

 
그림 16.9 CICD_ICFGR 레지스터의 비트 맵

GICD_ICFGR<n> 레지스터는 2개의 비트로 Int_config<x>를 설정합니다. 각 비트에 따라 인터럽트는 다음과 같이 설정됩니다.

0b00: 레벨 센시티브(level-sensitive)
0b10: 에지 트리거(edge-triggered)


[정보] 하드웨어 관점으로 인터럽트란

인터럽트는 하드웨어 관점으로 다음과 같은 전기 신호로 볼 수 있습니다. 

인터럽트 신호는 레벨 센시티브(level-sensitive)와 에지 트리거, 2가지 타입으로 분류됩니다. 레벨 센시티브(level-sensitive)는 특정 신호가 하이(High)나 로우(Low)로 유지돼야 인터럽트로 식별합니다. 에지 트리거(edge-trigger)는 신호가 하이서 로우, 로우에서 하이로 바뀌면 인터럽트가 유발됐다고 식별합니다.  

GICD_ICFGR 레지스터에 접근하는 예제 코드

이번에는 XEN 하이퍼바이저에서 GICD_ICFGR에 접근해 인터럽트를 레벨 센시티브 혹은 에지 트리거로 설정하는 예제 코드를 소개합니다. 

https://github.com/xen-project/xen/blob/stable-4.15/xen/arch/arm/gic-v3.c
01 static void gicv3_set_irq_type(struct irq_desc *desc, unsigned int type)
02 {
03    uint32_t cfg, actual, edgebit;
04    void __iomem *base;
05    unsigned int irq = desc->irq;
...
06    if ( irq >= NR_GIC_LOCAL_IRQS)
07        base = GICD + GICD_ICFGR + (irq / 16) * 4;
08    else
09        base = GICD_RDIST_SGI_BASE + GICR_ICFGR1;
10
11    cfg = readl_relaxed(base);
12
13    edgebit = 2u << (2 * (irq % 16));
14    if ( type & IRQ_TYPE_LEVEL_MASK )
15        cfg &= ~edgebit;
16    else if ( type & IRQ_TYPE_EDGE_BOTH )
17        cfg |= edgebit;
18
19    writel_relaxed(cfg, base);

먼저 06~07번째 줄을 봅시다.

06    if ( irq >= NR_GIC_LOCAL_IRQS)
07        base = GICD + GICD_ICFGR + (irq / 16) * 4;

07번째 줄을 보면 디스리뷰터의 베이스 주소인 GICD가 확인됩니다. 이 주소에 GICD_ICFGR 레지스터의 오프셋 주소와 함수의 인자(인터럽트 번호)를 더해 base에 저장합니다. 인터럽트 아이디를 바탕으로, 메모리 맵드 방식으로 접근하는 인터럽트 아이디에 해당되는 주소를 계산하는 루틴입니다.

이어서 11번째 줄을 분석하겠습니다.

11    cfg = readl_relaxed(base);

GICD_ICFGR<n> 레지스터의 값을 cfg에 저장하는 동작입니다.

이어서 13~17번째 줄을 봅시다.

13    edgebit = 2u << (2 * (irq % 16));
14    if ( type & IRQ_TYPE_LEVEL_MASK )
15        cfg &= ~edgebit;
16    else if ( type & IRQ_TYPE_EDGE_BOTH )
17        cfg |= edgebit;

인터럽트 속성을 에지 트리거 혹은 레벨 센시티브로 설정하는 루틴입니다. 14번째 줄은 type과 IRQ_TYPE_LEVEL_MASK 간 AND 비트 연산을 수행한 후 그 결과가 true이면 cfg 변수에 에지 트리거를 나타내는 값을 클리어하는 동작입니다. 간단히 설명하면 인터럽트를 레벨 센시티브로 설정하는 구문입니다.

16번째 줄은 인터럽트를 레벨 센시티브로 설정하는 구문입니다. type과 IRQ_TYPE_EDGE_BOTH 간 AND 비트 연산을 수행한 결과가 true이면 cfg 변수에 에지 트리거를 나타내는 값을 설정합니다. 

마지막으로 19번째 줄을 봅시다.

19    writel_relaxed(cfg, base);

에지 트리거 혹은 레벨 센시티브 속성 정보가 담긴 cfg를 base에 적용하는 동작입니다.


[Arm프로세서] GIC: GICD_IPRIORITYR<n> 레지스터 Arm: GIC

GICD_IPRIORITYR (Interrupt Priority Registers) 는 디스트리뷰터에 존재하는 Interrupt Priority Registers의 약자입니다. 레지스터의 이름과 같이 디스트리뷰터가 관리하는 SPI 인터럽트의 우선 순위를 정할 수 있습니다. 

레지스터 이름의 접미사인 <n>은 0~254 범위인데, 이 정보로  GICD_IPRIORITYR0~GICD_IPRIORITYR254 범위의 레지스터가 존재한다는 사실을 알 수 있습니다,

GICD_IPRIORITYR<n> 레지스터의 비트 맵

다음 그림은 GICD_IPRIORITYR 레지스터의 비트 맵 정보입니다.
 

그림 16.8 GICD_IPRIORITYR<n> 레지스터의 비트 맵

비트 맵을 보면 Priority_offset_0B~Priority_offset_3B가 확인되며, 각각 비트 필드는 8비트 단위로 구성됐습니다. GICD_IPRIORITYR<n>에서 n의 범위가 0~254 이므로 1016(1016 = 255 x 4)개 인터럽트 아이디에 해당되는 인터럽트의 우선 순위를 설정할 수 있습니다.

다음은 GICD_IPRIORITYR<n> 레지스터의 사용 예시입니다.

GICD_IPRIORITYR0 레지스터: 0~3 인터럽트 아이디에 해당되는 인터럽트를 설정
GICD_IPRIORITYR1 레지스터: 4~7 인터럽트 아이디에 해당되는 인터럽트를 설정

이어서 비트맵의 의미를 알아 봅시다. 비트 맵의 사이즈가 8비트(28)이니 10진수로 255입니다. 이 정보로 0~255 범위로 우선순위를 정할 수 있습니다.

GICD_IPRIORITYR 레지스터에 접근하는 예시 코드

GICD_IPRIORITYR 레지스터에 접근하는 코드를 리뷰하겠습니다.

이어서 XEN 하이퍼바이저에 구현된 gicv3_set_irq_priority() 함수를 분석하면서 GICD_IPRIORITYR 레지스터를 설정하는 방법을 배워봅시다. 

https://github.com/mirage/xen/blob/master/xen/arch/arm/gic-v3.c
01 static void gicv3_set_irq_priority(struct irq_desc *desc,
02                                   unsigned int priority)
03 {
04    unsigned int irq = desc->irq;
05
06    spin_lock(&gicv3.lock);
07
08    /* Set priority */
09    if ( irq < NR_GIC_LOCAL_IRQS )
10        writeb_relaxed(priority, GICD_RDIST_SGI_BASE + GICR_IPRIORITYR0 + irq);
11    else
12        writeb_relaxed(priority, GICD + GICD_IPRIORITYR + irq);
...
13 }

위 함수에서 12번째 줄이 GICD_IPRIORITYR 레지스터를 설정하는 루틴입니다. 디스트리뷰터의 베이스 주소를 나타내는 GICD에서 'GICD_IPRIORITYR + irq'를 더한 주소에 우선 순위(priority)를 저장하는 루틴입니다.

이처럼 GICD_IPRIORITY 레지스터는 메모리 맵드 I/O 방식으로 접근할 수 있습니다.


BUG(): CONFIG_PANIC_ON_OOPS, CONFIG_PANIC_ON_OOPS_VALUE Linux Kernel - Core Analysis

Sometime I noticed that system does not crash when the call to BUG() is made in the kernel driver. I just observed the stack trace from the kernel log and then find that target is running rather than entering crash mode.

To make the target crash when BUG() is called, the following config should be present;

CONFIG_PANIC_ON_OOPS=y
CONFIG_PANIC_ON_OOPS_VALUE=1

Let's look at kernel die() function which is called from BUG() function.

https://elixir.bootlin.com/linux/v5.15.30/source/arch/arm64/kernel/traps.c 
void die(const char *str, struct pt_regs *regs, int err)
{
...

if (in_interrupt())
panic("%s: Fatal exception in interrupt", str);
if (panic_on_oops)
panic("%s: Fatal exception", str);

If panic_on_oops is true, the panic() is called with "Fatal exception message". How panic_on_oops is specified? Is it configured during run-time?

Let's take a look at below statement;

https://elixir.bootlin.com/linux/v5.15.30/source/kernel/panic.c 
int panic_on_oops = CONFIG_PANIC_ON_OOPS_VALUE;

panic_on_oops depends on CONFIG_PANIC_ON_OOPS_VALUE.
Now is time to check the definition of CONFIG_PANIC_ON_OOPS_VALUE.

https://elixir.bootlin.com/linux/v5.15.30/source/lib/Kconfig.debug#L991
config PANIC_ON_OOPS_VALUE
int
range 0 1
default 0 if !PANIC_ON_OOPS
default 1 if PANIC_ON_OOPS

CONFIG_PANIC_ON_OOPS_VALUE can be configured by PANIC_ON_OOPS.

Conclusion

PANIC_ON_OOPS should be enabled to make the target crash when BUG() is called.

[공지] 리눅스 커널, Arm 아키텍처 - 오프라인 오픈 세미나(feat. IAMROOT) - 11/19(토) #리눅스 커널의 구조와 원리


- 10분이 신청해주셔서 세미나 신청 마감됐습니다. 
다음에 세미나를 또 진행하면 공지하겠습니다. 관심 가져 주셔서 감사합니다. -


안녕하세요, 리눅스 시스템 소프트웨어 개발자이자
'디버깅을 통해 배우는 리눅스 커널의 구조와 원리' 책의 저자인
김동현입니다.

세미나 공지를 위해 포스팅합니다. 

그 동안 온라인으로 다양한 세미나를 진행했는데요. 
이번에 오프 라인으로 세미나를 진행(IAMROOT 멤버 분들과)하려고 하는데, 
세미나를 신청하시면 누구나 세미나(무료)에 오셔서 들으실 수 있습니다. (선착순 10명)

관심있는 분은 세미나를 신청하시면 되고요, 유익한 정보를 공유하는 기회가 됐으면 좋겠습니다.

일시와 주제 및 장소는 다음과 같습니다.

* 일시
  
   - 11.19(토) 오후 2시~6시

* 장소
   
   - TOZ MoimCenter (삼성점): 16인 대형부스

     (약도)
     https://map.kakao.com/?urlX=514216.0&urlY=1114145.0&name=%28%EC%82%BC%EC%84%B1%EC%A0%90%29

* 주제

  1. 발표자: 이파란님 (1시간)

    -  QEMU로 Arm Trusted Firmware 디버깅하기 - 0.5hr
    -  다양한 배포판 버전에서 디버깅 커널(vmcore) 실행하기 - 0.5hr

  2. 발표자: 김동현 (3시간)

    -  리눅스 커널과 시스템 소프트웨어 분야의 전망 - 0.5hr
    -  리눅스 커널 Overview - 1.5hr
    -  Armv8 아키텍처 Overview - 1.0hr 

세미나 참석을 원하시면 아래 메일로 신청 메일을 보내주세요.

메일 수신자: 
   austindh.kim@gmail.com
   p4ranlee@gmail.com

----
(신청 예시)

-제목: [신청] 리눅스 커널 세미나

* 이름: 이병규
* 관심 분야: 리눅스 커널 드라이버, 하이퍼바이저, Rust 리눅스 
* Optional(이 내용은 꼭 기입하지 않아도 됩니다.)
   ex: 3년차 개발자, 학부 졸업생 or 대학원생  
----

참고로, 참석자 중에 질문을 가장 많이 하신 분께 제가 쓴 '디버깅을 통해 배우는 리눅스 커널의 구조와 원리' 책의 이북을 3분께 드릴 예정입니다.

감사합니다.
'디버깅을 통해 배우는 리눅스 커널의 구조와 원리' 저자,
김동현 올림.

[dev] command to build trace-cmd crash-utility-dev

To build trace-cmd, several libraries should be installed after pulling the source code.

 * libtraceevent
 * libtracefs

Install libtraceevent

The first thing to do is pull libtraceevent source using below commands;

$ git clone git://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git
Cloning into 'libtraceevent'...
remote: Enumerating objects: 2921, done.
remote: Total 2921 (delta 0), reused 0 (delta 0), pack-reused 2921
Receiving objects: 100% (2921/2921), 776.79 KiB | 1.79 MiB/s, done.
Resolving deltas: 100% (2087/2087), done.

Next, compile libtraceevent with 'make' command;

$ cd libtraceevent/
$ make
  DESCEND            src libtraceevent.a
  COMPILE FPIC       event-parse-api.o
  COMPILE FPIC       event-parse.o
  COMPILE FPIC       event-plugin.o
...
  BUILD PLUGIN       plugin_cfg80211.so
  BUILD PLUGIN       plugin_tlb.so
  GEN                libtraceevent-dynamic-list

And then 'sudo make install' command is used to install libtraceevent;

$ sudo make install
[sudo] password for austindh.kim:
  DESCEND            src libtraceevent.a
  BUILD STATIC LIB   libtraceevent.a
  DESCEND            src libtraceevent.so
  INSTALL     /home/austindh.kim/osc_src/libtraceevent_src/libtraceevent/include/traceevent/event-parse.h   to      /usr/local/include/traceevent
  INSTALL     /home/austindh.kim/osc_src/libtraceevent_src/libtraceevent/include/traceevent/event-utils.h   to      /usr/local/include/traceevent
  INSTALL     /home/austindh.kim/osc_src/libtraceevent_src/libtraceevent/include/traceevent/trace-seq.h     to      /usr/local/include/traceevent
...
  INSTALL     /home/austindh.kim/osc_src/libtraceevent_src/libtraceevent/plugins/plugin_tlb.so      to      /usr/local/lib64/traceevent/plugins

Install libtracefs

1. Pull libtracefs source with below command and then compile with 'make' command.

$ git clone https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/
$ make
  DESCEND            src libtracefs.so
  COMPILE FPIC       tracefs-utils.o
  COMPILE FPIC       tracefs-instance.o
  COMPILE FPIC       tracefs-events.o
  COMPILE FPIC       tracefs-tools.o
  COMPILE FPIC       tracefs-marker.o
  COMPILE FPIC       tracefs-kprobes.o
  COMPILE FPIC       tracefs-hist.o
  COMPILE FPIC       tracefs-filter.o

2. Use 'sudo make install' command to install libtracefs

$ sudo make install
  DESCEND            src libtracefs.a
  BUILD STATIC LIB   libtracefs.a
  DESCEND            src libtracefs.so
  INSTALL     /home/austindh.kim/osc_src/libtracefs_src/libtracefs/libtracefs.pc    to      /usr/local/lib/x86_64-linux-gnu/pkgconfig
  INSTALL     /home/austindh.kim/osc_src/libtracefs_src/libtracefs/lib/libtracefs.so.1.6.dev        to      /usr/local/lib64
  INSTALL     /home/austindh.kim/osc_src/libtracefs_src/libtracefs/include/tracefs.h        to      /usr/local/include/tracefs

Install libaudit-dev

Use below command to install libaudit-dev.

$ sudo apt-get install libaudit-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
...
(Reading database ... 691233 files and directories currently installed.)
Preparing to unpack .../libcap-ng-dev_0.7.7-3.1_amd64.deb ...
Unpacking libcap-ng-dev (0.7.7-3.1) ...
Selecting previously unselected package libaudit-dev:amd64.
Preparing to unpack .../libaudit-dev_1%3a2.8.2-1ubuntu1.1_amd64.deb ...
Unpacking libaudit-dev:amd64 (1:2.8.2-1ubuntu1.1) ...
Setting up libcap-ng-dev (0.7.7-3.1) ...
Setting up libaudit-dev:amd64 (1:2.8.2-1ubuntu1.1) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...

Install swig

Also swig should be installed with 'sudo apt-get install swig' command.

$ sudo apt-get install swig
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
...
Preparing to unpack .../swig_3.0.12-1_amd64.deb ...
Unpacking swig (3.0.12-1) ...
Setting up swig3.0 (3.0.12-1) ...
Setting up swig (3.0.12-1) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...

Install python-dev

Install python-dev with below command.

$ sudo apt-get install python-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
python-dev is already the newest version (2.7.15~rc1-1).
The following packages were automatically installed and are no longer required:
...


1. Now that I have installed all utilities to build trace-cmd, it is time to pull trace-cmd source.

$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/trace-cmd.git

2. Let's build trace-cmd with below command.

$ export PYTHON_VERS=python3
$ make all
Have zlib compression support
*************************************************************
ZSTD package not found, best compression algorithm not in use
*************************************************************
  COMPILE FPIC           trace-hash.o
  COMPILE FPIC           trace-hooks.o
  COMPILE FPIC           trace-input.o
  COMPILE FPIC           trace-output.o
  COMPILE FPIC           trace-recorder.o
  COMPILE FPIC           trace-util.o
  COMPILE FPIC           trace-filter-hash.o
  COMPILE FPIC           trace-filter.o
  COMPILE FPIC           trace-msg.o
...
swig -Wall -python -noproxy -I/home/austindh.kim/osc_src/trace_cmd_src/trace-cmd/include/trace-cmd -I/usr/local/include/traceevent ctracecmd.i
/usr/local/include/traceevent/event-parse.h:67: Warning 451: Setting a const char * variable may leak memory.
...
cc --shared /home/austindh.kim/osc_src/trace_cmd_src/trace-cmd/lib/trace-cmd/libtracecmd.a  ctracecmd_wrap.o -o ctracecmd.so -L/home/austindh.kim/osc_src/trace_cmd_src/trace-cmd/lib/trace-cmd -ltracecmd -L/usr/local/lib64 -ltraceevent -L/usr/local/lib64 -ltracefs -ltraceevent
make[1]: Nothing to be done for 'all'.
Note: to build man pages, type "make doc"
      to build unit tests, type "make test"

[Arm프로세서] GIC: GICD_IROUTER 레지스터 Arm: GIC

디스트리뷰터는 시스템에 존재하는 여러 Arm 코어에서 접근할 수 있는 SPI 인터럽트를 처리합니다. SPI는 Shared Peripheral Interrupt의 약자인데, 여기서 Shared는 “각 Arm 코어끼리 공유될 수 있다”라는 의미입니다. 

SPI 인터럽트를 디스트리뷰터가 특정 Arm 코어로 라우팅(Routing)해야 할 상황이면 GICD_IROUTER 레지스터를 설정해야 합니다.
---
[정보] IRQ Affinity란

멀티 코어 시스템에서는 인터럽트는 CPU 코어가 랜덤하게 받아 처리합니다. 종종 드라이버나 애플리케이션에서 특정 CPU 코어가 인터럽트를 받아 처리해야 할 상황에 직면합니다. 운영체제에서는 이를 “IRQ Affinity를 설정한다”고 명시합니다. 어떤 인터럽트를 1번째 CPU 코어에만 받아서 처리하도록 설정하면 이를 "해당 인터럽트를 1번째 CPU로 Affinity 설정했다"라고 말할 수 있습니다. 

시스템 소프트웨어 개발에서 affinity를 용어를 많이 사용하므로 Arm 스팩 문서에서도 Affinity란 용어를 사용해 인터럽트 라우팅을 설명합니다. 


GICD_IROUTER 레지스터는 Interrupt Routing Registers의 약자로 이름과 같이 디스트리뷰에서 관리하는 SPI를 라우팅하는 정보를 담고 있습니다. GICD_IROUTER 레지스터의 비트 맵은 다음과 같습니다.
 

그림 16.7 GICD_IROUTER 레지스터의 비트 맵

GICD_IROUTER 레지스터는 인터럽트 라우팅 정보를 설정하는 Aff3, Interrupt_Routing_Mode, Aff2, Aff1, Aff0 비트로 구성돼 있습니다. 먼저 Aff3 비트를 소개합니다.

Aff3, bits [39:32]

Affinity 레벨 3을 설정하는 용도의 비트입니다.

Interrupt_Routing_Mode, bit [31]

Affinity 하이라키에서 SPI를 어떤 방식으로 라우팅할 지 설정합니다. 각 비트는 다음과 같이 설정합니다.

0b0: Aff3, Aff2, Aff1, Aff0 비트 필드에 설정된 값으로 인터럽트가 라우팅 
0b1: 인터럽트는 어떤 Arm 코어로도 라우팅됨 

Aff2, bits [23:16]

Affinity 레벨 2를 지정합니다. 

Aff1, bits [15:8]

Affinity 레벨 1을 지정합니다. 

Aff0, bits [7:0]

Affinity 레벨 0을 지정합니다.

레지스터의 비트 맵의 이름에서 볼 수 있듯이 GICD_IROUTER<n> 레지스터를 통해 인터럽트의 Affinity(특정 CPU 코어에 라우팅) 속성을 설정할 수 있습니다.

[Arm프로세서] GIC: 디스트리뷰터 레지스터에 접근: 메모리 맵드 인터페이스 Arm: GIC

디스트리뷰터에서 제공하는 시스템 레지스터는 어떻게 접근할까요? 메모리 맵드 I/O 인터페이스를 통해 접근할 수 있습니다.
---
[중요] Arm 아키텍처에서 정의된 레지스터에 접근하는 2가지 방식

Arm 아키텍처에서 정의된 레지스터에 접근하는 방식은 크게 2가지로 분류됩니다.

어셈블리 명령어로 직접 접근
메모리 맵드 인터페이스로 접근

예를 들어 VBAR_EL1과 같은 시스템 레지스터는 MSR 혹은 MRS 명령어를 통해 쓰거나 읽을 수 있습니다. 시스템의 전반적인 속성을 저장하는 레지스터에 접근하는 방식입니다.

디스트리뷰터에서 정의된 시스템 레지스터에 접근하는 두 번째 방식은 '메모리 맵드 I/O 인터페이스'입니다. 접근하려는 I/O 디바이스나 하드웨어의 베이스 주소를 설정한 다음에, 베이스 주소에서 오프셋을 더한 주소에 접근해 레지스터에 접근하는 방식입니다.

특정한 패턴으로 약속한 메모리 주소를 통해 레지스터에 접근할 수 있는 방식입니다.
---

그렇다면 디스트리뷰터를 구성하는 레지스터를 메모리 맵드 인터페이스로 구성한 이유는 무엇일까요? GIC는 0~1024 범위인 인터럽트 아이디(INTID)를 설정할 수 있는데, 이를 위해 각각 레지스터를 정의한다면 1024개 레지스터가 필요합니다.

그래서 디스트리뷰터를 구성하는 레지스터는 다음과 같은 규칙으로 접근할 수 있습니다.

    “디스트리뷰터 베이스 주소 + 오프셋”
 
위에서 명시된 오프셋은 디스트리뷰터의 세부 동작 별로 지정됩니다.

다음 표에서 디스트리뷰터에서 제공하는 레지스터 별 오프셋을 확인할 수 있습니다.

표 16.4 디스트리뷰터 레지스터와 오프셋 목록

디스트리뷰터 베이스 주소에 위 표에서 명시된 오프셋을 더한 주소에 접근하면, 디스트리뷰터 레지스터에 접근할 수 있는 방식입니다. 

GIC를 구성하는 디스트리뷰터와 리디스트리뷰터에서 제공하는 시스템 레지스터는 위와 같이 메모리 맵드 방식으로 접근할 수 있습니다.

[Arm프로세서] GIC: 디스트리뷰터 (GICD_*) 시스템 레지스터 Arm: GIC

Distributor는 SPI 인터럽트를 설정할 수 있는 레지스터로 구성돼 있으며, 그 역할은 다음과 같습니다.

각각 SPI 인터럽트의 우선 순위를 설정하고 분배
각각 SPI 인터럽트를 비활성화 및 활성화
각각 SPI 인터럽트를 라우팅하는 정보 설정
level-sensitive or edge-triggered 방식 설정

이 중 Distributor의 핵심 동작은 다음과 같이 요약할 수 있습니다.

I/O 장치에서 요청한 SPI 인터럽트를 어느 CPU에 분배할까?
SPI 인터럽트 소스의 우선 순위를 어떻게 설정할까?

위와 같은 동작을 설정하려면 디스트리뷰터에서 제공하는 시스템 레지스터를 설정해야 합니다. 

Distributor에서 제공하는 레지스터 목록

Distributor에서 제공하는 시스템 레지스터의 목록은 다음과 같습니다.

GICD_ISENABLER<n> 
GICD_ICFGR<n> 
GICD_IPRIORITYR<n> 
GICD_IROUTER<n> 
GICD_IGROUPR<n> 
GICD_IGRPMODR<n>

프로그래머 입장에서 디스트리뷰터를 구성하는 레지스터를 통해 디스트리뷰터의 세부 동작을 설정할 수 있습니다. 

[정보] GIC의 프로그래머 모델이란

소프트웨어 관점으로 하드웨어 블록을 바라 본 기능을 프로그래머 모델이라고 합니다.
대부분 프로그래머 모델은 관련 동작을 설정할 수 있는 레지스터와 명령어로 구성돼 있습니다.

[Arm프로세서] GIC의 프로그래머 모델(Programmer Model) Arm: GIC

GIC의 기본 구조를 알아 봤으니 GIC의 프로그래머 모델을 소개합니다. GIC는 디스트리뷰터, 리디스트리뷰터 혹은 CPU interface와 같은 하드웨어 블록으로 구성됐으며 GIC에서 제공하는 레지스터를 통해 설정할 수 있습니다. 다음 그림은 GIC의 전체 구성도입니다.

 

그림 16.6 GIC 프로그래머 모델

그림 16.6는 GIC의 전체 구조를 나타냅니다. 먼저 그림의 가장 윗 부분에 있는 Distributor(디스트리뷰터)를 봅시다. GIC에서 디스트리뷰터는 1개 존재하며 디스트리뷰터는 SPI(Shared Peripheral Interrupt)를 받아 처리합니다. 디스트리뷰터가 처리하는 인터럽트는 여러 CPU 코어에서 받아 처리할 수 있습니다.

이어서 디스트리뷰터 아래에 있는 리디스트리뷰터를 봅시다. 리디스트리뷰터는 Arm 코어의 갯수 만큼 존재합니다. 그림 16.6에서 Arm 코어와 리디스트리뷰터의 개수는 4개입니다. 리디스트리뷰터는 특정 Arm 코어에서 처리하는 PPI(Private Peripheral Interrupt)를 관리합니다. 
---
[정보] 리디스트리뷰터는 언제 도입됐나?

리디스트리뷰터는 GICv3에 소개된 하드웨어 블록입니다. GICv2 버전에서 리디스트리뷰터는 존재하지 않았습니다. 


이어서 Redistribution 아래에 있는 CPU interface를 봅시다. CPU interface는 Arm 코어마다 존재하며 SGI(Software Generated Interrupt)와 PPI(Private Peripheral Interrupt)를 Arm 코어에 라우팅하는 역할을 수행합니다. 

CPU interface는 Arm 코어와 인터페이싱하는 하드웨어 블록입니다. GIC 인터럽트 핸들러에서 CPU interface에 존재하는 레지스터를 읽고 쓰는 명령어를 실행합니다.

여기서 GIC를 구성하는 디스트리뷰터, 리디스트리뷰터, CPU interface와 같은 3가지 하드웨어 블록을 알아봤습니다. 이어서 디스트리뷰터를 구성하는 레지스터를 소개합니다.
---


[Arm프로세서] GIC: 에지 트리거(Edge-triggered) 타입 인터럽트 Arm: GIC

이번에는 다음 그림을 보면서 'Edge-triggered' 인터럽트의 상태 머신을 알아봅시다.

 

그림 16.5 에지 트리거 인터럽트의 State Machine 변경 흐름

그림의 가장 왼쪽 부분에 있는 Inactive는 키보드와 터치와 같은 페리페럴에서 인터럽트가 발생하지 않는 상태입니다. 먼저 Inactive에서 pending로 상태가 바뀌는 흐름을 알아봅시다.

‘Inactive’ to ‘pending’

페리페럴이 인터럽트를 유발하지 않으면 상태 머신에서 Inactive 상태로 유지됩니다. 인터럽트가 발생(Assert)되면 GIC가 PE에게 인터럽트가 발생했다는 시그널을 보냅니다. 해당 인터럽트가 활성화됐고 우선 순위가 충분히 설정됐다면(우선 순위 레지스터보다 인터럽트 우선 순위가 높으면) Inactive에서 pending 상태로 변경됩니다.

‘Pending’ to ‘active’

인터럽트를 PE가 받아 소프트웨어적으로 ICC_IAR0_EL1 or ICC_IAR1_EL1 레지스터의 값을 읽으면 Pending에서 'active' 상태로 바뀝니다. 위에서 소개한 ICC_IAR_EL1 레지스터의 값만 읽으면 PE가 GIC에게 ACK를 전달하는 효과가 있습니다. GIC가 ACK를 받으면 해당 인터럽트의 시그널을 de-asserts합니다.  

‘Active’ to ‘active and pending’

페리페럴이 인터럽트 시그널을 re-assert하면 Active 상태에서 active and pending 상태로 바뀝니다. 이 동작이 레벨 트리거 타입 인터럽트에 비해 다릅니다.

‘Active and pending’ to ‘pending’

Arm 코어에서 실행되는 소프트웨어에서 ICC_IAR_EL1 레지스터에서 읽은 인터럽트 아이디를 EOIRs(End of Interrupt Registers) 레지스터에 써주면 Active and pending에서 pending으로 상태가 변경됩니다. Arm 코어에서 ICC_IAR_EL1 레지스터에서 읽은 인터럽트 아이디를 EOIRs 레지스터에 써줍니다.

CPU interface에서 인터럽트를 Arm 코어로 포워딩하면 GIC 인터럽트 핸들러에서 IAR 레지스트를 읽거나 EOI 레지스터에 인터럽트 아이디를 쓰는 루틴을 볼 수 있습니다. 이 과정에서 에지 트리거 타입의 인터럽트의 상태 머신이 어떻게 바뀌는지 머릿 속으로 그리면서 코드를 분석합시다.

여기까지 살펴본 레벨 센시티브 타입과 에지 트리거 인터럽트의 상태 변화도는 GIC의 동작 원리를 이해하기 위해 잘 익혀야 합니다.


1 2 3 4 5 6 7 8 9 10 다음