ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

205239
1625
172602


[리눅스커널][인터럽트후반부] IRQ 스레드는 언제 생성할까? 6. 인터럽트 후반부 처리


디바이스 드라이버의 초기화 코드는 대부분 부팅 과정에서 실행됩니다. 이 과정에서 request_threaded_irq() 함수를 호출해 IRQ 스레드를 생성합니다. IRQ 스레드는 생성되면 시스템 전원이 공급돼 동작하는 동안 해당 인터럽트 후반부를 처리하는 기능을 수행합니다.

이제, IRQ 전체 실행 흐름도를 보면서 IRQ 스레드 생성 과정을 배워볼까요?

IRQ 스레드 생성 전체 흐름도 파악하기 

IRQ 스레드를 생성하는 흐름도는 다음과 같습니다.

 
 
[그림 6.3] IRQ 스레드 생성 흐름도

request_threaded_irq() 함수를 호출하면 다음 동작을 수행합니다.

전달한 IRQ 스레드 정보를 해당 인터럽트 디스크립터에 설정 
kthread_create() 함수를 호출해서 IRQ 스레드 생성

kthread_create() 함수는 kthread_create_on_node() 함수로 치환됩니다. 
위 그림 오른쪽 윗부분에서 이해를 돕기 위해kthread_create() 함수를 호출하면 kthread_create_on_node() 함수를 호출하는 것처럼 표시했습니다.

우리는 커널 스레드를 생성할 때 kthread_create() 함수를 호출한다고 배웠습니다. IRQ 스레드도 kthread_create() 함수를 호출해서 생성합니다. IRQ 스레드도 커널 스레드의 한 종류입니다. 

이제부터 request_threaded_irq() 함수부터 __kthread_create_on_node() 함수까지 IRQ 스레드 어떤 방식으로 생성하는지 코드를 분석하겠습니다.
 
IRQ 스레드 생성: request_threaded_irq() 함수 분석하기

IRQ 스레드를 생성하기 위해서 request_threaded_irq() 함수를 호출해야 합니다.

함수 선언부와 인자를 소개합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h 
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
     irq_handler_t thread_fn,
     unsigned long flags, const char *name, void *dev);

request_threaded_irq() 함수에 전달되는 인자들은 다음과 같습니다.

타입 필드 필드 설명
unsigned int irq 인터럽트 번호
irq_handler_t  handler 인터럽트 핸들러 주소
irq_handler_t  thread_fn IRQ 스레드 핸들 함수 주소
unsigned long flags 인터럽터 핸들링 플래그
const char  name 인터럽트 이름

request_threaded_irq() 함수를 보니 어디선가 많이 본 것 같습니다.  

5장에서 인터럽트를 설정할 때 request_irq() 함수를 호출한다고 배웠습니다. 

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h 
01 static inline int __must_check
02 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
03     const char *name, void *dev)
04 {
05 return request_threaded_irq(irq, handler, NULL, flags, name, dev);
06 }

위 코드에서 보이듯이 request_irq() 함수에서 request_threaded_irq() 함수를 호출합니다. 05번째 줄 코드를 눈여겨 볼까요? 

    세 번째 인자를 NULL로 지정한다.

인터럽트를 설정할 때는 request_irq() 함수를 호출하면 세번째 인자를 NULL로 지정해서 request_threaded_irq() 함수를 호출합니다.

그런데 이렇게 request_threaded_irq() 함수를 호출해서 인터럽트 후반부를 IRQ 스레드로 처리하려면 세 번째 파라미터인 thread_fn에 IRQ 스레드 핸들러만 지정해야 합니다. 그러면 커널에서 인터럽트 이름과 번호 정보를 토대로 IRQ 스레드를 생성해 줍니다.

request_threaded_irq() 함수 선언부 코드 분석으로 다음 사실을 알게 됐습니다.

request_threaded_irq() 함수로 세 번째 인자에 IRQ 스레드 핸들러를 지정하면 커널 내부에서 지정한 인터럽트를 처리하는 IRQ 스레드를 생성한다.

request_threaded_irq() 함수를 호출할 때 request_irq() 함수와 같이 인터럽트 속성 정보도 같이 지정해야 한다.

request_threaded_irq() 함수 동작은 2단계로 나눌 수 있습니다.

1 단계: 인터럽트 디스크립터 설정

requested_threaded_irq() 함수에 전달된 인자를 인터럽트 디스크립터 필드에
저장합니다.

2 단계: IRQ 스레드 생성

irq_handler_t thread_fn 인자에 IRQ 스레드 핸들 주소를 저장하면 IRQ 스레드를 생성합니다.

 IRQ 스레드 생성 단계에 대해 소개했으니 이제 코드 분석으로 넘어 가겠습니다.
request_threaded_irq() 함수 코드를 소개합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c 
1 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
2 irq_handler_t thread_fn, unsigned long irqflags,
3 const char *devname, void *dev_id)
4 {
5 struct irqaction *action;
6 struct irq_desc *desc;
7 int retval;
...
8 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
9 if (!action)
10 return -ENOMEM;
...
11 action->handler = handler;
12 action->thread_fn = thread_fn;  
13 action->flags = irqflags;
14 action->name = devname;
15 action->dev_id = dev_id;
16 
17 chip_bus_lock(desc);
18 retval = __setup_irq(irq, desc, action);  

먼저 8~10 번째 줄 코드를 봅시다.

8 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
9 if (!action)
10 return -ENOMEM;

struct irqaction 타입인 action 지역 변수에 struct irqaction 구조체 크기만큼 동적 메모리를 할당합니다. 

action란 포인터 타입 지역 변수의 타입은 struct irqaction 구조체입니다. 이 변수는 8번째 줄 코드와 같이 kzmalloc() 함수를 호출해 동적 메모리 할당 받습니다. 다음에 11~12번째 줄 코드와 같이 인터럽트 핸들러와 IRQ 스레드 핸들러 정보를 저장합니다. 이 변수는 나중에 인터럽트 디스크립터의 action 필드에 저장됩니다. 

만약 메모리를 할당 받지 못하면 10 번째 줄 코드을 실행해 –ENOMEM 매크로를 반환하며 함수 실행을 종료합니다.

다음에 볼 11~15 번째 줄 코드는 다음 인자를 action 필드에 저장하는 동작입니다.

11 action->handler = handler;
12 action->thread_fn = thread_fn;  
13 action->flags = irqflags;
14 action->name = devname;
15 action->dev_id = dev_id;

12 번째 줄 코드를 보면 IRQ 스레드 핸들 함수 주소를 저장하고 있는 thread_fn 포인터를 action->thread_fn에 저장합니다.

struct irqaction 구조체 타입 action 변수는 해당 인터럽트 디스크립터인 struct irq_desc 구조체 주소에 저장됩니다.

Trace32로 확인한 인터럽트 디스크립터 구조체인 struct irq_desc 는 다음과 같습니다.

1 (struct irq_desc *) (struct irq_desc*)0xB008B300
...
2    (struct irqaction *) action = 0xBB4E6E40  
3      (irq_handler_t) handler = 0x8061EC00 = bcm2835_mmc_irq, /* 인터럽트 핸들러 */
4      (void *) dev_id = 0xBB47B010  /* 인터럽트 핸들러 핸들
5      (void *) percpu_dev_id = 0x0 = ,
6      (struct irqaction *) next = 0x0 = ,
7      (irq_handler_t) thread_fn = 0x8061DCC4 = bcm2835_mmc_thread_irq, /* IRQ Thread 핸들러 */
8      (struct task_struct *) thread = 0xBB516CC0 /* “irq/86-mmc1” IRQ 스레드의 태스크 디스크립터 */
9      (struct irqaction *) secondary = 0x0 = ,
10      (unsigned int) irq = 86, /* 인터럽트 번호 */

위 인터럽트 디스크립터는 라즈베리파이 86번 인터럽트를 관리하는 IRQ 스레드를 설정 정보를 포함합니다. 각각 필드에 대한 설명은 주석문을 참고하시기 바랍니다.

여기서 한 가지 의문이 생깁니다. 

    IRQ 스레드 속성을 저장하는 자료구조는 무엇일까?

우리는 5장에서 인터럽트 디스크립터는 인터럽트 속성을 저장하는 자료구조라고 배웠습니다.  IRQ 스레드도 이렇게 struct irq_desc 구조체인 인터럽트 디스크립터에서 같이 관리합니다.

다음 18번째 줄 코드를 보면 __setup_irq() 함수를 호출합니다.

18 retval = __setup_irq(irq, desc, action);

여기까지 코드를 보면 인터럽트 핸들러를 등록하는 실행 흐름과 똑같습니다. 그런데 __setup_irq() 함수 코드를 조금 더 살펴보면 IRQ 스레드로 설정할 때만 달리 동작하는 코드를 볼 수 있습니다. 

IRQ 스레드 생성: __setup_irq() 함수 분석

다음으로 __setup_irq() 함수 코드를 같이 분석해 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c 
1 static int
2 __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
3 {
4 struct irqaction *old, **old_ptr;
5 unsigned long flags, thread_mask = 0;
...
6 nested = irq_settings_is_nested_thread(desc);
...
7 if (new->thread_fn && !nested) {  
8 ret = setup_irq_thread(new, irq, false);
9 if (ret)
10 goto out_mput;

우선 __setup_irq() 함수에 전달되는 인자는 아래와 같습니다.

unsigned int irq: 인터럽트 번호
struct irq_desc *desc: 인터럽트 디스크립터
struct irqaction *new: 인터럽트 디스크립터의 action 멤버(struct irq_desc->action)

코드를 보면 알 수 있듯이 __setup_irq() 함수의 핵심 동작은 다음과 같습니다.

IRQ 스레드 핸들 함수가 등록됐는지 점검한다.
만약 IRQ 스레드가 등록이 됐으면 setup_irq_thread() 함수를 호출해 IRQ 스레드를 생성한다.

먼저 6 번째 줄 코드를 봅시다. 

6 if (new->thread_fn && !nested) {  
7 ret = setup_irq_thread(new, irq, false);

이 코드는 두 가지 조건을 점검합니다.  struct irqaction 타입인 new->thead_fn 필드에 함수 포인터가 등록됐거나 nested 변수가 0일 때 setup_irq_thread() 함수를 호출합니다. 

nested 변수는 현재 설정하는 IRQ 스레드가 nested 타입인지 점검합니다. 이 기능을 쓸 경우 nested 변수가 1이 됩니다. nested 변수를 읽어 오는 다음 코드를 눈여겨봅시다.

6 nested = irq_settings_is_nested_thread(desc);

여기서 new->thead_fn 로 IRQ 스레드 핸들러 함수가 등록됐고 nested 변수가 0이면 8번째 줄 코드를 실행됩니다.

IRQ 스레드 생성: setup_irq_thread() 함수 분석

이어서 IRQ 스레드를 생성하는 setup_irq_thread() 함수를 분석하겠습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c 
1 static int
2 setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
3 {
4 struct task_struct *t;
5 struct sched_param param = {
6 .sched_priority = MAX_USER_RT_PRIO/2,
7 };
8
9 if (!secondary) {
10 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   
11    new->name);
12 } else {
13 t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
14    new->name);
15 param.sched_priority -= 1;
16 }

위 코드를 보면 특별한 동작을 수행하지 않습니다. kthread_create() 함수를 호출해서 커널 스레드를 생성합니다. 이 코드로 IRQ 스레드도 커널 스레드의 한 종류라고 말할 수 있겠습니다.


커널 스레드는 커널 공간에서만 실행하는 프로세스입니다. 
커널 스레드는 유저 공간과 시스템 콜로 통신하지 않습니다. 커널 공간에서만 대부분 배경 작업으로 커널 리소스(메모리, 프로세스 관리)를 관리합니다.

커널에는 다양한 커널 스레드가 있습니다. 커널 서브 시스템이나 드라이버 목적에 맞게 커널 스레드를 생성할 수 있습니다.  

    IRQ 스레드 이외 다른 커널 스레드에는 무엇이 있을까?

워크큐를 실행하는 스레드를 워커 스레드, 메모리가 부족할 때 kswapd 스레드 그리고 Soft IRQ 후반부 처리용 ksoftirqd 스레드를 예를 들 수 있습니다. IRQ 스레드로 여러 커널 스레드 중 하나입니다.


먼저 10번째 줄 코드를 보겠습니다. irq_thread() 이란 IRQ 스레드를 제어하는 함수와 "irq/%d-%s"란 이름을 지정해 IRQ 스레드를 생성합니다. 

9 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   
10    new->name);

kthread_create() 함수를 호출할 때 다음과 같은 인자를 지정합니다.

irq_thread: IRQ 스레드 핸들 함수
new: IRQ 스레드 핸들 매개 인자(struct irqaction)
"irq/%d-%s": IRQ 스레드 이름 타입
irq: 인터럽트 번호
new->name: 인터럽트 이름 

kthread_create() 함수에 전달하는 두 번째 파라미터인 new는 IRQ 스레드 핸들 함수로 전달되는 매개 인자입니다. 이 매개 인자는 struct irqaction 구조체 타입입니다.

IRQ 스레드 핸들러 함수에 전달하는 매개인자 확인하기

IRQ 스레드의 스레드 핸들인 irq_thread() 함수 코드를 보면서 매개 인자 처리 방식에 대해 조금 더 살펴보겠습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c 
1 static int irq_thread(void *data)
2 {
3 struct callback_head on_exit_work;
4 struct irqaction *action = data;

우리는 커널 스레드를 생성하면 커널 스레드 핸들 함수에서 무한 루프를 돌면서 스레드 목적에 맞는 동작을 수행합니다. 이를 스레드 핸들 함수라고 합니다. IRQ 스레드는 irq_thread() 함수가 이 역할을 수행합니다. 

IRQ 스레드가 실행할 때 irq_thread() 함수가 실행하는데 함수 인자로 void 타입 data 포인터를 전달합니다. 위 irq_thread() 함수 4 번째 줄 코드를 눈여겨보면 이 포인터를 struct irqaction * 타입으로 캐스팅합니다.

다음 그림을 보면 이 과정을 쉽게 이해할 수 있습니다. 
 
[그림 6.4] IRQ 스레드 생성과 스레드 매개인자 전달 흐름도

위 그림에서 볼드체로 된 왼쪽 박스를 봅시다. 
struct irqaction 타입인 new 변수를 매개 인자로 등록합니다. 

오른쪽은 IRQ 스레드 핸들러로 매개인자가 전달되는 부분입니다. IRQ 스레드 핸들러인 irq_thread() 함수로 전달된 void 타입 data 변수는 struct irqaction 타입으로 캐스팅됩니다. 이 방식으로 IRQ 스레드에서 인터럽트 속성 정보가 담긴 struct irqaction 구조체 매개인자로 세부 제어를 할 수 있습니다.

이 부분에서 자연스럽게 다음과 같은 의문이 생깁니다. 

    IRQ 스레드 핸들러 함수로 매개인자는 왜 전달할까?

여기에는 그럴만한 이유가 있습니다. 

    IRQ 스레드 핸들러 함수에서 인터럽트 후반부를 처리하기 위해 인터럽트 세부 
      속성 정보가 담긴 자료구조가 필요하다. 

참고로, 커널 스레드를 처리하는 스레드 핸들러에서 이 방식으로 매개 인자를 전달합니다. 

IRQ 스레드 생성 예제 코드 분석해보기

IRQ 스레드를 생성하는 함수 흐름을 알아봤으니 IRQ 스레드를 생성하는 예제 코드를 살펴보겠습니다.

분석할 소스 코드는 다음과 같습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/mmc/host/bcm2835-mmc.c 
1 static int bcm2835_mmc_add_host(struct bcm2835_host *host)
2 {
3 struct mmc_host *mmc = host->mmc;
4 struct device *dev = mmc->parent;
...
5 bcm2835_mmc_init(host, 0);
6 ret = devm_request_threaded_irq(dev, host->irq, bcm2835_mmc_irq,
7 bcm2835_mmc_thread_irq, IRQF_SHARED,
8 mmc_hostname(mmc), host); 

bcm2835_mmc_add_host() 함수에서 라즈베리파이에서 86번 인터럽트 핸들러와 해당 IRQ 스레드를 설정하는 코드입니다. 어 그런데 뭔가 이상합니다. 

    request_threaded_irq() 함수 대신 devm_request_threaded_irq() 함수를 써서 
     IRQ 스레드를 설정하는 것 같다.

함수 이름이 다르니 다른 동작을 하는 함수로 보입니다. 하지만 devm_request_threaded_irq() 함수를 열어 보면 request_threaded_irq() 함수를 호출합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/devres.c 
1 int devm_request_threaded_irq(struct device *dev, unsigned int irq,
2       irq_handler_t handler, irq_handler_t thread_fn,
3       unsigned long irqflags, const char *devname,
4       void *dev_id)
5 {
6 struct irq_devres *dr;
7 int rc;
8
...
9
10 rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
11   dev_id);

10 번째 줄과 같이 devm_request_threaded_irq() 함수에서 request_threaded_irq() 함수를 호출합니다.

IRQ 스레드 생성 관점으로만 보면 devm_request_threaded_irq() 함수는 request_threaded_irq() 함수와 같은 기능입니다. 인터럽트를 struct device 구조체로 관리하는 방식만 다를 뿐입니다.

이번에 request_threaded_irq() 함수에 전달하는 인자가 약간 다른 것 같습니다.

6 ret = devm_request_threaded_irq(dev, host->irq, bcm2835_mmc_irq,
7 bcm2835_mmc_thread_irq, IRQF_SHARED,
8 mmc_hostname(mmc), host); 

6번째 줄을 보면 bcm2835_mmc_irq() 함수를 인터럽트 핸들러로 등록합니다. 이 함수는 86번 “mmc1” 인터럽트가 발생하면 호출되는 인터럽트 핸들러 함수입니다. 

7번째 줄 코드를 보면 request_threaded_irq() 함수 세 번째 인자로 bcm2835_mmc_thread_irq() 함수를 지정합니다. 이 함수를 IRQ 스레드 핸들러라고 합니다. IRQ 스레드가 실행될 때 호출되는 핸들러 함수입니다.  

조금 더 이해를 돕기 위해 request_threaded_irq() 함수의 선언부를 보면 세 번째 인자로 irq_handler_t thread_fn가 선언돼 있습니다. 두 번째 인자는 인터럽트 핸들러 함수입니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h  
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
     irq_handler_t thread_fn,
     unsigned long flags, const char *name, void *dev);

thread_fn 함수 포인터에 bcm2835_mmc_thread_irq() IRQ 스레드 핸들러 함수를 등록하는 것입니다.

이를 알기 쉬운 코드 형식으로 표현하면 각 인자를 다음과 같이 등록합니다.
의미 인자 선언 입력 인자
인터럽트 번호 irq host->irq
인터럽트 핸들러 handler bcm2835_mmc_irq
IRQ Thread 핸들러 thread_fn bcm2835_mmc_thread_irq

그러면 위에서 선언된 인자들은 언제 어떻게 실행할까요? 다음 절에서 알아볼 예정이지만 먼저 소개하겠습니다. 

인터럽트가 발생했을 때 인터럽트 컨택스트에서 수행하는 인터럽트 핸들러는 bcm2835_mmc_irq() 함수이다.
“irq/86-mmc1” IRQ 스레드가 실행하는 스레드 핸들러 함수는       bcm2835_mmc_thread_irq() 함수이다.

IRQ 스레드 이름을 정하는 규칙은 무엇일까

다음은 IRQ 스레드를 생성하는 코드를 보면서 86번 인터럽트에 대한 IRQ 스레드 이름을 어떤 규칙으로 생성하는지 알아봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c 
1 static int
2 setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
3 {
4 struct task_struct *t;
5 struct sched_param param = {
6 .sched_priority = MAX_USER_RT_PRIO/2,
7 };
8
9 if (!secondary) {
10 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   
11    new->name);
12 } else {
13 t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
14    new->name);
15 param.sched_priority -= 1;
16 }

위에서 분석한 코드 내용을 토대로 다음 사실을 알 수 있습니다.

    “irq=86 name=mmc1” 인터럽트의 IRQ 스레드 이름은 “irq/86-mmc1”이다.

그렇다면 “irq/86-mmc1” IRQ 스레드는 언제 실행될까요? 

    86번 인터럽트가 발생하면 호출되는 인터럽트 핸들러가 IRQ 스레드 실행 여부를 
    결정한다. 만약 86번 인터럽트가 발생하지 않으면 IRQ 스레드는 실행하지 않는다.

다른 리눅스 시스템에서 IRQ 스레드 생성 예제 코드 분석

라즈비안에선 86번 인터럽트를 처리할 한 개 IRQ 스레드만 생성합니다. 1개 IRQ 스레드만 생성하는 코드 밖에 없으니 이번에는 다른 리눅스 시스템에서 IRQ 스레드를 생성하는 예제 코드를 소개합니다

이번에는 request_threaded_irq() 함수를 호출해서 IRQ 스레드를 생성하는 과정을 살펴보겠습니다. 분석할 코드는 다음과 같습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/usb/dwc3/gadget.c 
1 static int dwc3_gadget_start(struct usb_gadget *g,
2 struct usb_gadget_driver *driver)
3 {
4 struct dwc3 *dwc = gadget_to_dwc(g);
5 unsigned long flags;
6 int ret = 0;
7 int irq;
8
9 irq = dwc->irq_gadget;
10 ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
11 IRQF_SHARED, "dwc3", dwc->ev_buf);

먼저 request_threaded_irq() 함수에 전달하는 인자를 살펴봅시다.

irq: 인터럽트 번호
dwc3_interrupt: 인터럽트 핸들러
dwc3_thread_interrupt: 인터럽트 스레드 핸들 함수
IRQF_SHARED: 인터럽트 플래그
"dwc3": 인터럽트 이름
dwc->ev_buf: 인터럽트 핸들러와 인터럽트 스레드 핸들 함수에 전달하는 매개인자

5장에서 인터럽트 핸들러를 설정할 때 썼던 request_irq() 함수와 유사해 보입니다. request_irq() 함수를 호출할 때와 차이점은 request_threaded_irq() 함수는 IRQ 스레드 핸들러인 dwc3_thread_interrupt() 함수 이름을 세 번째 인자로 전달합니다.

request_threaded_irq() 함수를 호출하면 해당 인터럽트에 대한 전용 IRQ 스레드가 생성됩니다. 그러면 리눅스 커널에서 IRQ 스레드 이름은 어떻게 결정할까요? 

      인터럽트 번호가 47이면 IRQ 스레드 이름은 "irq/47-dwc3"가 된다.

그렇다면 "irq/47-dwc3" IRQ 스레드를 깨울지 언제 결정할까요?

      인터럽트 발생 후 dwc3_interrupt() 인터럽트 핸들러에서 인터럽트에 대한 처리를 한 
      다음 "irq/47-dwc3" IRQ 스레드를 깨울지 결정한다.

 이후 "irq/47-dwc3" IRQ 스레드가 깨어나면 스레드 핸들러인 dwc3_thread_interrupt() 함수가 호출됩니다. 이 세부 동작은 다음 절에서 살펴볼 예정입니다.

이번에는 인터럽트 핸들러인 dwc3_thread_interrupt() 함수 분석으로 세부 동작을 알아보겠습니다.  

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/usb/dwc3/gadget.c 
static irqreturn_t dwc3_interrupt(int irq, void *_evt)
{
struct dwc3_event_buffer *evt = _evt;
return dwc3_check_event_buf(evt);
}

dwc 인터럽트가 발생하면 dwc3_interrupt() 이란 인터럽트 핸들러가 실행됩니다. dwc3_interrupt() 함수는 특별한 동작을 하지 않습니다. 바로 dwc3_check_event_buf() 함수를 호출합니다.

dwc3_check_event_buf() 함수 구현부는 다음과 같습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/usb/dwc3/gadget.c 
1 static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt)
2 {
3 struct dwc3 *dwc = evt->dwc;
4 u32 amount;
5 u32 count;
6 u32 reg;
7
8 if (pm_runtime_suspended(dwc->dev)) {
9 pm_runtime_get(dwc->dev);
10 disable_irq_nosync(dwc->irq_gadget);
11 dwc->pending_events = true;
12 return IRQ_HANDLED;
13 }
...
14 if (amount < count)
15 memcpy(evt->cache, evt->buf, count - amount);
16
17 dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), count);
18
19 return IRQ_WAKE_THREAD;
20}

복잡해 보이는 코드인데 IRQ 스레드 처리 관점으로 함수 코드를 분석하겠습니다.

위 함수를 눈여겨보면 디바이스를 제어하는 시나리오에 따라 12 번째 줄에서는 IRQ_HANDLED 그리고 19 번째 줄에서는 IRQ_WAKE_THREAD를 반환합니다. 

인터럽트가 발생한 후 IRQ 스레드를 깨워서 인터럽트 후반부로 후속 처리를 할 필요가 없을 때가 있습니다. 이 조건에서 다음 12 번째 줄 코드와 같이 IRQ_HANDLED를 반환합니다.

8 if (pm_runtime_suspended(dwc->dev)) {
9 pm_runtime_get(dwc->dev);
10 disable_irq_nosync(dwc->irq_gadget);
11 dwc->pending_events = true;
12 return IRQ_HANDLED;
13 }

IRQ 스레드가 해당 인터럽트 핸들러 이후 IRQ 스레드에서 인터럽트에 대한 후속 처리를 해야 할 때는 IRQ_WAKE_THREAD를 반환합니다.

17 dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), count);
18
19 return IRQ_WAKE_THREAD;
20}

이후 IRQ 스레드가 깨어난 후 IRQ 스레드 핸들러인 dwc3_thread_interrupt() 함수가 실행됩니다. 인터럽트 핸들러에서 바로 처리하지 못한 일을 수행합니다. 

여기까지 소스 코드 분석으로 다음의 결론을 내릴 수 있습니다.

    인터럽트 핸들러에서 IRQ_WAKE_THREAD를 반환할 때 IRQ 스레드를 깨운다.

인터럽트 핸들러에서 IRQ_WAKE_THREAD를 반환한 후 세부 동작은 6.4 절에서 자세히 다룰 예정입니다.


     "혹시 글을 읽고 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실하게 답변 올려 드리겠습니다!"


# Reference 인터럽트 후반부 처리








6.9 Soft IRQ 서비스는 누가 언제 처리하나?




6.13 Soft IRQ 디버깅
6.13.1 ftrace Soft IRQ 이벤트 분석 방법
6.13.2 /proc/softirqs로 Soft IRQ 서비스 실행 횟수 확인
 

# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2



    핑백

    덧글

    댓글 입력 영역