| // 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/tracing/user_events_data"; |
| const char *id_file = "/sys/kernel/tracing/events/user_events/__test_event/id"; |
| const char *fmt_file = "/sys/kernel/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 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; |
| } |
| |
| static int clear(int *check) |
| { |
| struct user_unreg unreg = {0}; |
| |
| unreg.size = sizeof(unreg); |
| unreg.disable_bit = 31; |
| unreg.disable_addr = (__u64)check; |
| |
| int fd = open(data_file, O_RDWR); |
| |
| if (fd == -1) |
| return -1; |
| |
| if (ioctl(fd, DIAG_IOCSUNREG, &unreg) == -1) |
| if (errno != ENOENT) |
| return -1; |
| |
| if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) |
| if (errno != ENOENT) |
| return -1; |
| |
| close(fd); |
| |
| return 0; |
| } |
| |
| FIXTURE(user) { |
| int data_fd; |
| int check; |
| }; |
| |
| FIXTURE_SETUP(user) { |
| self->data_fd = open(data_file, O_RDWR); |
| ASSERT_NE(-1, self->data_fd); |
| } |
| |
| FIXTURE_TEARDOWN(user) { |
| close(self->data_fd); |
| |
| if (clear(&self->check) != 0) |
| printf("WARNING: Clear didn't work!\n"); |
| } |
| |
| TEST_F(user, perf_write) { |
| struct perf_event_attr pe = {0}; |
| struct user_reg reg = {0}; |
| struct event event; |
| struct perf_event_mmap_page *perf_page; |
| int page_size = sysconf(_SC_PAGESIZE); |
| int id, fd, offset; |
| __u32 *val; |
| |
| reg.size = sizeof(reg); |
| reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; |
| reg.enable_bit = 31; |
| reg.enable_addr = (__u64)&self->check; |
| reg.enable_size = sizeof(self->check); |
| |
| /* Register should work */ |
| ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); |
| ASSERT_EQ(0, reg.write_index); |
| ASSERT_EQ(0, self->check); |
| |
| /* 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_EQ(1 << reg.enable_bit, self->check); |
| |
| 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++); |
| |
| munmap(perf_page, page_size * 2); |
| close(fd); |
| |
| /* Status should be updated */ |
| ASSERT_EQ(0, self->check); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| return test_harness_run(argc, argv); |
| } |