/* * Copyright (C) 2014-2015 Apple Computer, Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ #include #include #include #include #include #include #include #include "devicetree_private.h" /* * Device Tree Parsing Library * * This library implements the device tree serializer/updater/deserializer. * * There should be more stuff here, but for now a quick note. * * The dt_size variable contains the size the device tree will have upon * serialization. Any modification to the deserialized device tree must * update this variable so that it continues to contain the size the device * tree will have on re-serialization. This is because other modules need * to know the size of the device tree in order to properly lay out memory * (for example, in order to allocate a region of the kernel's memory map) */ static void free_node(dt_node_t *node); void dt_free(dt_node_t *dt); static size_t dt_size; static dt_node_t *dt_root; static void *dt_data; static bool dt_sealed; #define ROUND_SIZE(s) (((s) + 3) & ~3) void dt_init(void) { dt_size = 0; if (dt_root != NULL) { dt_free(dt_root); dt_root = NULL; } dt_data = NULL; dt_sealed = false; } size_t dt_get_size(void) { return dt_size; } static void free_node(dt_node_t *node) { if (node->props != NULL) { for (uint32_t i = 0; i < node->nprops; i++) { if (node->props[i].name_malloced) free(node->props[i].name); if (node->props[i].data_malloced) free(node->props[i].data); } free(node->props); node->props = NULL; } if (node->children != NULL) { for (uint32_t i = 0; i < node->nchildren; i++) { free_node(&node->children[i]); } free(node->children); node->children = NULL; } } /* Replaces placeholder values during deserialization. The placeholders are normal strings, * but the size field has bit 31 set to indicate it's a placeholder. If iBoot successfully * finds the requested data, it replaces the placeholder value with the requested data * (and sets bit 31 of the size back to 0). If iBoot doesn't find the requested data, * it removes the property from the device tree. * * Two kinds of placeholders are currently supported: * syscfg/ - looks up the key 'SKey' in syscfg * syscfg// - looks up the key 'SKey' in syscfg, and pads/truncates to specified * length (for compatibility with old-style devicetree placeholders) * macaddr/ - looks up the mac address envname in the environment using env_get_ethaddr * zeroes/ - creates a property with bytes zero bytes * * Multiple placeholders can be specified, with options separated by commas. The first * placeholder found is used. */ static bool expand_placeholder_prop(dtprop_t *prop) { uint32_t alloc_size; uint8_t ethaddr[6]; uint8_t dummy = 0; void *data = NULL; uint32_t data_size = 0; uint32_t data_pad_size = 0; char *placeholder; char *next_placeholder; // make sure placeholder is null-terminated if (memchr(prop->data, '\0', prop->size) == NULL) { panic("devicetree placeholder property '%s' missing null terminator", prop->name); } placeholder = prop->data; next_placeholder = placeholder; while ((placeholder = strsep(&next_placeholder, ",")) != NULL) { if (memcmp(placeholder, "syscfg/", 7) == 0) { char *details = placeholder + 7; uint8_t *tag_str; char *len_str; uint32_t len; uint32_t tag; tag_str = (uint8_t *)details; len_str = details; strsep(&len_str, "/"); if (strlen(details) != 4) { panic("dt: wrong length on placeholder %s", placeholder); } tag = tag_str[0] << 24 | tag_str[1] << 16 | tag_str[2] << 8 | tag_str[3]; if (syscfg_find_tag(tag, &data, &data_size)) { // For compatibility with old-style devicetree placeholders, a length // can be specified to truncate/pad the syscfg data to if (len_str != NULL) { len = (uint32_t)strtoul(len_str, NULL, 0); if (len > data_size) { data_pad_size = len - data_size; } else { data_size = len; } } break; } } else if (memcmp(placeholder, "macaddr/", 8) == 0) { char *details = placeholder + 8; if (env_get_ethaddr(details, ethaddr) == 0) { data = ethaddr; data_size = sizeof(ethaddr); break; } } else if (memcmp(placeholder, "zeroes/", 7) == 0) { char *details = placeholder + 7; // Set data to a dummy pointer so that data != NULL but request // copying 0 bytes. The padding will all get zeroed, resulting // in a property value with the requested number of zeroes data = &dummy; data_size = 0; data_pad_size = (uint32_t)strtoul(details, NULL, 0); } placeholder = next_placeholder; } uint32_t original_size = ROUND_SIZE(prop->size); if (data != NULL) { alloc_size = ROUND_SIZE(data_size + data_pad_size); // Since this function is called during initial parsing, we know // that the storage for the placeholder was in the original devicetree // buffer and not malloced in a new buffer. We only need a new buffer // if the new data is longer than the placeholder if (alloc_size > original_size) { prop->data = malloc(alloc_size); prop->data_malloced = true; } memcpy(prop->data, data, data_size); // zero-fill any padding bytes memset((uint8_t *)prop->data + data_size, 0, alloc_size - data_size); prop->size = data_size + data_pad_size; return true; } else { return false; } } static bool parse_node(dt_node_t *node, uint8_t **addrptr, const uint8_t *endaddr) { uint8_t *addr = *addrptr; if ((size_t)(endaddr - addr) < 2 * sizeof(uint32_t)) goto error; memcpy(&node->nprops, addr, 4); memcpy(&node->nchildren, addr + 4, 4); addr += 2 * sizeof(uint32_t); dt_size += 2 * sizeof(uint32_t); // Sanity checks if (node->nprops > 1024) { dprintf(DEBUG_INFO, "dt: too many properties\n"); goto error; } if (node->nchildren > 1024) { dprintf(DEBUG_INFO, "dt: too many children\n"); goto error; } if (node->nprops > 0) node->props = calloc(sizeof(*node->props), node->nprops); if (node->nchildren > 0) node->children = calloc(sizeof(*node->children), node->nchildren); // Parse each property, recording its size, the address of the name, // and the address of its data. // We could find the "name" property and cache it here, but instead // we'll do that if we ever traverse this node looking for a node // by path. for(uint32_t i = 0; i < node->nprops; i++) { dtprop_t *prop; uint32_t size_and_flags; bool prop_removed = false; ASSERT(addr <= endaddr); prop = &node->props[i]; if((size_t)(endaddr - addr) < kPropNameLength + sizeof(uint32_t)) { dprintf(DEBUG_INFO, "dt: not enough bytes for property name '%s'\n", (char *)addr); goto error; } if (memchr(addr, 0, kPropNameLength) == NULL) { dprintf(DEBUG_INFO, "dt: unterminated property name '%s'\n", (char *)addr); goto error; } prop->name = (char *)addr; memcpy(&size_and_flags, addr + kPropNameLength, sizeof(uint32_t)); addr += kPropNameLength + sizeof(uint32_t); prop->size = size_and_flags & DT_PROP_SIZE_MASK; if ((size_t)(endaddr - addr) < ROUND_SIZE(prop->size)) { dprintf(DEBUG_INFO, "dt: not enough bytes for property '%s'\n", prop->name); goto error; } prop->data = addr; addr += ROUND_SIZE(prop->size); if (size_and_flags & DT_PROP_FLAG_PLACEHOLDER) { // if placeholder expansion fails, then recycle the property slot // we used for the placeholder prop_removed = !expand_placeholder_prop(prop); if (prop_removed) { node->nprops--; i--; } } // Account for the size of the property after any placeholder expansion has occurred if (!prop_removed) dt_size += kPropNameLength + sizeof(uint32_t) + ROUND_SIZE(prop->size); } for (uint32_t i = 0; i < node->nchildren; i++) { if (!parse_node(&node->children[i], &addr, endaddr)) goto error; } *addrptr = addr; return true; error: free_node(node); return false; } dt_node_t *dt_deserialize(void *dtaddr, size_t dtlength) { uint8_t *startptr; uint8_t *endptr; dt_node_t *node; dt_init(); node = calloc(sizeof(*node), 1); startptr = dtaddr; endptr = dtaddr + dtlength; if (!parse_node(node, &startptr, endptr)) { dt_size = 0; free(node); return NULL; } dt_root = node; return node; } void dt_free(dt_node_t *dt) { free_node(dt); free(dt); } static void write_prop(char *name, void *data, uint32_t data_size, uint8_t **bufptr, uint8_t *end) { uint8_t *buf = *bufptr; uint32_t prop_buf_size = ROUND_SIZE(data_size); uint32_t total_size = kPropNameLength + sizeof(uint32_t) + prop_buf_size; // no overflow if (prop_buf_size < data_size || total_size < prop_buf_size) { panic("devicetree integer overflow"); } // Make sure there's room for the property name and value if ((size_t)(end - buf) < total_size) { panic("devicetree buffer overflow"); } // Write property name, padding with zeroes to 32 bytes memset(buf, 0, kPropNameLength); strlcpy((char *)buf, name, kPropNameLength); buf += kPropNameLength; // Then property size memcpy(buf, &data_size, sizeof(uint32_t)); buf += sizeof(uint32_t); // And property value, which won't be present if size is 0 if (prop_buf_size != 0) { memcpy(buf, data, data_size); // Pad if needed to multiple of 4 bytes if (data_size != prop_buf_size) memset(buf + data_size, 0, prop_buf_size - data_size); buf += prop_buf_size; } RELEASE_ASSERT(buf <= end); // let caller know how far we wrote *bufptr = buf; } static void write_node(dt_node_t *node, uint8_t **bufptr, uint8_t *end) { uint8_t *buf = *bufptr; RELEASE_ASSERT(*bufptr < end); // Make sure there's room for the node header if ((size_t)(end - buf) < 2 * sizeof(uint32_t)) panic("devicetree buffer overflow"); // Write the node header (number of properties, then number of children) memcpy(buf, &node->nprops, 4); memcpy(buf + 4, &node->nchildren, 4); buf += 8; // Write out each property for (uint32_t i = 0; i < node->nprops; i++) { dtprop_t *prop = &node->props[i]; write_prop(prop->name, prop->data, prop->size, &buf, end); } // Write out all of this node's children for (uint32_t i = 0; i < node->nchildren; i++) { write_node(&node->children[i], &buf, end); } // Let the caller know how far we wrote *bufptr = buf; RELEASE_ASSERT(*bufptr <= end); } void dt_serialize(dt_node_t *dt, void *buffer, size_t bufferlen) { uint8_t *startptr = buffer; uint8_t *endptr = startptr + bufferlen; if (dt == NULL) dt = dt_root; RELEASE_ASSERT(dt != NULL); if (bufferlen < dt_size) panic("devicetree length %zu > buffer size %zu", dt_size, bufferlen); write_node(dt, &startptr, endptr); // Make sure dt_size is accurate ASSERT(startptr == (uint8_t *)buffer + dt_size); } dt_node_t *dt_get_root(void) { return dt_root; } bool dt_find_node(dt_node_t *root, const char *path, dt_node_t **node) { uint32_t path_len; const char *rest; if (root == NULL) { if (dt_root == NULL) return false; root = dt_root; } if(path[0] == '\0') { *node = root; return true; } rest = strchr(path, '/'); if (rest == NULL) { rest = ""; path_len = strlen(path); } else { path_len = rest - path; rest++; } for (uint32_t i = 0; i < root->nchildren; i++) { dt_node_t *child = &root->children[i]; if (child->name == NULL) { char *prop_name = "name"; if (!dt_get_prop(child, &prop_name, &child->name, &child->name_size)) { dprintf(DEBUG_CRITICAL, "dt: found node with no name: %p\n", child); continue; } } if (child->name_size == path_len + 1 && memcmp(path, child->name, path_len) == 0) { return dt_find_node(child, rest, node); } } return false; } static bool find_prop(dt_node_t *node, const char *name, dtprop_t **prop_out) { ASSERT(node != NULL); ASSERT(name != NULL); for (uint32_t i = 0; i < node->nprops; i++) { dtprop_t *prop = &node->props[i]; if (strncmp(name, prop->name, kPropNameLength) == 0) { if (prop_out != NULL) *prop_out = prop; return true; } } return false; } bool dt_get_prop(dt_node_t *node, char **prop_name, void **prop_data, uint32_t *prop_size) { dtprop_t *prop; ASSERT(node != NULL); ASSERT(prop_name != NULL && *prop_name != NULL); if(find_prop(node, *prop_name, &prop)) { *prop_name = prop->name; if (prop_size != NULL) *prop_size = prop->size; if (prop_data != NULL) *prop_data = prop->data; return true; } else { return false; } } // Like dt_find_prop, but with a less obnoxious interface on prop_name bool dt_find_prop(dt_node_t *node, const char *prop_name, void **prop_data, uint32_t *prop_size) { dtprop_t *prop; ASSERT(node != NULL); ASSERT(prop_name != NULL); if(find_prop(node, prop_name, &prop)) { if (prop_size != NULL) *prop_size = prop->size; if (prop_data != NULL) *prop_data = prop->data; return true; } else { return false; } } bool dt_has_prop(dt_node_t *node, const char *prop_name) { return dt_find_prop(node, prop_name, NULL, NULL); } const char *dt_get_node_name(dt_node_t *node) { if (node->name == NULL) { char *prop_name = "name"; dt_get_prop(node, &prop_name, &node->name, &node->name_size); } return node->name; } static dtprop_t *add_prop(dt_node_t *node, const char *name) { uint32_t nprops = node->nprops + 1; dtprop_t *prop; ASSERT(strlen(name) < kPropNameLength); node->props = realloc(node->props, nprops * sizeof(*node->props)); prop = &node->props[nprops - 1]; memset(prop, 0, sizeof(*prop)); prop->name = calloc(kPropNameLength, 1); strlcpy(prop->name, name, kPropNameLength); prop->name_malloced = true; node->nprops = nprops; // The device tree will grow by the size of the property name and length fields // The caller of this function will account for the growth from the value dt_size += 4 + kPropNameLength; return prop; } void dt_set_prop(dt_node_t *node, const char *name, const void *data, uint32_t size) { dtprop_t *prop; size_t cur_alloc_size; size_t new_alloc_size; ASSERT(node != NULL); ASSERT(name != NULL); ASSERT(size == 0 || data != NULL); ASSERT(!dt_sealed); if (!find_prop(node, name, &prop)) prop = add_prop(node, name); // if we're changing the name, get rid of the cache if (prop->data == node->name) { node->name = NULL; node->name_size = 0; } // Data is always serialized with length padded to 4 bytes new_alloc_size = ROUND_SIZE(size); cur_alloc_size = ROUND_SIZE(prop->size); // zero-length properties are legal, but malloc(0) isn't // rest of this file handles prop.data == NULL and prop.size == 0 properly if (new_alloc_size > 0) { // Only re-allocate if needed. data_malloced will be false for // newly created properties because add_prop zeroes the struct // before returning it if (cur_alloc_size < new_alloc_size) { if (!prop->data_malloced) prop->data = malloc(new_alloc_size); else prop->data = realloc(prop->data, new_alloc_size); prop->data_malloced = true; } memcpy(prop->data, data, size); // zero-fill any padding bytes memset((uint8_t *)prop->data + size, 0, new_alloc_size - size); } prop->size = size; // add_prop accounted for the name and length fields, we just need // to fgure out the difference from the value. Since add_prop zeroes // out the struct, for new properties, cur_alloc_size will be 0 dt_size += new_alloc_size - cur_alloc_size; } void dt_set_prop_32(dt_node_t *node, const char *name, uint32_t value) { dt_set_prop(node, name, &value, sizeof(value)); } void dt_set_prop_64(dt_node_t *node, const char *name, uint64_t value) { dt_set_prop(node, name, &value, sizeof(value)); } void dt_set_prop_addr(dt_node_t *node, const char *name, uintptr_t value) { dt_set_prop(node, name, &value, sizeof(value)); } void dt_set_prop_str(dt_node_t *node, const char *name, const char *str) { dt_set_prop(node, name, str, strlen(str) + 1); } bool dt_remove_prop(dt_node_t *node, const char *name) { ASSERT(node != NULL); ASSERT(name != NULL); ASSERT(!dt_sealed); for (uint32_t i = 0; i < node->nprops; i++) { dtprop_t *prop = &node->props[i]; if (strncmp(name, prop->name, kPropNameLength) == 0) { // unlikely, but if we're removing the name, get rid of the cache if (prop->data == node->name) { node->name = NULL; node->name_size = 0; } dt_size -= ROUND_SIZE(prop->size) + 4 + kPropNameLength; memmove(prop, prop + 1, (node->nprops - i - 1) * sizeof(*prop)); node->nprops--; return true; } } return false; } bool dt_rename_prop(dt_node_t *node, const char *name, const char *new_name) { ASSERT(node != NULL); ASSERT(name != NULL); ASSERT(new_name != NULL); ASSERT(!dt_sealed); dtprop_t *prop; size_t new_name_len = strlen(new_name); ASSERT(new_name_len < kPropNameLength - 1); if (find_prop(node, name, &prop)) { // unlikely, but if we're renaming the name property, get rid of the cache if (prop->data == node->name) { node->name = NULL; node->name_size = 0; } memset(prop->name, 0, kPropNameLength); memcpy(prop->name, new_name, new_name_len); return true; } else { return false; } } void dt_seal(void) { dt_sealed = true; }