root/arch/riscv/kernel/pi/fdt_early.c
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/types.h>
#include <linux/init.h>
#include <linux/libfdt.h>
#include <linux/ctype.h>
#include <asm/csr.h>

#include "pi.h"

u64 get_kaslr_seed(uintptr_t dtb_pa)
{
        int node, len;
        fdt64_t *prop;
        u64 ret;

        node = fdt_path_offset((void *)dtb_pa, "/chosen");
        if (node < 0)
                return 0;

        prop = fdt_getprop_w((void *)dtb_pa, node, "kaslr-seed", &len);
        if (!prop || len != sizeof(u64))
                return 0;

        ret = fdt64_to_cpu(*prop);
        *prop = 0;
        return ret;
}

/**
 *  fdt_device_is_available - check if a device is available for use
 *
 * @fdt: pointer to the device tree blob
 * @node: offset of the node whose property to find
 *
 *  Returns true if the status property is absent or set to "okay" or "ok",
 *  false otherwise
 */
static bool fdt_device_is_available(const void *fdt, int node)
{
        const char *status;
        int statlen;

        status = fdt_getprop(fdt, node, "status", &statlen);
        if (!status)
                return true;

        if (statlen > 0) {
                if (!strcmp(status, "okay") || !strcmp(status, "ok"))
                        return true;
        }

        return false;
}

/* Copy of fdt_nodename_eq_ */
static int fdt_node_name_eq(const void *fdt, int offset,
                            const char *s)
{
        int olen;
        int len = strlen(s);
        const char *p = fdt_get_name(fdt, offset, &olen);

        if (!p || olen < len)
                /* short match */
                return 0;

        if (memcmp(p, s, len) != 0)
                return 0;

        if (p[len] == '\0')
                return 1;
        else if (!memchr(s, '@', len) && (p[len] == '@'))
                return 1;
        else
                return 0;
}

/**
 *  isa_string_contains - check if isa string contains an extension
 *
 * @isa_str: isa string to search
 * @ext_name: the extension to search for
 *
 *  Returns true if the extension is in the given isa string,
 *  false otherwise
 */
static bool isa_string_contains(const char *isa_str, const char *ext_name)
{
        size_t i, single_end, len = strlen(ext_name);
        char ext_end;

        /* Error must contain rv32/64 */
        if (strlen(isa_str) < 4)
                return false;

        if (len == 1) {
                single_end = strcspn(isa_str, "sSxXzZ");
                /* Search for single chars between rv32/64 and multi-letter extensions */
                for (i = 4; i < single_end; i++) {
                        if (tolower(isa_str[i]) == ext_name[0])
                                return true;
                }
                return false;
        }

        /* Skip to start of multi-letter extensions */
        isa_str = strpbrk(isa_str, "sSxXzZ");
        while (isa_str) {
                if (strncasecmp(isa_str, ext_name, len) == 0) {
                        ext_end = isa_str[len];
                        /* Check if matches the whole extension. */
                        if (ext_end == '\0' || ext_end == '_')
                                return true;
                }
                /* Multi-letter extensions must be split from other multi-letter
                 * extensions with an "_", the end of a multi-letter extension will
                 * either be the null character or the "_" at the start of the next
                 * multi-letter extension.
                 */
                isa_str = strchr(isa_str, '_');
                if (isa_str)
                        isa_str++;
        }

        return false;
}

/**
 *  early_cpu_isa_ext_available - check if cpu node has an extension
 *
 * @fdt: pointer to the device tree blob
 * @node: offset of the cpu node
 * @ext_name: the extension to search for
 *
 *  Returns true if the cpu node has the extension,
 *  false otherwise
 */
static bool early_cpu_isa_ext_available(const void *fdt, int node, const char *ext_name)
{
        const void *prop;
        int len;

        prop = fdt_getprop(fdt, node, "riscv,isa-extensions", &len);
        if (prop && fdt_stringlist_contains(prop, len, ext_name))
                return true;

        prop = fdt_getprop(fdt, node, "riscv,isa", &len);
        if (prop && isa_string_contains(prop, ext_name))
                return true;

        return false;
}

/**
 *  fdt_early_match_extension_isa - check if all cpu nodes have an extension
 *
 * @fdt: pointer to the device tree blob
 * @ext_name: the extension to search for
 *
 *  Returns true if the all available the cpu nodes have the extension,
 *  false otherwise
 */
bool fdt_early_match_extension_isa(const void *fdt, const char *ext_name)
{
        int node, parent;
        bool ret = false;

        parent = fdt_path_offset(fdt, "/cpus");
        if (parent < 0)
                return false;

        fdt_for_each_subnode(node, fdt, parent) {
                if (!fdt_node_name_eq(fdt, node, "cpu"))
                        continue;

                if (!fdt_device_is_available(fdt, node))
                        continue;

                if (!early_cpu_isa_ext_available(fdt, node, ext_name))
                        return false;

                ret = true;
        }

        return ret;
}

/**
 *  set_satp_mode_from_fdt - determine SATP mode based on the MMU type in fdt
 *
 * @dtb_pa: physical address of the device tree blob
 *
 *  Returns the SATP mode corresponding to the MMU type of the first enabled CPU,
 *  0 otherwise
 */
u64 set_satp_mode_from_fdt(uintptr_t dtb_pa)
{
        const void *fdt = (const void *)dtb_pa;
        const char *mmu_type;
        int node, parent;

        parent = fdt_path_offset(fdt, "/cpus");
        if (parent < 0)
                return 0;

        fdt_for_each_subnode(node, fdt, parent) {
                if (!fdt_node_name_eq(fdt, node, "cpu"))
                        continue;

                if (!fdt_device_is_available(fdt, node))
                        continue;

                mmu_type = fdt_getprop(fdt, node, "mmu-type", NULL);
                if (!mmu_type)
                        break;

                if (!strcmp(mmu_type, "riscv,sv39"))
                        return SATP_MODE_39;
                else if (!strcmp(mmu_type, "riscv,sv48"))
                        return SATP_MODE_48;
                break;
        }

        return 0;
}