-
[리눅스 커널] PM - SyscoreLinux/kernel 2023. 9. 1. 16:43
글의 참고
- https://elixir.bootlin.com/linux/latest/source/drivers/base/syscore.c#L47
- https://elixir.bootlin.com/linux/latest/source/include/linux/syscore_ops.h#L13
- https://www.cnblogs.com/hellokitty2/p/16225836.html
- https://lore.kernel.org/lkml/201103100133.06734.rjw@sisk.pl/
- http://www.wowotech.net/irq_subsystem/irq_handle_procedure.html
글의 전제
- 밑줄로 작성된 글은 강조 표시를 의미한다.
- 그림 출처는 항시 그림 아래에 표시했다.
글의 내용
- System Core
" 리눅스 커널의 `syscore`는 파워 매니지먼트 동작 관련 프레임워크로 만들어졌다. 그래서 헤더 및 소스 파일이 굉장히 심플한 구조를 갖는다. syscore 는 기본적으로 `모든 인터럽트가 비활성화된 상태 + 하나의 CPU 만 동작중 인 상태` 에서 실행되는 module 다.
From: "Rafael J. Wysocki" <rjw@sisk.pl>
To: LKML <linux-kernel@vger.kernel.org>
....
Some subsystems need to carry out suspend/resume and shutdown operations with one CPU on-line and interrupts disabled. The only way to register such operations is to define a sysdev class and a sysdev specifically for this purpose which is cumbersome and inefficient. Moreover, the arguments taken by sysdev suspend, resume and shutdown callbacks are practically never necessary.
For this reason, introduce a simpler interface allowing subsystems to register operations to be executed very late during system suspend and shutdown and very early during resume in the form of strcut syscore_ops objects.
- 참고 : https://lore.kernel.org/lkml/201103100133.06734.rjw@sisk.pl/- System core device driver
" syscore 로 등록되는 device driver 는 어떤 것들이 있을까? 한 번 상상해보자. 만약, system suspend 에서 막 깨어났을 때, 제일 먼저 해야할 일이 뭘까? 이 시점에 제일 먼저 turn on 되어야 하는 디바이스는 아주 core subsystem 이여야 한다는 것이다. 예를 들어, interrupt, gpio, clock 등이 있다. 커널 v6.5 를 기준으로 bootlin 에 `register_syscore_ops` 라고 검색해보자. 제일 많이 볼 수 있는 device driver 는 위에서 언급한 순서이다.
더보기- drivers/clk/at91/pmc.c, line 187
- drivers/clk/imx/clk-vf610.c, line 465
- drivers/clk/ingenic/pm.c, line 43
- drivers/clk/ingenic/tcu.c, line 489
- drivers/clk/mvebu/common.c, line 287
- drivers/clk/rockchip/clk-rk3288.c, line 976
- drivers/clk/samsung/clk-s5pv210-audss.c, line 177
- drivers/clk/samsung/clk.c, line 328
- drivers/clk/tegra/clk-tegra210.c, line 3816
- drivers/clocksource/timer-armada-370-xp.c, line 328
- drivers/cpuidle/cpuidle-psci.c, line 175
- drivers/gpio/gpio-mxc.c, line 648
- drivers/gpio/gpio-pxa.c, line 801
- drivers/gpio/gpio-sa1100.c, line 287
- drivers/hv/vmbus_drv.c, line 2662
- drivers/iommu/amd/init.c, line 3233
- drivers/iommu/intel/iommu.c, line 3082
- drivers/irqchip/exynos-combiner.c, line 267
- drivers/irqchip/irq-armada-370-xp.c, line 820
- drivers/irqchip/irq-bcm7038-l1.c, line 435
- drivers/irqchip/irq-gic-v3-its.c, line 5655
- drivers/irqchip/irq-i8259.c, line 323
- drivers/irqchip/irq-imx-gpcv2.c, line 279
- drivers/irqchip/irq-loongson-eiointc.c, line 407
- drivers/irqchip/irq-loongson-htpic.c, line 133
- drivers/irqchip/irq-loongson-htvec.c, line 216
- drivers/irqchip/irq-loongson-pch-lpc.c, line 218
- drivers/irqchip/irq-loongson-pch-pic.c, line 315
- drivers/irqchip/irq-mchp-eic.c, line 259
- drivers/irqchip/irq-mst-intc.c, line 170
- drivers/irqchip/irq-mtk-cirq.c, line 282
- drivers/irqchip/irq-sa11x0.c, line 124
- drivers/irqchip/irq-sifive-plic.c, line 545
- drivers/irqchip/irq-stm32-exti.c, line 660
- drivers/irqchip/irq-sun6i-r.c, line 349
- drivers/irqchip/irq-tegra.c, line 194
- drivers/irqchip/irq-vic.c, line 175
- System core process
" syscore 를 분석할 때, 가장 중요한 2 가지 핵심은 다음과 같다.
1. system suspend & resume 시에, syscore devices 들이 처리되는 순서
2. system suspend & resume 시에, syscore callbacks 들이 호출되는 타이밍1. syscore devices 들이 처리되는 순서
" `syscore.h` 파일을 보면, `struct syscore_ops` 구조체 하나만 존재한다. system core device 로 등록할 devices 들은 자신의 driver 에서 `register_syscore_ops()` 함수를 호출해서 커널에 syscore device 로 등록할 수 있다. 이 때, `syscore_ops_list` 에 저장된다. 개인적으로 syscore devices 는 생각보다 많지 않기 때문에, 리스트 자료구조를 이용해서 관리하는 것도 나쁘지 않다고 생각된다. system suspend 프로세스 거의 마지막쯤에, BSP 가 suspend 되기 전에, syscore_suspend() 함수를 호출해서 syscore device 로 등록된 모든 devices 들을 syscore_ops->suspend() callback 함수를 호출한다.
//include/linux/syscore_ops.h - v6.5 /* SPDX-License-Identifier: GPL-2.0-only */ /* * syscore_ops.h - System core operations. * * Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. */ #ifndef _LINUX_SYSCORE_OPS_H #define _LINUX_SYSCORE_OPS_H #include <linux/list.h> struct syscore_ops { struct list_head node; int (*suspend)(void); void (*resume)(void); void (*shutdown)(void); }; extern void register_syscore_ops(struct syscore_ops *ops); extern void unregister_syscore_ops(struct syscore_ops *ops); #ifdef CONFIG_PM_SLEEP extern int syscore_suspend(void); extern void syscore_resume(void); #endif extern void syscore_shutdown(void); #endif
" 전역 변수 `syscore_ops_list` 를 통해서 모든 syscore 디바이스를 저장한다. 등록 및 해제 코드가 너무 단순해서 설명은 생략한다.
//drivers/base/syscore.c - v6.5 // SPDX-License-Identifier: GPL-2.0 /* * syscore.c - Execution of system core operations. * * Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. */ #include <linux/syscore_ops.h> #include <linux/mutex.h> #include <linux/module.h> #include <linux/suspend.h> #include <trace/events/power.h> static LIST_HEAD(syscore_ops_list); static DEFINE_MUTEX(syscore_ops_lock); /** * register_syscore_ops - Register a set of system core operations. * @ops: System core operations to register. */ void register_syscore_ops(struct syscore_ops *ops) { mutex_lock(&syscore_ops_lock); list_add_tail(&ops->node, &syscore_ops_list); mutex_unlock(&syscore_ops_lock); } EXPORT_SYMBOL_GPL(register_syscore_ops); /** * unregister_syscore_ops - Unregister a set of system core operations. * @ops: System core operations to unregister. */ void unregister_syscore_ops(struct syscore_ops *ops) { mutex_lock(&syscore_ops_lock); list_del(&ops->node); mutex_unlock(&syscore_ops_lock); } EXPORT_SYMBOL_GPL(unregister_syscore_ops);
" `syscore` 는 팩토리 메소드 패턴형식으로 모든 syscore 콜백에서 디바이스들이 순회하면서 별도로 구현한 함수들을 호출하고 있다. 그런데, 루프문을 보면 `reverse` 로 되어있다. 즉, `syscore_ops_list` 리스트를 뒤에서 부터 꺼내온다는 것인데, 이 내용은 디바이스의 계층 관계에 대한 기본적인 내용을 알고 있어야 한다.
" `register_syscore_ops` 함수에서 `list_add_tail` 함수를 사용해서 새로운 `syscore` 디바이스를 뒤쪽에 추가시킨다. 이 말은, `syscore->suspend` 는 새롭게 추가되는 디바이스부터 먼저 호출된다는 것을 의미한다. 왜 그럴까? 한 번 생각해보자. PCI Bus Bridge 0 이 PCI Bus 0 과 PCI Bus 1 의 Bridge 역할을 한다고 가정하자. 그리고, PCI Bus Bridge 0 은 PCI Bus 1 을 관리한다고 볼 수 있다. 이러한 상황에서 PCI Bus Bridge 0 에 device driver 가 초기화 되지 않았는데, SCSI HBA device 를 초기화할 수 있을까? 불가능하다. SCSI HBA 에 access 하기 위해서는 최소 PCI Host Bridge 와 PCI Bus Bridge 0 의 초기화가 정상적으로 완료되 있어야 한다. 즉, 이 말은 즉, 상위 계층 디바이스는 반드시 하위 계층 디바이스보다 먼저 On 및 초기화가 되어야 있어야 한다는 뜻이다.
" 그렇다면, 위 block diagram 을 기준으로 리눅스 커널이 system suspend 를 시작한다고 하면, 어떤 디바이스부터 먼저 suspend 되어야 할까? 만약, PCI Bus Bridge 0 부터 suspended 되면, 그 아래있는 모든 PCI devices(SCSI HBA, PCI Bus Bridge 1, Ethernet, Cortex-A) 에는 접근조차 할 수 없게 된다. 결론적으로, syscore_ops_list 의 앞쪽에 저장되어 있을 수 록, 버스 컨트롤러, clock controller, gpio controller, interrupt controller 등 core subsystem drivers 들일 확률이 높다.
2. syscore callbacks 들이 호출되는 타이밍
" syscore 에서 두 번째로 중요한 내용은 `syscore callbacks 들이 호출되는 타이밍` 이다.
// drivers/base/syscore.c - v6.5 #ifdef CONFIG_PM_SLEEP /** * syscore_suspend - Execute all the registered system core suspend callbacks. * * This function is executed with one CPU on-line and disabled interrupts. */ int syscore_suspend(void) { struct syscore_ops *ops; int ret = 0; .... WARN_ONCE(!irqs_disabled(), "Interrupts enabled before system core suspend.\n"); /* Return error code if there are any wakeup interrupts pending. */ if (pm_wakeup_pending()) return -EBUSY; .... list_for_each_entry_reverse(ops, &syscore_ops_list, node) if (ops->suspend) { .... ret = ops->suspend(); if (ret) goto err_out; WARN_ONCE(!irqs_disabled(), "Interrupts enabled after %pS\n", ops->suspend); } .... return 0; err_out: pr_err("PM: System core suspend callback %pS failed.\n", ops->suspend); list_for_each_entry_continue(ops, &syscore_ops_list, node) if (ops->resume) ops->resume(); return ret; } #endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM_SLEEP // drivers/base/syscore.c - v6.5 /** * syscore_resume - Execute all the registered system core resume callbacks. * * This function is executed with one CPU on-line and disabled interrupts. */ void syscore_resume(void) { struct syscore_ops *ops; .... list_for_each_entry(ops, &syscore_ops_list, node) if (ops->resume) { pm_pr_dbg("Calling %pS\n", ops->resume); ops->resume(); WARN_ONCE(!irqs_disabled(), "Interrupts enabled after %pS\n", ops->resume); } trace_suspend_resume(TPS("syscore_resume"), 0, false); } #endif /* CONFIG_PM_SLEEP */
// drivers/base/syscore.c - v6.5 /** * syscore_shutdown - Execute all the registered system core shutdown callbacks. */ void syscore_shutdown(void) { struct syscore_ops *ops; mutex_lock(&syscore_ops_lock); list_for_each_entry_reverse(ops, &syscore_ops_list, node) if (ops->shutdown) { if (initcall_debug) pr_info("PM: Calling %pS\n", ops->shutdown); ops->shutdown(); } mutex_unlock(&syscore_ops_lock); }
" syscore callbakcs 함수들이 호출되기 위한 조건은 반드시 2 가지가 충족되어야 한다.
1. Boot-CPU 만 동작하고 있어야 한다. 즉, non-Boot CPUs 들은 Off 및 Sleep 상태여야 한다(CPU architecture 마다 다르다. arm 같은 경우, PSCI 에서 non-Boot CPUs 들은 모두 Off 다. x86 같은 경우는 ACPI 를 찾아보자).
2. local CPU interrupts 들이 모두 disabled 상태여야 한다." 그래서 syscore_resume 및 syscore_suspend 함수안에서 irqs_disabled() 함수를 통해 인터럽트 활성화여부를 판단하는 것을 볼 수 있다. 그리고, 모든 syscore devices 들의 ops->suspend() 함수가 호출되는 도중에도 interrupt 가 다시 enabled 될 수 있기 때문에, 루프문 안에서도 interrup disabled 여부를 검사한다. 이 말은 반드시 irq-safe 및 scheduler 에 의한 선점등이 없는 안전한 상황에서 suspend 및 resume이 되어야 하는 devices 들은 syscore devices 로 에 등록해야 한다는 것이다.
" 이 밖에 다른 특징으로는 syscore->suspend 는 core subsystem devices 들이기 때문에, 모든 external peripherals suspend 가 호출된 후에 호출된다. 즉, syscore devices 들을 제외한 시스템에 존재하는 모든 deivces 및 peripehrals 들이 suspended 가 되고나서 처리해야 할 일이 있다거나, 자식 및 하위 devices 들이 제대로 suspended 가 되었는지 알아보기 위해 사용할 수 도 있다.
" 아래는 `system-wide suspend` 마지막 과정인 `suspend_enter` 함수다. 여기서 `syscore_suspend` 함수가 어느 타이밍에 호출되는지를 눈여겨 보자.
1. non-boot CPU`s 종료 - pm_sleep_disable_secondary_cpus()
2. boot-CPU 인터럽트 비활성화 - arch_suspend_disable_irqs()// kernel/power/suspend.c - v6.5 static int suspend_enter(suspend_state_t state, bool *wakeup) { .... error = pm_sleep_disable_secondary_cpus(); .... arch_suspend_disable_irqs(); .... system_state = SYSTEM_SUSPEND; .... error = syscore_suspend(); if (!error) { *wakeup = pm_wakeup_pending(); if (!(suspend_test(TEST_CORE) || *wakeup)) { .... error = suspend_ops->enter(state); .... } else if (*wakeup) { error = -EBUSY; } syscore_resume(); } ... }
" `syscore_suspend` 호출되기 전 앞에서 이미 boot-CPU 만 on-line이고, 인터럽트 또한 disabled 되어있는 것을 확인할 수 있다. 즉, 이 시점에는 SoC 외부의 주변 장치들은 suspended 상태가 되었다고 볼 수 있다. syscore_suspend 함수에서는 SoC 내부의 코어 모듈들을 suspended 상태로 진입시킨다.
'Linux > kernel' 카테고리의 다른 글
[리눅스 커널] Cpuidle - overview & data structure (0) 2023.09.04 [리눅스 커널] Facilities & Helper (0) 2023.09.02 [리눅스 커널] PM - Freezing of task (0) 2023.09.01 [리눅스 커널] PM - Driver Power Management (0) 2023.08.30 [리눅스 커널] PM - Wakeup source (0) 2023.08.30