├── .github └── workflows │ └── test-compile.yml ├── .gitignore ├── API.md ├── LICENSE ├── Makefile ├── README.md ├── smoldtb.c ├── smoldtb.h ├── test-files ├── qemu-riscv64-virt-8.dtb └── qemu-riscv64-virt-8.dts └── test.c /.github/workflows/test-compile.yml: -------------------------------------------------------------------------------- 1 | name: This should build 2 | on: [ push, pull_request ] 3 | 4 | jobs: 5 | build-gcc: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Install GCC 9 | run: sudo apt install gcc-multilib 10 | - uses: actions/checkout@v4 11 | - name: Compile 12 | run: make clean all 13 | 14 | build-gcc-32: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Install GCC 18 | run: sudo apt install gcc-multilib 19 | - uses: actions/checkout@v4 20 | - name: Compile 21 | run: CC="gcc -m32" make clean all 22 | 23 | build-clang: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Download clang 27 | run: sudo apt install clang lld 28 | - uses: actions/checkout@v4 29 | - name: Compile 30 | run: CC="clang -fuse-ld=lld" LD="ld.lld" make clean all 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.elf 3 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Smol DTB API 2 | 3 | The API is split into three main groups of functions: 4 | - `dtb_find_*` functions are for quickly locating a node or property by some characteristic. 5 | - `dtb_get_*` are for manually traversing the tree. 6 | - `dtb_read_*` functions can be used to extra data from properties. 7 | 8 | While the device tree specification (v0.4 at the time of writing) uses big-endian integers, the API for smoldtb uses the native endianness of the machine it was compiled for. It will handle the conversion to big-endian internally (if necessary). 9 | 10 | ## Find functions 11 | 12 | `dtb_node* dtb_find_compatible(dtb_node* node, const char* str)`: Linearly searches the tree for any nodes with a 'compatible' property that matches this string. Since this property can contain multiple strings, all of them are checked for a given input. The first argument is where to start the search and can be `NULL` to begin at the root of the tree. If a compatible node has been found previously, that node can be used as the starting location for the search and this function will return the *next node* that matches. In the event no nodes have this compatible string, `NULL` is returned. 13 | 14 | `dtb_node* dtb_find_phandle(unsigned handle)`: Looks up which node is associated with a given phandle and returns it. If the phandle is unused, `NULL` is returned. 15 | 16 | `dtb_node* dtb_find(const char* path)`: Attempts to find a node based on the path provided. The path is a series of unit names (the trailing address part can be exempt) separated by a forward slash `/`, similar to a unix filepath. Returns `NULL` if the node couldn't be located. Properties cannot be looked up this way, you must look up the node and then use `dtb_get_prop()`. 17 | 18 | `dtb_node* dtb_find_child(dtb_node* node, const char* name)`: Attempts to find a child of a node with a matching unit name (unit address is exempt from the string comparison). Returns `NULL` if no matching child is present. 19 | 20 | `dtb_prop* dtb_find_prop(dtb_node* node, const char* name)`: Returns a property of this node with the matching name, or `NULL` if a property isn't found. 21 | 22 | ## Get functions 23 | 24 | `dtb_node* dtb_get_sibling(dtb_node* node)`: Returns this node's sibling (the next child of this node's parent). Note that a node will always have the same sibling. To traverse the tree horizontally this function should be called on the node returned by an earlier `dtb_get_sibling()` call. If a node has no sibling, `NULL` is returned. 25 | 26 | `dtb_node* dtb_get_child(dtb_node* node)`: Returns the first child of this node. Subsequent calls to this function will always return the same node, `dtb_get_sibling()` should be called on the child node to get further child nodes. Returns `NULL` if node has no children. 27 | 28 | `dtb_node* dtb_get_parent(dtb_node* node)`: Returns this nodes parent node, or `NULL` if node is at the root level. 29 | 30 | `dtb_prop* dtb_get_prop(dtb_node* node, size_t index)`: Returns the property with this index. While properties aren't stored this way, it can be useful for exploring a node's properties. If an index is beyond the number of properties a node has, `NULL` is returned. 31 | 32 | `void dtb_stat_node(dtb_node* node, dtb_node_stat* stat)`: Requires `stat` to be a pointer to a pre-allocated struct, and will provide info about `node` in `stat` such as the node's name, number of children and number of properties. 33 | 34 | ## Read Functions 35 | 36 | `const char* dtb_read_string(dtb_prop* prop, size_t index)`: String-based properties can contain multiple null-terminated strings, `index` selects which string you want to read. If the index is out of bounds `NULL` is returned, otherwise a pointer to the ASCII-encoded text (as per the Device Tree v0.4 spec) is returned. 37 | 38 | `size_t dtb_read_prop_values(dtb_prop* prop, size_t cell_count size_t* vals)`: The `cell_count` argument determines how many cells comprise a single value. This value is specific to the property you're trying to read and you should consult the spec about what to set this to. This function returns the number of values this property would contain for the given `cell_count`. If `vals` is non-null, this function will treat it as an array to write the values into. To use this function it's recommended to call it once with `vals = NULL` to determine how many values are present, then allocate space for the values, and then call the function again with `vals = your_buffer`. 39 | 40 | `size_t dtb_read_prop_pairs(dtb_prop* prop, dtb_pair layout, dtb_pair* vals)`: This function is similar to `dtb_read_values()` except it reads pairs of values. The `layout` argument replaces the `cell_count` argument of the previous function: `layout.a` is the number of cells for the first element of the value and `layout.b` is the cell count for second element. As above, if `vals` is non-null it is treated as an array to read the pairs of values into. 41 | 42 | `size_t dtb_read_prop_triplets(dtb_prop* prop, dtb_triplet layout, dtb_triplets* vals)`: This function is similar to `dtb_read_pairs()` except that it operates on three-element values. 43 | 44 | `size_t dtb_read_prop_quads(dtb_prop* prop, dtb_quad layout, dtb_quad* vals)`: Again this function is similar to the above ones, except it operates on 4-element values. 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Dean T. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | C_SRCS = test.c smoldtb.c 2 | C_FLAGS = -O0 -Wall -Wextra -g -DSMOLDTB_STATIC_BUFFER_SIZE=0x4000 -DSMOLDTB_ENABLE_WRITE_API 3 | TARGET = readfdt 4 | 5 | all: $(C_SRCS) 6 | $(CC) $(C_SRCS) $(C_FLAGS) -o $(TARGET) 7 | 8 | run: all 9 | ./$(TARGET) 10 | 11 | debug: all 12 | gdb ./$(TARGET) 13 | 14 | clean: 15 | -rm $(TARGET) 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Tiny Flattened Device Tree Parser 2 | Available [Codeberg](https://codeberg.org/r4/smoldtb) and [Github](https://github.com/deanoburrito/smoldtb). 3 | 4 | This project is a standalone C port of the device tree parser from my kernel [Northport](https://github.com/deanoburrito/northport). The original version has few limitations which are addressed here. 5 | 6 | This version does make use of a single larger buffer for storing node data. This can either be allocated by a user provided function, or from a statically allocated buffer inside the program executable. The second method is suitable for environments where dynamic memory allocation might not be available, but it does limit the maximum number of nodes the parser can process. 7 | 8 | ## Usage 9 | Copy `smoldtb.c` and `smoldtb.h` into your project and you're good to go. No additional compiler flags are required. 10 | 11 | The parser must be initialized before using it by calling `dtb_init()`. This function is the only time memory allocation/deallocation happens. You can call this multiple times, and it will re-initialize itself based on the new data device blob. Re-initializing the parser will destroy the previous parse data, so it effectively operates like a singleton. 12 | 13 | The parser assumes that the DTB is always available at it's original address (the one given to `dtb_init()`) at runtime. If the DTB is moved in memory you can re-initialize the parser with the new address. 14 | The arguments for `dtb_init(uintptr_t start, dtb_ops ops)` are as follows: 15 | 16 | - `uintptr_t start`: The address where the beginning of the flattened device tree can be found. This should be where the FDT header begins and contain the magic number. 17 | - `dtb_ops`: a struct containing a number of function pointers to the library may need to call at runtime. Best practice is to populate all of these. 18 | 19 | The `dtb_ops` struct has the following fields: 20 | - `void* (*malloc)(size_t length)`: This function is called to allocate the buffer used internally by the parser. This is called once per call to `dtb_init()`. It should return a pointer to a region of memory free for use by the library that is at least `length` bytes in length. This function (and `ops.free()`) are both unused if using a statically allocated buffer. 21 | - `void* (*free)(void* ptr, size_t length)`: Frees a buffer previously allocated by the above function. Only called when reinitializing the parser. 22 | - `void (*on_error)(const char* why)`: If the library encounters a fatal error and cannot continue it will call this function with a string describing what happened and why. 23 | 24 | ### Use Without Malloc/Free 25 | Define `SMOLDTB_STATIC_BUFFER_SIZE=your_buffer_size` when compiling `smoldtb.c` and the parser will only allocate from a single buffer, typically stored in the program's `.bss` section. When compiled with this option `ops.free()` and `ops.malloc()` are never called. 26 | 27 | In the event of parsing a DTB that contains too many nodes and/or properties for the static buffer, the parser will exit during `dtb_init()` (with a call to `ops.on_error()` if populated). 28 | 29 | ### Concurrency 30 | Not an advertised feature, but all API functions (except `dtb_init()`) will only read the internal structures and DTB. To be safe you may want to use a reader-writer lock around the library (only calls to `dtb_init()` will need the write lock). If you only plan to initialize the parser once, even this is not necessary. 31 | 32 | ## Standalone Reader 33 | This repo also can also build a tool called `readfdt` which takes a flattened device tree file as input, and will print a summary of it's contents. This tool is mainly intended for testing the library part of this project, but it does what it says. 34 | 35 | To build it, run `make all` in this project's directory. A C compiler is required. 36 | 37 | ## Changelog 38 | ### v1.0.0rc1 39 | - Renamed testing binary from `test.elf` to `readfdt`. 40 | - Added some comments in the code. 41 | - Blew up the number of lines in the code to really test the limits of 'smol'. 42 | - Properly implemented `dtb_find`. 43 | 44 | ### v0.2.0 45 | - Renamed `dtb_get_prop` to `dtb_find_prop`. 46 | - Added a new `dtb_get_prop` which returns a property based on an index, rather than name. 47 | - Added support for using a static buffer instead of malloc/free. 48 | - Better documentation. 49 | 50 | ### v0.1.0 51 | - Initial release. 52 | 53 | -------------------------------------------------------------------------------- /smoldtb.c: -------------------------------------------------------------------------------- 1 | #include "smoldtb.h" 2 | 3 | /* ---- Section: Defines and Structs ---- */ 4 | 5 | #define FDT_MAGIC 0xD00DFEED 6 | #define FDT_BEGIN_NODE 1 7 | #define FDT_END_NODE 2 8 | #define FDT_PROP 3 9 | #define FDT_NOP 4 10 | 11 | #define FDT_VERSION 17 12 | #define FDT_CELL_SIZE 4 13 | #define ROOT_NODE_STR "\'/\'" 14 | 15 | #define SMOLDTB_FOREACH_CONTINUE 0 16 | #define SMOLDTB_FOREACH_ABORT 1 17 | 18 | #ifndef SMOLDTB_NO_LOGGING 19 | #define LOG_ERROR(msg) do { if (state.ops.on_error != NULL) { state.ops.on_error(msg); }} while(false) 20 | #else 21 | #define LOG_ERROR(msg) 22 | #endif 23 | 24 | /* The 'fdt_*' structs represent data layouts taken directly from the device tree 25 | * specification. In contrast the 'dtb_*' structs are for the parser. 26 | */ 27 | struct fdt_header 28 | { 29 | uint32_t magic; 30 | uint32_t total_size; 31 | uint32_t offset_structs; 32 | uint32_t offset_strings; 33 | uint32_t offset_memmap_rsvd; 34 | uint32_t version; 35 | uint32_t last_comp_version; 36 | uint32_t boot_cpu_id; 37 | uint32_t size_strings; 38 | uint32_t size_structs; 39 | }; 40 | 41 | struct fdt_reserved_mem_entry 42 | { 43 | uint64_t base; 44 | uint64_t length; 45 | }; 46 | 47 | struct fdt_property 48 | { 49 | uint32_t length; 50 | uint32_t name_offset; 51 | }; 52 | 53 | /* The tree is represented in horizontal slices, where all child nodes are represented 54 | * in a singly-linked list. Only a pointer to the first child is stored in the parent, and 55 | * the list is build using the node->sibling pointer. 56 | * For reference the pointer building the tree are: 57 | * - parent: go up one level 58 | * - sibling: the next node on this level. To access the previous node, access the parent and then 59 | * the child pointer and iterate to just before the target. 60 | * - child: the first child node. 61 | */ 62 | struct dtb_node_t 63 | { 64 | dtb_node* parent; 65 | dtb_node* sibling; 66 | dtb_node* child; 67 | dtb_prop* props; 68 | const char* name; 69 | bool fromMalloc; 70 | }; 71 | 72 | /* Similar to nodes, properties are stored a singly linked list. */ 73 | struct dtb_prop_t 74 | { 75 | dtb_node* node; 76 | const char* name; 77 | void* data; 78 | dtb_prop* next; 79 | uint32_t length; 80 | bool fromMalloc; 81 | bool dataFromMalloc; 82 | }; 83 | 84 | /* Info for initializing the global state during init */ 85 | struct dtb_init_info 86 | { 87 | const uint32_t* cells; 88 | const char* strings; 89 | size_t cell_count; 90 | }; 91 | 92 | /* Global parser state */ 93 | struct dtb_state 94 | { 95 | dtb_node* root; 96 | dtb_node** handle_lookup; 97 | dtb_node* node_buff; 98 | size_t node_alloc_head; 99 | size_t node_alloc_max; 100 | dtb_prop* prop_buff; 101 | size_t prop_alloc_head; 102 | size_t prop_alloc_max; 103 | uint64_t* resv_memory; 104 | 105 | dtb_ops ops; 106 | }; 107 | 108 | struct dtb_state state; 109 | 110 | #ifdef SMOLDTB_STATIC_BUFFER_SIZE 111 | uint8_t big_buff[SMOLDTB_STATIC_BUFFER_SIZE]; 112 | #endif 113 | 114 | /* ---- Section: Utility Functions ---- */ 115 | 116 | static uint32_t be32(uint32_t input) 117 | { 118 | #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 119 | return input; 120 | #else 121 | uint32_t temp = 0; 122 | temp |= (input & 0xFF) << 24; 123 | temp |= (input & 0xFF00) << 8; 124 | temp |= (input & 0xFF0000) >> 8; 125 | temp |= (input & 0xFF000000) >> 24; 126 | return temp; 127 | #endif 128 | } 129 | 130 | static uint64_t be64(uint64_t input) 131 | { 132 | #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 133 | return input; 134 | #else 135 | uint64_t temp = 0; 136 | temp |= ((input >> 0) & 0xFF) << 56; 137 | temp |= ((input >> 8) & 0xFF) << 48; 138 | temp |= ((input >> 16) & 0xFF) << 40; 139 | temp |= ((input >> 24) & 0xFF) << 32; 140 | temp |= ((input >> 32) & 0xFF) << 24; 141 | temp |= ((input >> 40) & 0xFF) << 16; 142 | temp |= ((input >> 48) & 0xFF) << 8; 143 | temp |= ((input >> 56) & 0xFF) << 0; 144 | return temp; 145 | #endif 146 | } 147 | 148 | static size_t string_len(const char* str) 149 | { 150 | if (str == NULL) 151 | return 0; 152 | 153 | size_t count = 0; 154 | while (str[count] != 0) 155 | count++; 156 | return count; 157 | } 158 | 159 | static void* memcpy(void* dest, const void* src, size_t count) 160 | { 161 | uint8_t* d = (uint8_t*)dest; 162 | const uint8_t* s = src; 163 | 164 | for (size_t i = 0; i < count; i++) 165 | d[i] = s[i]; 166 | 167 | return dest; 168 | } 169 | 170 | static bool strings_eq(const char* a, const char* b, size_t len) 171 | { 172 | for (size_t i = 0; i < len; i++) 173 | { 174 | if (a[i] == 0 && b[i] == 0) 175 | return true; 176 | if (a[i] != b[i]) 177 | return false; 178 | } 179 | 180 | return true; 181 | } 182 | 183 | static size_t string_find_char(const char* str, char target) 184 | { 185 | size_t i = 0; 186 | while (str[i] != target) 187 | { 188 | if (str[i] == 0) 189 | return -1ul; 190 | i++; 191 | } 192 | return i; 193 | } 194 | 195 | static size_t dtb_align_up(size_t input, size_t alignment) 196 | { 197 | return ((input + alignment - 1) / alignment) * alignment; 198 | } 199 | 200 | static void do_foreach_sibling(dtb_node* begin, int (*action)(dtb_node* node, void* opaque), void* opaque) 201 | { 202 | if (begin == NULL) 203 | return; 204 | if (action == NULL) 205 | return; 206 | 207 | for (dtb_node* node = begin; node != NULL; node = node->sibling) 208 | { 209 | if (action(node, opaque) == SMOLDTB_FOREACH_ABORT) 210 | return; 211 | } 212 | } 213 | 214 | static void do_foreach_prop(dtb_node* node, int (*action)(dtb_node* node, dtb_prop* prop, void* opaque), void* opaque) 215 | { 216 | if (node == NULL) 217 | return; 218 | if (node->props == NULL) 219 | return; 220 | if (action == NULL) 221 | return; 222 | 223 | for (dtb_prop* prop = node->props; prop != NULL; prop = prop->next) 224 | { 225 | if (action(node, prop, opaque) == SMOLDTB_FOREACH_ABORT) 226 | return; 227 | } 228 | } 229 | 230 | static smoldtb_value extract_cells(const uint32_t* cells, size_t count) 231 | { 232 | smoldtb_value value = 0; 233 | for (size_t i = 0; i < count; i++) 234 | value |= (smoldtb_value)be32(cells[i]) << ((count - 1 - i) * 32); 235 | return value; 236 | } 237 | 238 | static void* try_malloc(size_t count) 239 | { 240 | if (state.ops.malloc != NULL) 241 | return state.ops.malloc(count); 242 | 243 | LOG_ERROR("try_malloc() called but state.ops.malloc is NULL"); 244 | return NULL; 245 | } 246 | 247 | static void try_free(void* ptr, size_t count) 248 | { 249 | if (state.ops.free != NULL) 250 | state.ops.free(ptr, count); 251 | 252 | LOG_ERROR("try_free() called but state.ops.free is NULL"); 253 | } 254 | 255 | /* ---- Section: Readonly-Mode Private Functions ---- */ 256 | 257 | static dtb_node* alloc_node() 258 | { 259 | if (state.node_alloc_head + 1 < state.node_alloc_max) 260 | return &state.node_buff[state.node_alloc_head++]; 261 | 262 | LOG_ERROR("Not enough space for source dtb node."); 263 | return NULL; 264 | } 265 | 266 | static dtb_prop* alloc_prop() 267 | { 268 | if (state.prop_alloc_head + 1 < state.prop_alloc_max) 269 | return &state.prop_buff[state.prop_alloc_head++]; 270 | 271 | LOG_ERROR("Not enough space for source dtb property."); 272 | return NULL; 273 | } 274 | 275 | static void free_buffers() 276 | { 277 | #ifndef SMOLDTB_STATIC_BUFFER_SIZE 278 | size_t buff_size = state.node_alloc_max * sizeof(dtb_node); 279 | buff_size += state.prop_alloc_max * sizeof(dtb_prop); 280 | buff_size += state.node_alloc_max * sizeof(void*); 281 | 282 | try_free(state.node_buff, buff_size); 283 | state.node_buff = NULL; 284 | state.prop_buff = NULL; 285 | state.handle_lookup = NULL; 286 | #endif 287 | 288 | state.node_alloc_head = state.node_alloc_max = 0; 289 | state.prop_alloc_head = state.prop_alloc_max = 0; 290 | } 291 | 292 | static bool alloc_buffers(struct dtb_init_info* init_info) 293 | { 294 | state.node_alloc_max = 0; 295 | state.prop_alloc_max = 0; 296 | for (size_t i = 0; i < init_info->cell_count; i++) 297 | { 298 | if (be32(init_info->cells[i]) == FDT_BEGIN_NODE) 299 | state.node_alloc_max++; 300 | else if (be32(init_info->cells[i]) == FDT_PROP) 301 | state.prop_alloc_max++; 302 | } 303 | 304 | size_t total_size = state.node_alloc_max * sizeof(dtb_node); 305 | total_size += state.prop_alloc_max * sizeof(dtb_prop); 306 | total_size += state.node_alloc_max * sizeof(void*); //we assume the worst case and that each node has a phandle prop 307 | 308 | #ifdef SMOLDTB_STATIC_BUFFER_SIZE 309 | if (total_size >= SMOLDTB_STATIC_BUFFER_SIZE) 310 | { 311 | LOG_ERROR("Too much data for statically allocated buffer."); 312 | return false; 313 | } 314 | uint8_t* buffer = big_buff; 315 | #else 316 | uint8_t* buffer = try_malloc(total_size); 317 | if (buffer == NULL) 318 | { 319 | LOG_ERROR("Failed to allocate big buffer."); 320 | return false; 321 | } 322 | #endif 323 | 324 | for (size_t i = 0; i < total_size; i++) 325 | buffer[i] = 0; 326 | 327 | state.node_buff = (dtb_node*)buffer; 328 | state.node_alloc_head = 0; 329 | state.prop_buff = (dtb_prop*)&state.node_buff[state.node_alloc_max]; 330 | state.prop_alloc_head = 0; 331 | state.handle_lookup = (dtb_node**)&state.prop_buff[state.prop_alloc_max]; 332 | 333 | return true; 334 | } 335 | 336 | /* This runs on every new property found, and handles some special cases for us. */ 337 | static void check_for_special_prop(dtb_node* node, dtb_prop* prop) 338 | { 339 | const char name0 = prop->name[0]; 340 | if (name0 != 'p' || name0 != 'l') 341 | return; //short circuit to save processing 342 | 343 | const size_t name_len = string_len(prop->name); 344 | 345 | const char str_phandle[] = "phandle"; 346 | const size_t len_phandle = sizeof(str_phandle) - 1; 347 | if (name_len == len_phandle && strings_eq(prop->name, str_phandle, name_len)) 348 | { 349 | smoldtb_value handle; 350 | dtb_read_prop_1(prop, 1, &handle); 351 | state.handle_lookup[handle] = node; 352 | return; 353 | } 354 | 355 | const char str_lhandle[] = "linux,phandle"; 356 | const size_t len_lhandle = sizeof(str_lhandle) - 1; 357 | if (name_len == len_lhandle && strings_eq(prop->name, str_lhandle, name_len)) 358 | { 359 | smoldtb_value handle; 360 | dtb_read_prop_1(prop, 1, &handle); 361 | state.handle_lookup[handle] = node; 362 | return; 363 | } 364 | } 365 | 366 | static dtb_prop* parse_prop(struct dtb_init_info* init_info, size_t* offset) 367 | { 368 | if (be32(init_info->cells[*offset]) != FDT_PROP) 369 | return NULL; 370 | 371 | (*offset)++; 372 | dtb_prop* prop = alloc_prop(); 373 | if (prop == NULL) 374 | { 375 | LOG_ERROR("Property allocation failed"); 376 | return NULL; 377 | } 378 | 379 | const struct fdt_property* fdtprop = (struct fdt_property*)(init_info->cells + *offset); 380 | prop->name = (const char*)(init_info->strings + be32(fdtprop->name_offset)); 381 | prop->data = (void*)(init_info->cells + *offset + 2); 382 | prop->length = be32(fdtprop->length); 383 | prop->fromMalloc = false; 384 | prop->dataFromMalloc = false; 385 | (*offset) += (dtb_align_up(be32(fdtprop->length), 4) / 4) + 2; 386 | 387 | return prop; 388 | } 389 | 390 | static dtb_node* parse_node(struct dtb_init_info* init_info, size_t* offset) 391 | { 392 | if (be32(init_info->cells[*offset]) != FDT_BEGIN_NODE) 393 | return NULL; 394 | 395 | dtb_node* node = alloc_node(); 396 | if (node == NULL) 397 | { 398 | LOG_ERROR("Node allocation failed"); 399 | return NULL; 400 | } 401 | node->name = (const char*)(init_info->cells + (*offset) + 1); 402 | node->fromMalloc = false; 403 | 404 | const size_t name_len = string_len(node->name); 405 | if (name_len == 0) 406 | node->name = NULL; 407 | *offset += (dtb_align_up(name_len + 1, FDT_CELL_SIZE) / FDT_CELL_SIZE) + 1; 408 | 409 | while (*offset < init_info->cell_count) 410 | { 411 | const uint32_t test = be32(init_info->cells[*offset]); 412 | if (test == FDT_END_NODE) 413 | { 414 | (*offset)++; 415 | return node; 416 | } 417 | else if (test == FDT_BEGIN_NODE) 418 | { 419 | dtb_node* child = parse_node(init_info, offset); 420 | if (child == NULL) 421 | continue; 422 | 423 | child->sibling = node->child; 424 | node->child = child; 425 | child->parent = node; 426 | } 427 | else if (test == FDT_PROP) 428 | { 429 | dtb_prop* prop = parse_prop(init_info, offset); 430 | if (prop == NULL) 431 | continue; 432 | 433 | prop->next = node->props; 434 | prop->node = node; 435 | node->props = prop; 436 | check_for_special_prop(node, prop); 437 | } 438 | else 439 | (*offset)++; 440 | } 441 | 442 | LOG_ERROR("Node is missing terminating tag."); 443 | return NULL; 444 | } 445 | 446 | /* ---- Section: Readonly-Mode Public API ---- */ 447 | 448 | size_t dtb_query_total_size(uintptr_t fdt_start) 449 | { 450 | if (fdt_start == 0) 451 | return 0; 452 | 453 | struct fdt_header* header = (struct fdt_header*)fdt_start; 454 | if (be32(header->magic) != FDT_MAGIC) 455 | return 0; 456 | 457 | return be32(header->total_size); 458 | } 459 | 460 | bool dtb_init(uintptr_t start, dtb_ops ops) 461 | { 462 | state.ops = ops; 463 | 464 | #if !defined(SMOLDTB_STATIC_BUFFER_SIZE) 465 | if (state.ops.malloc == NULL) 466 | { 467 | LOG_ERROR("smoldtb has been compiled without an internal static buffer, but not passed a malloc() function."); 468 | return false; 469 | } 470 | #endif 471 | 472 | struct dtb_init_info init_info; 473 | if (start == SMOLDTB_INIT_EMPTY_TREE) 474 | { 475 | state.root = NULL; 476 | return true; 477 | } 478 | 479 | struct fdt_header* header = (struct fdt_header*)start; 480 | if (be32(header->magic) != FDT_MAGIC) 481 | { 482 | LOG_ERROR("FDT has incorrect magic number."); 483 | return false; 484 | } 485 | 486 | state.resv_memory = (uint64_t*)(start + be32(header->offset_memmap_rsvd)); 487 | init_info.cells = (const uint32_t*)(start + be32(header->offset_structs)); 488 | init_info.cell_count = be32(header->size_structs) / sizeof(uint32_t); 489 | init_info.strings = (const char*)(start + be32(header->offset_strings)); 490 | 491 | if (state.node_buff != NULL) 492 | free_buffers(); 493 | if (!alloc_buffers(&init_info)) 494 | { 495 | LOG_ERROR("failed to allocate readonly buffer"); 496 | return false; 497 | } 498 | 499 | for (size_t i = 0; i < init_info.cell_count; i++) 500 | { 501 | if (be32(init_info.cells[i]) != FDT_BEGIN_NODE) 502 | continue; 503 | 504 | dtb_node* sub_root = parse_node(&init_info, &i); 505 | if (sub_root == NULL) 506 | continue; 507 | sub_root->sibling = state.root; 508 | state.root = sub_root; 509 | } 510 | 511 | return true; 512 | } 513 | 514 | dtb_node* dtb_find_compatible(dtb_node* start, const char* str) 515 | { 516 | size_t begin_index = 0; 517 | if (start != NULL) 518 | { 519 | const uintptr_t offset = (uintptr_t)start - (uintptr_t)state.node_buff; 520 | begin_index = offset / sizeof(dtb_node); 521 | begin_index++; //we want to start searching AFTER this node. 522 | } 523 | 524 | for (size_t i = begin_index; i < state.node_alloc_head; i++) 525 | { 526 | dtb_node* node = &state.node_buff[i]; 527 | if (dtb_is_compatible(node, str)) 528 | return node; 529 | } 530 | 531 | return NULL; 532 | } 533 | 534 | dtb_node* dtb_find_phandle(unsigned handle) 535 | { 536 | if (handle < state.node_alloc_max) 537 | return state.handle_lookup[handle]; 538 | 539 | return NULL; 540 | } 541 | 542 | static dtb_node* find_child_internal(dtb_node* start, const char* name, size_t name_bounds) 543 | { 544 | dtb_node* scan = start->child; 545 | while (scan != NULL) 546 | { 547 | size_t child_name_len = string_find_char(scan->name, '@'); 548 | if (child_name_len == -1ul) 549 | child_name_len = string_len(scan->name); 550 | 551 | if (child_name_len == name_bounds && strings_eq(scan->name, name, name_bounds)) 552 | return scan; 553 | 554 | scan = scan->sibling; 555 | } 556 | 557 | return NULL; 558 | } 559 | 560 | dtb_node* dtb_find(const char* name) 561 | { 562 | size_t seg_len; 563 | dtb_node* scan = state.root; 564 | while (scan != NULL) 565 | { 566 | while (name[0] == '/') 567 | name++; 568 | 569 | seg_len = string_find_char(name, '/'); 570 | if (seg_len == -1ul) 571 | seg_len = string_len(name); 572 | if (seg_len == 0) 573 | return scan; 574 | 575 | scan = find_child_internal(scan, name, seg_len); 576 | name += seg_len; 577 | } 578 | 579 | return NULL; 580 | } 581 | 582 | dtb_node* dtb_find_child(dtb_node* start, const char* name) 583 | { 584 | if (start == NULL) 585 | return NULL; 586 | 587 | return find_child_internal(start, name, string_len(name)); 588 | } 589 | 590 | dtb_prop* dtb_find_prop(dtb_node* node, const char* name) 591 | { 592 | if (node == NULL) 593 | return NULL; 594 | 595 | const size_t name_len = string_len(name); 596 | dtb_prop* prop = node->props; 597 | while (prop) 598 | { 599 | const size_t prop_name_len = string_len(prop->name); 600 | if (prop_name_len == name_len && strings_eq(prop->name, name, prop_name_len)) 601 | return prop; 602 | prop = prop->next; 603 | } 604 | 605 | return NULL; 606 | } 607 | 608 | dtb_node* dtb_get_sibling(dtb_node* node) 609 | { 610 | if (node == NULL || node->sibling == NULL) 611 | return NULL; 612 | return node->sibling; 613 | } 614 | 615 | dtb_node* dtb_get_child(dtb_node* node) 616 | { 617 | if (node == NULL) 618 | return NULL; 619 | return node->child; 620 | } 621 | 622 | dtb_node* dtb_get_parent(dtb_node* node) 623 | { 624 | if (node == NULL) 625 | return NULL; 626 | return node->parent; 627 | } 628 | 629 | 630 | dtb_prop* dtb_get_prop(dtb_node* node, size_t index) 631 | { 632 | if (node == NULL) 633 | return NULL; 634 | 635 | dtb_prop* prop = node->props; 636 | while (prop != NULL) 637 | { 638 | if (index == 0) 639 | return prop; 640 | 641 | index--; 642 | prop = prop->next; 643 | } 644 | 645 | return NULL; 646 | } 647 | 648 | static size_t get_cells_helper(dtb_node* node, const char* prop_name, size_t orDefault) 649 | { 650 | if (node == NULL) 651 | return orDefault; 652 | 653 | dtb_prop* prop = dtb_find_prop(node, prop_name); 654 | if (prop == NULL) 655 | return orDefault; 656 | 657 | smoldtb_value ret_value; 658 | if (dtb_read_prop_1(prop, 1, &ret_value) == 1) 659 | return (size_t)ret_value; 660 | return orDefault; 661 | } 662 | 663 | size_t dtb_get_addr_cells_of(dtb_node* node) 664 | { 665 | return get_cells_helper(node, "#address-cells", 2); 666 | } 667 | 668 | size_t dtb_get_size_cells_of(dtb_node* node) 669 | { 670 | return get_cells_helper(node, "#size-cells", 1); 671 | } 672 | 673 | size_t dtb_get_addr_cells_for(dtb_node* node) 674 | { 675 | if (node == NULL) 676 | return 2; 677 | return get_cells_helper(node->parent, "#address-cells", 2); 678 | } 679 | 680 | size_t dtb_get_size_cells_for(dtb_node* node) 681 | { 682 | if (node == NULL) 683 | return 1; 684 | return get_cells_helper(node->parent, "#size-cells", 1); 685 | } 686 | 687 | bool dtb_is_compatible(dtb_node* node, const char* str) 688 | { 689 | if (node == NULL || str == NULL) 690 | return false; 691 | 692 | dtb_prop* compat_prop = dtb_find_prop(node, "compatible"); 693 | if (compat_prop == NULL) 694 | return false; 695 | 696 | const size_t str_len = string_len(str); 697 | for (size_t i = 0; ; i++) 698 | { 699 | const char* check_str = dtb_read_prop_string(compat_prop, i); 700 | if (check_str == NULL) 701 | return false; 702 | if (strings_eq(check_str, str, str_len)) 703 | return true; 704 | } 705 | } 706 | 707 | bool dtb_stat_node(dtb_node* node, dtb_node_stat* stat) 708 | { 709 | if (node == NULL || stat == NULL) 710 | return false; 711 | 712 | stat->name = node->name; 713 | if (node == state.root) 714 | stat->name = ROOT_NODE_STR; 715 | 716 | stat->prop_count = 0; 717 | dtb_prop* prop = node->props; 718 | while (prop != NULL) 719 | { 720 | prop = prop->next; 721 | stat->prop_count++; 722 | } 723 | 724 | stat->child_count = 0; 725 | dtb_node* child = node->child; 726 | while (child != NULL) 727 | { 728 | child = child->sibling; 729 | stat->child_count++; 730 | } 731 | 732 | stat->sibling_count = 0; 733 | if (node->parent) 734 | { 735 | dtb_node* prime = node->parent->child; 736 | while (prime != NULL) 737 | { 738 | prime = prime->sibling; 739 | stat->sibling_count++; 740 | } 741 | } 742 | 743 | return true; 744 | } 745 | 746 | bool dtb_stat_prop(dtb_prop* prop, dtb_prop_stat* stat) 747 | { 748 | if (prop == NULL || stat == NULL) 749 | return false; 750 | 751 | stat->name = prop->name; 752 | stat->data = prop->data; 753 | stat->data_len = prop->length; 754 | return true; 755 | } 756 | 757 | size_t dtb_read_resv_memory(size_t entry_count, dtb_reserved_memory* vals) 758 | { 759 | size_t total_count = 0; 760 | while (be64(state.resv_memory[total_count * 2] != 0)) 761 | total_count++; 762 | 763 | if (entry_count == 0 || vals == NULL) 764 | return total_count; 765 | 766 | if (total_count < entry_count) 767 | entry_count = total_count; 768 | for (size_t i = 0; i < entry_count; i++) 769 | { 770 | vals[i].base = be64(state.resv_memory[i * 2]); 771 | vals[i].length = be64(state.resv_memory[i * 2]); 772 | } 773 | 774 | return entry_count; 775 | } 776 | 777 | const char* dtb_read_prop_string(dtb_prop* prop, size_t index) 778 | { 779 | if (prop == NULL) 780 | return NULL; 781 | 782 | const uint8_t* name = (const uint8_t*)prop->data; 783 | size_t curr_index = 0; 784 | for (size_t scan = 0; scan < prop->length * 4; scan++) 785 | { 786 | if (name[scan] == 0) 787 | { 788 | curr_index++; 789 | continue; 790 | } 791 | if (curr_index == index) 792 | return (const char*)&name[scan]; 793 | } 794 | 795 | return NULL; 796 | } 797 | 798 | size_t dtb_read_prop_1(dtb_prop* prop, size_t cell_count, smoldtb_value* vals) 799 | { 800 | if (prop == NULL || cell_count == 0) 801 | return 0; 802 | 803 | const uint32_t* prop_cells = prop->data; 804 | const struct fdt_property* fdtprop = (const struct fdt_property*)(prop_cells - 2); 805 | const size_t count = be32(fdtprop->length) / (cell_count * FDT_CELL_SIZE); 806 | if (vals == NULL) 807 | return count; 808 | 809 | for (size_t i = 0; i < count; i++) 810 | { 811 | const uint32_t* base = prop_cells + i * cell_count; 812 | vals[i] = extract_cells(base, cell_count); 813 | } 814 | 815 | return count; 816 | } 817 | 818 | size_t dtb_read_prop_2(dtb_prop* prop, dtb_pair layout, dtb_pair* vals) 819 | { 820 | if (prop == NULL || layout.a == 0 || layout.b == 0) 821 | return 0; 822 | 823 | const uint32_t* prop_cells = prop->data; 824 | const struct fdt_property* fdtprop = (const struct fdt_property*)(prop_cells - 2); 825 | const size_t count = be32(fdtprop->length) / ((layout.a + layout.b) * FDT_CELL_SIZE); 826 | if (vals == NULL) 827 | return count; 828 | 829 | for (size_t i = 0; i < count; i++) 830 | { 831 | const uint32_t* base = prop_cells + i * (layout.a + layout.b); 832 | vals[i].a = extract_cells(base, layout.a); 833 | vals[i].b = extract_cells(base + layout.a, layout.b); 834 | } 835 | return count; 836 | } 837 | 838 | size_t dtb_read_prop_3(dtb_prop* prop, dtb_triplet layout, dtb_triplet* vals) 839 | { 840 | if (prop == NULL || layout.a == 0 || layout.b == 0 || layout.c == 0) 841 | return 0; 842 | 843 | const uint32_t* prop_cells = prop->data; 844 | const struct fdt_property* fdtprop = (const struct fdt_property*)(prop_cells - 2); 845 | const size_t stride = layout.a + layout.b + layout.c; 846 | const size_t count = be32(fdtprop->length) / (stride * FDT_CELL_SIZE); 847 | if (vals == NULL) 848 | return count; 849 | 850 | for (size_t i = 0; i < count; i++) 851 | { 852 | const uint32_t* base = prop_cells + i * stride; 853 | vals[i].a = extract_cells(base, layout.a); 854 | vals[i].b = extract_cells(base + layout.a, layout.b); 855 | vals[i].c = extract_cells(base + layout.a + layout.b, layout.c); 856 | } 857 | return count; 858 | } 859 | 860 | size_t dtb_read_prop_4(dtb_prop* prop, dtb_quad layout, dtb_quad* vals) 861 | { 862 | if (prop == NULL || layout.a == 0 || layout.b == 0 || layout.c == 0 || layout.d == 0) 863 | return 0; 864 | 865 | const uint32_t* prop_cells = prop->data; 866 | const struct fdt_property* fdtprop = (const struct fdt_property*)(prop_cells - 2); 867 | const size_t stride = layout.a + layout.b + layout.c + layout.d; 868 | const size_t count = be32(fdtprop->length) / (stride * FDT_CELL_SIZE); 869 | if (vals == NULL) 870 | return count; 871 | 872 | for (size_t i = 0; i < count; i++) 873 | { 874 | const uint32_t* base = prop_cells + i * stride; 875 | vals[i].a = extract_cells(base, layout.a); 876 | vals[i].b = extract_cells(base + layout.a, layout.b); 877 | vals[i].c = extract_cells(base + layout.a + layout.b, layout.c); 878 | vals[i].d = extract_cells(base + layout.a + layout.b + layout.c, layout.d); 879 | } 880 | return count; 881 | } 882 | 883 | #ifdef SMOLDTB_ENABLE_WRITE_API 884 | /* ---- Section: Writable-Mode Private Functions ---- */ 885 | 886 | struct finalise_data 887 | { 888 | uint32_t* struct_buf; 889 | char* string_buf; 890 | size_t struct_ptr; 891 | size_t string_ptr; 892 | size_t struct_buf_size; 893 | size_t string_buf_size; 894 | bool print_success; 895 | }; 896 | 897 | struct name_collision_check 898 | { 899 | const char* name; 900 | size_t name_len; 901 | bool collision; 902 | }; 903 | 904 | static int destroy_props(dtb_node* node, dtb_prop* prop, void* opaque) 905 | { 906 | (void)node; 907 | (void)opaque; 908 | 909 | if (prop->dataFromMalloc) 910 | try_free(prop->data, prop->length); 911 | if (prop->fromMalloc) 912 | try_free(prop, sizeof(dtb_prop)); 913 | 914 | return SMOLDTB_FOREACH_CONTINUE; 915 | } 916 | 917 | static void destroy_dead_node(dtb_node* node) 918 | { 919 | if (node == NULL || node->parent != NULL) 920 | return; 921 | 922 | while (node->child != NULL) 923 | { 924 | dtb_node* deletee = node->child; 925 | node->child = node->child->sibling; 926 | 927 | deletee->parent = NULL; 928 | destroy_dead_node(deletee); 929 | } 930 | 931 | do_foreach_prop(node, destroy_props, NULL); 932 | if (node->fromMalloc) 933 | try_free(node, sizeof(dtb_node)); 934 | } 935 | 936 | static int init_finalise_data_prop(dtb_node* node, dtb_prop* prop, void* opaque) 937 | { 938 | (void)node; 939 | if (node == NULL || prop == NULL) 940 | return SMOLDTB_FOREACH_CONTINUE; 941 | 942 | struct finalise_data* data = opaque; 943 | data->struct_buf_size += 3; /* +1 for FDT_PROP token, +2 for prop description struct */ 944 | data->struct_buf_size += dtb_align_up(prop->length, FDT_CELL_SIZE) / FDT_CELL_SIZE; 945 | data->string_buf_size += string_len(prop->name) + 1; /* +1 for null terminator */ 946 | 947 | return SMOLDTB_FOREACH_CONTINUE; 948 | } 949 | 950 | static int init_finalise_data(dtb_node* node, void* opaque) 951 | { 952 | if (node == NULL) 953 | return SMOLDTB_FOREACH_CONTINUE; 954 | 955 | struct finalise_data* data = opaque; 956 | data->struct_buf_size += 2; /* +1 for BEGIN_NODE token, +1 for END_NODE token */ 957 | data->struct_buf_size += dtb_align_up(string_len(node->name) + 1, FDT_CELL_SIZE) / FDT_CELL_SIZE; /* +1 for null terminator */ 958 | 959 | do_foreach_prop(node, init_finalise_data_prop, opaque); 960 | do_foreach_sibling(node->child, init_finalise_data, opaque); 961 | 962 | return SMOLDTB_FOREACH_CONTINUE; 963 | } 964 | 965 | static int print_prop(dtb_node* node, dtb_prop* prop, void* opaque) 966 | { 967 | (void)node; 968 | struct finalise_data* data = opaque; 969 | 970 | const uint32_t name_offset = data->string_ptr; 971 | const size_t name_len = string_len(prop->name); 972 | if (data->string_ptr + name_len + 1 > data->string_buf_size) /* bounds check */ 973 | { 974 | data->print_success = false; 975 | return SMOLDTB_FOREACH_ABORT; 976 | } 977 | 978 | memcpy(data->string_buf + data->string_ptr, prop->name, name_len); 979 | data->string_buf[data->string_ptr + name_len] = 0; 980 | data->string_ptr += name_len + 1; /* +1 for null terminator */ 981 | 982 | const size_t data_cells = dtb_align_up(prop->length, FDT_CELL_SIZE) / FDT_CELL_SIZE; 983 | if (data->struct_ptr + 3 + data_cells > data->struct_buf_size) /* bounds check */ 984 | { 985 | data->print_success = false; 986 | return SMOLDTB_FOREACH_ABORT; 987 | } 988 | 989 | data->struct_buf[data->struct_ptr++] = be32(FDT_PROP); 990 | data->struct_buf[data->struct_ptr++] = be32((uint32_t)prop->length); 991 | data->struct_buf[data->struct_ptr++] = be32(name_offset); 992 | 993 | uint32_t* prop_cells = prop->data; 994 | for (size_t i = 0; i < data_cells; i++) 995 | data->struct_buf[data->struct_ptr++] = prop_cells[i]; 996 | 997 | return SMOLDTB_FOREACH_CONTINUE; 998 | } 999 | 1000 | static int print_node(dtb_node* node, void* opaque) 1001 | { 1002 | struct finalise_data* data = opaque; 1003 | const size_t name_len = string_len(node->name); 1004 | const size_t name_cells = dtb_align_up(name_len + 1, FDT_CELL_SIZE) / FDT_CELL_SIZE; 1005 | 1006 | if (data->struct_ptr + 1 + name_cells > data->struct_buf_size) /* bounds check */ 1007 | { 1008 | data->print_success = false; 1009 | return SMOLDTB_FOREACH_ABORT; 1010 | } 1011 | 1012 | data->struct_buf[data->struct_ptr++] = be32(FDT_BEGIN_NODE); 1013 | 1014 | uint8_t* name_buf = (uint8_t*)(data->struct_buf + data->struct_ptr); 1015 | memcpy(name_buf, node->name, name_len); 1016 | name_buf[name_len] = 0; 1017 | data->struct_ptr += name_cells; 1018 | 1019 | do_foreach_prop(node, print_prop, opaque); 1020 | if (!data->print_success) 1021 | return SMOLDTB_FOREACH_ABORT; 1022 | do_foreach_sibling(node->child, print_node, opaque); 1023 | if (!data->print_success) 1024 | return SMOLDTB_FOREACH_ABORT; 1025 | 1026 | if (data->struct_ptr + 1 > data->struct_buf_size) /* bounds check */ 1027 | { 1028 | data->print_success = false; 1029 | return SMOLDTB_FOREACH_ABORT; 1030 | } 1031 | data->struct_buf[data->struct_ptr++] = be32(FDT_END_NODE); 1032 | 1033 | return SMOLDTB_FOREACH_CONTINUE; 1034 | } 1035 | 1036 | static int check_sibling_name_collisions(dtb_node* node, void* opaque) 1037 | { 1038 | struct name_collision_check* check = opaque; 1039 | 1040 | if (!strings_eq(node->name, check->name, check->name_len)) 1041 | return SMOLDTB_FOREACH_CONTINUE; 1042 | 1043 | check->collision = true; 1044 | return SMOLDTB_FOREACH_ABORT; 1045 | } 1046 | 1047 | static int check_prop_name_collisions(dtb_node* node, dtb_prop* prop, void* opaque) 1048 | { 1049 | (void)node; 1050 | struct name_collision_check* check = opaque; 1051 | 1052 | if (!strings_eq(prop->name, check->name, check->name_len)) 1053 | return SMOLDTB_FOREACH_CONTINUE; 1054 | 1055 | check->collision = true; 1056 | return SMOLDTB_FOREACH_ABORT; 1057 | } 1058 | 1059 | /* ---- Section: Writable-Mode Public API ---- */ 1060 | 1061 | size_t dtb_finalise_to_buffer(void* buffer, size_t buffer_size, uint32_t boot_cpu_id, dtb_reserved_memory* resv, size_t resv_count) 1062 | { 1063 | struct finalise_data final_data; 1064 | final_data.struct_buf_size = 0; 1065 | final_data.string_buf_size = 1; /* we'll use 1 byte for the empty string */ 1066 | 1067 | do_foreach_sibling(state.root, init_finalise_data, &final_data); 1068 | const size_t reserved_block_size = (resv_count + 1) * sizeof(dtb_reserved_memory); 1069 | const size_t struct_buf_bytes = final_data.struct_buf_size * FDT_CELL_SIZE; 1070 | const size_t total_bytes = final_data.string_buf_size + struct_buf_bytes + 1071 | sizeof(struct fdt_header) + reserved_block_size; 1072 | 1073 | if (buffer == NULL || total_bytes < buffer_size) 1074 | return total_bytes; 1075 | if ((uintptr_t)buffer & 0b11) 1076 | return SMOLDTB_FINALISE_FAILURE; /* check buffer is aligned to a 32-bit boundary */ 1077 | 1078 | struct fdt_header* header = (struct fdt_header*)buffer; 1079 | header->magic = be32(FDT_MAGIC); 1080 | header->total_size = be32(total_bytes); 1081 | header->offset_structs = be32(sizeof(struct fdt_header) + 16); /* size of reserved block */ 1082 | header->offset_strings = be32(be32(header->offset_structs) + struct_buf_bytes); 1083 | header->offset_memmap_rsvd = be32(sizeof(struct fdt_header)); 1084 | header->version = be32(FDT_VERSION); 1085 | header->last_comp_version = be32(16); /* as per spec, this field must be 16. */ 1086 | header->boot_cpu_id = be32(boot_cpu_id); 1087 | header->size_strings = be32(final_data.string_buf_size); 1088 | header->size_structs = be32(struct_buf_bytes); 1089 | 1090 | uint64_t* reserved_block = (uint64_t*)(header + 1); 1091 | for (size_t i = 0; i < resv_count; i++) 1092 | { 1093 | reserved_block[i * 2 + 0] = be64(resv[i].base); 1094 | reserved_block[i * 2 + 1] = be64(resv[i].length); 1095 | } 1096 | reserved_block[resv_count * 2 + 0] = 0; 1097 | reserved_block[resv_count * 2 + 1] = 0; 1098 | 1099 | final_data.struct_buf = (uint32_t*)((uintptr_t)buffer + be32(header->offset_structs)); 1100 | final_data.string_buf = (char*)((uintptr_t)buffer + be32(header->offset_strings)); 1101 | final_data.struct_ptr = 0; 1102 | final_data.string_ptr = 1; 1103 | final_data.string_buf[0] = 0; 1104 | 1105 | final_data.print_success = true; 1106 | do_foreach_sibling(state.root, print_node, &final_data); 1107 | return final_data.print_success ? total_bytes : SMOLDTB_FINALISE_FAILURE; 1108 | } 1109 | 1110 | dtb_node* dtb_find_or_create_node(const char* path) 1111 | { 1112 | if (path == NULL) 1113 | return NULL; 1114 | 1115 | size_t seg_len; 1116 | dtb_node* scan = state.root; 1117 | while (scan != NULL) 1118 | { 1119 | while (path[0] == '/') 1120 | path++; 1121 | 1122 | seg_len = string_find_char(path, '/'); 1123 | if (seg_len == -1ul) 1124 | seg_len = string_len(path); 1125 | if (seg_len == 0) 1126 | return scan; 1127 | 1128 | dtb_node* next = find_child_internal(scan, path, seg_len); 1129 | if (next == NULL) 1130 | next = dtb_create_child(scan, path); 1131 | scan = next; 1132 | } 1133 | 1134 | return NULL; 1135 | } 1136 | 1137 | dtb_prop* dtb_find_or_create_prop(dtb_node* node, const char* name) 1138 | { 1139 | if (node == NULL || name == NULL) 1140 | return NULL; 1141 | 1142 | dtb_prop* prop = dtb_find_prop(node, name); 1143 | if (prop == NULL) 1144 | prop = dtb_create_prop(node, name); 1145 | return prop; 1146 | } 1147 | 1148 | dtb_node* dtb_create_sibling(dtb_node* node, const char* name) 1149 | { 1150 | if (node == NULL || name == NULL || node->parent == NULL) /* creating siblings of root node is disallowed */ 1151 | return NULL; 1152 | 1153 | struct name_collision_check check_data; 1154 | check_data.collision = false; 1155 | check_data.name = name; 1156 | check_data.name_len = string_len(name); 1157 | if (string_find_char(name, '/') < check_data.name_len) 1158 | check_data.name_len = string_find_char(name, '/'); 1159 | 1160 | do_foreach_sibling(node->parent->child, check_sibling_name_collisions, &check_data); 1161 | if (check_data.collision) 1162 | { 1163 | LOG_ERROR("Failed to create node with duplicate name."); 1164 | return NULL; 1165 | } 1166 | 1167 | const size_t name_len = string_len(name); 1168 | char* name_buf = try_malloc(name_len + 1); 1169 | if (name_buf == NULL) 1170 | return NULL; 1171 | memcpy(name_buf, name, name_len); 1172 | 1173 | dtb_node* sibling = try_malloc(sizeof(dtb_node)); 1174 | if (sibling == NULL) 1175 | { 1176 | LOG_ERROR("Failed to allocate node for sibling."); 1177 | return NULL; 1178 | } 1179 | 1180 | sibling->name = name_buf; 1181 | sibling->parent = node->parent; 1182 | 1183 | sibling->fromMalloc = true; 1184 | sibling->sibling = node->sibling; 1185 | node->sibling = sibling; 1186 | return sibling; 1187 | } 1188 | 1189 | dtb_node* dtb_create_child(dtb_node* node, const char* name) 1190 | { 1191 | if (node == NULL || name == NULL) 1192 | return NULL; 1193 | 1194 | struct name_collision_check check_data; 1195 | check_data.collision = false; 1196 | check_data.name = name; 1197 | check_data.name_len = string_len(name); 1198 | if (string_find_char(name, '/') < check_data.name_len) 1199 | check_data.name_len = string_find_char(name, '/'); 1200 | 1201 | do_foreach_sibling(node->child, check_sibling_name_collisions, &check_data); 1202 | if (check_data.collision) 1203 | { 1204 | LOG_ERROR("Failed to create node with duplicate name."); 1205 | return NULL; 1206 | } 1207 | 1208 | const size_t name_len = string_len(name); 1209 | char* name_buf = try_malloc(name_len + 1); 1210 | if (name_buf == NULL) 1211 | return NULL; 1212 | memcpy(name_buf, name, name_len); 1213 | 1214 | dtb_node* child = try_malloc(sizeof(dtb_node)); 1215 | if (child == NULL) 1216 | { 1217 | LOG_ERROR("Failed to allocate node for child."); 1218 | return NULL; 1219 | } 1220 | 1221 | child->parent = node; 1222 | child->name = name_buf; 1223 | child->fromMalloc = true; 1224 | child->sibling = node->child; 1225 | node->child = child; 1226 | return child; 1227 | } 1228 | 1229 | dtb_prop* dtb_create_prop(dtb_node* node, const char* name) 1230 | { 1231 | if (node == NULL || name == NULL) 1232 | return NULL; 1233 | 1234 | const size_t name_len = string_len(name); 1235 | struct name_collision_check check_data; 1236 | check_data.collision = false; 1237 | check_data.name = name; 1238 | check_data.name_len = name_len; 1239 | 1240 | do_foreach_prop(node, check_prop_name_collisions, &check_data); 1241 | if (check_data.collision) 1242 | { 1243 | LOG_ERROR("Failed to create prop with duplicate name."); 1244 | return NULL; 1245 | } 1246 | 1247 | char* name_buf = try_malloc(name_len + 1); 1248 | if (name_buf == NULL) 1249 | return NULL; 1250 | memcpy(name_buf, name, name_len); 1251 | 1252 | dtb_prop* prop = try_malloc(sizeof(dtb_prop)); 1253 | if (prop == NULL) 1254 | { 1255 | LOG_ERROR("Failed to allocate property"); 1256 | return NULL; 1257 | } 1258 | 1259 | prop->length = 0; 1260 | prop->data = NULL; 1261 | prop->name = name_buf; 1262 | prop->fromMalloc = true; 1263 | prop->dataFromMalloc = false; 1264 | prop->next = node->props; 1265 | prop->node = node; 1266 | node->props = prop; 1267 | return prop; 1268 | } 1269 | 1270 | bool dtb_destroy_node(dtb_node* node) 1271 | { 1272 | if (node == NULL) 1273 | return false; 1274 | 1275 | if (node->parent != NULL) /* break linkage in parents list of child nodes */ 1276 | { 1277 | dtb_node* scan = node->parent->child; 1278 | if (scan == node) 1279 | { 1280 | scan = NULL; 1281 | node->parent->child = node->sibling; 1282 | } 1283 | 1284 | while (scan != NULL) 1285 | { 1286 | if (scan->name == NULL) 1287 | { 1288 | LOG_ERROR("Corrupt internal state: node not in parent's child list."); 1289 | return false; 1290 | } 1291 | if (scan->sibling != node) 1292 | { 1293 | scan = scan->sibling; 1294 | continue; 1295 | } 1296 | 1297 | scan->sibling = node->sibling; 1298 | break; 1299 | } 1300 | } 1301 | 1302 | node->parent = NULL; 1303 | destroy_dead_node(node); 1304 | return true; 1305 | } 1306 | 1307 | bool dtb_destroy_prop(dtb_prop* prop) 1308 | { 1309 | if (prop == NULL) 1310 | return false; 1311 | 1312 | dtb_prop* scan = prop->node->props; 1313 | if (scan == prop) 1314 | { 1315 | scan = NULL; 1316 | prop->node->props = prop->next; 1317 | } 1318 | 1319 | while (scan != NULL) 1320 | { 1321 | if (scan->next == NULL) 1322 | return false; 1323 | if (scan->next != prop) 1324 | { 1325 | scan = scan->next; 1326 | continue; 1327 | } 1328 | 1329 | scan->next = prop->next; 1330 | break; 1331 | } 1332 | 1333 | if (prop->dataFromMalloc) 1334 | try_free(prop->data, prop->length); 1335 | if (prop->fromMalloc) 1336 | try_free(prop, sizeof(dtb_prop)); 1337 | 1338 | return true; 1339 | } 1340 | 1341 | static bool ensure_prop_has_buffer_for(dtb_prop* prop, size_t buf_size) 1342 | { 1343 | if (prop == NULL) 1344 | return false; 1345 | 1346 | if (prop->dataFromMalloc && buf_size <= prop->length) 1347 | return true; 1348 | 1349 | void* new_data = try_malloc(buf_size); 1350 | if (new_data == NULL) 1351 | return false; 1352 | if (prop->dataFromMalloc) 1353 | try_free(prop->data, prop->length); 1354 | 1355 | prop->data = new_data; 1356 | prop->length = buf_size; 1357 | return true; 1358 | } 1359 | 1360 | bool dtb_write_prop_string(dtb_prop* prop, const char* str, size_t str_len) 1361 | { 1362 | if (prop == NULL) 1363 | return false; 1364 | 1365 | if (!ensure_prop_has_buffer_for(prop, str_len)) 1366 | return false; 1367 | 1368 | memcpy(prop->data, str, str_len); 1369 | return true; 1370 | } 1371 | 1372 | static bool copy_prop_buffer(dtb_prop* prop, size_t buf_cells, const uint32_t* buf) 1373 | { 1374 | if (prop == NULL) 1375 | return false; 1376 | if (buf == NULL && buf_cells != 0) 1377 | return false; 1378 | 1379 | if (!ensure_prop_has_buffer_for(prop, buf_cells * FDT_CELL_SIZE)) 1380 | return false; 1381 | 1382 | uint32_t* dest_cells = prop->data; 1383 | for (size_t i = 0; i < buf_cells; i++) 1384 | dest_cells[i] = be32(buf[i]); 1385 | 1386 | return true; 1387 | } 1388 | 1389 | bool dtb_write_prop_1(dtb_prop* prop, size_t count, size_t cell_count, const smoldtb_value* vals) 1390 | { 1391 | const size_t buf_cells = count * cell_count; 1392 | return copy_prop_buffer(prop, buf_cells, (const uint32_t*)vals); 1393 | } 1394 | 1395 | bool dtb_write_prop_2(dtb_prop* prop, size_t count, dtb_pair layout, const dtb_pair* vals) 1396 | { 1397 | const size_t buf_cells = count * (layout.a + layout.b); 1398 | return copy_prop_buffer(prop, buf_cells, (const uint32_t*)vals); 1399 | } 1400 | 1401 | bool dtb_write_prop_3(dtb_prop* prop, size_t count, dtb_triplet layout, const dtb_triplet* vals) 1402 | { 1403 | const size_t buf_cells = count * (layout.a + layout.b + layout.c); 1404 | return copy_prop_buffer(prop, buf_cells, (const uint32_t*)vals); 1405 | } 1406 | 1407 | bool dtb_write_prop_4(dtb_prop* prop, size_t count, dtb_quad layout, const dtb_quad* vals) 1408 | { 1409 | const size_t buf_cells = count * (layout.a + layout.b + layout.c + layout.d); 1410 | return copy_prop_buffer(prop, buf_cells, (const uint32_t*)vals); 1411 | } 1412 | #endif /* SMOLDTB_ENABLE_WRITE_API */ 1413 | -------------------------------------------------------------------------------- /smoldtb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define SMOLDTB_INIT_EMPTY_TREE 0 12 | 13 | #ifndef smoldtb_value 14 | #define smoldtb_value uintmax_t 15 | #endif 16 | 17 | typedef struct dtb_node_t dtb_node; 18 | typedef struct dtb_prop_t dtb_prop; 19 | 20 | typedef struct 21 | { 22 | smoldtb_value a; 23 | smoldtb_value b; 24 | } dtb_pair; 25 | 26 | typedef struct 27 | { 28 | smoldtb_value a; 29 | smoldtb_value b; 30 | smoldtb_value c; 31 | } dtb_triplet; 32 | 33 | typedef struct 34 | { 35 | smoldtb_value a; 36 | smoldtb_value b; 37 | smoldtb_value c; 38 | smoldtb_value d; 39 | } dtb_quad; 40 | 41 | typedef struct 42 | { 43 | void* (*malloc)(size_t length); 44 | void (*free)(void* ptr, size_t length); 45 | void (*on_error)(const char* why); 46 | } dtb_ops; 47 | 48 | typedef struct 49 | { 50 | const char* name; 51 | size_t child_count; 52 | size_t prop_count; 53 | size_t sibling_count; 54 | } dtb_node_stat; 55 | 56 | typedef struct 57 | { 58 | const char* name; 59 | const void* data; 60 | size_t data_len; 61 | } dtb_prop_stat; 62 | 63 | typedef struct 64 | { 65 | uint64_t base; 66 | uint64_t length; 67 | } dtb_reserved_memory; 68 | 69 | size_t dtb_query_total_size(uintptr_t fdt_start); 70 | 71 | bool dtb_init(uintptr_t start, dtb_ops ops); 72 | 73 | dtb_node* dtb_find_compatible(dtb_node* node, const char* str); 74 | dtb_node* dtb_find_phandle(unsigned handle); 75 | dtb_node* dtb_find(const char* path); 76 | dtb_node* dtb_find_child(dtb_node* node, const char* name); 77 | dtb_prop* dtb_find_prop(dtb_node* node, const char* name); 78 | 79 | dtb_node* dtb_get_sibling(dtb_node* node); 80 | dtb_node* dtb_get_child(dtb_node* node); 81 | dtb_node* dtb_get_parent(dtb_node* node); 82 | dtb_prop* dtb_get_prop(dtb_node* node, size_t index); 83 | size_t dtb_get_addr_cells_of(dtb_node* node); 84 | size_t dtb_get_size_cells_of(dtb_node* node); 85 | size_t dtb_get_addr_cells_for(dtb_node* node); 86 | size_t dtb_get_size_cells_for(dtb_node* node); 87 | 88 | bool dtb_is_compatible(dtb_node* node, const char* str); 89 | bool dtb_stat_node(dtb_node* node, dtb_node_stat* stat); 90 | bool dtb_stat_prop(dtb_prop* prop, dtb_prop_stat* stat); 91 | 92 | size_t dtb_read_resv_memory(size_t entry_count, dtb_reserved_memory* vals); 93 | const char* dtb_read_prop_string(dtb_prop* prop, size_t index); 94 | size_t dtb_read_prop_1(dtb_prop* prop, size_t cell_count, smoldtb_value* vals); 95 | size_t dtb_read_prop_2(dtb_prop* prop, dtb_pair layout, dtb_pair* vals); 96 | size_t dtb_read_prop_3(dtb_prop* prop, dtb_triplet layout, dtb_triplet* vals); 97 | size_t dtb_read_prop_4(dtb_prop* prop, dtb_quad layout, dtb_quad* vals); 98 | 99 | #ifdef SMOLDTB_ENABLE_WRITE_API 100 | 101 | #define SMOLDTB_FINALISE_FAILURE ((size_t)-1) 102 | 103 | size_t dtb_finalise_to_buffer(void* buffer, size_t buffer_size, uint32_t boot_cpu_id, dtb_reserved_memory* resv, size_t resv_count); 104 | 105 | dtb_node* dtb_find_or_create_node(const char* path); 106 | dtb_prop* dtb_find_or_create_prop(dtb_node* node, const char* name); 107 | dtb_node* dtb_create_sibling(dtb_node* node, const char* name); 108 | dtb_node* dtb_create_child(dtb_node* node, const char* name); 109 | dtb_prop* dtb_create_prop(dtb_node* node, const char* name); 110 | 111 | bool dtb_destroy_node(dtb_node* node); 112 | bool dtb_destroy_prop(dtb_prop* prop); 113 | 114 | bool dtb_write_prop_string(dtb_prop* prop, const char* str, size_t str_len); 115 | bool dtb_write_prop_1(dtb_prop* prop, size_t count, size_t cell_count, const smoldtb_value* vals); 116 | bool dtb_write_prop_2(dtb_prop* prop, size_t count, dtb_pair layout, const dtb_pair* vals); 117 | bool dtb_write_prop_3(dtb_prop* prop, size_t count, dtb_triplet layout, const dtb_triplet* vals); 118 | bool dtb_write_prop_4(dtb_prop* prop, size_t count, dtb_quad layout, const dtb_quad* vals); 119 | #endif 120 | 121 | #ifdef __cplusplus 122 | } 123 | #endif 124 | -------------------------------------------------------------------------------- /test-files/qemu-riscv64-virt-8.dtb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeanoBurrito/smoldtb/72d3f8596fb91244e646944e09dcd93e1be03140/test-files/qemu-riscv64-virt-8.dtb -------------------------------------------------------------------------------- /test-files/qemu-riscv64-virt-8.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | 3 | / { 4 | #address-cells = <0x02>; 5 | #size-cells = <0x02>; 6 | compatible = "riscv-virtio"; 7 | model = "riscv-virtio,qemu"; 8 | 9 | fw-cfg@10100000 { 10 | dma-coherent; 11 | reg = <0x00 0x10100000 0x00 0x18>; 12 | compatible = "qemu,fw-cfg-mmio"; 13 | }; 14 | 15 | flash@20000000 { 16 | bank-width = <0x04>; 17 | reg = <0x00 0x20000000 0x00 0x2000000 0x00 0x22000000 0x00 0x2000000>; 18 | compatible = "cfi-flash"; 19 | }; 20 | 21 | chosen { 22 | bootargs = [00]; 23 | stdout-path = "/soc/uart@10000000"; 24 | }; 25 | 26 | memory@80000000 { 27 | device_type = "memory"; 28 | reg = <0x00 0x80000000 0x00 0x20000000>; 29 | }; 30 | 31 | cpus { 32 | #address-cells = <0x01>; 33 | #size-cells = <0x00>; 34 | timebase-frequency = <0x989680>; 35 | 36 | cpu@0 { 37 | phandle = <0x07>; 38 | device_type = "cpu"; 39 | reg = <0x00>; 40 | status = "okay"; 41 | compatible = "riscv"; 42 | riscv,isa = "rv64imafdcsuh"; 43 | mmu-type = "riscv,sv48"; 44 | 45 | interrupt-controller { 46 | #interrupt-cells = <0x01>; 47 | interrupt-controller; 48 | compatible = "riscv,cpu-intc-aia\0riscv,cpu-intc"; 49 | phandle = <0x08>; 50 | }; 51 | }; 52 | 53 | cpu@1 { 54 | phandle = <0x05>; 55 | device_type = "cpu"; 56 | reg = <0x01>; 57 | status = "okay"; 58 | compatible = "riscv"; 59 | riscv,isa = "rv64imafdcsuh"; 60 | mmu-type = "riscv,sv48"; 61 | 62 | interrupt-controller { 63 | #interrupt-cells = <0x01>; 64 | interrupt-controller; 65 | compatible = "riscv,cpu-intc-aia\0riscv,cpu-intc"; 66 | phandle = <0x06>; 67 | }; 68 | }; 69 | 70 | cpu@2 { 71 | phandle = <0x03>; 72 | device_type = "cpu"; 73 | reg = <0x02>; 74 | status = "okay"; 75 | compatible = "riscv"; 76 | riscv,isa = "rv64imafdcsuh"; 77 | mmu-type = "riscv,sv48"; 78 | 79 | interrupt-controller { 80 | #interrupt-cells = <0x01>; 81 | interrupt-controller; 82 | compatible = "riscv,cpu-intc-aia\0riscv,cpu-intc"; 83 | phandle = <0x04>; 84 | }; 85 | }; 86 | 87 | cpu@3 { 88 | phandle = <0x01>; 89 | device_type = "cpu"; 90 | reg = <0x03>; 91 | status = "okay"; 92 | compatible = "riscv"; 93 | riscv,isa = "rv64imafdcsuh"; 94 | mmu-type = "riscv,sv48"; 95 | 96 | interrupt-controller { 97 | #interrupt-cells = <0x01>; 98 | interrupt-controller; 99 | compatible = "riscv,cpu-intc-aia\0riscv,cpu-intc"; 100 | phandle = <0x02>; 101 | }; 102 | }; 103 | 104 | cpu-map { 105 | 106 | cluster0 { 107 | 108 | core0 { 109 | cpu = <0x07>; 110 | }; 111 | 112 | core1 { 113 | cpu = <0x05>; 114 | }; 115 | 116 | core2 { 117 | cpu = <0x03>; 118 | }; 119 | 120 | core3 { 121 | cpu = <0x01>; 122 | }; 123 | }; 124 | }; 125 | }; 126 | 127 | soc { 128 | #address-cells = <0x02>; 129 | #size-cells = <0x02>; 130 | compatible = "simple-bus"; 131 | ranges; 132 | 133 | rtc@101000 { 134 | interrupts = <0x0b 0x04>; 135 | interrupt-parent = <0x0c>; 136 | reg = <0x00 0x101000 0x00 0x1000>; 137 | compatible = "google,goldfish-rtc"; 138 | }; 139 | 140 | uart@10000000 { 141 | interrupts = <0x0a 0x04>; 142 | interrupt-parent = <0x0c>; 143 | clock-frequency = "\08@"; 144 | reg = <0x00 0x10000000 0x00 0x100>; 145 | compatible = "ns16550a"; 146 | }; 147 | 148 | poweroff { 149 | value = <0x5555>; 150 | offset = <0x00>; 151 | regmap = <0x0d>; 152 | compatible = "syscon-poweroff"; 153 | }; 154 | 155 | reboot { 156 | value = <0x7777>; 157 | offset = <0x00>; 158 | regmap = <0x0d>; 159 | compatible = "syscon-reboot"; 160 | }; 161 | 162 | test@100000 { 163 | phandle = <0x0d>; 164 | reg = <0x00 0x100000 0x00 0x1000>; 165 | compatible = "sifive,test1\0sifive,test0\0syscon"; 166 | }; 167 | 168 | pci@30000000 { 169 | interrupt-map-mask = <0x1800 0x00 0x00 0x07>; 170 | interrupt-map = <0x00 0x00 0x00 0x01 0x0c 0x20 0x04 0x00 0x00 0x00 0x02 0x0c 0x21 0x04 0x00 0x00 0x00 0x03 0x0c 0x22 0x04 0x00 0x00 0x00 0x04 0x0c 0x23 0x04 0x800 0x00 0x00 0x01 0x0c 0x21 0x04 0x800 0x00 0x00 0x02 0x0c 0x22 0x04 0x800 0x00 0x00 0x03 0x0c 0x23 0x04 0x800 0x00 0x00 0x04 0x0c 0x20 0x04 0x1000 0x00 0x00 0x01 0x0c 0x22 0x04 0x1000 0x00 0x00 0x02 0x0c 0x23 0x04 0x1000 0x00 0x00 0x03 0x0c 0x20 0x04 0x1000 0x00 0x00 0x04 0x0c 0x21 0x04 0x1800 0x00 0x00 0x01 0x0c 0x23 0x04 0x1800 0x00 0x00 0x02 0x0c 0x20 0x04 0x1800 0x00 0x00 0x03 0x0c 0x21 0x04 0x1800 0x00 0x00 0x04 0x0c 0x22 0x04>; 171 | ranges = <0x1000000 0x00 0x00 0x00 0x3000000 0x00 0x10000 0x2000000 0x00 0x40000000 0x00 0x40000000 0x00 0x40000000 0x3000000 0x04 0x00 0x04 0x00 0x04 0x00>; 172 | reg = <0x00 0x30000000 0x00 0x10000000>; 173 | msi-parent = <0x0a>; 174 | dma-coherent; 175 | bus-range = <0x00 0xff>; 176 | linux,pci-domain = <0x00>; 177 | device_type = "pci"; 178 | compatible = "pci-host-ecam-generic"; 179 | #size-cells = <0x02>; 180 | #interrupt-cells = <0x01>; 181 | #address-cells = <0x03>; 182 | }; 183 | 184 | virtio_mmio@10008000 { 185 | interrupts = <0x08 0x04>; 186 | interrupt-parent = <0x0c>; 187 | reg = <0x00 0x10008000 0x00 0x1000>; 188 | compatible = "virtio,mmio"; 189 | }; 190 | 191 | virtio_mmio@10007000 { 192 | interrupts = <0x07 0x04>; 193 | interrupt-parent = <0x0c>; 194 | reg = <0x00 0x10007000 0x00 0x1000>; 195 | compatible = "virtio,mmio"; 196 | }; 197 | 198 | virtio_mmio@10006000 { 199 | interrupts = <0x06 0x04>; 200 | interrupt-parent = <0x0c>; 201 | reg = <0x00 0x10006000 0x00 0x1000>; 202 | compatible = "virtio,mmio"; 203 | }; 204 | 205 | virtio_mmio@10005000 { 206 | interrupts = <0x05 0x04>; 207 | interrupt-parent = <0x0c>; 208 | reg = <0x00 0x10005000 0x00 0x1000>; 209 | compatible = "virtio,mmio"; 210 | }; 211 | 212 | virtio_mmio@10004000 { 213 | interrupts = <0x04 0x04>; 214 | interrupt-parent = <0x0c>; 215 | reg = <0x00 0x10004000 0x00 0x1000>; 216 | compatible = "virtio,mmio"; 217 | }; 218 | 219 | virtio_mmio@10003000 { 220 | interrupts = <0x03 0x04>; 221 | interrupt-parent = <0x0c>; 222 | reg = <0x00 0x10003000 0x00 0x1000>; 223 | compatible = "virtio,mmio"; 224 | }; 225 | 226 | virtio_mmio@10002000 { 227 | interrupts = <0x02 0x04>; 228 | interrupt-parent = <0x0c>; 229 | reg = <0x00 0x10002000 0x00 0x1000>; 230 | compatible = "virtio,mmio"; 231 | }; 232 | 233 | virtio_mmio@10001000 { 234 | interrupts = <0x01 0x04>; 235 | interrupt-parent = <0x0c>; 236 | reg = <0x00 0x10001000 0x00 0x1000>; 237 | compatible = "virtio,mmio"; 238 | }; 239 | 240 | aplic@d000000 { 241 | phandle = <0x0c>; 242 | riscv,num-sources = <0x35>; 243 | reg = <0x00 0xd000000 0x00 0x8000>; 244 | msi-parent = <0x0a>; 245 | interrupt-controller; 246 | #interrupt-cells = <0x02>; 247 | compatible = "riscv,aplic"; 248 | }; 249 | 250 | aplic@c000000 { 251 | phandle = <0x0b>; 252 | riscv,delegate = <0x0c 0x01 0x35>; 253 | riscv,children = <0x0c>; 254 | riscv,num-sources = <0x35>; 255 | reg = <0x00 0xc000000 0x00 0x8000>; 256 | msi-parent = <0x09>; 257 | interrupt-controller; 258 | #interrupt-cells = <0x02>; 259 | compatible = "riscv,aplic"; 260 | }; 261 | 262 | imsics@28000000 { 263 | phandle = <0x0a>; 264 | riscv,ipi-id = <0x01>; 265 | riscv,num-ids = <0xff>; 266 | reg = <0x00 0x28000000 0x00 0x4000>; 267 | interrupts-extended = <0x08 0x09 0x06 0x09 0x04 0x09 0x02 0x09>; 268 | msi-controller; 269 | interrupt-controller; 270 | #interrupt-cells = <0x00>; 271 | compatible = "riscv,imsics"; 272 | }; 273 | 274 | imsics@24000000 { 275 | phandle = <0x09>; 276 | riscv,ipi-id = <0x01>; 277 | riscv,num-ids = <0xff>; 278 | reg = <0x00 0x24000000 0x00 0x4000>; 279 | interrupts-extended = <0x08 0x0b 0x06 0x0b 0x04 0x0b 0x02 0x0b>; 280 | msi-controller; 281 | interrupt-controller; 282 | #interrupt-cells = <0x00>; 283 | compatible = "riscv,imsics"; 284 | }; 285 | 286 | clint@2000000 { 287 | interrupts-extended = <0x08 0x03 0x08 0x07 0x06 0x03 0x06 0x07 0x04 0x03 0x04 0x07 0x02 0x03 0x02 0x07>; 288 | reg = <0x00 0x2000000 0x00 0x10000>; 289 | compatible = "sifive,clint0\0riscv,clint0"; 290 | }; 291 | }; 292 | }; 293 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "smoldtb.h" 8 | 9 | size_t total_errors = 0; 10 | size_t total_mallocs = 0; 11 | size_t total_frees = 0; 12 | size_t current_memory_usage = 0; 13 | 14 | void dtb_on_error(const char* why) 15 | { 16 | printf("smoldtb error %zu: %s\r\n", total_errors, why); 17 | total_errors++; 18 | } 19 | 20 | void* dtb_malloc(size_t length) 21 | { 22 | current_memory_usage += length; 23 | printf("smoldtb malloc %zu: %zu (%zuB in use)\r\n", total_mallocs++, length, current_memory_usage); 24 | return malloc(length); 25 | } 26 | 27 | void dtb_free(void* ptr, size_t length) 28 | { 29 | (void)length; 30 | 31 | current_memory_usage -= length; 32 | printf("smoldtb free %zu: %zu (%zuB in use)\r\n", total_frees++, length, current_memory_usage); 33 | free(ptr); 34 | } 35 | 36 | static const char tree_corner = '\\'; 37 | static const char tree_cross = '+'; 38 | static const char tree_bar = '|'; 39 | static const char tree_space = ' '; 40 | 41 | static void print_node(dtb_node* node, char* indent_buff, int indent, bool is_last) 42 | { 43 | if (node == NULL) 44 | return; 45 | 46 | printf("%.*s", indent, indent_buff); 47 | if (is_last) 48 | { 49 | printf("%c", tree_corner); 50 | indent_buff[indent++] = tree_space; 51 | indent_buff[indent++] = ' '; 52 | } 53 | else 54 | { 55 | printf("%c", tree_cross); 56 | indent_buff[indent++] = tree_bar; 57 | indent_buff[indent++] = ' '; 58 | } 59 | 60 | dtb_node_stat stat; 61 | if (dtb_stat_node(node, &stat)) 62 | { 63 | printf("%s: %zu siblings, %zu children, %zu properties.\r\n", stat.name, 64 | stat.sibling_count, stat.child_count, stat.prop_count); 65 | } 66 | else 67 | printf("\r\n"); 68 | 69 | for (size_t i = 0; i < stat.prop_count; i++) 70 | { 71 | dtb_prop* prop = dtb_get_prop(node, i); 72 | if (prop == NULL) 73 | break; 74 | 75 | dtb_prop_stat pstat; 76 | if (dtb_stat_prop(prop, &pstat)) 77 | printf("%.*s %s: %zu bytes\r\n", indent, indent_buff, pstat.name, pstat.data_len); 78 | else 79 | printf("%.*s \r\n", indent, indent_buff); 80 | } 81 | 82 | dtb_node* child = dtb_get_child(node); 83 | while (child != NULL) 84 | { 85 | dtb_node* next = dtb_get_sibling(child); 86 | print_node(child, indent_buff, indent, next == NULL); 87 | child = next; 88 | } 89 | } 90 | 91 | static void print_file(const char* filename) 92 | { 93 | int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); 94 | if (fd == -1) 95 | { 96 | printf("Could not open output file %s\r\n", filename); 97 | return; 98 | } 99 | 100 | const size_t out_len = dtb_finalise_to_buffer(NULL, 0, 0, NULL, 0); 101 | if (ftruncate(fd, out_len) != 0) 102 | { 103 | printf("ftruncate failed.\r\n"); 104 | return; 105 | } 106 | 107 | void* buffer = mmap(NULL, out_len, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0); 108 | if (buffer == NULL) 109 | { 110 | printf("mmap() failed.\r\n"); 111 | return; 112 | } 113 | 114 | if (dtb_finalise_to_buffer(buffer, out_len, 0, NULL, 0) == SMOLDTB_FINALISE_FAILURE) 115 | printf("smoltdb reports finalise failure\r\n"); 116 | 117 | munmap(buffer, out_len); 118 | close(fd); 119 | 120 | printf("finalized in-memory dtb to file: %s\r\n", filename); 121 | } 122 | 123 | static void display_file(const char* filename, const char* output_filename) 124 | { 125 | int fd = open(filename, O_RDONLY); 126 | if (fd == -1) 127 | { 128 | printf("Could not open file %s\r\n", filename); 129 | return; 130 | } 131 | 132 | struct stat sb; 133 | if (fstat(fd, &sb) == -1) 134 | { 135 | printf("Could not stat file %s\r\n", filename); 136 | return; 137 | } 138 | 139 | void* buffer = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 140 | if (buffer == NULL) 141 | { 142 | printf("mmap() failed\r\n"); 143 | return; 144 | } 145 | 146 | dtb_ops ops; 147 | ops.malloc = dtb_malloc; 148 | ops.free = dtb_free; 149 | ops.on_error = dtb_on_error; 150 | dtb_init((uintptr_t)buffer, ops); 151 | 152 | char indent_buffer[256]; //256 levels of indentation should be enough for anyone. 153 | 154 | dtb_node* root = dtb_find("/"); 155 | while (root != NULL) 156 | { 157 | print_node(root, indent_buffer, 0, true); 158 | root = dtb_get_sibling(root); 159 | } 160 | 161 | if (output_filename != NULL) 162 | print_file(output_filename); 163 | 164 | munmap(buffer, sb.st_size); 165 | close(fd); 166 | } 167 | 168 | void show_usage() 169 | { 170 | printf("Usage: \n\ 171 | readfdt [output_filename] \n\ 172 | \n\ 173 | This program will parse a flattened device tree/device tree blob and \n\ 174 | output a summary of it's contents. \n\ 175 | If [output_filename] is provided, smoldtb will print it's internal representation \n\ 176 | of the device tree to the specified file in the FDT format. \n\ 177 | The intended purpose of this program is for testing smoldtb library code. \n\ 178 | "); 179 | } 180 | 181 | int main(int argc, char** argv) 182 | { 183 | if (argc != 2 && argc != 3) 184 | { 185 | show_usage(); 186 | return 0; 187 | } 188 | 189 | const char* output_filename = NULL; 190 | if (argc == 3) 191 | output_filename = argv[2]; 192 | 193 | display_file(argv[1], output_filename); 194 | return 0; 195 | } 196 | 197 | --------------------------------------------------------------------------------