Reset Controller 프레임워크
리눅스 reset controller 프레임워크를 "assert/deassert 몇 번 하는 API" 수준이 아니라, SoC 주변장치의 초기화와 복구를 표현하는 공통 상태 모델로 정리합니다. reset_control consumer API, shared/exclusive reset의 공식 의미, self-deasserting pulse reset, optional/array/acquire-release API, reset_controller_dev provider 등록, Device Tree의 resets/reset-names/#reset-cells 바인딩, clock/regulator/runtime PM과의 시퀀스, bootloader handoff, cold boot와 resume 차이, 실전 디버깅과 실패 패턴까지 자세히 다룹니다.
핵심 요약
- reset line과 reset control은 다릅니다 — 물리적 선(reset line) 하나 또는 여러 개를 제어하는 방법이 reset control이고, 이를 모아 관리하는 하드웨어가 reset controller입니다.
- exclusive vs shared — exclusive reset은 드라이버가 실제 물리 상태를 직접 바꾼다는 뜻이고, shared reset은 refcount 기반 의미만 보장됩니다.
- shared reset에서는 assert가 진짜 assert가 아닐 수 있습니다 — 다른 소비자가 deassert를 유지하고 있으면 선은 계속 deassert 상태일 수 있습니다.
- pulse reset과 level reset은 다릅니다 — self-deasserting reset은
reset_control_reset()로 pulse를 트리거하는 모델이고, level reset은assert/deassert로 상태를 유지합니다. - 시퀀스가 본질입니다 — 대개 regulator on, clock 준비, reset deassert, MMIO 초기화 순서가 맞아야 합니다.
- provider와 consumer 둘 다 중요합니다 — 소비자 드라이버만 보는 것으로는 부족하고, provider의
reset_control_ops와 DT xlate 규칙도 함께 봐야 합니다.
단계별 이해
- reset 선 식별
데이터시트와 DT에서 이 장치가 어떤 reset control과 연결되는지 먼저 찾습니다. - 모델 판단
이 reset이 level 유지형인지, pulse형인지, 공유형인지 단독형인지 구분합니다. - 시퀀스 정리
전원, clock, reset, 펌웨어 로드, MMIO 초기화 순서를 글로 먼저 적어 봅니다. - 에러 경로 설계
probe 실패, suspend/resume 실패, remove 시 reset 상태를 어디로 되돌릴지 결정합니다. - 공유 자원 여부 확인
다른 IP와 reset bit를 공유하거나 provider가 여러 line을 한 control로 묶는지 확인합니다.
reset framework가 필요한 이유와 책임 경계
주변장치는 전원만 공급된다고 해서 자동으로 정상 동작하지 않습니다. 이전 부팅에서 남은 DMA 상태, internal state machine, firmware MCU, bus handshake, PHY calibration 상태가 뒤섞인 채 남아 있을 수 있습니다. 이때 reset line을 적절히 assert하거나 pulse를 걸어 known-good state로 되돌리는 것이 필요합니다. reset controller 프레임워크는 이 과정을 consumer-driver마다 제멋대로 구현하지 않도록 공통 API와 공통 DT binding으로 정리합니다.
공식 커널 문서는 reset controller API의 책임을 명확히 구분합니다. 특히 일부 reset controller 하드웨어가 system restart 기능도 같이 제공할 수는 있지만, system restart handler는 reset controller API의 범위 밖이라고 설명합니다. 즉 이 프레임워크는 "장치 블록을 known state로 되돌리는 것"이 주제이지, 시스템 전체 재부팅을 설명하는 프레임워크는 아닙니다.
| 개념 | 의미 | 예시 |
|---|---|---|
| reset line | reset 신호가 실제로 전달되는 물리 선 | USB PHY reset pin, GPU reset input |
| reset control | 하나 또는 여러 reset line의 상태를 결정하는 제어 방법 | 레지스터의 한 비트, self-clearing trigger bit, 복합 시퀀스 엔진 |
| reset controller | 여러 reset control을 제공하는 하드웨어 블록 | SoC CCU/CRU/PRCM 내 reset block |
| reset consumer | reset 신호를 받아 리셋되는 장치 | UART, USB controller, DMA, PHY, 외부 IC |
consumer 관점의 공식 의미: exclusive와 shared
reset controller API는 reset control을 요청할 때부터 의미를 나눕니다. exclusive reset은 드라이버가 해당 control을 직접 제어한다고 가정하고, shared reset은 여러 소비자가 같은 control을 함께 사용하는 상황을 모델링합니다. 이 차이는 단순한 ownership 표시가 아니라, assert(), deassert(), reset()의 물리적 효과 자체를 바꿉니다.
| 종류 | 보장되는 의미 | 물리적 효과 | 언제 적합한가 |
|---|---|---|---|
| exclusive | 소비자가 실제 reset 상태를 직접 제어 | assert는 즉시 assert, deassert는 즉시 deassert | reset line이 장치 하나 또는 드라이버 한 개만의 책임일 때 |
| shared | deassert 요청에 대한 refcount 의미만 보장 | 첫 deassert와 마지막 assert만 물리 상태를 바꿀 수 있음 | 여러 consumer가 같은 reset control을 함께 써야 할 때 |
공식 문서 기준으로 shared reset은 clock framework의 shared enable과 비슷합니다. 첫 번째 deassert만 실제 선을 deassert할 수 있고, 마지막 assert만 실제 선을 assert할 수 있습니다. 그래서 shared reset에서 reset_control_assert()를 호출했다고 해서 물리 선이 반드시 assert된다고 기대하면 안 됩니다. 다른 소비자가 계속 deassert를 요구하고 있으면 선은 여전히 deassert 상태일 수 있습니다.
level reset과 self-deasserting pulse reset
모든 reset이 "assert 상태를 유지했다가 deassert"되는 것은 아닙니다. 어떤 하드웨어는 self-clearing bit를 쓰며, 소프트웨어가 trigger를 한 번 쓰면 하드웨어가 내부적으로 pulse를 발생시키고 자동으로 원래 상태로 돌아옵니다. 공식 커널 문서는 이런 경우를 self-deasserting reset control로 설명하고, consumer는 reset_control_reset()으로 pulse를 요청한다고 정의합니다.
| 모델 | 대표 API | 특징 | 주의점 |
|---|---|---|---|
| level reset | reset_control_assert(), reset_control_deassert() | 소프트웨어가 상태를 유지함 | assert 상태에서 bus access가 끊길 수 있습니다. |
| self-deasserting pulse reset | reset_control_reset() | 하드웨어가 pulse를 자체 생성 후 자동 해제 | pulse 폭과 내부 시퀀스는 하드웨어가 결정합니다. |
공식 문서는 일반적으로 self-deasserting pulse reset은 shared에 적합하지 않다고 설명합니다. 어느 consumer가 pulse를 요청해도 연결된 모든 주변장치가 reset되기 때문입니다. 다만 API는 shared pulse reset도 허용하며, 그 경우 첫 trigger만 실제 pulse를 내고 이후에는 모든 consumer가 reset_control_rearm()을 호출할 때까지 추가 pulse를 내지 않는 모델을 제공합니다.
reset_control_reset()/reset_control_rearm()를 쓴 경우 reset_control_assert()/reset_control_deassert()와 섞어 쓰지 말라고 경고합니다. 의미 모델이 다르기 때문입니다.
consumer API 전체 지도
기본 getter 몇 개만 알아도 대부분의 드라이버는 동작하지만, 실제 API 표면은 더 넓습니다. exclusive/shared, optional, deasserted convenience, released/acquire-release, bulk array가 모두 존재합니다.
| API 계열 | 예시 | 의미 |
|---|---|---|
| exclusive getter | devm_reset_control_get_exclusive() | 단독 제어 reset을 얻습니다. |
| shared getter | devm_reset_control_get_shared() | shared semantics의 reset을 얻습니다. |
| optional getter | devm_reset_control_get_optional_exclusive() | DT에 reset이 없으면 NULL을 반환합니다. |
| deasserted getter | devm_reset_control_get_shared_deasserted() | get과 deassert를 묶은 편의 API입니다. |
| released getter | reset_control_get_exclusive_released() | exclusive지만 처음에는 acquired 상태가 아닌 핸들을 얻습니다. |
| acquire/release | reset_control_acquire(), reset_control_release() | temporarily exclusive 핸들에 대한 실제 획득/반납입니다. |
| bulk/array | devm_reset_control_array_get(), reset_control_bulk_deassert() | 여러 reset을 한 번에 다룹니다. |
#include <linux/reset.h>
struct reset_control *rst;
rst = devm_reset_control_get_exclusive(dev, "core");
if (IS_ERR(rst))
return dev_err_probe(dev, PTR_ERR(rst),
"failed to get core reset\n");
reset_control_assert(rst);
usleep_range(10, 20);
reset_control_deassert(rst);
optional reset도 매우 중요합니다. 공식 문서 기준으로 optional getter는 reset이 DT에 아예 없을 때 NULL을 반환하고, 이후 reset API들은 NULL을 quietly 처리합니다. 즉 "플랫폼 A에는 reset이 있지만 플랫폼 B에는 없다" 같은 변종을 ifdef 없이 같은 driver로 처리할 수 있습니다.
struct reset_control *phy_rst;
phy_rst = devm_reset_control_get_optional_exclusive(dev, "phy");
if (IS_ERR(phy_rst))
return PTR_ERR(phy_rst);
/* phy_rst가 NULL이어도 API는 조용히 성공 */
reset_control_deassert(phy_rst);
*_exclusive_released()와 reset_control_acquire()/release() 조합이 필요합니다. 평소엔 놓아두고, 특정 민감 구간에서만 잠그는 모델입니다.
shared reset의 정확한 의미와 금지 패턴
shared reset은 가장 자주 오해되는 부분입니다. 공식 문서는 shared reset에 대해 아래 제약을 분명히 둡니다.
| 규칙 | 이유 |
|---|---|
deassert와 assert 호출 수는 균형을 맞춰야 함 | deassert refcount를 사용하기 때문입니다. |
shared reset에서 assert는 실제 물리 assert를 보장하지 않음 | 다른 consumer가 deassert를 유지하고 있을 수 있기 때문입니다. |
| shared reset line에서는 하드웨어가 reset되었을 것이라고 가정하면 안 됨 | 물리 선이 실제로 assert되지 않았을 수 있기 때문입니다. |
| shared line에서 pulse reset은 특별한 경우만 허용 | pulse는 연결된 모든 consumer를 재초기화할 수 있기 때문입니다. |
/* 잘못된 기대: shared reset에서 assert()가 실제 reset을 보장한다고 가정 */
reset_control_assert(shared_rst);
/* 여기서 "레지스터 상태가 깨끗해졌을 것"이라고 가정하면 안 됨 */
/* 올바른 접근: shared line은 refcount 의미만 신뢰 */
reset_control_deassert(shared_rst);
/* 최소한 내 consumer가 reset에 잡혀 있지는 않음을 보장받음 */
또한 shared reset line에서 reset_control_reset()과 reset_control_rearm()를 썼다면, 공식 문서 기준으로 assert/deassert 모델과 섞어 쓰면 안 됩니다. driver가 "이 reset은 level 상태를 관리하는가, pulse를 트리거하는가"를 처음부터 하나로 결정해야 합니다.
array와 bulk reset: 편하지만 순서는 보장되지 않음
하나의 장치가 여러 reset line을 갖는 경우도 흔합니다. 예를 들어 USB 컨트롤러는 core reset, PHY reset, bus reset을 따로 가질 수 있습니다. 이런 경우 bulk API나 array handle이 편리합니다. 하지만 공식 문서는 reset control array API는 개별 control을 어떤 순서로 처리할지 보장하지 않는다고 명시합니다.
| API | 용도 | 주의점 |
|---|---|---|
devm_reset_control_array_get() | DT에 있는 여러 reset을 opaque handle 하나로 묶기 | 개별 처리 순서를 보장하지 않습니다. |
reset_control_bulk_get_*() | 이름 배열을 기반으로 여러 reset을 얻기 | strict order bring-up에는 부적합할 수 있습니다. |
reset_control_bulk_assert()/deassert() | 여러 reset을 한 번에 처리 | MMIO block 간 의존성이 있으면 수동 순서 제어가 낫습니다. |
static struct reset_control_bulk_data rstcs[] = {
{ .id = "core" },
{ .id = "phy" },
};
ret = devm_reset_control_bulk_get_shared(dev, ARRAY_SIZE(rstcs), rstcs);
if (ret)
return ret;
ret = reset_control_bulk_deassert(ARRAY_SIZE(rstcs), rstcs);
if (ret)
return ret;
공식 문서는 array handle에 대해 reset_control_status()를 적용할 수 없다고도 설명합니다. 즉 array는 "묶음 처리"에는 좋지만, 개별 라인의 세밀한 상태 추적과 strict sequencing에는 적합하지 않습니다.
provider 관점: reset_controller_dev와 reset_control_ops
지금까지는 consumer 쪽만 봤지만, reset controller driver를 쓰려면 provider 인터페이스를 이해해야 합니다. 공식 문서는 provider driver가 struct reset_controller_dev를 채우고 reset_controller_register() 또는 devm_reset_controller_register()로 등록한다고 설명합니다. 실제 동작은 struct reset_control_ops의 콜백이 담당합니다.
| 구성 요소 | 역할 | 주의점 |
|---|---|---|
reset_controller_dev | provider의 핵심 descriptor | ops, DT xlate 정보, reset 개수, owner 등을 포함합니다. |
reset_control_ops | assert/deassert/reset/status 콜백 집합 | 모든 콜백이 필수는 아니지만, consumer 기대치와 맞아야 합니다. |
of_reset_n_cells | DT phandle 뒤 인자 개수 | #reset-cells와 맞아야 합니다. |
of_xlate | DT 인자를 내부 reset ID로 변환 | 단순 인덱스면 helper/기본 xlate로도 충분합니다. |
#include <linux/reset-controller.h>
struct my_reset {
struct reset_controller_dev rcdev;
void __iomem *base;
spinlock_t lock;
};
#define to_my_reset(_rcdev) container_of(_rcdev, struct my_reset, rcdev)
static int my_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
{
struct my_reset *rst = to_my_reset(rcdev);
unsigned long flags;
u32 val;
spin_lock_irqsave(&rst->lock, flags);
val = readl(rst->base + 0x100);
val |= BIT(id);
writel(val, rst->base + 0x100);
spin_unlock_irqrestore(&rst->lock, flags);
return 0;
}
static int my_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
{
struct my_reset *rst = to_my_reset(rcdev);
unsigned long flags;
u32 val;
spin_lock_irqsave(&rst->lock, flags);
val = readl(rst->base + 0x100);
val &= ~BIT(id);
writel(val, rst->base + 0x100);
spin_unlock_irqrestore(&rst->lock, flags);
return 0;
}
static const struct reset_control_ops my_reset_ops = {
.assert = my_reset_assert,
.deassert = my_reset_deassert,
.status = my_reset_status,
};
provider는 단순 비트 set/clear만 할 수도 있고, 하드웨어가 self-clearing pulse register를 제공한다면 .reset 콜백을 구현할 수도 있습니다. status 조회를 지원하지 않는 controller도 많으므로 .status는 optional인 경우가 흔합니다.
provider 등록과 DT xlate
Device Tree에서 consumer는 보통 resets = <&rst N> 형태로 provider를 참조합니다. provider는 자신이 phandle arguments를 몇 개 받는지 #reset-cells로 정의하고, 커널에서는 of_reset_n_cells와 of_xlate로 이를 해석합니다.
static int my_reset_probe(struct platform_device *pdev)
{
struct my_reset *rst;
rst = devm_kzalloc(&pdev->dev, sizeof(*rst), GFP_KERNEL);
if (!rst)
return -ENOMEM;
spin_lock_init(&rst->lock);
rst->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rst->base))
return PTR_ERR(rst->base);
rst->rcdev.owner = THIS_MODULE;
rst->rcdev.ops = &my_reset_ops;
rst->rcdev.of_node = pdev->dev.of_node;
rst->rcdev.of_reset_n_cells = 1;
rst->rcdev.nr_resets = 64;
return devm_reset_controller_register(&pdev->dev, &rst->rcdev);
}
rst: reset-controller@ff730000 {
compatible = "vendor,soc-reset";
reg = <0x0 0xff730000 0x0 0x1000>;
#reset-cells = <1>;
};
usb@12340000 {
compatible = "vendor,my-usb";
reg = <0x0 0x12340000 0x0 0x10000>;
resets = <&rst 12>, <&rst 13>;
reset-names = "core", "phy";
};
| DT 속성 | 의미 | 실전 포인트 |
|---|---|---|
#reset-cells | provider가 phandle 뒤에 받는 인자 수 | provider의 of_reset_n_cells와 맞아야 합니다. |
resets | consumer가 사용하는 reset control 참조 | 여러 개일 경우 순서가 ABI가 됩니다. |
reset-names | consumer가 reset 의미를 이름으로 구분 | index 의존 코드를 피하게 해 줍니다. |
reset-names를 두는 편이 좋습니다. 인덱스 0이 "core"인지 "phy"인지 driver가 암묵적으로 알고 있어야 하는 구조는 코드 리뷰와 보드 파생 관리에 취약합니다.
소비자 드라이버 구현 패턴
실전 consumer driver는 보통 다음 구조를 가집니다. reset만 따로 보는 것이 아니라 regulator, clock, runtime PM, firmware load와 한 흐름으로 다뤄야 합니다.
- 필요한 reset handle을 얻습니다.
- runtime PM 또는 power domain을 올립니다.
- regulator와 clock을 준비합니다.
- 필요 시 assert 후 최소 폭을 지킨 뒤 deassert합니다.
- MMIO 초기화와 firmware download를 수행합니다.
- 에러 시 되돌리는 순서를 명확히 정합니다.
static int my_ip_probe(struct platform_device *pdev)
{
struct my_ip *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->core_rst = devm_reset_control_get_exclusive(&pdev->dev, "core");
if (IS_ERR(priv->core_rst))
return dev_err_probe(&pdev->dev, PTR_ERR(priv->core_rst),
"failed to get core reset\n");
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret)
return ret;
ret = regulator_bulk_enable(priv->num_supplies, priv->supplies);
if (ret)
goto err_put_pm;
ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
if (ret)
goto err_disable_regs;
reset_control_assert(priv->core_rst);
usleep_range(10, 20);
ret = reset_control_deassert(priv->core_rst);
if (ret)
goto err_disable_clks;
ret = my_ip_hw_init(priv);
if (ret)
goto err_assert_rst;
return 0;
err_assert_rst:
reset_control_assert(priv->core_rst);
err_disable_clks:
clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
err_disable_regs:
regulator_bulk_disable(priv->num_supplies, priv->supplies);
err_put_pm:
pm_runtime_put(&pdev->dev);
return ret;
}
위 코드는 보편적인 패턴일 뿐이며, 하드웨어에 따라 assert가 필요 없거나, bootloader가 이미 assert 상태를 보장하거나, reset이 pulse형이어서 reset_control_reset() 하나로 끝날 수도 있습니다. 중요한 것은 데이터시트 요구를 API 의미에 정확히 매핑하는 것입니다.
clock, regulator, runtime PM과의 순서
reset 문제는 사실상 시퀀스 문제인 경우가 많습니다. reset deassert는 대개 "이제 장치 내부 상태기가 돌아가기 시작한다"는 뜻이므로, 그 전에 bus clock과 core clock이 살아 있어야 하고, 필요한 전압 레일이 안정화되어 있어야 하며, PM domain이 register 접근을 허용해야 합니다.
| 전형적 순서 | 왜 필요한가 |
|---|---|
| 1. PM domain / runtime PM resume | 레지스터가 응답 가능한 상태 확보 |
| 2. regulator enable | 전압 레일 안정화 |
| 3. clock prepare/enable | state machine이 clocked 상태가 되게 함 |
| 4. reset deassert 또는 pulse trigger | 장치를 실제 동작 상태로 전환 |
| 5. MMIO 초기화 / firmware load | 동작 조건 설정 |
resume 경로에서는 cold boot보다 더 미묘합니다. reset line을 유지해야 하는 장치도 있고, retention register를 믿지 못해 resume 때마다 pulse reset이 필요한 장치도 있습니다. 그래서 "cold boot에서 되면 끝"이 아니라 runtime suspend/resume까지 포함해서 시퀀스를 설계해야 합니다.
provider 내부 구현 패턴과 락
provider driver는 종종 shared MMIO register를 다룹니다. 예를 들어 reset bit와 clock gate bit가 같은 register bank에 있을 수 있고, 여러 reset line이 하나의 32비트 레지스터를 공유할 수 있습니다. 이때 provider가 자체 락을 두지 않으면 경쟁 조건으로 잘못된 비트가 깨질 수 있습니다.
| provider 구현 이슈 | 왜 문제인가 | 대응 |
|---|---|---|
| 공유 register RMW 경쟁 | 동시에 다른 reset bit를 바꿀 때 비트 손실 가능 | spinlock 등으로 보호 |
| self-clearing pulse bit polling | reset pulse 완료 시점을 모르고 다음 단계로 넘어갈 수 있음 | status bit 또는 하드웨어 완료 조건 확인 |
| status 미지원 | consumer가 실제 asserted 여부를 읽지 못함 | API 의미를 문서화하고, 필요시 polling을 다른 레지스터로 보완 |
| 복수 line을 한 control로 묶음 | consumer가 "내 장치만 reset"이라 생각하기 쉬움 | binding과 문서에서 관계를 명확히 표현 |
static int my_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
{
struct my_reset *rst = to_my_reset(rcdev);
unsigned long flags;
/* self-clearing pulse trigger 예시 */
spin_lock_irqsave(&rst->lock, flags);
writel(BIT(id), rst->base + 0x104);
spin_unlock_irqrestore(&rst->lock, flags);
/* 필요한 경우 완료 비트 polling */
return 0;
}
provider가 .reset를 구현한다고 해서 항상 .assert/.deassert도 구현해야 하는 것은 아닙니다. 하드웨어가 pulse trigger만 제공한다면 .reset만 의미 있을 수 있습니다. 반대로 level reset만 지원하면 .assert/.deassert만 구현하고 .reset는 비워 둘 수 있습니다.
고급 API: released, acquire/release, rearm, status
실전에서는 드문 편이지만, 아래 API들을 알아두면 framework의 의미가 더 분명해집니다.
| API | 의미 | 언제 유용한가 |
|---|---|---|
reset_control_get_exclusive_released() | exclusive지만 처음엔 acquired가 아닌 핸들 | 평소엔 놓아두고 특정 구간에만 잠깐 독점할 때 |
reset_control_acquire()/release() | temporarily exclusive reset control 잠금/해제 | 여러 consumer가 시간 분할로 독점 사용 |
reset_control_rearm() | shared self-deasserting pulse reset을 다시 pulse 가능 상태로 돌림 | 공유 pulse reset에서 다음 consumer가 다시 한 번 pulse를 쏠 수 있게 할 때 |
reset_control_status() | assert 상태 조회 | provider가 지원하는 경우에만 의미 있음 |
reset_control_rearm()을 균형 있게 호출해야 합니다. 이 모델은 "probe나 resume 전에 한 번만 확실히 reset되면 된다"는 장치에 적합합니다.
Device Tree binding 세부사항
consumer 노드가 여러 reset을 가질 때는 reset-names와 함께 쓰는 것이 좋고, provider 노드는 #reset-cells를 명확히 해야 합니다. 또한 어떤 SoC에서는 하나의 상위 노드가 clock provider와 reset provider를 동시에 제공하기도 합니다. 이 경우 같은 MMIO block에서 clock과 reset이 함께 관리되므로, CCF와 reset framework의 provider 드라이버가 분리되어도 register locking과 probe 순서는 신중히 설계해야 합니다.
ccu: clock-reset-controller@ff760000 {
compatible = "vendor,soc-ccu";
reg = <0x0 0xff760000 0x0 0x1000>;
#clock-cells = <1>;
#reset-cells = <1>;
};
ethernet@10020000 {
compatible = "vendor,my-mac";
clocks = <&ccu 4>, <&ccu 5>;
clock-names = "stmmaceth", "ptp_ref";
resets = <&ccu 9>;
reset-names = "stmmaceth";
};
| 실수 | 왜 문제인가 | 더 나은 방식 |
|---|---|---|
resets만 있고 reset-names가 없음 | driver가 인덱스 의미를 암묵적으로 가정해야 함 | 이름으로 의미를 명시 |
#reset-cells와 provider 구현 불일치 | phandle xlate 실패 | DT와 of_reset_n_cells를 일치시킴 |
| strict sequence 장치에 array API 사용 | 순서 보장 없음 | 이름별로 개별 handle을 가져옴 |
디버깅 절차와 관찰 포인트
reset 문제는 종종 "device not responding", "timeout", "register read returns 0xffffffff", "DMA never starts" 같은 비특이적 증상으로 나타납니다. 따라서 reset 자체만 보기보다, reset 전후의 power/clock/MMIO/interrupt 상태를 함께 기록하는 편이 훨씬 효과적입니다.
- DT binding 확인
resets,reset-names, provider#reset-cells가 실제 driver 기대와 맞는지 먼저 확인합니다. - probe ordering 확인
provider가 먼저 probe되었는지, consumer가-EPROBE_DEFER를 적절히 처리하는지 확인합니다. - 시퀀스 로깅
regulator on, clock enable, reset deassert, MMIO init 순서를 로그로 남깁니다. - 딜레이 검증
assert 폭과 deassert 후 안정화 시간이 데이터시트 최소값을 만족하는지 확인합니다. - resume와 cold boot 비교
cold boot에서만 성공하고 resume에서 실패하면 retention/reset 복구 문제가 많습니다.
# DT에서 reset 관련 노드 찾기
grep -R "reset-names" /sys/firmware/devicetree/base 2>/dev/null
# dmesg에서 provider/consumer 초기화 추적
dmesg | grep -i -E "reset|deassert|assert|probe defer|clock|regulator"
# 소비자 노드의 리소스 확인
grep -R "resets" /sys/firmware/devicetree/base 2>/dev/null
provider가 status를 지원하면 훨씬 편하지만, 많은 reset controller는 status를 제공하지 않습니다. 이 경우 reset 상태는 MMIO side effect, register accessibility, interrupt arrival 여부로 간접 확인하는 수밖에 없습니다.
흔한 실패 패턴과 원인 추적
| 증상 | 가능한 원인 | 먼저 볼 것 | 보통의 수정 방향 |
|---|---|---|---|
failed to get reset | DT 이름 불일치, provider 미등록, phandle 인덱스 오류 | reset-names, provider probe, -EPROBE_DEFER | dev_err_probe() 사용, DT binding 정리 |
| deassert 후 첫 MMIO read에서 hang | clock off, PM domain off, 전압 미안정 | clock/regulator/runtime PM 순서 | reset deassert 전 bring-up 순서 수정 |
| shared reset에서 갑자기 다른 장치도 죽음 | pulse reset 또는 exclusive 가정 오류 | line 공유 여부, provider topology | shared semantics 재검토, 전체 block 재설계 |
| cold boot는 되는데 resume에서 실패 | resume 시 reset/clock 복원 순서 누락 | PM callbacks, retention 여부 | resume 재초기화 경로 추가 |
| bulk API로는 안 되고 수동 순서에선 됨 | reset 순서 의존 하드웨어 | 데이터시트 sequence 요구 | array/bulk 대신 개별 reset control 사용 |
| assert/deassert는 되는데 reset()은 이상함 | pulse형과 level형 의미 혼동 | provider의 .reset 구현 방식 | driver가 사용하는 API 모델 통일 |
끝까지 따라가는 상태 전이 예제
아래는 USB controller가 cold boot와 runtime resume에서 reset을 어떻게 다르게 다뤄야 하는지 보여 주는 예제입니다.
| 시점 | 사건 | reset 관점 | 다른 서브시스템 | 사용자에게 보이는 증상 |
|---|---|---|---|---|
| T0 | bootloader가 core clock만 켜 둠 | reset 상태는 firmware 기본값 | bootloader handoff | 초기 콘솔은 살아 보일 수 있음 |
| T1 | provider probe | reset_controller_dev 등록 | clock/reset provider 초기화 | consumer는 아직 defer 가능 |
| T2 | USB driver probe | core, phy reset handle 획득 | DT parse | binding 틀리면 probe 실패 |
| T3 | power + clock 준비 | 아직 reset 유지 또는 pulse 전 | runtime PM, regulator, CCF | 순서가 틀리면 MMIO hang |
| T4 | reset deassert / pulse | 장치 내부 상태기 시작 | clock active | 이후 firmware load 가능 |
| T5 | runtime suspend | assert 유지 여부는 retention 정책에 따름 | clock off, regulator off 가능 | resume 설계 미흡 시 장치 사망 |
| T6 | runtime resume | 필요 시 reset 재트리거 또는 deassert 재확인 | PM restore | cold boot만 성공하는 버그가 드러남 |
관련 문서
- Common Clock Framework — reset과 같이 맞물리는 clock 시퀀스
- Regulator 프레임워크 — reset 전후 전압 레일과 DVFS 순서
- Device Tree —
resets,reset-names,#reset-cells바인딩 - 전원 관리 — runtime PM, suspend/resume 복구
- 디바이스 드라이버 — probe/remove와 devm 리소스 관리
- USB — PHY/core reset과 suspend/resume 복구의 대표 사례