열 관리 (Thermal Management)

Linux 커널 Thermal 서브시스템은 CPU/GPU/SoC 센서 온도를 기반으로 thermal zone, trip point, cooling map을 구성하고 cooling device를 제어해 과열을 방지합니다. step_wise/fair_share/power_allocator governor의 제어 전략, cpufreq/devfreq·fan·throttle 연동, ACPI·Device Tree·hwmon 통합, 열 한계와 성능 사이의 정책 설계, 운영 환경에서의 throttling 원인 분석과 디버깅(Debugging) 절차를 상세히 다룹니다.

전제 조건: 전원관리hwmon 문서를 먼저 읽으세요. Thermal 서브시스템은 온도 센서(hwmon)와 전력 제어(PM/cpufreq)를 연결하는 중간 레이어이므로, 양쪽 개념을 이해해야 전체 동작을 파악할 수 있습니다.
일상 비유: Thermal Management는 에어컨의 자동 온도 조절 시스템과 비슷합니다. 온도 센서(thermal zone)가 현재 실내 온도를 측정하고, 설정한 온도 임계값(trip point)을 초과하면 제어 알고리즘(thermal governor)이 냉각 장치(cooling device: 에어컨 압축기, 팬 속도)를 자동으로 조절합니다.

핵심 요약

  • Thermal Zone — CPU, GPU 등 온도를 측정하는 하드웨어 영역. 온도 센서와 trip point를 포함합니다.
  • Trip Point — 온도 임계값. active, passive, hot, critical 4가지 타입으로 cooling 정책을 다르게 트리거합니다.
  • Cooling Device — 온도를 낮추는 장치. CPU 주파수 제한, 팬 속도 증가, GPU throttling 등.
  • Thermal Governor — thermal zone과 cooling device를 연결하는 정책 알고리즘. step_wise, power_allocator 등.
  • 제어 루프 — 온도 샘플링 → trip 비교 → governor 판단 → cooling 조정 → 온도 하강 → 반복.

단계별 이해

  1. Thermal Zone 확인
    /sys/class/thermal/thermal_zone*/에서 현재 시스템의 온도 센서와 trip point를 확인합니다.
  2. Cooling Device 파악
    /sys/class/thermal/cooling_device*/에서 사용 가능한 cooling 수단과 상태 범위를 확인합니다.
  3. Governor 정책 이해
    step_wise, power_allocator 등 governor 알고리즘이 온도에 따라 cooling을 어떻게 조정하는지 학습합니다.
  4. Tracing으로 동작 추적
    thermal tracepoint를 활성화하여 온도 변화와 cooling 동작을 실시간(Real-time)으로 관찰합니다.
관련 문서: ACPI (ACPI Thermal Zone), hwmon (온도 센서), 전원 관리(Power Management) (DVFS 통합), CPU 토폴로지(Topology) (코어별 온도)

개요

Thermal Management 서브시스템은 하드웨어 과열을 방지하고 성능과 전력 소비를 균형있게 조절하는 커널의 핵심 컴포넌트입니다.

Thermal Zone temp / trip point Thermal Governor step_wise / power_allocator Cooling Device cpufreq / fan / devfreq 하드웨어 상태 온도 하강 / 성능 변화

주요 구성 요소

아키텍처

User Space /sys/class/thermal/thermal_zone* | /sys/class/thermal/cooling_device* Thermal Framework Core thermal_zone_device_register() thermal_cooling_device_register() | thermal_zone_device_update() Thermal Zone Thermal Governor Cooling Device Hardware Drivers ACPI Thermal | hwmon | CPU Freq

Thermal Zone

thermal_zone_device 구조체(Struct)

struct thermal_zone_device {
    int id;
    char type[THERMAL_NAME_LENGTH];
    struct device device;
    struct thermal_zone_device_ops *ops;
    const struct thermal_zone_params *tzp;
    struct thermal_governor *governor;
    struct list_head thermal_instances;  /* cooling device 바인딩 */
    int temperature;           /* 현재 온도 (milli-Celsius) */
    int last_temperature;      /* 이전 측정 온도 */
    int polling_delay;         /* 폴링 주기 (msec) */
    int passive;               /* passive cooling 활성화 */
    bool forced_passive;
    struct thermal_zone_device_ops *ops;
    struct list_head node;
};
코드 설명
  • id, typethermal zone의 고유 식별자와 이름입니다. typeTHERMAL_NAME_LENGTH(20) 이하의 문자열로, sysfs에서 /sys/class/thermal/thermal_zone0/type으로 노출됩니다.
  • ops온도 읽기, trip point 조회, cooling device 바인딩 등 thermal zone이 지원하는 콜백 함수 테이블입니다. include/linux/thermal.h에 정의됩니다.
  • governor이 thermal zone에 연결된 cooling 정책 governor입니다. step_wise, power_allocator 등이 할당됩니다.
  • thermal_instances이 zone에 바인딩된 cooling device 목록을 연결 리스트로 관리합니다. drivers/thermal/thermal_core.c에서 bind 콜백 호출 시 추가됩니다.
  • temperature, last_temperature현재와 이전 측정 온도(milli-Celsius 단위)입니다. governor가 온도 변화 방향을 판단할 때 두 값을 비교합니다.
  • polling_delay온도 폴링 주기(밀리초)입니다. 0이면 인터럽트 기반으로 동작하며, passive 상태에서는 별도의 passive_delay가 적용됩니다.

thermal_zone_device_ops

struct thermal_zone_device_ops {
    int (*bind)(struct thermal_zone_device *,
                  struct thermal_cooling_device *);
    int (*unbind)(struct thermal_zone_device *,
                    struct thermal_cooling_device *);
    int (*get_temp)(struct thermal_zone_device *, int *temp);
    int (*set_trips)(struct thermal_zone_device *, int, int);
    int (*get_mode)(struct thermal_zone_device *,
                     enum thermal_device_mode *);
    int (*set_mode)(struct thermal_zone_device *,
                     enum thermal_device_mode);
    int (*get_trip_type)(struct thermal_zone_device *, int,
                           enum thermal_trip_type *);
    int (*get_trip_temp)(struct thermal_zone_device *, int, int *);
    int (*get_crit_temp)(struct thermal_zone_device *, int *);
    int (*notify)(struct thermal_zone_device *, int,
                   enum thermal_trip_type);
};
코드 설명
  • bind / unbindthermal zone과 cooling device를 연결·해제하는 콜백입니다. zone 등록 시 커널이 모든 cooling device에 대해 bind를 호출하여 적합한 device를 연결합니다.
  • get_temp현재 온도를 milli-Celsius 단위로 읽어오는 콜백입니다. 센서 드라이버가 반드시 구현해야 하며, drivers/thermal/thermal_core.c의 폴링 루프에서 주기적으로 호출됩니다.
  • set_trips하드웨어 인터럽트 기반 센서에서 상한·하한 온도를 설정합니다. 폴링 없이 온도가 범위를 벗어날 때 IRQ로 알림받는 방식에 사용됩니다.
  • get_trip_type / get_trip_temptrip point의 유형(THERMAL_TRIP_PASSIVE, THERMAL_TRIP_CRITICAL 등)과 임계 온도를 조회합니다. governor가 cooling 판단 시 사용합니다.
  • get_crit_temp시스템 강제 종료 임계 온도를 반환합니다. 이 온도에 도달하면 orderly_poweroff()가 호출됩니다.
  • notifytrip point가 트리거될 때 호출되는 알림 콜백입니다. 드라이버가 추가 조치(로깅, 알람 등)를 수행할 수 있습니다.

Trip Points (온도 임계값)

Trip point는 특정 온도에 도달했을 때 cooling action을 트리거하는 임계값입니다.

Trip Type 설명 동작
THERMAL_TRIP_ACTIVE 능동적 냉각 시작 팬 속도 증가, 주파수 감소
THERMAL_TRIP_PASSIVE 수동적 냉각 시작 CPU 주파수 제한, GPU throttling
THERMAL_TRIP_HOT 고온 경고 로그 기록, 경고 알림
THERMAL_TRIP_CRITICAL 임계 온도 시스템 강제 종료 (orderly_poweroff)

Thermal Zone 등록

struct thermal_zone_device *
thermal_zone_device_register(
    const char *type,
    int trips,
    int mask,
    void *devdata,
    struct thermal_zone_device_ops *ops,
    struct thermal_zone_params *tzp,
    int passive_delay,
    int polling_delay
);

/* 예: ACPI Thermal Zone 등록 */
struct thermal_zone_device *tz;
tz = thermal_zone_device_register("acpitz", 4, 0xf, tz_data,
                                     &acpi_thermal_zone_ops,
                                     &acpi_thermal_params,
                                     10000, 0);
코드 설명
  • typethermal zone의 이름 문자열입니다. 예시에서 "acpitz"는 ACPI thermal zone을 나타내며, sysfs의 type 파일로 확인할 수 있습니다.
  • trips이 zone이 가진 trip point의 개수입니다. 예시에서 4는 active, passive, hot, critical 4개의 trip을 의미합니다.
  • maskuserspace에서 수정 가능한 trip point를 비트마스크로 지정합니다. 0xf는 4개의 trip 모두 sysfs를 통해 온도 조정이 가능함을 뜻합니다.
  • ops, tzp온도 읽기·trip 관리 콜백 테이블과 governor 파라미터(PID 게인 등)를 전달합니다. drivers/thermal/thermal_core.c에서 이 정보로 zone을 초기화합니다.
  • passive_delay, polling_delaypassive cooling 활성화 시와 일반 상태의 온도 폴링 주기(밀리초)입니다. 예시에서 10000은 10초마다 폴링, 0은 일반 상태에서 폴링 비활성(인터럽트 기반)을 의미합니다.

Cooling Device

thermal_cooling_device 구조체

struct thermal_cooling_device {
    int id;
    char type[THERMAL_NAME_LENGTH];
    struct device device;
    struct thermal_cooling_device_ops *ops;
    bool updated;
    unsigned long max_state;  /* 최대 cooling level */
    unsigned long cur_state;  /* 현재 cooling level */
    struct list_head node;
};
코드 설명
  • id, typecooling device의 고유 번호와 이름입니다. type은 sysfs에서 /sys/class/thermal/cooling_device0/type으로 확인할 수 있습니다 (예: "thermal-cpufreq-0", "Fan").
  • opscooling 상태를 조회·설정하는 콜백 함수 테이블입니다. governor가 이 ops를 통해 cooling level을 제어합니다.
  • max_state, cur_statecooling device가 지원하는 최대 레벨과 현재 레벨입니다. 0이 냉각 없음, max_state가 최대 냉각을 의미합니다. CPUFreq cooling의 경우 state가 높을수록 낮은 주파수에 대응합니다.
  • updatedcooling state가 변경되었는지 표시하는 플래그입니다. governor가 새 state를 설정한 후 true로 설정하며, 실제 적용 시 초기화됩니다.

thermal_cooling_device_ops

struct thermal_cooling_device_ops {
    int (*get_max_state)(struct thermal_cooling_device *,
                         unsigned long *);
    int (*get_cur_state)(struct thermal_cooling_device *,
                         unsigned long *);
    int (*set_cur_state)(struct thermal_cooling_device *,
                         unsigned long);
    int (*get_requested_power)(struct thermal_cooling_device *,
                                struct thermal_zone_device *, u32 *);
    int (*state2power)(struct thermal_cooling_device *,
                        struct thermal_zone_device *,
                        unsigned long, u32 *);
    int (*power2state)(struct thermal_cooling_device *,
                        struct thermal_zone_device *, u32,
                        unsigned long *);
};
코드 설명
  • get_max_statecooling device가 지원하는 최대 cooling level을 반환합니다. CPUFreq cooling의 경우 가용 주파수 테이블의 단계 수에서 1을 뺀 값입니다.
  • get_cur_state / set_cur_state현재 cooling level을 조회하거나 설정합니다. governor가 온도에 따라 set_cur_state를 호출하여 냉각 강도를 조절합니다. include/linux/thermal.h에 정의됩니다.
  • get_requested_powerIPA(Power Allocator) governor 전용 콜백으로, cooling device의 현재 소비 전력(mW)을 반환합니다. 이 값으로 전력 예산 분배를 계산합니다.
  • state2power / power2statecooling state와 전력(mW) 간 양방향 변환 콜백입니다. IPA가 전력 예산을 cooling state로 변환할 때 사용합니다. drivers/thermal/cpufreq_cooling.c에서 CPU 전력 모델 기반으로 구현됩니다.

Cooling Device 종류

Cooling Device 등록

/* CPUFreq Cooling Device 등록 */
struct thermal_cooling_device *cdev;
struct cpumask clip_cpus;

cpumask_set_cpu(0, &clip_cpus);
cdev = cpufreq_cooling_register(&clip_cpus);

/* Fan Cooling Device 등록 */
struct thermal_cooling_device *fan_cdev;
fan_cdev = thermal_cooling_device_register("Fan", fan_data,
                                              &fan_cooling_ops);

Thermal Governor

Thermal Governor는 thermal zone의 온도에 따라 cooling device를 제어하는 정책 알고리즘입니다.

Governor 종류

Governor 설명 적용 대상
step_wise 온도 변화에 따라 단계적으로 cooling state 조정 범용 (기본값)
fair_share 여러 cooling device에 cooling 부하를 공평하게 분배 다중 cooling device
power_allocator 전력 예산 기반 제어. PID 컨트롤러 사용 모바일 SoC, ARM big.LITTLE
bang_bang On/Off 방식. 임계값 초과 시 full cooling 단순 Fan 제어
user_space userspace 데몬이 cooling 제어 커스텀 열 정책

Step-Wise Governor (기본)

step_wise governor는 온도가 trip point를 초과하면 cooling state를 한 단계씩 증가시키고, 온도가 낮아지면 한 단계씩 감소시킵니다.

/* Step-Wise Governor 알고리즘 */
if (temperature > trip_temp) {
    /* 온도 상승: cooling state 증가 */
    new_state = cur_state + 1;
    if (new_state > max_state)
        new_state = max_state;
} else {
    /* 온도 하강: cooling state 감소 */
    if (cur_state > 0)
        new_state = cur_state - 1;
}
set_cur_state(cdev, new_state);
Step-Wise 온도 구간별 상태 전이 < trip_low state 감소 trip_low ~ trip 유지 (히스테리시스) > trip_temp state +1 증가 >> trip_temp max_state 직행 state 0 state N (유지) state N+1 max_state trip_low = trip_temp - hysteresis 온도 상승 시 +1씩, 하강 시 -1씩 (급격한 변화 방지)

Fair-Share Governor 알고리즘

/* Fair-Share: 각 cooling device에 가중치 비례로 cooling 분배 */
/* 온도 초과 비율 계산 */
percentage = (cur_temp - trip_temp) * 100 / (max_temp - trip_temp);
if (percentage > 100)
    percentage = 100;

/* 각 cooling device에 가중치 기반 state 할당 */
for (i = 0; i < tz->cdevs_count; i++) {
    weight = tz->cdevs_weight[i];
    total_weight += weight;
}

for (i = 0; i < tz->cdevs_count; i++) {
    cdev = tz->cdevs[i];
    weight_pct = tz->cdevs_weight[i] * 100 / total_weight;
    target_state = cdev->max_state * percentage * weight_pct / 10000;
    set_cur_state(cdev, target_state);
}

Power Allocator Governor

ARM 모바일 SoC에서 널리 사용됩니다. PID(Proportional-Integral-Derivative) 컨트롤러를 사용해 전력 예산을 동적으로 할당합니다.

struct thermal_zone_params {
    char governor_name[THERMAL_NAME_LENGTH];
    bool no_hwmon;
    int num_tbps;
    struct thermal_bind_params *tbp;

    /* Power Allocator 전용 */
    s32 k_po;           /* PID proportional gain (overshoot) */
    s32 k_pu;           /* PID proportional gain (undershoot) */
    s32 k_i;            /* PID integral gain */
    s32 k_d;            /* PID derivative gain */
    s32 integral_cutoff;
    int sustainable_power; /* 지속 가능 전력 (mW) */
};

Governor 선택

# sysfs로 governor 변경 */
# cat /sys/class/thermal/thermal_zone0/available_policies
step_wise fair_share power_allocator bang_bang user_space

# echo power_allocator > /sys/class/thermal/thermal_zone0/policy

IPA (Intelligent Power Allocation)

IPA(Power Allocator Governor)는 ARM에서 개발한 열 관리 알고리즘으로, PID 컨트롤러를 사용하여 sustainable_power 예산을 여러 cooling device에 동적으로 분배합니다. 온도를 목표값(control_temp)에 수렴시키면서 성능을 최대한 유지하는 것이 목표입니다.

IPA PID 제어 루프 목표 온도 control_temp 오차 (e) target - cur P: k_po/k_pu * e I: k_i * &Sigma;e D: k_d * &Delta;e Power Budget P + I + D + sust. Power 분배 CPU: weight * budget GPU: weight * budget ... 피드백 루프 cooling device → 주파수 제한 → 전력 감소 → 온도 하강 → 오차 변화 → PID 재계산 → 반복 Power Budget 분배 예시 (sustainable_power=5000mW) Total Budget: 5000mW CPU (weight=1024) → 3333mW (67%) GPU (weight=512) → 1667mW (33%) NPU (weight=0) → 0mW (idle)

IPA 알고리즘 핵심

/* drivers/thermal/gov_power_allocator.c */
static int power_allocator_throttle(
    struct thermal_zone_device *tz, int trip)
{
    s64 err, p, i, d, power_range;
    int control_temp = tz->trips[trip].temperature;
    int cur_temp;

    thermal_zone_get_temp(tz, &cur_temp);
    err = control_temp - cur_temp;

    /* Proportional: overshoot → k_po, undershoot → k_pu */
    if (err < 0)
        p = mul_frac(tz->tzp->k_po, err);
    else
        p = mul_frac(tz->tzp->k_pu, err);

    /* Integral: 누적 오차 (integral_cutoff으로 제한) */
    if (abs(err) < tz->tzp->integral_cutoff)
        params->err_integral += err;
    i = mul_frac(tz->tzp->k_i, params->err_integral);

    /* Derivative: 오차 변화율 */
    d = mul_frac(tz->tzp->k_d, err - params->prev_err);
    params->prev_err = err;

    /* 총 power budget */
    power_range = tz->tzp->sustainable_power + p + i + d;
    if (power_range < 0)
        power_range = 0;

    /* cooling device들에 power 분배 */
    divvy_up_power(tz, power_range);
    return 0;
}
코드 설명
  • err = control_temp - cur_temp목표 온도와 현재 온도의 차이(오차)를 계산합니다. 양수면 목표 미만(undershoot), 음수면 목표 초과(overshoot)입니다.
  • k_po / k_pu 분기비례항(P)의 게인을 온도 상태에 따라 다르게 적용합니다. 목표 초과 시 k_po로 완만하게 전력을 줄이고, 목표 미만 시 k_pu로 빠르게 전력을 늘립니다. drivers/thermal/gov_power_allocator.c에 구현되어 있습니다.
  • integral_cutoff적분항(I) 누적은 오차가 integral_cutoff 이내일 때만 수행됩니다. 큰 오차에서 적분이 과도하게 누적되는 windup 현상을 방지합니다.
  • k_d, prev_err미분항(D)은 오차의 변화율을 반영합니다. 급격한 온도 변화에 선제적으로 대응하여 오버슈트를 억제합니다.
  • sustainable_power + p + i + d기본 지속 가능 전력(sustainable_power)에 PID 보정값을 더해 총 전력 예산을 산출합니다. 음수가 되면 0으로 클램핑합니다.
  • divvy_up_power산출된 전력 예산을 각 cooling device의 contribution 가중치에 비례하여 분배합니다. 각 device는 할당받은 전력을 power2state로 cooling state로 변환합니다.

thermal_power_actor 등록

/* Cooling device가 IPA와 연동하려면 power ops 필수 */
struct thermal_cooling_device_ops {
    /* 기본 ops */
    int (*get_max_state)(...);
    int (*get_cur_state)(...);
    int (*set_cur_state)(...);

    /* Power Actor ops (IPA 필수) */
    int (*get_requested_power)(..., u32 *power);
    int (*state2power)(..., unsigned long state, u32 *power);
    int (*power2state)(..., u32 power, unsigned long *state);
};

/* get_requested_power: 현재 소비 전력 조회 */
/* state2power: cooling state → mW 변환 */
/* power2state: mW → cooling state 역변환 */

IPA Device Tree 바인딩

/* IPA를 위한 thermal-zone DT 설정 */
cpu-thermal {
    polling-delay-passive = <100>;
    polling-delay = <1000>;
    thermal-sensors = <&tmu 0>;

    /* IPA 전용 속성 */
    sustainable-power = <5000>; /* 5W (mW 단위) */

    trips {
        target: target-trip {
            temperature = <75000>; /* IPA 목표 온도 */
            hysteresis = <0>;
            type = "passive";
        };
    };

    cooling-maps {
        cpu-map {
            trip = <&target>;
            cooling-device = <&cpu_cooling 0 10>;
            contribution = <1024>;
        };
        gpu-map {
            trip = <&target>;
            cooling-device = <&gpu_cooling 0 5>;
            contribution = <512>;
        };
    };
};

IPA sysfs 튜닝

# IPA 파라미터 확인/조정
TZ=/sys/class/thermal/thermal_zone0

# sustainable_power 조회/설정 (mW)
# cat $TZ/sustainable_power
5000
# echo 4000 > $TZ/sustainable_power  # 4W로 낮추기

# PID 게인 확인 (자동 계산 또는 DT 지정)
# cat $TZ/k_po  # proportional gain (overshoot)
# cat $TZ/k_pu  # proportional gain (undershoot)
# cat $TZ/k_i   # integral gain
# cat $TZ/k_d   # derivative gain

# integral_cutoff: 이 온도차 이내에서만 적분항 누적
# cat $TZ/integral_cutoff

k_po / k_pu 자동 계산

IPA는 sustainable_power로부터 PID 게인을 자동 계산합니다:

예시: sustainable_power=5000, control_temp=75°C, switch_on=65°C인 경우:

overshoot 시 (온도 > target)에는 k_po로 완만하게 감소하고, undershoot 시 (온도 < target)에는 k_pu로 빠르게 증가합니다.

파라미터 설명 기본값 튜닝 방향
sustainable_power 목표 온도에서 유지 가능한 전력 (mW) DT 정의 높을수록 성능↑, 온도↑
k_po 비례 게인 (overshoot) 자동 계산 높으면 과열 시 빠른 제한
k_pu 비례 게인 (undershoot) 자동 계산 높으면 냉각 시 빠른 복원
k_i 적분 게인 자동 계산 높으면 정상상태 오차 제거 빠름
k_d 미분 게인 0 높으면 급격한 변화에 민감
integral_cutoff 적분항 활성화 온도 범위 0 (항상) 좁으면 목표 근처에서만 적분
항목 IPA (power_allocator) Step-Wise
제어 방식 PID + 전력 예산 분배 단순 단계 증감
다중 cooling device 가중치 기반 최적 분배 개별 독립 제어
성능 유지 목표 온도에서 최대 성능 보수적 제한
Power model 필요 필수 (power actor ops) 불필요
적합 환경 모바일 SoC (ARM big.LITTLE) 범용 (기본값)
튜닝 복잡도 높음 (PID 파라미터) 낮음

ACPI Thermal Zone

UEFI/ACPI 플랫폼에서는 ACPI 펌웨어(Firmware)가 thermal zone과 cooling method를 정의합니다.

ACPI Thermal Methods

Method 설명
_TMP 현재 온도 반환 (1/10 Kelvin 단위)
_CRT Critical 온도 임계값
_HOT Hot 온도 임계값
_PSV Passive 온도 임계값
_ACx Active cooling 임계값 (x = 0~9)
_ALx Active cooling 대응 팬 리스트
_PSL Passive cooling 대상 프로세서 리스트
_TC1, _TC2, _TSP Passive cooling 상수

ACPI Thermal Zone 예시

/* ACPI DSDT에서 Thermal Zone 정의 예시 (ASL) */
ThermalZone (TZ00) {
    Method (_TMP, 0) {
        /* EC(Embedded Controller)에서 온도 읽기 */
        Return (\_SB.PCI0.LPCB.EC0.TMP0)  /* 0.1K 단위 */
    }

    Method (_CRT) { Return (3732) }  /* 100°C (373.2K) */
    Method (_PSV) { Return (3632) }  /* 90°C */

    Method (_AC0) { Return (3532) }  /* 80°C */
    Method (_AL0) { Return (Package(){\_SB.FAN0}) }

    Name (_PSL, Package(){\_SB.CPU0, \_SB.CPU1})  /* Passive 대상 CPU */
}

Device Tree Thermal Binding

ARM/RISC-V 등 Device Tree 기반 플랫폼에서는 thermal zone, trip point, cooling map을 DT 노드로 정의합니다. 커널의 thermal_of.c가 DT를 파싱하여 thermal framework에 등록합니다.

Device Tree Thermal Zones 구조 thermal-zones { } cpu-thermal { } gpu-thermal { } trips { } trip point 정의 cooling-maps { } cooling 바인딩 thermal-sensors, polling-delay, polling-delay-passive sustainable-power, coefficients, #cooling-cells trips { } GPU trip points cooling-maps { } GPU cooling 바인딩

완전한 DT 예시

/* arch/arm64/boot/dts/vendor/soc.dtsi */
thermal-zones {
    cpu-thermal {
        polling-delay-passive = <100>; /* passive cooling 시 100ms */
        polling-delay = <1000>;         /* 일반 1초 */
        thermal-sensors = <&tmu 0>;     /* 센서 phandle + ID */

        trips {
            cpu_alert0: cpu-alert0 {
                temperature = <70000>; /* 70°C */
                hysteresis = <2000>;   /* 2°C */
                type = "passive";
            };
            cpu_alert1: cpu-alert1 {
                temperature = <85000>; /* 85°C */
                hysteresis = <2000>;
                type = "passive";
            };
            cpu_crit: cpu-crit {
                temperature = <100000>; /* 100°C */
                hysteresis = <0>;
                type = "critical";
            };
        };

        cooling-maps {
            map0 {
                trip = <&cpu_alert0>;
                cooling-device =
                    <&cpu0_cooling THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
            };
            map1 {
                trip = <&cpu_alert1>;
                cooling-device =
                    <&cpu0_cooling 5 THERMAL_NO_LIMIT>;
                /* state 5부터 max까지 */
            };
        };
    };
};

센서 바인딩

/* 센서 드라이버에서 thermal zone 자동 등록 */
static int my_sensor_probe(struct platform_device *pdev)
{
    struct my_sensor_data *data;
    struct thermal_zone_device *tz;

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);

    /* DT thermal-zones에서 이 센서를 참조하면 자동 연결 */
    tz = devm_thermal_of_zone_register(
        &pdev->dev, 0,  /* sensor ID */
        data, &my_sensor_ops);
    if (IS_ERR(tz))
        return PTR_ERR(tz);

    data->tz = tz;
    return 0;
}

static const struct thermal_zone_device_ops my_sensor_ops = {
    .get_temp = my_sensor_get_temp,
    .set_trips = my_sensor_set_trips,  /* IRQ 기반 trip 설정 */
};

.dtsi에서 multi-zone 정의

/* 여러 thermal zone을 하나의 dtsi에서 정의 */
thermal-zones {
    cpu-big-thermal {
        thermal-sensors = <&tmu 0>; /* big 코어 센서 */
        /* ... trips, cooling-maps ... */
    };
    cpu-little-thermal {
        thermal-sensors = <&tmu 1>; /* LITTLE 코어 센서 */
    };
    gpu-thermal {
        thermal-sensors = <&tmu 2>; /* GPU 센서 */
    };
    ddr-thermal {
        thermal-sensors = <&tmu 3>; /* DDR 메모리 센서 */
    };
};

복수 Cooling Device 바인딩

/* 하나의 trip에 여러 cooling device를 바인딩 */
cooling-maps {
    cpu-passive {
        trip = <&cpu_passive_trip>;
        cooling-device = <&cpu_cooling 0 10>;
        contribution = <1024>; /* IPA 가중치 */
    };
    gpu-passive {
        trip = <&cpu_passive_trip>;  /* 같은 trip 공유 */
        cooling-device = <&gpu_cooling 0 5>;
        contribution = <512>;
    };
    fan-active {
        trip = <&cpu_active_trip>;
        cooling-device = <&fan0 0 4>;
    };
};
코드 설명
  • trip = <&cpu_passive_trip>각 cooling map이 어떤 trip point에서 활성화되는지를 phandle로 참조합니다. 여러 cooling device가 동일한 trip을 공유할 수 있습니다.
  • cooling-device = <&cpu_cooling 0 10>cooling device의 phandle과 사용 가능한 state 범위(최소 0, 최대 10)를 지정합니다. 커널의 drivers/thermal/thermal_of.c가 이 바인딩을 파싱합니다.
  • contributionIPA governor에서 전력 예산 분배 시 사용되는 가중치입니다. 예시에서 CPU(1024)가 GPU(512)의 2배 비중을 가집니다. step_wise governor에서는 무시됩니다.
  • fan-active팬은 별도의 active trip에 바인딩됩니다. active trip은 passive보다 높은 온도에 설정되어, passive cooling으로 부족할 때 팬이 동작합니다.
DT 속성 타입 설명
thermal-sensors phandle + args 온도 센서 참조 (phandle + sensor ID)
polling-delay u32 (ms) 일반 폴링(Polling) 주기
polling-delay-passive u32 (ms) passive cooling 시 폴링 주기
sustainable-power u32 (mW) IPA용 지속 가능 전력
temperature s32 (mC) trip point 온도 (milli-Celsius)
hysteresis u32 (mC) trip 히스테리시스
type string "active", "passive", "hot", "critical"
cooling-device phandle + min + max cooling device phandle + state 범위
contribution u32 IPA power allocation 가중치
#cooling-cells u32 cooling device specifier 셀 수 (보통 2)
항목 Device Tree ACPI
플랫폼 ARM, RISC-V, MIPS x86, ARM ACPI
정의 위치 .dts / .dtsi 파일 ACPI DSDT/SSDT (ASL)
파싱 thermal_of.c drivers/acpi/thermal.c
센서 바인딩 thermal-sensors phandle _TMP ACPI 메서드
Trip point trips { } 노드 _CRT, _PSV, _HOT, _ACx
Cooling 바인딩 cooling-maps { } 노드 _PSL, _ALx
유연성 높음 (사용자 정의) 제한적 (펌웨어 의존)

sysfs 인터페이스

Thermal Zone sysfs

Thermal Zone sysfs /sys/class/thermal/thermal_zone0/ 기본 상태/정책 type (예: acpitz) temp (milli-Celsius) mode (enabled/disabled) policy / available_policies passive (지연 msec) Trip/Cooling 바인딩 trip_point_0_type trip_point_0_temp cdev0_trip_point cdev0_weight

Cooling Device sysfs

Cooling Device sysfs /sys/class/thermal/cooling_device0/ 상태 정보 type (Processor/Fan 등) max_state cur_state device -> ../../../devices/... 통계 정보 stats/ (CONFIG_THERMAL_STATISTICS) time_in_state_ms trans_table

sysfs 사용 예시

# 모든 thermal zone 온도 확인
# for tz in /sys/class/thermal/thermal_zone*; do
    echo "$tz: $(cat $tz/type) = $(cat $tz/temp | awk '{print $1/1000}')°C"
done

# Cooling device 상태 확인
# cat /sys/class/thermal/cooling_device0/type
Processor
# cat /sys/class/thermal/cooling_device0/cur_state
0
# cat /sys/class/thermal/cooling_device0/max_state
10

# Governor 변경
# echo power_allocator > /sys/class/thermal/thermal_zone0/policy

hwmon 통합

Thermal 서브시스템은 hwmon 센서와 통합되어 다양한 온도 센서를 thermal zone으로 노출할 수 있습니다.

/* hwmon thermal zone 자동 생성 (CONFIG_THERMAL_HWMON) */
struct thermal_hwmon_device {
    struct device *device;
    int count;
    struct list_head tz_list;
    struct list_head node;
};

/* /sys/class/hwmon/hwmon0/temp1_input과 연동 */

devm_thermal_of_zone_register() 자동 등록

hwmon 센서 드라이버에서 devm_thermal_of_zone_register()를 호출하면 Device Tree의 thermal-sensors phandle을 통해 thermal zone이 자동 생성됩니다.

/* hwmon 센서 드라이버에서 thermal zone 자동 등록 */
static int my_hwmon_get_temp(
    struct thermal_zone_device *tz, int *temp)
{
    struct my_hwmon_data *data = thermal_zone_device_priv(tz);
    int raw = readl(data->regs + TEMP_REG);

    *temp = raw_to_millicelsius(raw, data->cal);
    return 0;
}

static const struct thermal_zone_device_ops my_tz_ops = {
    .get_temp = my_hwmon_get_temp,
    .set_trips = my_hwmon_set_trips,  /* IRQ 기반 trip 지원 시 */
};

static int my_hwmon_probe(struct platform_device *pdev)
{
    struct my_hwmon_data *data;
    struct device *hwmon_dev;

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);

    /* hwmon 등록 */
    hwmon_dev = devm_hwmon_device_register_with_info(
        &pdev->dev, "my_sensor", data,
        &my_chip_info, NULL);

    /* thermal zone 자동 등록 (DT thermal-sensors 참조 시) */
    data->tz = devm_thermal_of_zone_register(
        &pdev->dev, 0, data, &my_tz_ops);
    if (IS_ERR(data->tz)) {
        /* DT에 thermal-zones 없으면 무시 가능 */
        if (PTR_ERR(data->tz) != -ENODEV)
            return PTR_ERR(data->tz);
        data->tz = NULL;
    }
    return 0;
}
hwmon ↔ thermal 자동 연동: CONFIG_THERMAL_HWMON=y이면 thermal zone이 등록될 때 자동으로 /sys/class/hwmon/hwmonN/temp1_input에 온도를 노출합니다. 반대로 hwmon 센서를 devm_thermal_of_zone_register()로 thermal zone에 연결하면 양방향 통합이 완성됩니다.

CPU Frequency Cooling

CPUFreq cooling device는 CPU 주파수를 제한하여 온도를 낮춥니다.

CPUFreq Cooling 등록

#include <linux/cpu_cooling.h>

struct thermal_cooling_device *cdev;
struct cpumask clip_cpus;
struct cpufreq_policy *policy;

policy = cpufreq_cpu_get(0);
cpumask_copy(&clip_cpus, policy->related_cpus);
cpufreq_cpu_put(policy);

/* Cooling device 등록 */
cdev = cpufreq_cooling_register(&clip_cpus);
if (IS_ERR(cdev))
    return PTR_ERR(cdev);

/* OPP 기반 cooling (전력 인식) */
cdev = of_cpufreq_cooling_register(np, &clip_cpus);

Cooling State와 주파수 매핑(Mapping)

CPUFreq cooling device는 가용 주파수를 cooling state로 매핑합니다.

/* 예: max_state=10, 가용 주파수가 11개라면 */
State 0: 2400 MHz  /* 최대 주파수 */
State 1: 2200 MHz
State 2: 2000 MHz
...
State 10: 400 MHz  /* 최소 주파수 */
동적 전력 공식: CPU의 동적 전력 소비는 P = C × V² × f로 모델링됩니다. 여기서 C는 effective capacitance(회로 스위칭 활동 계수), V는 공급 전압, f는 동작 주파수입니다. cooling state가 증가하면 주파수가 낮아지고, DVFS에 의해 전압도 함께 낮아져 전력이 세제곱에 가까운 비율로 감소합니다.

OPP → Cooling State 매핑 예시

Cooling State 주파수 (MHz) 전압 (mV) 예상 전력 (mW)
0 (최대 성능) 2400 1100 ~4200
2 2000 1000 ~2800
4 1600 900 ~1800
6 1200 800 ~1100
8 800 700 ~550
10 (최대 절전) 400 600 ~200

Devfreq Cooling

GPU, Memory Controller 등 devfreq 장치를 cooling device로 등록합니다.

#include <linux/devfreq_cooling.h>

struct thermal_cooling_device *
devfreq_cooling_register(struct devfreq *df,
                          struct devfreq_cooling_power *dfc_power);

/* GPU devfreq cooling 등록 */
struct devfreq *gpu_devfreq;
struct thermal_cooling_device *gpu_cdev;

gpu_cdev = devfreq_cooling_register(gpu_devfreq, NULL);

Devfreq Power Callback 상세

IPA governor와 연동하려면 devfreq_cooling_power 구조체에 전력 콜백(Callback)을 등록해야 합니다.

struct devfreq_cooling_power {
    /* 현재 주파수/전압에서 동적 전력 계산 */
    int (*get_dynamic_power)(struct devfreq *df,
                              unsigned long freq,
                              unsigned long voltage,
                              unsigned long *power);

    /* 정적(누설) 전력 - 온도 의존적 */
    int (*get_static_power)(struct devfreq *df,
                             unsigned long voltage,
                             unsigned long *power);

    /* 동적 전력 계수 (간이 모델용) */
    unsigned long dyn_power_coeff;
};

/* 등록 시 power model 포함 */
static struct devfreq_cooling_power my_power = {
    .get_dynamic_power = my_get_dyn_power,
    .get_static_power  = my_get_static_power,
    .dyn_power_coeff   = 300,  /* mW 기준 */
};

cdev = devfreq_cooling_register(devfreq, &my_power);
/* NULL 전달 시 Energy Model 기반 자동 계산 */

팬 제어 (Fan Cooling Device)

PWM(Pulse Width Modulation) 기반 팬 제어는 thermal framework의 cooling device로 등록되어 governor가 직접 팬 속도를 조절합니다. thermal-fan 드라이버는 PWM 채널과 연동하여 cooling state에 따라 duty cycle을 변경합니다.

PWM Fan Cooling 제어 흐름 Thermal Governor step_wise / bang_bang Fan Cooling Dev set_cur_state() PWM 제어 duty_cycle 변경 Fan RPM 변화 hwmon fan1_input Device Tree: pwm-fan 노드 → cooling-maps 바인딩 → thermal-zone 연결 cooling-levels = <0 102 170 230 255> (PWM duty: 0%, 40%, 67%, 90%, 100%)

Fan Cooling Device 등록

/* drivers/hwmon/pwm-fan.c 기반 fan cooling device */
static int pwm_fan_set_cur_state(
    struct thermal_cooling_device *cdev,
    unsigned long state)
{
    struct pwm_fan_ctx *ctx = cdev->devdata;
    unsigned int pwm_value;

    if (state > ctx->pwm_fan_max_state)
        return -EINVAL;

    /* cooling state → PWM duty cycle 매핑 */
    pwm_value = ctx->pwm_fan_cooling_levels[state];
    ctx->pwm_value = pwm_value;

    return __set_pwm(ctx, pwm_value);
}

static struct thermal_cooling_device_ops pwm_fan_cooling_ops = {
    .get_max_state = pwm_fan_get_max_state,
    .get_cur_state = pwm_fan_get_cur_state,
    .set_cur_state = pwm_fan_set_cur_state,
};

/* Cooling device 등록 */
cdev = devm_thermal_of_cooling_device_register(
    dev, np, "pwm-fan", ctx, &pwm_fan_cooling_ops);

Fan Device Tree 바인딩

/* Device Tree: pwm-fan 노드 */
fan0: pwm-fan {
    compatible = "pwm-fan";
    pwms = <&pwm1 0 25000>; /* 25kHz PWM */
    cooling-levels = <0 102 170 230 255>;
    /* state 0: off, 1: 40%, 2: 67%, 3: 90%, 4: 100% */
    #cooling-cells = <2>;
};

/* thermal-zones에서 fan 바인딩 */
thermal-zones {
    cpu-thermal {
        trips {
            fan_alert: fan-alert {
                temperature = <60000>;
                hysteresis = <5000>;
                type = "active";
            };
        };
        cooling-maps {
            map-fan {
                trip = <&fan_alert>;
                cooling-device = <&fan0 1 4>; /* state 1~4 */
            };
        };
    };
};

hwmon Fan 속성

# hwmon에서 팬 정보 확인
# cat /sys/class/hwmon/hwmon2/fan1_input   # 현재 RPM
2400
# cat /sys/class/hwmon/hwmon2/pwm1          # PWM duty (0~255)
170
# cat /sys/class/hwmon/hwmon2/pwm1_enable   # 제어 모드
1  # 0=off, 1=manual, 2=auto(thermal)

# Cooling device로 확인
# cat /sys/class/thermal/cooling_device2/type
pwm-fan
# cat /sys/class/thermal/cooling_device2/max_state
4
# cat /sys/class/thermal/cooling_device2/cur_state
2
Cooling State PWM Duty 예상 RPM 소음 수준
0 (off) 0% 0 무소음
1 (low) 40% ~1200 저소음
2 (medium) 67% ~2000 보통
3 (high) 90% ~3200 높음
4 (full) 100% ~3800 최대

Thermal Pressure & 스케줄러(Scheduler) 연계

Thermal throttling이 발생하면 CPU의 실제 성능이 감소합니다. 커널 스케줄러(특히 EAS)는 arch_set_thermal_pressure()를 통해 이 정보를 반영하여 태스크(Task) 배치를 최적화합니다.

Thermal Pressure → EAS 스케줄러 연동 Thermal Zone trip 초과 감지 CPUFreq Cooling freq_qos_update() thermal_pressure arch_set_thermal_ pressure(cpu, val) EAS 스케줄러 capacity 감소 반영 CPU Capacity = arch_scale_cpu_capacity() - thermal_pressure big 코어 1024 → throttle 시 thermal_pressure=512 → 유효 capacity=512 → EAS가 태스크를 throttle된 코어 대신 다른 코어로 이동

Thermal Pressure API

/* arch/arm64/kernel/topology.c */
void topology_update_thermal_pressure(
    const struct cpumask *cpus,
    unsigned long capped_freq)
{
    unsigned long max_capacity, capacity;
    u32 max_freq;
    int cpu;

    cpu = cpumask_first(cpus);
    max_capacity = arch_scale_cpu_capacity(cpu);
    max_freq = per_cpu(freq_factor, cpu);

    /* 제한된 주파수에 비례하여 capacity 감소량 계산 */
    capacity = mult_frac(max_capacity, capped_freq, max_freq);
    arch_set_thermal_pressure(cpus, max_capacity - capacity);
}

CPUFreq Cooling에서 Pressure 업데이트

/* drivers/thermal/cpufreq_cooling.c */
static int cpufreq_set_cur_state(
    struct thermal_cooling_device *cdev,
    unsigned long state)
{
    struct cpufreq_cooling_device *cd = cdev->devdata;
    unsigned int clip_freq;

    clip_freq = cd->freq_table[state].frequency;

    /* freq_qos로 최대 주파수 제한 */
    freq_qos_update_request(&cd->qos_req, clip_freq);

    /* thermal pressure 업데이트 → 스케줄러 반영 */
    topology_update_thermal_pressure(cd->policy->related_cpus,
                                       clip_freq);
    return 0;
}

Thermal Pressure 확인

# CPU별 thermal pressure 확인
# for cpu in /sys/devices/system/cpu/cpu*/topology; do
    echo "$(dirname $cpu): capacity=$(cat $(dirname $cpu)/cpu_capacity)"
done

# trace에서 thermal pressure 변화 확인
# echo 1 > /sys/kernel/debug/tracing/events/thermal/thermal_pressure_update/enable
# cat /sys/kernel/debug/tracing/trace
# thermal pressure trace 출력 예시
kworker/0:1-123 thermal_pressure_update: cpu=4 thermal_pressure=512
kworker/0:1-123 thermal_pressure_update: cpu=5 thermal_pressure=512
# → big 코어(4,5)의 capacity가 1024에서 512로 감소
# → EAS가 heavy 태스크를 LITTLE 코어로 분산
스케줄러 Thermal Pressure 반영 방식 효과
EAS (Energy Aware) capacity 감소 → energy model 재계산 throttle된 코어 회피, 에너지 효율 유지
CFS (Completely Fair) capacity 감소 → load balancing 가중치 조정 throttle된 코어의 runqueue 부하 감소
RT (Real-Time) 직접 반영 없음 (capacity aware 아님) RT 태스크는 thermal pressure 무시

GPU/NPU Thermal Cooling

모바일 SoC에서 GPU와 NPU(Neural Processing Unit)는 집중 연산 시 발열이 매우 크며, CPU와 thermal budget을 공유합니다. IPA governor로 여러 cooling device에 전력을 분배할 때 GPU/NPU의 power model이 핵심입니다.

Multi-Device Thermal Budget 분배 (IPA) IPA Governor (sustainable_power) CPU Cooling cpufreq P=CV²f weight: 60% GPU Cooling devfreq P=CV²f weight: 30% NPU Cooling devfreq custom weight: 10% Fan active cooling Total Power Budget = sustainable_power (예: 5000mW) CPU: 3000mW + GPU: 1500mW + NPU: 500mW = 5000mW (가중치 비례 배분)

GPU Power Model

/* GPU devfreq cooling에 power model 등록 */
static int gpu_get_dynamic_power(
    struct devfreq *df,
    unsigned long freq,
    unsigned long voltage,
    unsigned long *power)
{
    /* P_dynamic = C * V^2 * f */
    /* C = effective capacitance (GPU별 상수) */
    *power = (gpu_capacitance * voltage * voltage * freq) /
             (1000000000ULL);
    return 0;
}

static struct devfreq_cooling_power gpu_cooling_power = {
    .get_dynamic_power = gpu_get_dynamic_power,
    .get_static_power  = gpu_get_static_power,  /* 누설 전력 */
    .dyn_power_coeff   = 450,  /* mW at max freq/voltage */
};

cdev = devfreq_cooling_em_register(gpu_devfreq, &gpu_cooling_power);

NPU Cooling 등록

/* NPU devfreq cooling - 사용자 정의 power model */
static struct devfreq_cooling_power npu_cooling_power = {
    .get_dynamic_power = npu_get_dynamic_power,
    .get_static_power  = npu_get_static_power,
    .dyn_power_coeff   = 200,
};

/* NPU devfreq에 cooling 연결 */
npu_cdev = devfreq_cooling_em_register(npu_devfreq, &npu_cooling_power);
if (IS_ERR(npu_cdev))
    dev_warn(dev, "NPU cooling registration failed\n");

Multi-zone Device Tree 구성

/* CPU + GPU + NPU가 하나의 thermal zone을 공유하는 DT 예시 */
thermal-zones {
    soc-thermal {
        polling-delay = <1000>;
        polling-delay-passive = <100>;
        sustainable-power = <5000>; /* 5W */

        thermal-sensors = <&soc_temp_sensor 0>;

        trips {
            soc_target: soc-target {
                temperature = <75000>;
                hysteresis = <2000>;
                type = "passive";
            };
        };

        cooling-maps {
            cpu-map {
                trip = <&soc_target>;
                cooling-device = <&cpu_cooling 0 10>;
                contribution = <1024>; /* 가중치 */
            };
            gpu-map {
                trip = <&soc_target>;
                cooling-device = <&gpu_cooling 0 5>;
                contribution = <512>;
            };
            npu-map {
                trip = <&soc_target>;
                cooling-device = <&npu_cooling 0 3>;
                contribution = <256>;
            };
        };
    };
};
특성 CPU GPU NPU
주요 워크로드 범용 연산 그래픽/컴퓨트 AI 추론
Cooling 방식 cpufreq 제한 devfreq 제한 devfreq 제한
전력 소모 (최대) ~4W (big 코어) ~3W ~2W
Throttle 영향 응답성 저하 FPS 하락 추론 지연(Latency) 증가
IPA 가중치 (일반적) 높음 (1024) 중간 (512) 낮음 (256)
팁: 게이밍 시나리오에서는 GPU contribution을 높이고, ML 추론 시에는 NPU contribution을 높여 워크로드에 맞게 thermal budget을 배분하세요.

Userspace Governor 활용

user_space governor를 선택하면 커널은 cooling 제어를 하지 않으며, userspace 데몬이 온도를 모니터링하고 cooling device를 직접 제어합니다. 복잡한 정책이나 ML 기반 예측 제어에 유용합니다.

thermald 설정 예시

<!-- /etc/thermald/thermal-conf.xml -->
<ThermalConfiguration>
  <Platform>
    <Name>Custom Thermal Policy</Name>
    <ProductName>*</ProductName>
    <ThermalZones>
      <ThermalZone>
        <Type>cpu-thermal</Type>
        <TripPoints>
          <TripPoint>
            <SensorType>x86_pkg_temp</SensorType>
            <Temperature>75000</Temperature>
            <Type>passive</Type>
            <CoolingDevice>
              <Type>rapl_controller</Type>
              <Influence>100</Influence>
            </CoolingDevice>
          </TripPoint>
          <TripPoint>
            <SensorType>x86_pkg_temp</SensorType>
            <Temperature>85000</Temperature>
            <Type>passive</Type>
            <CoolingDevice>
              <Type>Processor</Type>
              <Influence>80</Influence>
            </CoolingDevice>
          </TripPoint>
        </TripPoints>
      </ThermalZone>
    </ThermalZones>
  </Platform>
</ThermalConfiguration>

Python 커스텀 Thermal 데몬

#!/usr/bin/env python3
# custom_thermal_daemon.py - Userspace 열 관리 데몬
import os, time, signal, sys

THERMAL_BASE = "/sys/class/thermal"
TARGET_TEMP = 75000  # 75°C (milli-Celsius)
POLL_INTERVAL = 2    # 초

def read_temp(zone_id):
    with open(f"{THERMAL_BASE}/thermal_zone{zone_id}/temp") as f:
        return int(f.read().strip())

def set_cooling_state(cdev_id, state):
    with open(f"{THERMAL_BASE}/cooling_device{cdev_id}/cur_state", "w") as f:
        f.write(str(state))

def get_max_state(cdev_id):
    with open(f"{THERMAL_BASE}/cooling_device{cdev_id}/max_state") as f:
        return int(f.read().strip())

def thermal_loop():
    max_state = get_max_state(0)
    cur_state = 0

    while True:
        temp = read_temp(0)
        if temp > TARGET_TEMP + 5000:
            cur_state = min(cur_state + 2, max_state)
        elif temp > TARGET_TEMP:
            cur_state = min(cur_state + 1, max_state)
        elif temp < TARGET_TEMP - 3000:
            cur_state = max(cur_state - 1, 0)

        set_cooling_state(0, cur_state)
        print(f"temp={temp/1000:.1f}°C state={cur_state}/{max_state}")
        time.sleep(POLL_INTERVAL)

if __name__ == "__main__":
    thermal_loop()

uevent 기반 모니터링

#!/bin/bash
# thermal_uevent_monitor.sh - uevent로 thermal 이벤트 감시

# udevadm으로 thermal uevent 실시간 수신
udevadm monitor --kernel --subsystem-match=thermal |
while read line; do
    echo "[$(date +%H:%M:%S)] $line"
    # 여기서 커스텀 액션 수행 가능
done

systemd 서비스 파일

# /etc/systemd/system/custom-thermald.service
[Unit]
Description=Custom Thermal Management Daemon
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/local/bin/custom_thermal_daemon.py
Restart=always
RestartSec=5
Nice=-10
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=50

[Install]
WantedBy=multi-user.target
항목 Kernel Governor Userspace Governor
반응 속도 빠름 (커널 컨텍스트) 느림 (sysfs I/O 오버헤드(Overhead))
정책 복잡도 제한적 (고정 알고리즘) 자유 (ML, 예측, 커스텀)
안전성 높음 (critical은 커널 처리) 데몬 크래시 시 위험
디버깅 tracepoint 필요 일반 로그/printf 가능
적합 환경 임베디드, 모바일 서버, 데스크톱, R&D

Thermal Interrupts

일부 하드웨어는 온도 임계값 도달 시 인터럽트(Interrupt)를 발생시킵니다.

Thermal IRQ 처리

static irqreturn_t thermal_zone_irq_handler(int irq, void *data)
{
    struct thermal_zone_device *tz = data;

    /* 온도 업데이트 트리거 */
    thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);

    return IRQ_HANDLED;
}

/* 인터럽트 등록 */
devm_request_threaded_irq(dev, irq, NULL,
                           thermal_zone_irq_handler,
                           IRQF_ONESHOT, "thermal", tz);

커널 5.9+에서 thermal 이벤트를 netlink로 userspace에 전달합니다.

#include <linux/thermal.h>

/* Thermal Netlink 이벤트 */
enum thermal_notify_event {
    THERMAL_EVENT_UNSPECIFIED,
    THERMAL_EVENT_TEMP_SAMPLE,       /* 온도 샘플링 */
    THERMAL_TRIP_VIOLATED,           /* trip point 초과 */
    THERMAL_TRIP_CHANGED,            /* trip point 변경 */
    THERMAL_DEVICE_DOWN,             /* thermal zone 비활성화 */
    THERMAL_DEVICE_UP,               /* thermal zone 활성화 */
    THERMAL_DEVICE_NEW,              /* 새 thermal zone 등록 */
    THERMAL_DEVICE_DEL,              /* thermal zone 제거 */
};

/* Netlink 그룹: THERMAL_GENL_SAMPLING_GROUP, THERMAL_GENL_EVENT_GROUP */

커널 5.9+의 thermal netlink는 Generic Netlink(genl) 패밀리를 사용하여 userspace에 thermal 이벤트를 비동기로 전달합니다. polling 없이 실시간으로 온도 변화와 trip 이벤트를 수신할 수 있어 효율적입니다.

Thermal Generic Netlink 흐름 Thermal Core thermal_notify_* thermal_genl genl_family 직렬화 Netlink Multicast sampling / event 그룹 thermald / 커스텀 데몬 모니터링 도구 Generic Netlink 메시지 구조 nlmsghdr → genlmsghdr (cmd=THERMAL_GENL_CMD_*) → [NLA attrs: zone_id, temp, trip_id, cdev_id, ...] Multicast Groups: "thermal_sampling" (TEMP_SAMPLE) | "thermal_event" (TRIP_VIOLATED, ZONE_UP/DOWN, ...) Commands: TZ_GET / TZ_GET_TRIP / CDEV_GET / TZ_GET_GOV (unicast 쿼리)
이벤트 Multicast 그룹 설명
THERMAL_GENL_EVENT_TZ_CREATE event 새 thermal zone 등록
THERMAL_GENL_EVENT_TZ_DELETE event thermal zone 삭제
THERMAL_GENL_EVENT_TZ_ENABLE event thermal zone 활성화
THERMAL_GENL_EVENT_TZ_DISABLE event thermal zone 비활성화
THERMAL_GENL_EVENT_TZ_TRIP_UP event trip point 초과 (온도 상승)
THERMAL_GENL_EVENT_TZ_TRIP_DOWN event trip point 하회 (온도 하강)
THERMAL_GENL_EVENT_TZ_TRIP_CHANGE event trip point 온도값 변경
THERMAL_GENL_EVENT_CDEV_UPDATE event cooling device 상태 변경
THERMAL_GENL_SAMPLING_TEMP sampling 주기적 온도 샘플 데이터
/* include/uapi/linux/thermal.h - Netlink 속성 */
enum thermal_genl_attr {
    THERMAL_GENL_ATTR_UNSPEC,
    THERMAL_GENL_ATTR_TZ,             /* nested: zone 정보 */
    THERMAL_GENL_ATTR_TZ_ID,          /* u32: zone ID */
    THERMAL_GENL_ATTR_TZ_TEMP,        /* s32: milli-Celsius */
    THERMAL_GENL_ATTR_TZ_TRIP,        /* nested: trip 정보 */
    THERMAL_GENL_ATTR_TZ_TRIP_ID,     /* u32: trip ID */
    THERMAL_GENL_ATTR_TZ_TRIP_TYPE,   /* u32: trip 유형 */
    THERMAL_GENL_ATTR_TZ_TRIP_TEMP,   /* s32: trip 온도 */
    THERMAL_GENL_ATTR_TZ_TRIP_HYST,   /* s32: 히스테리시스 */
    THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT, /* u32: cooling 가중치 */
    THERMAL_GENL_ATTR_CDEV,           /* nested: cdev 정보 */
    THERMAL_GENL_ATTR_CDEV_ID,        /* u32: cdev ID */
    THERMAL_GENL_ATTR_CDEV_CUR_STATE, /* u32: 현재 state */
    THERMAL_GENL_ATTR_CDEV_MAX_STATE, /* u32: 최대 state */
    THERMAL_GENL_ATTR_TZ_GOV,         /* nested: governor 정보 */
    THERMAL_GENL_ATTR_TZ_GOV_NAME,    /* string: governor 이름 */
};
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>

static int thermal_event_handler(struct nl_msg *msg, void *arg)
{
    struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
    struct genlmsghdr *ghdr = nlmsg_data(nlmsg_hdr(msg));

    nla_parse(attrs, THERMAL_GENL_ATTR_MAX,
              genlmsg_attrdata(ghdr, 0),
              genlmsg_attrlen(ghdr, 0), NULL);

    if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
        int tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
        int temp = attrs[THERMAL_GENL_ATTR_TZ_TEMP] ?
                   nla_get_s32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]) : 0;
        printf("[event] zone=%d temp=%d cmd=%d\n", tz_id, temp, ghdr->cmd);
    }
    return NL_OK;
}

int main(void)
{
    struct nl_sock *sk = nl_socket_alloc();
    int family, grp_event;

    genl_connect(sk);
    family = genl_ctrl_resolve(sk, "thermal");
    grp_event = genl_ctrl_resolve_grp(sk, "thermal", "event");
    nl_socket_add_membership(sk, grp_event);

    nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM,
                          thermal_event_handler, NULL);

    while (1)
        nl_recvmsgs_default(sk);
    return 0;
}
# thermal_netlink_monitor.py
import socket, struct

# Generic Netlink으로 thermal 이벤트 수신
NETLINK_GENERIC = 16

def create_genl_socket():
    sk = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_GENERIC)
    sk.bind((0, 0))
    return sk

def monitor_thermal_events():
    # 실제 사용 시 pyroute2 라이브러리 권장
    from pyroute2 import GenericNetlinkSocket
    gnl = GenericNetlinkSocket()
    gnl.bind("thermal", gnl.marshal)
    print("Thermal Netlink 이벤트 대기 중...")
    while True:
        msg = gnl.get()
        for m in msg:
            print(f"Event: {m}")

if __name__ == "__main__":
    monitor_thermal_events()
# thermal netlink 쿼리 (iproute2 기반 도구 필요 시)
# 또는 커널 tools/thermal/ 도구 사용

# tmon - 커널 제공 thermal 모니터
# cd /usr/src/linux/tools/thermal/tmon
# make
# ./tmon

# Thermal zone 정보 일괄 수집 (debugfs)
# cat /sys/kernel/debug/thermal/thermal_zone*/temp

디버깅 & 모니터링

Tracing

# Thermal tracepoint 활성화
# cd /sys/kernel/debug/tracing
# echo 1 > events/thermal/enable

# 주요 tracepoint
thermal_temperature         # 온도 샘플링
thermal_zone_trip           # trip point 도달
cdev_update                 # cooling device 상태 변경
thermal_power_allocator     # power allocator governor

# trace 출력
# cat trace
     kworker/0:1-123 [000] .... 123.456: thermal_temperature: thermal_zone=acpitz temp=55000

실시간 모니터링

#!/bin/bash
# thermal_monitor.sh - 실시간 온도 모니터링

while true; do
    clear
    echo "===== Thermal Status ====="

    for tz in /sys/class/thermal/thermal_zone*; do
        type=$(cat $tz/type)
        temp=$(cat $tz/temp)
        policy=$(cat $tz/policy 2>/dev/null || echo "N/A")

        printf "%-20s: %6.2f°C  Governor: %s\\n" \
               "$type" $(awk "BEGIN {print $temp/1000}") "$policy"
    done

    echo
    echo "===== Cooling Devices ====="
    for cdev in /sys/class/thermal/cooling_device*; do
        type=$(cat $cdev/type)
        cur=$(cat $cdev/cur_state)
        max=$(cat $cdev/max_state)

        printf "%-20s: %2d / %2d\\n" "$type" "$cur" "$max"
    done

    sleep 1
done

thermald (Thermal Daemon)

Intel thermald는 userspace thermal management 데몬입니다.

# thermald 설치 (Ubuntu/Debian)
# apt install thermald

# 서비스 상태 확인
# systemctl status thermald

# 설정 파일: /etc/thermald/thermal-conf.xml
# 로그: journalctl -u thermald

실전 디버깅 시나리오

Thermal 문제는 재현이 어렵고 여러 서브시스템에 걸쳐 발생합니다. 체계적인 디버깅 파이프라인(Pipeline)을 구축하면 빠르게 원인을 찾을 수 있습니다.

Thermal 디버깅 파이프라인 증상 확인 throttle/shutdown 온도 수집 sysfs / tracepoint Governor 분석 policy / trip 확인 Cooling 확인 cdev state/binding 진단: ftrace thermal_* 이벤트 + bpftrace 분석 + dmesg 패턴 매칭 thermal_temperature / thermal_zone_trip / cdev_update / thermal_power_allocator Software 원인 - Governor 미설정 / 잘못된 trip point - Cooling device 미등록 / 바인딩 누락 - 드라이버 get_temp() 오류 반환 - polling_delay=0인데 IRQ 미설정 Hardware 원인 - 센서 캘리브레이션 오프셋 - 써멀 패드/페이스트 불량 - 팬 고장 / PWM 신호 불량 - ACPI DSDT 버그 (_TMP 반환값 오류)

Tracepoint 기반 분석

# Thermal 전용 tracepoint 활성화
# cd /sys/kernel/debug/tracing
# echo 0 > tracing_on
# echo > trace

# 핵심 thermal 이벤트만 활성화
# echo 1 > events/thermal/thermal_temperature/enable
# echo 1 > events/thermal/thermal_zone_trip/enable
# echo 1 > events/thermal/cdev_update/enable
# echo 1 > events/thermal/thermal_power_allocator/enable

# 타임스탬프 포함 수집 시작
# echo 1 > tracing_on

# 부하 실행 후 수집
# stress-ng --cpu 4 --timeout 30s
# echo 0 > tracing_on
# cat trace > /tmp/thermal_trace.txt

bpftrace 스크립트

/* thermal_watch.bt - 온도 변화 실시간 추적 */
tracepoint:thermal:thermal_temperature
{
    printf("%-16s zone=%-12s temp=%d (trip=%d)\n",
           comm, str(args->thermal_zone),
           args->temp, args->temp_prev);
}

tracepoint:thermal:cdev_update
{
    printf("%-16s cdev=%-12s target=%d\n",
           comm, str(args->type), args->target);
}

tracepoint:thermal:thermal_zone_trip
{
    printf("*** TRIP *** zone=%s trip=%d type=%s temp=%d\n",
           str(args->thermal_zone), args->trip,
           str(args->trip_type), args->temp);
}

Thermal 상태 덤프(Dump) 스크립트

#!/bin/bash
# thermal_dump.sh - 현재 thermal 서브시스템 전체 상태 덤프

echo "=== Thermal Zones ==="
for tz in /sys/class/thermal/thermal_zone*; do
    name=$(basename $tz)
    type=$(cat $tz/type)
    temp=$(cat $tz/temp)
    policy=$(cat $tz/policy 2>/dev/null || echo "N/A")
    mode=$(cat $tz/mode 2>/dev/null || echo "N/A")
    printf "  %-20s type=%-16s temp=%6d  policy=%-16s mode=%s\n" \
           "$name" "$type" "$temp" "$policy" "$mode"

    # Trip point 나열
    for tp in $tz/trip_point_*_temp; do
        [ -f "$tp" ] || continue
        idx=$(basename $tp | sed 's/trip_point_\(.*\)_temp/\1/')
        trip_type=$(cat $tz/trip_point_${idx}_type 2>/dev/null)
        trip_temp=$(cat $tp)
        printf "    trip_%s: type=%-10s temp=%d\n" "$idx" "$trip_type" "$trip_temp"
    done
done

echo
echo "=== Cooling Devices ==="
for cd in /sys/class/thermal/cooling_device*; do
    name=$(basename $cd)
    type=$(cat $cd/type)
    cur=$(cat $cd/cur_state)
    max=$(cat $cd/max_state)
    printf "  %-20s type=%-16s state=%d/%d\n" \
           "$name" "$type" "$cur" "$max"
done

echo
echo "=== dmesg thermal 메시지 ==="
dmesg | grep -iE "thermal|trip|cooling|throttl" | tail -20

perf를 이용한 thermal 이벤트 분석

# perf로 thermal tracepoint 수집
# perf record -e thermal:thermal_temperature \
    -e thermal:cdev_update \
    -e thermal:thermal_zone_trip \
    -a -- sleep 60

# 결과 분석
# perf script | head -50
# perf stat -e thermal:thermal_temperature -a -- sleep 10
증상 확인 항목 해결 방법
갑작스런 shutdown dmesg | grep critical critical trip point 확인, 팬/히트싱크 점검
CPU throttling 지속 cpufreq cur_freq + thermal trip_point trip point 온도 조정, IPA sustainable_power 증가
온도 0 표시 cat thermal_zone*/temp 센서 드라이버 로딩 확인, get_temp() 반환값 검증
Cooling 미동작 cat cdev*/cur_state 바인딩 확인 (cdev*_trip_point), governor 정책 점검
온도 진동 (oscillation) tracepoint thermal_temperature hysteresis 값 증가, polling_delay 조정

SoC별 Thermal 드라이버 비교

각 SoC 벤더는 자체 thermal sensor IP와 드라이버를 제공합니다. 레지스터(Register) 접근 모델, 인터럽트 구조, 캘리브레이션 방식이 크게 다릅니다.

SoC Thermal 드라이버 구조 비교 Thermal Core (thermal_core.c) Qualcomm TSENS MMIO 레지스터 IRQ upper/lower eFuse 캘리브레이션 Samsung TMU TRIMINFO 레지스터 level-triggered IRQ 1/2-point 캘리브레이션 MediaTek thermal ADC + auxadc polling 기반 OTP 캘리브레이션 Intel DPTF ACPI INT340X SCI 인터럽트 MSR/PECI 읽기 공통 인터페이스: thermal_zone_device_register() / devm_thermal_of_zone_register() ops->get_temp() / ops->get_trip_temp() / ops->set_trips() Rockchip TSADC (ADC 기반, 2-point cal) TI Bandgap (DRA7xx, OMAP, AM335x)

Qualcomm TSENS 드라이버

/* drivers/thermal/qcom/tsens.c - Qualcomm Temperature Sensor */
static int tsens_get_temp(struct tsens_sensor *s, int *temp)
{
    struct tsens_priv *priv = s->priv;
    int last_temp, ret;

    /* MMIO 레지스터에서 ADC 값 읽기 */
    ret = regmap_field_read(priv->rf[LAST_TEMP_0 + s->hw_id], &last_temp);
    if (ret)
        return ret;

    /* ADC → milli-Celsius 변환 (slope, offset 적용) */
    *temp = tsens_hw_to_mC(s, last_temp);
    return 0;
}

/* eFuse 캘리브레이션 데이터 읽기 */
static int tsens_read_calibration(struct tsens_priv *priv)
{
    u32 *qfprom_cdata;
    int mode;

    qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
    mode = (qfprom_cdata[0] >> TSENS_CAL_SEL_SHIFT) & TSENS_CAL_SEL_MASK;
    /* mode: 0=no cal, 1=one-point, 2=two-point */
    return tsens_calibrate_sensors(priv, mode, qfprom_cdata);
}

Samsung Exynos TMU

/* drivers/thermal/samsung/exynos_tmu.c */
static int exynos_tmu_read(struct exynos_tmu_data *data)
{
    int temp_code, temp;

    /* TRIMINFO 레지스터로 센서 캘리브레이션 */
    temp_code = readl(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);

    /* 1-point 또는 2-point 캘리브레이션 적용 */
    switch (data->cal_type) {
    case TYPE_ONE_POINT_TRIMMING:
        temp = temp_code - data->trim25 + 25;
        break;
    case TYPE_TWO_POINT_TRIMMING:
        temp = (temp_code - data->trim25) *
               (85 - 25) / (data->trim85 - data->trim25) + 25;
        break;
    }
    return temp;
}

Intel INT340X (DPTF)

/* drivers/thermal/intel/int340x_thermal/ */
/* ACPI INT3403 센서 읽기 */
static int int3403_get_temp(struct thermal_zone_device *tz, int *temp)
{
    struct int34x_thermal_zone *d = thermal_zone_device_priv(tz);
    unsigned long long tmp;
    acpi_status status;

    /* _TMP ACPI 메서드 호출 */
    status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp);
    if (ACPI_FAILURE(status))
        return -EIO;

    /* 1/10 Kelvin → milli-Celsius 변환 */
    *temp = deci_kelvin_to_millicelsius(tmp);
    return 0;
}

Rockchip TSADC

/* drivers/thermal/rockchip_thermal.c */
/* ADC 기반 온도 읽기 + 2-point 캘리브레이션 */
static int rk_tsadcv2_get_temp(const struct chip_tsadc_table *table,
                                  int chn, void __iomem *regs,
                                  int *out_temp)
{
    u32 val;

    val = readl_relaxed(regs + TSADCV2_DATA(chn));
    return rk_tsadcv2_code_to_temp(table, val, out_temp);
}
항목 Qualcomm TSENS Samsung TMU MediaTek Intel DPTF Rockchip
센서 방식 BJT ADC BJT ADC auxadc PECI/MSR TSADC
캘리브레이션 eFuse 1/2pt TRIMINFO 1/2pt OTP 펌웨어 eFuse 2pt
인터럽트 upper/lower IRQ level IRQ polling SCI/GPE level IRQ
레지스터 접근 MMIO regmap MMIO direct MMIO auxadc ACPI AML MMIO direct
센서 수 (최대) 16개 5개 가변 ACPI 정의 2~3개
소스 경로 qcom/tsens.c samsung/exynos_tmu.c mediatek/ intel/int340x_thermal/ rockchip_thermal.c
캘리브레이션 방식 정확도 설명
No calibration ±10°C 공장 캘리브레이션 없음, ADC raw 값 직접 사용
1-point ±5°C 25°C 기준점 하나로 오프셋(Offset) 보정
2-point ±2°C 25°C + 85°C 두 기준점으로 slope + offset 보정
Firmware-based ±1°C Intel PECI/펌웨어가 내부적으로 보정 완료

Thermal 에뮬레이션 & 테스트

CONFIG_THERMAL_EMULATION을 활성화하면 실제 하드웨어 없이도 thermal zone에 가상 온도를 주입하여 governor 동작, trip point 반응, cooling device 제어를 테스트할 수 있습니다.

Thermal Emulation 테스트 흐름 emul_temp 쓰기 echo 85000 > zone_device_update 온도 갱신 트리거 Governor 실행 trip 비교 & 결정 Cooling 상태 변경 set_cur_state() 검증: cur_state 확인 + tracepoint 로그 + sysfs temp 읽기 echo 0 > emul_temp → 에뮬레이션 해제, 실제 센서 값 복원

기본 에뮬레이션 테스트

#!/bin/bash
# thermal_emul_test.sh - Thermal Emulation 기본 테스트

TZ="/sys/class/thermal/thermal_zone0"

# 에뮬레이션 가능 여부 확인
if [ ! -f "$TZ/emul_temp" ]; then
    echo "ERROR: CONFIG_THERMAL_EMULATION not enabled"
    exit 1
fi

# 현재 상태 저장
REAL_TEMP=$(cat $TZ/temp)
echo "현재 실제 온도: ${REAL_TEMP}mC"

# 에뮬레이션: 85°C (passive trip 테스트)
echo 85000 > $TZ/emul_temp
sleep 1
NEW_TEMP=$(cat $TZ/temp)
echo "에뮬레이션 온도: ${NEW_TEMP}mC"

# Cooling device 반응 확인
for cd in /sys/class/thermal/cooling_device*; do
    echo "  $(cat $cd/type): state=$(cat $cd/cur_state)/$(cat $cd/max_state)"
done

# 에뮬레이션 해제 (0 쓰기)
echo 0 > $TZ/emul_temp
echo "에뮬레이션 해제. 실제 온도 복원."

Trip Point 자동 검증

#!/bin/bash
# trip_point_test.sh - 모든 trip point에 대해 cooling 반응 검증

TZ="/sys/class/thermal/thermal_zone0"
PASS=0; FAIL=0

for tp in $TZ/trip_point_*_temp; do
    [ -f "$tp" ] || continue
    idx=$(basename $tp | sed 's/trip_point_\(.*\)_temp/\1/')
    trip_temp=$(cat $tp)
    trip_type=$(cat $TZ/trip_point_${idx}_type)

    # trip 온도 + 1°C로 에뮬레이션
    emul_temp=$(( trip_temp + 1000 ))
    echo $emul_temp > $TZ/emul_temp
    sleep 2

    # Cooling device가 활성화되었는지 확인
    any_active=0
    for cd in /sys/class/thermal/cooling_device*; do
        state=$(cat $cd/cur_state)
        [ "$state" -gt 0 ] && any_active=1
    done

    if [ $any_active -eq 1 ]; then
        echo "[PASS] trip_${idx} (${trip_type}): ${trip_temp}mC → cooling 활성화"
        PASS=$(( PASS + 1 ))
    else
        echo "[FAIL] trip_${idx} (${trip_type}): ${trip_temp}mC → cooling 미동작"
        FAIL=$(( FAIL + 1 ))
    fi
done

echo 0 > $TZ/emul_temp
echo "결과: PASS=$PASS, FAIL=$FAIL"

Python 테스트 프레임워크

# thermal_test.py - Thermal emulation 자동 테스트
import os, time, unittest

class ThermalEmulationTest(unittest.TestCase):
    TZ_PATH = "/sys/class/thermal/thermal_zone0"

    def setUp(self):
        # 에뮬레이션 가능 확인
        self.assertTrue(
            os.path.exists(f"{self.TZ_PATH}/emul_temp"),
            "CONFIG_THERMAL_EMULATION 비활성")

    def tearDown(self):
        # 에뮬레이션 해제
        self._write_emul(0)

    def _write_emul(self, temp_mc):
        with open(f"{self.TZ_PATH}/emul_temp", "w") as f:
            f.write(str(temp_mc))

    def _read_temp(self):
        with open(f"{self.TZ_PATH}/temp") as f:
            return int(f.read().strip())

    def test_emulation_sets_temp(self):
        self._write_emul(75000)
        time.sleep(0.5)
        self.assertEqual(self._read_temp(), 75000)

    def test_cooling_activates_above_trip(self):
        # trip_point_0_temp 초과 시 cooling 활성화
        with open(f"{self.TZ_PATH}/trip_point_0_temp") as f:
            trip = int(f.read().strip())
        self._write_emul(trip + 5000)
        time.sleep(2)
        # 최소 하나의 cooling device가 활성화
        active = False
        for cd in os.listdir("/sys/class/thermal"):
            if cd.startswith("cooling_device"):
                with open(f"/sys/class/thermal/{cd}/cur_state") as f:
                    if int(f.read().strip()) > 0:
                        active = True
        self.assertTrue(active, "cooling device가 활성화되지 않음")

if __name__ == "__main__":
    unittest.main()

KUnit Thermal 테스트

/* tools/testing/selftests/thermal/ 기반 테스트 예시 */
#include <kunit/test.h>
#include <linux/thermal.h>

static void thermal_emul_test(struct kunit *test)
{
    struct thermal_zone_device *tz;
    int temp;

    tz = thermal_zone_get_zone_by_name("cpu-thermal");
    KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tz);

    /* 에뮬레이션 온도 설정 */
    tz->emul_temperature = 85000;
    thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);

    thermal_zone_get_temp(tz, &temp);
    KUNIT_EXPECT_EQ(test, temp, 85000);
}
테스트 시나리오 에뮬레이션 온도 기대 결과
정상 동작 40000 (40°C) 모든 cooling device state = 0
Passive trip 도달 trip_passive + 1000 CPU freq 제한 시작
Active trip 도달 trip_active + 1000 Fan cooling 활성화
Critical trip 도달 trip_critical + 1000 시스템 shutdown 호출
히스테리시스 검증 trip - hysteresis - 1000 cooling 해제 확인
에뮬레이션 해제 0 실제 센서 온도 복원

성능 고려사항

Polling Delay 조정

온도 폴링 주기는 응답 속도와 CPU 오버헤드 사이의 트레이드오프입니다.

/* 폴링 주기 예시 */
passive_delay = 10000;  /* 10초 (passive cooling 시) */
polling_delay = 0;       /* 0 = 인터럽트 기반 */

Hysteresis (히스테리시스)

온도가 임계값 근처에서 진동할 때 cooling device가 계속 on/off되는 것을 방지합니다.

struct thermal_trip {
    int temperature;        /* trip 온도 */
    int hysteresis;         /* 히스테리시스 (milli-Celsius) */
    enum thermal_trip_type type;
};

/* 예: trip_temp=80°C, hysteresis=5°C */
/*   → 85°C 초과 시 cooling 시작 */
/*   → 75°C 이하로 떨어지면 cooling 중단 */
코드 설명
  • temperaturetrip point의 임계 온도(milli-Celsius)입니다. 이 온도를 초과하면 해당 trip에 바인딩된 cooling device가 활성화됩니다. include/linux/thermal.h에 정의됩니다.
  • hysteresis온도 진동 방지를 위한 히스테리시스 값입니다. cooling 시작 온도(temperature)와 중단 온도(temperature - hysteresis)에 차이를 두어, 임계값 근처에서 cooling device가 반복적으로 켜지고 꺼지는 것을 방지합니다.
  • typeTHERMAL_TRIP_ACTIVE, THERMAL_TRIP_PASSIVE, THERMAL_TRIP_HOT, THERMAL_TRIP_CRITICAL 중 하나입니다. CRITICALorderly_poweroff()를 트리거하며, PASSIVE는 주파수 제한 등 소프트웨어 기반 cooling을 활성화합니다.
  • 예시 (80°C, 5°C)trip_temp가 80000(80°C)이고 hysteresis가 5000(5°C)이면, 온도가 80°C를 초과할 때 cooling이 시작되고 75°C 아래로 내려가야 cooling이 중단됩니다. 이 5°C 간격이 불필요한 on/off 반복을 방지합니다.

Polling vs Interrupt 성능 비교

항목 Polling 방식 Interrupt 방식
CPU 오버헤드 polling_delay마다 wakeup 이벤트 시에만 처리
반응 지연 최대 polling_delay만큼 즉시 (IRQ latency)
전력 소비 주기적 wakeup으로 idle 깨짐 유휴 시 전력 소비 없음
구현 복잡도 낮음 (get_temp만 구현) 높음 (set_trips + IRQ 핸들러(Handler))
설정 polling_delay > 0 polling_delay = 0
권장 환경 데스크톱/서버 모바일/임베디드 (배터리)
최적화 팁: 모바일 환경에서는 polling_delay=0 (interrupt 기반) + set_trips() 콜백을 구현하여 CPU wakeup을 최소화하세요. 하드웨어가 interrupt를 지원하지 않으면 polling_delay_passive=100 (100ms), polling_delay=2000 (2초)가 적절한 시작점입니다.

커널 설정

CONFIG_THERMAL=y                       # Thermal 서브시스템
CONFIG_THERMAL_HWMON=y                 # hwmon 통합
CONFIG_THERMAL_GOV_STEP_WISE=y         # Step-Wise Governor
CONFIG_THERMAL_GOV_FAIR_SHARE=y        # Fair-Share Governor
CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y   # Power Allocator Governor
CONFIG_THERMAL_GOV_BANG_BANG=y         # Bang-Bang Governor
CONFIG_THERMAL_GOV_USER_SPACE=y        # Userspace Governor
CONFIG_CPU_THERMAL=y                   # CPUFreq Cooling
CONFIG_DEVFREQ_THERMAL=y               # Devfreq Cooling
CONFIG_THERMAL_EMULATION=y             # 온도 에뮬레이션 (테스트용)
CONFIG_THERMAL_STATISTICS=y            # Cooling 통계

# 플랫폼 특화
CONFIG_ACPI_THERMAL=y                  # ACPI Thermal Zone
CONFIG_INTEL_POWERCLAMP=y              # Intel Idle Injection
CONFIG_X86_PKG_TEMP_THERMAL=y          # Intel Package Temperature
CONFIG_INT340X_THERMAL=y               # Intel INT3400/INT3403 DPTF

임베디드 최소 Thermal 설정

# 임베디드 ARM SoC 최소 thermal 설정
CONFIG_THERMAL=y
CONFIG_THERMAL_OF=y                     # Device Tree thermal 지원
CONFIG_THERMAL_GOV_STEP_WISE=y         # 기본 governor
CONFIG_CPU_THERMAL=y                   # CPUFreq Cooling
CONFIG_THERMAL_EMULATION=y             # 테스트용 (개발 빌드만)

# IPA 사용 시 추가
CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y
CONFIG_DEVFREQ_THERMAL=y               # GPU devfreq cooling
CONFIG_ENERGY_MODEL=y                  # Energy Model (IPA 필수)

# 불필요한 항목 제거 (Flash 절약)
# CONFIG_THERMAL_HWMON is not set     # hwmon 불필요 시
# CONFIG_THERMAL_STATISTICS is not set # 통계 불필요 시
# CONFIG_ACPI_THERMAL is not set      # ARM이므로 ACPI 없음

Common Issues

주의: Thermal 서브시스템 오류는 하드웨어 손상을 초래할 수 있습니다.
  • Critical 온도 미설정_CRT 메서드 누락 시 시스템이 과열로 손상될 수 있음
  • Passive Cooling 무한 루프 — 너무 짧은 passive_delay로 CPU 부하 증가
  • Cooling Device 미바인딩 — thermal zone과 cooling device가 연결되지 않아 cooling 동작 안 함
  • ACPI Thermal Zone 초기화 실패 — BIOS 버그로 _TMP 메서드 실패

Troubleshooting

# Thermal zone 정보 확인
# cat /sys/class/thermal/thermal_zone0/type
# cat /sys/class/thermal/thermal_zone0/temp
# cat /sys/class/thermal/thermal_zone0/mode

# Cooling device 바인딩 확인
# ls -l /sys/class/thermal/thermal_zone0/cdev*

# ACPI thermal zone 디버깅
# dmesg | grep -i thermal
# dmesg | grep -i acpi

# Thermal emulation (테스트용)
# echo 85000 > /sys/class/thermal/thermal_zone0/emul_temp

참고 자료

커널 공식 문서

LWN.net 기사

도구 및 유틸리티

커널 소스

관련 문서:
  • RAPL & Powercap — Intel 전력 제한과 thermal 연계
  • CPUFreq — CPU 주파수 스케일링(Frequency Scaling)과 thermal cooling 통합
다음 학습: