/* * Copyright (C) 2014, Red Hat Inc, Andrew Jones * * This work is licensed under the terms of the GNU LGPL, version 2. */ #include "libcflat.h" #include "libfdt/libfdt.h" #include "devicetree.h" static const void *fdt; const void *dt_fdt(void) { return fdt; } bool dt_available(void) { return fdt_check_header(fdt) == 0; } int dt_get_nr_cells(int fdtnode, u32 *nr_address_cells, u32 *nr_size_cells) { const struct fdt_property *prop; u32 *nr_cells; int len, nac, nsc; prop = fdt_get_property(fdt, fdtnode, "#address-cells", &len); if (prop == NULL) return len; nr_cells = (u32 *)prop->data; nac = fdt32_to_cpu(*nr_cells); prop = fdt_get_property(fdt, fdtnode, "#size-cells", &len); if (prop == NULL) return len; nr_cells = (u32 *)prop->data; nsc = fdt32_to_cpu(*nr_cells); *nr_address_cells = nac; *nr_size_cells = nsc; return 0; } void dt_reg_init(struct dt_reg *reg, u32 nr_address_cells, u32 nr_size_cells) { memset(reg, 0, sizeof(struct dt_reg)); reg->nr_address_cells = nr_address_cells; reg->nr_size_cells = nr_size_cells; } int dt_get_reg(int fdtnode, int regidx, struct dt_reg *reg) { const struct fdt_property *prop; u32 *cells, i; unsigned nr_tuple_cells; int len; prop = fdt_get_property(fdt, fdtnode, "reg", &len); if (prop == NULL) return len; cells = (u32 *)prop->data; nr_tuple_cells = reg->nr_address_cells + reg->nr_size_cells; regidx *= nr_tuple_cells; if (regidx + nr_tuple_cells > len/sizeof(u32)) return -FDT_ERR_NOTFOUND; for (i = 0; i < reg->nr_address_cells; ++i) reg->address_cells[i] = fdt32_to_cpu(cells[regidx + i]); regidx += reg->nr_address_cells; for (i = 0; i < reg->nr_size_cells; ++i) reg->size_cells[i] = fdt32_to_cpu(cells[regidx + i]); return 0; } int dt_pbus_translate_node(int fdtnode, int regidx, struct dt_pbus_reg *pbus_reg) { struct dt_reg raw_reg; u32 nac, nsc; int parent, ret; parent = fdt_parent_offset(fdt, fdtnode); if (parent < 0) return parent; ret = dt_get_nr_cells(parent, &nac, &nsc); if (ret != 0) return ret; dt_reg_init(&raw_reg, nac, nsc); ret = dt_get_reg(fdtnode, regidx, &raw_reg); if (ret < 0) return ret; pbus_reg->addr = dt_pbus_read_cells(raw_reg.nr_address_cells, raw_reg.address_cells); pbus_reg->size = dt_pbus_read_cells(raw_reg.nr_size_cells, raw_reg.size_cells); return 0; } int dt_pbus_translate(const struct dt_device *dev, int regidx, void *reg) { return dt_pbus_translate_node(dev->fdtnode, regidx, reg); } int dt_bus_match_any(const struct dt_device *dev __unused, int fdtnode) { /* matches any device with a valid node */ return fdtnode < 0 ? fdtnode : 1; } static const struct dt_bus dt_default_bus = { .match = dt_bus_match_any, .translate = dt_pbus_translate, }; void dt_bus_init_defaults(struct dt_bus *bus) { memcpy(bus, &dt_default_bus, sizeof(struct dt_bus)); } void dt_device_init(struct dt_device *dev, const struct dt_bus *bus, void *info) { memset(dev, 0, sizeof(struct dt_device)); dev->bus = bus; dev->info = info; } int dt_device_find_compatible(const struct dt_device *dev, const char *compatible) { int node, ret; node = fdt_node_offset_by_compatible(fdt, -1, compatible); while (node >= 0) { ret = dev->bus->match(dev, node); if (ret < 0) return ret; else if (ret) break; node = fdt_node_offset_by_compatible(fdt, node, compatible); } return node; } int dt_pbus_get_base_compatible(const char *compatible, struct dt_pbus_reg *base) { struct dt_device dev; int node; dt_device_init(&dev, &dt_default_bus, NULL); node = dt_device_find_compatible(&dev, compatible); if (node < 0) return node; dt_device_bind_node(&dev, node); return dt_pbus_get_base(&dev, base); } int dt_get_memory_params(struct dt_pbus_reg *regs, int nr_regs) { const char *pn = "device_type", *pv = "memory"; int node, ret, reg_idx, pl = strlen(pv) + 1, nr = 0; struct dt_pbus_reg reg; node = fdt_node_offset_by_prop_value(fdt, -1, pn, pv, pl); while (node >= 0) { reg_idx = 0; while (nr < nr_regs) { ret = dt_pbus_translate_node(node, reg_idx, ®); if (ret == -FDT_ERR_NOTFOUND) break; if (ret < 0) return ret; regs[nr].addr = reg.addr; regs[nr].size = reg.size; ++nr, ++reg_idx; } node = fdt_node_offset_by_prop_value(fdt, node, pn, pv, pl); } return node != -FDT_ERR_NOTFOUND ? node : nr; } int dt_for_each_cpu_node(void (*func)(int fdtnode, u64 regval, void *info), void *info) { const struct fdt_property *prop; int cpus, cpu, ret, len; struct dt_reg raw_reg; u32 nac, nsc; u64 regval; cpus = fdt_path_offset(fdt, "/cpus"); if (cpus < 0) return cpus; ret = dt_get_nr_cells(cpus, &nac, &nsc); if (ret < 0) return ret; dt_reg_init(&raw_reg, nac, nsc); dt_for_each_subnode(cpus, cpu) { prop = fdt_get_property(fdt, cpu, "device_type", &len); if (prop == NULL) return len; if (len != 4 || strcmp((char *)prop->data, "cpu")) continue; ret = dt_get_reg(cpu, 0, &raw_reg); if (ret < 0) return ret; regval = raw_reg.address_cells[0]; if (nac == 2) regval = (regval << 32) | raw_reg.address_cells[1]; func(cpu, regval, info); } return 0; } int dt_get_bootargs(const char **bootargs) { const struct fdt_property *prop; int node, len; *bootargs = NULL; node = fdt_path_offset(fdt, "/chosen"); if (node < 0) return node; prop = fdt_get_property(fdt, node, "bootargs", &len); if (!prop) return len; *bootargs = prop->data; return 0; } int dt_get_default_console_node(void) { const struct fdt_property *prop; int node, len; node = fdt_path_offset(fdt, "/chosen"); if (node < 0) return node; prop = fdt_get_property(fdt, node, "stdout-path", &len); if (!prop) { prop = fdt_get_property(fdt, node, "linux,stdout-path", &len); if (!prop) return len; } return fdt_path_offset(fdt, prop->data); } int dt_get_initrd(const char **initrd, u32 *size) { const struct fdt_property *prop; const char *start, *end; int node, len; u32 *data; *initrd = NULL; *size = 0; node = fdt_path_offset(fdt, "/chosen"); if (node < 0) return node; prop = fdt_get_property(fdt, node, "linux,initrd-start", &len); if (!prop) return len; data = (u32 *)prop->data; start = (const char *)(unsigned long)fdt32_to_cpu(*data); prop = fdt_get_property(fdt, node, "linux,initrd-end", &len); if (!prop) { assert(len != -FDT_ERR_NOTFOUND); return len; } data = (u32 *)prop->data; end = (const char *)(unsigned long)fdt32_to_cpu(*data); *initrd = start; *size = (unsigned long)end - (unsigned long)start; return 0; } int dt_init(const void *fdt_ptr) { int ret; ret = fdt_check_header(fdt_ptr); if (ret < 0) return ret; /* Sanity check the path. */ ret = fdt_path_offset(fdt_ptr, "/"); if (ret < 0) return ret; fdt = fdt_ptr; return 0; }