1144 lines
25 KiB
C
1144 lines
25 KiB
C
/*
|
|
* drivers/misc/tegra-profiler/hrt.c
|
|
*
|
|
* Copyright (c) 2015-2020, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/err.h>
|
|
#include <linux/rculist.h>
|
|
#include <linux/random.h>
|
|
#include <linux/version.h>
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0))
|
|
#include <linux/sched/task_stack.h>
|
|
#endif
|
|
#include <clocksource/arm_arch_timer.h>
|
|
|
|
#include <asm/cputype.h>
|
|
#include <asm/irq_regs.h>
|
|
#include <asm/arch_timer.h>
|
|
|
|
#include <linux/tegra_profiler.h>
|
|
|
|
#include "quadd.h"
|
|
#include "hrt.h"
|
|
#include "comm.h"
|
|
#include "mmap.h"
|
|
#include "ma.h"
|
|
#include "power_clk.h"
|
|
#include "tegra.h"
|
|
#include "debug.h"
|
|
|
|
static struct quadd_hrt_ctx hrt = {
|
|
.active = ATOMIC_INIT(0),
|
|
.mmap_active = ATOMIC_INIT(0),
|
|
};
|
|
|
|
struct hrt_pid_node {
|
|
struct list_head list;
|
|
struct rcu_head rcu;
|
|
pid_t pid;
|
|
};
|
|
|
|
static void pid_free_rcu(struct rcu_head *head)
|
|
{
|
|
struct hrt_pid_node *entry =
|
|
container_of(head, struct hrt_pid_node, rcu);
|
|
kfree(entry);
|
|
}
|
|
|
|
static int pid_list_add(pid_t pid)
|
|
{
|
|
struct hrt_pid_node *entry;
|
|
|
|
entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
|
if (!entry)
|
|
return -ENOMEM;
|
|
|
|
entry->pid = pid;
|
|
INIT_LIST_HEAD(&entry->list);
|
|
|
|
raw_spin_lock(&hrt.pid_list_lock);
|
|
list_add_tail_rcu(&entry->list, &hrt.pid_list);
|
|
raw_spin_unlock(&hrt.pid_list_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pid_list_del(pid_t pid)
|
|
{
|
|
struct hrt_pid_node *entry;
|
|
|
|
raw_spin_lock(&hrt.pid_list_lock);
|
|
list_for_each_entry(entry, &hrt.pid_list, list) {
|
|
if (entry->pid == pid) {
|
|
list_del_rcu(&entry->list);
|
|
call_rcu(&entry->rcu, pid_free_rcu);
|
|
break;
|
|
}
|
|
}
|
|
raw_spin_unlock(&hrt.pid_list_lock);
|
|
}
|
|
|
|
static void pid_list_clear(void)
|
|
{
|
|
struct hrt_pid_node *entry, *next;
|
|
|
|
raw_spin_lock(&hrt.pid_list_lock);
|
|
list_for_each_entry_safe(entry, next, &hrt.pid_list, list) {
|
|
list_del_rcu(&entry->list);
|
|
call_rcu(&entry->rcu, pid_free_rcu);
|
|
}
|
|
raw_spin_unlock(&hrt.pid_list_lock);
|
|
}
|
|
|
|
static int pid_list_search(pid_t pid)
|
|
{
|
|
struct hrt_pid_node *entry;
|
|
|
|
/* The possible PID wrapping around: should we somehow handle this? */
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(entry, &hrt.pid_list, list) {
|
|
if (entry->pid == pid) {
|
|
rcu_read_unlock();
|
|
return 1;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline u32 get_task_state(struct task_struct *task)
|
|
{
|
|
return (u32)(task->state | task->exit_state);
|
|
}
|
|
|
|
static inline u64 get_posix_clock_monotonic_time(void)
|
|
{
|
|
struct timespec64 ts;
|
|
|
|
ktime_get_ts64(&ts);
|
|
return timespec64_to_ns(&ts);
|
|
}
|
|
|
|
static inline u64 get_arch_time(struct timecounter *tc)
|
|
{
|
|
u64 frac = 0;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
|
|
u64 value;
|
|
#else
|
|
cycle_t value;
|
|
#endif
|
|
const struct cyclecounter *cc = tc->cc;
|
|
|
|
value = cc->read(cc);
|
|
return cyclecounter_cyc2ns(cc, value, 0, &frac);
|
|
}
|
|
|
|
u64 quadd_get_time(void)
|
|
{
|
|
struct timecounter *tc = hrt.tc;
|
|
|
|
return (tc && hrt.use_arch_timer) ?
|
|
get_arch_time(tc) :
|
|
get_posix_clock_monotonic_time();
|
|
}
|
|
|
|
static void
|
|
__put_sample(struct quadd_record_data *data,
|
|
struct quadd_iovec *vec,
|
|
int vec_count, int cpu_id)
|
|
{
|
|
ssize_t err;
|
|
struct quadd_comm_data_interface *comm = hrt.quadd_ctx->comm;
|
|
|
|
data->seqid = atomic_inc_return(&hrt.seqid);
|
|
|
|
err = comm->put_sample(data, vec, vec_count, cpu_id);
|
|
if (err < 0)
|
|
atomic64_inc(&hrt.skipped_samples);
|
|
|
|
atomic64_inc(&hrt.counter_samples);
|
|
}
|
|
|
|
void
|
|
quadd_put_sample_this_cpu(struct quadd_record_data *data,
|
|
struct quadd_iovec *vec, int vec_count)
|
|
{
|
|
__put_sample(data, vec, vec_count, -1);
|
|
}
|
|
|
|
void
|
|
quadd_put_sample(struct quadd_record_data *data,
|
|
struct quadd_iovec *vec, int vec_count)
|
|
{
|
|
__put_sample(data, vec, vec_count, 0);
|
|
}
|
|
|
|
static void put_header(int cpuid, bool is_uncore)
|
|
{
|
|
int vec_idx = 0;
|
|
u32 uncore_freq;
|
|
struct quadd_iovec vec[2];
|
|
int nr_events = 0, max_events = QUADD_MAX_COUNTERS;
|
|
struct quadd_event events[QUADD_MAX_COUNTERS];
|
|
struct quadd_record_data record;
|
|
struct quadd_header_data *hdr = &record.hdr;
|
|
struct quadd_parameters *param = &hrt.quadd_ctx->param;
|
|
unsigned int extra = param->reserved[QUADD_PARAM_IDX_EXTRA];
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
struct quadd_event_source *pmu = ctx->pmu;
|
|
struct quadd_event_source *carmel_pmu = ctx->carmel_pmu;
|
|
|
|
hdr->time = quadd_get_time();
|
|
|
|
record.record_type = QUADD_RECORD_TYPE_HEADER;
|
|
|
|
hdr->magic = QUADD_HEADER_MAGIC;
|
|
hdr->samples_version = QUADD_SAMPLES_VERSION;
|
|
hdr->io_version = QUADD_IO_VERSION;
|
|
|
|
hdr->flags = 0;
|
|
|
|
if (param->backtrace)
|
|
hdr->flags |= QUADD_HDR_FLAG_BACKTRACE;
|
|
if (param->use_freq)
|
|
hdr->flags |= QUADD_HDR_FLAG_USE_FREQ;
|
|
|
|
#ifdef QM_DEBUG_SAMPLES_ENABLE
|
|
hdr->flags |= QUADD_HDR_FLAG_DEBUG_SAMPLES;
|
|
#endif
|
|
|
|
hdr->freq = param->freq;
|
|
hdr->ma_freq = param->ma_freq;
|
|
hdr->power_rate_freq = param->power_rate_freq;
|
|
|
|
if (hdr->power_rate_freq > 0)
|
|
hdr->flags |= QUADD_HDR_FLAG_POWER_RATE;
|
|
if (extra & QUADD_PARAM_EXTRA_GET_MMAP)
|
|
hdr->flags |= QUADD_HDR_FLAG_GET_MMAP;
|
|
|
|
hdr->extra_length = 0;
|
|
|
|
if (param->backtrace) {
|
|
struct quadd_unw_methods *um = &hrt.um;
|
|
|
|
if (um->fp)
|
|
hdr->flags |= QUADD_HDR_FLAG_BT_FP;
|
|
if (um->ut)
|
|
hdr->flags |= QUADD_HDR_FLAG_BT_UT;
|
|
if (um->ut_ce)
|
|
hdr->flags |= QUADD_HDR_FLAG_BT_UT_CE;
|
|
if (um->dwarf)
|
|
hdr->flags |= QUADD_HDR_FLAG_BT_DWARF;
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0))
|
|
if (hrt.use_arch_timer)
|
|
hdr->flags |= QUADD_HDR_FLAG_USE_ARCH_TIMER;
|
|
#endif
|
|
|
|
if (hrt.get_stack_offset)
|
|
hdr->flags |= QUADD_HDR_FLAG_STACK_OFFSET;
|
|
|
|
hdr->flags |= QUADD_HDR_FLAG_HAS_CPUID;
|
|
|
|
if (quadd_mode_is_sampling(ctx))
|
|
hdr->flags |= QUADD_HDR_FLAG_MODE_SAMPLING;
|
|
if (quadd_mode_is_tracing(ctx))
|
|
hdr->flags |= QUADD_HDR_FLAG_MODE_TRACING;
|
|
if (quadd_mode_is_sample_all(ctx))
|
|
hdr->flags |= QUADD_HDR_FLAG_MODE_SAMPLE_ALL;
|
|
if (quadd_mode_is_trace_all(ctx))
|
|
hdr->flags |= QUADD_HDR_FLAG_MODE_TRACE_ALL;
|
|
if (quadd_mode_is_sample_tree(ctx))
|
|
hdr->flags |= QUADD_HDR_FLAG_MODE_SAMPLE_TREE;
|
|
if (quadd_mode_is_trace_tree(ctx))
|
|
hdr->flags |= QUADD_HDR_FLAG_MODE_TRACE_TREE;
|
|
|
|
if (ctx->pclk_cpufreq)
|
|
hdr->flags |= QUADD_HDR_FLAG_CPUFREQ;
|
|
|
|
if (is_uncore) {
|
|
hdr->cpu_id = U8_MAX;
|
|
hdr->flags |= QUADD_HDR_FLAG_UNCORE;
|
|
|
|
uncore_freq = param->reserved[QUADD_PARAM_IDX_UNCORE_FREQ];
|
|
|
|
if (carmel_pmu)
|
|
nr_events =
|
|
carmel_pmu->current_events(cpuid, events,
|
|
max_events);
|
|
} else {
|
|
hdr->cpu_id = cpuid;
|
|
if (pmu)
|
|
nr_events =
|
|
pmu->current_events(cpuid, events, max_events);
|
|
}
|
|
|
|
hdr->nr_events = nr_events;
|
|
|
|
vec[vec_idx].base = events;
|
|
vec[vec_idx].len = nr_events * sizeof(events[0]);
|
|
vec_idx++;
|
|
|
|
if (is_uncore) {
|
|
vec[vec_idx].base = &uncore_freq;
|
|
vec[vec_idx].len = sizeof(uncore_freq);
|
|
vec_idx++;
|
|
}
|
|
|
|
__put_sample(&record, &vec[0], vec_idx, cpuid);
|
|
}
|
|
|
|
static void
|
|
put_sched_sample(struct task_struct *task, bool is_sched_in, u64 ts)
|
|
{
|
|
int vec_idx = 0;
|
|
u32 vpid, vtgid;
|
|
unsigned int flags;
|
|
struct quadd_iovec vec[2];
|
|
struct quadd_record_data record;
|
|
struct quadd_sched_data *s = &record.sched;
|
|
|
|
s->time = ts;
|
|
s->flags = 0;
|
|
s->cpu_id = quadd_get_processor_id(NULL, &flags);
|
|
|
|
record.record_type = QUADD_RECORD_TYPE_SCHED;
|
|
|
|
if (flags & QUADD_CPUMODE_TEGRA_POWER_CLUSTER_LP)
|
|
s->flags |= QUADD_SCHED_FLAG_LP_MODE;
|
|
if (is_sched_in)
|
|
s->flags |= QUADD_SCHED_FLAG_SCHED_IN;
|
|
if (task->flags & PF_KTHREAD)
|
|
s->flags |= QUADD_SCHED_FLAG_PF_KTHREAD;
|
|
|
|
s->pid = task_pid_nr(task);
|
|
s->tgid = task_tgid_nr(task);
|
|
|
|
s->task_state = get_task_state(task);
|
|
|
|
if (!(task->flags & PF_EXITING)) {
|
|
vpid = task_pid_vnr(task);
|
|
vtgid = task_tgid_vnr(task);
|
|
|
|
if (s->pid != vpid || s->tgid != vtgid) {
|
|
vec[vec_idx].base = &vpid;
|
|
vec[vec_idx].len = sizeof(vpid);
|
|
vec_idx++;
|
|
|
|
vec[vec_idx].base = &vtgid;
|
|
vec[vec_idx].len = sizeof(vtgid);
|
|
vec_idx++;
|
|
|
|
s->flags |= QUADD_SCHED_FLAG_IS_VPID;
|
|
}
|
|
}
|
|
|
|
quadd_put_sample_this_cpu(&record, vec, vec_idx);
|
|
}
|
|
|
|
static void put_comm_sample(struct task_struct *task, bool exec)
|
|
{
|
|
char name[TASK_COMM_LEN];
|
|
struct quadd_iovec vec;
|
|
struct quadd_record_data record;
|
|
struct quadd_comm_data *s = &record.comm;
|
|
|
|
s->time = quadd_get_time();
|
|
s->flags = 0;
|
|
|
|
s->pid = (u32)task_pid_nr(task);
|
|
s->tgid = (u32)task_tgid_nr(task);
|
|
|
|
memset(name, 0, sizeof(name));
|
|
get_task_comm(name, task);
|
|
name[sizeof(name) - 1] = '\0';
|
|
|
|
record.record_type = QUADD_RECORD_TYPE_COMM;
|
|
|
|
if (exec)
|
|
s->flags |= QUADD_COMM_FLAG_EXEC;
|
|
|
|
vec.base = name;
|
|
vec.len = ALIGN(strlen(name) + 1, sizeof(u64));
|
|
|
|
s->length = (u16)vec.len;
|
|
|
|
quadd_put_sample(&record, &vec, 1);
|
|
}
|
|
|
|
static int get_sample_data(struct quadd_sample_data *s,
|
|
struct pt_regs *regs,
|
|
struct task_struct *task)
|
|
{
|
|
unsigned int flags, user_mode;
|
|
struct quadd_ctx *quadd_ctx = hrt.quadd_ctx;
|
|
|
|
s->cpu_id = quadd_get_processor_id(regs, &flags);
|
|
|
|
user_mode = user_mode(regs);
|
|
|
|
if (user_mode)
|
|
s->flags |= QUADD_SAMPLE_FLAG_USER_MODE;
|
|
if (flags & QUADD_CPUMODE_TEGRA_POWER_CLUSTER_LP)
|
|
s->flags |= QUADD_SAMPLE_FLAG_LP_MODE;
|
|
if (flags & QUADD_CPUMODE_THUMB)
|
|
s->flags |= QUADD_SAMPLE_FLAG_THUMB_MODE;
|
|
if (in_interrupt())
|
|
s->flags |= QUADD_SAMPLE_FLAG_IN_INTERRUPT;
|
|
if (task->flags & PF_KTHREAD)
|
|
s->flags |= QUADD_SCHED_FLAG_PF_KTHREAD;
|
|
|
|
/* For security reasons, hide IPs from the kernel space. */
|
|
if (!user_mode && !quadd_ctx->collect_kernel_ips)
|
|
s->ip = 0;
|
|
else
|
|
s->ip = instruction_pointer(regs);
|
|
|
|
s->pid = task_pid_nr(task);
|
|
s->tgid = task_tgid_nr(task);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long
|
|
get_stack_offset(struct task_struct *task,
|
|
struct pt_regs *regs,
|
|
struct quadd_callchain *cc)
|
|
{
|
|
unsigned long sp;
|
|
struct vm_area_struct *vma;
|
|
struct mm_struct *mm = task->mm;
|
|
|
|
if (!regs || !mm)
|
|
return -ENOMEM;
|
|
|
|
sp = cc->nr > 0 ? cc->curr_sp :
|
|
quadd_user_stack_pointer(regs);
|
|
|
|
vma = find_vma(mm, sp);
|
|
if (!vma)
|
|
return -ENOMEM;
|
|
|
|
return vma->vm_end - sp;
|
|
}
|
|
|
|
static void
|
|
read_all_sources(struct pt_regs *regs, struct task_struct *task, u64 ts)
|
|
{
|
|
u32 vpid, vtgid;
|
|
u32 state, extra_data = 0, urcs = 0, ts_delta;
|
|
u64 ts_start, ts_end;
|
|
int i, vec_idx = 0, bt_size = 0;
|
|
int nr_events = 0, nr_positive_events = 0;
|
|
struct pt_regs *user_regs;
|
|
struct quadd_iovec vec[9];
|
|
struct quadd_event_data events[QUADD_MAX_COUNTERS];
|
|
u32 events_extra[QUADD_MAX_COUNTERS];
|
|
struct quadd_event_context event_ctx;
|
|
|
|
struct quadd_record_data record_data;
|
|
struct quadd_sample_data *s = &record_data.sample;
|
|
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
struct quadd_cpu_context *cpu_ctx = this_cpu_ptr(hrt.cpu_ctx);
|
|
struct quadd_callchain *cc = &cpu_ctx->cc;
|
|
|
|
if (!hrt_is_active(cpu_ctx))
|
|
return;
|
|
|
|
if (task->flags & PF_EXITING)
|
|
return;
|
|
|
|
s->time = ts_start = ts;
|
|
s->flags = 0;
|
|
|
|
if (ctx->pmu && ctx->get_pmu_info()->active)
|
|
nr_events = ctx->pmu->read(events, ARRAY_SIZE(events));
|
|
|
|
if (!nr_events)
|
|
return;
|
|
|
|
if (user_mode(regs))
|
|
user_regs = regs;
|
|
else
|
|
user_regs = current_pt_regs();
|
|
|
|
if (get_sample_data(s, regs, task))
|
|
return;
|
|
|
|
vec[vec_idx].base = &extra_data;
|
|
vec[vec_idx].len = sizeof(extra_data);
|
|
vec_idx++;
|
|
|
|
cc->nr = 0;
|
|
|
|
event_ctx.regs = user_regs;
|
|
event_ctx.task = task;
|
|
event_ctx.user_mode = user_mode(regs);
|
|
event_ctx.is_sched = !in_interrupt();
|
|
|
|
if (ctx->param.backtrace) {
|
|
cc->um = hrt.um;
|
|
|
|
bt_size = quadd_get_user_callchain(&event_ctx, cc, ctx);
|
|
if (bt_size > 0) {
|
|
int ip_size = cc->cs_64 ? sizeof(u64) : sizeof(u32);
|
|
int nr_types = DIV_ROUND_UP(bt_size, 8);
|
|
|
|
vec[vec_idx].base = cc->cs_64 ?
|
|
(void *)cc->ip_64 : (void *)cc->ip_32;
|
|
vec[vec_idx].len = bt_size * ip_size;
|
|
vec_idx++;
|
|
|
|
vec[vec_idx].base = cc->types;
|
|
vec[vec_idx].len = nr_types * sizeof(cc->types[0]);
|
|
vec_idx++;
|
|
|
|
if (cc->cs_64)
|
|
s->flags |= QUADD_SAMPLE_FLAG_IP64;
|
|
}
|
|
|
|
urcs |= (cc->urc_fp & QUADD_SAMPLE_URC_MASK) <<
|
|
QUADD_SAMPLE_URC_SHIFT_FP;
|
|
urcs |= (cc->urc_ut & QUADD_SAMPLE_URC_MASK) <<
|
|
QUADD_SAMPLE_URC_SHIFT_UT;
|
|
urcs |= (cc->urc_dwarf & QUADD_SAMPLE_URC_MASK) <<
|
|
QUADD_SAMPLE_URC_SHIFT_DWARF;
|
|
|
|
s->flags |= QUADD_SAMPLE_FLAG_URCS;
|
|
|
|
vec[vec_idx].base = &urcs;
|
|
vec[vec_idx].len = sizeof(urcs);
|
|
vec_idx++;
|
|
}
|
|
s->callchain_nr = bt_size;
|
|
|
|
if (hrt.get_stack_offset) {
|
|
long offset = get_stack_offset(task, user_regs, cc);
|
|
|
|
if (offset > 0) {
|
|
u32 off = offset >> 2;
|
|
|
|
off = min_t(u32, off, 0xffff);
|
|
extra_data |= off << QUADD_SED_STACK_OFFSET_SHIFT;
|
|
}
|
|
}
|
|
|
|
record_data.record_type = QUADD_RECORD_TYPE_SAMPLE;
|
|
|
|
s->events_flags = 0;
|
|
for (i = 0; i < nr_events; i++) {
|
|
u32 value = (u32)events[i].delta;
|
|
|
|
if (value > 0) {
|
|
s->events_flags |= 1 << i;
|
|
events_extra[nr_positive_events++] = value;
|
|
}
|
|
}
|
|
|
|
if (nr_positive_events == 0)
|
|
return;
|
|
|
|
vec[vec_idx].base = events_extra;
|
|
vec[vec_idx].len = nr_positive_events * sizeof(events_extra[0]);
|
|
vec_idx++;
|
|
|
|
state = get_task_state(task);
|
|
if (state) {
|
|
s->flags |= QUADD_SAMPLE_FLAG_STATE;
|
|
vec[vec_idx].base = &state;
|
|
vec[vec_idx].len = sizeof(state);
|
|
vec_idx++;
|
|
}
|
|
|
|
ts_end = quadd_get_time();
|
|
ts_delta = (u32)(ts_end - ts_start);
|
|
|
|
vec[vec_idx].base = &ts_delta;
|
|
vec[vec_idx].len = sizeof(ts_delta);
|
|
vec_idx++;
|
|
|
|
vpid = task_pid_vnr(task);
|
|
vtgid = task_tgid_vnr(task);
|
|
|
|
if (s->pid != vpid || s->tgid != vtgid) {
|
|
vec[vec_idx].base = &vpid;
|
|
vec[vec_idx].len = sizeof(vpid);
|
|
vec_idx++;
|
|
|
|
vec[vec_idx].base = &vtgid;
|
|
vec[vec_idx].len = sizeof(vtgid);
|
|
vec_idx++;
|
|
|
|
s->flags |= QUADD_SAMPLE_FLAG_IS_VPID;
|
|
}
|
|
|
|
quadd_put_sample_this_cpu(&record_data, vec, vec_idx);
|
|
}
|
|
|
|
static enum hrtimer_restart hrtimer_handler(struct hrtimer *hrtimer)
|
|
{
|
|
struct pt_regs *regs;
|
|
|
|
regs = get_irq_regs();
|
|
|
|
if (!atomic_read(&hrt.active))
|
|
return HRTIMER_NORESTART;
|
|
|
|
qm_debug_handler_sample(regs);
|
|
|
|
if (regs)
|
|
read_all_sources(regs, current, quadd_get_time());
|
|
|
|
hrtimer_forward_now(hrtimer, ns_to_ktime(hrt.sample_period));
|
|
qm_debug_timer_forward(regs, hrt.sample_period);
|
|
|
|
return HRTIMER_RESTART;
|
|
}
|
|
|
|
static void start_hrtimer(struct quadd_cpu_context *cpu_ctx)
|
|
{
|
|
u32 period = prandom_u32_max(hrt.sample_period);
|
|
|
|
hrtimer_start(&cpu_ctx->hrtimer, ns_to_ktime(period),
|
|
HRTIMER_MODE_REL_PINNED);
|
|
qm_debug_timer_start(NULL, period);
|
|
}
|
|
|
|
static void cancel_hrtimer(struct quadd_cpu_context *cpu_ctx)
|
|
{
|
|
hrtimer_cancel(&cpu_ctx->hrtimer);
|
|
qm_debug_timer_cancel();
|
|
}
|
|
|
|
static void init_hrtimer(struct quadd_cpu_context *cpu_ctx)
|
|
{
|
|
#if (defined(CONFIG_PREEMPT_RT_FULL) && \
|
|
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)))
|
|
hrtimer_init(&cpu_ctx->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD);
|
|
#else
|
|
hrtimer_init(&cpu_ctx->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
#endif
|
|
cpu_ctx->hrtimer.function = hrtimer_handler;
|
|
}
|
|
|
|
static inline bool
|
|
__is_profile_process(struct task_struct *task, bool is_trace, bool is_sample)
|
|
{
|
|
pid_t root_pid = hrt.root_pid;
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
|
|
if (root_pid > 0 && task_tgid_nr(task) == root_pid)
|
|
return true;
|
|
|
|
if ((is_trace && quadd_mode_is_trace_tree(ctx)) ||
|
|
(is_sample && quadd_mode_is_sample_tree(ctx)))
|
|
return pid_list_search(task_tgid_nr(task));
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline bool
|
|
validate_task(struct task_struct *task)
|
|
{
|
|
return task && !is_idle_task(task);
|
|
}
|
|
|
|
static inline bool
|
|
is_sample_process(struct task_struct *task)
|
|
{
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
|
|
if (!validate_task(task) || !quadd_mode_is_sampling(ctx))
|
|
return false;
|
|
|
|
if (quadd_mode_is_sample_all(ctx))
|
|
return true;
|
|
|
|
return __is_profile_process(task, false, true);
|
|
}
|
|
|
|
static inline bool
|
|
is_trace_process(struct task_struct *task)
|
|
{
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
|
|
if (!validate_task(task) || !quadd_mode_is_tracing(ctx))
|
|
return false;
|
|
|
|
if (quadd_mode_is_trace_all(ctx))
|
|
return true;
|
|
|
|
return __is_profile_process(task, true, false);
|
|
}
|
|
|
|
static inline bool
|
|
is_profile_process(struct task_struct *task)
|
|
{
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
|
|
if (!validate_task(task) ||
|
|
(!quadd_mode_is_tracing(ctx) && !quadd_mode_is_sampling(ctx)))
|
|
return false;
|
|
|
|
if (quadd_mode_is_process_all(ctx))
|
|
return true;
|
|
|
|
return __is_profile_process(task, true, true);
|
|
}
|
|
|
|
static void
|
|
add_active_thread(struct quadd_cpu_context *cpu_ctx, pid_t pid, pid_t tgid)
|
|
{
|
|
struct quadd_thread_data *t_data = &cpu_ctx->active_thread;
|
|
|
|
if (unlikely(hrt_is_active(cpu_ctx)))
|
|
pr_warn_once("%s: warning: active thread: %d\n",
|
|
__func__, (int)pid);
|
|
|
|
t_data->pid = pid;
|
|
t_data->tgid = tgid;
|
|
}
|
|
|
|
static void
|
|
remove_active_thread(struct quadd_cpu_context *cpu_ctx, pid_t pid)
|
|
{
|
|
struct quadd_thread_data *t_data = &cpu_ctx->active_thread;
|
|
|
|
t_data->pid = -1;
|
|
t_data->tgid = -1;
|
|
}
|
|
|
|
static inline pid_t get_active_thread(struct quadd_cpu_context *cpu_ctx)
|
|
{
|
|
struct quadd_thread_data *t_data = &cpu_ctx->active_thread;
|
|
|
|
return t_data->pid;
|
|
}
|
|
|
|
static inline int
|
|
is_task_active(struct quadd_cpu_context *cpu_ctx, struct task_struct *task)
|
|
{
|
|
return task_pid_nr(task) == get_active_thread(cpu_ctx);
|
|
}
|
|
|
|
void __quadd_task_sched_in(struct task_struct *prev,
|
|
struct task_struct *task)
|
|
{
|
|
bool trace_flag, sample_flag;
|
|
struct quadd_cpu_context *cpu_ctx = this_cpu_ptr(hrt.cpu_ctx);
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
|
|
if (likely(!atomic_read(&hrt.active)))
|
|
return;
|
|
|
|
sample_flag = is_sample_process(task);
|
|
trace_flag = is_trace_process(task);
|
|
|
|
if (sample_flag || trace_flag)
|
|
add_active_thread(cpu_ctx, task->pid, task->tgid);
|
|
|
|
if (trace_flag) {
|
|
put_sched_sample(task, true, quadd_get_time());
|
|
cpu_ctx->is_tracing_enabled = 1;
|
|
}
|
|
|
|
if (sample_flag) {
|
|
if (likely(!cpu_ctx->is_sampling_enabled)) {
|
|
if (ctx->pmu)
|
|
ctx->pmu->start();
|
|
|
|
if (quadd_mode_is_sampling_timer(ctx))
|
|
start_hrtimer(cpu_ctx);
|
|
|
|
cpu_ctx->is_sampling_enabled = 1;
|
|
} else
|
|
pr_warn_once("warning: sampling is already enabled\n");
|
|
}
|
|
}
|
|
|
|
void __quadd_task_sched_out(struct task_struct *prev,
|
|
struct task_struct *next)
|
|
{
|
|
u64 ts;
|
|
struct pt_regs *user_regs;
|
|
struct quadd_cpu_context *cpu_ctx = this_cpu_ptr(hrt.cpu_ctx);
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
|
|
if (likely(!atomic_read(&hrt.active)))
|
|
return;
|
|
|
|
ts = quadd_get_time();
|
|
|
|
if (quadd_mode_is_sampling(ctx) && cpu_ctx->is_sampling_enabled) {
|
|
if (quadd_mode_is_sampling_sched(ctx) &&
|
|
is_task_active(cpu_ctx, prev)) {
|
|
user_regs = task_pt_regs(prev);
|
|
if (user_regs)
|
|
read_all_sources(user_regs, prev, ts);
|
|
}
|
|
|
|
if (quadd_mode_is_sampling_timer(ctx))
|
|
cancel_hrtimer(cpu_ctx);
|
|
|
|
if (ctx->pmu)
|
|
ctx->pmu->stop();
|
|
|
|
cpu_ctx->is_sampling_enabled = 0;
|
|
}
|
|
|
|
if (quadd_mode_is_tracing(ctx) && cpu_ctx->is_tracing_enabled) {
|
|
if (is_task_active(cpu_ctx, prev))
|
|
put_sched_sample(prev, false, ts);
|
|
cpu_ctx->is_tracing_enabled = 0;
|
|
}
|
|
|
|
remove_active_thread(cpu_ctx, prev->pid);
|
|
}
|
|
|
|
void __quadd_event_mmap(struct vm_area_struct *vma)
|
|
{
|
|
if (likely(!atomic_read(&hrt.mmap_active)))
|
|
return;
|
|
|
|
if (!is_sample_process(current))
|
|
return;
|
|
|
|
quadd_process_mmap(vma, current);
|
|
}
|
|
|
|
bool quadd_is_inherited(struct task_struct *task)
|
|
{
|
|
struct task_struct *p;
|
|
|
|
if (unlikely(hrt.root_pid == 0))
|
|
return false;
|
|
|
|
for (p = task; p != &init_task;) {
|
|
if (task_tgid_nr(p) == hrt.root_pid)
|
|
return true;
|
|
|
|
rcu_read_lock();
|
|
p = rcu_dereference(p->real_parent);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void __quadd_event_fork(struct task_struct *task)
|
|
{
|
|
pid_t tgid;
|
|
|
|
if (likely(!atomic_read(&hrt.mmap_active)))
|
|
return;
|
|
|
|
if (!quadd_mode_is_process_tree(hrt.quadd_ctx))
|
|
return;
|
|
|
|
tgid = task_tgid_nr(task);
|
|
if (pid_list_search(tgid))
|
|
return;
|
|
|
|
read_lock(&tasklist_lock);
|
|
if (quadd_is_inherited(task)) {
|
|
quadd_get_task_mmaps(hrt.quadd_ctx, task);
|
|
pid_list_add(tgid);
|
|
}
|
|
read_unlock(&tasklist_lock);
|
|
}
|
|
|
|
void __quadd_event_exit(struct task_struct *task)
|
|
{
|
|
pid_t tgid;
|
|
|
|
if (likely(!atomic_read(&hrt.mmap_active)))
|
|
return;
|
|
|
|
if (!quadd_mode_is_process_tree(hrt.quadd_ctx))
|
|
return;
|
|
|
|
tgid = task_tgid_nr(task);
|
|
if (!pid_list_search(tgid))
|
|
return;
|
|
|
|
read_lock(&tasklist_lock);
|
|
if (quadd_is_inherited(task))
|
|
pid_list_del(tgid);
|
|
read_unlock(&tasklist_lock);
|
|
}
|
|
|
|
void __quadd_event_comm(struct task_struct *task, bool exec)
|
|
{
|
|
if (likely(!atomic_read(&hrt.active)))
|
|
return;
|
|
|
|
if (!is_profile_process(task))
|
|
return;
|
|
|
|
put_comm_sample(task, exec);
|
|
}
|
|
|
|
static void reset_cpu_ctx(void)
|
|
{
|
|
int cpu_id;
|
|
struct quadd_cpu_context *cpu_ctx;
|
|
struct quadd_thread_data *t_data;
|
|
|
|
for_each_possible_cpu(cpu_id) {
|
|
cpu_ctx = per_cpu_ptr(hrt.cpu_ctx, cpu_id);
|
|
t_data = &cpu_ctx->active_thread;
|
|
|
|
cpu_ctx->is_sampling_enabled = 0;
|
|
cpu_ctx->is_tracing_enabled = 0;
|
|
|
|
t_data->pid = -1;
|
|
t_data->tgid = -1;
|
|
}
|
|
}
|
|
|
|
static void get_initial_samples(struct quadd_ctx *ctx)
|
|
{
|
|
struct task_struct *p, *t;
|
|
|
|
if (quadd_mode_is_sampling(ctx))
|
|
quadd_get_mmaps(ctx);
|
|
|
|
if (quadd_mode_is_process_all(ctx)) {
|
|
bool is_tree = quadd_mode_is_process_tree(ctx);
|
|
|
|
read_lock(&tasklist_lock);
|
|
for_each_process(p) {
|
|
for_each_thread(p, t)
|
|
put_comm_sample(t, false);
|
|
|
|
if (is_tree && quadd_is_inherited(p))
|
|
pid_list_add(task_pid_nr(p));
|
|
}
|
|
read_unlock(&tasklist_lock);
|
|
} else if (quadd_mode_is_process_tree(ctx)) {
|
|
read_lock(&tasklist_lock);
|
|
for_each_process(p) {
|
|
if (quadd_is_inherited(p)) {
|
|
pid_list_add(task_pid_nr(p));
|
|
for_each_thread(p, t)
|
|
put_comm_sample(t, false);
|
|
}
|
|
}
|
|
read_unlock(&tasklist_lock);
|
|
} else {
|
|
pid_t root_pid = hrt.root_pid;
|
|
|
|
if (root_pid > 0) {
|
|
read_lock(&tasklist_lock);
|
|
p = get_pid_task(find_vpid(root_pid), PIDTYPE_PID);
|
|
if (p) {
|
|
for_each_thread(p, t)
|
|
put_comm_sample(t, false);
|
|
put_task_struct(p);
|
|
}
|
|
read_unlock(&tasklist_lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
int quadd_hrt_start(void)
|
|
{
|
|
int cpuid;
|
|
u64 period;
|
|
long freq;
|
|
unsigned int extra;
|
|
struct quadd_ctx *ctx = hrt.quadd_ctx;
|
|
struct quadd_parameters *param = &ctx->param;
|
|
|
|
freq = ctx->param.freq;
|
|
freq = max_t(long, QUADD_HRT_MIN_FREQ, freq);
|
|
period = NSEC_PER_SEC / freq;
|
|
hrt.sample_period = period;
|
|
hrt.root_pid = param->nr_pids > 0 ? param->pids[0] : 0;
|
|
|
|
if (ctx->param.ma_freq > 0)
|
|
hrt.ma_period = MSEC_PER_SEC / ctx->param.ma_freq;
|
|
else
|
|
hrt.ma_period = 0;
|
|
|
|
atomic64_set(&hrt.counter_samples, 0);
|
|
atomic64_set(&hrt.skipped_samples, 0);
|
|
atomic_set(&hrt.seqid, 0);
|
|
|
|
reset_cpu_ctx();
|
|
|
|
extra = param->reserved[QUADD_PARAM_IDX_EXTRA];
|
|
|
|
if (param->backtrace) {
|
|
struct quadd_unw_methods *um = &hrt.um;
|
|
|
|
um->fp = extra & QUADD_PARAM_EXTRA_BT_FP ? 1 : 0;
|
|
um->ut = extra & QUADD_PARAM_EXTRA_BT_UT ? 1 : 0;
|
|
um->ut_ce = extra & QUADD_PARAM_EXTRA_BT_UT_CE ? 1 : 0;
|
|
um->dwarf = extra & QUADD_PARAM_EXTRA_BT_DWARF ? 1 : 0;
|
|
|
|
pr_info("unw methods: fp/ut/ut_ce/dwarf: %u/%u/%u/%u\n",
|
|
um->fp, um->ut, um->ut_ce, um->dwarf);
|
|
}
|
|
|
|
if (hrt.tc && (extra & QUADD_PARAM_EXTRA_USE_ARCH_TIMER) &&
|
|
(hrt.arch_timer_user_access ||
|
|
(extra & QUADD_PARAM_EXTRA_FORCE_ARCH_TIMER)))
|
|
hrt.use_arch_timer = 1;
|
|
else
|
|
hrt.use_arch_timer = 0;
|
|
|
|
pr_info("timer: %s\n", hrt.use_arch_timer ? "arch" : "monotonic clock");
|
|
|
|
hrt.get_stack_offset =
|
|
(extra & QUADD_PARAM_EXTRA_STACK_OFFSET) ? 1 : 0;
|
|
|
|
for_each_possible_cpu(cpuid) {
|
|
if (ctx->pmu->get_arch(cpuid))
|
|
put_header(cpuid, false);
|
|
}
|
|
put_header(0, true);
|
|
|
|
atomic_set(&hrt.mmap_active, 1);
|
|
|
|
/* Enable the mmap events processing before quadd_get_mmaps()
|
|
* otherwise we can miss some events.
|
|
*/
|
|
smp_wmb();
|
|
|
|
get_initial_samples(ctx);
|
|
quadd_ma_start(&hrt);
|
|
|
|
/* Enable the sampling only after quadd_get_mmaps() */
|
|
smp_wmb();
|
|
|
|
atomic_set(&hrt.active, 1);
|
|
|
|
pr_info("Start hrt: freq/period: %ld/%llu\n", freq, period);
|
|
return 0;
|
|
}
|
|
|
|
void quadd_hrt_stop(void)
|
|
{
|
|
pr_info("Stop hrt, samples all/skipped: %lld/%lld\n",
|
|
(long long)atomic64_read(&hrt.counter_samples),
|
|
(long long)atomic64_read(&hrt.skipped_samples));
|
|
|
|
quadd_ma_stop(&hrt);
|
|
|
|
atomic_set(&hrt.active, 0);
|
|
atomic_set(&hrt.mmap_active, 0);
|
|
|
|
pid_list_clear();
|
|
|
|
/* reset_cpu_ctx(); */
|
|
}
|
|
|
|
void quadd_hrt_deinit(void)
|
|
{
|
|
if (atomic_read(&hrt.active))
|
|
quadd_hrt_stop();
|
|
|
|
free_percpu(hrt.cpu_ctx);
|
|
}
|
|
|
|
void quadd_hrt_get_state(struct quadd_module_state *state)
|
|
{
|
|
state->nr_all_samples = atomic64_read(&hrt.counter_samples);
|
|
state->nr_skipped_samples = atomic64_read(&hrt.skipped_samples);
|
|
}
|
|
|
|
static void init_arch_timer(void)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
|
|
struct arch_timer_kvm_info *info;
|
|
#endif
|
|
|
|
u32 cntkctl = arch_timer_get_cntkctl();
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
|
|
info = arch_timer_get_kvm_info();
|
|
hrt.tc = &info->timecounter;
|
|
#else
|
|
hrt.tc = arch_timer_get_timecounter();
|
|
#endif
|
|
|
|
hrt.arch_timer_user_access =
|
|
(cntkctl & ARCH_TIMER_USR_VCT_ACCESS_EN) ? 1 : 0;
|
|
}
|
|
|
|
struct quadd_hrt_ctx *quadd_hrt_init(struct quadd_ctx *ctx)
|
|
{
|
|
int cpu_id;
|
|
u64 period;
|
|
long freq;
|
|
struct quadd_cpu_context *cpu_ctx;
|
|
|
|
hrt.quadd_ctx = ctx;
|
|
|
|
atomic_set(&hrt.active, 0);
|
|
atomic_set(&hrt.mmap_active, 0);
|
|
|
|
freq = ctx->param.freq;
|
|
freq = max_t(long, QUADD_HRT_MIN_FREQ, freq);
|
|
period = NSEC_PER_SEC / freq;
|
|
hrt.sample_period = period;
|
|
hrt.root_pid = 0;
|
|
|
|
INIT_LIST_HEAD(&hrt.pid_list);
|
|
raw_spin_lock_init(&hrt.pid_list_lock);
|
|
|
|
if (ctx->param.ma_freq > 0)
|
|
hrt.ma_period = MSEC_PER_SEC / ctx->param.ma_freq;
|
|
else
|
|
hrt.ma_period = 0;
|
|
|
|
atomic64_set(&hrt.counter_samples, 0);
|
|
atomic64_set(&hrt.skipped_samples, 0);
|
|
atomic_set(&hrt.seqid, 0);
|
|
|
|
init_arch_timer();
|
|
|
|
hrt.cpu_ctx = alloc_percpu(struct quadd_cpu_context);
|
|
if (!hrt.cpu_ctx)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
for_each_possible_cpu(cpu_id) {
|
|
cpu_ctx = per_cpu_ptr(hrt.cpu_ctx, cpu_id);
|
|
|
|
cpu_ctx->is_sampling_enabled = 0;
|
|
cpu_ctx->is_tracing_enabled = 0;
|
|
|
|
cpu_ctx->active_thread.pid = -1;
|
|
cpu_ctx->active_thread.tgid = -1;
|
|
|
|
cpu_ctx->cc.hrt = &hrt;
|
|
|
|
init_hrtimer(cpu_ctx);
|
|
}
|
|
|
|
return &hrt;
|
|
}
|