blob: ea0427aac1f98fe512338ddd795ddc1d99341016 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
#define _GNU_SOURCE
#include <dirent.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <linux/perf_event.h>
#include "../kselftest_harness.h"
#define RB_SIZE 0x3000
#define AUX_SIZE 0x10000
#define AUX_OFFS 0x4000
#define HOLE_SIZE 0x1000
/* Reserve space for rb, aux with space for shrink-beyond-vma testing. */
#define REGION_SIZE (2 * RB_SIZE + 2 * AUX_SIZE)
#define REGION_AUX_OFFS (2 * RB_SIZE)
#define MAP_BASE 1
#define MAP_AUX 2
#define EVENT_SRC_DIR "/sys/bus/event_source/devices"
FIXTURE(perf_mmap)
{
int fd;
void *ptr;
void *region;
};
FIXTURE_VARIANT(perf_mmap)
{
bool aux;
unsigned long ptr_size;
};
FIXTURE_VARIANT_ADD(perf_mmap, rb)
{
.aux = false,
.ptr_size = RB_SIZE,
};
FIXTURE_VARIANT_ADD(perf_mmap, aux)
{
.aux = true,
.ptr_size = AUX_SIZE,
};
static bool read_event_type(struct dirent *dent, __u32 *type)
{
char typefn[512];
FILE *fp;
int res;
snprintf(typefn, sizeof(typefn), "%s/%s/type", EVENT_SRC_DIR, dent->d_name);
fp = fopen(typefn, "r");
if (!fp)
return false;
res = fscanf(fp, "%u", type);
fclose(fp);
return res > 0;
}
FIXTURE_SETUP(perf_mmap)
{
struct perf_event_attr attr = {
.size = sizeof(attr),
.disabled = 1,
.exclude_kernel = 1,
.exclude_hv = 1,
};
struct perf_event_attr attr_ok = {};
unsigned int eacces = 0, map = 0;
struct perf_event_mmap_page *rb;
struct dirent *dent;
void *aux, *region;
DIR *dir;
self->ptr = NULL;
dir = opendir(EVENT_SRC_DIR);
if (!dir)
SKIP(return, "perf not available.");
region = mmap(NULL, REGION_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
ASSERT_NE(region, MAP_FAILED);
self->region = region;
// Try to find a suitable event on this system
while ((dent = readdir(dir))) {
int fd;
if (!read_event_type(dent, &attr.type))
continue;
fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0);
if (fd < 0) {
if (errno == EACCES)
eacces++;
continue;
}
// Check whether the event supports mmap()
rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
if (rb == MAP_FAILED) {
close(fd);
continue;
}
if (!map) {
// Save the event in case that no AUX capable event is found
attr_ok = attr;
map = MAP_BASE;
}
if (!variant->aux)
continue;
rb->aux_offset = AUX_OFFS;
rb->aux_size = AUX_SIZE;
// Check whether it supports a AUX buffer
aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, fd, AUX_OFFS);
if (aux == MAP_FAILED) {
munmap(rb, RB_SIZE);
close(fd);
continue;
}
attr_ok = attr;
map = MAP_AUX;
munmap(aux, AUX_SIZE);
munmap(rb, RB_SIZE);
close(fd);
break;
}
closedir(dir);
if (!map) {
if (!eacces)
SKIP(return, "No mappable perf event found.");
else
SKIP(return, "No permissions for perf_event_open()");
}
self->fd = syscall(SYS_perf_event_open, &attr_ok, 0, -1, -1, 0);
ASSERT_NE(self->fd, -1);
rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, self->fd, 0);
ASSERT_NE(rb, MAP_FAILED);
if (!variant->aux) {
self->ptr = rb;
return;
}
if (map != MAP_AUX)
SKIP(return, "No AUX event found.");
rb->aux_offset = AUX_OFFS;
rb->aux_size = AUX_SIZE;
aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, self->fd, AUX_OFFS);
ASSERT_NE(aux, MAP_FAILED);
self->ptr = aux;
}
FIXTURE_TEARDOWN(perf_mmap)
{
ASSERT_EQ(munmap(self->region, REGION_SIZE), 0);
if (self->fd != -1)
ASSERT_EQ(close(self->fd), 0);
}
TEST_F(perf_mmap, remap)
{
void *tmp, *ptr = self->ptr;
unsigned long size = variant->ptr_size;
// Test the invalid remaps
ASSERT_EQ(mremap(ptr, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
ASSERT_EQ(mremap(ptr + HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
ASSERT_EQ(mremap(ptr + size - HOLE_SIZE, HOLE_SIZE, size, MREMAP_MAYMOVE), MAP_FAILED);
// Shrink the end of the mapping such that we only unmap past end of the VMA,
// which should succeed and poke a hole into the PROT_NONE region
ASSERT_NE(mremap(ptr + size - HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
// Remap the whole buffer to a new address
tmp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(tmp, MAP_FAILED);
// Try splitting offset 1 hole size into VMA, this should fail
ASSERT_EQ(mremap(ptr + HOLE_SIZE, size - HOLE_SIZE, size - HOLE_SIZE,
MREMAP_MAYMOVE | MREMAP_FIXED, tmp), MAP_FAILED);
// Remapping the whole thing should succeed fine
ptr = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tmp);
ASSERT_EQ(ptr, tmp);
ASSERT_EQ(munmap(tmp, size), 0);
}
TEST_F(perf_mmap, unmap)
{
unsigned long size = variant->ptr_size;
// Try to poke holes into the mappings
ASSERT_NE(munmap(self->ptr, HOLE_SIZE), 0);
ASSERT_NE(munmap(self->ptr + HOLE_SIZE, HOLE_SIZE), 0);
ASSERT_NE(munmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE), 0);
}
TEST_F(perf_mmap, map)
{
unsigned long size = variant->ptr_size;
// Try to poke holes into the mappings by mapping anonymous memory over it
ASSERT_EQ(mmap(self->ptr, HOLE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
ASSERT_EQ(mmap(self->ptr + HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
ASSERT_EQ(mmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
}
TEST_HARNESS_MAIN