This is the 12th day of my participation in the August More Text Challenge. For details, see:August is more challenging

This article focuses on how to write device trees for new machines. It aims to describe the basic concepts of device trees and how they apply to describing machines. For a complete technical description of the device tree data format, see the ePAPR V1.1 specification. The ePAPR specification contains more detailed information about device trees than the description in this article, see this article for more advanced uses of device trees not covered in this article.

Basic data format

A device tree is a simple tree structure of nodes and attributes. Properties are key-value pairs, and nodes can contain properties and child nodes. The following code is the structure template of the device tree.

/ DTS-V1 /; / {node1 {a-string-property = "a string"; A-string-list-property = "first string", "second string"; // Hexadecimal is implied in byte arrays. A -byte-data-property = [01 23 34 56]; Child-node1 {// Boolean,first-child-property defined as true, not defined as false. First-child-boolean-property; second-child-cell-property = <1>; A - string - property = "Hello, world"; }; child-node2 { }; }; node2 { an-empty-property; a-cell-property = <1 2 3 4>; }; };Copy the code

This tree is obviously useless because it doesn’t describe anything, but it does show the structure of the nodes and properties. Both:

  • Single root node: “/”
  • Several child nodes: “node1” and “node2”
  • Children of node1: “child-node1” and “child-node2”
  • A bunch of attributes scattered around the tree.

Properties are simple key-value pairs where the value can be empty or contain any byte stream. Although data types are not encoded into data structures, there are some basic data representations that can be represented in device tree source files.

  • Text strings (null-terminated) are represented by double quotes:

string-property = “a string”;

  • ‘Cells’ is a 32-bit unsigned integer separated by Angle brackets:

cell-property = <0xbeef 123 0xabcd1234>;

  • Binary data is separated by square brackets:

binary-property = [0x01 0x23 0x45 0x67];

  • You can use commas to join together different representations of data:

mixed-property = “a string”, [0x01 0x23 0x45 0x67], <0x12345678>;

  • Commas are also used to create lists of strings:

string-list = “red fish”, “blue fish”;

The basic concept

The best way to understand how a device tree can be used is to build one from scratch. Let’s start with a simple machine and build a device tree to describe it.

The prototype

Consider the following imagined machine (loosely based on ARM Versatile), built by “Acme” and named “Coyote’s Revenge” :

  • Single-core 32-bit ARM processor
  • The processor local bus is connected to the memory mapped serial port, SPI bus controller, I2C controller, interrupt controller and external bus bridge
  • One 256MB SDRAM with base address 0
  • Two serial ports based on 0x101F1000 and 0x101F2000
  • 1 controller based on 0x101F3000GPIO
  • 1 SPI controller based on 0x10170000 with the following components:
  • MMC slots with SS pins are connected to GPIO# 1
  • External bus bridging appliances have the following equipment:
  • SMC SMC91111 Ethernet device connected to the external bus, based on 0x10100000
  • The I2C controller is based on 0x10160000 and has the following devices: Maxim DS1338 real time clock. Response to slave address 1101000 (0x58)
  • 64MB NOR flash based on 0x30000000

The initial structure

The first step is to set up the skeleton structure for the machine. This is the minimum structure required for an effective device tree. At this stage, you need the property Compatible to uniquely identify the machine.

/ DTS-V1 /; / {revenge = "acme, coyotes-revenge"; };Copy the code

Compatible specifies the name of the system. It contains a string of the form “.” It is important to specify the exact device and include the manufacturer name to avoid namespace conflicts. Since the operating system will use this compatible value to decide which schema to choose to start the machine running, it is important to put the right data into this property. In theory, compatibility is all the data an operating system needs to uniquely identify a machine. If all the machine details are hardcoded, then the operating system might only need to include the compatible property of “Acme, coyotes-revenge”.

CPU

The next step is to describe each CPU. Add a container node named “cpus” for each CPU that contains a child node. The processor platform described below is the dual-core Cortex A9.

/ DTS-V1 /; / {revenge = "acme, coyotes-revenge"; Cpus {CPU @0 {compatible = "ARM, cortex-a9"; }; CPU @1 {compatible = "arm, cortex-a9"; }; }; };Copy the code

The compatibility attribute in each CPU node is a string that specifies the exact CPU model in the form, just like the compatibility attribute at the top level. More attributes will be added to the CPU node later, but first we need to cover more basic concepts.

The name of the node

It’s worth taking a moment to talk about naming conventions. Each node must have a name [@] in the form. Is a simple ASCII string that can contain up to 31 characters. Typically, a node is named after the type of device it represents. That is, the node of the 3COM Ethernet adapter will use the name Ethernet instead of 3COM509.

If the node describes a device with an address, the unit address information needs to be included. Typically, the cell address is the primary address used to access the device and is represented in the node’s REg property. We will cover the reg property later in this document.

Peer nodes must be uniquely named. In this case, the node names are name and unit-address. The name can be systematic, but as long as the addresses are different (for example, serial@101f1000 and serial@101f2000), this is fine.

For complete details on node naming, see Section 2.2.1 of the ePAPR specification.

equipment

Each device in the system is represented by a device tree node. The next step is to populate the tree with nodes for each device. For now, the new node will remain empty until we can discuss how to handle address ranges and IRQs.

/ DTS-V1 /;
/ {
    compatible =“acme,coyotes-revenge”;
    cpus {
        cpu @ 0 {
             compatible =“arm,cortex-a9”;
        };
        cpu @ 1 {
             compatible =“arm,cortex-a9”;
        };
    };
    
    serial@101F0000 {
        compatible =“arm,pl011”;
    };
    
    serial@101F2000 {
        compatible =“arm,pl011”;
    };
    
    gpio@101F3000 {
        compatible =“arm,pl061”;
    };
    
    interrupt-controller@ 10140000 {
        compatible =“arm,pl190”;
    };
    
    spi@10115000 {
        compatible = "arm,pl022";
    };
    
    external-bus {
        ethernet@0,0 {
            compatible =“smc,smc91c111”;
        };
        i2c@1,0 {
             compatible =“acme,a1234-i2c-bus”;
            rtc @ 58 {
                compatible =“maxim,ds1338”;
            };
        };
        flash @ 2,0 {
             compatible =“samsung,k8f1315ebm”,“cfi-flash”;
        };
    };
};
Copy the code

In this tree, a node has been added for each device in the system, and the hierarchy reflects how devices are connected to the system. That is, the device on the external bus is a child of the external bus node, and the I2C device is a child of the I2C bus controller node. In general, a hierarchy is a view of the system from the CPU’s point of view. The tree is invalid at this time. It lacks information about the connections between devices. This data will be added later. Some things to watch out for in this tree:

  • Each device node has a compatible property.
  • Flash nodes have two strings in their compatibility properties. Read on in the next section to find out why.
  • As mentioned earlier, the node name reflects the type of device, not a specific model. See Section 2.2.2 of the ePAPR specification for a list of defined generic node names that should be used whenever possible.

Understand the compatible Property

Each node in the tree representing the device needs to have the compatible attribute. Compatible is the key that the operating system uses to determine binding devices and device drivers. Compatible is a list of strings. The first string in the list specifies the exact device “,” that the node represents in the form. The remaining strings represent other devices that the device is compatible with.

For example, the Freescale MPC8349 System-on-chip (SoC) has a serial device that implements the National Semiconductor NS16550 register interface. Therefore, the compatible property of the MPC8349 serial device should be: Compatible = “FSL, MPC8349-UART “,” NS16550 “. In this case, the FSL, MPC8349-UART specifies the exact device and uses the NS16550 to declare that it is regime-level compatible with the National Semiconductor 16550 UART.

Note: NS16550 has no manufacturer prefix due to historical reasons. All new compatibility values should use the manufacturer prefix. This allows existing device drivers to be bound to newer devices while still uniquely identifying the exact hardware.

Warning: Do not use wildcard compatibility values such as “FSL, MPC83xx-UART” or similar values. Semiconductor vendors will always change, and it will be too late to change it, breaking your wildcard assumptions. Instead, select a specific semiconductor implementation and make all subsequent chips compatible with it.

How does addressing work

Addressable devices encode address information into the device tree using the following attributes:

  • reg
  • #address-cells
  • #size-cells

Reg = <address1 length1 [address2 length2][address3 length3]… >. Each tuple represents the address range used by the device. Each address value is a list of one or more 32-bit integers called cells. Similarly, the length value can be a list of cells or it can be empty.

Because the address and length fields are variable-size variables, the #address-cells and #size-cells genera in the parent node are used to indicate how many cells are in each field. Or, to put it another way, proper interpretation of the reg attribute requires the # address-cells and #size-cells values of the parent node. To see how this all works, start with the CPU and add addressing properties to the sample device tree.

CPU addressing

The CPU node information is the simplest case when discussing addressing. Each CPU is assigned a unique ID, and there is no size associated with the CPU ID.

cpus {
    #address-cells = <1>;
    #size-cells = <0>;
    cpu @ 0 {
         compatible =“arm,cortex-a9”; 
         reg = <0>;
    };
    cpu @ 1 {
         compatible =“arm,cortex-a9”; 
         reg = <1>;
    };
};
Copy the code

In the cpus node, #address-cells is set to 1 and #size-cells is set to 0. This means that the sub-reg value is a single uint32 and there is no address for the size field. In this case, two cpus are assigned addresses 0 and 1. # size-Cells is 0 for CPU nodes because each CPU is assigned only one address.

You’ll also notice that the reg value matches the value in the node name. By convention, if a node has a reg attribute, the node name must contain unit-address, which is the first address value in the reg attribute.

Memory mapped device

Instead of a single address value in a CPU node, a memory mapped device is assigned a series of address Spaces to which all operations of the memory device are mapped. # size-Cells is used to indicate the size of the length field in each subreg tuple. In the following example, each address value is 1 cell (32-bit), and each length value is also 1 cell, as is typical on 32-bit systems. For #address-cells and # size-Cells, the 64-bit machine can use the value 2 to get 64-bit addressing in the device tree.

/ DTS-V1 /; / { #address-cells = <1>; #size-cells = <1>; . Serial @101f0000 {compatible = "arm, pl011"; reg = <0x101f0000 0x1000>; }; Serial @101f2000 {compatible = "arm, pl011"; reg = <0x101f2000 0x1000>; }; Gpio @101f3000 {compatible = "arm, pl061"; reg = <0x101f3000 0x1000 0x101f4000 0x0010>; }; Interrupt-controller @1014000 {compatible = "arm, pl190"; reg = <0x10140000 0x1000>; }; spi@10115000 { compatible = "arm,pl022"; reg = <0x10115000 0x1000 >; }; . };Copy the code

Each device is assigned a base address and the size of the region is assigned to it. The GPIO device address in this example is assigned two address ranges; 0x101f3000 … 0 x101f3fff and 0 x101f4000.. 0 x101f400f. Some devices exist on buses with different addressing schemes. For example, separate chip selection lines can be used to connect devices to an external bus. Since each parent node defines the addressing domain for its children, address mappings can be selected to best describe the system. The following code shows the address assignment for devices connected to the external bus, with the chip selection number encoded into the address.

external-bus{ #address-cells = <2>; #size-cells = <1>; Ethernet @ 0,0 {compatible = "SMC, smc91c111"; reg = <0 0 0x1000>; }; i2C@1 0 {compatible = "acme, a1234-i2C-bus"; reg = <1 0 0x1000>; rt@58 {compatible = "maxim, ds1338"; }; }; Flash @ 2 0 {compatible = "Samsung, k8f1315ebm", "cF-flash"; reg = <2 0 0x4000000>; }; };Copy the code

External-bus uses two cells to define address values: one for the chip selection of the chip, and one for the base address offset relative to the selected chip. The length field is kept as a single cell because only the offset part of the address needs to have a range. Thus, in this example, each REg entry contains three cells; The chip selection, offset and length of the chip.

Because the address domain is contained within the node and its children, the parent node is free to define any addressing scheme that makes sense to the bus. Nodes other than the immediate parent and child nodes generally do not have to care about the local addressing domain and must map addresses from one domain to another.

Non-memory mapped device

Memory not mapped on the processor bus by other devices. They can have address ranges, but the CPU cannot access them directly. Conversely, the parent device’s driver performs indirect access on behalf of the CPU.

In the case of i2C devices, each device is assigned an address, but there is no length or range associated with it. This looks about the same as CPU address allocation.

i2C@1 0 {compatible = "acme, a1234-i2C-bus"; #address-cells = <1>; #size-cells = <0>; reg = <1 0 0x1000>; rt@58 {compatible = "maxim, ds1338"; reg = <58>; }; };Copy the code

Range (Address translation)

We’ve already discussed how to assign addresses to devices, but at this point these addresses are only local to the device node. It does not yet describe how to map from these addresses to addresses that the CPU can use.

The root node always describes the ADDRESS space view of the CPU. The children of the root node are already using the CPU’s address domain, so no explicit mapping is required. For example, the serial @101f0000 device is directly assigned address 0x101f0000.

Nodes that are not direct children of the root do not use the CPU’s address domain. To get a memory-mapped address, the device tree must specify how to translate the address from one domain to another. The ranges property is used for this purpose.

Here is a sample device tree with the Ranges property added.

/ DTS-V1 /; / {revenge = "acme, coyotes-revenge"; #address-cells = <1>; #size-cells = <1>; . external-bus{ #address-cells = <2> #size-cells = <1>; Ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet 10 0x10160000 0x10000 // Chipselect 2, i2c controller 2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash Ethernet @ 0,0 {compatible = "SMC, smc91c111"; reg = <0 0 0x1000>; }; i2C@1 0 {compatible = "acme, a1234-i2C-bus"; #size-cells = <0>; reg = <1 0 0x1000>; rt@58 {compatible = "maxim, ds1338"; reg = <58>; }; }; Flash @ 2 0 {compatible = "Samsung, k8f1315ebm", "cF-flash"; reg = <2 0 0x4000000>; }; }; };Copy the code

Ranges is a list of address translations. Each entry in the range table is a tuple containing the child address, the parent address, and the size of the region in the child address space. The size of each field depends on the child’s **#address-cells ** value, the parent’s #address-cells value, and the child’s **#size-cells ** value. For the external bus in our example, the child address is 2 units, the parent address is 1 cell, and the size is also 1 cell. The mapping of ranges is as follows:

  • The offset of the chip selected as 0 is 0, which maps to the address range 0x10100000.. 0x1010ffff
  • The offset of the chip selected as 1 is 0, which maps to the address range 0x10160000.. 0x1016ffff
  • The offset of the chip selected as 2 is 0, which maps to the address range 0x30000000.. 0x30ffffff

Alternatively, if the parent and child address Spaces are the same, the node can add an empty ranges property instead. The existence of the empty range attribute means that addresses in the child address space map 1:1 to the parent address space.

You might ask why address translation works perfectly for 1:1 mappings. Some buses, such as PCI, have completely different address Spaces whose details need to be exposed to the operating system. It has a device with a DMA engine that needs to know the real address on the bus. Sometimes it is necessary to group devices together because they all share the same software-programmable physical address map. Whether 1:1 mapping should be used depends largely on the information required by the operating system and the hardware design.

You should also notice that there is no ranges property in the ranges i2c @ 1,0 node. The reason for this is that, unlike the external bus, devices on the I2C bus are not memory-mapped in the CPU’s address domain. Instead, the CPU accesses the rtc-58 device indirectly through the i2c @ 1,0 device. The lack of ranges property means that devices cannot be accessed directly by any device other than the parent.

How interrupts work

Unlike address range translation, which follows the natural structure of a tree, interrupt signals can originate and end at any device in the machine. Unlike device addressing, which is expressed naturally in a device tree, the interrupt signal is represented as a link between nodes independent of the tree. Four properties are used to describe a broken connection:

  • Interrupt-controller – An empty property that declares the node to be the device that receives interrupt signals, the interrupt controller.

  • This is a property of the interrupt controller node. It indicates how many cells are in the interrupt specifier for the interrupt controller (similar to #address-cells and # size-Cells).

  • Interrupt-parent A property of the device node that contains the phandle(Pointer Handle) of the interrupt controller connected to it. A node that does not have the interrupt-parent property can also inherit it from its parent.

  • A property of the Interrupts device node that contains a list of Interrupt Specifiers, one for each interrupt output signal on the device.

  • Interrupt-names Specifies the name of “interrupts” on the device node. The driver can use this command to query the interrupts number.

An interrupt specifier is one or more cells of data (as defined by #interrupt-cells) that specifies which interrupt input is associated with the device. Most devices have only one interrupt output, as shown in the following example, but there can be multiple interrupt outputs on the device. The meaning of an Interrupt Specifier depends entirely on the interrupt controller device to which it is bound. Each interrupt controller can determine the number of cells (the number of cells) needed to uniquely define interrupt input.

The following code adds a break connection to our Coyote Revenge sample machine:

/ DTS-V1 /; / {revenge = "acme, coyotes-revenge"; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&intc>; //phandle = <&intc> cpus { #address-cells = <1>; #size-cells = <0>; CPU @0 {compatible = "arm, cortex-a9"; reg = <0>; }; CPU @1 {compatible = "arm, cortex-a9"; reg = <1>; }; }; Serial @101f0000 {compatible = "arm, pl011"; reg = <0x101f0000 0x1000>; interrupts= <1 0>; }; Serial @101f2000 {compatible = "arm, pl011"; reg = <0x101f2000 0x1000>; interrupts = <2 0>; }; Gpio0@101f3000 {compatible = "arm, pl061"; reg = <0x101f3000 0x1000 0x101f4000 0x0010>; interrupts = <3 0>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; Gpio1@101f5000 {compatible = "arm, pl061"; reg = <0x101f3000 0x1000 0x101f4000 0x0010>; interrupts = <4 0>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; Intc: interrupt-controller @1014000 {compatible = "arm, pl190"; reg = <0x10140000 0x1000>; interrupt-controller; #interrupt-cells = <2>; }; SP@10115000 {compatible = "arm, pl022"; reg = <0x10115000 0x1000>; interrupts = <4 0>; }; external-bus{ #address-cells = <2> #size-cells = <1>; Ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet 10 0x10160000 0x10000 // Chipselect 2, i2c controller 2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash Ethernet @ 0,0 {compatible = "SMC, smc91c111"; reg = <0 0 0x1000>; interrupts = <5 2>; }; i2C@1 0 {compatible = "acme, a1234-i2C-bus"; #address-cells = <1>; #size-cells = <0>; reg = <1 0 0x1000>; interrupts = <6 2>; rtc@58 {compatible = "maxim, ds1338"; reg = <58>; interrupts = <7 3>; }; }; Flash @ 2 0 {compatible = "Samsung, k8f1315ebm", "cF-flash"; reg = <2 0 0x4000000>; }; }; };Copy the code

A few things to note:

  • The machine has only one primary interrupt controller, interrupt controller @10140000, and there can be multiple secondary interrupt controllers, such as GPIo0@101F3000, GPIo1@101F5000, etc., which form a cascading relationship with the primary interrupt control.

  • The tag ‘intc: ‘has been added to the interrupt controller node and is used to assign the phandle to the interrupt-parent property in the root node. This interrupt-parent is the default value for the system because all child nodes inherit it unless it is explicitly overridden.

  • Each component uses the interrupt property to specify a different interrupt input line.

  • #interrupt-cells is 2, so each interrupt specifier has 2 cells. This example uses the first cell to encode the interrupt number used by the device, and the second cell to encode the interrupt attribute flags, such as high or low level triggered, or edge triggered, or level sensitive. For any given interrupt controller, refer to the controller’s binding document to learn how to code the Interrupt Specifier.

Device specific data

In addition to public properties, you can add arbitrary properties and child nodes to a node. As long as you follow a few rules, you can add any data your operating system needs.

  • First, new device-specific attribute names should use manufacturer prefixes so that they don’t conflict with existing standard attribute names.

  • Second, the meaning of the properties and child nodes must be recorded in the binding so that the device driver author knows how to interpret the data. The binding document describes the meaning of a particular compatibility value, which attributes it should have, which child nodes it might have, and which devices it represents. Each uniquely compatible value should have its own binding (or declare compatibility with another compatible value). Bindings for new devices are documented in this Wiki. See the home page for a description of the document format and review process

  • Third, publish the new binding for review on the [email protected] mailing list. Checking for new bindings catches many common errors that can cause problems in the future.

Special nodes

Aliases node

Specific nodes are usually referenced by the full path /external-bus/ethernet@0,0, but when the user really wants to know “Which device is eth0?” “, which can be troublesome. The aliases node can be used to assign short aliases to full device paths. Such as:

aliases{
    ethernet0 = &eth0;
    serial0 = &serial0;
};
Copy the code

The operating system encourages the use of aliases when assigning identifiers to devices.

You’ll notice the new syntax used here. property = &label; This syntax specifies the full node path referenced by the tag as a string attribute. This is different from phandle = < &label >; phandle = < &label >; phandle = < &label >;

Feature node

The chosen node does not represent the real device, but is used to specify where data, such as boot parameters, is passed between the bootloader and the operating system. Data in the chosen node does not represent hardware. Typically, the chosen node is left empty in the.dts source file and populated at boot time. In our example system, the firmware might add the following to the selected node:

Chosen {bootargs = "root = / dev/NFS rw nfsroot = 192.168.1.1 console = ttyS0,115200"; };Copy the code

Advanced topics

Advanced sampler

Now that we’ve defined the basics, let’s add some hardware to the sample machine to discuss some more complex use cases.

The advanced example computer adds a PCI main bridge with control registers mapped to 0x10180000, while BARs are programmed to start at address 0x80000000.

Given what we already know about the device tree, we can describe the PCI main bridge by adding the following nodes.

pci@10180000 {compatible = "arm, versatile- PCI-hostbridge", "pci"; reg = <0x10180000 0x1000>; interrupts = <8 0>; };Copy the code

PCI bridge,

This section describes the Host/PCI Bridge node.

Note that this section assumes some basic knowledge of PCI. This is not a tutorial on PCI, if you need more in-depth information, please read it. You can also refer to ePAPR V1.1 or PCI Bus Binding to Open Firmware. A complete working example of the Freescale MPC5200 can be found here.

PCI Bus Number

Each PCI bus segment is uniquely numbered, and the bus number is disclosed in the PCI node using bus-ranges, which contain two cells. The first cell gives the bus number assigned to the node, and the second cell gives the maximum bus number of any slave PCI bus.

The prototype has a single PCI bus, so both units are 0.

pci @ 0x10180000 {
    compatible = “arm,versatile-pci-hostbridge”,“pci”;
    reg = <0x10180000 0x1000>;
    interrupts = <8 0>; 
    bus-ranges = <0 0>;
};
Copy the code

PCI address translation

Similar to the local bus described earlier, the PCI address space is completely separated from the CPU address space, so address translation is required to translate from the PCI address to the CPU address. As usual, this is done using the range, #address-cells and #size-cells properties.

pci@0x10180000 {compatible = "arm, versatile- PCI-hostbridge", "pci"; reg = <0x10180000 0x1000>; interrupts = <8 0>; bus-ranges = <0 0>; #address-cells = <3> #size-cells = <2>; ranges = < 0x42000000 0 0x80000000 0x80000000 0 0x20000000 0x02000000 0 0xa0000000 0xa0000000 0 0x10000000 0x01000000 0 0x00000000 0xb0000000 0 0x01000000 >; };Copy the code

As you can see, the child address (PCI address) uses 3 cells, and the PCI range is encoded as 2 cells. The first question might be why we need three 32-bit Cells to specify PCI addresses. The three cells are expressed as phys. Hi, phys. Mid and phys.

  • phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr
  • phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
  • phys.low cell: llllllll llllllll llllllll llllllll

PCI addresses are 64 bits wide and encoded as phys.mid and phys.low. However, the really interesting thing is in Phys. High, which is a domain with:

  • N: relocatable area flag (not available here)
  • P: Prefetchable area flag
  • T: alias address flag (not working here)
  • Ss: Space code
  • 00: configuration space
  • 01: indicates the I/O space
  • 10:32-bit storage space
  • 11:64 bit storage space
  • BBBBBBBB: PCI bus number. PCI can be layered. So we might have PCI/PCI Bridges, which will define the subbus.
  • DDDDD: Device number, usually associated with an IDSEL signal connection.
  • FFF: indicates the function number. For multi-function PCI devices.
  • RRRRRRRR: Registration number; This parameter is used to configure the period.

For PCI address translation purposes, the important fields are P and SS. The values of p and ss in phys.hi determine which PCI address space is accessed. So, looking at our scope properties, we have three areas:

  • A 32-bit prefetching storage area, starting with the 512 MB PCI address 0x80000000, will be mapped to the address 0x80000000 on the host CPU.
  • A 32-bit non-prefetched memory region, starting at 256 MB PCI address 0xa0000000, will be mapped to address 0xa0000000 on the host CPU.
  • An I/O area, starting at the 16 MB PCI address 0x00000000, will be mapped to the address 0xB0000000 on the host CPU.

To make Wrench work, the existence of the phys. Hi bit field means that the operating system needs to know that this node represents the PCI bridge so that it can ignore irrelevant fields for translation. The operating system looks for the string “PCI” in the PCI bus node to determine if additional fields need to be masked.

PCI DMA address translation

The above range defines how the CPU looks at PCI memory and helps the CPU set the correct memory window and write the correct parameters to the various PCI device registers. This is sometimes called outbound memory.

A special case of address translation concerns how the PCI host hardware views the system’s core memory. This occurs when the PCI master controller acts as the master controller and has independent access to the system’s core memory. Since this is usually different from the VIEW of the CPU (due to the way the memory lines are connected), it may be necessary to program it into the PCI master controller at initialization time. This is considered DMA because the PCI bus performs direct memory access independently, so the mappings are named DMA-ranges. This type of memory mapping is sometimes called inbound Memory and is not part of the PCI device tree specification.

In some cases, ROM (BIOS) or similar devices will set these registers at startup, but in other cases, the PCI controller is not initialized at all, and these conversions need to be set from the device tree. The PCI host driver then typically parses the DMA-Ranges property and sets some registers in the host controller accordingly.

Extend the above example:

pci@0x10180000 {compatible = "arm, versatile- PCI-hostbridge", "pci"; reg = <0x10180000 0x1000>; interrupts = <8 0>; bus-ranges = <0 0>; #address-cells = <3> #size-cells = <2>; ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000 0x02000000 0 0xa0000000 0xa0000000 0 0x10000000 0x01000000 0 0x00000000 0xb0000000 0 0x01000000 dma-ranges = <0x02000000 0 0x00000000 0x80000000 0 0x20000000 >; };Copy the code

This DMA-Ranges entry indicates that from the perspective of the PCI host controller, 512MB at PCI address 0x00000000 will appear at address 0x80000000 in primary core memory. As you can see, we just set the SS address type to 0x02, indicating that this is some 32-bit memory.

Advanced interrupt mapping

Now we come to the most interesting part, PCI interrupt mapping. PCI devices can trigger interrupts using #INTA, # INTB, # INTC and #INTD. Single-function devices are obligated to use #INTA for interruption. If a multi-function device uses a single interrupt pin, #INTA must be used, if two interrupt pins are used, #INB, and so on. Because of these rules, # INTA usually has more functionality than #INTB, #INTC, and #INTD. To assign interrupt #INTD to interrupt #INTA four interrupts to four IRQ interrupt lines, each PCI slot or device is usually connected to a different input on the interrupt controller in a rotating fashion to avoid all #INTA clients connecting to the same input interrupt line. This process is called swizzling interruption. Therefore, the device tree requires a way to map each PCI interrupt signal to the interrupt controller input. The #interrupt-cells, interrupt-map, and ** interrupt-mapmask** properties are used to describe the mapping of the interrupt.

In fact, the interrupt maps described here are not limited to the PCI bus; any node can specify a complex interrupt map, but the PCI case is by far the most common.

pci@0x10180000 {compatible = "arm, versatile- PCI-hostbridge", "pci"; reg = <0x10180000 0x1000>; interrupts = <8 0>; bus-ranges = <0 0>; #address-cells = <3> #size-cells = <2>; ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000 0x02000000 0 0xa0000000 0xa0000000 0 0x10000000 0x01000000 0 0x00000000 0xb0000000 0 0x01000000>; #interrupt-cells = <1>; interrupt-map-mask = <0xf800 0 0 7>; interrupt-map = < 0xc000 0 0 1 &intc 9 3 // 1st slot 0xc000 0 0 2 &intc 10 3 0xc000 0 0 3 &intc 11 3 0xc000 0 0 4 &intc 12 3 0xc800 0 0 1 &intc 10 3 // 2nd slot 0xc800 0 0 2 &intc 11 3 0xc800 0 0 3 &intc 12 3 0xc800 0 0 4 &intc 9 3 >; };Copy the code

First, you’ll notice that the PCI interrupt number uses only one cell, unlike the system interrupt controller that uses two cells; One for the IRQ interrupt number and one for the interrupt attribute flag. PCI requires only one cell for interrupts because PCI interrupts are specified to be always level low sensitive.

In our sample board, we have two PCI slots with four interrupt lines each, so we have to map eight interrupt lines to the interrupt controller. This is done using the interrupt-map property. The exact process of interrupt mapping is described below.

Since the interrupt number (#INTA, etc.) is insufficient to distinguish between multiple PCI devices on a single PCI bus, we must also indicate which PCI device triggered the interrupt line. Fortunately, each PCI device has a unique device number that we can use. To distinguish interrupts for several PCI devices, we need a tuple consisting of the PCI device number and the PCI interrupt number. More generally, we construct a cell interrupt specifier that has four cells:

  • Three #address-cells are composed of phys. Hi, phys. Mid, phys
  • A # interrupt-cell (#INTA, # INTB, # INTC, # INTD).

Because we only need the device number portion of the PCI address, the interrupt-map-mask attribute comes into play. Interrupt-mapmask is also a 4-tuple like an element interrupt specifier. The 1 in the mask indicates which part of the cell interrupt specifier should be considered. In our example, we can see that we only need the device number part of phys. Hi, we need 3 bits to distinguish the four interrupt lines (counting PCI interrupt lines starts at 1, not 0!). .

Now we can construct the interrupt-map property. This property is a table in which each entry contains a child (PCI bus) cell interrupt specifier, a parent handle (the interrupt controller responsible for providing interrupts), and a parent cell interrupt specifier. So, in the first line, we can read that PCI interrupt #INTA is mapped to IRQ9 and that our interrupt controller is low.

The only missing piece is the odd number in the PCI bus unit interrupt specifier. The important part of the cell interrupt specifier is the device number from the phys. Hi bit field. The device number is board specific and depends on how each PCI master controller activates the IDSEL pins on each device. In this example, PCI slot 1 is assigned device ID 24 (0x18), and PCI slot 2 is assigned device ID25 (0x19). The phys. Hi value for each time slot is determined by shifting the device number up by 11 bits to the DDDDD portion of the in-place domain, as follows:

  • The phys.hi of slot 1 is 0xC000, and
  • Phys. Hi for slot 2 is 0xC800.

To put everything together to show the interrupt mapping properties:

  • The #INTA of slot 1 is IRQ9 and is level-low sensitive on the primary interrupt controller
  • The #INTB of slot 1 is IRQ10 and is level-low sensitive on the primary interrupt controller
  • The #INTC of slot 1 is IRQ11 and is level-low sensitive on the primary interrupt controller
  • The #INTD of slot 1 is IRQ12 and is level-low sensitive on the primary interrupt controller

and

  • The #INTA of slot 2 is IRQ10 and is level-low sensitive on the primary interrupt controller
  • The #INTB of slot 2 is IRQ11 and is level-low sensitive on the primary interrupt controller
  • The #INTC of slot 2 is IRQ12 and is level-low sensitive on the primary interrupt controller
  • The #INTD of slot 2 is IRQ9 and is level-low sensitive on the primary interrupt controller

The ** Interrupts = <8 0>**; Property describes interrupts that may be triggered by the HOST/PCI bridge controller itself. Do not confuse these interrupts with interrupts that PCI devices may trigger (using INTA, INTB,…). .

One last thing to note. As with the interrupt parent property, the presence of an interrupt mapping property on a node changes the default interrupt controller for all children and grandchildren. In this PCI example, this means that the PCI main bridge becomes the default interrupt controller. If a device connected via the PCI bus is directly connected to another interrupt controller, you also need to specify its own interrupt parent property.