root/arch/powerpc/kernel/hw_breakpoint_constraints.c
// SPDX-License-Identifier: GPL-2.0+
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <asm/hw_breakpoint.h>
#include <asm/sstep.h>
#include <asm/cache.h>

static bool dar_in_user_range(unsigned long dar, struct arch_hw_breakpoint *info)
{
        return ((info->address <= dar) && (dar - info->address < info->len));
}

static bool ea_user_range_overlaps(unsigned long ea, int size,
                                   struct arch_hw_breakpoint *info)
{
        return ((ea < info->address + info->len) &&
                (ea + size > info->address));
}

static bool dar_in_hw_range(unsigned long dar, struct arch_hw_breakpoint *info)
{
        unsigned long hw_start_addr, hw_end_addr;

        hw_start_addr = ALIGN_DOWN(info->address, HW_BREAKPOINT_SIZE);
        hw_end_addr = ALIGN(info->address + info->len, HW_BREAKPOINT_SIZE);

        return ((hw_start_addr <= dar) && (hw_end_addr > dar));
}

static bool ea_hw_range_overlaps(unsigned long ea, int size,
                                 struct arch_hw_breakpoint *info)
{
        unsigned long hw_start_addr, hw_end_addr;
        unsigned long align_size = HW_BREAKPOINT_SIZE;

        /*
         * On p10 predecessors, quadword is handle differently then
         * other instructions.
         */
        if (!cpu_has_feature(CPU_FTR_ARCH_31) && size == 16)
                align_size = HW_BREAKPOINT_SIZE_QUADWORD;

        hw_start_addr = ALIGN_DOWN(info->address, align_size);
        hw_end_addr = ALIGN(info->address + info->len, align_size);

        return ((ea < hw_end_addr) && (ea + size > hw_start_addr));
}

/*
 * If hw has multiple DAWR registers, we also need to check all
 * dawrx constraint bits to confirm this is _really_ a valid event.
 * If type is UNKNOWN, but privilege level matches, consider it as
 * a positive match.
 */
static bool check_dawrx_constraints(struct pt_regs *regs, int type,
                                    struct arch_hw_breakpoint *info)
{
        if (OP_IS_LOAD(type) && !(info->type & HW_BRK_TYPE_READ))
                return false;

        /*
         * The Cache Management instructions other than dcbz never
         * cause a match. i.e. if type is CACHEOP, the instruction
         * is dcbz, and dcbz is treated as Store.
         */
        if ((OP_IS_STORE(type) || type == CACHEOP) && !(info->type & HW_BRK_TYPE_WRITE))
                return false;

        if (is_kernel_addr(regs->nip) && !(info->type & HW_BRK_TYPE_KERNEL))
                return false;

        if (user_mode(regs) && !(info->type & HW_BRK_TYPE_USER))
                return false;

        return true;
}

/*
 * Return true if the event is valid wrt dawr configuration,
 * including extraneous exception. Otherwise return false.
 */
bool wp_check_constraints(struct pt_regs *regs, ppc_inst_t instr,
                          unsigned long ea, int type, int size,
                          struct arch_hw_breakpoint *info)
{
        bool in_user_range = dar_in_user_range(regs->dar, info);
        bool dawrx_constraints;

        /*
         * 8xx supports only one breakpoint and thus we can
         * unconditionally return true.
         */
        if (IS_ENABLED(CONFIG_PPC_8xx)) {
                if (!in_user_range)
                        info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
                return true;
        }

        if (unlikely(ppc_inst_equal(instr, ppc_inst(0)))) {
                if (cpu_has_feature(CPU_FTR_ARCH_31) &&
                    !dar_in_hw_range(regs->dar, info))
                        return false;

                return true;
        }

        dawrx_constraints = check_dawrx_constraints(regs, type, info);

        if (type == UNKNOWN) {
                if (cpu_has_feature(CPU_FTR_ARCH_31) &&
                    !dar_in_hw_range(regs->dar, info))
                        return false;

                return dawrx_constraints;
        }

        if (ea_user_range_overlaps(ea, size, info))
                return dawrx_constraints;

        if (ea_hw_range_overlaps(ea, size, info)) {
                if (dawrx_constraints) {
                        info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
                        return true;
                }
        }
        return false;
}

void wp_get_instr_detail(struct pt_regs *regs, ppc_inst_t *instr,
                         int *type, int *size, unsigned long *ea)
{
        struct instruction_op op;
        int err;

        pagefault_disable();
        err = __get_user_instr(*instr, (void __user *)regs->nip);
        pagefault_enable();

        if (err)
                return;

        analyse_instr(&op, regs, *instr);
        *type = GETTYPE(op.type);
        *ea = op.ea;

        if (!(regs->msr & MSR_64BIT))
                *ea &= 0xffffffffUL;


        *size = GETSIZE(op.type);
        if (*type == CACHEOP) {
                *size = l1_dcache_bytes();
                *ea &= ~(*size - 1);
        } else if (*type == LOAD_VMX || *type == STORE_VMX) {
                *ea &= ~(*size - 1);
        }
}