This is the documentation for the latest (master) development branch of Zephyr. If you are looking for the documentation of previous releases, use the drop-down menu on the left and select the desired version.

Legacy devicetree macros

Warning

As of Zephyr v2.3, you can still use these macros if you set CONFIG_LEGACY_DEVICETREE_MACROS=y in your application’s Kconfig. Legacy macro support will be maintained for at least two releases, including v2.3 itself.

See Devicetree access from C/C++ for a usage guide for the new API, and Migrate from the legacy macros for a migration guide for existing code.

This page describes legacy C preprocessor macros which Zephyr’s build system generates from a devicetree. It assumes you’re familiar with the concepts in Introduction to devicetree and Devicetree bindings.

These macros have somewhat inconsistent names, and their use in new code is discouraged. See Devicetree access from C/C++ for the recommended API.

These macros are generated by the devicetree scripts, start with DT_, and use all-uppercase.

Legacy node identifiers

Macros generated from individual devicetree nodes or their properties start with DT_<node>, where <node> is a C identifier for the devicetree node. This section describes the different <node> values in the legacy macros.

We’ll use the following DTS fragment from the FRDM-K64F board’s devicetree as the main example throughout this section.

/ {

     aliases {
             i2c-0 = &i2c0;
     };

     soc {
             i2c0: i2c@40066000 {
                     compatible = "nxp,kinetis-i2c";
                     reg = <0x40066000 0x1000>;
                     status = "okay";
                     /* ... */

                     fxos8700@1d {
                             compatible = "nxp,fxos8700";
                             status = "okay";
                             /* ... */
                     };
             };
     };
};

The binding for the “nxp,fxos8700” compatible property contains this line:

on-bus: i2c

The generated macros for this example can be found in a build directory for the FXOS8700 sample application built for the frdm_k64f board, in the file build/zephyr/include/generated/devicetree_legacy_unfixed.h.

Here is part of devicetree_legacy_unfixed.h showing some of the macros for the node labeled i2c0. Notice the comment with the node’s path in the devicetree and its dependency relationships with other nodes.

/*
 * Devicetree node:
 *   /soc/i2c@40066000
 *
 * Binding (compatible = nxp,kinetis-i2c):
 *   $ZEPHYR_BASE/dts/bindings/i2c/nxp,kinetis-i2c.yaml
 *
 * Dependency Ordinal: 66
 *
 * Requires:
 *   6   /soc
 *   ...
 *
 * Supports:
 *   67  /soc/i2c@40066000/fxos8700@1d
 *
 * Description:
 *   Kinetis I2C node
 */
#define DT_NXP_KINETIS_I2C_40066000_BASE_ADDRESS    0x40066000
#define DT_NXP_KINETIS_I2C_40066000_SIZE            4096
#define DT_ALIAS_I2C_0_BASE_ADDRESS                 DT_NXP_KINETIS_I2C_40066000_BASE_ADDRESS
#define DT_ALIAS_I2C_0_SIZE                         DT_NXP_KINETIS_I2C_40066000_SIZE
#define DT_INST_0_NXP_KINETIS_I2C_BASE_ADDRESS      DT_NXP_KINETIS_I2C_40066000_BASE_ADDRESS
#define DT_INST_0_NXP_KINETIS_I2C_SIZE              DT_NXP_KINETIS_I2C_40066000_SIZE

Most macros are generated for individual nodes or their properties. Some macros are generated for “global” information about the entire devicetree.

In this example, the node identifiers for i2c@40066000 are:

  • NXP_KINETIS_I2C_40066000

  • ALIAS_I2C_0

  • INST_0_NXP_KINETIS_I2C

In general, the following DT_<node> macro prefixes are created for each node.

DT_(<bus>_)<compatible>_<unit-address>

The node’s compatible property converted to a C identifier, followed by its unit address. If the node has multiple compatible strings, the one for its matching binding is used.

If the node appears on a bus (and therefore has on-bus: in its binding, like fxos8700@1d does), then the compatible string and unit address of the bus node is put before the compatible string for the node itself. If the node does not appear on a bus (no on-bus: in the binding, like i2c@40066000) then there will be no <bus>_ portion in the node identifier.

The i2c@40066000 node identifier is NXP_KINETIS_I2C_40066000:

  • there is no <bus>_ portion

  • <compatible> is NXP_KINETIS_I2C, which is its compatible "nxp,kinetis-i2c" converted to a C identifier by uppercasing and replacing non-alphanumeric characters with underscores

  • <unit-address> is 40066000

The fxos8700@1d node identifier is NXP_KINETIS_I2C_40066000_NXP_FXOS8700_1D:

  • <bus> is NXP_KINETIS_I2C_40066000

  • <compatible> is NXP_FXOS8700

  • <unit-address> is 1D

If the node has no unit address, the unit address of the parent node plus the node’s name converted to a C identifier is used for <unit-address> instead. If the parent node has no unit address either, the name of the node is used as a fallback.

For example, take this DTS fragment:

ethernet@400c0004 {
        compatible = "nxp,kinetis-ethernet";
        reg = <0x400c0004 0x620>;
        status = "okay";
        ptp {
                compatible = "nxp,kinetis-ptp";
                status = "okay";
                interrupts = <0x52 0x0>;
        };
};

The ptp node identifier is NXP_KINETIS_PTP_400C0004_PTP:

  • there is no <bus>_ portion

  • <compatible> is NXP_KINETIS_PTP

  • <unit-address> is 400C0004_PTP, which combines its parent’s unit address and the node’s name converted to a C identifier

Here is another example DTS fragment.

soc {
       temp1 {
               compatible = "nxp,kinetis-temperature";
               status = "okay";
       };
};

The temp1 node identifier is NXP_KINETIS_TEMPERATURE_TEMP1:

  • there is no <bus>_ portion

  • <compatible> is NXP_KINETIS_TEMPERATURE

  • <unit-address> is the fallback value TEMP1, because neither the node nor its parent have a unit address

DT_INST_<instance-number>_<compatible>

An instance number for the node, combined with its compatible converted to a C identifier.

The instance number is a unique index among all enabled (status = "okay") nodes that have a particular compatible string, starting from zero.

The i2c@40066000 node identifier in the main example is INST_0_NXP_KINETIS_I2C:

  • <instance-number> is 0 because it was the first node with compatible “nxp,kinetis-i2c” that the devicetree scripts happened to discover as they walked the tree

  • <compatible> is NXP_KINETIS_I2C

As another example, if there are two enabled nodes that have compatible = "foo,uart", then these node identifiers get generated:

INST_0_FOO_UART
INST_1_FOO_UART

Warning

Instance numbers are simple indexes among enabled nodes with the same compatible. They in no way reflect any numbering scheme that might exist in SoC documentation, node labels or unit addresses, or properties of the /aliases node.

There is no guarantee that the same node will have the same instance number between application builds. The only guarantee is that instance numbers will start at 0, be contiguous, and be assigned for each enabled node with a matching compatible.

DT_ALIAS_<alias>

Generated from the names of any properties in the /aliases node. See aliases and chosen nodes for an overview.

Here is a simple example.

/ {
    aliases {
            uart-1 = &my_uart;
    };

    my_uart: uart@12345 { /* ... */ };
};

The uart@12345 node identifier is ALIAS_UART_1: <alias> is UART_1 by uppercasing uart-1 and replacing non-alphanumeric characters with underscores. The alias refers to uart@12345 using its label my_uart.

For such a simple concept, dealing with aliases can be surprisingly tricky due to multiple names which have only minor differences.

For a real-world example, the i2c@40066000 node’s alias identifier in the main example is ALIAS_I2C_0: <alias> is I2C_0 because the property i2c-0 = &i2c0; in the /aliases node “points at” i2c@40066000 using its label i2c0. The alias name i2c-0 is converted to C identifier I2C_0.

The differences between i2c-0, &i2c0, i2c0, and i2c@40006000 in this example are very subtle and can be quite confusing at first. Here is some more clarification:

  • i2c-0 is the name of a property in the /aliases node; this is the alias name

  • &i2c0 is that property’s value, which is the phandle of the the node with label i2c0

  • i2c@40006000 is the name of the node which happens to have label i2c0 in this example

See the devicetree specification for full details.

Note

Another DT_<compatible>_<alias> form is also generated for aliases. For the example above, assuming the compatible string for the &uart1 node is "foo,uart", this gives DT_FOO_UART_UART_1.

Macros generated for properties

Macros for node property values have the form DT_<node>_<property>, where <node> is a node identifier and <property> identifies the property. The macros generated for a property usually depend on its type: key in the matching devicetree binding.

The following general purpose rules apply in most cases:

However, some “special” properties get individual treatment:

No macros are currently generated for properties with type phandle, phandles, path, or compound.

Generic property macros

This section documents the macros generated for non-“special” properties by example. These properties are handled based on their devicetree binding type: keys.

In the generic case, the <property> portion of a DT_<node>_<property> macro begins with the property’s name converted to a C identifier by uppercasing it and replacing non-alphanumeric characters with underscores. For example, a baud-rate property has a <property> portion that starts with BAUD_RATE.

The table below gives the values generated for simple types. Note that an index is added at the end of identifiers generated from properties with array or string-array type, and that array properties generate an additional compound initializer ({ ... }).

Type

Property and value

Generated macros

int

foo = <1>

#define DT_<node>_FOO 1

array

foo = <1 2>

#define DT_<node>_FOO_0 1
#define DT_<node>_FOO_1 2
#define DT_<node>_FOO {1, 2}

string

foo = "bar"

#define DT_<node>_FOO "bar"

string-array

foo = "bar", "baz"

#define DT_<node>_FOO_0 "bar"
#define DT_<node>_FOO_1 "baz"

uint8-array

foo = [01 02]

#define DT_<node>_FOO {0x01, 0x02}

For type: boolean, the generated macro is set to 1 if the property exists on the node, and to 0 otherwise:

#define DT_<node>_FOO 0/1

For non-boolean types the property macros are not generated if the binding’s category is optional and the property is not present in the devicetree source.

Properties with type phandle-array

The generation for properties with type phandle-array is the most complex. To understand it, it is a good idea to first go through the documentation for phandle-array in Devicetree bindings.

Take the following devicetree nodes and binding contents as an example:

Devicetree nodes for PWM controllers
pwm_ctrl_0: pwm-controller-0 {
     label = "pwm-0";
     #pwm-cells = <2>;
     /* ... */
};

pwm_ctrl_1: pwm-controller-1 {
     label = "pwm-1";
     #pwm-cells = <2>;
     /* ... */
};
pwm-cells declaration in binding for vendor,pwm-controller
pwm-cells:
    - channel
    - period

Assume the property assignment looks like this:

pwm-user@0 {
        status = "okay";
        pwms = <&pwm_ctrl_0 1 10>, <&pwm_ctrl_1 2 20>;
        pwm-names = "first", "second";
        /* ... */
};

These macros then get generated.

#define DT_<node>_PWMS_CONTROLLER_0        "pwm-0"
#define DT_<node>_PWMS_CHANNEL_0           1
#define DT_<node>_PWMS_PERIOD_0            10

#define DT_<node>_PWMS_CONTROLLER_1        "pwm-1"
#define DT_<node>_PWMS_CHANNEL_1           2
#define DT_<node>_PWMS_PERIOD_1            20

#define DT_<node>_PWMS_NAMES_0             "first"
#define DT_<node>_PWMS_NAMES_1             "second"

#define DT_<node>_FIRST_PWMS_CONTROLLER    DT_<node>_PWMS_CONTROLLER_0
#define DT_<node>_FIRST_PWMS_CHANNEL       DT_<node>_PWMS_CHANNEL_0
#define DT_<node>_FIRST_PWMS_PERIOD        DT_<node>_PWMS_PERIOD_0

#define DT_<node>_SECOND_PWMS_CONTROLLER   DT_<node>_PWMS_CONTROLLER_1
#define DT_<node>_SECOND_PWMS_CHANNEL      DT_<node>_PWMS_CHANNEL_1
#define DT_<node>_SECOND_PWMS_PERIOD       DT_<node>_PWMS_PERIOD_1

/* Initializers */

#define DT_<node>_PWMS_0                   {"pwm-0", 1, 10}
#define DT_<node>_PWMS_1                   {"pwm-1", 2, 20}
#define DT_<node>_PWMS                     {DT_<node>_PWMS_0, DT_<node>_PWMS_1}
#define DT_<node>_PWMS_COUNT               2

Macros with a *_0 suffix deal with the first entry in pwms (<&pwm_ctrl_0 1 10>). Macros with a *_1 suffix deal with the second entry (<&pwm_ctrl_1 2 20>). The index suffix is only added if there’s more than one entry in the property.

The DT_<node>_PWMS_CONTROLLER(_<index>) macros are set to the string from the label property of the referenced controller. The DT_<node>_PWMS_CHANNEL(_<index>) and DT_<node>_PWMS_PERIOD(_<index>) macros are set to the values of the corresponding cells in the pwms property, with macro names generated from the strings in pwm-cells: in the binding for the controller.

The macros in the /* Initializers */ section provide the same information as DT_<node>_PWMS_{CONTROLLER,CHANNEL,PERIOD}(_<index>), except as compound initializers that can be used to initialize C struct variables.

If a pwm-names property exists on the same node as pwms (more generally, if a foo-names property is defined next to a foo property with type phandle-array), it gives a list of strings that name each entry in pwms. The names are used to generate extra macro names with the name instead of an index, like DT_<node>_FIRST_PWMS_CONTROLLER above.

Properties with enum: in the binding

Properties declared with an enum: key in their binding generate a macro that gives the the zero-based index of the property’s value in the enum: list.

Take this binding declaration as an example:

properties:
    foo:
        type: string
        enum:
            - one
            - two
            - three

The property foo = "three" then generates this macro:

#define DT_<node>_FOO_ENUM 2

reg property macros

reg properties generate the macros DT_<node>_BASE_ADDRESS(_<index>) and DT_<node>_SIZE(_<index>). <index> is a numeric index starting from 0, which is only added if there’s more than one register defined in reg.

For example, the reg = <0x4004700 0x1060> assignment in the example devicetree above gives these macros:

#define DT_<node>_BASE_ADDRESS    0x40047000
#define DT_<node>_SIZE            4192

Note

The length of the address and size portions of reg is determined from the #address-cells and #size-cells properties. See the devicetree specification for more information.

In this case, both #address-cells and #size-cells are 1, and there’s just a single register in reg. Four numbers would give two registers.

If a reg-names property exists on the same node as reg, it gives a list of strings that names each register in reg. The names are used to generate extra macros. For example, reg-names = "foo" together with the example node generates these macros:

#define DT_<node>_FOO_BASE_ADDRESS    0x40047000
#define DT_<node>_FOO_SIZE            4192

interrupts property macros

Take these devicetree nodes as an example:

timer@123 {
     interrupts = <1 5 2 6>;
     interrupt-parent = <&intc>;
     /* ... */
};

intc: interrupt-controller { /* ... */ };

Assume that the binding for interrupt-controller has these lines:

interrupt-cells:
    - irq
    - priority

Then these macros get generated for timer@123:

#define DT_<node>_IRQ_0                   1
#define DT_<node>_IRQ_0_PRIORITY          5
#define DT_<node>_IRQ_1                   2
#define DT_<node>_IRQ_1_PRIORITY          6

These macros have the the format DT_<node>_IRQ_<index>(_<name>), where <node> is the node identifier for timer@123, <index> is an index that identifies the particular interrupt, and <name> is the identifier for the cell value (a number within interrupts = <...>), taken from the binding.

Bindings for interrupt controllers are expected to declare a cell named irq in interrupt-cells, giving the interrupt number. The _<name> suffix is skipped for macros generated from irq cells, which is why there’s e.g. a DT_<node>_IRQ_0 macro and no DT_<node>_IRQ_0_IRQ macro.

If the interrupt controller in turn generates other interrupts, Zephyr uses a multi-level interrupt encoding for the interrupt numbers at each level. See Multi-level Interrupt handling for more information.

There is also hard-coded logic for mapping Arm GIC interrupts to linear IRQ numbers. See the source code for details.

Additional macros that use names instead of indices for interrupts can be generated by including an interrupt-names property on the interrupt-generating node. For example, this node:

timer@456 {
    interrupts = <10 50 20 60>;
    interrupt-parent = <&intc>;
    interrupt-names = "timer-a", "timer-b";
    /* ... */
};

generates these macros:

#define DT_<node>_IRQ_TIMER_A             1
#define DT_<node>_IRQ_TIMER_A_PRIORITY    5
#define DT_<node>_IRQ_TIMER_B             2
#define DT_<node>_IRQ_TIMER_B_PRIORITY    6

clocks property macros

clocks work the same as other Properties with type phandle-array, except the generated macros have CLOCK in them instead of CLOCKS, giving for example DT_<node>_CLOCK_CONTROLLER_0 instead of DT_<node>_CLOCKS_CONTROLLER_0.

If a clocks controller node has a "fixed-clock" compatible, it must also have a clock-frequency property giving its frequency in Hertz. In this case, an additional macro is generated:

#define DT_<node>_CLOCKS_CLOCK_FREQUENCY <frequency>

cs-gpios property macros

Take these devicetree nodes as an example. where the binding for vendor,spi-controller is assumed to have bus: spi, and the bindings for the SPI slaves are assumed to have on-bus: spi:

gpioa: gpio@400ff000 {
     compatible = "vendor,gpio-ctlr";
     reg = <0x400ff000 0x40>;
     label = "GPIOA";
     gpio-controller;
     #gpio-cells = <0x1>;
};

spi {
     compatible = "vendor,spi-controller";
     cs-gpios = <&gpioa 1>, <&gpioa 2>;
     spi-slave@0 {
             compatible = "vendor,foo-spi-device";
             reg = <0>;
     };
     spi-slave@1 {
             compatible = "vendor,bar-spi-device";
             reg = <1>;
     };
};

Here, the unit address of the SPI slaves (0 and 1) is taken as a chip select number, which is used as an index into cs-gpios (a phandle-array). spi-slave@0 is matched to <&gpioa 1>, and spi-slave@1 to <&gpiob 2>.

The output for spi-slave@0 and spi-slave@1 is the same as if the devicetree had looked like this:

gpioa: gpio@400ff000 {
     compatible = "vendor,gpio-ctlr";
     reg = <0x400ff000 0x40>;
     label = "GPIOA";
     gpio-controller;
     #gpio-cells = <1>;
};

spi {
     compatible = "vendor,spi-controller";
     spi-slave@0 {
             compatible = "vendor,foo-spi-device";
             reg = <0>;
             cs-gpios = <&gpioa 1>;
     };
     spi-slave@1 {
             compatible = "vendor,bar-spi-device";
             reg = <1>;
             cs-gpios = <&gpioa 2>;
     };
};

See the phandle-array section in Generic property macros for more information.

For example, since the node labeled gpioa has property label = "GPIOA" and 1 and 2 are pin numbers, macros like the following will be generated for spi-slave@0:

#define DT_<node>_CS_GPIOS_CONTROLLER    "GPIOA"
#define DT_<node>_CS_GPIOS_PIN           1

Other macros

These are generated in addition to macros generated for properties.

Node existence flags

An “existence flag” is a macro which is defined when the devicetree contains nodes matching some criterion.

Existence flags are generated for each compatible property that appears on an enabled node:

#define DT_COMPAT_<compatible> 1

An existence flag is also written for all enabled nodes with a matching compatible:

#define DT_INST_<instance-number>_<compatible>    1

For the i2c@40066000 node in the example above, assuming the node is the first node with compatible = "nxp,kinetis-i2c", the following existence flags would be generated:

/* At least one node had compatible nxp,kinetis-i2c: */
#define DT_COMPAT_NXP_KINETIS_I2C    1

/* Instance 0 of compatible nxp,kinetis-i2c exists: */
#define DT_INST_0_NXP_KINETIS_I2C    1

If additional nodes had compatible nxp,kinetis-i2c, additional existence flags would be generated:

#define DT_INST_1_NXP_KINETIS_I2C    1
#define DT_INST_2_NXP_KINETIS_I2C    1
/* ... and so on, one for each node with this compatible. */

Macros generated from flash partitions

Note

This section only covers flash partitions. See aliases and chosen nodes for some other flash-related macros that get generated from devicetree, via /chosen.

If a node has a name that looks like partition@<unit-address>, it is assumed to represent a flash partition.

Assume the devicetree has this:

flash@0 {
     /* ... */
     label = "foo-flash";

     partitions {
             /* ... */
             #address-cells = <1>;
             #size-cells = <1>;

             boot_partition: partition@0 {
                     label = "mcuboot";
                     reg = <0x00000000 0x00010000>;
                     read-only;
             };
             slot0_partition: partition@10000 {
                     label = "image-0";
                     reg = <0x00010000 0x00020000
                            0x00040000 0x00010000>;
             };
             /* ... */
};

These macros then get generated:

#define DT_FLASH_AREA_MCUBOOT_ID           0
#define DT_FLASH_AREA_MCUBOOT_READ_ONLY    1
#define DT_FLASH_AREA_MCUBOOT_OFFSET_0     0x0
#define DT_FLASH_AREA_MCUBOOT_SIZE_0       0x10000
#define DT_FLASH_AREA_MCUBOOT_OFFSET       DT_FLASH_AREA_MCUBOOT_OFFSET_0
#define DT_FLASH_AREA_MCUBOOT_SIZE         DT_FLASH_AREA_MCUBOOT_SIZE_0
#define DT_FLASH_AREA_MCUBOOT_DEV          "foo-flash"

#define DT_FLASH_AREA_IMAGE_0_ID           0
#define DT_FLASH_AREA_IMAGE_0_READ_ONLY    1
#define DT_FLASH_AREA_IMAGE_0_OFFSET_0     0x10000
#define DT_FLASH_AREA_IMAGE_0_SIZE_0       0x20000
#define DT_FLASH_AREA_IMAGE_0_OFFSET_1     0x40000
#define DT_FLASH_AREA_IMAGE_0_SIZE_1       0x10000
#define DT_FLASH_AREA_IMAGE_0_OFFSET       DT_FLASH_AREA_IMAGE_0_OFFSET_0
#define DT_FLASH_AREA_IMAGE_0_SIZE         DT_FLASH_AREA_IMAGE_0_SIZE_0
#define DT_FLASH_AREA_IMAGE_0_DEV          "foo-flash"

/* Same macros, just with index instead of label */
#define DT_FLASH_AREA_0_ID           0
#define DT_FLASH_AREA_0_READ_ONLY    1
...

The *_ID macro gives the zero-based index for the partition.

The *_OFFSET_<index> and *_SIZE_<index> macros give the offset and size for each partition, derived from reg. The *_OFFSET and *_SIZE macros, with no index, are aliases that point to the first sector (with index 0).

aliases and chosen nodes

Using an alias with a common name for a particular node makes it easier for you to write board-independent source code. Devicetree aliases nodes are used for this purpose, by mapping certain generic, commonly used names to specific hardware resources:

aliases {
   led0 = &led0;
   sw0 = &button0;
   sw1 = &button1;
   uart-0 = &uart0;
   uart-1 = &uart1;
};

Certain software subsystems require a specific hardware resource to bind to in order to function properly. Some of those subsystems are used with many different boards, which makes using the devicetree chosen nodes very convenient. By doing so, the software subsystem can rely on having the specific hardware peripheral assigned to it. In the following example we bind the shell to uart1 in this board:

chosen {
   zephyr,shell-uart = &uart1;
};

The table below lists Zephyr-specific chosen properties. The macro identifiers that start with CONFIG_* are generated from Kconfig symbols that reference devicetree data via the Kconfig preprocessor.

Note

Since the particular devicetree isn’t known while generating Kconfig documentation, the Kconfig symbol reference pages linked below do not include information derived from devicetree. Instead, you might see e.g. an empty default:

default "" if HAS_DTS

To see how the preprocessor is used for a symbol, look it up directly in the Kconfig file where it is defined instead. The reference page for the symbol gives the definition location.

chosen node name

Generated macros

zephyr,flash

DT_FLASH_BASE_ADDRESS/DT_FLASH_SIZE/DT_FLASH_ERASE_BLOCK_SIZE/DT_FLASH_WRITE_BLOCK_SIZE

zephyr,code-partition

DT_CODE_PARTITION_OFFSET/DT_CODE_PARTITION_SIZE

zephyr,sram

CONFIG_SRAM_BASE_ADDRESS/CONFIG_SRAM_SIZE

zephyr,ccm

DT_CCM_BASE_ADDRESS/DT_CCM_SIZE

zephyr,dtcm

DT_DTCM_BASE_ADDRESS/DT_DTCM_SIZE

zephyr,ipc_shm

DT_IPC_SHM_BASE_ADDRESS/DT_IPC_SHM_SIZE

zephyr,console

CONFIG_UART_CONSOLE_ON_DEV_NAME

zephyr,shell-uart

CONFIG_UART_SHELL_ON_DEV_NAME

zephyr,bt-uart

CONFIG_BT_UART_ON_DEV_NAME

zephyr,uart-pipe

CONFIG_UART_PIPE_ON_DEV_NAME

zephyr,bt-mon-uart

CONFIG_BT_MONITOR_ON_DEV_NAME

zephyr,bt-c2h-uart

CONFIG_BT_CTLR_TO_HOST_UART_DEV_NAME

zephyr,uart-mcumgr

CONFIG_UART_MCUMGR_ON_DEV_NAME

Legacy flash partitions

Devicetree can be used to describe a partition layout for any flash device in the system.

Two important uses for this mechanism are:

  1. To force the Zephyr image to be linked into a specific area on Flash.

    This is useful, for example, if the Zephyr image must be linked at some offset from the flash device’s start, to be loaded by a bootloader at runtime.

  2. To generate compile-time definitions for the partition layout, which can be shared by Zephyr subsystems and applications to operate on specific areas in flash.

    This is useful, for example, to create areas for storing file systems or other persistent state. These defines only describe the boundaries of each partition. They don’t, for example, initialize a partition’s flash contents with a file system.

Partitions are generally managed using device tree overlays. See Set devicetree overlays for examples.

Defining Partitions

The partition layout for a flash device is described inside the partitions child node of the flash device’s node in the device tree.

You can define partitions for any flash device on the system.

Most Zephyr-supported SoCs with flash support in device tree will define a label flash0. This label refers to the primary on-die flash programmed to run Zephyr. To generate partitions for this device, add the following snippet to a device tree overlay file:

&flash0 {
        partitions {
                compatible = "fixed-partitions";
                #address-cells = <1>;
                #size-cells = <1>;

                /* Define your partitions here; see below */
        };
};

To define partitions for another flash device, modify the above to either use its label or provide a complete path to the flash device node in the device tree.

The content of the partitions node looks like this:

partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        partition1_label: partition@START_OFFSET_1 {
                label = "partition1_name";
                reg = <0xSTART_OFFSET_1 0xSIZE_1>;
        };

        /* ... */

        partitionN_label: partition@START_OFFSET_N {
                label = "partitionN_name";
                reg = <0xSTART_OFFSET_N 0xSIZE_N>;
        };
};

Where:

  • partitionX_label are device tree labels that can be used elsewhere in the device tree to refer to the partition

  • partitionX_name controls how defines generated by the Zephyr build system for this partition will be named

  • START_OFFSET_x is the start offset in hexadecimal notation of the partition from the beginning of the flash device

  • SIZE_x is the hexadecimal size, in bytes, of the flash partition

The partitions do not have to cover the entire flash device. The device tree compiler currently does not check if partitions overlap; you must ensure they do not when defining them.

Example Primary Flash Partition Layout

Here is a complete (but hypothetical) example device tree overlay snippet illustrating these ideas. Notice how the partitions do not overlap, but also do not cover the entire device.

&flash0 {
        partitions {
                compatible = "fixed-partitions";
                #address-cells = <1>;
                #size-cells = <1>;

                code_dts_label: partition@8000 {
                        label = "zephyr-code";
                        reg = <0x00008000 0x34000>;
                };

                data_dts_label: partition@70000 {
                        label = "application-data";
                        reg = <0x00070000 0xD000>;
                };
        };
};

Linking Zephyr Within a Partition

To force the linker to output a Zephyr image within a given flash partition, add this to a device tree overlay:

/ {
        chosen {
                zephyr,code-partition = &slot0_partition;
        };
};

Then, enable the CONFIG_USE_DT_CODE_PARTITION Kconfig option.

Flash Partition Macros

The Zephyr build system generates definitions for each flash device partition. These definitions are available to any files which include <zephyr.h>.

Consider this flash partition:

dts_label: partition@START_OFFSET {
        label = "def-name";
        reg = <0xSTART_OFFSET 0xSIZE>;
};

The build system will generate the following corresponding defines:

#define DT_FLASH_AREA_DEF_NAME_DEV          "def-name"
#define DT_FLASH_AREA_DEF_NAME_OFFSET_0     0xSTART_OFFSET
#define DT_FLASH_AREA_DEF_NAME_SIZE_0       0xSIZE
#define DT_FLASH_AREA_DEF_NAME_OFFSET       DT_FLASH_AREA_DEF_NAME_OFFSET_0
#define DT_FLASH_AREA_DEF_NAME_SIZE         DT_FLASH_AREA_DEF_NAME_SIZE_0

As you can see, the label property is capitalized when forming the macro names. Other simple conversions to ensure it is a valid C identifier, such as converting “-” to “_”, are also performed. The offsets and sizes are available as well.

MCUboot Partitions

MCUboot is a secure bootloader for 32-bit microcontrollers.

Some Zephyr boards provide definitions for the flash partitions which are required to build MCUboot itself, as well as any applications which must be chain-loaded by MCUboot.

The device tree labels for these partitions are:

boot_partition

This is the partition where the bootloader is expected to be placed. MCUboot’s build system will attempt to link the MCUboot image into this partition.

slot0_partition

MCUboot loads the executable application image from this partition. Any application bootable by MCUboot must be linked to run from this partition.

slot1_partition

This is the partition which stores firmware upgrade images. Zephyr applications which receive firmware updates must ensure the upgrade images are placed in this partition (the Zephyr DFU subsystem can be used for this purpose). MCUboot checks for upgrade images in this partition, and can move them to slot0_partition for execution. The slot0_partition and slot1_partition must be the same size.

scratch_partition

This partition is used as temporary storage while swapping the contents of slot0_partition and slot1_partition.

Important

Upgrade images are only temporarily stored in slot1_partition. They must be linked to execute of out of slot0_partition.

See the MCUboot documentation for more details on these partitions.

File System Partitions

storage_partition

This is the area where e.g. LittleFS or NVS or FCB expects its partition.

ABNF grammar

This section contains an Augmented Backus-Naur Form grammar for the macros generated from a devicetree. See RFC 7405 (which extends RFC 5234) for a syntax specification.

; dt-macro is the top level nonterminal. It defines the possible
; macros generated by gen_defines.py.
;
; A dt-macro starts with uppercase "DT_" followed by either:
;
; - a property-macro, generated for a particular node
;   property
; - some other-macro, a catch-all for other types of macros,
;   which contain either global information about the tree or
;   are special cases
;
; This does *not* cover macros pulled out of DT via Kconfig,
; like CONFIG_SRAM_BASE_ADDRESS, etc.
dt-macro = %s"DT_" ( property-macro / other-macro )

; --------------------------------------------------------------------
; A property-macro is a sequence of:
;
; - node-id: a way to identify a node
; - property-id: a way to identify one of the node's properties
; - property-suf: an optional property-specific suffix
property-macro    = node-id "_" property-id ["_" property-suf]

; A node-id is a way to refer to a node within the devicetree.
; There are a few different flavors.

node-id           = compat-unit-id / inst-id / alias-id

compat-unit-id    = [bus-id-part "_"] compat-id-part "_" unit-addr-id-part
inst-id           = %s"INST_" 1*DIGIT "_" compat-id-part
alias-id          = %s"ALIAS_" alias-id-part

; Various components of a property-macro are just c-idents,
; which are made of uppercase letters, numbers, and underscores.
;
; This is a problem, because it makes it possible for different nodes
; or properties in a devicetree to generate the same macro twice
; with different values.

bus-id-part       = c-ident     ; ID for information about a node's bus
compat-id-part    = c-ident     ; ID for a node's compatible
unit-addr-id-part = c-ident     ; ID for a node's unit-address
alias-id-part     = c-ident     ; ID for an /aliases node property
property-id       = c-ident     ; ID for a node property -- this also
                                ; covers special cases like "reg",
                                ; "interrupts", and "cs-gpios" for now,
                                ; as they all collide with non-special
                                ; cases.
property-suf      = c-ident     ; a suffix for part of a property value,
                                ; like an array index or a phandle
                                ; specifier name converted to a c-ident

; --------------------------------------------------------------------
; An other-macro is a grab bag for everything that isn't a
; property-macro. It reuses some of the nonterminals (namely node-id
; and compat-id-part) defined above.
other-macro     = existence-flag / bus-macro / flash-macro / chosen-macro

existence-flag  = compat-existence-flag / inst-existence-flag
compat-flag     = %s"COMPAT_" c-ident
inst-flag       = %s"INST_" 1*DIGIT "_" c-ident

bus-macro       = bus-name-macro / on-bus-macro
bus-name-macro  = node-id %s"_BUS_NAME"
on-bus-macro    = compat-id-part %s"_BUS_" bus-name
bus-name        = c-ident       ; a bus name ("i2c") to a DT C
                                ; identifier ("I2C")

flash-macro     = %s"FLASH_AREA_" node-label-ident "_" flash-suf
flash-suf       = %s"ID" / %s"READ_ONLY" / (%s"OFFSET" ["_" 1*DIGIT]) /
                  (%s"SIZE" ["_" 1*DIGIT]) / %s"DEV"

; Macros generated from /chosen node properties.
chosen-macro    = chosen-flash /
                  %s"CODE_PARTITION_OFFSET" / %s"CODE_PARTITION_SIZE" /
                  %s"CCM_BASE_ADDRESS" / %s"CCM_SIZE" /
                  %s"DTCM_BASE_ADDRESS" / %s"DTCM_SIZE" /
                  %s"IPC_SHM_BASE_ADDRESS" / %s"IPC_SHM_SIZE"
; These come from the /chosen/zephyr,flash property.
chosen-flash    = %s"FLASH_BASE_ADDRESS" /
                  %s"FLASH_SIZE" /
                  %s"FLASH_ERASE_BLOCK_SIZE" /
                  %s"FLASH_WRITE_BLOCK_SIZE"

; --------------------------------------------------------------------
; Helper definitions.

; A c-ident is one or more:
; - uppercase letters (A-Z)
; - numbers (0-9)
; - underscores ("_")
;
; They are the result of converting names or combinations of names
; from devicetree to a valid component of a C identifier by
; uppercasing letters and converting non-alphanumeric characters to
; underscores.
c-ident = 1*( UPPER / DIGIT / "_" )

; a node's "label" property value, as an identifier
node-label-ident = c-ident

; "uppercase ASCII letter" turns out to be pretty annoying to specify
; in RFC-7405 syntax.
;
; This is just ASCII letters A (0x41) through Z (0x5A).
UPPER = %x41-5A