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 차이, 실전 디버깅과 실패 패턴까지 자세히 다룹니다.

전제 조건: Common Clock Framework, Regulator 프레임워크, Device Tree, 전원 관리 문서를 먼저 읽으세요. reset은 단독으로 잘 동작해도 clock, regulator, runtime PM 순서가 어긋나면 장치가 전혀 다른 방식으로 망가집니다.
일상 비유: reset 라인은 장비 캐비닛의 재기동 스위치 + 인터록 시스템과 비슷합니다. 어떤 장비는 전원을 넣고 신호선을 맞춘 뒤 재기동해야 하고, 어떤 장비는 공유 스위치를 쓰기 때문에 내 장비만 따로 껐다 켠다고 생각하면 곤란합니다.

핵심 요약

  • 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 규칙도 함께 봐야 합니다.

단계별 이해

  1. reset 선 식별
    데이터시트와 DT에서 이 장치가 어떤 reset control과 연결되는지 먼저 찾습니다.
  2. 모델 판단
    이 reset이 level 유지형인지, pulse형인지, 공유형인지 단독형인지 구분합니다.
  3. 시퀀스 정리
    전원, clock, reset, 펌웨어 로드, MMIO 초기화 순서를 글로 먼저 적어 봅니다.
  4. 에러 경로 설계
    probe 실패, suspend/resume 실패, remove 시 reset 상태를 어디로 되돌릴지 결정합니다.
  5. 공유 자원 여부 확인
    다른 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 linereset 신호가 실제로 전달되는 물리 선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 consumerreset 신호를 받아 리셋되는 장치UART, USB controller, DMA, PHY, 외부 IC
reset line과 reset control, reset controller는 서로 다른 개념이다 Reset Controller 여러 reset control 보유 control A: bit[3] control B: pulse trigger reset line 0 USB core로 전달 reset line 1 + 2 복합 pulse 시퀀스 USB core consumer USB PHY consumer DMA engine consumer
핵심 구분: reset control 하나가 꼭 reset line 하나만 제어하는 것은 아닙니다. 공식 커널 문서도 "한 trigger action이 여러 reset line에 carefully timed pulse sequence를 일으킬 수 있다"고 설명합니다. 그래서 단순 비트 토글로만 사고하면 복합 reset 하드웨어를 오해하기 쉽습니다.

consumer 관점의 공식 의미: exclusive와 shared

reset controller API는 reset control을 요청할 때부터 의미를 나눕니다. exclusive reset은 드라이버가 해당 control을 직접 제어한다고 가정하고, shared reset은 여러 소비자가 같은 control을 함께 사용하는 상황을 모델링합니다. 이 차이는 단순한 ownership 표시가 아니라, assert(), deassert(), reset()의 물리적 효과 자체를 바꿉니다.

종류보장되는 의미물리적 효과언제 적합한가
exclusive소비자가 실제 reset 상태를 직접 제어assert는 즉시 assert, deassert는 즉시 deassertreset line이 장치 하나 또는 드라이버 한 개만의 책임일 때
shareddeassert 요청에 대한 refcount 의미만 보장첫 deassert와 마지막 assert만 물리 상태를 바꿀 수 있음여러 consumer가 같은 reset control을 함께 써야 할 때

공식 문서 기준으로 shared reset은 clock framework의 shared enable과 비슷합니다. 첫 번째 deassert만 실제 선을 deassert할 수 있고, 마지막 assert만 실제 선을 assert할 수 있습니다. 그래서 shared reset에서 reset_control_assert()를 호출했다고 해서 물리 선이 반드시 assert된다고 기대하면 안 됩니다. 다른 소비자가 계속 deassert를 요구하고 있으면 선은 여전히 deassert 상태일 수 있습니다.

shared reset은 "내가 assert했다"보다 "아직 누가 deassert를 원하는가"가 중요하다 Consumer A deassert 요청 deassert_count = 1 Shared reset control A deassert → 물리 선 deassert refcount 기반 의미 Consumer B deassert 요청 deassert_count = 2 이후 A가 assert를 호출해도 물리 선은 아직 assert되지 않을 수 있다 B가 여전히 deassert를 원하면 line은 deassert 상태를 유지한다 마지막 consumer가 assert해 refcount가 0이 될 때 비로소 물리 assert가 가능해진다
공식 문서의 강한 경고: shared reset control을 쓰는 consumer는 "assert를 호출했으니 하드웨어 레지스터와 내부 상태가 reset되었을 것"이라고 가정하면 안 됩니다. 반대로 다른 consumer가 모두 물러났다면 어느 순간 실제 assert가 들어올 수도 있으므로, reset이 발생할 가능성도 염두에 둬야 합니다.

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 resetreset_control_assert(), reset_control_deassert()소프트웨어가 상태를 유지함assert 상태에서 bus access가 끊길 수 있습니다.
self-deasserting pulse resetreset_control_reset()하드웨어가 pulse를 자체 생성 후 자동 해제pulse 폭과 내부 시퀀스는 하드웨어가 결정합니다.

공식 문서는 일반적으로 self-deasserting pulse reset은 shared에 적합하지 않다고 설명합니다. 어느 consumer가 pulse를 요청해도 연결된 모든 주변장치가 reset되기 때문입니다. 다만 API는 shared pulse reset도 허용하며, 그 경우 첫 trigger만 실제 pulse를 내고 이후에는 모든 consumer가 reset_control_rearm()을 호출할 때까지 추가 pulse를 내지 않는 모델을 제공합니다.

level 유지형 reset과 self-deasserting pulse reset은 API 의미가 다르다 Level reset assert(): 선을 active 상태로 유지 deassert(): 선을 inactive 상태로 유지 소프트웨어가 유지 상태를 책임짐 Self-deasserting pulse reset reset(): trigger 비트를 한 번 씀 하드웨어가 pulse를 발생시키고 자동 해제 pulse 폭과 세부 시퀀스는 provider 하드웨어가 결정
혼용 금지: 공식 문서는 shared reset line에서 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 getterdevm_reset_control_get_exclusive()단독 제어 reset을 얻습니다.
shared getterdevm_reset_control_get_shared()shared semantics의 reset을 얻습니다.
optional getterdevm_reset_control_get_optional_exclusive()DT에 reset이 없으면 NULL을 반환합니다.
deasserted getterdevm_reset_control_get_shared_deasserted()get과 deassert를 묶은 편의 API입니다.
released getterreset_control_get_exclusive_released()exclusive지만 처음에는 acquired 상태가 아닌 핸들을 얻습니다.
acquire/releasereset_control_acquire(), reset_control_release()temporarily exclusive 핸들에 대한 실제 획득/반납입니다.
bulk/arraydevm_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);
released/acquire-release 패턴: reset을 항상 한 driver가 독점하지 않고, 특정 구간에서만 잠깐 독점해야 하는 경우 *_exclusive_released()reset_control_acquire()/release() 조합이 필요합니다. 평소엔 놓아두고, 특정 민감 구간에서만 잠그는 모델입니다.

shared reset의 정확한 의미와 금지 패턴

shared reset은 가장 자주 오해되는 부분입니다. 공식 문서는 shared reset에 대해 아래 제약을 분명히 둡니다.

규칙이유
deassertassert 호출 수는 균형을 맞춰야 함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 간 의존성이 있으면 수동 순서 제어가 낫습니다.
배열 API의 한계: "reset A를 먼저 deassert하고 20us 기다린 뒤 reset B를 deassert해야 한다" 같은 하드웨어에는 array/bulk API를 쓰면 안 됩니다. 이런 경우는 이름별로 개별 reset control을 얻어 driver가 순서를 직접 관리해야 합니다.
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_devprovider의 핵심 descriptorops, DT xlate 정보, reset 개수, owner 등을 포함합니다.
reset_control_opsassert/deassert/reset/status 콜백 집합모든 콜백이 필수는 아니지만, consumer 기대치와 맞아야 합니다.
of_reset_n_cellsDT phandle 뒤 인자 개수#reset-cells와 맞아야 합니다.
of_xlateDT 인자를 내부 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_cellsof_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-cellsprovider가 phandle 뒤에 받는 인자 수provider의 of_reset_n_cells와 맞아야 합니다.
resetsconsumer가 사용하는 reset control 참조여러 개일 경우 순서가 ABI가 됩니다.
reset-namesconsumer가 reset 의미를 이름으로 구분index 의존 코드를 피하게 해 줍니다.
DT의 resets/reset-names가 consumer를 provider에 연결한다 provider node rst: reset-controller@... #reset-cells = <1> 12 = usb core 13 = usb phy consumer node resets = <&rst 12>, <&rst 13> reset-names = "core", "phy" devm_reset_control_get_exclusive(dev, "core") devm_reset_control_get_optional_exclusive(dev, "phy") driver-side mapping "core" → index 0 "phy" → index 1 이름이 ABI를 설명함 reset-names가 없으면 consumer driver가 인덱스 의미를 암묵적으로 가정해야 한다 가독성과 유지보수성을 위해 이름을 명확히 두는 편이 낫다
DT 작성 팁: 여러 reset line이 있는 IP는 reset-names를 두는 편이 좋습니다. 인덱스 0이 "core"인지 "phy"인지 driver가 암묵적으로 알고 있어야 하는 구조는 코드 리뷰와 보드 파생 관리에 취약합니다.

소비자 드라이버 구현 패턴

실전 consumer driver는 보통 다음 구조를 가집니다. reset만 따로 보는 것이 아니라 regulator, clock, runtime PM, firmware load와 한 흐름으로 다뤄야 합니다.

  1. 필요한 reset handle을 얻습니다.
  2. runtime PM 또는 power domain을 올립니다.
  3. regulator와 clock을 준비합니다.
  4. 필요 시 assert 후 최소 폭을 지킨 뒤 deassert합니다.
  5. MMIO 초기화와 firmware download를 수행합니다.
  6. 에러 시 되돌리는 순서를 명확히 정합니다.
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/enablestate machine이 clocked 상태가 되게 함
4. reset deassert 또는 pulse trigger장치를 실제 동작 상태로 전환
5. MMIO 초기화 / firmware load동작 조건 설정
흔한 버그: reset deassert 전에 bus clock이 안 살아 있으면 첫 번째 register access에서 hang이 나거나 timeout만 보일 수 있습니다. 겉보기엔 MMIO 버그나 인터럽트 문제처럼 보여도 실제 원인은 reset/clock 순서인 경우가 매우 많습니다.
대부분의 주변장치는 "전원 → clock → reset → MMIO" 순서를 요구한다 runtime PM / genpd register access 가능 regulator on 전압 안정화 clock enable bus/core clock reset deassert state machine 시작 MMIO init / firmware driver bring-up reverse path도 중요: driver stop 시에는 대개 MMIO quiesce → reset assert → clock off → regulator off 순서를 검토해야 한다

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 pollingreset 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가 지원하는 경우에만 의미 있음
rearm의 의미: shared pulse reset에서 실제 pulse는 첫 요청에만 발생합니다. 이후 다시 pulse가 필요하다면 모든 consumer가 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 상태를 함께 기록하는 편이 훨씬 효과적입니다.

  1. DT binding 확인
    resets, reset-names, provider #reset-cells가 실제 driver 기대와 맞는지 먼저 확인합니다.
  2. probe ordering 확인
    provider가 먼저 probe되었는지, consumer가 -EPROBE_DEFER를 적절히 처리하는지 확인합니다.
  3. 시퀀스 로깅
    regulator on, clock enable, reset deassert, MMIO init 순서를 로그로 남깁니다.
  4. 딜레이 검증
    assert 폭과 deassert 후 안정화 시간이 데이터시트 최소값을 만족하는지 확인합니다.
  5. 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 resetDT 이름 불일치, provider 미등록, phandle 인덱스 오류reset-names, provider probe, -EPROBE_DEFERdev_err_probe() 사용, DT binding 정리
deassert 후 첫 MMIO read에서 hangclock off, PM domain off, 전압 미안정clock/regulator/runtime PM 순서reset deassert 전 bring-up 순서 수정
shared reset에서 갑자기 다른 장치도 죽음pulse reset 또는 exclusive 가정 오류line 공유 여부, provider topologyshared 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 모델 통일
가장 자주 놓치는 것: reset 문제를 reset API 호출 수만 세면서 디버깅하면 실패합니다. 실제 원인은 provider 미등록, clock off, regulator off, 잘못된 PM ordering, bootloader handoff 의존성인 경우가 훨씬 많습니다.

끝까지 따라가는 상태 전이 예제

아래는 USB controller가 cold boot와 runtime resume에서 reset을 어떻게 다르게 다뤄야 하는지 보여 주는 예제입니다.

시점사건reset 관점다른 서브시스템사용자에게 보이는 증상
T0bootloader가 core clock만 켜 둠reset 상태는 firmware 기본값bootloader handoff초기 콘솔은 살아 보일 수 있음
T1provider probereset_controller_dev 등록clock/reset provider 초기화consumer는 아직 defer 가능
T2USB driver probecore, phy reset handle 획득DT parsebinding 틀리면 probe 실패
T3power + clock 준비아직 reset 유지 또는 pulse 전runtime PM, regulator, CCF순서가 틀리면 MMIO hang
T4reset deassert / pulse장치 내부 상태기 시작clock active이후 firmware load 가능
T5runtime suspendassert 유지 여부는 retention 정책에 따름clock off, regulator off 가능resume 설계 미흡 시 장치 사망
T6runtime resume필요 시 reset 재트리거 또는 deassert 재확인PM restorecold boot만 성공하는 버그가 드러남
cold boot와 runtime resume를 가로지르는 reset 상태 전이 T0 bootloader partial setup T1 provider probe rcdev 등록 T2 consumer probe reset get T3 power + clock ready T4 reset release MMIO init T5 suspend policy-dependent T6 resume re-sync resume는 cold boot의 축소판이 아니라 별도 시퀀스다. reset을 다시 걸어야 하는지, 유지해야 하는지, shared semantics가 유지되는지까지 따로 검토해야 한다.
상태 전이 해석 팁: cold boot만 성공하는 드라이버는 종종 bootloader가 남겨 둔 reset/clock 상태에 우연히 의존합니다. 진짜 검증은 module reload, runtime suspend/resume, probe error path에서 reset 상태가 스스로 복원되는지 보는 것입니다.