| /* |
| * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com> |
| * |
| * 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 && 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) |
| continue; |
| |
| 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 char *p, *q; |
| int node, len; |
| |
| node = fdt_path_offset(fdt, "/chosen"); |
| if (node < 0) |
| return node; |
| |
| p = fdt_getprop(fdt, node, "stdout-path", &len); |
| if (!p) { |
| p = fdt_getprop(fdt, node, "linux,stdout-path", &len); |
| if (!p) |
| return len; |
| } |
| |
| q = strchrnul(p, ':'); |
| len = q - p; |
| |
| return fdt_path_offset_namelen(fdt, p, len); |
| } |
| |
| int dt_get_initrd(const char **initrd, u32 *size) |
| { |
| const struct fdt_property *prop; |
| u64 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 = fdt32_to_cpu(*data); |
| if (len == 8) { |
| data++; |
| start = (start << 32) | 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 = fdt32_to_cpu(*data); |
| if (len == 8) { |
| data++; |
| end = (end << 32) | fdt32_to_cpu(*data); |
| } |
| |
| assert(start < end); |
| assert(sizeof(long) == 8 || !(end >> 32)); |
| |
| *initrd = (char *)(unsigned long)start; |
| *size = end - 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; |
| } |