ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] PM - Syscore
    Linux/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://patchwork.kernel.org/project/linux-mediatek/patch/1598943859-21857-2-git-send-email-claude.yen@mediatek.com/

    - https://linux.kernel.narkive.com/hcQQZHlP/rfc-patch-1-2-acpi-use-syscore-instead-of-pm-power-off-prepare-to-prepare-for-poweroff

    - 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 는 위에서 언급한 순서이다.

    더보기

     

     

     

    - 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 상태로 진입시킨다.

Designed by Tistory.