Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] 인터럽트 핸들러 등록(1) - #CS [라즈베리파이][커널]인터럽트

인터럽트 핸들러를 등록하는 처리 과정을 배우기 전에 우선 핸들러란 단어의 의미를 알 필요가 있습니다. 보통 핸들러는 동적으로 바뀌는 액션을 처리하기 위한 용도로 호출합니다. 그래서 핸들러는 함수 포인터로 등록해서 처리하는 경우가 대부분입니다. 만약 어떤 시스템에 인터럽트를 36개로 설계했으면 36개의 if else 문으로 인터럽트 핸들러를 호출하면 어떻게 될까요? 만약 다른 시스템에서 40개면요? 참 리눅스 커널 코드를 유지 보수하기 어렵겠죠. 그래서 인터럽트 핸들러는 함수 포인터로 등록합니다. 인터럽트 핸들러는 해당 인터럽트를 처리하기 위한 함수들이며 보통 인터럽트 종류만큼 인터럽트 핸들러 함수들이 있습니다.

인터럽트 핸들러 등록 시 기본 파라미터
해당 인터럽트가 발생하면 해당 인터럽트 핸들러 함수가 호출돼야 합니다. 이를 위해 디바이스 드라이버 코드에서 request_irq 함수을 써서 인터럽트 핸들러를 등록해야 합니다. 그럼request_irq 함수를 실제 어떻게 사용하는지 라즈베리안 리눅스 커널 코드를 잠깐 살펴보겠습니다. 

[drivers/usb/host/dwc_otg/dwc_otg_driver.c] 파일에 있는 dwc_otg_driver_probe 함수를 열어 볼까요? dwc_otg_driver_probe 함수를 보면 dwc_otg_common_irq 함수를 인터럽트 핸들러로 등록합니다.
[drivers/usb/host/dwc_otg/dwc_otg_driver.c]
static int dwc_otg_driver_probe()
          int retval = 0;
 dwc_otg_device_t *dwc_otg_device;
          int devirq;
//...
dev_dbg(&_dev->dev, "Calling request_irq(%d)\n", devirq);
retval = request_irq(devirqdwc_otg_common_irq,
                             IRQF_SHARED,
                             "dwc_otg", dwc_otg_device);

각 파라미터는 다음과 같이 대응합니다.
- 인터럽트 번호: devirq
- 인터럽트 핸들러: dwc_otg_common_irq
- 인터럽트 이름: " dwc_otg "
- dev_info: struct dwc_otg_device_t 타입의 디바이스 처리 인스턴스
- flags: 0x80(IRQF_SHARED)
 
request_irq 함수 마지막 파라미터는 dwc_otg_device_t 구조체인 dwc_otg_device란 포인터 변수인데, 이 주소는 인터럽트 핸들러에서 쓰는 핸들로 등록합니다. 이 포인터는 인터럽트 디스크립터의 struct irq_desc->action.dev_info 멤버로 저장됩니다. 이후 나중에 인터럽트 핸들러인 dwc_otg_common_irq 함수가 호출될 때 파라미터로 그대로 전달받습니다.

그럼 이제 위 코드에서 설정한 인자를 request_irq 함수가 어떻게 처리하는지 살펴볼 차례입니다.
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

코드를 보니 디바이스 드라이버에서 request_irq 함수를 호출하지만 파라미터를 그대로 실어서 request_threaded_irq함수를 호출한다는 사실을 알 수 있습니다. request_threaded_irq 함수를 살펴보면 인터럽트 디스크립터 구조체 struct irq_desc로 메모리를 할당하고 디바이스 드라이버에서 채워준 파라미터를 아래 코드에서 설정합니다.
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
 irq_handler_t thread_fn, unsigned long irqflags,
 const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
// ..생략..
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
 
해당 인터럽트가 떠서 dwc_otg_common_irq 인터럽트 핸들러가 호출되면, void 타입 dev_id 포인터는 struct dwc_otg_device_t 구조체로 캐스팅됩니다. 이제irq 62번에 해당하는 dwc_otg_common_irq 핸들러 함수를 열어서 정말 이렇게 동작하는지 살펴볼까요?
1 static irqreturn_t dwc_otg_common_irq(int irq, void *dev)
2 {
3 int32_t retval = IRQ_NONE;
4
5 retval = dwc_otg_handle_common_intr(dev);
6 if (retval != 0) {
7 S3C2410X_CLEAR_EINTPEND();
8 }
9 return IRQ_RETVAL(retval);
10 }
11 int32_t dwc_otg_handle_common_intr(void *dev)
12 {
13 int retval = 0;
14 gintsts_data_t gintsts;
15 gintmsk_data_t gintmsk_reenable = { .d32 = 0 };
16 gpwrdn_data_t gpwrdn = {.d32 = 0 };
17 dwc_otg_device_t *otg_dev = dev;

위 함수에서 다섯 번째 줄 코드를 보면 dwc_otg_common_irq함수 인자인 dev 를 dwc_otg_handle_common_intr 함수에 그대로 전달합니다.
5 retval = dwc_otg_handle_common_intr(dev);
 
이후 dwc_otg_handle_common_intr 함수에서는 전달받은 dev 인자를 dwc_otg_device_t 타입 변수로 전달합니다. dwc_otg_handle_common_intr 함수를 조금 더 분석하면 이 otg_dev 자료구조로 디바이스 드라이버나 하드웨어를 제어한다는 점을 알 수 있습니다.
17 dwc_otg_device_t *otg_dev = dev;

이렇게 인터럽트를 처리하기 위한 자료구조를 포인터로 request_irq 함수 마지막 파라미터로 등록하면 인터럽트 핸들러에서 이 포틴어를 캐스팅해서 쓸 수 있습니다. 
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev)
 
이를 다른 용어로 디바이스 핸들이라고도 합니다. 이런 기법은 인터럽트 핸들러를 처리할 때 아주 많이 쓰이니 코드를 자주 보면서 눈에 익힐 필요가 있습니다.

그럼 이제까지 배운 내용이 실제 어떻게 동작하는지 알아보겠습니다. 코드를 짜서 실제 보드에서 어떤 로그가 찍히는지 확인해야 공부한 내용이 더 오래 머릿속에 남거든요.

인터럽트 핸들러 등록 동작 확인하기
우리는 request_irq 함수로 인터럽트 핸들러를 등록하면 request_threaded_irq 함수에서 인터럽트 디스크립터에 해당 인터럽트 설정 정보를 저장하는 코드를 살펴봤습니다. 이제 실제 라즈베리파이에서 어떤 코드 흐름으로 인터럽트 핸들러를 등록하는지 알아봐야 할 차례입니다. 

그럼 이 동작은 어떻게 확인할까요? request_threaded_irq 함수에 적절한 디버깅 코드를 추가하면 인터럽트 핸들러를 등록 과정을 알 수 있습니다. 그럼 라즈베리안에 다음 디버깅 코드를 입력하면서 이 정보를 확인해 볼까요?

아래 코드와 같이 request_threaded_irq 함수에서 7번째와 11번째 코드에서 인터럽트 핸들러 정보를 인터럽트 디스크립터에 저장합니다.
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 action->handler = handler;
8 action->thread_fn = thread_fn;
9 action->flags = irqflags;
10 action->name = devname;
11 action->dev_id = dev_id;
12
13         if (irq == 62 || irq == 92 ) {
14 printk("[+][irq_debug] irq_num: %d, func: %s, line:%d, caller:%pS \n", 
15 irq, __func__, __LINE__, (void *)__builtin_return_address(0));
16 dump_stack();
17 }

이 코드 다음에 위와 같이 13번째와 17번째 코드를 입력하면 인터럽트 핸들러를 등록할 때 콜스택을 커널 로그로 볼 수 있습니다.

그럼 디버깅 코드를 조금 더 자세히 살펴볼게요. 
우선 13번째 줄 코드입니다. request_threaded_irq 함수 첫 번째 인자로 irq가 전달됩니다. 이 값은 unsigned int 타입으로 인터럽트 번호를 의미합니다.
13         if (irq == 62 || irq == 92 ) {
 
그래서 인터럽트 번호가 62번, 92번일 때만 if 문 아래에 코드가 실행합니다. 이 조건을 추가한 이유는 모든 인터럽트에 대한 커널 로그 정보를 출력하면 시스템에 과부하를 줄 수 있기 때문입니다. 

다음은 커널 로그를 출력하는 코드입니다.
14 printk("[+][irq_debug] irq_num: %d, func: %s, line:%d, caller:%pS \n", 
15 irq, __func__, __LINE__, (void *)__builtin_return_address(0));

irq는 인터럽트 번호이고 __func__, __LINE__은 각각 해당 코드의 함수 이름과 코드 라인 정보를 알려주는 매크로입니다. __builtin_return_address(0) 빌트인 매크로는 자기 자신을 호출한 함수 정보를 알려주죠. 
 
__func__, __LINE__, __builtin_return_address(0) 매크로는 리눅스 커널에 다양한 방식으로 활용되고 있으니, 처음 보는 함수를 디버깅할 때 자주 활용하면 좋습니다.

이번에는 핵심 디버깅 코드입니다. 함수 이름과 같이 현재 코드를 실행한 콜스택 정보를 출력합니다.
16 dump_stack();
 
위 코드를 입력하고 커널 빌드를 한 후 커널 이미지를 라즈베리안에 설치합니다. 이후 리부팅을 시키고 커널 로그를 열어보면 다음 로그를 확인할 수 있습니다. 참고로 라즈베리안에서 커널 로그는 /var/log/kern.log에 저장됩니다.
[0.696159] Dedicated Tx FIFOs mode
[0.696436] [+][irq_debug] irq_num: 62, func: request_threaded_irq, line:1777, caller:pcd_init+0x138/0x240 
[0.696449] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.14.39-v7+ #11
[0.696456] Hardware name: BCM2835
[0.696476] [<8010ffe0>] (unwind_backtrace) from [<8010c21c>] (show_stack+0x20/0x24)
[0.696494] [<8010c21c>] (show_stack) from [<8078721c>] (dump_stack+0xc8/0x10c)
[0.696514] [<8078721c>] (dump_stack) from [<801780f0>] (request_threaded_irq+0x1a4/0x1a8)
[0.696531] [<801780f0>] (request_threaded_irq) from [<805d38e4>] (pcd_init+0x138/0x240)
[0.696548] [<805d38e4>] (pcd_init) from [<805c5768>] (dwc_otg_driver_probe+0x728/0x884)
[0.696565] [<805c5768>] (dwc_otg_driver_probe) from [<805464f8>] (platform_drv_probe+0x60/0xc0)
[0.696583] [<805464f8>] (platform_drv_probe) from [<80544a84>] (driver_probe_device+0x244/0x2f8)
[0.696603] [<80544a84>] (driver_probe_device) from [<80544c00>] (__driver_attach+0xc8/0xcc)
[0.696621] [<80544c00>] (__driver_attach) from [<80542c0c>] (bus_for_each_dev+0x78/0xac)
[0.696640] [<80542c0c>] (bus_for_each_dev) from [<805443a0>] (driver_attach+0x2c/0x30)
[0.696659] [<805443a0>] (driver_attach) from [<80543de8>] (bus_add_driver+0x114/0x220)
[0.696678] [<80543de8>] (bus_add_driver) from [<805453a4>] (driver_register+0x88/0x104)
[0.696696][<805453a4>](driver_register) from [<80546444>](__platform_driver_register+0x50/0x58)
[0.696713][<80546444>](__platform_driver_register) from[<80b39a60>] (dwc_otg_driver_init+0x74/0x120)
[0.696733] [<80b39a60>] (dwc_otg_driver_init) from [<80101c1c>] (do_one_initcall+0x54/0x17c)
[0.696753] [<80101c1c>] (do_one_initcall) from [<80b01058>](kernel_init_freeable+0x224/0x2b8)
[0.696771] [<80b01058>] (kernel_init_freeable) from [<8079c1f0>] (kernel_init+0x18/0x124)
[0.696788] [<8079c1f0>] (kernel_init) from [<801080cc>] (ret_from_fork+0x14/0x28)
[0.696875] WARN::dwc_otg_hcd_init:1046: FIQ DMA bounce buffers: virt = 0xbad04000 dma = 0xfad04000 len=9024

위 로그는 인터럽트 62번을 설정할 때 콜스택 정보를 보여줍니다. 커널 로그에 찍히는 콜스택에 do_one_initcall/platform_drv_probe 함수가 보이니 리눅스 커널이 부팅할 때 인터럽트를 핸들러를 설정한다는 정보를 알 수 있습니다.

사실 아래 로그가 출력될 때 “[0.696436]”은 커널 부팅 후 초 단위 타임스탬프 정보를 알려줍니다. 즉 부팅 후 0.696436초에 이 코드가 실행됐다는 의미죠. .
[0.696436] [+][irq_debug] irq_num: 62, func: request_threaded_irq, line:1777, caller:pcd_init+0x138/0x240 
 
그리고 62번 인터럽트는 pcd_init 함수에서 설정하고 있다는 정보도 확인할 수 있습니다.

정리하면 인터럽트 핸들러는 커널이 부팅하는 과정에서 디바이스 드라이버를 초기화할 때 설정한다는 점을 알 수 있습니다. 코드를 분석하다가 커널 로그로 실제 해당 코드가 언제 수행되는지 알게 되니 더 많은 것을 얻을 수 있죠.


핑백

덧글

댓글 입력 영역