root/src/system/boot/platform/riscv/fdt.cpp
/*
 * Copyright 2021, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 */


#include "fdt.h"
#include <SupportDefs.h>
#include <ByteOrder.h>
#include <KernelExport.h>
#include <boot/stage2.h>
#include <arch/generic/debug_uart_8250.h>
#include <arch/riscv64/arch_uart_sifive.h>
#include <arch_cpu_defs.h>

extern "C" {
#include <libfdt.h>
}

#include "mmu.h"
#include "smp.h"
#include "graphics.h"
#include "virtio.h"
#include "Htif.h"
#include "Clint.h"
#include "FwCfg.h"


void* gFdt = NULL;
ClintRegs *volatile gClintRegs = NULL;

static uint64 sTimerFrequrency = 10000000;

static addr_range sPlic = {0};
static addr_range sClint = {0};
static uart_info sUart{};


static bool
HasFdtString(const char* prop, int size, const char* pattern)
{
        int patternLen = strlen(pattern);
        const char* propEnd = prop + size;
        while (propEnd - prop > 0) {
                int curLen = strlen(prop);
                if (curLen == patternLen && memcmp(prop, pattern, curLen + 1) == 0)
                        return true;
                prop += curLen + 1;
        }
        return false;
}


static bool
GetReg(const void* fdt, int node, uint32 addressCells, uint32 sizeCells, size_t idx,
        addr_range& range)
{
        int propSize;
        const uint8* prop = (const uint8*)fdt_getprop(fdt, node, "reg", &propSize);
        if (prop == NULL)
                return false;

        size_t entrySize = 4*(addressCells + sizeCells);
        if ((idx + 1)*entrySize > (size_t)propSize)
                return false;

        prop += idx*entrySize;

        switch (addressCells) {
                case 1: range.start = fdt32_to_cpu(*(uint32*)prop); prop += 4; break;
                case 2: range.start = fdt64_to_cpu(*(uint64*)prop); prop += 8; break;
                default: panic("unsupported addressCells");
        }
        switch (sizeCells) {
                case 1: range.size = fdt32_to_cpu(*(uint32*)prop); prop += 4; break;
                case 2: range.size = fdt64_to_cpu(*(uint64*)prop); prop += 8; break;
                default: panic("unsupported sizeCells");
        }
        return true;
}


static uint32
GetInterrupt(const void* fdt, int node)
{
        if (uint32* prop = (uint32*)fdt_getprop(fdt, node, "interrupts-extended", NULL)) {
                return fdt32_to_cpu(*(prop + 1));
        }
        if (uint32* prop = (uint32*)fdt_getprop(fdt, node, "interrupts", NULL)) {
                return fdt32_to_cpu(*prop);
        }
        dprintf("[!] no interrupt field\n");
        return 0;
}


static void
HandleFdt(const void* fdt, int node, uint32 addressCells, uint32 sizeCells,
        uint32 interruptCells /* from parent node */)
{
        // TODO: handle different field sizes

        const char* name = fdt_get_name(fdt, node, NULL);
        if (strcmp(name, "cpus") == 0) {
                if (uint32* prop = (uint32*)fdt_getprop(fdt, node, "timebase-frequency", NULL))
                        sTimerFrequrency = fdt32_to_cpu(*prop);
        }

        const char* device_type = (const char*)fdt_getprop(fdt, node,
                "device_type", NULL);
        if (device_type != NULL && strcmp(device_type, "memory") == 0) {
                gMemBase = (uint8*)fdt64_to_cpu(*((uint64*)fdt_getprop(fdt, node,
                        "reg", NULL) + 0));
                gTotalMem = fdt64_to_cpu(*((uint64*)fdt_getprop(fdt, node,
                        "reg", NULL) + 1));
                return;
        }
        int compatibleLen;
        const char* compatible = (const char*)fdt_getprop(fdt, node,
                "compatible", &compatibleLen);
        if (compatible == NULL) return;
        if (HasFdtString(compatible, compatibleLen, "riscv,clint0")) {
                uint64* reg = (uint64*)fdt_getprop(fdt, node, "reg", NULL);
                sClint.start = fdt64_to_cpu(*(reg + 0));
                sClint.size  = fdt64_to_cpu(*(reg + 1));
                gClintRegs = (ClintRegs*)sClint.start;
        } else if (HasFdtString(compatible, compatibleLen, "riscv,plic0")) {
                GetReg(fdt, node, addressCells, sizeCells, 0, sPlic);
                int propSize;
                if (uint32* prop = (uint32*)fdt_getprop(fdt, node, "interrupts-extended", &propSize)) {
                        dprintf("PLIC contexts\n");
                        uint32 contextId = 0;
                        for (uint32 *it = prop; (uint8_t*)it - (uint8_t*)prop < propSize; it += 2) {
                                uint32 phandle = fdt32_to_cpu(*it);
                                uint32 interrupt = fdt32_to_cpu(*(it + 1));
                                if (interrupt == sExternInt) {
                                        CpuInfo* cpuInfo = smp_find_cpu(phandle);
                                        dprintf("  context %" B_PRIu32 ": %" B_PRIu32 "\n", contextId, phandle);
                                        if (cpuInfo != NULL) {
                                                cpuInfo->plicContext = contextId;
                                                dprintf("    hartId: %" B_PRIu32 "\n", cpuInfo->hartId);
                                        }
                                }
                                contextId++;
                        }
                }
        } else if (HasFdtString(compatible, compatibleLen, "virtio,mmio")) {
                uint64* reg = (uint64*)fdt_getprop(fdt, node, "reg", NULL);
                virtio_register(
                        fdt64_to_cpu(*(reg + 0)), fdt64_to_cpu(*(reg + 1)),
                        GetInterrupt(fdt, node));
        } else if (
                strcmp(sUart.kind, "") == 0 && (
                        HasFdtString(compatible, compatibleLen, "ns16550a") ||
                        HasFdtString(compatible, compatibleLen, "sifive,uart0"))
        ) {
                if (HasFdtString(compatible, compatibleLen, "ns16550a"))
                        strcpy(sUart.kind, UART_KIND_8250);
                else if (HasFdtString(compatible, compatibleLen, "sifive,uart0"))
                        strcpy(sUart.kind, UART_KIND_SIFIVE);

                uint64* reg = (uint64*)fdt_getprop(fdt, node, "reg", NULL);
                sUart.regs.start = fdt64_to_cpu(*(reg + 0));
                sUart.regs.size  = fdt64_to_cpu(*(reg + 1));
                sUart.irq = GetInterrupt(fdt, node);
                const void* prop = fdt_getprop(fdt, node, "clock-frequency", NULL);
                sUart.clock = (prop == NULL) ? 0 : fdt32_to_cpu(*(uint32*)prop);
        } else if (HasFdtString(compatible, compatibleLen, "qemu,fw-cfg-mmio")) {
                gFwCfgRegs = (FwCfgRegs *volatile)
                        fdt64_to_cpu(*(uint64*)fdt_getprop(fdt, node, "reg", NULL));
        } else if (HasFdtString(compatible, compatibleLen, "simple-framebuffer")) {
                gFramebuf.colors = (uint32*)fdt64_to_cpu(
                        *(uint64*)fdt_getprop(fdt, node, "reg", NULL));
                gFramebuf.stride = fdt32_to_cpu(
                        *(uint32*)fdt_getprop(fdt, node, "stride", NULL)) / 4;
                gFramebuf.width = fdt32_to_cpu(
                        *(uint32*)fdt_getprop(fdt, node, "width", NULL));
                gFramebuf.height = fdt32_to_cpu(
                        *(uint32*)fdt_getprop(fdt, node, "height", NULL));
        }
}


void
fdt_init(void* fdt)
{
        dprintf("FDT: %p\n", fdt);
        gFdt = fdt;

        int res = fdt_check_header(gFdt);
        if (res != 0) {
                panic("Invalid FDT: %s\n", fdt_strerror(res));
        }

        dprintf("FDT valid, size: %" B_PRIu32 "\n", fdt_totalsize(gFdt));

        int node = -1;
        int depth = -1;
        while ((node = fdt_next_node(gFdt, node, &depth)) >= 0 && depth >= 0) {
                HandleFdt(gFdt, node, 2, 2, 1);
        }
}


void
fdt_set_kernel_args()
{
        uint32_t fdtSize = fdt_totalsize(gFdt);

        // libfdt requires 8-byte alignment
        gKernelArgs.arch_args.fdt = (void*)(addr_t)kernel_args_malloc(fdtSize, 8);

        if (gKernelArgs.arch_args.fdt != NULL)
                memcpy(gKernelArgs.arch_args.fdt, gFdt, fdt_totalsize(gFdt));
        else
                panic("unable to malloc for FDT!\n");

        gKernelArgs.arch_args.timerFrequency = sTimerFrequrency;

        gKernelArgs.arch_args.htif.start = (addr_t)gHtifRegs;
        gKernelArgs.arch_args.htif.size = sizeof(HtifRegs);

        gKernelArgs.arch_args.plic  = sPlic;
        gKernelArgs.arch_args.clint = sClint;
        gKernelArgs.arch_args.uart  = sUart;
}