| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * treegen - generate test device tree blobs |
| * |
| * Build example device tree blobs for test. Note that some of the generated |
| * blobs are deliberately malformed, in order to test error conditions. |
| */ |
| #include <assert.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| |
| #include <libfdt_env.h> |
| #include <fdt.h> |
| |
| #include "tests.h" |
| #include "testdata.h" |
| |
| /* |
| * Buffer with write cursor. Emit helpers write at the cursor, advance it, and |
| * return the offset at which data was written. |
| */ |
| struct buf { |
| char *data; |
| size_t size, cursor; |
| }; |
| |
| #define CHUNKSIZE 1024 |
| |
| static struct buf buf_init(void) |
| { |
| struct buf b = { NULL, 0, 0 }; |
| |
| return b; |
| } |
| |
| /* Low-level helpers */ |
| |
| static size_t emit_bytes(struct buf *b, const void *data, int len) |
| { |
| size_t off = b->cursor; |
| |
| if (!len) |
| return off; |
| |
| if (b->cursor + len > b->size) { |
| size_t newsize = b->cursor + len + CHUNKSIZE; |
| |
| b->data = xrealloc(b->data, newsize); |
| memset(b->data + b->size, 0, newsize - b->size); |
| b->size = newsize; |
| } |
| memcpy(b->data + b->cursor, data, len); |
| b->cursor += len; |
| return off; |
| } |
| |
| static size_t emit_u32(struct buf *b, uint32_t val) |
| { |
| fdt32_t beval = cpu_to_fdt32(val); |
| |
| return emit_bytes(b, &beval, sizeof(beval)); |
| } |
| |
| static size_t emit_u64(struct buf *b, uint64_t val) |
| { |
| fdt64_t beval = cpu_to_fdt64(val); |
| |
| return emit_bytes(b, &beval, sizeof(beval)); |
| } |
| |
| static size_t emit_string(struct buf *b, const char *str) |
| { |
| return emit_bytes(b, str, strlen(str) + 1); |
| } |
| |
| #define MAX_ALIGNMENT 8 |
| |
| static size_t emit_align(struct buf *b, size_t alignment) |
| { |
| size_t pad = ((b->cursor + alignment - 1) & ~(alignment - 1)) - b->cursor; |
| static const char zeros[MAX_ALIGNMENT]; |
| |
| assert(alignment <= MAX_ALIGNMENT); |
| assert(pad < alignment); |
| |
| return emit_bytes(b, zeros, pad); |
| } |
| |
| static size_t start_block(struct buf *b) |
| { |
| return b->cursor; |
| } |
| |
| static void fill_prop_name(struct buf *b, size_t strs, size_t prop, size_t stroff) |
| { |
| struct fdt_property *fp = (struct fdt_property *)(b->data + prop); |
| |
| fp->nameoff = cpu_to_fdt32(stroff - strs); |
| } |
| |
| /* FDT structure helpers */ |
| |
| static size_t emit_fdt_header(struct buf *b) |
| { |
| size_t off = emit_u32(b, FDT_MAGIC); |
| |
| emit_u32(b, 0); /* totalsize */ |
| emit_u32(b, 0); /* off_dt_struct */ |
| emit_u32(b, 0); /* off_dt_strings */ |
| emit_u32(b, 0); /* off_mem_rsvmap */ |
| emit_u32(b, 0x11); /* version */ |
| emit_u32(b, 0x10); /* last_comp_version */ |
| emit_u32(b, 0); /* boot_cpuid_phys */ |
| emit_u32(b, 0); /* size_dt_strings */ |
| emit_u32(b, 0); /* size_dt_struct */ |
| return off; |
| } |
| |
| static void finish_rsvmap(struct buf *b, size_t hdr, size_t rsvmap) |
| { |
| struct fdt_header *fh = (struct fdt_header *)(b->data + hdr); |
| |
| fh->off_mem_rsvmap = cpu_to_fdt32(rsvmap - hdr); |
| } |
| |
| static void finish_struct_block(struct buf *b, size_t hdr, size_t start) |
| { |
| struct fdt_header *fh = (struct fdt_header *)(b->data + hdr); |
| |
| fh->off_dt_struct = cpu_to_fdt32(start - hdr); |
| fh->size_dt_struct = cpu_to_fdt32(b->cursor - start); |
| } |
| |
| static void finish_strings_block(struct buf *b, size_t hdr, size_t start) |
| { |
| struct fdt_header *fh = (struct fdt_header *)(b->data + hdr); |
| |
| fh->off_dt_strings = cpu_to_fdt32(start - hdr); |
| fh->size_dt_strings = cpu_to_fdt32(b->cursor - start); |
| } |
| |
| static void finish_totalsize(struct buf *b, size_t hdr) |
| { |
| struct fdt_header *fh = (struct fdt_header *)(b->data + hdr); |
| |
| fh->totalsize = cpu_to_fdt32(b->cursor - hdr); |
| } |
| |
| static size_t emit_rsvmap_entry(struct buf *b, uint64_t addr, uint64_t size) |
| { |
| size_t off = emit_u64(b, addr); |
| |
| emit_u64(b, size); |
| return off; |
| } |
| |
| static size_t emit_rsvmap_term(struct buf *b) |
| { |
| return emit_rsvmap_entry(b, 0, 0); |
| } |
| |
| static void emit_rsvmap_empty(struct buf *b, size_t hdr) |
| { |
| size_t rsvmap = start_block(b); |
| |
| emit_rsvmap_term(b); |
| finish_rsvmap(b, hdr, rsvmap); |
| } |
| |
| static size_t emit_begin_node(struct buf *b, const char *name) |
| { |
| size_t off = emit_u32(b, FDT_BEGIN_NODE); |
| |
| emit_string(b, name); |
| emit_align(b, FDT_TAGSIZE); |
| return off; |
| } |
| |
| static size_t emit_end_node(struct buf *b) |
| { |
| return emit_u32(b, FDT_END_NODE); |
| } |
| |
| static size_t emit_fdt_end(struct buf *b) |
| { |
| return emit_u32(b, FDT_END); |
| } |
| |
| static size_t emit_prop_header(struct buf *b, int nameoff, int len) |
| { |
| size_t off = emit_u32(b, FDT_PROP); |
| |
| emit_u32(b, len); |
| emit_u32(b, nameoff); |
| return off; |
| } |
| |
| static size_t emit_prop_nil(struct buf *b, int nameoff) |
| { |
| return emit_prop_header(b, nameoff, 0); |
| } |
| |
| static size_t emit_prop_u32(struct buf *b, int nameoff, uint32_t val) |
| { |
| size_t off = emit_prop_header(b, nameoff, 4); |
| |
| emit_u32(b, val); |
| return off; |
| } |
| |
| static size_t emit_prop_u64(struct buf *b, int nameoff, uint64_t val) |
| { |
| size_t off = emit_prop_header(b, nameoff, 8); |
| |
| emit_u64(b, val); |
| return off; |
| } |
| |
| static size_t emit_prop_str(struct buf *b, int nameoff, const char *str) |
| { |
| int len = strlen(str) + 1; |
| size_t off = emit_prop_header(b, nameoff, len); |
| |
| emit_bytes(b, str, len); |
| emit_align(b, FDT_TAGSIZE); |
| return off; |
| } |
| |
| #define emit_prop_strings(b, nameoff, ...) \ |
| emit_prop_strings_(b, nameoff, __VA_ARGS__, NULL) |
| |
| static size_t emit_prop_strings_(struct buf *b, int nameoff, ...) |
| { |
| size_t off = emit_prop_header(b, nameoff, 0); |
| size_t start = b->cursor; |
| va_list ap; |
| const char *s; |
| |
| va_start(ap, nameoff); |
| while ((s = va_arg(ap, const char *)) != NULL) |
| emit_string(b, s); |
| va_end(ap); |
| |
| ((struct fdt_property *)(b->data + off))->len = |
| cpu_to_fdt32(b->cursor - start); |
| emit_align(b, FDT_TAGSIZE); |
| return off; |
| } |
| |
| /* |
| * Tree generators |
| * |
| * Each function builds a complete DTB in a buffer. |
| */ |
| |
| static struct buf make_test_tree1(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| size_t p_compatible, p_prop_int, p_prop_int64, p_prop_str, |
| p_address_cells, p_size_cells; |
| size_t p_s1_compatible, p_s1_reg, p_s1_prop_int; |
| size_t p_s1ss_compatible, p_s1ss_placeholder, p_s1ss_prop_int; |
| size_t p_s2_reg, p_s2_linux_phandle, p_s2_prop_int, |
| p_s2_address_cells, p_s2_size_cells; |
| size_t p_s2ss0_reg, p_s2ss0_phandle, p_s2ss0_compatible, |
| p_s2ss0_prop_int; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| { |
| size_t rsvmap = start_block(&b); |
| |
| emit_rsvmap_entry(&b, TEST_ADDR_1, TEST_SIZE_1); |
| emit_rsvmap_entry(&b, TEST_ADDR_2, TEST_SIZE_2); |
| emit_rsvmap_term(&b); |
| finish_rsvmap(&b, hdr, rsvmap); |
| } |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| p_compatible = emit_prop_str(&b, 0, "test_tree1"); |
| p_prop_int = emit_prop_u32(&b, 0, TEST_VALUE_1); |
| p_prop_int64 = emit_prop_u64(&b, 0, TEST_VALUE64_1); |
| p_prop_str = emit_prop_str(&b, 0, TEST_STRING_1); |
| p_address_cells = emit_prop_u32(&b, 0, 1); |
| p_size_cells = emit_prop_u32(&b, 0, 0); |
| |
| emit_begin_node(&b, "subnode@1"); |
| p_s1_compatible = emit_prop_str(&b, 0, "subnode1"); |
| p_s1_reg = emit_prop_u32(&b, 0, 1); |
| p_s1_prop_int = emit_prop_u32(&b, 0, TEST_VALUE_1); |
| |
| emit_begin_node(&b, "subsubnode"); |
| p_s1ss_compatible = emit_prop_strings(&b, 0, |
| "subsubnode1", "subsubnode"); |
| p_s1ss_placeholder = emit_prop_strings(&b, 0, |
| "this is a placeholder string", |
| "string2"); |
| p_s1ss_prop_int = emit_prop_u32(&b, 0, TEST_VALUE_1); |
| emit_end_node(&b); |
| |
| emit_begin_node(&b, "ss1"); |
| emit_end_node(&b); |
| |
| emit_end_node(&b); |
| |
| emit_begin_node(&b, "subnode@2"); |
| p_s2_reg = emit_prop_u32(&b, 0, 2); |
| p_s2_linux_phandle = emit_prop_u32(&b, 0, PHANDLE_1); |
| p_s2_prop_int = emit_prop_u32(&b, 0, TEST_VALUE_2); |
| p_s2_address_cells = emit_prop_u32(&b, 0, 1); |
| p_s2_size_cells = emit_prop_u32(&b, 0, 0); |
| |
| emit_begin_node(&b, "subsubnode@0"); |
| p_s2ss0_reg = emit_prop_u32(&b, 0, 0); |
| p_s2ss0_phandle = emit_prop_u32(&b, 0, PHANDLE_2); |
| p_s2ss0_compatible = emit_prop_strings(&b, 0, |
| "subsubnode2", "subsubnode"); |
| p_s2ss0_prop_int = emit_prop_u32(&b, 0, TEST_VALUE_2); |
| emit_end_node(&b); |
| |
| emit_begin_node(&b, "ss2"); |
| emit_end_node(&b); |
| |
| emit_end_node(&b); |
| |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| { |
| size_t strs = start_block(&b); |
| size_t s; |
| |
| s = emit_string(&b, "compatible"); |
| fill_prop_name(&b, strs, p_compatible, s); |
| fill_prop_name(&b, strs, p_s1_compatible, s); |
| fill_prop_name(&b, strs, p_s1ss_compatible, s); |
| fill_prop_name(&b, strs, p_s2ss0_compatible, s); |
| |
| s = emit_string(&b, "prop-int"); |
| fill_prop_name(&b, strs, p_prop_int, s); |
| fill_prop_name(&b, strs, p_s1_prop_int, s); |
| fill_prop_name(&b, strs, p_s1ss_prop_int, s); |
| fill_prop_name(&b, strs, p_s2_prop_int, s); |
| fill_prop_name(&b, strs, p_s2ss0_prop_int, s); |
| |
| fill_prop_name(&b, strs, p_prop_int64, |
| emit_string(&b, "prop-int64")); |
| |
| fill_prop_name(&b, strs, p_prop_str, |
| emit_string(&b, "prop-str")); |
| |
| fill_prop_name(&b, strs, p_s2_linux_phandle, |
| emit_string(&b, "linux,phandle")); |
| |
| fill_prop_name(&b, strs, p_s2ss0_phandle, |
| emit_string(&b, "phandle")); |
| |
| s = emit_string(&b, "reg"); |
| fill_prop_name(&b, strs, p_s1_reg, s); |
| fill_prop_name(&b, strs, p_s2_reg, s); |
| fill_prop_name(&b, strs, p_s2ss0_reg, s); |
| |
| fill_prop_name(&b, strs, p_s1ss_placeholder, |
| emit_string(&b, "placeholder")); |
| |
| s = emit_string(&b, "#address-cells"); |
| fill_prop_name(&b, strs, p_address_cells, s); |
| fill_prop_name(&b, strs, p_s2_address_cells, s); |
| |
| s = emit_string(&b, "#size-cells"); |
| fill_prop_name(&b, strs, p_size_cells, s); |
| fill_prop_name(&b, strs, p_s2_size_cells, s); |
| |
| finish_strings_block(&b, hdr, strs); |
| } |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_truncated_property(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| size_t p; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| p = emit_prop_header(&b, 0, 4); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| { |
| size_t strs = start_block(&b); |
| |
| emit_string(&b, "truncated"); |
| fill_prop_name(&b, strs, p, strs); |
| finish_strings_block(&b, hdr, strs); |
| } |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_bad_node_char(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| emit_begin_node(&b, "sub$node"); |
| emit_end_node(&b); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| finish_strings_block(&b, hdr, start_block(&b)); |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_bad_node_format(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| emit_begin_node(&b, "subnode@1@2"); |
| emit_end_node(&b); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| finish_strings_block(&b, hdr, start_block(&b)); |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_bad_prop_char(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| size_t p; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| p = emit_prop_u32(&b, 0, TEST_VALUE_1); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| { |
| size_t strs = start_block(&b); |
| |
| fill_prop_name(&b, strs, p, |
| emit_string(&b, "prop$erty")); |
| finish_strings_block(&b, hdr, strs); |
| } |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_ovf_size_strings(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| emit_prop_u32(&b, 0x10000000, 0); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| { |
| size_t strs = start_block(&b); |
| |
| emit_string(&b, "x"); |
| finish_strings_block(&b, hdr, strs); |
| } |
| |
| ((struct fdt_header *)(b.data + hdr))->size_dt_strings = |
| cpu_to_fdt32(0xffffffff); |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_truncated_string(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| size_t p_good, p_bad; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| p_good = emit_prop_nil(&b, 0); |
| p_bad = emit_prop_nil(&b, 0); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| { |
| size_t strs = start_block(&b); |
| |
| fill_prop_name(&b, strs, p_good, emit_string(&b, "good")); |
| fill_prop_name(&b, strs, p_bad, emit_bytes(&b, "bad", 3)); |
| finish_strings_block(&b, hdr, strs); |
| } |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_truncated_memrsv(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| |
| hdr = emit_fdt_header(&b); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| finish_strings_block(&b, hdr, start_block(&b)); |
| |
| { |
| size_t rsvmap; |
| |
| emit_align(&b, 8); |
| rsvmap = start_block(&b); |
| emit_rsvmap_entry(&b, TEST_ADDR_1, TEST_SIZE_1); |
| finish_rsvmap(&b, hdr, rsvmap); |
| } |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_unterminated_memrsv(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| |
| hdr = emit_fdt_header(&b); |
| |
| { |
| size_t rsvmap; |
| |
| emit_align(&b, 8); |
| rsvmap = start_block(&b); |
| emit_rsvmap_entry(&b, TEST_ADDR_1, TEST_SIZE_1); |
| finish_rsvmap(&b, hdr, rsvmap); |
| } |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| finish_strings_block(&b, hdr, start_block(&b)); |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_two_roots(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, ""); |
| emit_end_node(&b); |
| emit_begin_node(&b, ""); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| finish_strings_block(&b, hdr, start_block(&b)); |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| static struct buf make_named_root(void) |
| { |
| struct buf b = buf_init(); |
| size_t hdr; |
| |
| hdr = emit_fdt_header(&b); |
| emit_align(&b, 8); |
| |
| emit_rsvmap_empty(&b, hdr); |
| |
| { |
| size_t ss = start_block(&b); |
| |
| emit_begin_node(&b, "fake"); |
| emit_end_node(&b); |
| emit_fdt_end(&b); |
| finish_struct_block(&b, hdr, ss); |
| } |
| |
| finish_strings_block(&b, hdr, start_block(&b)); |
| |
| finish_totalsize(&b, hdr); |
| |
| return b; |
| } |
| |
| /* Tree table and main */ |
| |
| static struct { |
| struct buf (*generator)(void); |
| const char *filename; |
| } trees[] = { |
| #define TREE(name) { make_##name, #name ".dtb" } |
| TREE(test_tree1), |
| TREE(bad_node_char), TREE(bad_node_format), TREE(bad_prop_char), |
| TREE(ovf_size_strings), |
| TREE(truncated_property), TREE(truncated_string), |
| TREE(truncated_memrsv), |
| TREE(unterminated_memrsv), |
| TREE(two_roots), |
| TREE(named_root), |
| }; |
| |
| int main(int argc, char *argv[]) |
| { |
| unsigned int i; |
| |
| if (argc != 2) { |
| fprintf(stderr, "Usage: %s <output-dir>\n", argv[0]); |
| return 1; |
| } |
| |
| if (chdir(argv[1]) != 0) { |
| perror("chdir()"); |
| return 1; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(trees); i++) { |
| struct buf b = trees[i].generator(); |
| const char *filename = trees[i].filename; |
| int fd; |
| ssize_t ret; |
| |
| printf("Tree \"%s\", %zu bytes\n", filename, b.cursor); |
| |
| fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); |
| if (fd < 0) |
| perror("open()"); |
| |
| ret = write(fd, b.data, b.cursor); |
| if (ret < 0 || (size_t)ret != b.cursor) |
| perror("write()"); |
| |
| close(fd); |
| free(b.data); |
| } |
| |
| return 0; |
| } |