| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * User Events Perf Events Test Program | |
| * | |
| * Copyright (c) 2021 Beau Belgrave <beaub@linux.microsoft.com> | |
| */ | |
| #include <errno.h> | |
| #include <linux/user_events.h> | |
| #include <linux/perf_event.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <fcntl.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #include <asm/unistd.h> | |
| #include "../kselftest_harness.h" | |
| const char *data_file = "/sys/kernel/debug/tracing/user_events_data"; | |
| const char *status_file = "/sys/kernel/debug/tracing/user_events_status"; | |
| const char *id_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/id"; | |
| const char *fmt_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/format"; | |
| struct event { | |
| __u32 index; | |
| __u32 field1; | |
| __u32 field2; | |
| }; | |
| static long perf_event_open(struct perf_event_attr *pe, pid_t pid, | |
| int cpu, int group_fd, unsigned long flags) | |
| { | |
| return syscall(__NR_perf_event_open, pe, pid, cpu, group_fd, flags); | |
| } | |
| static inline int status_check(char *status_page, int status_bit) | |
| { | |
| return status_page[status_bit >> 3] & (1 << (status_bit & 7)); | |
| } | |
| static int get_id(void) | |
| { | |
| FILE *fp = fopen(id_file, "r"); | |
| int ret, id = 0; | |
| if (!fp) | |
| return -1; | |
| ret = fscanf(fp, "%d", &id); | |
| fclose(fp); | |
| if (ret != 1) | |
| return -1; | |
| return id; | |
| } | |
| static int get_offset(void) | |
| { | |
| FILE *fp = fopen(fmt_file, "r"); | |
| int ret, c, last = 0, offset = 0; | |
| if (!fp) | |
| return -1; | |
| /* Read until empty line */ | |
| while (true) { | |
| c = getc(fp); | |
| if (c == EOF) | |
| break; | |
| if (last == '\n' && c == '\n') | |
| break; | |
| last = c; | |
| } | |
| ret = fscanf(fp, "\tfield:u32 field1;\toffset:%d;", &offset); | |
| fclose(fp); | |
| if (ret != 1) | |
| return -1; | |
| return offset; | |
| } | |
| FIXTURE(user) { | |
| int status_fd; | |
| int data_fd; | |
| }; | |
| FIXTURE_SETUP(user) { | |
| self->status_fd = open(status_file, O_RDONLY); | |
| ASSERT_NE(-1, self->status_fd); | |
| self->data_fd = open(data_file, O_RDWR); | |
| ASSERT_NE(-1, self->data_fd); | |
| } | |
| FIXTURE_TEARDOWN(user) { | |
| close(self->status_fd); | |
| close(self->data_fd); | |
| } | |
| TEST_F(user, perf_write) { | |
| struct perf_event_attr pe = {0}; | |
| struct user_reg reg = {0}; | |
| int page_size = sysconf(_SC_PAGESIZE); | |
| char *status_page; | |
| struct event event; | |
| struct perf_event_mmap_page *perf_page; | |
| int id, fd, offset; | |
| __u32 *val; | |
| reg.size = sizeof(reg); | |
| reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; | |
| status_page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, | |
| self->status_fd, 0); | |
| ASSERT_NE(MAP_FAILED, status_page); | |
| /* Register should work */ | |
| ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); | |
| ASSERT_EQ(0, reg.write_index); | |
| ASSERT_NE(0, reg.status_bit); | |
| ASSERT_EQ(0, status_check(status_page, reg.status_bit)); | |
| /* Id should be there */ | |
| id = get_id(); | |
| ASSERT_NE(-1, id); | |
| offset = get_offset(); | |
| ASSERT_NE(-1, offset); | |
| pe.type = PERF_TYPE_TRACEPOINT; | |
| pe.size = sizeof(pe); | |
| pe.config = id; | |
| pe.sample_type = PERF_SAMPLE_RAW; | |
| pe.sample_period = 1; | |
| pe.wakeup_events = 1; | |
| /* Tracepoint attach should work */ | |
| fd = perf_event_open(&pe, 0, -1, -1, 0); | |
| ASSERT_NE(-1, fd); | |
| perf_page = mmap(NULL, page_size * 2, PROT_READ, MAP_SHARED, fd, 0); | |
| ASSERT_NE(MAP_FAILED, perf_page); | |
| /* Status should be updated */ | |
| ASSERT_NE(0, status_check(status_page, reg.status_bit)); | |
| event.index = reg.write_index; | |
| event.field1 = 0xc001; | |
| event.field2 = 0xc01a; | |
| /* Ensure write shows up at correct offset */ | |
| ASSERT_NE(-1, write(self->data_fd, &event, sizeof(event))); | |
| val = (void *)(((char *)perf_page) + perf_page->data_offset); | |
| ASSERT_EQ(PERF_RECORD_SAMPLE, *val); | |
| /* Skip over header and size, move to offset */ | |
| val += 3; | |
| val = (void *)((char *)val) + offset; | |
| /* Ensure correct */ | |
| ASSERT_EQ(event.field1, *val++); | |
| ASSERT_EQ(event.field2, *val++); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| return test_harness_run(argc, argv); | |
| } |