Path: blob/master/arch/loongarch/kernel/hw_breakpoint.c
26424 views
// SPDX-License-Identifier: GPL-2.01/*2* Copyright (C) 2022-2023 Loongson Technology Corporation Limited3*/4#define pr_fmt(fmt) "hw-breakpoint: " fmt56#include <linux/hw_breakpoint.h>7#include <linux/kprobes.h>8#include <linux/perf_event.h>910#include <asm/hw_breakpoint.h>1112/* Breakpoint currently in use for each BRP. */13static DEFINE_PER_CPU(struct perf_event *, bp_on_reg[LOONGARCH_MAX_BRP]);1415/* Watchpoint currently in use for each WRP. */16static DEFINE_PER_CPU(struct perf_event *, wp_on_reg[LOONGARCH_MAX_WRP]);1718int hw_breakpoint_slots(int type)19{20/*21* We can be called early, so don't rely on22* our static variables being initialised.23*/24switch (type) {25case TYPE_INST:26return get_num_brps();27case TYPE_DATA:28return get_num_wrps();29default:30pr_warn("unknown slot type: %d\n", type);31return 0;32}33}3435#define READ_WB_REG_CASE(OFF, N, REG, T, VAL) \36case (OFF + N): \37LOONGARCH_CSR_WATCH_READ(N, REG, T, VAL); \38break3940#define WRITE_WB_REG_CASE(OFF, N, REG, T, VAL) \41case (OFF + N): \42LOONGARCH_CSR_WATCH_WRITE(N, REG, T, VAL); \43break4445#define GEN_READ_WB_REG_CASES(OFF, REG, T, VAL) \46READ_WB_REG_CASE(OFF, 0, REG, T, VAL); \47READ_WB_REG_CASE(OFF, 1, REG, T, VAL); \48READ_WB_REG_CASE(OFF, 2, REG, T, VAL); \49READ_WB_REG_CASE(OFF, 3, REG, T, VAL); \50READ_WB_REG_CASE(OFF, 4, REG, T, VAL); \51READ_WB_REG_CASE(OFF, 5, REG, T, VAL); \52READ_WB_REG_CASE(OFF, 6, REG, T, VAL); \53READ_WB_REG_CASE(OFF, 7, REG, T, VAL); \54READ_WB_REG_CASE(OFF, 8, REG, T, VAL); \55READ_WB_REG_CASE(OFF, 9, REG, T, VAL); \56READ_WB_REG_CASE(OFF, 10, REG, T, VAL); \57READ_WB_REG_CASE(OFF, 11, REG, T, VAL); \58READ_WB_REG_CASE(OFF, 12, REG, T, VAL); \59READ_WB_REG_CASE(OFF, 13, REG, T, VAL);6061#define GEN_WRITE_WB_REG_CASES(OFF, REG, T, VAL) \62WRITE_WB_REG_CASE(OFF, 0, REG, T, VAL); \63WRITE_WB_REG_CASE(OFF, 1, REG, T, VAL); \64WRITE_WB_REG_CASE(OFF, 2, REG, T, VAL); \65WRITE_WB_REG_CASE(OFF, 3, REG, T, VAL); \66WRITE_WB_REG_CASE(OFF, 4, REG, T, VAL); \67WRITE_WB_REG_CASE(OFF, 5, REG, T, VAL); \68WRITE_WB_REG_CASE(OFF, 6, REG, T, VAL); \69WRITE_WB_REG_CASE(OFF, 7, REG, T, VAL); \70WRITE_WB_REG_CASE(OFF, 8, REG, T, VAL); \71WRITE_WB_REG_CASE(OFF, 9, REG, T, VAL); \72WRITE_WB_REG_CASE(OFF, 10, REG, T, VAL); \73WRITE_WB_REG_CASE(OFF, 11, REG, T, VAL); \74WRITE_WB_REG_CASE(OFF, 12, REG, T, VAL); \75WRITE_WB_REG_CASE(OFF, 13, REG, T, VAL);7677static u64 read_wb_reg(int reg, int n, int t)78{79u64 val = 0;8081switch (reg + n) {82GEN_READ_WB_REG_CASES(CSR_CFG_ADDR, ADDR, t, val);83GEN_READ_WB_REG_CASES(CSR_CFG_MASK, MASK, t, val);84GEN_READ_WB_REG_CASES(CSR_CFG_CTRL, CTRL, t, val);85GEN_READ_WB_REG_CASES(CSR_CFG_ASID, ASID, t, val);86default:87pr_warn("Attempt to read from unknown breakpoint register %d\n", n);88}8990return val;91}92NOKPROBE_SYMBOL(read_wb_reg);9394static void write_wb_reg(int reg, int n, int t, u64 val)95{96switch (reg + n) {97GEN_WRITE_WB_REG_CASES(CSR_CFG_ADDR, ADDR, t, val);98GEN_WRITE_WB_REG_CASES(CSR_CFG_MASK, MASK, t, val);99GEN_WRITE_WB_REG_CASES(CSR_CFG_CTRL, CTRL, t, val);100GEN_WRITE_WB_REG_CASES(CSR_CFG_ASID, ASID, t, val);101default:102pr_warn("Attempt to write to unknown breakpoint register %d\n", n);103}104}105NOKPROBE_SYMBOL(write_wb_reg);106107enum hw_breakpoint_ops {108HW_BREAKPOINT_INSTALL,109HW_BREAKPOINT_UNINSTALL,110};111112/*113* hw_breakpoint_slot_setup - Find and setup a perf slot according to operations114*115* @slots: pointer to array of slots116* @max_slots: max number of slots117* @bp: perf_event to setup118* @ops: operation to be carried out on the slot119*120* Return:121* slot index on success122* -ENOSPC if no slot is available/matches123* -EINVAL on wrong operations parameter124*/125126static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots,127struct perf_event *bp, enum hw_breakpoint_ops ops)128{129int i;130struct perf_event **slot;131132for (i = 0; i < max_slots; ++i) {133slot = &slots[i];134switch (ops) {135case HW_BREAKPOINT_INSTALL:136if (!*slot) {137*slot = bp;138return i;139}140break;141case HW_BREAKPOINT_UNINSTALL:142if (*slot == bp) {143*slot = NULL;144return i;145}146break;147default:148pr_warn_once("Unhandled hw breakpoint ops %d\n", ops);149return -EINVAL;150}151}152153return -ENOSPC;154}155156void ptrace_hw_copy_thread(struct task_struct *tsk)157{158memset(tsk->thread.hbp_break, 0, sizeof(tsk->thread.hbp_break));159memset(tsk->thread.hbp_watch, 0, sizeof(tsk->thread.hbp_watch));160}161162/*163* Unregister breakpoints from this task and reset the pointers in the thread_struct.164*/165void flush_ptrace_hw_breakpoint(struct task_struct *tsk)166{167int i;168struct thread_struct *t = &tsk->thread;169170for (i = 0; i < LOONGARCH_MAX_BRP; i++) {171if (t->hbp_break[i]) {172unregister_hw_breakpoint(t->hbp_break[i]);173t->hbp_break[i] = NULL;174}175}176177for (i = 0; i < LOONGARCH_MAX_WRP; i++) {178if (t->hbp_watch[i]) {179unregister_hw_breakpoint(t->hbp_watch[i]);180t->hbp_watch[i] = NULL;181}182}183}184185static int hw_breakpoint_control(struct perf_event *bp,186enum hw_breakpoint_ops ops)187{188u32 ctrl, privilege;189int i, max_slots, enable;190struct pt_regs *regs;191struct perf_event **slots;192struct arch_hw_breakpoint *info = counter_arch_bp(bp);193194if (arch_check_bp_in_kernelspace(info))195privilege = CTRL_PLV0_ENABLE;196else197privilege = CTRL_PLV3_ENABLE;198199/* Whether bp belongs to a task. */200if (bp->hw.target)201regs = task_pt_regs(bp->hw.target);202203if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {204/* Breakpoint */205slots = this_cpu_ptr(bp_on_reg);206max_slots = boot_cpu_data.watch_ireg_count;207} else {208/* Watchpoint */209slots = this_cpu_ptr(wp_on_reg);210max_slots = boot_cpu_data.watch_dreg_count;211}212213i = hw_breakpoint_slot_setup(slots, max_slots, bp, ops);214215if (WARN_ONCE(i < 0, "Can't find any breakpoint slot"))216return i;217218switch (ops) {219case HW_BREAKPOINT_INSTALL:220/* Set the FWPnCFG/MWPnCFG 1~4 register. */221if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {222write_wb_reg(CSR_CFG_ADDR, i, 0, info->address);223write_wb_reg(CSR_CFG_MASK, i, 0, info->mask);224write_wb_reg(CSR_CFG_ASID, i, 0, 0);225write_wb_reg(CSR_CFG_CTRL, i, 0, privilege);226} else {227write_wb_reg(CSR_CFG_ADDR, i, 1, info->address);228write_wb_reg(CSR_CFG_MASK, i, 1, info->mask);229write_wb_reg(CSR_CFG_ASID, i, 1, 0);230ctrl = encode_ctrl_reg(info->ctrl);231write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl | privilege);232}233enable = csr_read64(LOONGARCH_CSR_CRMD);234csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD);235if (bp->hw.target && test_tsk_thread_flag(bp->hw.target, TIF_LOAD_WATCH))236regs->csr_prmd |= CSR_PRMD_PWE;237break;238case HW_BREAKPOINT_UNINSTALL:239/* Reset the FWPnCFG/MWPnCFG 1~4 register. */240if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {241write_wb_reg(CSR_CFG_ADDR, i, 0, 0);242write_wb_reg(CSR_CFG_MASK, i, 0, 0);243write_wb_reg(CSR_CFG_CTRL, i, 0, 0);244write_wb_reg(CSR_CFG_ASID, i, 0, 0);245} else {246write_wb_reg(CSR_CFG_ADDR, i, 1, 0);247write_wb_reg(CSR_CFG_MASK, i, 1, 0);248write_wb_reg(CSR_CFG_CTRL, i, 1, 0);249write_wb_reg(CSR_CFG_ASID, i, 1, 0);250}251if (bp->hw.target)252regs->csr_prmd &= ~CSR_PRMD_PWE;253break;254}255256return 0;257}258259/*260* Install a perf counter breakpoint.261*/262int arch_install_hw_breakpoint(struct perf_event *bp)263{264return hw_breakpoint_control(bp, HW_BREAKPOINT_INSTALL);265}266267void arch_uninstall_hw_breakpoint(struct perf_event *bp)268{269hw_breakpoint_control(bp, HW_BREAKPOINT_UNINSTALL);270}271272static int get_hbp_len(u8 hbp_len)273{274unsigned int len_in_bytes = 0;275276switch (hbp_len) {277case LOONGARCH_BREAKPOINT_LEN_1:278len_in_bytes = 1;279break;280case LOONGARCH_BREAKPOINT_LEN_2:281len_in_bytes = 2;282break;283case LOONGARCH_BREAKPOINT_LEN_4:284len_in_bytes = 4;285break;286case LOONGARCH_BREAKPOINT_LEN_8:287len_in_bytes = 8;288break;289}290291return len_in_bytes;292}293294/*295* Check whether bp virtual address is in kernel space.296*/297int arch_check_bp_in_kernelspace(struct arch_hw_breakpoint *hw)298{299unsigned int len;300unsigned long va;301302va = hw->address;303len = get_hbp_len(hw->ctrl.len);304305return (va >= TASK_SIZE) && ((va + len - 1) >= TASK_SIZE);306}307308/*309* Extract generic type and length encodings from an arch_hw_breakpoint_ctrl.310* Hopefully this will disappear when ptrace can bypass the conversion311* to generic breakpoint descriptions.312*/313int arch_bp_generic_fields(struct arch_hw_breakpoint_ctrl ctrl,314int *gen_len, int *gen_type)315{316/* Type */317switch (ctrl.type) {318case LOONGARCH_BREAKPOINT_EXECUTE:319*gen_type = HW_BREAKPOINT_X;320break;321case LOONGARCH_BREAKPOINT_LOAD:322*gen_type = HW_BREAKPOINT_R;323break;324case LOONGARCH_BREAKPOINT_STORE:325*gen_type = HW_BREAKPOINT_W;326break;327case LOONGARCH_BREAKPOINT_LOAD | LOONGARCH_BREAKPOINT_STORE:328*gen_type = HW_BREAKPOINT_RW;329break;330default:331return -EINVAL;332}333334/* Len */335switch (ctrl.len) {336case LOONGARCH_BREAKPOINT_LEN_1:337*gen_len = HW_BREAKPOINT_LEN_1;338break;339case LOONGARCH_BREAKPOINT_LEN_2:340*gen_len = HW_BREAKPOINT_LEN_2;341break;342case LOONGARCH_BREAKPOINT_LEN_4:343*gen_len = HW_BREAKPOINT_LEN_4;344break;345case LOONGARCH_BREAKPOINT_LEN_8:346*gen_len = HW_BREAKPOINT_LEN_8;347break;348default:349return -EINVAL;350}351352return 0;353}354355/*356* Construct an arch_hw_breakpoint from a perf_event.357*/358static int arch_build_bp_info(struct perf_event *bp,359const struct perf_event_attr *attr,360struct arch_hw_breakpoint *hw)361{362/* Type */363switch (attr->bp_type) {364case HW_BREAKPOINT_X:365hw->ctrl.type = LOONGARCH_BREAKPOINT_EXECUTE;366break;367case HW_BREAKPOINT_R:368hw->ctrl.type = LOONGARCH_BREAKPOINT_LOAD;369break;370case HW_BREAKPOINT_W:371hw->ctrl.type = LOONGARCH_BREAKPOINT_STORE;372break;373case HW_BREAKPOINT_RW:374hw->ctrl.type = LOONGARCH_BREAKPOINT_LOAD | LOONGARCH_BREAKPOINT_STORE;375break;376default:377return -EINVAL;378}379380/* Len */381switch (attr->bp_len) {382case HW_BREAKPOINT_LEN_1:383hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_1;384break;385case HW_BREAKPOINT_LEN_2:386hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_2;387break;388case HW_BREAKPOINT_LEN_4:389hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_4;390break;391case HW_BREAKPOINT_LEN_8:392hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_8;393break;394default:395return -EINVAL;396}397398/* Address */399hw->address = attr->bp_addr;400401return 0;402}403404/*405* Validate the arch-specific HW Breakpoint register settings.406*/407int hw_breakpoint_arch_parse(struct perf_event *bp,408const struct perf_event_attr *attr,409struct arch_hw_breakpoint *hw)410{411int ret;412u64 alignment_mask;413414/* Build the arch_hw_breakpoint. */415ret = arch_build_bp_info(bp, attr, hw);416if (ret)417return ret;418419if (hw->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {420alignment_mask = 0x3;421hw->address &= ~alignment_mask;422}423424return 0;425}426427static void update_bp_registers(struct pt_regs *regs, int enable, int type)428{429u32 ctrl;430int i, max_slots;431struct perf_event **slots;432struct arch_hw_breakpoint *info;433434switch (type) {435case 0:436slots = this_cpu_ptr(bp_on_reg);437max_slots = boot_cpu_data.watch_ireg_count;438break;439case 1:440slots = this_cpu_ptr(wp_on_reg);441max_slots = boot_cpu_data.watch_dreg_count;442break;443default:444return;445}446447for (i = 0; i < max_slots; ++i) {448if (!slots[i])449continue;450451info = counter_arch_bp(slots[i]);452if (enable) {453if ((info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) && (type == 0)) {454write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);455write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);456} else {457ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1);458if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD)459ctrl |= 0x1 << MWPnCFG3_LoadEn;460if (info->ctrl.type == LOONGARCH_BREAKPOINT_STORE)461ctrl |= 0x1 << MWPnCFG3_StoreEn;462write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl);463}464regs->csr_prmd |= CSR_PRMD_PWE;465} else {466if ((info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) && (type == 0)) {467write_wb_reg(CSR_CFG_CTRL, i, 0, 0);468} else {469ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1);470if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD)471ctrl &= ~0x1 << MWPnCFG3_LoadEn;472if (info->ctrl.type == LOONGARCH_BREAKPOINT_STORE)473ctrl &= ~0x1 << MWPnCFG3_StoreEn;474write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl);475}476regs->csr_prmd &= ~CSR_PRMD_PWE;477}478}479}480NOKPROBE_SYMBOL(update_bp_registers);481482/*483* Debug exception handlers.484*/485void breakpoint_handler(struct pt_regs *regs)486{487int i;488struct perf_event *bp, **slots;489490slots = this_cpu_ptr(bp_on_reg);491492for (i = 0; i < boot_cpu_data.watch_ireg_count; ++i) {493if ((csr_read32(LOONGARCH_CSR_FWPS) & (0x1 << i))) {494bp = slots[i];495if (bp == NULL)496continue;497perf_bp_event(bp, regs);498csr_write32(0x1 << i, LOONGARCH_CSR_FWPS);499update_bp_registers(regs, 0, 0);500}501}502}503NOKPROBE_SYMBOL(breakpoint_handler);504505void watchpoint_handler(struct pt_regs *regs)506{507int i;508struct perf_event *wp, **slots;509510slots = this_cpu_ptr(wp_on_reg);511512for (i = 0; i < boot_cpu_data.watch_dreg_count; ++i) {513if ((csr_read32(LOONGARCH_CSR_MWPS) & (0x1 << i))) {514wp = slots[i];515if (wp == NULL)516continue;517perf_bp_event(wp, regs);518csr_write32(0x1 << i, LOONGARCH_CSR_MWPS);519update_bp_registers(regs, 0, 1);520}521}522}523NOKPROBE_SYMBOL(watchpoint_handler);524525static int __init arch_hw_breakpoint_init(void)526{527int cpu;528529boot_cpu_data.watch_ireg_count = get_num_brps();530boot_cpu_data.watch_dreg_count = get_num_wrps();531532pr_info("Found %d breakpoint and %d watchpoint registers.\n",533boot_cpu_data.watch_ireg_count, boot_cpu_data.watch_dreg_count);534535for (cpu = 1; cpu < NR_CPUS; cpu++) {536cpu_data[cpu].watch_ireg_count = boot_cpu_data.watch_ireg_count;537cpu_data[cpu].watch_dreg_count = boot_cpu_data.watch_dreg_count;538}539540return 0;541}542arch_initcall(arch_hw_breakpoint_init);543544void hw_breakpoint_thread_switch(struct task_struct *next)545{546u64 addr, mask;547struct pt_regs *regs = task_pt_regs(next);548549if (test_tsk_thread_flag(next, TIF_SINGLESTEP)) {550addr = read_wb_reg(CSR_CFG_ADDR, 0, 0);551mask = read_wb_reg(CSR_CFG_MASK, 0, 0);552if (!((regs->csr_era ^ addr) & ~mask))553csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS);554regs->csr_prmd |= CSR_PRMD_PWE;555} else {556/* Update breakpoints */557update_bp_registers(regs, 1, 0);558/* Update watchpoints */559update_bp_registers(regs, 1, 1);560}561}562563void hw_breakpoint_pmu_read(struct perf_event *bp)564{565}566567/*568* Dummy function to register with die_notifier.569*/570int hw_breakpoint_exceptions_notify(struct notifier_block *unused,571unsigned long val, void *data)572{573return NOTIFY_DONE;574}575576577