hwmon (Hardware Monitoring)
hwmon 서브시스템을 서버·임베디드 하드웨어 상태 관측의 표준 인터페이스 관점에서 심층 분석합니다. 온도·전압·전류·전력·팬 센서 채널 모델과 단위 규약, sysfs 속성 설계 원칙, 임계값 알람과 hysteresis 운용, I2C/PMBus 기반 센서 드라이버 통합, thermal 서브시스템 및 fancontrol/lm-sensors 연계, 샘플링 주기와 필터링에 따른 정확도·오버헤드 균형, 보드 캘리브레이션과 오탐 방지, 현장 모니터링 자동화를 위한 실전 패턴까지 폭넓게 다룹니다.
핵심 요약
- hwmon core — 센서 드라이버를 표준 인터페이스로 묶는 공통 계층
- sysfs — 사용자 공간이 읽고 쓰는 표준 파일 인터페이스
- 라벨/채널 규약 —
temp1_input,fan1_input같은 일관된 네이밍 - 임계값 관리 — 경고/치명 온도, 팬 최소 RPM 등 한계값 모니터링
- lm-sensors — 현장에서 가장 널리 쓰는 userspace 도구
단계별 이해
- 채널 이름부터 읽기
temp*,fan*,in*,power*패턴으로 센서 유형을 분류합니다. - 단위 해석하기
milli/micro 단위를 실제 값으로 변환해 오탐(예: 45000을 45도 대신 45000도로 오해) 을 방지합니다. - 임계값 연결하기
*_max,*_crit,*_alarm조합으로 알람 조건을 점검합니다. - 운영 자동화 적용
sensors,fancontrol, 모니터링 에이전트로 주기 수집/경보를 구성합니다.
개요
hwmon(Hardware Monitoring) 서브시스템은 다양한 하드웨어 센서를 표준화된 방식으로 커널에 통합합니다.
센서 종류
| 센서 타입 | 측정 대상 | sysfs 파일 |
|---|---|---|
| Temperature | 온도 (milli-Celsius) | temp[1-*]_input |
| Voltage | 전압 (milli-Volts) | in[0-*]_input |
| Fan | 팬 속도 (RPM) | fan[1-*]_input |
| PWM | 팬 제어 (0-255) | pwm[1-*] |
| Current | 전류 (milli-Amperes) | curr[1-*]_input |
| Power | 전력 (micro-Watts) | power[1-*]_input |
| Energy | 에너지 (micro-Joules) | energy[1-*]_input |
| Humidity | 습도 (milli-percent) | humidity[1-*]_input |
hwmon 아키텍처
sysfs 인터페이스
hwmon sysfs 구조
sysfs 사용 예시
# hwmon 디바이스 목록
$ ls /sys/class/hwmon/
hwmon0 hwmon1 hwmon2
# 드라이버 이름 확인
$ cat /sys/class/hwmon/hwmon0/name
coretemp
# CPU 온도 읽기 (milli-Celsius → Celsius)
$ cat /sys/class/hwmon/hwmon0/temp1_input
45000
$ awk '{print $1/1000 "°C"}' /sys/class/hwmon/hwmon0/temp1_input
45°C
# 팬 속도 확인
$ cat /sys/class/hwmon/hwmon2/fan1_input
1234
# PWM 값 읽기/쓰기 (0-255)
$ cat /sys/class/hwmon/hwmon2/pwm1
128
$ echo 200 | sudo tee /sys/class/hwmon/hwmon2/pwm1
lm-sensors
lm-sensors는 hwmon 데이터를 편리하게 읽고 설정하는 userspace 도구입니다.
sensors-detect
# lm-sensors 설치
$ sudo apt install lm-sensors
# 센서 자동 감지
$ sudo sensors-detect
# Do you want to probe now? (YES/no): YES
# (여러 질문에 YES 응답)
# /etc/modules 또는 /etc/modules-load.d/에 모듈 자동 로드 설정
sensors 명령
# 모든 센서 값 출력
$ sensors
coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +45.0°C (high = +80.0°C, crit = +100.0°C)
Core 0: +43.0°C (high = +80.0°C, crit = +100.0°C)
Core 1: +44.0°C (high = +80.0°C, crit = +100.0°C)
nct6775-isa-0a20
Adapter: ISA adapter
Vcore: +1.23 V (min = +0.00 V, max = +1.74 V)
in1: +1.02 V (min = +0.00 V, max = +0.00 V) ALARM
fan1: 1234 RPM (min = 0 RPM)
fan2: 987 RPM (min = 0 RPM)
# 특정 칩만 출력
$ sensors coretemp-isa-0000
# 화씨 단위로 출력
$ sensors -f
# 원시 값 출력 (단위 없음)
$ sensors -u
센서 설정 (/etc/sensors3.conf)
# 센서 라벨 커스터마이징
chip "coretemp-isa-*"
label temp1 "Package Temp"
label temp2 "Core 0 Temp"
label temp3 "Core 1 Temp"
# 전압 보정 (스케일링)
chip "nct6775-*"
label in0 "Vcore"
compute in0 @ * 2, @ / 2 # 2배 스케일
# 센서 무시
chip "nct6775-*"
ignore in1 # in1 센서 숨김
# 임계값 설정
chip "coretemp-*"
set temp1_max 85
set temp1_crit 100
hwmon 드라이버 작성
hwmon_chip_info 구조체
#include <linux/hwmon.h>
/* 채널 정보 */
static const struct hwmon_channel_info *my_info[] = {
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT,
HWMON_T_INPUT),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT),
NULL
};
/* hwmon ops */
static umode_t my_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_temp:
if (attr == hwmon_temp_input || attr == hwmon_temp_max)
return S_IRUGO;
if (attr == hwmon_temp_crit)
return S_IRUGO | S_IWUSR;
break;
case hwmon_fan:
return S_IRUGO;
default:
break;
}
return 0;
}
static int my_hwmon_read(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct my_data *data = dev_get_drvdata(dev);
switch (type) {
case hwmon_temp:
if (attr == hwmon_temp_input) {
*val = read_temperature(data, channel);
return 0;
}
break;
case hwmon_fan:
if (attr == hwmon_fan_input) {
*val = read_fan_rpm(data, channel);
return 0;
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static int my_hwmon_write(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct my_data *data = dev_get_drvdata(dev);
switch (type) {
case hwmon_temp:
if (attr == hwmon_temp_crit) {
set_temp_crit(data, channel, val);
return 0;
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static const struct hwmon_ops my_hwmon_ops = {
.is_visible = my_hwmon_is_visible,
.read = my_hwmon_read,
.write = my_hwmon_write,
};
static const struct hwmon_chip_info my_chip_info = {
.ops = &my_hwmon_ops,
.info = my_info,
};
hwmon 디바이스 등록
/* hwmon 디바이스 등록 */
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct my_data *data;
struct device *hwmon_dev;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
/* hwmon 디바이스 등록 */
hwmon_dev = devm_hwmon_device_register_with_info(dev, "mydriver",
data, &my_chip_info,
NULL);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
return 0;
}
주요 hwmon 드라이버
coretemp (Intel CPU)
# coretemp 모듈 로드
$ sudo modprobe coretemp
# CPU 온도 확인
$ sensors coretemp-isa-0000
coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +45.0°C (high = +80.0°C, crit = +100.0°C)
Core 0: +43.0°C (high = +80.0°C, crit = +100.0°C)
Core 1: +44.0°C (high = +80.0°C, crit = +100.0°C)
Core 2: +45.0°C (high = +80.0°C, crit = +100.0°C)
Core 3: +42.0°C (high = +80.0°C, crit = +100.0°C)
lm75 (I2C 온도 센서)
/* Device Tree에서 lm75 정의 */
&i2c0 {
lm75@48 {
compatible = "national,lm75";
reg = <0x48>;
};
};
# sysfs 확인
$ cat /sys/class/hwmon/hwmon1/name
lm75
$ cat /sys/class/hwmon/hwmon1/temp1_input
25000
nct6775 (Super-I/O)
PMBus/SMBus
PMBus는 전원 공급 장치(PSU) 모니터링에 사용되는 I2C/SMBus 기반 프로토콜입니다.
PMBus 드라이버
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/pmbus.h>
/* PMBus 디바이스 정보 */
static struct pmbus_driver_info my_pmbus_info = {
.pages = 1,
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_POUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_FAN12,
};
/* I2C 드라이버 probe */
static int my_pmbus_probe(struct i2c_client *client)
{
return pmbus_do_probe(client, &my_pmbus_info);
}
Fan Control
pwmconfig
# PWM 팬 제어 자동 설정
$ sudo pwmconfig
# (마법사가 각 팬/센서 조합을 테스트)
# 설정 파일 생성: /etc/fancontrol
fancontrol 서비스
# /etc/fancontrol 예시
INTERVAL=10
DEVPATH=hwmon2=devices/platform/nct6775.2592
DEVNAME=hwmon2=nct6775
FCTEMPS=hwmon2/pwm1=hwmon0/temp2_input
FCFANS=hwmon2/pwm1=hwmon2/fan1_input
MINTEMP=hwmon2/pwm1=40
MAXTEMP=hwmon2/pwm1=70
MINSTART=hwmon2/pwm1=150
MINSTOP=hwmon2/pwm1=100
# fancontrol 데몬 시작
$ sudo systemctl start fancontrol
$ sudo systemctl enable fancontrol
커널 설정
CONFIG_HWMON=y # hwmon 서브시스템
# CPU 센서
CONFIG_SENSORS_CORETEMP=m # Intel Core/Xeon 온도
CONFIG_SENSORS_K10TEMP=m # AMD K10/K11 온도
# I2C 센서
CONFIG_SENSORS_LM75=m # LM75 온도 센서
CONFIG_SENSORS_LM90=m # LM90/ADM1032 온도 센서
# Super-I/O
CONFIG_SENSORS_NCT6775=m # Nuvoton NCT6775 계열
CONFIG_SENSORS_IT87=m # ITE IT87 계열
# PMBus
CONFIG_SENSORS_PMBUS=m # PMBus 드라이버
# 기타
CONFIG_SENSORS_DELL_SMM=m # Dell 팬 제어
CONFIG_SENSORS_APPLESMC=m # Apple SMC
hwmon_chip_info 심화
hwmon 서브시스템의 새로운 등록 API(devm_hwmon_device_register_with_info)는 세 가지 핵심 구조체를 중심으로 동작합니다:
hwmon_chip_info, hwmon_channel_info, hwmon_ops. 이 구조체들의 관계와 각 콜백 함수의 역할을 상세히 살펴봅니다.
struct hwmon_chip_info
hwmon_chip_info는 hwmon 디바이스 등록에 필요한 모든 메타데이터를 담는 최상위 구조체입니다.
/* include/linux/hwmon.h */
struct hwmon_chip_info {
const struct hwmon_ops *ops; /* 콜백 함수 집합 */
const struct hwmon_channel_info const **info; /* 채널 배열 (NULL 종료) */
};
hwmon_chip_info는 const로 선언하여 .rodata 섹션에 배치합니다.
런타임에 변경할 필요가 없는 정적 메타데이터이므로, 커널 메모리 보호 정책에 부합합니다.
struct hwmon_channel_info
hwmon_channel_info는 특정 센서 유형의 채널 구성을 정의합니다. 매크로 HWMON_CHANNEL_INFO()를 통해 간결하게 초기화합니다.
/* 채널 정보 구조체 */
struct hwmon_channel_info {
enum hwmon_sensor_types type; /* hwmon_temp, hwmon_fan, ... */
const u32 *config; /* 채널별 속성 비트마스크 배열 (0 종료) */
};
/* 매크로를 사용한 초기화 예제 */
static const struct hwmon_channel_info *my_hwmon_info[] = {
/* 온도 채널 2개: temp1은 input/max/crit/label, temp2는 input만 */
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT),
/* 팬 채널 1개: input/min/alarm */
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM),
/* 전압 채널 3개 */
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT),
/* PWM 채널 1개 */
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
NULL
};
struct hwmon_ops 콜백 상세
hwmon_ops는 네 가지 콜백 함수를 정의하며, 각각의 역할이 명확히 구분됩니다.
| 콜백 | 시그니처 | 역할 | 필수 여부 |
|---|---|---|---|
| is_visible | umode_t (*)(const void *, enum hwmon_sensor_types, u32, int) |
sysfs 속성의 권한(읽기/쓰기) 결정 | 필수 |
| read | int (*)(struct device *, enum hwmon_sensor_types, u32, int, long *) |
정수형 센서 값 읽기 | 읽기 속성이 있으면 필수 |
| write | int (*)(struct device *, enum hwmon_sensor_types, u32, int, long) |
정수형 센서 임계값 쓰기 | 쓰기 속성이 있으면 필수 |
| read_string | int (*)(struct device *, enum hwmon_sensor_types, u32, int, const char **) |
문자열 속성 읽기 (label 등) | label 속성이 있으면 필수 |
struct hwmon_ops {
umode_t (*is_visible)(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel);
int (*read)(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long *val);
int (*write)(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long val);
int (*read_string)(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel,
const char **str);
};
is_visible 콜백 구현 패턴
is_visible은 센서 유형, 속성, 채널 번호를 기반으로 sysfs 파일의 권한을 결정합니다.
0을 반환하면 해당 속성이 sysfs에 노출되지 않습니다.
static umode_t my_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct my_data *data = drvdata;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_label:
return 0444; /* 읽기 전용 */
case hwmon_temp_max:
case hwmon_temp_crit:
return 0644; /* 읽기/쓰기 */
case hwmon_temp_max_hyst:
/* 채널 0만 히스테리시스 지원 */
return (channel == 0) ? 0644 : 0;
default:
return 0;
}
case hwmon_fan:
/* 팬 채널이 실제로 존재하는지 하드웨어 확인 */
if (channel >= data->fan_count)
return 0;
return 0444;
case hwmon_pwm:
if (attr == hwmon_pwm_input)
return 0644;
if (attr == hwmon_pwm_enable)
return 0644;
return 0;
default:
return 0;
}
}
is_visible의 첫 번째 인자는 const void *drvdata이지 struct device *가 아닙니다.
devm_hwmon_device_register_with_info()에 전달한 drvdata 포인터가 그대로 전달됩니다.
dev_get_drvdata()를 호출하면 안 됩니다.
read_string 콜백
read_string은 *_label 속성에 대한 문자열을 반환합니다. 반환하는 문자열은 드라이버가 관리하는 정적 메모리여야 합니다.
static const char * const my_temp_labels[] = {
"CPU Package",
"Core 0",
"Core 1",
"Core 2",
"Core 3",
};
static int my_read_string(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel,
const char **str)
{
if (type == hwmon_temp && attr == hwmon_temp_label) {
if (channel >= ARRAY_SIZE(my_temp_labels))
return -EOPNOTSUPP;
*str = my_temp_labels[channel];
return 0;
}
return -EOPNOTSUPP;
}
hwmon_chip_info 초기화 완전 예제
#include <linux/hwmon.h>
#include <linux/module.h>
/* 드라이버 전용 데이터 */
struct my_sensor_data {
struct device *dev;
struct regmap *regmap;
int fan_count;
long temp_max[4];
long temp_crit[4];
struct mutex lock;
};
/* 채널 정보 정의 */
static const struct hwmon_channel_info *my_sensor_info[] = {
HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM,
HWMON_I_INPUT),
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
NULL
};
/* ops 콜백 - is_visible, read, write, read_string 구현 */
static const struct hwmon_ops my_sensor_ops = {
.is_visible = my_sensor_is_visible,
.read = my_sensor_read,
.write = my_sensor_write,
.read_string = my_sensor_read_string,
};
/* 최종 chip_info 구조체 */
static const struct hwmon_chip_info my_sensor_chip_info = {
.ops = &my_sensor_ops,
.info = my_sensor_info,
};
chip 채널에 HWMON_C_REGISTER_TZ 플래그를 설정하면,
hwmon 코어가 자동으로 thermal zone을 등록합니다. 별도의 thermal_zone_device_register() 호출이 불필요합니다.
sysfs 인터페이스 상세
hwmon의 sysfs 인터페이스는 엄격한 명명 규칙과 단위 규약을 따릅니다. 이를 정확히 이해해야 사용자 공간 도구(lm-sensors, collectd 등)와의 호환성을 보장할 수 있습니다.
명명 규칙
모든 hwmon sysfs 속성은 <type><number>_<item> 형식을 따릅니다.
| 구성요소 | 설명 | 예 |
|---|---|---|
| type | 센서 유형 접두사 | temp, fan, in, curr, power, energy, humidity |
| number | 채널 번호 (1-based, in은 0-based) | 1, 2, 3 |
| item | 속성 종류 | input, max, min, crit, alarm, label |
temp1, fan1, curr1).
예외적으로 전압(in) 채널은 0-based입니다(in0, in1, ...).
이는 역사적 이유로 in0이 보통 CPU 코어 전압(Vcore)을 나타내기 때문입니다.
단위 규약
| 센서 유형 | sysfs 단위 | 실제 단위 | 변환 예 |
|---|---|---|---|
| 온도 (temp) | milli-Celsius (m°C) | °C | 45000 = 45.000°C |
| 전압 (in) | milli-Volts (mV) | V | 1230 = 1.230V |
| 전류 (curr) | milli-Amperes (mA) | A | 5200 = 5.200A |
| 전력 (power) | micro-Watts (uW) | W | 65000000 = 65W |
| 에너지 (energy) | micro-Joules (uJ) | J | 1234567890 = 1234.567890J |
| 팬 (fan) | RPM | RPM | 1200 = 1200RPM |
| PWM | 0-255 (dimensionless) | 듀티 비율 | 128 = 약 50% |
| 습도 (humidity) | milli-percent (m%) | % | 45000 = 45.000% |
속성 유형별 분류
| 속성 접미사 | 의미 | 읽기/쓰기 | 설명 |
|---|---|---|---|
_input |
현재 측정값 | RO | 하드웨어에서 직접 읽은 실시간 센서 값 |
_max |
최대 임계값 | RW | 경고 수준 상한 |
_min |
최소 임계값 | RW | 경고 수준 하한 |
_crit |
치명적 임계값 | RW | 즉각 대응 필요 수준 |
_lcrit |
하한 치명적 임계값 | RW | 하한 즉각 대응 수준 |
_emergency |
비상 임계값 | RW | 하드웨어 보호 동작 트리거 |
_max_hyst |
최대 히스테리시스 | RW | 알람 해제 지점 (max - hyst) |
_crit_hyst |
치명 히스테리시스 | RW | 치명 알람 해제 지점 |
_alarm |
알람 상태 | RO | 0=정상, 1=알람 발생 |
_label |
라벨 | RO | 사용자 친화적 이름 (예: "CPU Core 0") |
_enable |
활성화 | RW | 0=비활성, 1=활성 |
_fault |
센서 오류 | RO | 0=정상, 1=센서 읽기 실패 |
온도 채널 sysfs 속성 전체 목록
| 속성 | 권한 | 단위 | 설명 |
|---|---|---|---|
tempN_input | RO | m°C | 현재 온도 |
tempN_max | RW | m°C | 경고 상한 |
tempN_max_hyst | RW | m°C | 경고 해제 지점 |
tempN_min | RW | m°C | 경고 하한 |
tempN_min_hyst | RW | m°C | 하한 해제 지점 |
tempN_crit | RW | m°C | 치명적 상한 |
tempN_crit_hyst | RW | m°C | 치명적 해제 지점 |
tempN_lcrit | RW | m°C | 치명적 하한 |
tempN_emergency | RW | m°C | 비상 온도 (HW 셧다운) |
tempN_emergency_hyst | RW | m°C | 비상 해제 지점 |
tempN_alarm | RO | bool | 알람 상태 |
tempN_max_alarm | RO | bool | max 초과 알람 |
tempN_min_alarm | RO | bool | min 미만 알람 |
tempN_crit_alarm | RO | bool | crit 초과 알람 |
tempN_emergency_alarm | RO | bool | emergency 초과 알람 |
tempN_label | RO | 문자열 | 센서 라벨 |
tempN_enable | RW | bool | 센서 활성화 |
tempN_type | RW | 정수 | 센서 종류 (1=PII, 2=PIIIN 등) |
tempN_offset | RW | m°C | 보정 오프셋 |
tempN_fault | RO | bool | 센서 읽기 실패 |
tempN_rated_min | RO | m°C | 센서 측정 범위 하한 |
tempN_rated_max | RO | m°C | 센서 측정 범위 상한 |
팬 채널 sysfs 속성 전체 목록
| 속성 | 권한 | 단위 | 설명 |
|---|---|---|---|
fanN_input | RO | RPM | 현재 팬 속도 |
fanN_min | RW | RPM | 최소 팬 속도 임계값 |
fanN_max | RO | RPM | 최대 팬 속도 |
fanN_div | RW | 정수 | 팬 분주비 (1, 2, 4, 8, ...) |
fanN_pulses | RW | 정수 | 회전당 펄스 수 (보통 2) |
fanN_target | RW | RPM | 목표 팬 속도 |
fanN_alarm | RO | bool | 팬 속도 알람 |
fanN_min_alarm | RO | bool | 최소 RPM 미달 알람 |
fanN_fault | RO | bool | 팬 센서/배선 오류 |
fanN_label | RO | 문자열 | 팬 라벨 |
fanN_enable | RW | bool | 팬 모니터링 활성화 |
온도 센서 드라이버 작성
hwmon 서브시스템에 온도 센서를 등록하는 완전한 드라이버 예제를 단계별로 살펴봅니다.
I2C 온도 센서를 대상으로 하며, devm_hwmon_device_register_with_info() API를 사용합니다.
드라이버 전체 구조
/*
* my_temp_sensor.c - I2C 온도 센서 hwmon 드라이버 예제
*
* 가상의 I2C 온도 센서를 위한 hwmon 드라이버.
* 2채널 온도 읽기, 임계값 설정, 라벨 지원.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/regmap.h>
#include <linux/mutex.h>
/* 센서 레지스터 맵 */
#define REG_TEMP_LOCAL 0x00 /* 로컬 온도 */
#define REG_TEMP_REMOTE 0x01 /* 원격 온도 */
#define REG_TEMP_LOCAL_HI 0x05 /* 로컬 상한 */
#define REG_TEMP_REMOTE_HI 0x07 /* 원격 상한 */
#define REG_TEMP_LOCAL_CRIT 0x20 /* 로컬 치명 */
#define REG_TEMP_REMOTE_CRIT 0x19 /* 원격 치명 */
#define REG_STATUS 0x02 /* 상태 레지스터 */
#define REG_CONFIG 0x03 /* 설정 레지스터 */
struct my_temp_data {
struct regmap *regmap;
struct mutex lock;
};
/* 레지스터 값을 milli-Celsius로 변환 */
static long reg_to_mc(unsigned int reg_val)
{
int temp = (s8)reg_val; /* 부호 확장 */
return temp * 1000; /* Celsius → milli-Celsius */
}
/* milli-Celsius를 레지스터 값으로 변환 */
static u8 mc_to_reg(long mc)
{
long celsius = DIV_ROUND_CLOSEST(mc, 1000);
return (u8)clamp_val(celsius, -128, 127);
}
/* 온도 읽기 레지스터 맵 */
static const u8 temp_input_regs[] = {
REG_TEMP_LOCAL, REG_TEMP_REMOTE
};
static const u8 temp_max_regs[] = {
REG_TEMP_LOCAL_HI, REG_TEMP_REMOTE_HI
};
static const u8 temp_crit_regs[] = {
REG_TEMP_LOCAL_CRIT, REG_TEMP_REMOTE_CRIT
};
static const char * const temp_labels[] = {
"Local", "Remote"
};
콜백 함수 구현
static umode_t my_temp_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type != hwmon_temp)
return 0;
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_label:
return 0444;
case hwmon_temp_max:
case hwmon_temp_crit:
return 0644;
default:
return 0;
}
}
static int my_temp_read(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct my_temp_data *data = dev_get_drvdata(dev);
unsigned int regval;
int ret;
if (type != hwmon_temp || channel >= 2)
return -EOPNOTSUPP;
mutex_lock(&data->lock);
switch (attr) {
case hwmon_temp_input:
ret = regmap_read(data->regmap,
temp_input_regs[channel], ®val);
break;
case hwmon_temp_max:
ret = regmap_read(data->regmap,
temp_max_regs[channel], ®val);
break;
case hwmon_temp_crit:
ret = regmap_read(data->regmap,
temp_crit_regs[channel], ®val);
break;
default:
ret = -EOPNOTSUPP;
break;
}
mutex_unlock(&data->lock);
if (ret)
return ret;
*val = reg_to_mc(regval);
return 0;
}
static int my_temp_write(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct my_temp_data *data = dev_get_drvdata(dev);
u8 regval = mc_to_reg(val);
int ret;
if (type != hwmon_temp || channel >= 2)
return -EOPNOTSUPP;
mutex_lock(&data->lock);
switch (attr) {
case hwmon_temp_max:
ret = regmap_write(data->regmap,
temp_max_regs[channel], regval);
break;
case hwmon_temp_crit:
ret = regmap_write(data->regmap,
temp_crit_regs[channel], regval);
break;
default:
ret = -EOPNOTSUPP;
break;
}
mutex_unlock(&data->lock);
return ret;
}
static int my_temp_read_string(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel,
const char **str)
{
if (type == hwmon_temp && attr == hwmon_temp_label
&& channel < 2) {
*str = temp_labels[channel];
return 0;
}
return -EOPNOTSUPP;
}
probe 함수 및 모듈 등록
/* 채널 정의 */
static const struct hwmon_channel_info *my_temp_info[] = {
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL),
NULL
};
static const struct hwmon_ops my_temp_ops = {
.is_visible = my_temp_is_visible,
.read = my_temp_read,
.write = my_temp_write,
.read_string = my_temp_read_string,
};
static const struct hwmon_chip_info my_temp_chip_info = {
.ops = &my_temp_ops,
.info = my_temp_info,
};
/* regmap 설정 */
static const struct regmap_config my_temp_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x3F,
.cache_type = REGCACHE_RBTREE,
};
static int my_temp_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct my_temp_data *data;
struct device *hwmon_dev;
/* 디바이스 데이터 할당 */
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
/* regmap 초기화 */
data->regmap = devm_regmap_init_i2c(client,
&my_temp_regmap_config);
if (IS_ERR(data->regmap))
return PTR_ERR(data->regmap);
mutex_init(&data->lock);
/* hwmon 디바이스 등록 */
hwmon_dev = devm_hwmon_device_register_with_info(
dev, "my_temp_sensor", data,
&my_temp_chip_info, NULL);
if (IS_ERR(hwmon_dev))
return dev_err_probe(dev, PTR_ERR(hwmon_dev),
"hwmon 등록 실패\n");
dev_info(dev, "hwmon 센서 등록 완료\n");
return 0;
}
static const struct i2c_device_id my_temp_id[] = {
{ "my-temp-sensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_temp_id);
static const struct of_device_id my_temp_of_match[] = {
{ .compatible = "vendor,my-temp-sensor" },
{ }
};
MODULE_DEVICE_TABLE(of, my_temp_of_match);
static struct i2c_driver my_temp_driver = {
.driver = {
.name = "my-temp-sensor",
.of_match_table = my_temp_of_match,
},
.probe = my_temp_probe,
.id_table = my_temp_id,
};
module_i2c_driver(my_temp_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My Temperature Sensor hwmon driver");
MODULE_LICENSE("GPL");
devm_hwmon_device_register_with_info()는 디바이스 관리(device-managed) API입니다.
드라이버 제거 시 자동으로 hwmon 디바이스가 해제되므로 별도의 remove 콜백이 불필요합니다.
dev_err_probe()는 -EPROBE_DEFER를 올바르게 처리하는 에러 보고 함수입니다.
Device Tree 바인딩
/* Device Tree에서 온도 센서 정의 */
&i2c1 {
status = "okay";
temp_sensor: temperature-sensor@4c {
compatible = "vendor,my-temp-sensor";
reg = <0x4c>;
/* 선택적: thermal zone 연결 */
#thermal-sensor-cells = <1>;
};
};
/* thermal zone에서 hwmon 센서 참조 */
thermal-zones {
cpu-thermal {
polling-delay-passive = <250>;
polling-delay = <1000>;
thermal-sensors = <&temp_sensor 0>;
trips {
cpu_alert: cpu-alert {
temperature = <75000>;
hysteresis = <2000>;
type = "passive";
};
cpu_crit: cpu-crit {
temperature = <100000>;
hysteresis = <5000>;
type = "critical";
};
};
};
};
팬 제어 (Fan Control)
hwmon 서브시스템에서 팬 제어는 PWM(Pulse Width Modulation) 방식으로 이루어집니다. PWM 듀티 사이클을 조절하여 팬 회전 속도를 제어하며, 온도 센서와 연동하여 자동 팬 제어를 구성할 수 있습니다.
PWM 팬 제어 기초
PWM 값은 0(정지)부터 255(최대 속도)까지의 범위를 사용합니다. 팬 속도는 PWM 듀티 사이클에 비례합니다.
| PWM 값 | 듀티 사이클 | 의미 |
|---|---|---|
0 | 0% | 팬 정지 |
64 | ~25% | 저속 동작 |
128 | ~50% | 중속 동작 |
192 | ~75% | 고속 동작 |
255 | 100% | 최대 속도 |
pwm_enable 모드
pwmN_enable 속성은 팬 제어 모드를 결정합니다. 이 값에 따라 팬 동작 방식이 크게 달라집니다.
| 값 | 모드 | 설명 | 사용 시나리오 |
|---|---|---|---|
0 |
Full Speed | PWM 제어 비활성. 팬이 항상 최대 속도로 동작 | 디버깅, 고부하 테스트 |
1 |
Manual | 사용자가 직접 pwmN 값 설정 |
fancontrol, 커스텀 제어 |
2 |
Automatic | 하드웨어/BIOS가 온도 기반 자동 제어 | 기본 동작, 서버 환경 |
3 |
Smart Fan III | 칩 내장 스마트 팬 알고리즘 (칩 의존) | 특수 Super-I/O 칩 |
4 |
Smart Fan IV | 확장 스마트 팬 (칩 의존) | Nuvoton NCT6775 등 |
5 |
Smart Fan V | 최신 스마트 팬 알고리즘 (칩 의존) | 최신 Super-I/O 칩 |
pwm_enable=0(Full Speed)은 팬이 최대 속도로 회전하므로 소음이 매우 큽니다.
반대로 pwm=0(Manual 모드에서)은 팬을 완전히 정지시키므로, 적절한 온도 모니터링 없이 사용하면 과열 위험이 있습니다.
팬 속도 계산
팬 속도(RPM)는 타코미터 펄스를 기반으로 계산됩니다. Super-I/O 칩은 보통 다음 공식을 사용합니다:
RPM = (클럭 주파수 * 60) / (카운트 값 * 분주비 * 펄스 수)
# 예: Nuvoton NCT6775
# 클럭 = 1.35MHz, 카운트 = 270, 분주비 = 2, 펄스 = 2
RPM = (1350000 * 60) / (270 * 2 * 2) = 75000 RPM? (실제로는 카운트가 더 큼)
# 실제 카운트 값 = 67500 → RPM = 1200
RPM = (1350000 * 60) / (67500 * 1 * 1) = 1200 RPM
# 팬 분주비 확인 및 조정 (일부 칩에서 지원)
$ cat /sys/class/hwmon/hwmon2/fan1_div
2
# 분주비 증가 시: 저속 팬 정밀도 향상, 고속 팬 반응 저하
# 분주비 감소 시: 고속 팬 반응 향상, 저속 팬 정밀도 저하
# 회전당 펄스 수 설정
$ cat /sys/class/hwmon/hwmon2/fan1_pulses
2
팬 제어 히스테리시스
온도가 임계값을 넘어 팬 속도를 올린 뒤, 온도가 조금만 내려가도 다시 느려지면 팬이 빈번하게 속도를 변경하며 불쾌한 소음을 유발합니다. 히스테리시스는 이 문제를 해결합니다.
# fancontrol 히스테리시스 설정 예시
# /etc/fancontrol 파일에서:
# MINTEMP: 이 온도 이하에서 팬 최소 속도
# MAXTEMP: 이 온도 이상에서 팬 최대 속도
# 사이 구간은 선형 보간
MINTEMP=hwmon2/pwm1=40 # 40°C 이하: 팬 최소
MAXTEMP=hwmon2/pwm1=70 # 70°C 이상: 팬 최대
MINSTART=hwmon2/pwm1=150 # 팬 시작 PWM (정지→회전 전환)
MINSTOP=hwmon2/pwm1=100 # 팬 정지 PWM (회전→정지 전환)
# MINSTART > MINSTOP 이면 히스테리시스 영역 존재
# 팬이 시작되려면 PWM >= 150 필요하지만
# 이미 회전 중이면 PWM >= 100까지 유지
수동 팬 제어 예제
# Manual 모드로 전환
$ echo 1 | sudo tee /sys/class/hwmon/hwmon2/pwm1_enable
# PWM 값 설정 (50% 속도)
$ echo 128 | sudo tee /sys/class/hwmon/hwmon2/pwm1
# 팬 속도 확인
$ cat /sys/class/hwmon/hwmon2/fan1_input
1200
# 다시 Auto 모드로 복귀
$ echo 2 | sudo tee /sys/class/hwmon/hwmon2/pwm1_enable
# 팬 최대 속도 강제 (비상 냉각)
$ echo 0 | sudo tee /sys/class/hwmon/hwmon2/pwm1_enable
전압/전류/전력 모니터링
hwmon 서브시스템은 온도와 팬 속도 외에도 전압(in), 전류(curr), 전력(power) 모니터링을 지원합니다. 서버 환경에서 전원 레일 모니터링과 전력 예산 관리에 핵심적인 기능입니다.
전압 채널 (in)
전압 채널은 in0부터 시작하는 0-based 번호를 사용합니다. 단위는 milli-Volts입니다.
# 전압 센서 값 읽기
$ cat /sys/class/hwmon/hwmon2/in0_input
1230 # 1.230V (Vcore)
$ cat /sys/class/hwmon/hwmon2/in3_input
3360 # 3.360V (+3.3V 레일)
# 전압 임계값 확인
$ cat /sys/class/hwmon/hwmon2/in0_min
700 # 최소 0.700V
$ cat /sys/class/hwmon/hwmon2/in0_max
1740 # 최대 1.740V
# 알람 상태
$ cat /sys/class/hwmon/hwmon2/in0_alarm
0 # 정상 (범위 내)
전류 채널 (curr)
전류 채널은 curr1부터 시작하는 1-based 번호를 사용합니다. 단위는 milli-Amperes입니다.
# 전류 값 읽기
$ cat /sys/class/hwmon/hwmon3/curr1_input
5200 # 5.200A
# 전류 임계값 설정
$ echo 10000 | sudo tee /sys/class/hwmon/hwmon3/curr1_max
10000 # 최대 10A
$ echo 15000 | sudo tee /sys/class/hwmon/hwmon3/curr1_crit
15000 # 치명 15A
전력 채널 (power)
전력 채널은 power1부터 시작합니다. 단위는 micro-Watts입니다.
# 전력 값 읽기
$ cat /sys/class/hwmon/hwmon3/power1_input
65000000 # 65W
# 전력 이력 최대값
$ cat /sys/class/hwmon/hwmon3/power1_input_highest
120000000 # 120W (피크)
# 전력 제한 (power capping)
$ cat /sys/class/hwmon/hwmon3/power1_cap
95000000 # 95W TDP 제한
$ echo 80000000 | sudo tee /sys/class/hwmon/hwmon3/power1_cap
80000000 # 80W로 제한 변경
전압/전류/전력 sysfs 속성 요약
| 채널 유형 | sysfs 접두사 | 주요 속성 | 단위 |
|---|---|---|---|
| 전압 (in) | inN_ |
input |
mV |
min / max | mV | ||
lcrit / crit | mV | ||
alarm | bool | ||
label | 문자열 | ||
enable | bool | ||
| 전류 (curr) | currN_ |
input |
mA |
min / max | mA | ||
lcrit / crit | mA | ||
alarm | bool | ||
label | 문자열 | ||
average | mA | ||
| 전력 (power) | powerN_ |
input |
uW |
average | uW | ||
cap | uW | ||
cap_max / cap_min | uW | ||
crit | uW | ||
input_highest | uW | ||
alarm | bool |
ACPI_POWER_METER 드라이버나
IPMI 기반 ipmi_si 드라이버가 전력 모니터링 데이터를 hwmon으로 노출합니다.
BMC(Baseboard Management Controller)를 통해 원격 전력 모니터링이 가능합니다.
전력 모니터링 드라이버 코드 패턴
/* 전력 채널 구현 예제 */
static const struct hwmon_channel_info *power_info[] = {
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
HWMON_I_ALARM | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL),
HWMON_CHANNEL_INFO(curr,
HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_CRIT |
HWMON_C_ALARM | HWMON_C_LABEL),
HWMON_CHANNEL_INFO(power,
HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CRIT |
HWMON_P_ALARM | HWMON_P_LABEL |
HWMON_P_INPUT_HIGHEST),
NULL
};
/* read 콜백에서 전력 값 계산 */
static int power_read(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct power_data *data = dev_get_drvdata(dev);
switch (type) {
case hwmon_power:
if (attr == hwmon_power_input) {
/* P = V * I (uW = mV * mA) */
long voltage_mv = read_voltage(data);
long current_ma = read_current(data);
*val = voltage_mv * current_ma; /* uW 단위 */
return 0;
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
PMBus 서브시스템
PMBus(Power Management Bus)는 SMBus(System Management Bus) 위에 구축된 전원 관리 프로토콜입니다.
전원 공급 장치(PSU), 전압 레귤레이터(VRM), 전력 변환기 등을 표준화된 방식으로 모니터링하고 제어합니다.
리눅스 커널의 PMBus 드라이버는 drivers/hwmon/pmbus/에 위치합니다.
PMBus 프로토콜 개요
PMBus는 SMBus의 명령 기반 통신을 확장하여 전원 관리에 필요한 표준 명령 세트를 정의합니다.
| 명령 코드 | 명령 | 설명 |
|---|---|---|
0x79 | STATUS_WORD | 종합 상태 (16비트) |
0x88 | READ_VIN | 입력 전압 읽기 |
0x89 | READ_IIN | 입력 전류 읽기 |
0x8B | READ_VOUT | 출력 전압 읽기 |
0x8C | READ_IOUT | 출력 전류 읽기 |
0x8D | READ_TEMPERATURE_1 | 온도 1 읽기 |
0x8E | READ_TEMPERATURE_2 | 온도 2 읽기 |
0x96 | READ_POUT | 출력 전력 읽기 |
0x97 | READ_PIN | 입력 전력 읽기 |
0x3C | IOUT_OC_FAULT_LIMIT | 출력 과전류 한계 |
0x4F | OT_FAULT_LIMIT | 과온도 한계 |
PMBus 데이터 포맷
PMBus는 두 가지 데이터 포맷을 사용합니다:
/* LINEAR11 포맷: 전압/전류/전력 값에 사용 */
/* 16비트 = 지수(5비트, 부호 있음) + 가수(11비트, 부호 있음) */
/* 실제 값 = 가수 * 2^지수 */
static long linear11_to_val(u16 raw)
{
s16 exponent = ((s16)raw) >> 11; /* 상위 5비트 (부호 확장) */
s16 mantissa = raw & 0x7FF; /* 하위 11비트 */
/* 11비트 부호 확장 */
if (mantissa & 0x400)
mantissa |= 0xF800;
if (exponent >= 0)
return mantissa << exponent;
else
return mantissa >> (-exponent);
}
/* LINEAR16 포맷: VOUT에 주로 사용 */
/* 16비트 가수, 지수는 VOUT_MODE 명령으로 별도 설정 */
pmbus_driver_info 구조체
#include <linux/pmbus.h>
struct pmbus_driver_info {
int pages; /* PMBus 페이지 수 */
int phases[PMBUS_PAGES]; /* 페이지별 위상 수 */
u32 func[PMBUS_PAGES]; /* 페이지별 기능 플래그 */
/* 선택적 콜백 */
int (*read_byte_data)(struct i2c_client *client,
int page, int reg);
int (*read_word_data)(struct i2c_client *client,
int page, int phase, int reg);
int (*write_word_data)(struct i2c_client *client,
int page, int reg, u16 word);
int (*write_byte)(struct i2c_client *client,
int page, u8 value);
int (*identify)(struct i2c_client *client,
struct pmbus_driver_info *info);
};
pages=2이면 2개의 독립 출력, phases[0]=4이면 첫 번째 출력이 4위상 구성입니다.
가상 레지스터
PMBus 코어는 실제 하드웨어 레지스터 외에 "가상 레지스터"를 제공합니다.
드라이버의 read_word_data 콜백에서 가상 레지스터 요청을 처리합니다.
/* 가상 레지스터 처리 예제 */
static int my_pmbus_read_word(struct i2c_client *client,
int page, int phase, int reg)
{
switch (reg) {
case PMBUS_VIRT_READ_VIN_AVG:
/* 하드웨어 고유 레지스터에서 평균 전압 읽기 */
return pmbus_read_word_data(client, page, phase,
0xD0); /* 벤더 레지스터 */
case PMBUS_VIRT_READ_IOUT_AVG:
return pmbus_read_word_data(client, page, phase,
0xD1);
default:
return -ENODATA; /* PMBus 코어가 기본 처리 */
}
}
커스텀 PMBus 드라이버 예제
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/pmbus.h>
static int my_psu_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
int vout_mode;
/* VOUT 모드 확인 (LINEAR / VID / DIRECT) */
vout_mode = pmbus_read_byte_data(client, 0,
PMBUS_VOUT_MODE);
if (vout_mode < 0)
return vout_mode;
/* 팬 지원 여부에 따라 func 플래그 조정 */
if (pmbus_check_byte_register(client, 0,
PMBUS_FAN_CONFIG_12))
info->func[0] |= PMBUS_HAVE_FAN12;
return 0;
}
static struct pmbus_driver_info my_psu_info = {
.pages = 1,
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_POUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_STATUS_TEMP,
.identify = my_psu_identify,
};
static int my_psu_probe(struct i2c_client *client)
{
return pmbus_do_probe(client, &my_psu_info);
}
static const struct i2c_device_id my_psu_id[] = {
{ "my-psu", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_psu_id);
static struct i2c_driver my_psu_driver = {
.driver = {
.name = "my-psu",
},
.probe = my_psu_probe,
.id_table = my_psu_id,
};
module_i2c_driver(my_psu_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My PSU PMBus driver");
MODULE_LICENSE("GPL");
pmbus_driver_info에 기능 플래그만 설정하면 됩니다.
lm-sensors 통합
lm-sensors는 hwmon sysfs 인터페이스를 편리하게 사용할 수 있게 해주는 사용자 공간 도구 모음입니다.
libsensors 라이브러리, sensors 명령, fancontrol 데몬, sensord 로깅 데몬 등으로 구성됩니다.
libsensors 라이브러리
libsensors는 프로그래밍 방식으로 hwmon 센서를 읽는 C 라이브러리입니다.
#include <sensors/sensors.h>
#include <stdio.h>
int main(void)
{
const sensors_chip_name *chip;
const sensors_feature *feature;
const sensors_subfeature *sub;
double val;
int chip_nr = 0;
/* 초기화 */
if (sensors_init(NULL) != 0) {
fprintf(stderr, "sensors 초기화 실패\n");
return 1;
}
/* 모든 칩 순회 */
while ((chip = sensors_get_detected_chips(
NULL, &chip_nr))) {
char name[128];
sensors_snprintf_chip_name(name, sizeof(name), chip);
printf("칩: %s\n", name);
int feat_nr = 0;
while ((feature = sensors_get_features(
chip, &feat_nr))) {
char *label = sensors_get_label(chip, feature);
sub = sensors_get_subfeature(chip, feature,
SENSORS_SUBFEATURE_TEMP_INPUT);
if (sub && sensors_get_value(chip, sub->number,
&val) == 0) {
printf(" %s: %.1f C\n", label, val);
}
free(label);
}
}
sensors_cleanup();
return 0;
}
/* 컴파일: gcc -o mymon mymon.c -lsensors */
sensors.conf 고급 설정
# /etc/sensors3.conf 또는 /etc/sensors.d/*.conf
# === 칩 매칭 패턴 ===
chip "nct6775-*" # 모든 nct6775
chip "coretemp-isa-0000" # 특정 인스턴스
chip "*-i2c-*-48" # I2C 주소 0x48의 모든 칩
# === 라벨 커스터마이징 ===
chip "nct6775-*"
label temp1 "System Board"
label temp2 "CPU Socket"
label temp3 "Auxiliary"
label fan1 "CPU Fan"
label fan2 "System Fan"
label in0 "Vcore"
label in1 "+12V"
label in2 "AVCC"
# === 전압 보정 (하드웨어 분압기 보상) ===
chip "nct6775-*"
# in1은 분압 회로가 있어 실제 값의 1/11
# compute in <읽기식>, <쓰기식>
compute in1 @*11, @/11 # 읽을 때 11배, 쓸 때 1/11
compute in2 @*2, @/2 # 2배 분압 보정
# === 임계값 재설정 ===
chip "nct6775-*"
set temp1_max 75 # 경고 75도
set temp1_crit 90 # 치명 90도
set in0_min 0.80 # Vcore 최소 0.80V
set in0_max 1.40 # Vcore 최대 1.40V
# === 센서 무시 (사용하지 않는 입력 숨김) ===
chip "nct6775-*"
ignore temp4 # 미연결 온도 센서
ignore temp5
ignore fan3 # 미연결 팬 포트
ignore in5 # 미연결 전압 입력
ignore in6
fancontrol 설정 상세
# /etc/fancontrol - 완전한 설정 예시
# pwmconfig 도구로 자동 생성 후 수동 조정 가능
# 폴링 간격 (초)
INTERVAL=10
# 디바이스 경로 매핑
DEVPATH=hwmon2=devices/platform/nct6775.2592 hwmon0=devices/platform/coretemp.0
# 디바이스 이름
DEVNAME=hwmon2=nct6775 hwmon0=coretemp
# PWM과 온도 센서 연결 (어떤 온도로 어떤 팬을 제어할지)
FCTEMPS=hwmon2/pwm1=hwmon0/temp1_input hwmon2/pwm2=hwmon0/temp1_input
# PWM과 팬 입력 연결 (어떤 팬의 RPM을 모니터링할지)
FCFANS=hwmon2/pwm1=hwmon2/fan1_input hwmon2/pwm2=hwmon2/fan2_input
# 최소 온도 (이 이하에서 최소 PWM)
MINTEMP=hwmon2/pwm1=40 hwmon2/pwm2=35
# 최대 온도 (이 이상에서 최대 PWM = 255)
MAXTEMP=hwmon2/pwm1=75 hwmon2/pwm2=70
# 팬 시작에 필요한 최소 PWM (정지→회전 전환)
MINSTART=hwmon2/pwm1=150 hwmon2/pwm2=150
# 팬 유지에 필요한 최소 PWM (회전→정지 전환)
MINSTOP=hwmon2/pwm1=100 hwmon2/pwm2=100
# 최소 PWM 값 (MINTEMP 이하에서 사용)
MINPWM=hwmon2/pwm1=80 hwmon2/pwm2=80
# 최대 PWM 값 (기본 255)
MAXPWM=hwmon2/pwm1=255 hwmon2/pwm2=255
# fancontrol 서비스 관리
$ sudo systemctl start fancontrol
$ sudo systemctl enable fancontrol
$ sudo systemctl status fancontrol
# fancontrol 로그 확인
$ journalctl -u fancontrol -f
# sensord 데몬 (주기적 로깅)
$ sudo apt install sensord
$ sudo systemctl start sensord
# /var/log/syslog에 센서 값 주기적 기록
hwmonN의 번호는 부팅마다 변경될 수 있습니다.
fancontrol은 DEVPATH/DEVNAME으로 안정적 매칭을 수행하지만,
수동 스크립트에서는 /sys/class/hwmon/hwmon*/name을 확인하여 올바른 디바이스를 찾아야 합니다.
알람 및 임계값
hwmon의 알람 시스템은 센서 값이 설정된 임계값을 초과하거나 미달할 때 트리거됩니다. 임계값은 계층 구조로 관리되며, 각 계층마다 다른 대응 동작이 연결됩니다.
온도 임계값 계층
임계값 유형별 동작
| 임계값 | sysfs 속성 | 방향 | 동작 | 심각도 |
|---|---|---|---|---|
| emergency | tempN_emergency |
상한 | 하드웨어 자동 셧다운 (OS 관여 없음) | 최고 |
| crit | tempN_crit |
상한 | OS 주도 정상 셧다운, CPU 스로틀링 | 높음 |
| max | tempN_max |
상한 | 경고 로그, 팬 속도 증가, 알림 | 중간 |
| min | tempN_min |
하한 | 냉각 과다 경고, 센서 오류 가능성 | 낮음 |
| lcrit | tempN_lcrit |
하한 | 동결 방지 동작 (산업용) | 높음 |
히스테리시스 메커니즘
히스테리시스는 알람이 발생한 후, 값이 임계값보다 일정 폭 이상 회복되어야 알람이 해제되는 메커니즘입니다. 이를 통해 임계값 경계에서의 빈번한 알람 반복을 방지합니다.
# 임계값 및 히스테리시스 설정 예시
# temp_max = 80°C, temp_max_hyst = 75°C
# 시나리오:
# 1. 온도 79°C → 정상 (알람 없음)
# 2. 온도 81°C → temp_max_alarm = 1 (알람 발생)
# 3. 온도 79°C → temp_max_alarm = 1 (아직 해제 안됨, hyst 미만 아님)
# 4. 온도 74°C → temp_max_alarm = 0 (해제: 75°C hyst 이하)
# 히스테리시스 값 읽기
$ cat /sys/class/hwmon/hwmon0/temp1_max
80000
$ cat /sys/class/hwmon/hwmon0/temp1_max_hyst
75000
# 히스테리시스 설정 (일부 칩에서만 쓰기 가능)
$ echo 73000 | sudo tee /sys/class/hwmon/hwmon0/temp1_max_hyst
알람 비트 구조
일부 hwmon 칩은 개별 알람 속성 대신 단일 alarms 비트맵을 제공합니다. 이는 레거시 인터페이스입니다.
# 레거시 alarms 비트맵 (일부 칩)
$ cat /sys/class/hwmon/hwmon2/alarms
16
# 비트 해석 (칩마다 비트 할당이 다름)
# 비트 0: in0 알람
# 비트 1: in1 알람
# 비트 4: temp1 알람 → 16 = 0x10 = 비트 4 설정
# 개별 알람 확인 (신규 인터페이스, 권장)
$ cat /sys/class/hwmon/hwmon2/temp1_alarm
1
$ cat /sys/class/hwmon/hwmon2/temp1_max_alarm
1
$ cat /sys/class/hwmon/hwmon2/temp1_crit_alarm
0
인터럽트 기반 알림
일부 hwmon 칩(특히 I2C 센서)은 ALERT# 핀을 통해 인터럽트 기반 알림을 지원합니다.
/* SMBus Alert 프로토콜을 지원하는 hwmon 칩 설정 */
/* I2C 컨트롤러가 SMBus Alert 인터럽트를 처리 */
/* Device Tree에서 ALERT 설정 */
/* 온도 센서의 ALERT# 핀이 GPIO로 연결된 경우 */
temp_sensor: temperature-sensor@4c {
compatible = "ti,tmp75";
reg = <0x4c>;
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
};
/* 드라이버에서 인터럽트 처리 */
static irqreturn_t temp_alert_handler(int irq, void *dev_id)
{
struct my_data *data = dev_id;
unsigned int status;
regmap_read(data->regmap, REG_STATUS, &status);
if (status & STATUS_TEMP_HIGH)
hwmon_notify_event(data->hwmon_dev,
hwmon_temp, hwmon_temp_max_alarm, 0);
if (status & STATUS_TEMP_CRIT)
hwmon_notify_event(data->hwmon_dev,
hwmon_temp, hwmon_temp_crit_alarm, 0);
return IRQ_HANDLED;
}
ACPI 열 관리 연동
hwmon 서브시스템과 thermal 서브시스템은 밀접하게 연동됩니다. ACPI thermal zone은 플랫폼 수준의 열 관리를 담당하고, hwmon은 개별 센서 데이터를 제공합니다. 두 서브시스템의 연동 구조를 이해하면 효과적인 열 관리 전략을 수립할 수 있습니다.
ACPI Thermal Zone
ACPI는 열 관리를 위해 다음 요소를 정의합니다:
| ACPI 요소 | 설명 | hwmon/thermal 매핑 |
|---|---|---|
_TMP |
현재 온도 반환 | temp_input |
_PSV |
Passive Cooling 임계값 | trip_point_N_temp (passive) |
_AC0..9 |
Active Cooling 임계값 (팬 단계) | trip_point_N_temp (active) |
_CRT |
Critical 온도 (OS 셧다운) | trip_point_N_temp (critical) |
_HOT |
Hot 온도 (S4 진입) | trip_point_N_temp (hot) |
_PSL |
Passive Cooling 대상 프로세서 목록 | cpu_cooling_device |
_AL0..9 |
Active Cooling 팬 목록 | fan_cooling_device |
hwmon과 thermal zone 자동 연결
hwmon_chip_info에서 HWMON_C_REGISTER_TZ 플래그를 설정하면 hwmon 코어가
자동으로 thermal zone을 등록합니다.
/* hwmon에서 thermal zone 자동 등록 */
static const struct hwmon_channel_info *my_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ), /* thermal zone 자동 등록! */
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT),
NULL
};
/*
* 등록 후 자동 생성:
* /sys/class/thermal/thermal_zone{N}/
* temp - temp1_input와 동일한 값
* type - "hwmon{N}"
* trip_point_0_temp - temp1_max 값
* trip_point_1_temp - temp1_crit 값
*/
# thermal zone 확인
$ ls /sys/class/thermal/
cooling_device0 thermal_zone0 thermal_zone1
# thermal zone 정보
$ cat /sys/class/thermal/thermal_zone0/type
x86_pkg_temp
$ cat /sys/class/thermal/thermal_zone0/temp
45000
$ cat /sys/class/thermal/thermal_zone0/policy
step_wise
# trip point 확인
$ cat /sys/class/thermal/thermal_zone0/trip_point_0_temp
80000
$ cat /sys/class/thermal/thermal_zone0/trip_point_0_type
passive
# 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
thermald는 ACPI thermal zone과 hwmon 데이터를 모두 활용하여
지능적인 열 관리를 수행합니다. P-state 제한, T-state 스로틀링, 팬 제어를 종합적으로 관리합니다.
sudo apt install thermald && sudo systemctl enable thermald로 설치합니다.
고급 드라이버 패턴
실제 hwmon 드라이버 개발에서 자주 사용되는 고급 패턴들을 살펴봅니다. regmap 기반 I/O, 멀티칩 드라이버, 가상 hwmon 센서, 에러 복구 등 실전 패턴을 다룹니다.
regmap 기반 hwmon 드라이버
regmap은 레지스터 접근을 추상화하여 I2C/SPI/MMIO 등 다양한 버스에서 동일한 코드를 사용할 수 있게 합니다.
캐싱, 바이트 순서 변환, 레지스터 범위 검증 등을 자동 처리합니다.
#include <linux/hwmon.h>
#include <linux/regmap.h>
#include <linux/i2c.h>
/* 레지스터 정의 */
#define REG_TEMP_MSB 0x00
#define REG_TEMP_LSB 0x01
#define REG_CONFIG 0x02
#define REG_TEMP_HYST 0x03
#define REG_TEMP_LIMIT 0x04
#define REG_MANUFACTURER 0xFE
#define REG_DEVICE_ID 0xFF
struct regmap_hwmon_data {
struct regmap *regmap;
struct mutex update_lock;
unsigned long last_updated;
bool valid;
/* 캐시된 값 */
s16 temp_raw;
s16 temp_limit;
s16 temp_hyst;
};
/* regmap 설정: 읽기 가능/쓰기 가능 레지스터 정의 */
static bool regmap_hwmon_readable(struct device *dev,
unsigned int reg)
{
switch (reg) {
case REG_TEMP_MSB ... REG_TEMP_LIMIT:
case REG_MANUFACTURER:
case REG_DEVICE_ID:
return true;
default:
return false;
}
}
static bool regmap_hwmon_writeable(struct device *dev,
unsigned int reg)
{
switch (reg) {
case REG_CONFIG:
case REG_TEMP_HYST:
case REG_TEMP_LIMIT:
return true;
default:
return false;
}
}
static bool regmap_hwmon_volatile(struct device *dev,
unsigned int reg)
{
/* 온도 값은 volatile (캐시하지 않음) */
return reg == REG_TEMP_MSB || reg == REG_TEMP_LSB;
}
static const struct regmap_config regmap_hwmon_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xFF,
.cache_type = REGCACHE_RBTREE,
.readable_reg = regmap_hwmon_readable,
.writeable_reg = regmap_hwmon_writeable,
.volatile_reg = regmap_hwmon_volatile,
};
/* 16비트 온도 읽기 (MSB + LSB 결합) */
static int read_temp_raw(struct regmap_hwmon_data *data, long *val)
{
unsigned int msb, lsb;
int ret;
ret = regmap_read(data->regmap, REG_TEMP_MSB, &msb);
if (ret)
return ret;
ret = regmap_read(data->regmap, REG_TEMP_LSB, &lsb);
if (ret)
return ret;
/* 12비트 온도: MSB[7:0] + LSB[7:4], 0.0625도 해상도 */
s16 raw = (msb << 4) | (lsb >> 4);
if (raw & 0x800)
raw |= 0xF000; /* 부호 확장 */
*val = raw * 625 / 10; /* 0.0625도 → milli-Celsius */
return 0;
}
멀티칩 드라이버 패턴
하나의 보드에 동일한 센서 칩이 여러 개 장착된 경우, 각 칩을 독립적인 hwmon 디바이스로 등록합니다.
/* 같은 드라이버로 여러 I2C 주소의 칩을 지원 */
static const struct i2c_device_id multi_chip_id[] = {
{ "sensor-a", 0 }, /* 기본형 */
{ "sensor-b", 1 }, /* 확장형 (추가 채널) */
{ }
};
static int multi_probe(struct i2c_client *client)
{
const struct i2c_device_id *id =
i2c_match_id(multi_chip_id, client);
const struct hwmon_chip_info *chip_info;
switch (id->driver_data) {
case 0:
chip_info = &sensor_a_chip_info; /* 2채널 */
break;
case 1:
chip_info = &sensor_b_chip_info; /* 4채널 */
break;
default:
return -ENODEV;
}
/* 각 probe 호출마다 별도 hwmon 디바이스 생성 */
return PTR_ERR_OR_ZERO(
devm_hwmon_device_register_with_info(
&client->dev, id->name, data,
chip_info, NULL));
}
가상 hwmon 센서
실제 하드웨어 센서 없이 계산된 값을 hwmon으로 노출할 수 있습니다. 예를 들어 여러 온도 센서의 가중 평균을 하나의 가상 센서로 제공합니다.
/* 가상 hwmon 센서: 다중 센서의 가중 평균 */
static int virtual_read(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct virtual_data *data = dev_get_drvdata(dev);
long sum = 0;
int count = 0;
int i;
if (type != hwmon_temp || attr != hwmon_temp_input)
return -EOPNOTSUPP;
/* 등록된 실제 센서들의 가중 평균 계산 */
for (i = 0; i < data->num_sources; i++) {
struct thermal_zone_device *tz = data->sources[i];
int temp;
if (thermal_zone_get_temp(tz, &temp) == 0) {
sum += temp * data->weights[i];
count += data->weights[i];
}
}
if (count == 0)
return -ENODATA;
*val = DIV_ROUND_CLOSEST(sum, count);
return 0;
}
에러 복구 패턴
I2C 통신 오류, 센서 타임아웃 등에 대한 복구 패턴입니다.
/* 재시도 패턴: I2C 통신 실패 시 */
#define MAX_RETRIES 3
#define RETRY_DELAY_MS 10
static int read_sensor_with_retry(struct my_data *data,
u8 reg, unsigned int *val)
{
int ret, retries;
for (retries = 0; retries < MAX_RETRIES; retries++) {
ret = regmap_read(data->regmap, reg, val);
if (ret == 0)
return 0;
dev_dbg(data->dev,
"읽기 실패 reg=0x%02x 시도=%d err=%d\n",
reg, retries + 1, ret);
msleep(RETRY_DELAY_MS);
}
dev_err(data->dev,
"센서 읽기 최종 실패 reg=0x%02x\n", reg);
return ret;
}
/* 센서 오류 시 fault 속성 반영 */
static int safe_read(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct my_data *data = dev_get_drvdata(dev);
int ret;
if (attr == hwmon_temp_fault) {
*val = data->sensor_fault[channel];
return 0;
}
if (attr == hwmon_temp_input) {
ret = read_sensor_with_retry(data,
temp_regs[channel], val);
if (ret) {
data->sensor_fault[channel] = 1;
*val = 0; /* fault 시 0 반환 */
return 0; /* 에러 대신 0 반환하여 sysfs 읽기 성공 */
}
data->sensor_fault[channel] = 0;
}
return 0;
}
read 콜백에서 에러를 반환하면 sysfs 읽기 자체가 실패합니다.
사용자 공간 도구가 이를 올바르게 처리하지 못할 수 있으므로, 가능하면 _fault 속성을 설정하고
읽기는 성공시키는 패턴이 권장됩니다. 다만 하드웨어 오류가 명확한 경우 -EIO를 반환해도 됩니다.
디버깅 및 테스트
hwmon 드라이버 개발과 운영 시 활용할 수 있는 디버깅 도구와 테스트 방법을 정리합니다.
sysfs를 통한 기본 디버깅
# ====== hwmon 디바이스 전체 조회 ======
# 등록된 모든 hwmon 디바이스 목록
$ for d in /sys/class/hwmon/hwmon*; do
echo "$(basename $d): $(cat $d/name 2>/dev/null)"
done
hwmon0: coretemp
hwmon1: nct6775
hwmon2: acpitz
# 특정 hwmon 디바이스의 모든 속성 나열
$ ls -la /sys/class/hwmon/hwmon0/
$ find /sys/class/hwmon/hwmon0/ -maxdepth 1 -name "temp*" \
-exec sh -c 'echo "$(basename {}): $(cat {})"' \;
# 디바이스 드라이버 정보
$ readlink -f /sys/class/hwmon/hwmon0/device/driver
/sys/bus/platform/drivers/coretemp
# hwmon 디바이스가 어떤 물리 디바이스에 연결되었는지
$ readlink -f /sys/class/hwmon/hwmon0/device
/sys/devices/platform/coretemp.0
i2c-tools를 이용한 디버깅
# i2c-tools 설치
$ sudo apt install i2c-tools
# I2C 버스 목록 확인
$ i2cdetect -l
i2c-0 smbus SMBus I801 adapter at efa0 SMBus adapter
i2c-1 i2c i915 gmbus dpb I2C adapter
# 특정 버스의 디바이스 스캔
$ sudo i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- 08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- 4c -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
# 특정 디바이스의 레지스터 덤프
$ sudo i2cdump -y 0 0x48
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: 2d 00 00 4b 50 ff ff ff ff ff ff ff ff ff ff ff
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# 특정 레지스터 읽기
$ sudo i2cget -y 0 0x48 0x00
0x2d # 온도: 45도 (0x2D = 45)
# 레지스터 쓰기 (주의! 하드웨어 손상 가능)
$ sudo i2cset -y 0 0x48 0x03 0x50 # 히스테리시스 80도
i2cset으로 임의의 레지스터에 쓰면 하드웨어 설정이 변경될 수 있습니다.
데이터시트를 확인하고, 프로덕션 환경에서는 사용하지 마세요.
커널 로그 분석
# hwmon 관련 커널 메시지 필터링
$ dmesg | grep -i hwmon
[ 2.345678] coretemp coretemp.0: hwmon_device_register_with_info
[ 2.345789] nct6775 nct6775.2592: hwmon0 registered
# 특정 드라이버의 디버그 메시지 활성화
$ echo "module nct6775 +p" | sudo tee /sys/kernel/debug/dynamic_debug/control
$ echo "file drivers/hwmon/hwmon.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control
# 실시간 로그 모니터링
$ dmesg -w | grep -E "hwmon|nct6775|coretemp"
# I2C 통신 트레이싱
$ echo 1 | sudo tee /sys/kernel/debug/tracing/events/i2c/enable
$ cat /sys/kernel/debug/tracing/trace_pipe | head -50
hwmon 에뮬레이션
실제 하드웨어 없이 hwmon 드라이버를 테스트하려면 가상 I2C 어댑터를 사용할 수 있습니다.
# i2c-stub 모듈로 가상 I2C 디바이스 생성
$ sudo modprobe i2c-stub chip_addr=0x48
# 가상 디바이스에 레지스터 값 설정
$ sudo i2cset -y 10 0x48 0x00 0x2D # temp = 45도
$ sudo i2cset -y 10 0x48 0x03 0x50 # hyst = 80도
# 드라이버 바인딩
$ echo "lm75 0x48" | sudo tee /sys/bus/i2c/devices/i2c-10/new_device
# hwmon 디바이스 확인
$ cat /sys/class/hwmon/hwmon*/name | grep lm75
# 제거
$ echo 0x48 | sudo tee /sys/bus/i2c/devices/i2c-10/delete_device
$ sudo modprobe -r i2c-stub
모니터링 자동화 스크립트
#!/bin/bash
# hwmon 모니터링 스크립트 - 주기적으로 센서 값을 로깅하고 알람 확인
INTERVAL=5
LOG_FILE="/var/log/hwmon_monitor.log"
TEMP_WARN=80000 # 경고 온도 (mC)
TEMP_CRIT=95000 # 치명 온도 (mC)
find_hwmon_by_name() {
local name=$1
for d in /sys/class/hwmon/hwmon*; do
if [ "$(cat $d/name 2>/dev/null)" = "$name" ]; then
echo "$d"
return 0
fi
done
return 1
}
while true; do
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
HWMON_DIR=$(find_hwmon_by_name "coretemp")
if [ -n "$HWMON_DIR" ]; then
TEMP=$(cat $HWMON_DIR/temp1_input 2>/dev/null)
# 로깅
echo "$TIMESTAMP temp=$TEMP" >> $LOG_FILE
# 임계값 확인
if [ "$TEMP" -ge "$TEMP_CRIT" ]; then
logger -p daemon.crit "CPU 온도 치명: ${TEMP}mC"
elif [ "$TEMP" -ge "$TEMP_WARN" ]; then
logger -p daemon.warning "CPU 온도 경고: ${TEMP}mC"
fi
fi
sleep $INTERVAL
done
KUnit 테스트
커널 6.x부터 hwmon 드라이버에 대한 KUnit 테스트를 작성할 수 있습니다.
#include <kunit/test.h>
#include <linux/hwmon.h>
/* 테스트: 온도 변환 함수 검증 */
static void test_temp_conversion(struct kunit *test)
{
/* 양수 온도 */
KUNIT_EXPECT_EQ(test, reg_to_mc(45), 45000L);
KUNIT_EXPECT_EQ(test, reg_to_mc(100), 100000L);
/* 음수 온도 */
KUNIT_EXPECT_EQ(test, reg_to_mc(0xE7), -25000L);
/* 역변환 */
KUNIT_EXPECT_EQ(test, mc_to_reg(45000), 45);
KUNIT_EXPECT_EQ(test, mc_to_reg(-25000), (u8)0xE7);
/* 경계값 클램핑 */
KUNIT_EXPECT_EQ(test, mc_to_reg(200000), 127);
KUNIT_EXPECT_EQ(test, mc_to_reg(-200000), (u8)0x80);
}
/* 테스트: is_visible 콜백 검증 */
static void test_is_visible(struct kunit *test)
{
struct my_temp_data data = { .fan_count = 2 };
/* temp_input은 읽기 전용 */
KUNIT_EXPECT_EQ(test,
my_temp_is_visible(&data, hwmon_temp,
hwmon_temp_input, 0),
(umode_t)0444);
/* temp_max는 읽기/쓰기 */
KUNIT_EXPECT_EQ(test,
my_temp_is_visible(&data, hwmon_temp,
hwmon_temp_max, 0),
(umode_t)0644);
/* 존재하지 않는 팬 채널 */
KUNIT_EXPECT_EQ(test,
my_temp_is_visible(&data, hwmon_fan,
hwmon_fan_input, 5),
(umode_t)0);
}
static struct kunit_case hwmon_test_cases[] = {
KUNIT_CASE(test_temp_conversion),
KUNIT_CASE(test_is_visible),
{}
};
static struct kunit_suite hwmon_test_suite = {
.name = "hwmon_my_driver_test",
.test_cases = hwmon_test_cases,
};
kunit_test_suite(hwmon_test_suite);
./tools/testing/kunit/kunit.py run hwmon_my_driver_test --kunitconfig=drivers/hwmon/.kunitconfig
으로 hwmon 테스트를 실행할 수 있습니다.
참고자료
- Linux hwmon Documentation
- hwmon sysfs Interface
- PMBus Core Documentation
- lm-sensors GitHub
- PMBus Specification
drivers/hwmon/— hwmon 드라이버 소스drivers/hwmon/pmbus/— PMBus 드라이버 소스include/linux/hwmon.h— hwmon API 헤더Documentation/hwmon/sysfs-interface.rst— sysfs ABI 규격Documentation/hwmon/hwmon-kernel-api.rst— hwmon 커널 API
- Thermal Management — 열 관리 통합
- I2C/SPI/GPIO — 센서 통신
- 전원 관리 — 전력 모니터링
- Regulator 프레임워크 — 전압 레귤레이터
- Watchdog — 시스템 감시 타이머
관련 문서
- Regulator 프레임워크 — Linux Regulator 프레임워크: regulator_desc, regulator_o