237 lines
5.6 KiB
C
237 lines
5.6 KiB
C
// 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
|