root/sys/arch/powerpc/powerpc/fixup.c
/*      $NetBSD: fixup.c,v 1.13 2022/01/01 01:15:11 macallan Exp $      */
/*-
 * Copyright (c) 2010, 2011 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Raytheon BBN Technologies Corp and Defense Advanced Research Projects
 * Agency and which was developed by Matt Thomas of 3am Software Foundry.
 *
 * This material is based upon work supported by the Defense Advanced Research
 * Projects Agency and Space and Naval Warfare Systems Center, Pacific, under
 * Contract No. N66001-09-C-2073.
 * Approved for Public Release, Distribution Unlimited
 *
 * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fixup.c,v 1.13 2022/01/01 01:15:11 macallan Exp $");

#ifdef _KERNEL_OPT
#include "opt_ppcarch.h"
#endif

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

#include <powerpc/instr.h>
#include <powerpc/spr.h>
#include <powerpc/include/cpu.h>
#include <powerpc/include/oea/spr.h>

static inline void
fixup_jump(uint32_t *insnp, const struct powerpc_jump_fixup_info *jfi)
{
        union instr instr = { .i_int = *insnp };

        KASSERT(instr.i_any.i_opcd == OPC_B);

        instr.i_i.i_li = jfi->jfi_real - fixup_addr2offset(insnp);

        *insnp = instr.i_int;

        __asm(
                "dcbst  0,%0"   "\n\t"
                "sync"          "\n\t"
                "icbi   0,%0"   "\n\t"
                "sync"          "\n\t"
                "isync"
            :: "b"(insnp));
}

void
powerpc_fixup_stubs(uint32_t *start, uint32_t *end,
        uint32_t *stub_start, uint32_t *stub_end)
{
        extern uint32_t __stub_start[], __stub_end[];
#ifdef DEBUG
        size_t fixups_done = 0;
        uint64_t cycles = 0;
#ifdef PPC_OEA601
        if ((mfpvr() >> 16) == MPC601)
            cycles = rtc_nanosecs() >> 7;
        else
#endif
            cycles = mftb();
#endif

        if (stub_start == NULL) {
                stub_start = __stub_start;
                stub_end = __stub_end;
        }

        if (end > __stub_start)
                end = __stub_start;

        for (uint32_t *insnp = start; insnp < end; insnp++) {
                struct powerpc_jump_fixup_info fixup;
                union instr instr = { .i_int = *insnp };
                uint32_t *stub = insnp + instr.i_i.i_li;
                u_int opcode = instr.i_any.i_opcd;

                /*
                 * First we check to see if this is a jump and whether it is
                 * within the range we are interested in.
                 */
                if (opcode != OPC_B || stub < stub_start || stub_end <= stub)
                        continue;

                fixup.jfi_stub = fixup_addr2offset(stub);
                fixup.jfi_real = 0;

                /*
                 * We know it's a jump, now we need to figure out where it goes.
                 */
                register_t fixreg[32];
                register_t ctr = 0;
                uint32_t valid_mask = (1 << 1);
#ifdef DIAGNOSTIC
                int r_lr = -1;
#endif
                for (; stub < stub_end && fixup.jfi_real == 0; stub++) {
                        const union instr i = { .i_int = *stub };

                        switch (i.i_any.i_opcd) {
                        case OPC_integer_31: {
                                const u_int rs = i.i_x.i_rs;
                                const u_int ra = i.i_x.i_ra;
                                const u_int rb = i.i_x.i_rb;
                                switch (i.i_x.i_xo) {
                                case OPC31_MFSPR: {
#ifdef DIAGNOSTIC
                                        const u_int spr = (rb << 5) | ra;
                                        KASSERT(spr == SPR_LR);
                                        r_lr = rs;
#endif
                                        valid_mask |= (1 << rs);
                                        break;
                                }
                                case OPC31_MTSPR: {
#ifdef DIAGNOSTIC
                                        const u_int spr = (rb << 5) | ra;
                                        KASSERT(valid_mask & (1 << rs));
                                        KASSERT(spr == SPR_CTR);
#endif
                                        ctr = fixreg[rs];
                                        break;
                                }
                                case OPC31_OR: {
#ifdef DIAGNOSTIC
                                        KASSERT(valid_mask & (1 << rs));
                                        KASSERT(valid_mask & (1 << rb));
#endif
                                        fixreg[ra] = fixreg[rs] | fixreg[rb];
                                        valid_mask |= 1 << ra;
                                        break;
                                }
                                default:
                                        panic("%s: %p: unexpected insn 0x%08x",
                                            __func__, stub, i.i_int);
                                }
                                break;
                        }
                        case OPC_ADDI:
                        case OPC_ADDIS: {
                                const u_int rs = i.i_d.i_rs;
                                const u_int ra = i.i_d.i_ra;
                                register_t d = i.i_d.i_d << ((i.i_d.i_opcd & 1) * 16);
                                if (ra) {
                                        KASSERT(valid_mask & (1 << ra));
                                        d += fixreg[ra];
                                }
                                fixreg[rs] = d;
                                valid_mask |= (1 << rs);
                                break;
                        }
                        case OPC_LWZ: {
                                const u_int rs = i.i_d.i_rs;
                                const u_int ra = i.i_d.i_ra;
                                register_t addr = i.i_d.i_d;
                                if (ra) {
                                        KASSERT(valid_mask & (1 << ra));
                                        addr += fixreg[ra];
                                }
                                fixreg[rs] = *(uint32_t *)addr;
                                valid_mask |= (1 << rs);
                                break;
                        }
                        case OPC_STW: {
#ifdef DIAGNOSTIC
                                KASSERT((i.i_d.i_rs == r_lr || i.i_d.i_rs == 31) && i.i_d.i_ra == 1);
#endif
                                break;
                        }
                        case OPC_STWU: {
                                KASSERT(i.i_d.i_rs == 1 && i.i_d.i_ra == 1);
                                KASSERT(i.i_d.i_d < 0);
                                break;
                        }
                        case OPC_branch_19: {
#ifdef DIAGNOSTIC
                                KASSERT(r_lr == -1 || i.i_int == 0x4e800421);
                                KASSERT(r_lr != -1 || i.i_int == 0x4e800420);
#endif
                                if (ctr == 0) {
                                        panic("%s: jump at %p to %p would "
                                            "branch to 0", __func__, insnp,
                                            insnp + instr.i_i.i_li);
                                }
                                fixup.jfi_real = fixup_addr2offset(ctr);
                                break;
                        }
                        case OPC_RLWINM: {
                                // LLVM emits these for bool operands.
                                // Ignore them.
                                break;
                        }
                        default: {
                                panic("%s: %p: unexpected insn 0x%08x",
                                            __func__, stub, i.i_int);
                        }
                        }
                }
                KASSERT(fixup.jfi_real != 0);
                /*
                 * Now we know the real destination to branch to.  Replace the
                 * old displacement with the new displacement.
                 */
#if 0
                printf("%s: %p: change from %#x to %#x\n",
                    __func__, insnp, fixup.jfi_stub << 2, fixup.jfi_real << 2);
#endif
                fixup_jump(insnp, &fixup);
#ifdef DEBUG
                fixups_done++;
#endif
        }

#ifdef DEBUG

#ifdef PPC_OEA601
        if ((mfpvr() >> 16) == MPC601)
            cycles = (rtc_nanosecs() >> 7) - cycles;
        else
#endif
        cycles = mftb() - cycles;

        printf("%s: %zu fixup%s done in %"PRIu64" cycles\n", __func__,
            fixups_done, fixups_done == 1 ? "" : "s",
            cycles);
#endif
}

void
cpu_fixup_stubs(void)
{
        extern uint32_t _ftext[];
        extern uint32_t _etext[];

        powerpc_fixup_stubs(_ftext, _etext, NULL, NULL);
}