Embedded : : Linux/: : Device Tree

[Device Tree] 10. Device Tree + Platform Driver 통합

Jay.P Morgan 2026. 3. 4. 16:33

 

 

  10.  Device Tree + Platform Driver 통합

 

  10.1  DT + Flatform Driver 통합의 흐름

 

Device Tree(DT)와 Platform Driver의 통합은 현대 리눅스 커널 하드웨어 관리의 핵심입니다. 하드웨어의 '명세(Data)'와 이를 제어하는 '로직(Code)'을 완전히 분리하여, 커널을 다시 빌드하지 않고도 다양한 보드에 대응할 수 있게 설계되었습니다.

 

전체적인 흐름을 [인식 - 매칭 - 자원 획득]의 3단계로 나누어 설명해 드릴게요.

 

1. 인식: DT 노드가 Platform Device가 되는 과정

커널이 부팅될 때, of_platform_populate() 함수가 호출되면서 디바이스 트리의 노드들을 훑습니다.

  • 대상: compatible 속성을 가진 노드들(주로 soc 아래의 자식 노드들)이 대상입니다.
  • 결과: 각 노드는 커널 내부에서 struct platform_device라는 객체로 변환됩니다.
  • 정보 저장: 이때 노드에 적힌 reg, interrupts 정보는 struct resource 형태로 변환되어 해당 장치 객체 안에 저장됩니다.

 

2. 매칭: "너 내 동료가 돼라" (compatible 체크)

커널에 로드된 드라이버와 생성된 장치 객체를 연결하는 과정입니다.

  • 드라이버 측: struct platform_driver 구조체 내부의 of_match_table에 지원 가능한 compatible 문자열 리스트를 적어둡니다.
  • 장치(DT) 측: 노드에 적힌 compatible 문자열을 확인합니다.
  • 매칭 성공: 두 문자열이 완벽히 일치하면, 커널은 드라이버의 probe() 함수를 호출하며 장치 객체의 포인터를 인자로 넘겨줍니다.

 

3. 통합의 정수: probe() 함수에서의 상호작용

드라이버의 probe() 함수 안에서는 앞서 살펴본 OF_API를 사용하여 DT의 데이터를 실제 제어 코드로 가져옵니다.

단계 수행 작업 핵심 함수 (API)
자원 매핑 DT의 reg 주소를 커널 가상 주소로 변환 devm_platform_ioremap_resource()
인터럽트 DT의 interrupts 번호를 가져옴 platform_get_irq()
커스텀 설정 제조사 고유 프로퍼티(fifo-depth 등) 추출 of_property_read_u32()
클럭/리셋 연결된 클럭 및 리셋 라인 제어 devm_clk_get(), devm_reset_control_get()
 

 

4. 통합 구조의 장점

  1. Binary Reusability: 동일한 드라이버 바이너리를 수정 없이 여러 SoC에 사용할 수 있습니다. (버전별 차이는 DT 매칭 데이터(.data)로 해결)
  2. Clean Code: 하드웨어의 물리적 주소가 코드에 하드코딩되지 않아 코드가 깔끔해집니다.
  3. Dynamic Support: 앞서 설명한 DT Overlay와 결합하면, 실행 중인 시스템에 드라이버와 장치를 동적으로 붙였다 뗄 수 있습니다.

 

요약 코드 흐름

// 1. 드라이버는 "이 문자열을 가진 장치를 찾습니다"라고 선언
static const struct of_device_id my_match[] = {
    { .compatible = "my,awesome-sensor" },
    { }
};

// 2. 매칭되면 실행되는 함수
static int my_probe(struct platform_device *pdev) {
    // 3. DT(pdev->dev.of_node)에서 정보를 꺼내 하드웨어 초기화
    struct device_node *np = pdev->dev.of_node;
    ...
}

결론적으로, Device Tree는 하드웨어의 "설계도"이고, Platform Driver는 그 설계도를 보고 작업을 수행하는 "기술자"입니다. 커널은 이 둘 사이에서 compatible이라는 "계약서"를 확인하여 중매해 주는 역할을 합니다.

 

 

 

  10.2  완전한 DT 기반 Platform Driver 예제

 

아래의 코드는 현대적인 리눅스 커널 플랫폼 드라이버의 정석을 보여주는 아주 깔끔한 예제입니다. 이 드라이버는 "하드웨어 설명서(Device Tree)"를 읽어서 실제 장치를 구동하는 "기술자" 역할을 합니다.

 

 
  /* ===== 완전한 DT 기반 Platform Driver 예제 ===== */
 
  #include <linux/module.h>
  #include <linux/platform_device.h>
  #include <linux/of.h>
  #include <linux/of_device.h>
  #include <linux/clk.h>
  #include <linux/reset.h>
  #include <linux/io.h>
 
  /* 칩 버전별 데이터 */
  struct my_hw_data {
          int fifo_depth;
          bool has_dma;
  };
 
  static const struct my_hw_data hw_v1 = { .fifo_depth = 16, .has_dma = false };
  static const struct my_hw_data hw_v2 = { .fifo_depth = 64, .has_dma = true };
 
  /* of_device_id: compatible 문자열 → 드라이버 매칭 테이블 */
  static const struct of_device_id my_of_ids[] = {
          { .compatible = "myvendor,my-device-v1", .data = &hw_v1 },
          { .compatible = "myvendor,my-device-v2", .data = &hw_v2 },
          { /* sentinel */ }
  };
  MODULE_DEVICE_TABLE(of, my_of_ids);
 
  struct my_dev {
          void __iomem *base;
          struct clk *clk;
          struct reset_control *rst;
          const struct my_hw_data *hw;
          int irq;
  };
 
  static int my_probe(struct platform_device *pdev)
  {
          struct device *dev = &pdev->dev;
          struct my_dev *priv;
          u32 fifo_thr;
          int ret;
 
          priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
          if  (!priv)
                 return -ENOMEM;
 
          /* 1. compatible에 연결된 하드웨어 데이터 가져오기 */
          priv->hw = of_device_get_match_data(dev);
          if  (!priv->hw)
                 return -ENODEV;
 
          /* 2. reg → MMIO 매핑 (devm 관리) */
          priv->base = devm_platform_ioremap_resource(pdev, 0);
          if  (IS_ERR(priv->base))
                 return PTR_ERR(priv->base);
 
          /* 3. interrupts → IRQ 번호 */
          priv->irq = platform_get_irq(pdev, 0);
          if  (priv->irq < 0)
                 return priv->irq;
 
          /* 4. clocks → 클럭 가져오기 + 활성화 */
          priv->clk = devm_clk_get_enabled(dev, NULL);  /* v6.3+ */
          if  (IS_ERR(priv->clk))
                 return dev_err_probe(dev, PTR_ERR(priv->clk),
                                                        "failed to get clock\\n");
 
          /* 5. resets → 리셋 제어 */
          priv->rst = devm_reset_control_get_exclusive(dev, NULL);
          if  (IS_ERR(priv->rst))
                 return PTR_ERR(priv->rst);
          reset_control_deassert(priv->rst);
 
          /* 6. 커스텀 프로퍼티 읽기 (선택적, 기본값 지원) */
          ret = of_property_read_u32(dev->of_node, "fifo-threshold", &fifo_thr);
          if  (ret)
                 fifo_thr = priv->hw->fifo_depth / 2;      /* DT에 없으면 기본값 */
 
          platform_set_drvdata(pdev, priv);
 
          dev_info(dev, "probed: fifo=%d dma=%d irq=%d\\n",
                          priv->hw->fifo_depth, priv->hw->has_dma, priv->irq);
          return 0;
  }
 
  static void my_remove(struct platform_device *pdev)
  {
          struct my_dev *priv = platform_get_drvdata(pdev);
          reset_control_assert(priv->rst);
  }
 
  static struct platform_driver my_driver = {
          .probe = my_probe,
          .remove = my_remove,
          .driver = {
                  .name = "my-device",
                  .of_match_table = my_of_ids,         /* DT 매칭 테이블 등록 */
                  .pm = &my_pm_ops,                         /* 전원 관리 (선택) */
          },
  };
  module_platform_driver(my_driver);
 
  /*
   * 매칭 순서 (우선순위):
   * 1. of_match_table — Device Tree compatible 매칭
   * 2. acpi_match_table — ACPI _HID 매칭
   * 3. id_table — platform_device_id 이름 매칭
   * 4. driver.name — platform_device.name 직접 비교 (폴백)
   */
 
fwnode API (v4.13+): Device Tree와 ACPI 양쪽을 지원하는 드라이버는 of_* 대신 device_property_read_*(fwnode) API를 사용하면 DT/ACPI 코드를 통일할 수 있습니다. 예: device_property_read_u32(dev, "fifo-depth", &val)

 

각 파트가 어떤 의미를 갖는지, 실무적인 관점에서 핵심만 짚어드릴게요.

 

💡 한 줄 요약 (이 코드는 "Device Tree에서 하드웨어 정보를 뽑아내어(OF API), 커널의 자원 관리 시스템(devm)을 이용해 안전하고 효율적으로 장치를 초기화하는 모던 드라이버"의 전형입니다.)

 

 

1. 버전 관리의 묘미: of_device_id와 Match Data

가장 눈에 띄는 부분은 hw_v1, hw_v2 같은 구조체와 my_of_ids 테이블입니다.

  • 왜 이렇게 하나요? 똑같은 기능을 하는 UART라도 SoC 버전에 따라 FIFO 크기가 다를 수 있습니다. 코드 로직은 똑같은데 숫자 몇 개 때문에 드라이버를 새로 만드는 건 낭비죠.
  • 어떻게 작동하나요? DT에 적힌 compatible 문자열에 따라 커널이 자동으로 그에 맞는 hw_vX 데이터를 probe함수에 전달해줍니다. 드라이버는 of_device_get_match_data() 한 줄로 "아, 나는 지금 64바이트 FIFO를 가진 v2 장치를 다루고 있구나"라고 바로 알게 됩니다.

 

2. my_probe(): 하드웨어를 깨우는 6단계 과정

장치가 발견되면 실행되는 probe 함수는 일종의 체크리스트입니다.

  1. 메모리 할당: devm_kzalloc으로 드라이버가 쓸 메모리를 잡습니다.
  2. 데이터 매칭: 위에서 말한 버전별 사양 정보를 가져옵니다.
  3. I/O 매핑: DT의 reg 주소를 가져와 커널이 접근할 수 있는 가상 주소(base)로 바꿉니다. 이제 base + offset으로 레지스터를 읽고 쓸 수 있습니다.
  4. 인터럽트: 장치가 CPU에 신호를 보낼 통로(IRQ 번호)를 확보합니다.
  5. 에너지 공급 (Clock & Reset): 하드웨어에 전기를 넣어주고(Clock), 굳어있는 칩을 풀어줍니다(Reset).
  6. 세부 설정: DT에 적힌 fifo-threshold 같은 추가 옵션을 읽습니다.

 

3. devm_ (Managed API)의 마법

코드 곳곳에 보이는 devm_ 접두사가 이 코드의 핵심입니다.

  • 자동 청소: 예전에는 드라이버가 종료될 때(remove) 할당한 메모리를 풀고, 매핑을 풀고, 클럭을 끄는 코드를 일일이 다 짜야 했습니다. 하나라도 까먹으면 메모리 누수가 발생했죠.
  • 편의성: devm_을 쓰면 드라이버가 언로드될 때 커널이 역순으로 모든 자원을 알아서 해제해줍니다. 그래서 my_remove() 함수가 저렇게 짧아질 수 있는 겁니다.

 

4. 매칭 우선순위 (The Matching Hierarchy)

주석에 달린 우선순위는 시스템이 하드웨어를 찾는 "고집"의 순서입니다.

순위 방식 특징
1 Device Tree 임베디드 리눅스의 표준. compatible 문자열 비교.
2 ACPI 주로 x86(PC/서버) 환경에서 사용.
3 ID Table 레거시 방식. 특정 이름 목록을 가지고 비교.
4 Driver Name 최후의 보루. 드라이버 이름과 장치 이름이 같으면 매칭.

 

 

 

 

 

참고: https://www.minzkn.com/linuxkernel/pages/device-tree.html#dt-compile