| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright 2022, Athira Rajeev, IBM Corp. |
| * Copyright 2022, Madhavan Srinivasan, IBM Corp. |
| * Copyright 2022, Kajol Jain, IBM Corp. |
| */ |
| |
| #include <unistd.h> |
| #include <sys/syscall.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| |
| #include "misc.h" |
| |
| #define PAGE_SIZE sysconf(_SC_PAGESIZE) |
| |
| /* Storage for platform version */ |
| int pvr; |
| u64 platform_extended_mask; |
| |
| /* Mask and Shift for Event code fields */ |
| int ev_mask_pmcxsel, ev_shift_pmcxsel; //pmcxsel field |
| int ev_mask_marked, ev_shift_marked; //marked filed |
| int ev_mask_comb, ev_shift_comb; //combine field |
| int ev_mask_unit, ev_shift_unit; //unit field |
| int ev_mask_pmc, ev_shift_pmc; //pmc field |
| int ev_mask_cache, ev_shift_cache; //Cache sel field |
| int ev_mask_sample, ev_shift_sample; //Random sampling field |
| int ev_mask_thd_sel, ev_shift_thd_sel; //thresh_sel field |
| int ev_mask_thd_start, ev_shift_thd_start; //thresh_start field |
| int ev_mask_thd_stop, ev_shift_thd_stop; //thresh_stop field |
| int ev_mask_thd_cmp, ev_shift_thd_cmp; //thresh cmp field |
| int ev_mask_sm, ev_shift_sm; //SDAR mode field |
| int ev_mask_rsq, ev_shift_rsq; //radix scope qual field |
| int ev_mask_l2l3, ev_shift_l2l3; //l2l3 sel field |
| int ev_mask_mmcr3_src, ev_shift_mmcr3_src; //mmcr3 field |
| |
| static void init_ev_encodes(void) |
| { |
| ev_mask_pmcxsel = 0xff; |
| ev_shift_pmcxsel = 0; |
| ev_mask_marked = 1; |
| ev_shift_marked = 8; |
| ev_mask_unit = 0xf; |
| ev_shift_unit = 12; |
| ev_mask_pmc = 0xf; |
| ev_shift_pmc = 16; |
| ev_mask_sample = 0x1f; |
| ev_shift_sample = 24; |
| ev_mask_thd_sel = 0x7; |
| ev_shift_thd_sel = 29; |
| ev_mask_thd_start = 0xf; |
| ev_shift_thd_start = 36; |
| ev_mask_thd_stop = 0xf; |
| ev_shift_thd_stop = 32; |
| |
| switch (pvr) { |
| case POWER10: |
| ev_mask_thd_cmp = 0x3ffff; |
| ev_shift_thd_cmp = 0; |
| ev_mask_rsq = 1; |
| ev_shift_rsq = 9; |
| ev_mask_comb = 3; |
| ev_shift_comb = 10; |
| ev_mask_cache = 3; |
| ev_shift_cache = 20; |
| ev_mask_sm = 0x3; |
| ev_shift_sm = 22; |
| ev_mask_l2l3 = 0x1f; |
| ev_shift_l2l3 = 40; |
| ev_mask_mmcr3_src = 0x7fff; |
| ev_shift_mmcr3_src = 45; |
| break; |
| case POWER9: |
| ev_mask_comb = 3; |
| ev_shift_comb = 10; |
| ev_mask_cache = 0xf; |
| ev_shift_cache = 20; |
| ev_mask_thd_cmp = 0x3ff; |
| ev_shift_thd_cmp = 40; |
| ev_mask_sm = 0x3; |
| ev_shift_sm = 50; |
| break; |
| default: |
| FAIL_IF_EXIT(1); |
| } |
| } |
| |
| /* Return the extended regs mask value */ |
| static u64 perf_get_platform_reg_mask(void) |
| { |
| if (have_hwcap2(PPC_FEATURE2_ARCH_3_1)) |
| return PERF_POWER10_MASK; |
| if (have_hwcap2(PPC_FEATURE2_ARCH_3_00)) |
| return PERF_POWER9_MASK; |
| |
| return -1; |
| } |
| |
| int check_extended_regs_support(void) |
| { |
| int fd; |
| struct event event; |
| |
| event_init(&event, 0x1001e); |
| |
| event.attr.type = 4; |
| event.attr.sample_period = 1; |
| event.attr.disabled = 1; |
| event.attr.sample_type = PERF_SAMPLE_REGS_INTR; |
| event.attr.sample_regs_intr = platform_extended_mask; |
| |
| fd = event_open(&event); |
| if (fd != -1) |
| return 0; |
| |
| return -1; |
| } |
| |
| int platform_check_for_tests(void) |
| { |
| pvr = PVR_VER(mfspr(SPRN_PVR)); |
| |
| /* |
| * Check for supported platforms |
| * for sampling test |
| */ |
| if ((pvr != POWER10) && (pvr != POWER9)) |
| goto out; |
| |
| /* |
| * Check PMU driver registered by looking for |
| * PPC_FEATURE2_EBB bit in AT_HWCAP2 |
| */ |
| if (!have_hwcap2(PPC_FEATURE2_EBB) || !have_hwcap2(PPC_FEATURE2_ARCH_3_00)) |
| goto out; |
| |
| return 0; |
| |
| out: |
| printf("%s: Tests unsupported for this platform\n", __func__); |
| return -1; |
| } |
| |
| int check_pvr_for_sampling_tests(void) |
| { |
| SKIP_IF(platform_check_for_tests()); |
| |
| platform_extended_mask = perf_get_platform_reg_mask(); |
| /* check if platform supports extended regs */ |
| if (check_extended_regs_support()) |
| goto out; |
| |
| init_ev_encodes(); |
| return 0; |
| |
| out: |
| printf("%s: Sampling tests un-supported\n", __func__); |
| return -1; |
| } |
| |
| /* |
| * Allocate mmap buffer of "mmap_pages" number of |
| * pages. |
| */ |
| void *event_sample_buf_mmap(int fd, int mmap_pages) |
| { |
| size_t page_size = sysconf(_SC_PAGESIZE); |
| size_t mmap_size; |
| void *buff; |
| |
| if (mmap_pages <= 0) |
| return NULL; |
| |
| if (fd <= 0) |
| return NULL; |
| |
| mmap_size = page_size * (1 + mmap_pages); |
| buff = mmap(NULL, mmap_size, |
| PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
| |
| if (buff == MAP_FAILED) { |
| perror("mmap() failed."); |
| return NULL; |
| } |
| return buff; |
| } |
| |
| /* |
| * Post process the mmap buffer. |
| * - If sample_count != NULL then return count of total |
| * number of samples present in the mmap buffer. |
| * - If sample_count == NULL then return the address |
| * of first sample from the mmap buffer |
| */ |
| void *__event_read_samples(void *sample_buff, size_t *size, u64 *sample_count) |
| { |
| size_t page_size = sysconf(_SC_PAGESIZE); |
| struct perf_event_header *header = sample_buff + page_size; |
| struct perf_event_mmap_page *metadata_page = sample_buff; |
| unsigned long data_head, data_tail; |
| |
| /* |
| * PERF_RECORD_SAMPLE: |
| * struct { |
| * struct perf_event_header hdr; |
| * u64 data[]; |
| * }; |
| */ |
| |
| data_head = metadata_page->data_head; |
| /* sync memory before reading sample */ |
| mb(); |
| data_tail = metadata_page->data_tail; |
| |
| /* Check for sample_count */ |
| if (sample_count) |
| *sample_count = 0; |
| |
| while (1) { |
| /* |
| * Reads the mmap data buffer by moving |
| * the data_tail to know the last read data. |
| * data_head points to head in data buffer. |
| * refer "struct perf_event_mmap_page" in |
| * "include/uapi/linux/perf_event.h". |
| */ |
| if (data_head - data_tail < sizeof(header)) |
| return NULL; |
| |
| data_tail += sizeof(header); |
| if (header->type == PERF_RECORD_SAMPLE) { |
| *size = (header->size - sizeof(header)); |
| if (!sample_count) |
| return sample_buff + page_size + data_tail; |
| data_tail += *size; |
| *sample_count += 1; |
| } else { |
| *size = (header->size - sizeof(header)); |
| if ((metadata_page->data_tail + *size) > metadata_page->data_head) |
| data_tail = metadata_page->data_head; |
| else |
| data_tail += *size; |
| } |
| header = (struct perf_event_header *)((void *)header + header->size); |
| } |
| return NULL; |
| } |
| |
| int collect_samples(void *sample_buff) |
| { |
| u64 sample_count; |
| size_t size = 0; |
| |
| __event_read_samples(sample_buff, &size, &sample_count); |
| return sample_count; |
| } |
| |
| static void *perf_read_first_sample(void *sample_buff, size_t *size) |
| { |
| return __event_read_samples(sample_buff, size, NULL); |
| } |
| |
| u64 *get_intr_regs(struct event *event, void *sample_buff) |
| { |
| u64 type = event->attr.sample_type; |
| u64 *intr_regs; |
| size_t size = 0; |
| |
| if ((type ^ (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_BRANCH_STACK)) && |
| (type ^ PERF_SAMPLE_REGS_INTR)) |
| return NULL; |
| |
| intr_regs = (u64 *)perf_read_first_sample(sample_buff, &size); |
| if (!intr_regs) |
| return NULL; |
| |
| if (type & PERF_SAMPLE_BRANCH_STACK) { |
| /* |
| * PERF_RECORD_SAMPLE and PERF_SAMPLE_BRANCH_STACK: |
| * struct { |
| * struct perf_event_header hdr; |
| * u64 number_of_branches; |
| * struct perf_branch_entry[number_of_branches]; |
| * u64 data[]; |
| * }; |
| * struct perf_branch_entry { |
| * u64 from; |
| * u64 to; |
| * u64 misc; |
| * }; |
| */ |
| intr_regs += ((*intr_regs) * 3) + 1; |
| } |
| |
| /* |
| * First entry in the sample buffer used to specify |
| * PERF_SAMPLE_REGS_ABI_64, skip perf regs abi to access |
| * interrupt registers. |
| */ |
| ++intr_regs; |
| |
| return intr_regs; |
| } |
| |
| static const int __perf_reg_mask(const char *register_name) |
| { |
| if (!strcmp(register_name, "R0")) |
| return 0; |
| else if (!strcmp(register_name, "R1")) |
| return 1; |
| else if (!strcmp(register_name, "R2")) |
| return 2; |
| else if (!strcmp(register_name, "R3")) |
| return 3; |
| else if (!strcmp(register_name, "R4")) |
| return 4; |
| else if (!strcmp(register_name, "R5")) |
| return 5; |
| else if (!strcmp(register_name, "R6")) |
| return 6; |
| else if (!strcmp(register_name, "R7")) |
| return 7; |
| else if (!strcmp(register_name, "R8")) |
| return 8; |
| else if (!strcmp(register_name, "R9")) |
| return 9; |
| else if (!strcmp(register_name, "R10")) |
| return 10; |
| else if (!strcmp(register_name, "R11")) |
| return 11; |
| else if (!strcmp(register_name, "R12")) |
| return 12; |
| else if (!strcmp(register_name, "R13")) |
| return 13; |
| else if (!strcmp(register_name, "R14")) |
| return 14; |
| else if (!strcmp(register_name, "R15")) |
| return 15; |
| else if (!strcmp(register_name, "R16")) |
| return 16; |
| else if (!strcmp(register_name, "R17")) |
| return 17; |
| else if (!strcmp(register_name, "R18")) |
| return 18; |
| else if (!strcmp(register_name, "R19")) |
| return 19; |
| else if (!strcmp(register_name, "R20")) |
| return 20; |
| else if (!strcmp(register_name, "R21")) |
| return 21; |
| else if (!strcmp(register_name, "R22")) |
| return 22; |
| else if (!strcmp(register_name, "R23")) |
| return 23; |
| else if (!strcmp(register_name, "R24")) |
| return 24; |
| else if (!strcmp(register_name, "R25")) |
| return 25; |
| else if (!strcmp(register_name, "R26")) |
| return 26; |
| else if (!strcmp(register_name, "R27")) |
| return 27; |
| else if (!strcmp(register_name, "R28")) |
| return 28; |
| else if (!strcmp(register_name, "R29")) |
| return 29; |
| else if (!strcmp(register_name, "R30")) |
| return 30; |
| else if (!strcmp(register_name, "R31")) |
| return 31; |
| else if (!strcmp(register_name, "NIP")) |
| return 32; |
| else if (!strcmp(register_name, "MSR")) |
| return 33; |
| else if (!strcmp(register_name, "ORIG_R3")) |
| return 34; |
| else if (!strcmp(register_name, "CTR")) |
| return 35; |
| else if (!strcmp(register_name, "LINK")) |
| return 36; |
| else if (!strcmp(register_name, "XER")) |
| return 37; |
| else if (!strcmp(register_name, "CCR")) |
| return 38; |
| else if (!strcmp(register_name, "SOFTE")) |
| return 39; |
| else if (!strcmp(register_name, "TRAP")) |
| return 40; |
| else if (!strcmp(register_name, "DAR")) |
| return 41; |
| else if (!strcmp(register_name, "DSISR")) |
| return 42; |
| else if (!strcmp(register_name, "SIER")) |
| return 43; |
| else if (!strcmp(register_name, "MMCRA")) |
| return 44; |
| else if (!strcmp(register_name, "MMCR0")) |
| return 45; |
| else if (!strcmp(register_name, "MMCR1")) |
| return 46; |
| else if (!strcmp(register_name, "MMCR2")) |
| return 47; |
| else if (!strcmp(register_name, "MMCR3")) |
| return 48; |
| else if (!strcmp(register_name, "SIER2")) |
| return 49; |
| else if (!strcmp(register_name, "SIER3")) |
| return 50; |
| else if (!strcmp(register_name, "PMC1")) |
| return 51; |
| else if (!strcmp(register_name, "PMC2")) |
| return 52; |
| else if (!strcmp(register_name, "PMC3")) |
| return 53; |
| else if (!strcmp(register_name, "PMC4")) |
| return 54; |
| else if (!strcmp(register_name, "PMC5")) |
| return 55; |
| else if (!strcmp(register_name, "PMC6")) |
| return 56; |
| else if (!strcmp(register_name, "SDAR")) |
| return 57; |
| else if (!strcmp(register_name, "SIAR")) |
| return 58; |
| else |
| return -1; |
| } |
| |
| u64 get_reg_value(u64 *intr_regs, char *register_name) |
| { |
| int register_bit_position; |
| |
| register_bit_position = __perf_reg_mask(register_name); |
| |
| if (register_bit_position < 0 || (!((platform_extended_mask >> |
| (register_bit_position - 1)) & 1))) |
| return -1; |
| |
| return *(intr_regs + register_bit_position); |
| } |
| |
| int get_thresh_cmp_val(struct event event) |
| { |
| int exp = 0; |
| u64 result = 0; |
| u64 value; |
| |
| if (!have_hwcap2(PPC_FEATURE2_ARCH_3_1)) |
| return EV_CODE_EXTRACT(event.attr.config, thd_cmp); |
| |
| value = EV_CODE_EXTRACT(event.attr.config1, thd_cmp); |
| |
| if (!value) |
| return value; |
| |
| /* |
| * Incase of P10, thresh_cmp value is not part of raw event code |
| * and provided via attr.config1 parameter. To program threshold in MMCRA, |
| * take a 18 bit number N and shift right 2 places and increment |
| * the exponent E by 1 until the upper 10 bits of N are zero. |
| * Write E to the threshold exponent and write the lower 8 bits of N |
| * to the threshold mantissa. |
| * The max threshold that can be written is 261120. |
| */ |
| if (value > 261120) |
| value = 261120; |
| while ((64 - __builtin_clzl(value)) > 8) { |
| exp++; |
| value >>= 2; |
| } |
| |
| /* |
| * Note that it is invalid to write a mantissa with the |
| * upper 2 bits of mantissa being zero, unless the |
| * exponent is also zero. |
| */ |
| if (!(value & 0xC0) && exp) |
| result = -1; |
| else |
| result = (exp << 8) | value; |
| return result; |
| } |
| |
| /* |
| * Utility function to check for generic compat PMU |
| * by comparing base_platform value from auxv and real |
| * PVR value. |
| */ |
| static bool auxv_generic_compat_pmu(void) |
| { |
| int base_pvr = 0; |
| |
| if (!strcmp(auxv_base_platform(), "power9")) |
| base_pvr = POWER9; |
| else if (!strcmp(auxv_base_platform(), "power10")) |
| base_pvr = POWER10; |
| |
| return (!base_pvr); |
| } |
| |
| /* |
| * Check for generic compat PMU. |
| * First check for presence of pmu_name from |
| * "/sys/bus/event_source/devices/cpu/caps". |
| * If doesn't exist, fallback to using value |
| * auxv. |
| */ |
| bool check_for_generic_compat_pmu(void) |
| { |
| char pmu_name[256]; |
| |
| memset(pmu_name, 0, sizeof(pmu_name)); |
| if (read_sysfs_file("bus/event_source/devices/cpu/caps/pmu_name", |
| pmu_name, sizeof(pmu_name)) < 0) |
| return auxv_generic_compat_pmu(); |
| |
| if (!strcmp(pmu_name, "ISAv3")) |
| return true; |
| else |
| return false; |
| } |
| |
| /* |
| * Check if system is booted in compat mode. |
| */ |
| bool check_for_compat_mode(void) |
| { |
| char *platform = auxv_platform(); |
| char *base_platform = auxv_base_platform(); |
| |
| return strcmp(platform, base_platform); |
| } |