Linux Kernel(4.9) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이]인터럽트(5) - 인터럽트 디스크립터란 라즈베리_리눅스커널_인터럽트

인터럽트 디스크립터란
프로세스마다 프로세스를 관리하는 태스크 디스크립터(struct task_struct)가 있습니다. 페이지도 페이지를 관리하는 페이지 디스크립터(struct page)가 있죠. 마찬가지로 리눅스 커널에서 발생하는 인터럽트 종류만큼 인터럽트 디스크립터가 있습니다. 만약 인터럽트 개수가 34개면 이를 관리하는 34개의 인터럽트 디스크립터가 있습니다.

이미 설명했듯이 request_irq 함수를 써서 인터럽트 번호와 인터럽트 핸들러를 등록합니다. 
그럼 커널은 해당 인터럽트가 발생하면 어떤 정보를 참고해서 이미 등록한 인터럽트 핸들러를 호출할 수 있을까요. 정답 인터럽트 디스크립터입니다. 

각 디바이스 드라이버에서 인터럽트 번호, dev_id 그리고 인터럽트 핸들러 정보를 채워 request_irq 함수를 호출합니다.
static inline int __must_check
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_threaded_irq 함수에서 커널은 이 파라미터들 이외에 다른 정보를 채워서 인터럽트 디스크립터를 생성합니다. 이 후 인터럽트 디스크립터는 인터럽트 디스크립터 테이블에 등록합니다.

인터럽트 디스크립터 자료 구조는 아래와 같습니다.
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;

가장 중요한 인터럽트 정보는 struct irq_desc.irq_data 멤버에 저장돼 있습니다. 해당 정보는 아래와 같습니다.
struct irqaction {
irq_handler_t handler;           //<<-- [1] 
void *dev_id;           //<<--  [2]
void __percpu *percpu_dev_id;  
struct irqaction *next;
irq_handler_t thread_fn;        //<<--  [3]
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq;               //<<--  [4]
unsigned int flags;               //<<--  [5]
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;

[1]:  irq_handler_t handler: 인터럽트 핸들러를 저장합니다.
[2]: void *dev_id: 디바이스 드라이버에서 지정한 데이터가 저장됩니다. 다양한 자료 구조를 지원해야 하므로 void 타입으로 선언했습니다.
[3]: thread_fn: 많은 드라이버가 Soft IRQ로 커널 쓰레드 레벨로 디바이스 데이터를 처리합니다. 이 경우 설정됩니다.
[4]: 인터럽트 번호입니다.
[5]: 설정값입니다. 아래 값 중 하나죠.
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008

이렇게 인터럽트 디스크립터는 해당 인터럽트에 대한 명세서라고 볼 수 있습니다. 
추가로 여러 중요한 정보를 저장하는데, 이 값들이 어떻게 처리되는지 살펴보겠습니다.
1. 각 CPU 별로 발생한 인터럽트 발생 개수
2. 인터럽트 처리 상태

인터럽트 발생 횟수
대부분 디바이스 드라이버는 인터럽트 핸들러로 하드웨어 디바이스가 발생시킨 인터럽트와 통신합니다. 예를 들면 터치 디바이스에서 사용자가 터치를 입력하면 터치 디바이스에서 올려주는 인터럽트로 터치 인풋이 발생했다고 알려줍니다. 그럼 디바이스 드라이버에서 인터럽트 핸들러를 등록한 후 해당 디바이스가 제대로 인터럽트를 발생시키는지 어떻게 알 수 있을까요. 이럴 때는 인터럽트 발생 횟수를 점검하면 됩니다. 이번에는 어떤 흐름으로 인터럽트 발생 횟수를 인터럽트 디스크립터가 관리하는지 점검하겠습니다.

인터럽트 발생 횟수는 인터럽트 디스크립터 내struct irq_desc->kstat_irqs란 per-cpu 타입 멤버 변수에 저장됩니다.
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;

인터럽트가 발생하면 아래 흐름으로 handle_fastoei_irq 함수가 호출됩니다.
이후 해당 인터럽트 번호로 인터럽트 디스크립터를 가져와서, kstat_incr_irq_this_cpu 함수를 호출합니다.
void
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;

raw_spin_lock(&desc->lock);

if (!irq_may_run(desc))
goto out;

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(irq, desc);  //<<-- 
// 생략
handle_irq_event(desc); 

아래 코드를 보면 struct irq_desc->kstat_irqs 값을 1만큼 증가시킵니다.
static inline void kstat_incr_irqs_this_cpu(struct irq_desc *desc)
{
__this_cpu_inc(*desc->kstat_irqs);
__this_cpu_inc(kstat.irqs_sum);
}

void kstat_incr_irq_this_cpu(unsigned int irq)
{
kstat_incr_irqs_this_cpu(irq, irq_to_desc(irq));
}

라즈베리 커널 코드에서 아래 패치를 적용하면 framebuffer driver 인터럽트 발생 횟수를 확인할 수 있습니다.
diff --git a/drivers/video/fbdev/bcm2708_fb.c b/drivers/video/fbdev/bcm2708_fb.c
index 612293c..afc91ea 100644
--- a/drivers/video/fbdev/bcm2708_fb.c
+++ b/drivers/video/fbdev/bcm2708_fb.c
@@ -804,6 +804,46 @@ static int bcm2708_fb_register(struct bcm2708_fb *fb)
    return ret;
 }
 
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel_stat.h>
+
+static void interrupt_debug_irq_times(int irq_num)
+{
+   struct irqaction *action;
+   struct irq_desc *desc;
+
+   int cpu_num = 0;
+   unsigned long all_count = 0;
+
+   desc = irq_to_desc(irq_num);
+   
+   action = desc->action;
+
+   if (!action ) {
+       pr_err("invalid action at %s line: %d \n", __func__, __LINE__);
+       return;
+   }
+
+
+   for_each_online_cpu(cpu_num) {
+      all_count |= kstat_irqs_cpu(i, j);
+      printk("%10u ", kstat_irqs_cpu(action->irq, cpu_num));
+      printk("all_count: %10u ", all_count); 
+}
+
 static int bcm2708_fb_probe(struct platform_device *dev)
 {
    struct device_node *fw_np;
@@ -859,6 +899,7 @@ static int bcm2708_fb_probe(struct platform_device *dev)
        goto free_dma_chan;
    }

+   interrupt_debug_irq_times(fb->dma_irq);

    pr_info("BCM2708FB: allocated DMA channel %d @ %p\n",
           fb->dma_chan, fb->dma_chan_base);

위 코드는 추가한 다음에 커널 로그를 확인하면 아래와 같이 인터럽트 발생횟수를 확인할 수 있습니다. 
<4>[    2.700485 / 01-01 00:00:02.699][1]          12          0          0          0          
<4>[    2.700485 / 01-01 00:00:02.699][1] all_count: 12

인터럽트 처리 상태
현재 인터럽트가 어느 단계로 처리되는지 알고 싶으면 어떤 변수를 참고하면 좋을까요?
이럴 때 인터럽트 디스크립터 struct irq_desc.irq_data.state_use_accessors 멤버를 확인하면 됩니다.

아래 enum 타입 선언부만 봐도 어떤 의미인지 알 수 있을 것 같은데요. 
이 중에서 IRQD_IRQ_INPROGRESS 값이 어떻게 업데이트되는지 점검해보겠습니다.
enum {
IRQD_TRIGGER_MASK = 0xf,
IRQD_SETAFFINITY_PENDING = (1 <<  8),
IRQD_NO_BALANCING = (1 << 10),
IRQD_PER_CPU = (1 << 11),
IRQD_AFFINITY_SET = (1 << 12),
IRQD_LEVEL = (1 << 13),
IRQD_WAKEUP_STATE = (1 << 14),
IRQD_MOVE_PCNTXT = (1 << 15),
IRQD_IRQ_DISABLED = (1 << 16),
IRQD_IRQ_MASKED = (1 << 17),
IRQD_IRQ_INPROGRESS = (1 << 18),  //<<  0x40000
IRQD_WAKEUP_ARMED = (1 << 19),
};

우선 struct irq_desc.irq_data.state_use_accessors 멤버는 irqd_set 함수로 업데이트됩니다.

아래 코드를 살펴보면, 인터럽트 핸들러를 처리하기 전후로 irqd_set 함수가 호출되어struct irq_desc.irq_data.state_use_accessors 멤버 변수 비트를 설정하고 해제합니다.
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;

desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); // //<<-- 
raw_spin_unlock(&desc->lock);

ret = handle_irq_event_percpu(desc, action);

raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); ////<<-- 
return ret;
}

그럼 실제 코어 덤프에서 이 값을 확인해볼까요?
아래는 Trace32로 인터럽트 디스크립터를 올려본 결과인데, 328번 인터럽트 번호에 struct irq_desc.irq_data.state_use_accessors는 0x4002입니다.
 (struct irq_desc *) (struct irq_desc *)0xDF4533C0 = 0xDF4533C0 = __bss_stop+0x1DD59510 -> (
    (struct irq_data) irq_data = (
      (unsigned int) irq = 328 = 0x0148, 
      (long unsigned int) hwirq = 13 = 0x0D, 
      (unsigned int) node = 0 = 0x0,
      (unsigned int) state_use_accessors = 16386 = 0x4002,

위 디버깅 정보를 해석하면 현재 인터럽트 핸들러 내부에서 어떤 코드가 실행되고 있다고 분석할 수 있습니다.

위에서 설명한 정보들은 실제 리눅스 코어 덤프에서 크래시 유틸리티(Crash-Utililty) 프로그램으로 인터럽트 디스크립터 정보를 볼 수 있습니다. 아래는 인터럽트 디스크립터 주소 eb4f3000를 통해 확인해본 각 멤버 변수들 값입니다.

[1]: 인터럽트 번호는 248
[2]: 인터럽트 핸들러는 gpio_keys_gpio_isr
[3]: dev_id는 0xe9505198입니다.
struct irq_desc {
  irq_data = {
    mask = 0,
    irq = 248,  //<<--[1]
    hwirq = 91,
    node = 4294967295,
    state_use_accessors = 3,
    chip = 0xc193646c 
    domain = 0xeb471440,
    parent_data = 0x0,
    handler_data = 0x0,
    chip_data = 0xebbb6a58,
    msi_desc = 0x0,
    affinity = {{
        bits = {255}
      }}
  },
  kstat_irqs = 0xc18d83c4,
  handle_irq = 0xc017b714 <handle_edge_irq>,
  action = 0xe956e400,

crash> struct irqaction 0xe956e400
struct irqaction {
  handler = 0xc07903ac <gpio_keys_gpio_isr>,  //<<--[2]
  dev_id = 0xe9505198, //<<--[3]
  percpu_dev_id = 0x0,
  next = 0x0,
  thread_fn = 0x0,
  thread = 0x0,
  irq = 248,
  flags = 131,
  thread_flags = 0,
  thread_mask = 0,
  name = 0xc342c00c "volume_up",
  dir = 0xe9579540
}

dev_id 주소가 0xe9505198인데, 이 void 타입 주소는 아래 코드에서 struct gpio_button_data 구조체로 캐스팅합니다.
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;

BUG_ON(irq != bdata->irq);

이 값을 크래시 유틸리티로 확인하면 아래와 같습니다.
crash> struct gpio_button_data 0xe9505198
struct gpio_button_data {
  button = 0xe9555418,
  input = 0xe9502840,
  timer = {
    entry = {
      next = 0x0,
      prev = 0x200
    },
    expires = 4294938339,
    base = 0xec0d0040,
    function = 0xc0790394 <gpio_keys_gpio_timer>,
    data = 3914355096,
    slack = -1,
    start_pid = -1,
    start_site = 0x0,
    start_comm = "000000000000000000000000000000"
  },
  work = {
    data = {
      counter = 64
    },
    entry = {
      next = 0xe95051d8,
      prev = 0xe95051d8
    },
    func = 0xc0790d0c <gpio_keys_gpio_work_func>



덧글

댓글 입력 영역