mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-20 19:23:57 +08:00
0195c00244
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQIVAwUAT3NKzROxKuMESys7AQKElw/+JyDxJSlj+g+nymkx8IVVuU8CsEwNLgRk 8KEnRfLhGtkXFLSJYWO6jzGo16F8Uqli1PdMFte/wagSv0285/HZaKlkkBVHdJ/m u40oSjgT013bBh6MQ0Oaf8pFezFUiQB5zPOA9QGaLVGDLXCmgqUgd7exaD5wRIwB ZmyItjZeAVnDfk1R+ZiNYytHAi8A5wSB+eFDCIQYgyulA1Igd1UnRtx+dRKbvc/m rWQ6KWbZHIdvP1ksd8wHHkrlUD2pEeJ8glJLsZUhMm/5oMf/8RmOCvmo8rvE/qwl eDQ1h4cGYlfjobxXZMHqAN9m7Jg2bI946HZjdb7/7oCeO6VW3FwPZ/Ic75p+wp45 HXJTItufERYk6QxShiOKvA+QexnYwY0IT5oRP4DrhdVB/X9cl2MoaZHC+RbYLQy+ /5VNZKi38iK4F9AbFamS7kd0i5QszA/ZzEzKZ6VMuOp3W/fagpn4ZJT1LIA3m4A9 Q0cj24mqeyCfjysu0TMbPtaN+Yjeu1o1OFRvM8XffbZsp5bNzuTDEvviJ2NXw4vK 4qUHulhYSEWcu9YgAZXvEWDEM78FXCkg2v/CrZXH5tyc95kUkMPcgG+QZBB5wElR FaOKpiC/BuNIGEf02IZQ4nfDxE90QwnDeoYeV+FvNj9UEOopJ5z5bMPoTHxm4cCD NypQthI85pc= =G9mT -----END PGP SIGNATURE----- Merge tag 'split-asm_system_h-for-linus-20120328' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-asm_system Pull "Disintegrate and delete asm/system.h" from David Howells: "Here are a bunch of patches to disintegrate asm/system.h into a set of separate bits to relieve the problem of circular inclusion dependencies. I've built all the working defconfigs from all the arches that I can and made sure that they don't break. The reason for these patches is that I recently encountered a circular dependency problem that came about when I produced some patches to optimise get_order() by rewriting it to use ilog2(). This uses bitops - and on the SH arch asm/bitops.h drags in asm-generic/get_order.h by a circuituous route involving asm/system.h. The main difficulty seems to be asm/system.h. It holds a number of low level bits with no/few dependencies that are commonly used (eg. memory barriers) and a number of bits with more dependencies that aren't used in many places (eg. switch_to()). These patches break asm/system.h up into the following core pieces: (1) asm/barrier.h Move memory barriers here. This already done for MIPS and Alpha. (2) asm/switch_to.h Move switch_to() and related stuff here. (3) asm/exec.h Move arch_align_stack() here. Other process execution related bits could perhaps go here from asm/processor.h. (4) asm/cmpxchg.h Move xchg() and cmpxchg() here as they're full word atomic ops and frequently used by atomic_xchg() and atomic_cmpxchg(). (5) asm/bug.h Move die() and related bits. (6) asm/auxvec.h Move AT_VECTOR_SIZE_ARCH here. Other arch headers are created as needed on a per-arch basis." Fixed up some conflicts from other header file cleanups and moving code around that has happened in the meantime, so David's testing is somewhat weakened by that. We'll find out anything that got broken and fix it.. * tag 'split-asm_system_h-for-linus-20120328' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-asm_system: (38 commits) Delete all instances of asm/system.h Remove all #inclusions of asm/system.h Add #includes needed to permit the removal of asm/system.h Move all declarations of free_initmem() to linux/mm.h Disintegrate asm/system.h for OpenRISC Split arch_align_stack() out from asm-generic/system.h Split the switch_to() wrapper out of asm-generic/system.h Move the asm-generic/system.h xchg() implementation to asm-generic/cmpxchg.h Create asm-generic/barrier.h Make asm-generic/cmpxchg.h #include asm-generic/cmpxchg-local.h Disintegrate asm/system.h for Xtensa Disintegrate asm/system.h for Unicore32 [based on ver #3, changed by gxt] Disintegrate asm/system.h for Tile Disintegrate asm/system.h for Sparc Disintegrate asm/system.h for SH Disintegrate asm/system.h for Score Disintegrate asm/system.h for S390 Disintegrate asm/system.h for PowerPC Disintegrate asm/system.h for PA-RISC Disintegrate asm/system.h for MN10300 ...
1241 lines
25 KiB
C
1241 lines
25 KiB
C
/**
|
|
* arch/s390/oprofile/hwsampler.c
|
|
*
|
|
* Copyright IBM Corp. 2010
|
|
* Author: Heinz Graalfs <graalfs@de.ibm.com>
|
|
*/
|
|
|
|
#include <linux/kernel_stat.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/semaphore.h>
|
|
#include <linux/oom.h>
|
|
#include <linux/oprofile.h>
|
|
|
|
#include <asm/facility.h>
|
|
#include <asm/cpu_mf.h>
|
|
#include <asm/irq.h>
|
|
|
|
#include "hwsampler.h"
|
|
#include "op_counter.h"
|
|
|
|
#define MAX_NUM_SDB 511
|
|
#define MIN_NUM_SDB 1
|
|
|
|
#define ALERT_REQ_MASK 0x4000000000000000ul
|
|
#define BUFFER_FULL_MASK 0x8000000000000000ul
|
|
|
|
DECLARE_PER_CPU(struct hws_cpu_buffer, sampler_cpu_buffer);
|
|
|
|
struct hws_execute_parms {
|
|
void *buffer;
|
|
signed int rc;
|
|
};
|
|
|
|
DEFINE_PER_CPU(struct hws_cpu_buffer, sampler_cpu_buffer);
|
|
EXPORT_PER_CPU_SYMBOL(sampler_cpu_buffer);
|
|
|
|
static DEFINE_MUTEX(hws_sem);
|
|
static DEFINE_MUTEX(hws_sem_oom);
|
|
|
|
static unsigned char hws_flush_all;
|
|
static unsigned int hws_oom;
|
|
static struct workqueue_struct *hws_wq;
|
|
|
|
static unsigned int hws_state;
|
|
enum {
|
|
HWS_INIT = 1,
|
|
HWS_DEALLOCATED,
|
|
HWS_STOPPED,
|
|
HWS_STARTED,
|
|
HWS_STOPPING };
|
|
|
|
/* set to 1 if called by kernel during memory allocation */
|
|
static unsigned char oom_killer_was_active;
|
|
/* size of SDBT and SDB as of allocate API */
|
|
static unsigned long num_sdbt = 100;
|
|
static unsigned long num_sdb = 511;
|
|
/* sampling interval (machine cycles) */
|
|
static unsigned long interval;
|
|
|
|
static unsigned long min_sampler_rate;
|
|
static unsigned long max_sampler_rate;
|
|
|
|
static int ssctl(void *buffer)
|
|
{
|
|
int cc;
|
|
|
|
/* set in order to detect a program check */
|
|
cc = 1;
|
|
|
|
asm volatile(
|
|
"0: .insn s,0xB2870000,0(%1)\n"
|
|
"1: ipm %0\n"
|
|
" srl %0,28\n"
|
|
"2:\n"
|
|
EX_TABLE(0b, 2b) EX_TABLE(1b, 2b)
|
|
: "+d" (cc), "+a" (buffer)
|
|
: "m" (*((struct hws_ssctl_request_block *)buffer))
|
|
: "cc", "memory");
|
|
|
|
return cc ? -EINVAL : 0 ;
|
|
}
|
|
|
|
static int qsi(void *buffer)
|
|
{
|
|
int cc;
|
|
cc = 1;
|
|
|
|
asm volatile(
|
|
"0: .insn s,0xB2860000,0(%1)\n"
|
|
"1: lhi %0,0\n"
|
|
"2:\n"
|
|
EX_TABLE(0b, 2b) EX_TABLE(1b, 2b)
|
|
: "=d" (cc), "+a" (buffer)
|
|
: "m" (*((struct hws_qsi_info_block *)buffer))
|
|
: "cc", "memory");
|
|
|
|
return cc ? -EINVAL : 0;
|
|
}
|
|
|
|
static void execute_qsi(void *parms)
|
|
{
|
|
struct hws_execute_parms *ep = parms;
|
|
|
|
ep->rc = qsi(ep->buffer);
|
|
}
|
|
|
|
static void execute_ssctl(void *parms)
|
|
{
|
|
struct hws_execute_parms *ep = parms;
|
|
|
|
ep->rc = ssctl(ep->buffer);
|
|
}
|
|
|
|
static int smp_ctl_ssctl_stop(int cpu)
|
|
{
|
|
int rc;
|
|
struct hws_execute_parms ep;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
cb->ssctl.es = 0;
|
|
cb->ssctl.cs = 0;
|
|
|
|
ep.buffer = &cb->ssctl;
|
|
smp_call_function_single(cpu, execute_ssctl, &ep, 1);
|
|
rc = ep.rc;
|
|
if (rc) {
|
|
printk(KERN_ERR "hwsampler: CPU %d CPUMF SSCTL failed.\n", cpu);
|
|
dump_stack();
|
|
}
|
|
|
|
ep.buffer = &cb->qsi;
|
|
smp_call_function_single(cpu, execute_qsi, &ep, 1);
|
|
|
|
if (cb->qsi.es || cb->qsi.cs) {
|
|
printk(KERN_EMERG "CPUMF sampling did not stop properly.\n");
|
|
dump_stack();
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smp_ctl_ssctl_deactivate(int cpu)
|
|
{
|
|
int rc;
|
|
struct hws_execute_parms ep;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
cb->ssctl.es = 1;
|
|
cb->ssctl.cs = 0;
|
|
|
|
ep.buffer = &cb->ssctl;
|
|
smp_call_function_single(cpu, execute_ssctl, &ep, 1);
|
|
rc = ep.rc;
|
|
if (rc)
|
|
printk(KERN_ERR "hwsampler: CPU %d CPUMF SSCTL failed.\n", cpu);
|
|
|
|
ep.buffer = &cb->qsi;
|
|
smp_call_function_single(cpu, execute_qsi, &ep, 1);
|
|
|
|
if (cb->qsi.cs)
|
|
printk(KERN_EMERG "CPUMF sampling was not set inactive.\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smp_ctl_ssctl_enable_activate(int cpu, unsigned long interval)
|
|
{
|
|
int rc;
|
|
struct hws_execute_parms ep;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
cb->ssctl.h = 1;
|
|
cb->ssctl.tear = cb->first_sdbt;
|
|
cb->ssctl.dear = *(unsigned long *) cb->first_sdbt;
|
|
cb->ssctl.interval = interval;
|
|
cb->ssctl.es = 1;
|
|
cb->ssctl.cs = 1;
|
|
|
|
ep.buffer = &cb->ssctl;
|
|
smp_call_function_single(cpu, execute_ssctl, &ep, 1);
|
|
rc = ep.rc;
|
|
if (rc)
|
|
printk(KERN_ERR "hwsampler: CPU %d CPUMF SSCTL failed.\n", cpu);
|
|
|
|
ep.buffer = &cb->qsi;
|
|
smp_call_function_single(cpu, execute_qsi, &ep, 1);
|
|
if (ep.rc)
|
|
printk(KERN_ERR "hwsampler: CPU %d CPUMF QSI failed.\n", cpu);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smp_ctl_qsi(int cpu)
|
|
{
|
|
struct hws_execute_parms ep;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
ep.buffer = &cb->qsi;
|
|
smp_call_function_single(cpu, execute_qsi, &ep, 1);
|
|
|
|
return ep.rc;
|
|
}
|
|
|
|
static inline unsigned long *trailer_entry_ptr(unsigned long v)
|
|
{
|
|
void *ret;
|
|
|
|
ret = (void *)v;
|
|
ret += PAGE_SIZE;
|
|
ret -= sizeof(struct hws_trailer_entry);
|
|
|
|
return (unsigned long *) ret;
|
|
}
|
|
|
|
static void hws_ext_handler(struct ext_code ext_code,
|
|
unsigned int param32, unsigned long param64)
|
|
{
|
|
struct hws_cpu_buffer *cb = &__get_cpu_var(sampler_cpu_buffer);
|
|
|
|
if (!(param32 & CPU_MF_INT_SF_MASK))
|
|
return;
|
|
|
|
kstat_cpu(smp_processor_id()).irqs[EXTINT_CPM]++;
|
|
atomic_xchg(&cb->ext_params, atomic_read(&cb->ext_params) | param32);
|
|
|
|
if (hws_wq)
|
|
queue_work(hws_wq, &cb->worker);
|
|
}
|
|
|
|
static void worker(struct work_struct *work);
|
|
|
|
static void add_samples_to_oprofile(unsigned cpu, unsigned long *,
|
|
unsigned long *dear);
|
|
|
|
static void init_all_cpu_buffers(void)
|
|
{
|
|
int cpu;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
memset(cb, 0, sizeof(struct hws_cpu_buffer));
|
|
}
|
|
}
|
|
|
|
static int is_link_entry(unsigned long *s)
|
|
{
|
|
return *s & 0x1ul ? 1 : 0;
|
|
}
|
|
|
|
static unsigned long *get_next_sdbt(unsigned long *s)
|
|
{
|
|
return (unsigned long *) (*s & ~0x1ul);
|
|
}
|
|
|
|
static int prepare_cpu_buffers(void)
|
|
{
|
|
int cpu;
|
|
int rc;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
rc = 0;
|
|
for_each_online_cpu(cpu) {
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
atomic_set(&cb->ext_params, 0);
|
|
cb->worker_entry = 0;
|
|
cb->sample_overflow = 0;
|
|
cb->req_alert = 0;
|
|
cb->incorrect_sdbt_entry = 0;
|
|
cb->invalid_entry_address = 0;
|
|
cb->loss_of_sample_data = 0;
|
|
cb->sample_auth_change_alert = 0;
|
|
cb->finish = 0;
|
|
cb->oom = 0;
|
|
cb->stop_mode = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* allocate_sdbt() - allocate sampler memory
|
|
* @cpu: the cpu for which sampler memory is allocated
|
|
*
|
|
* A 4K page is allocated for each requested SDBT.
|
|
* A maximum of 511 4K pages are allocated for the SDBs in each of the SDBTs.
|
|
* Set ALERT_REQ mask in each SDBs trailer.
|
|
* Returns zero if successful, <0 otherwise.
|
|
*/
|
|
static int allocate_sdbt(int cpu)
|
|
{
|
|
int j, k, rc;
|
|
unsigned long *sdbt;
|
|
unsigned long sdb;
|
|
unsigned long *tail;
|
|
unsigned long *trailer;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
if (cb->first_sdbt)
|
|
return -EINVAL;
|
|
|
|
sdbt = NULL;
|
|
tail = sdbt;
|
|
|
|
for (j = 0; j < num_sdbt; j++) {
|
|
sdbt = (unsigned long *)get_zeroed_page(GFP_KERNEL);
|
|
|
|
mutex_lock(&hws_sem_oom);
|
|
/* OOM killer might have been activated */
|
|
barrier();
|
|
if (oom_killer_was_active || !sdbt) {
|
|
if (sdbt)
|
|
free_page((unsigned long)sdbt);
|
|
|
|
goto allocate_sdbt_error;
|
|
}
|
|
if (cb->first_sdbt == 0)
|
|
cb->first_sdbt = (unsigned long)sdbt;
|
|
|
|
/* link current page to tail of chain */
|
|
if (tail)
|
|
*tail = (unsigned long)(void *)sdbt + 1;
|
|
|
|
mutex_unlock(&hws_sem_oom);
|
|
|
|
for (k = 0; k < num_sdb; k++) {
|
|
/* get and set SDB page */
|
|
sdb = get_zeroed_page(GFP_KERNEL);
|
|
|
|
mutex_lock(&hws_sem_oom);
|
|
/* OOM killer might have been activated */
|
|
barrier();
|
|
if (oom_killer_was_active || !sdb) {
|
|
if (sdb)
|
|
free_page(sdb);
|
|
|
|
goto allocate_sdbt_error;
|
|
}
|
|
*sdbt = sdb;
|
|
trailer = trailer_entry_ptr(*sdbt);
|
|
*trailer = ALERT_REQ_MASK;
|
|
sdbt++;
|
|
mutex_unlock(&hws_sem_oom);
|
|
}
|
|
tail = sdbt;
|
|
}
|
|
mutex_lock(&hws_sem_oom);
|
|
if (oom_killer_was_active)
|
|
goto allocate_sdbt_error;
|
|
|
|
rc = 0;
|
|
if (tail)
|
|
*tail = (unsigned long)
|
|
((void *)cb->first_sdbt) + 1;
|
|
|
|
allocate_sdbt_exit:
|
|
mutex_unlock(&hws_sem_oom);
|
|
return rc;
|
|
|
|
allocate_sdbt_error:
|
|
rc = -ENOMEM;
|
|
goto allocate_sdbt_exit;
|
|
}
|
|
|
|
/*
|
|
* deallocate_sdbt() - deallocate all sampler memory
|
|
*
|
|
* For each online CPU all SDBT trees are deallocated.
|
|
* Returns the number of freed pages.
|
|
*/
|
|
static int deallocate_sdbt(void)
|
|
{
|
|
int cpu;
|
|
int counter;
|
|
|
|
counter = 0;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
unsigned long start;
|
|
unsigned long sdbt;
|
|
unsigned long *curr;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
if (!cb->first_sdbt)
|
|
continue;
|
|
|
|
sdbt = cb->first_sdbt;
|
|
curr = (unsigned long *) sdbt;
|
|
start = sdbt;
|
|
|
|
/* we'll free the SDBT after all SDBs are processed... */
|
|
while (1) {
|
|
if (!*curr || !sdbt)
|
|
break;
|
|
|
|
/* watch for link entry reset if found */
|
|
if (is_link_entry(curr)) {
|
|
curr = get_next_sdbt(curr);
|
|
if (sdbt)
|
|
free_page(sdbt);
|
|
|
|
/* we are done if we reach the start */
|
|
if ((unsigned long) curr == start)
|
|
break;
|
|
else
|
|
sdbt = (unsigned long) curr;
|
|
} else {
|
|
/* process SDB pointer */
|
|
if (*curr) {
|
|
free_page(*curr);
|
|
curr++;
|
|
}
|
|
}
|
|
counter++;
|
|
}
|
|
cb->first_sdbt = 0;
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
static int start_sampling(int cpu)
|
|
{
|
|
int rc;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
rc = smp_ctl_ssctl_enable_activate(cpu, interval);
|
|
if (rc) {
|
|
printk(KERN_INFO "hwsampler: CPU %d ssctl failed.\n", cpu);
|
|
goto start_exit;
|
|
}
|
|
|
|
rc = -EINVAL;
|
|
if (!cb->qsi.es) {
|
|
printk(KERN_INFO "hwsampler: CPU %d ssctl not enabled.\n", cpu);
|
|
goto start_exit;
|
|
}
|
|
|
|
if (!cb->qsi.cs) {
|
|
printk(KERN_INFO "hwsampler: CPU %d ssctl not active.\n", cpu);
|
|
goto start_exit;
|
|
}
|
|
|
|
printk(KERN_INFO
|
|
"hwsampler: CPU %d, CPUMF Sampling started, interval %lu.\n",
|
|
cpu, interval);
|
|
|
|
rc = 0;
|
|
|
|
start_exit:
|
|
return rc;
|
|
}
|
|
|
|
static int stop_sampling(int cpu)
|
|
{
|
|
unsigned long v;
|
|
int rc;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
rc = smp_ctl_qsi(cpu);
|
|
WARN_ON(rc);
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
if (!rc && !cb->qsi.es)
|
|
printk(KERN_INFO "hwsampler: CPU %d, already stopped.\n", cpu);
|
|
|
|
rc = smp_ctl_ssctl_stop(cpu);
|
|
if (rc) {
|
|
printk(KERN_INFO "hwsampler: CPU %d, ssctl stop error %d.\n",
|
|
cpu, rc);
|
|
goto stop_exit;
|
|
}
|
|
|
|
printk(KERN_INFO "hwsampler: CPU %d, CPUMF Sampling stopped.\n", cpu);
|
|
|
|
stop_exit:
|
|
v = cb->req_alert;
|
|
if (v)
|
|
printk(KERN_ERR "hwsampler: CPU %d CPUMF Request alert,"
|
|
" count=%lu.\n", cpu, v);
|
|
|
|
v = cb->loss_of_sample_data;
|
|
if (v)
|
|
printk(KERN_ERR "hwsampler: CPU %d CPUMF Loss of sample data,"
|
|
" count=%lu.\n", cpu, v);
|
|
|
|
v = cb->invalid_entry_address;
|
|
if (v)
|
|
printk(KERN_ERR "hwsampler: CPU %d CPUMF Invalid entry address,"
|
|
" count=%lu.\n", cpu, v);
|
|
|
|
v = cb->incorrect_sdbt_entry;
|
|
if (v)
|
|
printk(KERN_ERR
|
|
"hwsampler: CPU %d CPUMF Incorrect SDBT address,"
|
|
" count=%lu.\n", cpu, v);
|
|
|
|
v = cb->sample_auth_change_alert;
|
|
if (v)
|
|
printk(KERN_ERR
|
|
"hwsampler: CPU %d CPUMF Sample authorization change,"
|
|
" count=%lu.\n", cpu, v);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int check_hardware_prerequisites(void)
|
|
{
|
|
if (!test_facility(68))
|
|
return -EOPNOTSUPP;
|
|
return 0;
|
|
}
|
|
/*
|
|
* hws_oom_callback() - the OOM callback function
|
|
*
|
|
* In case the callback is invoked during memory allocation for the
|
|
* hw sampler, all obtained memory is deallocated and a flag is set
|
|
* so main sampler memory allocation can exit with a failure code.
|
|
* In case the callback is invoked during sampling the hw sampler
|
|
* is deactivated for all CPUs.
|
|
*/
|
|
static int hws_oom_callback(struct notifier_block *nfb,
|
|
unsigned long dummy, void *parm)
|
|
{
|
|
unsigned long *freed;
|
|
int cpu;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
freed = parm;
|
|
|
|
mutex_lock(&hws_sem_oom);
|
|
|
|
if (hws_state == HWS_DEALLOCATED) {
|
|
/* during memory allocation */
|
|
if (oom_killer_was_active == 0) {
|
|
oom_killer_was_active = 1;
|
|
*freed += deallocate_sdbt();
|
|
}
|
|
} else {
|
|
int i;
|
|
cpu = get_cpu();
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
if (!cb->oom) {
|
|
for_each_online_cpu(i) {
|
|
smp_ctl_ssctl_deactivate(i);
|
|
cb->oom = 1;
|
|
}
|
|
cb->finish = 1;
|
|
|
|
printk(KERN_INFO
|
|
"hwsampler: CPU %d, OOM notify during CPUMF Sampling.\n",
|
|
cpu);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&hws_sem_oom);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block hws_oom_notifier = {
|
|
.notifier_call = hws_oom_callback
|
|
};
|
|
|
|
static int hws_cpu_callback(struct notifier_block *nfb,
|
|
unsigned long action, void *hcpu)
|
|
{
|
|
/* We do not have sampler space available for all possible CPUs.
|
|
All CPUs should be online when hw sampling is activated. */
|
|
return (hws_state <= HWS_DEALLOCATED) ? NOTIFY_OK : NOTIFY_BAD;
|
|
}
|
|
|
|
static struct notifier_block hws_cpu_notifier = {
|
|
.notifier_call = hws_cpu_callback
|
|
};
|
|
|
|
/**
|
|
* hwsampler_deactivate() - set hardware sampling temporarily inactive
|
|
* @cpu: specifies the CPU to be set inactive.
|
|
*
|
|
* Returns 0 on success, !0 on failure.
|
|
*/
|
|
int hwsampler_deactivate(unsigned int cpu)
|
|
{
|
|
/*
|
|
* Deactivate hw sampling temporarily and flush the buffer
|
|
* by pushing all the pending samples to oprofile buffer.
|
|
*
|
|
* This function can be called under one of the following conditions:
|
|
* Memory unmap, task is exiting.
|
|
*/
|
|
int rc;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
rc = 0;
|
|
mutex_lock(&hws_sem);
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
if (hws_state == HWS_STARTED) {
|
|
rc = smp_ctl_qsi(cpu);
|
|
WARN_ON(rc);
|
|
if (cb->qsi.cs) {
|
|
rc = smp_ctl_ssctl_deactivate(cpu);
|
|
if (rc) {
|
|
printk(KERN_INFO
|
|
"hwsampler: CPU %d, CPUMF Deactivation failed.\n", cpu);
|
|
cb->finish = 1;
|
|
hws_state = HWS_STOPPING;
|
|
} else {
|
|
hws_flush_all = 1;
|
|
/* Add work to queue to read pending samples.*/
|
|
queue_work_on(cpu, hws_wq, &cb->worker);
|
|
}
|
|
}
|
|
}
|
|
mutex_unlock(&hws_sem);
|
|
|
|
if (hws_wq)
|
|
flush_workqueue(hws_wq);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* hwsampler_activate() - activate/resume hardware sampling which was deactivated
|
|
* @cpu: specifies the CPU to be set active.
|
|
*
|
|
* Returns 0 on success, !0 on failure.
|
|
*/
|
|
int hwsampler_activate(unsigned int cpu)
|
|
{
|
|
/*
|
|
* Re-activate hw sampling. This should be called in pair with
|
|
* hwsampler_deactivate().
|
|
*/
|
|
int rc;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
rc = 0;
|
|
mutex_lock(&hws_sem);
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
if (hws_state == HWS_STARTED) {
|
|
rc = smp_ctl_qsi(cpu);
|
|
WARN_ON(rc);
|
|
if (!cb->qsi.cs) {
|
|
hws_flush_all = 0;
|
|
rc = smp_ctl_ssctl_enable_activate(cpu, interval);
|
|
if (rc) {
|
|
printk(KERN_ERR
|
|
"CPU %d, CPUMF activate sampling failed.\n",
|
|
cpu);
|
|
}
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&hws_sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int check_qsi_on_setup(void)
|
|
{
|
|
int rc;
|
|
unsigned int cpu;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
rc = smp_ctl_qsi(cpu);
|
|
WARN_ON(rc);
|
|
if (rc)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (!cb->qsi.as) {
|
|
printk(KERN_INFO "hwsampler: CPUMF sampling is not authorized.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cb->qsi.es) {
|
|
printk(KERN_WARNING "hwsampler: CPUMF is still enabled.\n");
|
|
rc = smp_ctl_ssctl_stop(cpu);
|
|
if (rc)
|
|
return -EINVAL;
|
|
|
|
printk(KERN_INFO
|
|
"CPU %d, CPUMF Sampling stopped now.\n", cpu);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_qsi_on_start(void)
|
|
{
|
|
unsigned int cpu;
|
|
int rc;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
rc = smp_ctl_qsi(cpu);
|
|
WARN_ON(rc);
|
|
|
|
if (!cb->qsi.as)
|
|
return -EINVAL;
|
|
|
|
if (cb->qsi.es)
|
|
return -EINVAL;
|
|
|
|
if (cb->qsi.cs)
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void worker_on_start(unsigned int cpu)
|
|
{
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
cb->worker_entry = cb->first_sdbt;
|
|
}
|
|
|
|
static int worker_check_error(unsigned int cpu, int ext_params)
|
|
{
|
|
int rc;
|
|
unsigned long *sdbt;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
rc = 0;
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
sdbt = (unsigned long *) cb->worker_entry;
|
|
|
|
if (!sdbt || !*sdbt)
|
|
return -EINVAL;
|
|
|
|
if (ext_params & CPU_MF_INT_SF_PRA)
|
|
cb->req_alert++;
|
|
|
|
if (ext_params & CPU_MF_INT_SF_LSDA)
|
|
cb->loss_of_sample_data++;
|
|
|
|
if (ext_params & CPU_MF_INT_SF_IAE) {
|
|
cb->invalid_entry_address++;
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
if (ext_params & CPU_MF_INT_SF_ISE) {
|
|
cb->incorrect_sdbt_entry++;
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
if (ext_params & CPU_MF_INT_SF_SACA) {
|
|
cb->sample_auth_change_alert++;
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void worker_on_finish(unsigned int cpu)
|
|
{
|
|
int rc, i;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
if (cb->finish) {
|
|
rc = smp_ctl_qsi(cpu);
|
|
WARN_ON(rc);
|
|
if (cb->qsi.es) {
|
|
printk(KERN_INFO
|
|
"hwsampler: CPU %d, CPUMF Stop/Deactivate sampling.\n",
|
|
cpu);
|
|
rc = smp_ctl_ssctl_stop(cpu);
|
|
if (rc)
|
|
printk(KERN_INFO
|
|
"hwsampler: CPU %d, CPUMF Deactivation failed.\n",
|
|
cpu);
|
|
|
|
for_each_online_cpu(i) {
|
|
if (i == cpu)
|
|
continue;
|
|
if (!cb->finish) {
|
|
cb->finish = 1;
|
|
queue_work_on(i, hws_wq,
|
|
&cb->worker);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void worker_on_interrupt(unsigned int cpu)
|
|
{
|
|
unsigned long *sdbt;
|
|
unsigned char done;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
sdbt = (unsigned long *) cb->worker_entry;
|
|
|
|
done = 0;
|
|
/* do not proceed if stop was entered,
|
|
* forget the buffers not yet processed */
|
|
while (!done && !cb->stop_mode) {
|
|
unsigned long *trailer;
|
|
struct hws_trailer_entry *te;
|
|
unsigned long *dear = 0;
|
|
|
|
trailer = trailer_entry_ptr(*sdbt);
|
|
/* leave loop if no more work to do */
|
|
if (!(*trailer & BUFFER_FULL_MASK)) {
|
|
done = 1;
|
|
if (!hws_flush_all)
|
|
continue;
|
|
}
|
|
|
|
te = (struct hws_trailer_entry *)trailer;
|
|
cb->sample_overflow += te->overflow;
|
|
|
|
add_samples_to_oprofile(cpu, sdbt, dear);
|
|
|
|
/* reset trailer */
|
|
xchg((unsigned char *) te, 0x40);
|
|
|
|
/* advance to next sdb slot in current sdbt */
|
|
sdbt++;
|
|
/* in case link bit is set use address w/o link bit */
|
|
if (is_link_entry(sdbt))
|
|
sdbt = get_next_sdbt(sdbt);
|
|
|
|
cb->worker_entry = (unsigned long)sdbt;
|
|
}
|
|
}
|
|
|
|
static void add_samples_to_oprofile(unsigned int cpu, unsigned long *sdbt,
|
|
unsigned long *dear)
|
|
{
|
|
struct hws_data_entry *sample_data_ptr;
|
|
unsigned long *trailer;
|
|
|
|
trailer = trailer_entry_ptr(*sdbt);
|
|
if (dear) {
|
|
if (dear > trailer)
|
|
return;
|
|
trailer = dear;
|
|
}
|
|
|
|
sample_data_ptr = (struct hws_data_entry *)(*sdbt);
|
|
|
|
while ((unsigned long *)sample_data_ptr < trailer) {
|
|
struct pt_regs *regs = NULL;
|
|
struct task_struct *tsk = NULL;
|
|
|
|
/*
|
|
* Check sampling mode, 1 indicates basic (=customer) sampling
|
|
* mode.
|
|
*/
|
|
if (sample_data_ptr->def != 1) {
|
|
/* sample slot is not yet written */
|
|
break;
|
|
} else {
|
|
/* make sure we don't use it twice,
|
|
* the next time the sampler will set it again */
|
|
sample_data_ptr->def = 0;
|
|
}
|
|
|
|
/* Get pt_regs. */
|
|
if (sample_data_ptr->P == 1) {
|
|
/* userspace sample */
|
|
unsigned int pid = sample_data_ptr->prim_asn;
|
|
if (!counter_config.user)
|
|
goto skip_sample;
|
|
rcu_read_lock();
|
|
tsk = pid_task(find_vpid(pid), PIDTYPE_PID);
|
|
if (tsk)
|
|
regs = task_pt_regs(tsk);
|
|
rcu_read_unlock();
|
|
} else {
|
|
/* kernelspace sample */
|
|
if (!counter_config.kernel)
|
|
goto skip_sample;
|
|
regs = task_pt_regs(current);
|
|
}
|
|
|
|
mutex_lock(&hws_sem);
|
|
oprofile_add_ext_hw_sample(sample_data_ptr->ia, regs, 0,
|
|
!sample_data_ptr->P, tsk);
|
|
mutex_unlock(&hws_sem);
|
|
skip_sample:
|
|
sample_data_ptr++;
|
|
}
|
|
}
|
|
|
|
static void worker(struct work_struct *work)
|
|
{
|
|
unsigned int cpu;
|
|
int ext_params;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = container_of(work, struct hws_cpu_buffer, worker);
|
|
cpu = smp_processor_id();
|
|
ext_params = atomic_xchg(&cb->ext_params, 0);
|
|
|
|
if (!cb->worker_entry)
|
|
worker_on_start(cpu);
|
|
|
|
if (worker_check_error(cpu, ext_params))
|
|
return;
|
|
|
|
if (!cb->finish)
|
|
worker_on_interrupt(cpu);
|
|
|
|
if (cb->finish)
|
|
worker_on_finish(cpu);
|
|
}
|
|
|
|
/**
|
|
* hwsampler_allocate() - allocate memory for the hardware sampler
|
|
* @sdbt: number of SDBTs per online CPU (must be > 0)
|
|
* @sdb: number of SDBs per SDBT (minimum 1, maximum 511)
|
|
*
|
|
* Returns 0 on success, !0 on failure.
|
|
*/
|
|
int hwsampler_allocate(unsigned long sdbt, unsigned long sdb)
|
|
{
|
|
int cpu, rc;
|
|
mutex_lock(&hws_sem);
|
|
|
|
rc = -EINVAL;
|
|
if (hws_state != HWS_DEALLOCATED)
|
|
goto allocate_exit;
|
|
|
|
if (sdbt < 1)
|
|
goto allocate_exit;
|
|
|
|
if (sdb > MAX_NUM_SDB || sdb < MIN_NUM_SDB)
|
|
goto allocate_exit;
|
|
|
|
num_sdbt = sdbt;
|
|
num_sdb = sdb;
|
|
|
|
oom_killer_was_active = 0;
|
|
register_oom_notifier(&hws_oom_notifier);
|
|
|
|
for_each_online_cpu(cpu) {
|
|
if (allocate_sdbt(cpu)) {
|
|
unregister_oom_notifier(&hws_oom_notifier);
|
|
goto allocate_error;
|
|
}
|
|
}
|
|
unregister_oom_notifier(&hws_oom_notifier);
|
|
if (oom_killer_was_active)
|
|
goto allocate_error;
|
|
|
|
hws_state = HWS_STOPPED;
|
|
rc = 0;
|
|
|
|
allocate_exit:
|
|
mutex_unlock(&hws_sem);
|
|
return rc;
|
|
|
|
allocate_error:
|
|
rc = -ENOMEM;
|
|
printk(KERN_ERR "hwsampler: CPUMF Memory allocation failed.\n");
|
|
goto allocate_exit;
|
|
}
|
|
|
|
/**
|
|
* hwsampler_deallocate() - deallocate hardware sampler memory
|
|
*
|
|
* Returns 0 on success, !0 on failure.
|
|
*/
|
|
int hwsampler_deallocate(void)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&hws_sem);
|
|
|
|
rc = -EINVAL;
|
|
if (hws_state != HWS_STOPPED)
|
|
goto deallocate_exit;
|
|
|
|
measurement_alert_subclass_unregister();
|
|
deallocate_sdbt();
|
|
|
|
hws_state = HWS_DEALLOCATED;
|
|
rc = 0;
|
|
|
|
deallocate_exit:
|
|
mutex_unlock(&hws_sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
unsigned long hwsampler_query_min_interval(void)
|
|
{
|
|
return min_sampler_rate;
|
|
}
|
|
|
|
unsigned long hwsampler_query_max_interval(void)
|
|
{
|
|
return max_sampler_rate;
|
|
}
|
|
|
|
unsigned long hwsampler_get_sample_overflow_count(unsigned int cpu)
|
|
{
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
|
|
return cb->sample_overflow;
|
|
}
|
|
|
|
int hwsampler_setup(void)
|
|
{
|
|
int rc;
|
|
int cpu;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
mutex_lock(&hws_sem);
|
|
|
|
rc = -EINVAL;
|
|
if (hws_state)
|
|
goto setup_exit;
|
|
|
|
hws_state = HWS_INIT;
|
|
|
|
init_all_cpu_buffers();
|
|
|
|
rc = check_hardware_prerequisites();
|
|
if (rc)
|
|
goto setup_exit;
|
|
|
|
rc = check_qsi_on_setup();
|
|
if (rc)
|
|
goto setup_exit;
|
|
|
|
rc = -EINVAL;
|
|
hws_wq = create_workqueue("hwsampler");
|
|
if (!hws_wq)
|
|
goto setup_exit;
|
|
|
|
register_cpu_notifier(&hws_cpu_notifier);
|
|
|
|
for_each_online_cpu(cpu) {
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
INIT_WORK(&cb->worker, worker);
|
|
rc = smp_ctl_qsi(cpu);
|
|
WARN_ON(rc);
|
|
if (min_sampler_rate != cb->qsi.min_sampl_rate) {
|
|
if (min_sampler_rate) {
|
|
printk(KERN_WARNING
|
|
"hwsampler: different min sampler rate values.\n");
|
|
if (min_sampler_rate < cb->qsi.min_sampl_rate)
|
|
min_sampler_rate =
|
|
cb->qsi.min_sampl_rate;
|
|
} else
|
|
min_sampler_rate = cb->qsi.min_sampl_rate;
|
|
}
|
|
if (max_sampler_rate != cb->qsi.max_sampl_rate) {
|
|
if (max_sampler_rate) {
|
|
printk(KERN_WARNING
|
|
"hwsampler: different max sampler rate values.\n");
|
|
if (max_sampler_rate > cb->qsi.max_sampl_rate)
|
|
max_sampler_rate =
|
|
cb->qsi.max_sampl_rate;
|
|
} else
|
|
max_sampler_rate = cb->qsi.max_sampl_rate;
|
|
}
|
|
}
|
|
register_external_interrupt(0x1407, hws_ext_handler);
|
|
|
|
hws_state = HWS_DEALLOCATED;
|
|
rc = 0;
|
|
|
|
setup_exit:
|
|
mutex_unlock(&hws_sem);
|
|
return rc;
|
|
}
|
|
|
|
int hwsampler_shutdown(void)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&hws_sem);
|
|
|
|
rc = -EINVAL;
|
|
if (hws_state == HWS_DEALLOCATED || hws_state == HWS_STOPPED) {
|
|
mutex_unlock(&hws_sem);
|
|
|
|
if (hws_wq)
|
|
flush_workqueue(hws_wq);
|
|
|
|
mutex_lock(&hws_sem);
|
|
|
|
if (hws_state == HWS_STOPPED) {
|
|
measurement_alert_subclass_unregister();
|
|
deallocate_sdbt();
|
|
}
|
|
if (hws_wq) {
|
|
destroy_workqueue(hws_wq);
|
|
hws_wq = NULL;
|
|
}
|
|
|
|
unregister_external_interrupt(0x1407, hws_ext_handler);
|
|
hws_state = HWS_INIT;
|
|
rc = 0;
|
|
}
|
|
mutex_unlock(&hws_sem);
|
|
|
|
unregister_cpu_notifier(&hws_cpu_notifier);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* hwsampler_start_all() - start hardware sampling on all online CPUs
|
|
* @rate: specifies the used interval when samples are taken
|
|
*
|
|
* Returns 0 on success, !0 on failure.
|
|
*/
|
|
int hwsampler_start_all(unsigned long rate)
|
|
{
|
|
int rc, cpu;
|
|
|
|
mutex_lock(&hws_sem);
|
|
|
|
hws_oom = 0;
|
|
|
|
rc = -EINVAL;
|
|
if (hws_state != HWS_STOPPED)
|
|
goto start_all_exit;
|
|
|
|
interval = rate;
|
|
|
|
/* fail if rate is not valid */
|
|
if (interval < min_sampler_rate || interval > max_sampler_rate)
|
|
goto start_all_exit;
|
|
|
|
rc = check_qsi_on_start();
|
|
if (rc)
|
|
goto start_all_exit;
|
|
|
|
rc = prepare_cpu_buffers();
|
|
if (rc)
|
|
goto start_all_exit;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
rc = start_sampling(cpu);
|
|
if (rc)
|
|
break;
|
|
}
|
|
if (rc) {
|
|
for_each_online_cpu(cpu) {
|
|
stop_sampling(cpu);
|
|
}
|
|
goto start_all_exit;
|
|
}
|
|
hws_state = HWS_STARTED;
|
|
rc = 0;
|
|
|
|
start_all_exit:
|
|
mutex_unlock(&hws_sem);
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
register_oom_notifier(&hws_oom_notifier);
|
|
hws_oom = 1;
|
|
hws_flush_all = 0;
|
|
/* now let them in, 1407 CPUMF external interrupts */
|
|
measurement_alert_subclass_register();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hwsampler_stop_all() - stop hardware sampling on all online CPUs
|
|
*
|
|
* Returns 0 on success, !0 on failure.
|
|
*/
|
|
int hwsampler_stop_all(void)
|
|
{
|
|
int tmp_rc, rc, cpu;
|
|
struct hws_cpu_buffer *cb;
|
|
|
|
mutex_lock(&hws_sem);
|
|
|
|
rc = 0;
|
|
if (hws_state == HWS_INIT) {
|
|
mutex_unlock(&hws_sem);
|
|
return rc;
|
|
}
|
|
hws_state = HWS_STOPPING;
|
|
mutex_unlock(&hws_sem);
|
|
|
|
for_each_online_cpu(cpu) {
|
|
cb = &per_cpu(sampler_cpu_buffer, cpu);
|
|
cb->stop_mode = 1;
|
|
tmp_rc = stop_sampling(cpu);
|
|
if (tmp_rc)
|
|
rc = tmp_rc;
|
|
}
|
|
|
|
if (hws_wq)
|
|
flush_workqueue(hws_wq);
|
|
|
|
mutex_lock(&hws_sem);
|
|
if (hws_oom) {
|
|
unregister_oom_notifier(&hws_oom_notifier);
|
|
hws_oom = 0;
|
|
}
|
|
hws_state = HWS_STOPPED;
|
|
mutex_unlock(&hws_sem);
|
|
|
|
return rc;
|
|
}
|