Device Tree

1. Tổng quan

Với sự phát triển của khoa học công nghệ, trong hệ thống nhúng (embedded system), ngày càng có nhiều phần cứng được ra đời và sử dụng. Việc xác định và cấu hình các thiết bị phần cứng có thể trở nên phức tạp. Do đó, cần có một phương pháp chuẩn hóa nào đó mô tả thông phần cứng giúp người lập trình viên hiểu thông tin mô tả phần cứng chỉ theo 1 hướng xác định.

Ví dụ: Cùng với một thanh ghi trong vi điều khiển, các lập trình viên có thể define thanh ghi đó bằng các tên khác nhau khiến việc đọc và phát triển source code khó khăn.

Device tree (DT) giúp giải quyết vấn đề này bằng cách cung cấp một phương tiện mô tả cấu hình phần cứng của hệ thống. Deviece tree là một file môt tả cấu trúc cây, ở đó thì các device được đại diện bằng các node. Mỗi node có các trường, các trường có thể được gán giá trị hoặc để trống.

This image has an empty alt attribute; its file name is image-45.png

Device tree có thể mô tả các thông tin sau:

  • Số lượng và loại CPU
  • Địa chỉ cơ bản và kích thước bộ nhớ SoC
  • Các đường bus và kết nối ngoại vi với SoC
  • Ngắt và bộ điều khiển ngắt
  • Bộ điều khiển GPIO và trạng thái của GPIO

Về cơ bản, Device tree giúp nhúng thông tin cấu hình phần cứng vào mã của kernel. Sau khi hệ thống khởi động, bootloader sẽ chuyển Device tree vào Linux kernel và dựa vào thông tin các device được khai báo trong Device tree, các thiết bị được khởi tạo như platform_device, spi_device. i2c-device,…Bên cạnh đó, các thông tin về bộ nhớ, ngắt, tài nguyên mà deivce sử dụng cũng được chuyển đến kernel và kernel sẽ liên kết các thông tin này với device tương ứng.

Để tìm hiểu chi tiếp về Device Tree, có thể truy cập vào: The Devicetree Project

Công dụng của Device Tree

Device Tree cho phép dễ dàng thay đổi cấu trúc phần cứng của hệ thống mà không cần sửa đổi mã nguồn driver.

Device Tree giúp tách rời thông tin phần cứng từ mã nguồn kernel, làm cho kernel trở nên chuyên biệt hóa và dễ tái sử dụng trên nhiều nền tảng phần cứng.

2. Cơ bản về Device Tree source

DTS (Device Tree Source)

File có định dạng .dts là một file mô tả Device Tree ở định dạng văn bản. Trong ARM Linuxx, tệp .dts tương ứng với kiến trúc ARM và thường được đặt trong thư mục /arch/arm/boot/dts/.

DTSI (Device Tree Source Include)

Với một hệ thống SoC cơ bản sẽ tương ứng với nhiều board khác nhau, nên sẽ có nhiều thành phần phổ biến vầ Device Tree sẽ lưu trữ các thành phần phổ biến đó trong file có định dạng .dtsi . Các file này tương ứng với các file thư viện trong lập trình C.

DTC (Device Tree Compiler)

DTC là công cụ biên dịch DTS và có thể chuyển đổi các file .dts thành file có định dạng .dtb và công cụ này có sẵn trong mã nguồn kernel. Khi Linux Kernel bật Device Tree, trình biên dịch DTC sẽ thực hiện biên dịch DTS khi kernel được biên dịch.

DTB (Devce Tree Blob)

File có định dạng .dtb là file Device Tree được biểu diễn ở dạng mã nhị phân được biên dịch từ file .dts thông qua DTC

This image has an empty alt attribute; its file name is image-34.png

Việc biên dịch ngược từ .dtb sang .dts cũng có thể thực hiện được:

3. Các kiểu dữ liệu và thuộc cơ bản trong Device Tree

Một số kiểu dữ liệu sử dụng trong DT bao gồm:

  • Chuỗi, nằm trong dấu ngoặc kép “string”. Ta có thể sử dụng dấu phẩy để tạo một danh sách các chuỗi.
  • Số nguyên dương 32-bit, nằm trong dấu ngoặc nhọn <data>.
  • Dữ liệu boolean, là một trường trống. Nếu nó được khai báo trong device tree tức giá trị là true, nếu không được khai báo thì tức là false.

Đơn vị cơ bản của Device Tree là node, được lắp ráp thành cấu trúc dạng cây, chỉ có một node cha cho mỗi node ngoại trừ node gốc. Chỉ có một node gốc trong cây thiết bị và mỗi node chứa một số giá trị thuộc tính mô tả một số đặc điểm của node.

Xét vị dụ về một đoạn source trong file .dts bao gồm các thành phần sau

  • Node gốc: \
  • Node cho device: ví dụ uart@0xab010000
    • Tên node: uart
    • Địa chỉ của device: 0xab010000
  • Node con của node cpus: cpu@0

Trước khi chúng ta bắt đầu tìm hiểu về các API có thể được sử dụng để lấy ra dữ liệu từ các thuốc tính trong node, ta cùng tìm hiểu về định dạng cơ bản của một node cũng như các thuộc tính thường xuyên sử dụng.

<node-name>[@<unit-address>]

node-name: là một chuỗi có chiều dài tối đa 31 kí tự tuân theo quy chuẩn của C

unit-adress: (có thể có hoặc không)

  • Địa chỉ của modul phần cứng, nó phụ thuộc loại bus trong không gian chứa phần cứng đó
  • Địa chỉ này phải khớp với địa chỉ đầu tiên trong trường “reg”

Ví dụ:

Trong đoạn trích DT trên, bộ điều khiển I2C (i2c@021a0000) là một thiết bị được ánh xạ vào vùng nhớ. Do đó, phần địa chỉ node tương ứng với phần đầu của vùng nhớ nó ánh xạ tới. Tuy nhiên expander là một thiết bị I2C nên phần địa chỉ của node tương ứng với địa chỉ I2C trên bus của nó.

model

Trường model là kiểu dữ liệu chuỗi kí tự, nó xác định tên nhà sản xuất cũng như số hiệu model của thiết bị.

Trường này được khuyến khích dưới dạng “manufactuer, model”

compatible

Trường compatible có thể bao gồm 1 chuỗi hoặc nhiều chuỗi.

Là chuỗi string dùng để mapping giữa device node và driver, driver và device node phải có chung 1 chuỗi compatible khi đăng kí device node.

Định địa chỉ device: reg, #address-cells và #size-cells

Mỗi device được gán vào ít nhất một node trên DT. Có một số trường thông thường mà loại thiết bị nào cũng có, đặc biệt là các thiết bị nằm trên bus mà kernel biết (như SPI, I2C, platform, MDIO…). Các trường đó là reg, #address-cells và #size-cells. Mục đích của 3 trường này là định địa chỉ của chúng trên bus:

  • #size-cells: chỉ ra có bao nhiêu 32-bit cell được sử dụng để đaị diện cho kích thước địa chỉ của device.
  • #address-cells: chỉ ra có bao nhiêu 32-bit cell được dùng để đại diện cho địa chỉ.

Trường reg là một danh sách các cặp giá trị thường là địa chỉ của IO register block, thường bao gồm offset và length:

reg = <adress0 size0 [address1 size1] [address2 size2] …>

mỗi cặp giá trị address và cell đại diện cho vùng địa chỉ mà device sử dụng.

Ví dụ vơi GPIO0 trên beaglebone black:

Các device sẽ thừa kế #size-cells và #address-cells của các node cha (thông thường là các node đại diện cho bus controller).

status

Trường status giúp kernel nhận biến để load driver điều khiển thiết bị. Do đôi khi trong trạng thái “disable” để tiết kiệm năng lượng. Muốn enble thiết bị ta set giá trị cuả trường status là  “okay”.

4. Đăng ký và sử dụng device node

Đăng ký device node

Trong thực tế, để đăng kí  một device node. Đa số sử dụng kiểu of match table:

  • Driver đăng ký bảng of match table có chứa các chuỗi compatible
  • Device node có trường competible (trùng với chuỗi compatible trong driver)

Driver sử dụng DT có 2 hàm proberemove cũng có chức năng tương tự như với hàm init và hàm exit trong các driver thông thường. Tuy nhiên cần có thêm một số điều kiện:

  • Hàm probe được gọi ra khi thỏa mãn hai điều kiện: driver được load lên với câu lệnh insmod và 2 chuỗi compatible trong bảng of match table của driver và trong deivce node (có trường status = “okay”) phải trùng nhau. Hàm probe của driver được gọi và truyền vào con trỏ chứa device node
  • Hàm remove được gọi ra khi unload driver

Hai hàm trên của được khai báo trong driver thông qua struct: (xét ví dụ blink_led)

Kết hợp với macro sau để đăng kí driver với hệ điều hành:

Các API lấy thông tin từ device node

Kernel cung cấp API để lấy tài nguyên của thiết bị như vùng nhớ, ngắt, DMA chanel, clock,… từ các trường trong device node:

  • pdev: Con trỏ đến cấu trúc platform_device đại diện cho thiết bị.
  • type: Loại tài nguyên cần truy xuất, ví dụ: IORESOURCE_MEM cho tài nguyên bộ nhớ hoặc IORESOURCE_IO cho tài nguyên I/O.
  • num: Số thứ tự của tài nguyên cần truy xuất, có thể là 0, 1, 2, …

Tài nguyên được trả về struct resource:

  • start: Tùy thuộc vào tài nguyên, đây có thể là địa chỉ bắt đầu của một vùng bộ nhớ, IRQ line number, số kênh DMA hoặc offset thanh ghi
  • end: là điểm cuối của vùng nhớ hoặc là điểm cuối của offset thanh ghi. Trong trường hợp kênh IRQ hoặc DMA, hầu hết các giá trị bắt đầu và kết thúc đều giống nhau.
  • name: Tên của tài nguyên, thường được sử dụng để mô tả mục đích của tài nguyên.
  • flags: Các cờ đặc tả tài nguyên, như IORESOURCE_MEM cho tài nguyên bộ nhớ, IORESOURCE_IO cho tài nguyên I/O, và các cờ khác.

Ví dụ một đoạn code trong hàm probe với trường “reg” (đại diện cho vùng nhớ)

Ngoài ra, kernel cũng cung cấp các API để người dùng có thể lấy dữ liệu từ các trường tự định nghĩa trong devie node

  • Dữ liệu kiểu kí tự hoặc chuỗi kí tự:
  • Dữ liệu kiểu số nguyên:

5. Tính ghi đè

Trong Device Tree, chúng ta có thể sửa đổi thông tin các node bằng cách thêm thông tin các trường ở bên dưới phần đã khai báo node thông qua nhãn hoặc thay đổi hoàn toàn node. Với trường hợp trùng lặp (node hoặc thuộc tính trong node), nội dung được khai báo sau trong quá trình biên dịch luôn được ưu tiên.

Ta xét ví dụ sau:

Chúng ta sẽ thấy rằng kết quả từ quá trình biên dịch sẽ là thuộc tính trạng thái có giá trị “okay”.

Tóm lại, các định nghĩa sau luôn ghi đè lên các định nghĩa trước đó. Để ghi đè toàn bộ nút, bạn chỉ cần tạo lại chúng giống như cách bạn làm đối với các thuộc tính.

5. Ví dụ bink led

Ta xét một ví dụ với node device tree mô tả LED như sau:

Với các trường tự định nghĩa:

led_regs = <A B C D E>;

  •           A: Số thứ tự của chân
  •           B: địa chỉ của GPIO_SETDATAOUT_OFFSET
  •           C: địa chỉ của GPIO_CLEARDATAOUT_OFFSET
  •           D: địa chỉ của GPIO_OE_OFFSET
  •           E: địa chỉ của GPIO_DATAOUT

led_config = <A B>;

  •           A: số lần nháy
  •           B: khoảng cách giữa 2 lần nháy, tính bằng giây

Chương trình driver:

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top