/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include "dtb_node.h"
#include "libfdt.h"
#include "libfdt_env.h"
#include <ctype.h>

static const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)
{
    if (*base == 0)
    {
        if (s[0] == '0')
        {
            if (tolower(s[1]) == 'x' && isxdigit((int)s[2]))
                *base = 16;
            else
                *base = 8;
        }
        else
            *base = 10;
    }
    if (*base == 16 && s[0] == '0' && tolower(s[1]) == 'x')
        s += 2;
    return s;
}

unsigned long simple_strtoul(const char *cp, char **endp,
                unsigned int base)
{
    unsigned long result = 0;
    unsigned long value;

    cp = _parse_integer_fixup_radix(cp, &base);

    while (isxdigit((int)*cp) && (value = isdigit((int)*cp) ? *cp-'0' : (islower((int)*cp)
        ? toupper(*cp) : *cp)-'A'+10) < base)
    {
        result = result*base + value;
        cp++;
    }

    if (endp)
        *endp = (char *)cp;

    return result;
}

int strict_strtoul(const char *cp, unsigned int base, unsigned long *res)
{
    char *tail;
    unsigned long val;
    size_t len;

    *res = 0;
    len = strlen(cp);
    if (len == 0)
        return -EINVAL;

    val = simple_strtoul(cp, &tail, base);
    if (tail == cp)
        return -EINVAL;

    if ((*tail == '\0') ||
        ((len == (size_t)(tail - cp) + 1) && (*tail == '\n')))
    {
        *res = val;
        return 0;
    }

    return -EINVAL;
}

long simple_strtol(const char *cp, char **endp, unsigned int base)
{
    if (*cp == '-')
        return -simple_strtoul(cp + 1, endp, base);

    return simple_strtoul(cp, endp, base);
}

rt_bool_t dtb_node_read_bool(const struct dtb_node *node, const char *propname)
{
    const void *prop;

    RT_ASSERT(dtb_node_valid(node));
    debug("%s: %s: ", __func__, propname);

    prop = dtb_node_get_property(node, propname, NULL);

    debug("%s\n", prop ? "true" : "false");

    return prop ? RT_TRUE : RT_FALSE;
}

const void *dtb_node_read_prop(const struct dtb_node *node, const char *propname, int *sizep)
{
    const char *val = NULL;
    int len;

    RT_ASSERT(dtb_node_valid(node));
    debug("%s: %s: ", __func__, propname);

    struct dtb_property *prop = dtb_node_get_dtb_node_property(node, propname, &len);

    if (prop)
    {
        val = prop->value;
        len = prop->size;
    }

    if (!val)
    {
        debug("<not found>\n");
        if (sizep)
            *sizep = -FDT_ERR_NOTFOUND;
        return NULL;
    }
    if (sizep)
        *sizep = len;

    return val;
}

const char *dtb_node_read_string(const struct dtb_node *node, const char *propname)
{
    const char *str;
    int len;

    str = dtb_node_read_prop(node, propname, &len);
    if (!str)
        return NULL;

    if (strnlen(str, len) >= len)
    {
        debug("<invalid>\n");
        return NULL;
    }
    debug("%s\n", str);

    return str;
}

const struct dtb_node *dtb_node_find_subnode(const struct dtb_node *node, const char *subnode_name)
{
    const struct dtb_node *subnode;

    RT_ASSERT(dtb_node_valid(node));
    debug("%s: %s: ", __func__, subnode_name);

    for (node = node->child; node; node = node->sibling)
    {
        if (!strcmp(subnode_name, node->name))
            break;
    }
    subnode = node;

    debug("%s\n", dtb_node_valid(subnode) ?\
          dtb_node_get_name(subnode) : "<none>");

    return subnode;
}

struct dtb_node *dtb_node_first_subnode(const struct dtb_node *node)
{
    RT_ASSERT(dtb_node_valid(node));

    return node->child;
}

struct dtb_node *dtb_node_next_subnode(const struct dtb_node *node)
{
    RT_ASSERT(dtb_node_valid(node));

    return node->sibling;
}

const char *dtb_node_get_name(const struct dtb_node *node)
{
    if (!dtb_node_valid(node))
    {
        debug("%s node not valid\n", __func__);
        return NULL;
    }

    return strrchr(node->path, '/') + 1;

}

struct dtb_node *dtb_node_get_by_phandle(uint32_t phandle)
{
    if (dtb_node_active())
        return dtb_node_find_node_by_phandle(phandle);
    return NULL;
}

int dtb_node_read_size(const struct dtb_node *node, const char *propname)
{
    struct dtb_property *prop = dtb_node_get_dtb_node_property( node, propname, NULL);

    if (prop)
        return prop->size;

    return -EINVAL;
}

int dtb_node_get_addr_and_size_by_index(const struct dtb_node *node, int index, size_t *addr, size_t *size)
{
    const uint32_t *prop;
    int psize;
    int onesize, na, ns;

    na = dtb_node_n_addr_cells(node);
    ns = dtb_node_n_size_cells(node);

    prop = dtb_node_get_dtb_node_property_value(node, "reg", &psize);
    if (prop == NULL)
    {
        return -1;
    }

    psize /= 4;
    onesize = na + ns;

    if (psize >= (index + 1) * onesize)
    {
        prop += index * onesize;

        if (addr)
        {
            *addr = dtb_node_read_number(prop, na);
        }
        if (size)
        {
            *size = dtb_node_read_number(prop + na, ns);
        }

        return 0;
    }

    return -1;
}

size_t dtb_node_get_addr_index(const struct dtb_node *node, int index)
{
    int na;
    size_t size;

    const uint32_t *prop_val;
    uint flags;

    prop_val = dtb_node_get_address(node, index,
                    (uint64_t *)&size, &flags);
    if (!prop_val)
        return -1;

    na = dtb_node_n_addr_cells(node);

    return dtb_node_read_number(prop_val, na);
}

size_t dtb_node_get_addr(const struct dtb_node *node)
{
    return dtb_node_get_addr_index(node, 0);
}

int dtb_node_stringlist_search(const struct dtb_node *node, const char *property,
                 const char *string)
{
    return dtb_node_property_match_string(node, property, string);
}

int dtb_node_read_string_index(const struct dtb_node *node, const char *property, int index,
                 const char **outp)
{
    return dtb_node_property_read_string_index(node, property, index, outp);
}

int dtb_node_read_string_count(const struct dtb_node *node, const char *property)
{
    return dtb_node_property_count_strings(node, property);
}

struct dtb_node *dtb_node_path(const char *path)
{
    if (dtb_node_active())
        return dtb_node_find_node_by_path(path);
    return NULL;
}

const char *dtb_node_get_chosen_prop(const char *name)
{
    const struct dtb_node *chosen_node;

    chosen_node = (const struct dtb_node *)dtb_node_path("/chosen");

    return dtb_node_read_string(chosen_node, name);
}

struct dtb_node *dtb_node_get_chosen_node(const char *name)
{
    const char *prop;

    prop = dtb_node_get_chosen_prop(name);
    if (!prop)
        return NULL;

    return dtb_node_path(prop);
}

const void *dtb_node_get_property(const struct dtb_node *node, const char *propname, int *lenp)
{
    return dtb_node_get_dtb_node_property_value(node, propname, lenp);
}

rt_bool_t dtb_node_is_available(const struct dtb_node *node)
{
    return dtb_node_device_is_available(node);
}

size_t dtb_node_get_addr_size(const struct dtb_node *node, const char *property,
                size_t *sizep)
{
    int na, ns;
    int psize;
    const uint32_t *prop = dtb_node_get_dtb_node_property_value(node, property, &psize);

    if (!prop)
        return -1;
    na = dtb_node_n_addr_cells(node);
    ns = dtb_node_n_size_cells(node);
    *sizep = dtb_node_read_number(prop + na, ns);

    return dtb_node_read_number(prop, na);
}

const uint8_t *dtb_node_read_u8_array_ptr(const struct dtb_node *node, const char *propname,
                    size_t sz)
{
    int psize;
    const uint32_t *prop = dtb_node_get_dtb_node_property_value(node, propname, &psize);

    if (!prop || sz != psize)
        return NULL;
    return (uint8_t *)prop;
}

int dtb_node_find_all_compatible_node(const struct dtb_node *from, const char *compatible, struct dtb_node **node_table, int max_num, int *node_num)
{
    const struct dtb_node *dn;
    int num = 0;
    for_each_of_allnodes_from(from, dn)
    {
        if (dtb_node_get_dtb_node_compatible_match(dn, compatible) &&
            dtb_node_get(dn))
        {

            num++;
            *node_table = (struct dtb_node *)dn;
            node_table++;
            if (num >= max_num)
            {
                break;
            }
        }
    }
    *node_num = num;
    dtb_node_put(from);

    return 0;
}

int dtb_node_write_prop(const struct dtb_node *node, const char *propname, int len,
              const void *value)
{
    struct dtb_property *pp;
    struct dtb_property *pp_last = NULL;
    struct dtb_property *new;

    if (!dtb_node_active())
        return -ENOSYS;

    if (!node)
        return -EINVAL;

    for (pp = node->properties; pp; pp = pp->next)
    {
        if (strcmp(pp->name, propname) == 0)
        {
            /* Property exists -> change value */
            pp->value = (void *)value;
            pp->size = len;
            return 0;
        }
        pp_last = pp;
    }

    if (!pp_last)
        return -ENOENT;

    /* Property does not exist -> append new property */
    new = (struct dtb_property *)malloc(sizeof(struct dtb_property));
    if (!new)
        return -ENOMEM;

    new->name = strdup(propname);
    if (!new->name)
    {
        free(new);
        return -ENOMEM;
    }

    new->value = (void *)value;
    new->size = len;
    new->next = NULL;

    pp_last->next = new;

    return 0;
}

int dtb_node_write_string(const struct dtb_node *node, const char *propname, const char *value)
{
    if (!dtb_node_active())
        return -ENOSYS;

    RT_ASSERT(dtb_node_valid(node));

    debug("%s: %s = %s", __func__, propname, value);

    return dtb_node_write_prop(node, propname, strlen(value) + 1, value);
}

int dtb_node_set_enabled(const struct dtb_node *node, rt_bool_t value)
{
    if (!dtb_node_active())
        return -ENOSYS;

    RT_ASSERT(dtb_node_valid(node));

    if (value)
        return dtb_node_write_string(node, "status", "okay");
    else
        return dtb_node_write_string(node, "status", "disable");
}

/**
 * dtb_node_irq_find_parent - Given a device node, find its interrupt parent node
 * @child: pointer to device node
 *
 * Returns a pointer to the interrupt parent node, or NULL if the interrupt
 * parent could not be determined.
 */
static struct dtb_node *dtb_node_irq_find_parent(struct dtb_node *child)
{
    struct dtb_node *p;
    phandle parent;

    if (!dtb_node_get(child))
        return NULL;
    do
    {
        if (dtb_node_read_u32_array(child, "interrupt-parent", &parent, 1))
        {
            p = dtb_node_get_parent(child);
        }
        else
        {
            p = dtb_node_get_by_phandle(parent);
        }
        dtb_node_put(child);
        child = p;
    } while (p && dtb_node_get_property(p, "#interrupt-cells", NULL) == NULL);

    return p;
}

int dtb_node_irq_get(struct dtb_node *dev, int index)
{
    int rc = 0;
    struct fdt_phandle_args out_irq;
    struct dtb_node *p;
    uint32_t intsize;
    int res, i;

    p = dtb_node_irq_find_parent(dev);
    if (p == NULL)
        return -EINVAL;
    /* Get size of interrupt specifier */
    if (dtb_node_read_u32_array(p, "#interrupt-cells", &intsize, 1))
    {
        res = -EINVAL;
        goto out;
    }

    debug(" path:%s, parent=%pOF, intsize=%d\n", p->path,p, intsize);

    /* Copy intspec into irq structure */
    out_irq.np = p;
    out_irq.args_count = intsize;
    for (i = 0; i < intsize; i++)
    {
        res = dtb_node_read_u32_index(dev, "interrupts",
                         (index * 3 + i),
                         out_irq.args + i);
        if (res)
            goto out;
    }
    rc = out_irq.args[1];
 out:
    dtb_node_put(p);
    return rc;

}

/**
 * dtb_node_irq_get_byname - Decode a node's IRQ and return it as a Linux IRQ number
 * @dev: pointer to device tree node
 * @name: IRQ name
 *
 * Returns Linux IRQ number on success, or 0 on the IRQ mapping failure, or
 * -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case
 * of any other failure.
 */
int dtb_node_irq_get_byname(struct dtb_node *dev, const char *name)
{
    int index;

    if (!name)
        return -EINVAL;

    index = dtb_node_stringlist_search(dev, "interrupt-names", name);
    if (index < 0)
        return index;

    return dtb_node_irq_get(dev, index);
}

/**
 * dtb_node_irq_count - Count the number of IRQs a node uses
 * @dev: pointer to device tree node
 */
int dtb_node_irq_count(struct dtb_node *device)
{
    struct dtb_node *p;
    uint32_t intsize;
    int nr = 0, res = 0;

    p = dtb_node_irq_find_parent(device);
    if (p == NULL)
        return -EINVAL;

    /* Get size of interrupt specifier */
    if (dtb_node_read_u32_array(p, "#interrupt-cells", &intsize, 1))
    {
        res = -EINVAL;
        goto out;
    }

    debug(" path:%s, parent=%pOF, intsize=%d\n", p->path,p, intsize);

    res = dtb_node_read_size(device, "interrupts");
    if (res < 0)
    {
        goto out;
    }
    nr = res / (intsize * 4);
 out:
    dtb_node_put(p);

    return nr;
}
