Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  hw_breakpoint.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
 */

#define pr_fmt(fmt) "hw-breakpoint: " fmt

#include <linux/hw_breakpoint.h>
#include <linux/kprobes.h>
#include <linux/perf_event.h>

#include <asm/hw_breakpoint.h>

/* Breakpoint currently in use for each BRP. */
static DEFINE_PER_CPU(struct perf_event *, bp_on_reg[LOONGARCH_MAX_BRP]);

/* Watchpoint currently in use for each WRP. */
static DEFINE_PER_CPU(struct perf_event *, wp_on_reg[LOONGARCH_MAX_WRP]);

int hw_breakpoint_slots(int type)
{
 /*
 * We can be called early, so don't rely on
 * our static variables being initialised.
 */

 switch (type) {
 case TYPE_INST:
  return get_num_brps();
 case TYPE_DATA:
  return get_num_wrps();
 default:
  pr_warn("unknown slot type: %d\n", type);
  return 0;
 }
}

#define READ_WB_REG_CASE(OFF, N, REG, T, VAL)  \
 case (OFF + N):     \
  LOONGARCH_CSR_WATCH_READ(N, REG, T, VAL); \
  break

#define WRITE_WB_REG_CASE(OFF, N, REG, T, VAL)  \
 case (OFF + N):     \
  LOONGARCH_CSR_WATCH_WRITE(N, REG, T, VAL); \
  break

#define GEN_READ_WB_REG_CASES(OFF, REG, T, VAL)  \
 READ_WB_REG_CASE(OFF, 0, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 1, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 2, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 3, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 4, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 5, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 6, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 7, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 8, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 9, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 10, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 11, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 12, REG, T, VAL);  \
 READ_WB_REG_CASE(OFF, 13, REG, T, VAL);

#define GEN_WRITE_WB_REG_CASES(OFF, REG, T, VAL) \
 WRITE_WB_REG_CASE(OFF, 0, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 1, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 2, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 3, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 4, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 5, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 6, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 7, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 8, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 9, REG, T, VAL);  \
 WRITE_WB_REG_CASE(OFF, 10, REG, T, VAL); \
 WRITE_WB_REG_CASE(OFF, 11, REG, T, VAL); \
 WRITE_WB_REG_CASE(OFF, 12, REG, T, VAL); \
 WRITE_WB_REG_CASE(OFF, 13, REG, T, VAL);

static u64 read_wb_reg(int reg, int n, int t)
{
 u64 val = 0;

 switch (reg + n) {
 GEN_READ_WB_REG_CASES(CSR_CFG_ADDR, ADDR, t, val);
 GEN_READ_WB_REG_CASES(CSR_CFG_MASK, MASK, t, val);
 GEN_READ_WB_REG_CASES(CSR_CFG_CTRL, CTRL, t, val);
 GEN_READ_WB_REG_CASES(CSR_CFG_ASID, ASID, t, val);
 default:
  pr_warn("Attempt to read from unknown breakpoint register %d\n", n);
 }

 return val;
}
NOKPROBE_SYMBOL(read_wb_reg);

static void write_wb_reg(int reg, int n, int t, u64 val)
{
 switch (reg + n) {
 GEN_WRITE_WB_REG_CASES(CSR_CFG_ADDR, ADDR, t, val);
 GEN_WRITE_WB_REG_CASES(CSR_CFG_MASK, MASK, t, val);
 GEN_WRITE_WB_REG_CASES(CSR_CFG_CTRL, CTRL, t, val);
 GEN_WRITE_WB_REG_CASES(CSR_CFG_ASID, ASID, t, val);
 default:
  pr_warn("Attempt to write to unknown breakpoint register %d\n", n);
 }
}
NOKPROBE_SYMBOL(write_wb_reg);

enum hw_breakpoint_ops {
 HW_BREAKPOINT_INSTALL,
 HW_BREAKPOINT_UNINSTALL,
};

/*
 * hw_breakpoint_slot_setup - Find and setup a perf slot according to operations
 *
 * @slots: pointer to array of slots
 * @max_slots: max number of slots
 * @bp: perf_event to setup
 * @ops: operation to be carried out on the slot
 *
 * Return:
 * slot index on success
 * -ENOSPC if no slot is available/matches
 * -EINVAL on wrong operations parameter
 */


static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots,
        struct perf_event *bp, enum hw_breakpoint_ops ops)
{
 int i;
 struct perf_event **slot;

 for (i = 0; i < max_slots; ++i) {
  slot = &slots[i];
  switch (ops) {
  case HW_BREAKPOINT_INSTALL:
   if (!*slot) {
    *slot = bp;
    return i;
   }
   break;
  case HW_BREAKPOINT_UNINSTALL:
   if (*slot == bp) {
    *slot = NULL;
    return i;
   }
   break;
  default:
   pr_warn_once("Unhandled hw breakpoint ops %d\n", ops);
   return -EINVAL;
  }
 }

 return -ENOSPC;
}

void ptrace_hw_copy_thread(struct task_struct *tsk)
{
 memset(tsk->thread.hbp_break, 0, sizeof(tsk->thread.hbp_break));
 memset(tsk->thread.hbp_watch, 0, sizeof(tsk->thread.hbp_watch));
}

/*
 * Unregister breakpoints from this task and reset the pointers in the thread_struct.
 */

void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
{
 int i;
 struct thread_struct *t = &tsk->thread;

 for (i = 0; i < LOONGARCH_MAX_BRP; i++) {
  if (t->hbp_break[i]) {
   unregister_hw_breakpoint(t->hbp_break[i]);
   t->hbp_break[i] = NULL;
  }
 }

 for (i = 0; i < LOONGARCH_MAX_WRP; i++) {
  if (t->hbp_watch[i]) {
   unregister_hw_breakpoint(t->hbp_watch[i]);
   t->hbp_watch[i] = NULL;
  }
 }
}

static int hw_breakpoint_control(struct perf_event *bp,
     enum hw_breakpoint_ops ops)
{
 u32 ctrl, privilege;
 int i, max_slots, enable;
 struct pt_regs *regs;
 struct perf_event **slots;
 struct arch_hw_breakpoint *info = counter_arch_bp(bp);

 if (arch_check_bp_in_kernelspace(info))
  privilege = CTRL_PLV0_ENABLE;
 else
  privilege = CTRL_PLV3_ENABLE;

 /*  Whether bp belongs to a task. */
 if (bp->hw.target)
  regs = task_pt_regs(bp->hw.target);

 if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
  /* Breakpoint */
  slots = this_cpu_ptr(bp_on_reg);
  max_slots = boot_cpu_data.watch_ireg_count;
 } else {
  /* Watchpoint */
  slots = this_cpu_ptr(wp_on_reg);
  max_slots = boot_cpu_data.watch_dreg_count;
 }

 i = hw_breakpoint_slot_setup(slots, max_slots, bp, ops);

 if (WARN_ONCE(i < 0, "Can't find any breakpoint slot"))
  return i;

 switch (ops) {
 case HW_BREAKPOINT_INSTALL:
  /* Set the FWPnCFG/MWPnCFG 1~4 register. */
  if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
   write_wb_reg(CSR_CFG_ADDR, i, 0, info->address);
   write_wb_reg(CSR_CFG_MASK, i, 0, info->mask);
   write_wb_reg(CSR_CFG_ASID, i, 0, 0);
   write_wb_reg(CSR_CFG_CTRL, i, 0, privilege);
  } else {
   write_wb_reg(CSR_CFG_ADDR, i, 1, info->address);
   write_wb_reg(CSR_CFG_MASK, i, 1, info->mask);
   write_wb_reg(CSR_CFG_ASID, i, 1, 0);
   ctrl = encode_ctrl_reg(info->ctrl);
   write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl | privilege);
  }
  enable = csr_read64(LOONGARCH_CSR_CRMD);
  csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD);
  if (bp->hw.target && test_tsk_thread_flag(bp->hw.target, TIF_LOAD_WATCH))
   regs->csr_prmd |= CSR_PRMD_PWE;
  break;
 case HW_BREAKPOINT_UNINSTALL:
  /* Reset the FWPnCFG/MWPnCFG 1~4 register. */
  if (info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
   write_wb_reg(CSR_CFG_ADDR, i, 0, 0);
   write_wb_reg(CSR_CFG_MASK, i, 0, 0);
   write_wb_reg(CSR_CFG_CTRL, i, 0, 0);
   write_wb_reg(CSR_CFG_ASID, i, 0, 0);
  } else {
   write_wb_reg(CSR_CFG_ADDR, i, 1, 0);
   write_wb_reg(CSR_CFG_MASK, i, 1, 0);
   write_wb_reg(CSR_CFG_CTRL, i, 1, 0);
   write_wb_reg(CSR_CFG_ASID, i, 1, 0);
  }
  if (bp->hw.target)
   regs->csr_prmd &= ~CSR_PRMD_PWE;
  break;
 }

 return 0;
}

/*
 * Install a perf counter breakpoint.
 */

int arch_install_hw_breakpoint(struct perf_event *bp)
{
 return hw_breakpoint_control(bp, HW_BREAKPOINT_INSTALL);
}

void arch_uninstall_hw_breakpoint(struct perf_event *bp)
{
 hw_breakpoint_control(bp, HW_BREAKPOINT_UNINSTALL);
}

static int get_hbp_len(u8 hbp_len)
{
 unsigned int len_in_bytes = 0;

 switch (hbp_len) {
 case LOONGARCH_BREAKPOINT_LEN_1:
  len_in_bytes = 1;
  break;
 case LOONGARCH_BREAKPOINT_LEN_2:
  len_in_bytes = 2;
  break;
 case LOONGARCH_BREAKPOINT_LEN_4:
  len_in_bytes = 4;
  break;
 case LOONGARCH_BREAKPOINT_LEN_8:
  len_in_bytes = 8;
  break;
 }

 return len_in_bytes;
}

/*
 * Check whether bp virtual address is in kernel space.
 */

int arch_check_bp_in_kernelspace(struct arch_hw_breakpoint *hw)
{
 unsigned int len;
 unsigned long va;

 va = hw->address;
 len = get_hbp_len(hw->ctrl.len);

 return (va >= TASK_SIZE) && ((va + len - 1) >= TASK_SIZE);
}

/*
 * Extract generic type and length encodings from an arch_hw_breakpoint_ctrl.
 * Hopefully this will disappear when ptrace can bypass the conversion
 * to generic breakpoint descriptions.
 */

int arch_bp_generic_fields(struct arch_hw_breakpoint_ctrl ctrl,
      int *gen_len, int *gen_type)
{
 /* Type */
 switch (ctrl.type) {
 case LOONGARCH_BREAKPOINT_EXECUTE:
  *gen_type = HW_BREAKPOINT_X;
  break;
 case LOONGARCH_BREAKPOINT_LOAD:
  *gen_type = HW_BREAKPOINT_R;
  break;
 case LOONGARCH_BREAKPOINT_STORE:
  *gen_type = HW_BREAKPOINT_W;
  break;
 case LOONGARCH_BREAKPOINT_LOAD | LOONGARCH_BREAKPOINT_STORE:
  *gen_type = HW_BREAKPOINT_RW;
  break;
 default:
  return -EINVAL;
 }

 /* Len */
 switch (ctrl.len) {
 case LOONGARCH_BREAKPOINT_LEN_1:
  *gen_len = HW_BREAKPOINT_LEN_1;
  break;
 case LOONGARCH_BREAKPOINT_LEN_2:
  *gen_len = HW_BREAKPOINT_LEN_2;
  break;
 case LOONGARCH_BREAKPOINT_LEN_4:
  *gen_len = HW_BREAKPOINT_LEN_4;
  break;
 case LOONGARCH_BREAKPOINT_LEN_8:
  *gen_len = HW_BREAKPOINT_LEN_8;
  break;
 default:
  return -EINVAL;
 }

 return 0;
}

/*
 * Construct an arch_hw_breakpoint from a perf_event.
 */

static int arch_build_bp_info(struct perf_event *bp,
         const struct perf_event_attr *attr,
         struct arch_hw_breakpoint *hw)
{
 /* Type */
 switch (attr->bp_type) {
 case HW_BREAKPOINT_X:
  hw->ctrl.type = LOONGARCH_BREAKPOINT_EXECUTE;
  break;
 case HW_BREAKPOINT_R:
  hw->ctrl.type = LOONGARCH_BREAKPOINT_LOAD;
  break;
 case HW_BREAKPOINT_W:
  hw->ctrl.type = LOONGARCH_BREAKPOINT_STORE;
  break;
 case HW_BREAKPOINT_RW:
  hw->ctrl.type = LOONGARCH_BREAKPOINT_LOAD | LOONGARCH_BREAKPOINT_STORE;
  break;
 default:
  return -EINVAL;
 }

 /* Len */
 switch (attr->bp_len) {
 case HW_BREAKPOINT_LEN_1:
  hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_1;
  break;
 case HW_BREAKPOINT_LEN_2:
  hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_2;
  break;
 case HW_BREAKPOINT_LEN_4:
  hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_4;
  break;
 case HW_BREAKPOINT_LEN_8:
  hw->ctrl.len = LOONGARCH_BREAKPOINT_LEN_8;
  break;
 default:
  return -EINVAL;
 }

 /* Address */
 hw->address = attr->bp_addr;

 return 0;
}

/*
 * Validate the arch-specific HW Breakpoint register settings.
 */

int hw_breakpoint_arch_parse(struct perf_event *bp,
        const struct perf_event_attr *attr,
        struct arch_hw_breakpoint *hw)
{
 int ret;
 u64 alignment_mask;

 /* Build the arch_hw_breakpoint. */
 ret = arch_build_bp_info(bp, attr, hw);
 if (ret)
  return ret;

 if (hw->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) {
  alignment_mask = 0x3;
  hw->address &= ~alignment_mask;
 }

 return 0;
}

static void update_bp_registers(struct pt_regs *regs, int enable, int type)
{
 u32 ctrl;
 int i, max_slots;
 struct perf_event **slots;
 struct arch_hw_breakpoint *info;

 switch (type) {
 case 0:
  slots = this_cpu_ptr(bp_on_reg);
  max_slots = boot_cpu_data.watch_ireg_count;
  break;
 case 1:
  slots = this_cpu_ptr(wp_on_reg);
  max_slots = boot_cpu_data.watch_dreg_count;
  break;
 default:
  return;
 }

 for (i = 0; i < max_slots; ++i) {
  if (!slots[i])
   continue;

  info = counter_arch_bp(slots[i]);
  if (enable) {
   if ((info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) && (type == 0)) {
    write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);
    write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);
   } else {
    ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1);
    if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD)
     ctrl |= 0x1 << MWPnCFG3_LoadEn;
    if (info->ctrl.type == LOONGARCH_BREAKPOINT_STORE)
     ctrl |= 0x1 << MWPnCFG3_StoreEn;
    write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl);
   }
   regs->csr_prmd |= CSR_PRMD_PWE;
  } else {
   if ((info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) && (type == 0)) {
    write_wb_reg(CSR_CFG_CTRL, i, 0, 0);
   } else {
    ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1);
    if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD)
     ctrl &= ~0x1 << MWPnCFG3_LoadEn;
    if (info->ctrl.type == LOONGARCH_BREAKPOINT_STORE)
     ctrl &= ~0x1 << MWPnCFG3_StoreEn;
    write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl);
   }
   regs->csr_prmd &= ~CSR_PRMD_PWE;
  }
 }
}
NOKPROBE_SYMBOL(update_bp_registers);

/*
 * Debug exception handlers.
 */

void breakpoint_handler(struct pt_regs *regs)
{
 int i;
 struct perf_event *bp, **slots;

 slots = this_cpu_ptr(bp_on_reg);

 for (i = 0; i < boot_cpu_data.watch_ireg_count; ++i) {
  if ((csr_read32(LOONGARCH_CSR_FWPS) & (0x1 << i))) {
   bp = slots[i];
   if (bp == NULL)
    continue;
   perf_bp_event(bp, regs);
   csr_write32(0x1 << i, LOONGARCH_CSR_FWPS);
   update_bp_registers(regs, 0, 0);
  }
 }
}
NOKPROBE_SYMBOL(breakpoint_handler);

void watchpoint_handler(struct pt_regs *regs)
{
 int i;
 struct perf_event *wp, **slots;

 slots = this_cpu_ptr(wp_on_reg);

 for (i = 0; i < boot_cpu_data.watch_dreg_count; ++i) {
  if ((csr_read32(LOONGARCH_CSR_MWPS) & (0x1 << i))) {
   wp = slots[i];
   if (wp == NULL)
    continue;
   perf_bp_event(wp, regs);
   csr_write32(0x1 << i, LOONGARCH_CSR_MWPS);
   update_bp_registers(regs, 0, 1);
  }
 }
}
NOKPROBE_SYMBOL(watchpoint_handler);

static int __init arch_hw_breakpoint_init(void)
{
 int cpu;

 boot_cpu_data.watch_ireg_count = get_num_brps();
 boot_cpu_data.watch_dreg_count = get_num_wrps();

 pr_info("Found %d breakpoint and %d watchpoint registers.\n",
  boot_cpu_data.watch_ireg_count, boot_cpu_data.watch_dreg_count);

 for (cpu = 1; cpu < NR_CPUS; cpu++) {
  cpu_data[cpu].watch_ireg_count = boot_cpu_data.watch_ireg_count;
  cpu_data[cpu].watch_dreg_count = boot_cpu_data.watch_dreg_count;
 }

 return 0;
}
arch_initcall(arch_hw_breakpoint_init);

void hw_breakpoint_thread_switch(struct task_struct *next)
{
 u64 addr, mask;
 struct pt_regs *regs = task_pt_regs(next);

 if (test_tsk_thread_flag(next, TIF_SINGLESTEP)) {
  addr = read_wb_reg(CSR_CFG_ADDR, 0, 0);
  mask = read_wb_reg(CSR_CFG_MASK, 0, 0);
  if (!((regs->csr_era ^ addr) & ~mask))
   csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS);
  regs->csr_prmd |= CSR_PRMD_PWE;
 } else {
  /* Update breakpoints */
  update_bp_registers(regs, 1, 0);
  /* Update watchpoints */
  update_bp_registers(regs, 1, 1);
 }
}

void hw_breakpoint_pmu_read(struct perf_event *bp)
{
}

/*
 * Dummy function to register with die_notifier.
 */

int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
        unsigned long val, void *data)
{
 return NOTIFY_DONE;
}

Messung V0.5
C=96 H=94 G=94

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge