Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

175162
807
85243


[Linux][Kernel] 인터럽트(Interrupt) - request_threaded_irq overview(1) [Linux][Kernel] IRQ(Interrupt)

리눅스 커널 인터럽트 개념 중에 top/bottom half란 용어를 많이 들어봤죠?

하드웨어적으로 인터럽트가 처리될 때는 되도록 실시간으로 처리할 데이터만 빨리 처리하고,
빨리 처리해도 안되는 동작은 쓰레드 레벨로 수행한다는 거죠.

수 없이 이런 글을 읽어봤는데, 사실 인터럽트 서비스 루틴에서 발생하는 수 많은 이슈를 대응하기 전까지는 개념이머리 속에 잘 들어오지 않더라구요. 그래서 이번 시간에 좀 좀 정리를 해보려고 해요.

리눅스 커널에서 bottom half로 처리되는 루틴은 크게 세 가지가 있어요.
1> softirq
2> workqueue
3> irq_thread

이번 시간에는 irq_thread에 대해서 touch 드라이버를 예를 들어서 분석을 해보려구요.
touch의 IRQ 관련 루틴은 두 조각으로 크게 나눌 수가 있어요.

인터럽트가 들어왔을 때 즉, IRQ Context에서는 빠른 처리를 해야 하는 코드, 조금 나중에 처리해도 되는 루틴은 IRQ thread 배치하는 거죠

IRQ Context 즉 인터럽트가 올라왔을 때 터치 이벤트를 input event로 유저 공간에 올려주는 짓거리를 하면 어떻게 될까요?
시스템이 이상한 짓 거리를 하면서 와치독으로 리셋 될 꺼에요.

이렇게요. request_irq()를 통해 ISR에서 input event를 전달했더니, 커널은 아래와 같은 유언을 남기고 뻗어 버렸어요.
[   21.719319 01-01 10:20:37.054] [callstack mxt_interrupt,2449] task[InputReader]========= 
[   21.719382 01-01 10:20:37.054] BUG: scheduling while atomic: InputReader/1039/0x00010001
[   21.719417 01-01 10:20:37.054] [<c00169a8>] (unwind_backtrace+0x0/0x144) from [<c083e0e8>] (dump_stack+0x20/0x24)
[   21.719432 01-01 10:20:37.054] [<c083e0e8>] (dump_stack+0x20/0x24) from [<c083edfc>] (__schedule_bug+0x50/0x5c)
[   21.719444 01-01 10:20:37.054] [<c083edfc>] (__schedule_bug+0x50/0x5c) from [<c0845b54>] (__schedule+0x7c4/0x890)
[   21.719455 01-01 10:20:37.054] [<c0845b54>] (__schedule+0x7c4/0x890) from [<c0845d70>] (schedule+0x40/0x80)
[   21.719468 01-01 10:20:37.054] [<c0845d70>] (schedule+0x40/0x80) from [<c0843bc0>] (schedule_timeout+0x190/0x33c)
[   21.719480 01-01 10:20:37.054] [<c0843bc0>] (schedule_timeout+0x190/0x33c) from [<c08450b4>] (wait_for_common+0xb8/0x15c)
[   21.719491 01-01 10:20:37.054] [<c08450b4>] (wait_for_common+0xb8/0x15c) from [<c0845198>] (wait_for_completion_timeout+0x1c/0x20)
[   21.719504 01-01 10:20:37.054] [<c0845198>] (wait_for_completion_timeout+0x1c/0x20) from [<c04dbd88>] (tegra_i2c_xfer_msg+0x380/0x958)
[   21.719517 01-01 10:20:37.054] [<c04dbd88>] (tegra_i2c_xfer_msg+0x380/0x958) from [<c04dc674>] (tegra_i2c_xfer+0x314/0x438)
[   21.719531 01-01 10:20:37.054] [<c04dc674>] (tegra_i2c_xfer+0x314/0x438) from [<c04d7bb8>] (i2c_transfer+0xc4/0x128)
[   21.719546 01-01 10:20:37.054] [<c04d7bb8>] (i2c_transfer+0xc4/0x128) from [<c04b3174>] (__mxt_read_reg+0x70/0xc8)
[   21.719560 01-01 10:20:37.054] [<c04b3174>] (__mxt_read_reg+0x70/0xc8) from [<c04b6f60>] (mxt_read_and_process_messages+0x58/0x1648)
[   21.719572 01-01 10:20:37.054] [<c04b6f60>] (mxt_read_and_process_messages+0x58/0x1648) from [<c04b85c8>] (mxt_interrupt+0x78/0x144)
[   21.719588 01-01 10:20:37.054] [<c04b85c8>] (mxt_interrupt+0x78/0x144) from [<c00e8000>] (handle_irq_event_percpu+0x88/0x2ec)
[   21.719601 01-01 10:20:37.054] [<c00e8000>] (handle_irq_event_percpu+0x88/0x2ec) from [<c00e82b0>] (handle_irq_event+0x4c/0x6c)
[   21.719614 01-01 10:20:37.054] [<c00e82b0>] (handle_irq_event+0x4c/0x6c) from [<c00ead98>] (handle_level_irq+0xbc/0x118)
[   21.719626 01-01 10:20:37.054] [<c00ead98>] (handle_level_irq+0xbc/0x118) from [<c00e7780>] (generic_handle_irq+0x3c/0x50)
[   21.719642 01-01 10:20:37.054] [<c00e7780>] (generic_handle_irq+0x3c/0x50) from [<c0311138>] (tegra_gpio_irq_handler+0xa8/0x124)
[   21.719655 01-01 10:20:37.054] [<c0311138>] (tegra_gpio_irq_handler+0xa8/0x124) from [<c00e7780>] (generic_handle_irq+0x3c/0x50)
[   21.719669 01-01 10:20:37.054] [<c00e7780>] (generic_handle_irq+0x3c/0x50) from [<c000fa04>] (handle_IRQ+0x5c/0xbc)
[   21.719682 01-01 10:20:37.054] [<c000fa04>] (handle_IRQ+0x5c/0xbc) from [<c0008504>] (gic_handle_irq+0x34/0x68)
[   21.719694 01-01 10:20:37.054] [<c0008504>] (gic_handle_irq+0x34/0x68) from [<c000ec40>] (__irq_svc+0x40/0x70)


혹시 다니는 회사가 특근비 많이 주면 한번 이렇게 코드를 짜보세요. 이슈 빵빵 터질 께 분명하거든요. 주말에 나와서 실컷 디버깅할 수 있을 꺼에요. 1달 동안 디버깅 하다가 input event는 irq thread에서 처리하는 코드로 수정하시면 되요.

probe로 등록된 touch_drvier_probe() 함수에서 아래 타입으로 등록을 합니다.
인터럽트가 떴을 때 호출되는 touch_irq_handler 핸들러와 irq_thread로 호출되는 함수 touch_irq_thread를 등록하죠.
인터럽트 핸들러도 전달되는 device id 포인터는 struct touch_core_data *ts로 지정하네요.
static int touch_drvier_probe(struct platform_device *pdev)
{
struct touch_core_data *ts;
int ret;

ret = request_threaded_irq(irq, touch_irq_handlertouch_irq_thread
ts->irqflags | IRQF_ONESHOT, "touch", ts);

그럼, 인터럽트 descriptor 변수 중 action(struct irq_desc->action)에 아래와 같이 등록을 하죠.
참고로, 인터럽트 descriptor는 struct irq_desc 타입인데요. 각각 인터럽트에 대한 정보를 담고 있어요.
page descriptor와 비슷하죠.
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;  // touch_irq_handler
action->thread_fn = thread_fn;  // touch_irq_thread
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;  // ts

이제, touch_irq_thread가 언제 어떻게 호출되는지 살펴볼까요?
우선 해당 IRQ가 Trigger되어야 하구요. IRQ가 Trigger되면 아래 함수 handle_irq_event_percpu가 호출되거든요.

아래 디버깅 정보는 touch_irq_handler() 함수에 stack trace를 걸고 받아 본 ftrace 로그 중 일부인데요.
 => touch_irq_handler
 => handle_irq_event_percpu
 => handle_irq_event
 => handle_fasteoi_irq
 => generic_handle_irq
 => __handle_domain_irq
 => gic_handle_irq
 => __irq_svc
 => lpm_cpuidle_enter
 => lpm_cpuidle_enter
 => cpuidle_enter_state
 => cpuidle_enter
 => cpu_startup_entry
 => secondary_start_kernel
 => 

아래 코드의 [1] action->handler(irq, action->dev_id); 코드에서 해당 ISR인  touch_irq_handler이 호출되구요.
리턴 값이 [2] IRQ_WAKE_THREAD이면  __irq_wake_thread() 함수 호출로 IRQ Thread를 Runqueue에 넣어줘서 쓰레드 레벨에서 실행되게 해요.
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;

do {
irqreturn_t res;

trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);  //<<--[1]
trace_irq_handler_exit(irq, action, res);

if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
      irq, action->handler))
local_irq_disable();

switch (res) {
case IRQ_WAKE_THREAD:
/*
 * Catch drivers which return WAKE_THREAD but
 * did not set up a thread function
 */
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}

__irq_wake_thread(desc, action);  //<<--[2]

touch_irq_handler 코드를 잠깐 보면 역시나 IRQ_WAKE_THREAD 리턴 하네요.
흠,  IRQ_HANDLED로 리턴할 때는 Suspend 상태라 터치 이벤트를 처리하고 싶지 않은가 봐요.
코드를 보고 유추할 수 있는 점은 이 터치 드라이버는 Platform Device로 등록되어 처리되는 것 같네요.
irqreturn_t touch_irq_handler(int irq, void *dev_id)
{
struct touch_core_data *ts = (struct touch_core_data *) dev_id;

if (atomic_read(&ts->state.pm) >= DEV_PM_SUSPEND) {
TOUCH_I("interrupt in suspend[%d]\n",
atomic_read(&ts->state.pm));
atomic_set(&ts->state.pm, DEV_PM_SUSPEND_IRQ);
wake_lock_timeout(&ts->lpwg_wake_lock, msecs_to_jiffies(1000));
return IRQ_HANDLED;
    }
return IRQ_WAKE_THREAD;
}


역시 wake_up_process()를 호출해서 IRQ 쓰레드를 Runqueue에 넣어주죠. 실행 되라구요.
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
if (action->thread->flags & PF_EXITING)
return;
if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))
return;
desc->threads_oneshot |= action->thread_mask;
atomic_inc(&desc->threads_active);

wake_up_process(action->thread);  //<<--
}

그럼 정확히 어느 함수에서 IRQ 쓰레드를 Runqueue에 넣어 주냐구요?
아래 함수에요. 친절히 런큐에 넣어주고 프로세스 상태를 TASK_RUNNING로 바꾸어 주네요.
함수 흐름은? (wake_up_process -> try_to_wake_up -> ttwu_remote -> ttwu_do_wakeup) 이렇죠.
static void
ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
check_preempt_curr(rq, p, wake_flags);
trace_sched_wakeup(p, true);

p->state = TASK_RUNNING;
이제 irq_thread가 실행될 차례에요. 
[1] 루틴과 같이 handler_fn에 irq_thread_fn 함수로 함수 포인터를 지정하고,
[2] 루틴에서 해당 touch_irq_thread irq_thread 핸들 함수가 호출되는 거죠. 
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action);

if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
&action->thread_flags))
handler_fn = irq_forced_thread_fn;
else
handler_fn = irq_thread_fn;  //<<--[1]

init_task_work(&on_exit_work, irq_thread_dtor);
task_work_add(current, &on_exit_work, false);

irq_thread_check_affinity(desc, action);

while (!irq_wait_for_interrupt(action)) {
irqreturn_t action_ret;

irq_thread_check_affinity(desc, action);

action_ret = handler_fn(desc, action);  //<<--[2]

콜 트레이스는 어떤 흐름일까요? T32로 봤더니 아래와 같네요.
-011|owner_running(inline)
-011|mutex_spin_on_owner()
-012|__mutex_lock_common(inline)
-012|__mutex_lock_slowpath()
-013|current_thread_info(inline)
-013|mutex_set_owner(inline)
-013|mutex_lock()
-014|himax_bus_read()
-015|himax_read_event_stack()
-016|himax_touch_get()
-017|himax_irq_handler()
-018|touch_irq_thread()
-019|irq_thread_fn(inline)
-019|irq_thread()
-020|kthread()
-021|ret_from_fork(asm)
-022|ret_fast_syscall(asm)

"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
그리고 혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려드리겠습니다!"

5장 인터럽트 핸들링
5.7 인터럽트 디버깅
   5.7.3 인터럽트 핸들러 함수 파악하기
   5.7.4 인터럽트 핸들러 실행 시간 점검하기



#Reference 시스템 콜


Reference(워크큐)
워크큐(Workqueue) Overview


덧글

  • 김인용 2014/11/22 23:19 # 삭제 답글

    안녕하세요 request_thread_irq로 등록 시 따로 irq를 확인하는 것 없이 default primary handlerㄱㅏ 실행 되는 것으로 보이는데
    굳이 전혀 아무 역할도 하지 않는 handler를 사용하는 이유를 알 수가 없네요 ㅠㅠ 혹시 아시는지요 ?
  • 애클슨 2015/04/03 16:12 # 삭제 답글

    좋은 글 감사합니다. 도움이 많이 되고있습니다.
  • 애클슨 2015/04/03 16:13 # 삭제 답글

    좋은 글 감사합니다. 도움이 많이 되고있습니다.
댓글 입력 영역