root/sys/arch/aarch64/aarch64/db_interface.c
/* $NetBSD: db_interface.c,v 1.25 2026/05/30 06:30:14 skrll Exp $ */

/*
 * Copyright (c) 2017 Ryo Shimizu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "opt_cpuoptions.h"

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: db_interface.c,v 1.25 2026/05/30 06:30:14 skrll Exp $");

#include <sys/param.h>
#include <sys/types.h>

#include <uvm/uvm.h>
#include <uvm/uvm_ddb.h>
#include <uvm/uvm_prot.h>
#ifdef __HAVE_PMAP_PV_TRACK
#include <uvm/pmap/pmap_pvt.h>
#endif

#include <aarch64/armreg.h>
#include <aarch64/db_machdep.h>
#include <aarch64/locore.h>
#include <aarch64/machdep.h>
#include <aarch64/pmap.h>

#include <arm/cpufunc.h>

#include <ddb/db_access.h>
#include <ddb/db_command.h>
#include <ddb/db_output.h>
#include <ddb/db_variables.h>
#include <ddb/db_sym.h>
#include <ddb/db_extern.h>
#include <ddb/db_interface.h>

#include <dev/cons.h>

db_regs_t ddb_regs;

static bool
db_accessible_address(vaddr_t addr, bool readonly)
{
        register_t s;
        uint64_t par;
        int space;

        space = aarch64_addressspace(addr);
        if (space != AARCH64_ADDRSPACE_LOWER &&
            space != AARCH64_ADDRSPACE_UPPER)
                return false;

        s = daif_disable(DAIF_I|DAIF_F);

        switch (aarch64_addressspace(addr)) {
        case AARCH64_ADDRSPACE_LOWER:
                if (readonly)
                        reg_s1e0r_write(addr);
                else
                        reg_s1e0w_write(addr);
                break;
        case AARCH64_ADDRSPACE_UPPER:
                if (readonly)
                        reg_s1e1r_write(addr);
                else
                        reg_s1e1w_write(addr);
                break;
        }
        isb();
        par = reg_par_el1_read();

        reg_daif_write(s);

        return ((par & PAR_F) == 0);
}

void
db_read_bytes(vaddr_t addr, size_t size, char *data)
{
        vaddr_t lastpage = -1;
        const char *src;

        for (src = (const char *)addr; size > 0;) {
                const vaddr_t va = (vaddr_t)src;
                uintptr_t tmp;

                if (lastpage != atop(va) && !db_accessible_address(va, true)) {
                        db_printf("address %p is invalid\n", src);
                        memset(data, 0, size);  /* stubs are filled by zero */
                        return;
                }
                lastpage = atop(va);

                if (aarch64_pan_enabled)
                        reg_pan_write(0); /* disable PAN */

                tmp = (uintptr_t)src | (uintptr_t)data;
                if (size >= 8 && (tmp & 7) == 0) {
                        *(uint64_t *)data = *(const uint64_t *)src;
                        src += 8;
                        data += 8;
                        size -= 8;
                } else if (size >= 4 && (tmp & 3) == 0) {
                        *(uint32_t *)data = *(const uint32_t *)src;
                        src += 4;
                        data += 4;
                        size -= 4;
                } else if (size >= 2 && (tmp & 1) == 0) {
                        *(uint16_t *)data = *(const uint16_t *)src;
                        src += 2;
                        data += 2;
                        size -= 2;
                } else {
                        *data++ = *src++;
                        size--;
                }

                if (aarch64_pan_enabled)
                        reg_pan_write(1); /* enable PAN */
        }
}

static void
db_write_text(vaddr_t addr, size_t size, const char *data)
{
        pt_entry_t *ptep, pte;
        size_t s;

        /*
         * consider page boundary, and
         * it works even if kernel_text is mapped with L2 or L3.
         */
        if (atop(addr) != atop(addr + size - 1)) {
                s = PAGE_SIZE - (addr & PAGE_MASK);
                db_write_text(addr, s, data);
                addr += s;
                size -= s;
                data += s;
        }
        while (size > 0) {
                ptep = kvtopte(addr);
                KASSERT(ptep != NULL);

                /*
                 * change to writable.  it is required to keep execute permission.
                 * because if the block/page to which the target address belongs is
                 * the same as the block/page to which this function belongs, then
                 * if PROT_EXECUTE is dropped and TLB is invalidated, the program
                 * will stop...
                 */
                /* old pte is returned by pmap_kvattr */
                pte = pmap_kvattr(ptep, VM_PROT_EXECUTE | VM_PROT_READ | VM_PROT_WRITE);
                /* dsb(ishst) included in aarch64_tlbi_by_va */
                aarch64_tlbi_by_va(addr);

                s = size;
                if (size > PAGE_SIZE)
                        s = PAGE_SIZE;

                memcpy((void *)addr, data, s);
                cpu_icache_sync_range(addr, size);

                /* restore pte */
                *ptep = pte;
                /* dsb(ishst) included in aarch64_tlbi_by_va */
                aarch64_tlbi_by_va(addr);

                addr += s;
                size -= s;
                data += s;
        }
}

void
db_write_bytes(vaddr_t addr, size_t size, const char *data)
{
        vaddr_t kernstart, datastart;
        vaddr_t lastpage = -1;
        char *dst;

        /* if readonly page, require changing attribute to write */
        extern char __kernel_text[], __data_start[];
        kernstart = trunc_page((vaddr_t)__kernel_text);
        datastart = trunc_page((vaddr_t)__data_start);
        if (kernstart <= addr && addr < datastart) {
                size_t s;

                s = datastart - addr;
                if (s > size)
                        s = size;
                db_write_text(addr, s, data);
                addr += s;
                size -= s;
                data += s;
        }

        for (dst = (char *)addr; size > 0;) {
                const vaddr_t va = (vaddr_t)dst;
                uintptr_t tmp;

                if (lastpage != atop(va) && !db_accessible_address(va, false)) {
                        db_printf("address %p is invalid\n", dst);
                        return;
                }
                lastpage = atop(va);

                if (aarch64_pan_enabled)
                        reg_pan_write(0); /* disable PAN */

                tmp = (uintptr_t)dst | (uintptr_t)data;
                if (size >= 8 && (tmp & 7) == 0) {
                        *(uint64_t *)dst = *(const uint64_t *)data;
                        dst += 8;
                        data += 8;
                        size -= 8;
                } else if (size >= 4 && (tmp & 3) == 0) {
                        *(uint32_t *)dst = *(const uint32_t *)data;
                        dst += 4;
                        data += 4;
                        size -= 4;
                } else if (size >= 2 && (tmp & 1) == 0) {
                        *(uint16_t *)dst = *(const uint16_t *)data;
                        dst += 2;
                        data += 2;
                        size -= 2;
                } else {
                        *dst++ = *data++;
                        size--;
                }

                if (aarch64_pan_enabled)
                        reg_pan_write(1); /* enable PAN */
        }
}

/*
 * return register value of $X0..$X30, $SP or 0($XZR)
 */
static uint64_t
db_fetch_reg(unsigned int reg, db_regs_t *regs, bool use_sp)
{
        if (reg >= 32)
                panic("db_fetch_reg: botch");

        if (reg == 31) {
                /* $SP or $XZR */
                return use_sp ? regs->tf_sp : 0;
        }
        return regs->tf_reg[reg];
}

static inline uint64_t
SignExtend(int bitwidth, uint64_t imm, unsigned int multiply)
{
        const uint64_t signbit = ((uint64_t)1 << (bitwidth - 1));
        const uint64_t immmax = signbit << 1;

        if (imm & signbit)
                imm -= immmax;
        return imm * multiply;
}

db_addr_t
db_branch_taken(db_expr_t inst, db_addr_t pc, db_regs_t *regs)
{
        LE32TOH(inst);

#define INSN_FMT_RN(insn)               (((insn) >> 5) & 0x1f)
#define INSN_FMT_IMM26(insn)    ((insn) & 0x03ffffff)
#define INSN_FMT_IMM19(insn)    (((insn) >> 5) & 0x7ffff)
#define INSN_FMT_IMM14(insn)    (((insn) >> 5) & 0x3fff)

        if ((inst & 0xfffffc1f) == 0xd65f0000 ||        /* ret xN */
            (inst & 0xfffffc1f) == 0xd63f0000 ||        /* blr xN */
            (inst & 0xfffffc1f) == 0xd61f0000) {        /* br xN */
                return db_fetch_reg(INSN_FMT_RN(inst), regs, false);
        }

        if ((inst & 0xfc000000) == 0x94000000 ||        /* bl imm */
            (inst & 0xfc000000) == 0x14000000) {        /* b imm */
                return SignExtend(26, INSN_FMT_IMM26(inst), 4) + pc;
        }

        if ((inst & 0xff000010) == 0x54000000 ||        /* b.cond */
            (inst & 0x7f000000) == 0x35000000 ||        /* cbnz */
            (inst & 0x7f000000) == 0x34000000) {        /* cbz */
                return SignExtend(19, INSN_FMT_IMM19(inst), 4) + pc;
        }

        if ((inst & 0x7f000000) == 0x37000000 ||        /* tbnz */
            (inst & 0x7f000000) == 0x36000000) {        /* tbz */
                return SignExtend(14, INSN_FMT_IMM14(inst), 4) + pc;
        }

        panic("branch_taken: botch");
}

bool
db_inst_unconditional_flow_transfer(db_expr_t inst)
{
        LE32TOH(inst);

        if ((inst & 0xfffffc1f) == 0xd65f0000 ||        /* ret xN */
            (inst & 0xfc000000) == 0x94000000 ||        /* bl */
            (inst & 0xfffffc1f) == 0xd63f0000 ||        /* blr */
            (inst & 0xfc000000) == 0x14000000 ||        /* b imm */
            (inst & 0xfffffc1f) == 0xd61f0000)          /* br */
                return true;

#define INSN_FMT_COND(insn)     ((insn) & 0xf)
#define CONDITION_AL    14

        if ((inst & 0xff000010) == 0x54000000 &&        /* b.cond */
            INSN_FMT_COND(inst) == CONDITION_AL)        /* always? */
                return true;

        return false;
}

void
db_pte_print(pt_entry_t pte, int level,
    void (*pr)(const char *, ...) __printflike(1, 2))
{
        if (pte == 0) {
                pr(" UNUSED\n");
                return;
        }

        pr(" %s", (pte & LX_VALID) ? "VALID" : "**INVALID**");

        if (level == 0 ||
            (level == 1 && l1pde_is_table(pte)) ||
            (level == 2 && l2pde_is_table(pte))) {

                /* L0/L1/L2 TABLE */
                if (level == 0 && (pte & LX_TYPE) != LX_TYPE_TBL)
                        pr(" **ILLEGAL TYPE**"); /* L0 doesn't support block */
                else
                        pr(" L%d-TABLE", level);

                pr(", PA=%lx", l0pde_pa(pte));

                if (pte & LX_TBL_NSTABLE)
                        pr(", NSTABLE");
                if (pte & LX_TBL_APTABLE)
                        pr(", APTABLE");
                if (pte & LX_TBL_UXNTABLE)
                        pr(", UXNTABLE");
                if (pte & LX_TBL_PXNTABLE)
                        pr(", PXNTABLE");

        } else if ((level == 1 && l1pde_is_block(pte)) ||
            (level == 2 && l2pde_is_block(pte)) ||
            level == 3) {

                /* L1/L2 BLOCK or L3 PAGE */
                switch (level) {
                case 1:
                        pr(" L1(1G)-BLOCK");
                        break;
                case 2:
                        pr(" L2(2M)-BLOCK");
                        break;
                case 3:
                        pr(" %s", l3pte_is_page(pte) ?
                            "L3(4K)-PAGE" : "**ILLEGAL TYPE**");
                        break;
                }

                pr(", PA=%lx", l3pte_pa(pte));

#ifdef ARMV81_HAFDBS
                if (pte & LX_BLKPAG_DBM)
                        pr(", %s", "DBM");
#endif
                pr(", %s", (pte & LX_BLKPAG_UXN) ? "UXN" : "UX");
                pr(", %s", (pte & LX_BLKPAG_PXN) ? "PXN" : "PX");

                if (pte & LX_BLKPAG_CONTIG)
                        pr(", CONTIG");

                pr(", %s", (pte & LX_BLKPAG_NG) ? "nG" : "G");
                pr(", %s", (pte & LX_BLKPAG_AF) ?
                    "accessible" :
                    "**fault** ");

                switch (pte & LX_BLKPAG_SH) {
                case LX_BLKPAG_SH_NS:
                        pr(", SH_NS");
                        break;
                case LX_BLKPAG_SH_OS:
                        pr(", SH_OS");
                        break;
                case LX_BLKPAG_SH_IS:
                        pr(", SH_IS");
                        break;
                default:
                        pr(", SH_??");
                        break;
                }

                pr(", %s", (pte & LX_BLKPAG_AP_RO) ? "RO" : "RW");
                pr(", %s", (pte & LX_BLKPAG_APUSER) ? "EL0" : "EL1");
                pr(", %s", (pte & LX_BLKPAG_NS) ? "NS" : "secure");

                switch (pte & LX_BLKPAG_ATTR_MASK) {
                case LX_BLKPAG_ATTR_NORMAL_WB:
                        pr(", WB");
                        break;
                case LX_BLKPAG_ATTR_NORMAL_NC:
                        pr(", NC");
                        break;
                case LX_BLKPAG_ATTR_NORMAL_WT:
                        pr(", WT");
                        break;
                case LX_BLKPAG_ATTR_DEVICE_MEM:
                        pr(", DEV");
                        break;
                case LX_BLKPAG_ATTR_DEVICE_MEM_NP:
                        pr(", DEV(NP)");
                        break;
                default:
                        pr(", ATTR(%lu)", __SHIFTOUT(pte, LX_BLKPAG_ATTR_INDX));
                        break;
                }

                if (pte & LX_BLKPAG_OS_0)
                        pr(", " PMAP_PTE_OS0);
                if (pte & LX_BLKPAG_OS_1)
                        pr(", " PMAP_PTE_OS1);
                if (pte & LX_BLKPAG_OS_2)
                        pr(", " PMAP_PTE_OS2);
                if (pte & LX_BLKPAG_OS_3)
                        pr(", " PMAP_PTE_OS3);
        } else {
                pr(" **ILLEGAL TYPE**");
        }
        pr("\n");
}

void
db_pteinfo(vaddr_t va, void (*pr)(const char *, ...) __printflike(1, 2))
{
        struct vm_page *pg;
        bool user;
        pd_entry_t *l0, *l1, *l2, *l3;
        pd_entry_t pde;
        pt_entry_t pte;
        uint64_t ttbr;
        paddr_t pa;
        unsigned int idx;

        switch (aarch64_addressspace(va)) {
        case AARCH64_ADDRSPACE_UPPER:
                user = false;
                ttbr = reg_ttbr1_el1_read();
                break;
        case AARCH64_ADDRSPACE_LOWER:
                user = true;
                ttbr = reg_ttbr0_el1_read();
                break;
        default:
                pr("illegal address space\n");
                return;
        }
        pa = ttbr & TTBR_BADDR;
        l0 = (pd_entry_t *)AARCH64_PA_TO_KVA(pa);

        /*
         * traverse L0 -> L1 -> L2 -> L3 table
         */
        pr("TTBR%d=%016"PRIx64", pa=%016"PRIxPADDR", va=%p",
            user ? 0 : 1, ttbr, pa, l0);
        pr(", input-va=%016"PRIxVADDR
            ", L0-index=%ld, L1-index=%ld, L2-index=%ld, L3-index=%ld\n",
            va,
            (va & L0_ADDR_BITS) >> L0_SHIFT,
            (va & L1_ADDR_BITS) >> L1_SHIFT,
            (va & L2_ADDR_BITS) >> L2_SHIFT,
            (va & L3_ADDR_BITS) >> L3_SHIFT);

        idx = l0pde_index(va);
        pde = l0[idx];

        pr("L0[%3d]=%016"PRIx64":", idx, pde);
        db_pte_print(pde, 0, pr);

        if (!l0pde_valid(pde))
                return;

        l1 = (pd_entry_t *)AARCH64_PA_TO_KVA(l0pde_pa(pde));
        idx = l1pde_index(va);
        pde = l1[idx];

        pr(" L1[%3d]=%016"PRIx64":", idx, pde);
        db_pte_print(pde, 1, pr);

        if (!l1pde_valid(pde) || l1pde_is_block(pde))
                return;

        l2 = (pd_entry_t *)AARCH64_PA_TO_KVA(l1pde_pa(pde));
        idx = l2pde_index(va);
        pde = l2[idx];

        pr("  L2[%3d]=%016"PRIx64":", idx, pde);
        db_pte_print(pde, 2, pr);

        if (!l2pde_valid(pde) || l2pde_is_block(pde))
                return;

        l3 = (pd_entry_t *)AARCH64_PA_TO_KVA(l2pde_pa(pde));
        idx = l3pte_index(va);
        pte = l3[idx];

        pr("   L3[%3d]=%016"PRIx64":", idx, pte);
        db_pte_print(pte, 3, pr);

        pa = l3pte_pa(pte);
        pg = PHYS_TO_VM_PAGE(pa);

        if (pg != NULL) {
                uvm_page_printit(pg, false, pr);

                pmap_db_mdpg_print(pg, pr);
        } else {
#ifdef __HAVE_PMAP_PV_TRACK
                if (pmap_pv_tracked(pa))
                        pr("PV tracked");
                else
                        pr("No VM_PAGE or PV tracked");
#else
                pr("no VM_PAGE\n");
#endif
        }
}

static void
dump_ln_table(bool countmode, pd_entry_t *pdp, int level, int lnindex,
    vaddr_t va, void (*pr)(const char *, ...) __printflike(1, 2))
{
        struct vm_page *pg;
        pd_entry_t pde;
        paddr_t pa;
        int i, n;
        const char *spaces[4] = { " ", "  ", "   ", "    " };
        const char *spc = spaces[level];

        pa = AARCH64_KVA_TO_PA((vaddr_t)pdp);
        pg = PHYS_TO_VM_PAGE(pa);

        if (pg == NULL) {
                pr("%sL%d: pa=%lx pg=NULL\n", spc, level, pa);
        } else {
                pr("%sL%d: pa=%lx pg=%p\n", spc, level, pa, pg);
        }

        for (i = n = 0; i < Ln_ENTRIES; i++) {
                db_read_bytes((db_addr_t)&pdp[i], sizeof(pdp[i]), (char *)&pde);
                if (lxpde_valid(pde)) {
                        if (!countmode)
                                pr("%sL%d[%3d] %3dth, va=%016lx, pte=%016lx:",
                                    spc, level, i, n, va, pde);
                        n++;

                        if ((level != 0 && level != 3 && l1pde_is_block(pde)) ||
                            (level == 3 && l3pte_is_page(pde))) {
                                if (!countmode)
                                        db_pte_print(pde, level, pr);
                        } else if (level != 3 && l1pde_is_table(pde)) {
                                if (!countmode)
                                        db_pte_print(pde, level, pr);
                                pa = l0pde_pa(pde);
                                dump_ln_table(countmode,
                                    (pd_entry_t *)AARCH64_PA_TO_KVA(pa),
                                    level + 1, i, va, pr);
                        } else {
                                if (!countmode)
                                        db_pte_print(pde, level, pr);
                        }
                }

                switch (level) {
                case 0:
                        va += L0_SIZE;
                        break;
                case 1:
                        va += L1_SIZE;
                        break;
                case 2:
                        va += L2_SIZE;
                        break;
                case 3:
                        va += L3_SIZE;
                        break;
                }
        }

        if (level == 0)
                pr("L0 has %d entries\n", n);
        else
                pr("%sL%d[%3d] has %d L%d entries\n", spaces[level - 1],
                    level - 1, lnindex, n, level);
}

static void
db_dump_l0table(bool countmode, pd_entry_t *pdp, vaddr_t va_base,
    void (*pr)(const char *, ...) __printflike(1, 2))
{
        dump_ln_table(countmode, pdp, 0, 0, va_base, pr);
}

void
db_ttbrdump(bool countmode, vaddr_t va,
    void (*pr)(const char *, ...) __printflike(1, 2))
{
        struct pmap *pm, _pm;

        pm = (struct pmap *)va;
        db_read_bytes((db_addr_t)va, sizeof(_pm), (char *)&_pm);

        pr("pmap=%p\n", pm);
        pmap_db_pmap_print(&_pm, pr);

        db_dump_l0table(countmode, pmap_l0table(pm),
            (pm == pmap_kernel()) ? 0xffff000000000000UL : 0, pr);
}

void
cpu_Debugger(void)
{
        __asm __volatile ("brk #0xffff");
}