1 /* 2 * Performance event support for s390x 3 * 4 * Copyright IBM Corp. 2012 5 * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License (version 2 only) 9 * as published by the Free Software Foundation. 10 */ 11 #define KMSG_COMPONENT "perf" 12 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt 13 14 #include <linux/kernel.h> 15 #include <linux/perf_event.h> 16 #include <linux/kvm_host.h> 17 #include <linux/percpu.h> 18 #include <linux/export.h> 19 #include <asm/irq.h> 20 #include <asm/cpu_mf.h> 21 #include <asm/lowcore.h> 22 #include <asm/processor.h> 23 24 const char *perf_pmu_name(void) 25 { 26 if (cpum_cf_avail() || cpum_sf_avail()) 27 return "CPU-measurement facilities (CPUMF)"; 28 return "pmu"; 29 } 30 EXPORT_SYMBOL(perf_pmu_name); 31 32 int perf_num_counters(void) 33 { 34 int num = 0; 35 36 if (cpum_cf_avail()) 37 num += PERF_CPUM_CF_MAX_CTR; 38 39 return num; 40 } 41 EXPORT_SYMBOL(perf_num_counters); 42 43 static struct kvm_s390_sie_block *sie_block(struct pt_regs *regs) 44 { 45 struct stack_frame *stack = (struct stack_frame *) regs->gprs[15]; 46 47 if (!stack) 48 return NULL; 49 50 return (struct kvm_s390_sie_block *) stack->empty1[0]; 51 } 52 53 static bool is_in_guest(struct pt_regs *regs) 54 { 55 unsigned long ip = instruction_pointer(regs); 56 57 if (user_mode(regs)) 58 return false; 59 60 return ip == (unsigned long) &sie_exit; 61 } 62 63 static unsigned long guest_is_user_mode(struct pt_regs *regs) 64 { 65 return sie_block(regs)->gpsw.mask & PSW_MASK_PSTATE; 66 } 67 68 static unsigned long instruction_pointer_guest(struct pt_regs *regs) 69 { 70 return sie_block(regs)->gpsw.addr & PSW_ADDR_INSN; 71 } 72 73 unsigned long perf_instruction_pointer(struct pt_regs *regs) 74 { 75 return is_in_guest(regs) ? instruction_pointer_guest(regs) 76 : instruction_pointer(regs); 77 } 78 79 static unsigned long perf_misc_guest_flags(struct pt_regs *regs) 80 { 81 return guest_is_user_mode(regs) ? PERF_RECORD_MISC_GUEST_USER 82 : PERF_RECORD_MISC_GUEST_KERNEL; 83 } 84 85 unsigned long perf_misc_flags(struct pt_regs *regs) 86 { 87 if (is_in_guest(regs)) 88 return perf_misc_guest_flags(regs); 89 90 return user_mode(regs) ? PERF_RECORD_MISC_USER 91 : PERF_RECORD_MISC_KERNEL; 92 } 93 94 void perf_event_print_debug(void) 95 { 96 struct cpumf_ctr_info cf_info; 97 unsigned long flags; 98 int cpu; 99 100 if (!cpum_cf_avail()) 101 return; 102 103 local_irq_save(flags); 104 105 cpu = smp_processor_id(); 106 memset(&cf_info, 0, sizeof(cf_info)); 107 if (!qctri(&cf_info)) { 108 pr_info("CPU[%i] CPUM_CF: ver=%u.%u A=%04x E=%04x C=%04x\n", 109 cpu, cf_info.cfvn, cf_info.csvn, 110 cf_info.auth_ctl, cf_info.enable_ctl, cf_info.act_ctl); 111 print_hex_dump_bytes("CPUMF Query: ", DUMP_PREFIX_OFFSET, 112 &cf_info, sizeof(cf_info)); 113 } 114 115 local_irq_restore(flags); 116 } 117 118 /* See also arch/s390/kernel/traps.c */ 119 static unsigned long __store_trace(struct perf_callchain_entry *entry, 120 unsigned long sp, 121 unsigned long low, unsigned long high) 122 { 123 struct stack_frame *sf; 124 struct pt_regs *regs; 125 126 while (1) { 127 sp = sp & PSW_ADDR_INSN; 128 if (sp < low || sp > high - sizeof(*sf)) 129 return sp; 130 sf = (struct stack_frame *) sp; 131 perf_callchain_store(entry, sf->gprs[8] & PSW_ADDR_INSN); 132 /* Follow the backchain. */ 133 while (1) { 134 low = sp; 135 sp = sf->back_chain & PSW_ADDR_INSN; 136 if (!sp) 137 break; 138 if (sp <= low || sp > high - sizeof(*sf)) 139 return sp; 140 sf = (struct stack_frame *) sp; 141 perf_callchain_store(entry, 142 sf->gprs[8] & PSW_ADDR_INSN); 143 } 144 /* Zero backchain detected, check for interrupt frame. */ 145 sp = (unsigned long) (sf + 1); 146 if (sp <= low || sp > high - sizeof(*regs)) 147 return sp; 148 regs = (struct pt_regs *) sp; 149 perf_callchain_store(entry, sf->gprs[8] & PSW_ADDR_INSN); 150 low = sp; 151 sp = regs->gprs[15]; 152 } 153 } 154 155 void perf_callchain_kernel(struct perf_callchain_entry *entry, 156 struct pt_regs *regs) 157 { 158 unsigned long head; 159 struct stack_frame *head_sf; 160 161 if (user_mode(regs)) 162 return; 163 164 head = regs->gprs[15]; 165 head_sf = (struct stack_frame *) head; 166 167 if (!head_sf || !head_sf->back_chain) 168 return; 169 170 head = head_sf->back_chain; 171 head = __store_trace(entry, head, S390_lowcore.async_stack - ASYNC_SIZE, 172 S390_lowcore.async_stack); 173 174 __store_trace(entry, head, S390_lowcore.thread_info, 175 S390_lowcore.thread_info + THREAD_SIZE); 176 } 177