From patchwork Tue Jan 21 16:21:57 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Jones X-Patchwork-Id: 3518971 Return-Path: X-Original-To: patchwork-kvm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 2BE389F2ED for ; Tue, 21 Jan 2014 16:23:25 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id CB7EF2015E for ; Tue, 21 Jan 2014 16:23:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 596FD2015A for ; Tue, 21 Jan 2014 16:23:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755139AbaAUQWz (ORCPT ); Tue, 21 Jan 2014 11:22:55 -0500 Received: from mx1.redhat.com ([209.132.183.28]:51029 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755096AbaAUQWp (ORCPT ); Tue, 21 Jan 2014 11:22:45 -0500 Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id s0LGMO2p007837 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Tue, 21 Jan 2014 11:22:25 -0500 Received: from hawk.usersys.redhat.com.com (dhcp-1-167.brq.redhat.com [10.34.1.167]) by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id s0LGM6NX007833; Tue, 21 Jan 2014 11:22:23 -0500 From: Andrew Jones To: kvmarm@lists.cs.columbia.edu, kvm@vger.kernel.org Cc: christoffer.dall@linaro.org Subject: [PATCH 11/17] add support for device trees Date: Tue, 21 Jan 2014 17:21:57 +0100 Message-Id: <1390321323-1855-12-git-send-email-drjones@redhat.com> In-Reply-To: <1390321323-1855-1-git-send-email-drjones@redhat.com> References: <1390321323-1855-1-git-send-email-drjones@redhat.com> X-Scanned-By: MIMEDefang 2.67 on 10.5.11.12 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Spam-Status: No, score=-7.5 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add some device tree functions, built on libfdt, to the common code in order to facilitate the extraction of boot info and device base addresses. These functions are generic and arch-neutral. It's expected that more functions will be added as more information from the device tree is needed. Signed-off-by: Andrew Jones --- lib/devicetree.c | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/devicetree.h | 95 ++++++++++++++++++++ lib/libcflat.h | 1 + 3 files changed, 353 insertions(+) create mode 100644 lib/devicetree.c create mode 100644 lib/devicetree.h diff --git a/lib/devicetree.c b/lib/devicetree.c new file mode 100644 index 0000000000000..a9ab7ed367704 --- /dev/null +++ b/lib/devicetree.c @@ -0,0 +1,257 @@ +#include "libcflat.h" +#include "libfdt/libfdt.h" +#include "devicetree.h" + +static const void *fdt; + +const char *dt_strerror(int errval) +{ + if (errval == -EINVAL) + return "Invalid argument"; + return fdt_strerror(errval); +} + +int dt_set(const void *fdt_ptr) +{ + int ret = fdt_check_header(fdt_ptr); + if (ret == 0) + fdt = fdt_ptr; + return ret; +} + +const void *dt_get(void) +{ + return fdt; +} + +int dt_get_bootargs_ptr(char **bootargs) +{ + const struct fdt_property *prop; + int node, err; + + node = fdt_path_offset(fdt, "/chosen"); + if (node < 0) + return node; + + prop = fdt_get_property(fdt, node, "bootargs", &err); + if (prop) + *bootargs = (char *)prop->data; + else if (err != -FDT_ERR_NOTFOUND) + return err; + + return 0; +} + +int dt_bus_default_match(const struct dt_bus *bus __unused, + int nodeoffset __unused) +{ + /* just select first node found */ + return 1; +} + +int dt_bus_default_translate(const struct dt_bus *bus __unused, + struct dt_reg *reg, void **addr, size_t *size) +{ + u64 temp64; + + if (!reg || !addr) + return -EINVAL; + + /* + * default translate only understands u32 (<1> <1>) and + * u64 (<2> <1>|<2>) addresses + */ + if (reg->nr_address_cells < 1 + || reg->nr_address_cells > 2 + || reg->nr_size_cells < 1 + || reg->nr_size_cells > 2) + return -EINVAL; + + if (reg->nr_address_cells == 2) + temp64 = ((u64)reg->address_cells[0] << 32) + | reg->address_cells[1]; + else + temp64 = reg->address_cells[0]; + + /* + * If we're 32-bit, then the upper word of a two word + * address better be zero. + */ + if (sizeof(void *) == sizeof(u32) && reg->nr_address_cells > 1 + && reg->address_cells[0] != 0) + return -EINVAL; + + *addr = (void *)(unsigned long)temp64; + + if (size) { + if (reg->nr_size_cells == 2) + temp64 = ((u64)reg->size_cells[0] << 32) + | reg->size_cells[1]; + else + temp64 = reg->size_cells[0]; + + if (sizeof(size_t) == sizeof(u32) && reg->nr_size_cells > 1 + && reg->size_cells[0] != 0) + return -EINVAL; + + *size = (size_t)temp64; + } + + return 0; +} + +const struct dt_bus dt_default_bus = { + .name = "default", + .match = dt_bus_default_match, + .translate = dt_bus_default_translate, +}; + +void dt_bus_init_defaults(struct dt_bus *bus, const char *name) +{ + *bus = dt_default_bus; + bus->name = name; +} + +int dt_bus_find_device_compatible(const struct dt_bus *bus, + const char *compatible) +{ + int node, ret; + + if (!bus || !bus->match) + return -EINVAL; + + node = fdt_node_offset_by_compatible(fdt, -1, compatible); + + while (node >= 0) { + if ((ret = bus->match(bus, node)) < 0) + return ret; + else if (ret) + break; + node = fdt_node_offset_by_compatible(fdt, node, compatible); + } + + return node; +} + +static int __dt_get_num_cells(int node, u32 *address_cells, u32 *size_cells) +{ + const struct fdt_property *prop; + u32 *data; + int err; + + prop = fdt_get_property(fdt, node, "#address-cells", &err); + if (!prop && err == -FDT_ERR_NOTFOUND) { + + node = fdt_parent_offset(fdt, node); + if (node < 0) + return node; + + return __dt_get_num_cells(node, address_cells, size_cells); + + } else if (!prop) { + return err; + } + + data = (u32 *)prop->data; + *address_cells = fdt32_to_cpu(*data); + + prop = fdt_get_property(fdt, node, "#size-cells", &err); + if (!prop) { + printf("we can read #address-cells, but not #size-cells?\n"); + return err; + } + + data = (u32 *)prop->data; + *size_cells = fdt32_to_cpu(*data); + + return 0; +} + +int dt_get_num_cells(int nodeoffset, u32 *address_cells, u32 *size_cells) +{ + if (!address_cells || !size_cells) + return -EINVAL; + return __dt_get_num_cells(nodeoffset, address_cells, size_cells); +} + +int dt_get_reg(int nodeoffset, int regidx, u32 *address_cells, + u32 *size_cells, struct dt_reg *reg) +{ + const struct fdt_property *prop; + u32 *data, regsz, i; + int err; + + if (!address_cells || !size_cells || !reg) + return -EINVAL; + + memset(reg, 0, sizeof(struct dt_reg)); + + /* + * We assume #size-cells == 0 means translation is impossible, + * reserving it to indicate that we don't know what #address-cells + * and #size-cells are yet, and thus must try to get them from the + * parent. + */ + if (*size_cells == 0 && (err = dt_get_num_cells(nodeoffset, + address_cells, size_cells)) < 0) + return err; + + prop = fdt_get_property(fdt, nodeoffset, "reg", &err); + if (prop == NULL) + return err; + + regsz = (*address_cells + *size_cells) * sizeof(u32); + + if ((regidx + 1) * regsz > prop->len) + return -EINVAL; + + data = (u32 *)(prop->data + regidx * regsz); + + for (i = 0; i < *address_cells; ++i, ++data) + reg->address_cells[i] = fdt32_to_cpu(*data); + for (i = 0; i < *size_cells; ++i, ++data) + reg->size_cells[i] = fdt32_to_cpu(*data); + + reg->nr_address_cells = *address_cells; + reg->nr_size_cells = *size_cells; + + return 0; +} + +int __dt_bus_translate_reg(int nodeoffset, const struct dt_bus *bus, + int regidx, u32 *address_cells, u32 *size_cells, + void **addr, size_t *size) +{ + struct dt_reg reg; + int ret; + + if (!bus || !bus->translate) + return -EINVAL; + + ret = dt_get_reg(nodeoffset, regidx, address_cells, size_cells, ®); + if (ret < 0) + return ret; + + return bus->translate(bus, ®, addr, size); +} + +int dt_bus_translate_reg(int nodeoffset, const struct dt_bus *bus, + int regidx, void **addr, size_t *size) +{ + /* + * size_cells == 0 tells dt_get_reg to get address_cells + * and size_cells from the parent node + */ + u32 address_cells, size_cells = 0; + return __dt_bus_translate_reg(nodeoffset, bus, regidx, + &address_cells, &size_cells, addr, size); +} + +int dt_get_memory_params(void **start, size_t *size) +{ + int node = fdt_path_offset(fdt, "/memory"); + if (node < 0) + return node; + + return dt_bus_translate_reg(node, &dt_default_bus, 0, start, size); +} diff --git a/lib/devicetree.h b/lib/devicetree.h new file mode 100644 index 0000000000000..1f2d61b46a308 --- /dev/null +++ b/lib/devicetree.h @@ -0,0 +1,95 @@ +#ifndef _DEVICETREE_H_ +#define _DEVICETREE_H_ +#include "libcflat.h" + +/* + * set/get the fdt pointer + */ +extern int dt_set(const void *fdt_ptr); +extern const void *dt_get(void); + +/* + * bootinfo accessors + */ +extern int dt_get_bootargs_ptr(char **bootargs); +extern int dt_get_memory_params(void **start, size_t *size); + +#define MAX_ADDRESS_CELLS 4 +#define MAX_SIZE_CELLS 4 +struct dt_reg { + u32 nr_address_cells; + u32 nr_size_cells; + u32 address_cells[MAX_ADDRESS_CELLS]; + u32 size_cells[MAX_SIZE_CELLS]; +}; + +struct dt_bus { + const char *name; + int (*match)(const struct dt_bus *bus, int nodeoffset); + /* + * match() returns + * - a positive value on match + * - zero on no match + * - a negative value on error + */ + int (*translate)(const struct dt_bus *bus, struct dt_reg *reg, + void **addr, size_t *size); + /* + * translate() returns + * - zero on success + * - a negative value on error + */ + void *private; +}; + +extern const struct dt_bus dt_default_bus; +extern void dt_bus_init_defaults(struct dt_bus *bus, const char *name); +extern int dt_bus_default_match(const struct dt_bus *bus, int nodeoffset); +extern int dt_bus_default_translate(const struct dt_bus *bus, + struct dt_reg *reg, void **addr, + size_t *size); + +/* + * find an fdt device node compatible with @compatible using match() + * from the given bus @bus. + */ +extern int dt_bus_find_device_compatible(const struct dt_bus *bus, + const char *compatible); + +/* + * translate the reg indexed by @regidx of the "reg" property of the + * device node at @nodeoffset using translate() from the given bus @bus. + * returns the translation in @addr and @size + */ +extern int dt_bus_translate_reg(int nodeoffset, const struct dt_bus *bus, + int regidx, void **addr, size_t *size); + +/* + * same as dt_bus_translate_reg, but uses the given @address_cells and + * @size_cells rather than pulling them from the parent of @nodeoffset + */ +extern int __dt_bus_translate_reg(int nodeoffset, const struct dt_bus *bus, + int regidx, u32 *address_cells, + u32 *size_cells, void **addr, + size_t *size); + +/* + * read the "reg" property of @nodeoffset, which is defined by @address_cells + * and @size_cells, and store the reg indexed by @regidx into @reg + */ +extern int dt_get_reg(int nodeoffset, int regidx, u32 *address_cells, + u32 *size_cells, struct dt_reg *reg); + +/* + * searches up the devicetree for @address_cells and @size_cells, + * starting from @nodeoffset + */ +extern int dt_find_num_cells(int nodeoffset, u32 *address_cells, + u32 *size_cells); + +/* + * convert devicetree errors to strings + */ +extern const char *dt_strerror(int errval); + +#endif diff --git a/lib/libcflat.h b/lib/libcflat.h index 2cde64a560956..fdaaf2a8ab31d 100644 --- a/lib/libcflat.h +++ b/lib/libcflat.h @@ -61,6 +61,7 @@ extern long atol(const char *ptr); #define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER) +#define __unused __attribute__((__unused__)) #define NULL ((void *)0UL) #include "errno.h" #endif