root/tools/testing/selftests/powerpc/utils.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2013-2015, Michael Ellerman, IBM Corp.
 */

#define _GNU_SOURCE     /* For CPU_ZERO etc. */

#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <link.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <linux/limits.h>

#include "utils.h"

static char auxv[4096];

int read_file(const char *path, char *buf, size_t count, size_t *len)
{
        ssize_t rc;
        int fd;
        int err;
        char eof;

        fd = open(path, O_RDONLY);
        if (fd < 0)
                return -errno;

        rc = read(fd, buf, count);
        if (rc < 0) {
                err = -errno;
                goto out;
        }

        if (len)
                *len = rc;

        /* Overflow if there are still more bytes after filling the buffer */
        if (rc == count) {
                rc = read(fd, &eof, 1);
                if (rc != 0) {
                        err = -EOVERFLOW;
                        goto out;
                }
        }

        err = 0;

out:
        close(fd);
        errno = -err;
        return err;
}

int read_file_alloc(const char *path, char **buf, size_t *len)
{
        size_t read_offset = 0;
        size_t buffer_len = 0;
        char *buffer = NULL;
        int err;
        int fd;

        fd = open(path, O_RDONLY);
        if (fd < 0)
                return -errno;

        /*
         * We don't use stat & preallocate st_size because some non-files
         * report 0 file size. Instead just dynamically grow the buffer
         * as needed.
         */
        while (1) {
                ssize_t rc;

                if (read_offset >= buffer_len / 2) {
                        char *next_buffer;

                        buffer_len = buffer_len ? buffer_len * 2 : 4096;
                        next_buffer = realloc(buffer, buffer_len);
                        if (!next_buffer) {
                                err = -errno;
                                goto out;
                        }
                        buffer = next_buffer;
                }

                rc = read(fd, buffer + read_offset, buffer_len - read_offset);
                if (rc < 0) {
                        err = -errno;
                        goto out;
                }

                if (rc == 0)
                        break;

                read_offset += rc;
        }

        *buf = buffer;
        if (len)
                *len = read_offset;

        err = 0;

out:
        close(fd);
        if (err)
                free(buffer);
        errno = -err;
        return err;
}

int write_file(const char *path, const char *buf, size_t count)
{
        int fd;
        int err;
        ssize_t rc;

        fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd < 0)
                return -errno;

        rc = write(fd, buf, count);
        if (rc < 0) {
                err = -errno;
                goto out;
        }

        if (rc != count) {
                err = -EOVERFLOW;
                goto out;
        }

        err = 0;

out:
        close(fd);
        errno = -err;
        return err;
}

int read_auxv(char *buf, ssize_t buf_size)
{
        int err;

        err = read_file("/proc/self/auxv", buf, buf_size, NULL);
        if (err) {
                perror("Error reading /proc/self/auxv");
                return err;
        }

        return 0;
}

int read_debugfs_file(const char *subpath, char *buf, size_t count)
{
        char path[PATH_MAX] = "/sys/kernel/debug/";

        strncat(path, subpath, sizeof(path) - strlen(path) - 1);

        return read_file(path, buf, count, NULL);
}

int write_debugfs_file(const char *subpath, const char *buf, size_t count)
{
        char path[PATH_MAX] = "/sys/kernel/debug/";

        strncat(path, subpath, sizeof(path) - strlen(path) - 1);

        return write_file(path, buf, count);
}

static int validate_int_parse(const char *buffer, size_t count, char *end)
{
        int err = 0;

        /* Require at least one digit */
        if (end == buffer) {
                err = -EINVAL;
                goto out;
        }

        /* Require all remaining characters be whitespace-ish */
        for (; end < buffer + count; end++) {
                if (*end == '\0')
                        break;

                if (*end != ' ' && *end != '\n') {
                        err = -EINVAL;
                        goto out;
                }
        }

out:
        errno = -err;
        return err;
}

static int parse_bounded_int(const char *buffer, size_t count, intmax_t *result,
                             int base, intmax_t min, intmax_t max)
{
        int err;
        char *end;

        errno = 0;
        *result = strtoimax(buffer, &end, base);

        if (errno)
                return -errno;

        err = validate_int_parse(buffer, count, end);
        if (err)
                goto out;

        if (*result < min || *result > max)
                err = -EOVERFLOW;

out:
        errno = -err;
        return err;
}

static int parse_bounded_uint(const char *buffer, size_t count, uintmax_t *result,
                              int base, uintmax_t max)
{
        int err = 0;
        char *end;

        errno = 0;
        *result = strtoumax(buffer, &end, base);

        if (errno)
                return -errno;

        err = validate_int_parse(buffer, count, end);
        if (err)
                goto out;

        if (*result > max)
                err = -EOVERFLOW;

out:
        errno = -err;
        return err;
}

int parse_intmax(const char *buffer, size_t count, intmax_t *result, int base)
{
        return parse_bounded_int(buffer, count, result, base, INTMAX_MIN, INTMAX_MAX);
}

int parse_uintmax(const char *buffer, size_t count, uintmax_t *result, int base)
{
        return parse_bounded_uint(buffer, count, result, base, UINTMAX_MAX);
}

int parse_int(const char *buffer, size_t count, int *result, int base)
{
        intmax_t parsed;
        int err = parse_bounded_int(buffer, count, &parsed, base, INT_MIN, INT_MAX);

        *result = parsed;
        return err;
}

int parse_uint(const char *buffer, size_t count, unsigned int *result, int base)
{
        uintmax_t parsed;
        int err = parse_bounded_uint(buffer, count, &parsed, base, UINT_MAX);

        *result = parsed;
        return err;
}

int parse_long(const char *buffer, size_t count, long *result, int base)
{
        intmax_t parsed;
        int err = parse_bounded_int(buffer, count, &parsed, base, LONG_MIN, LONG_MAX);

        *result = parsed;
        return err;
}

int parse_ulong(const char *buffer, size_t count, unsigned long *result, int base)
{
        uintmax_t parsed;
        int err = parse_bounded_uint(buffer, count, &parsed, base, ULONG_MAX);

        *result = parsed;
        return err;
}

int read_long(const char *path, long *result, int base)
{
        int err;
        char buffer[32] = {0};

        err = read_file(path, buffer, sizeof(buffer) - 1, NULL);
        if (err)
                return err;

        return parse_long(buffer, sizeof(buffer), result, base);
}

int read_ulong(const char *path, unsigned long *result, int base)
{
        int err;
        char buffer[32] = {0};

        err = read_file(path, buffer, sizeof(buffer) - 1, NULL);
        if (err)
                return err;

        return parse_ulong(buffer, sizeof(buffer), result, base);
}

int write_long(const char *path, long result, int base)
{
        int err;
        int len;
        char buffer[32];

        /* Decimal only for now: no format specifier for signed hex values */
        if (base != 10) {
                err = -EINVAL;
                goto out;
        }

        len = snprintf(buffer, sizeof(buffer), "%ld", result);
        if (len < 0 || len >= sizeof(buffer)) {
                err = -EOVERFLOW;
                goto out;
        }

        err = write_file(path, buffer, len);

out:
        errno = -err;
        return err;
}

int write_ulong(const char *path, unsigned long result, int base)
{
        int err;
        int len;
        char buffer[32];
        char *fmt;

        switch (base) {
        case 10:
                fmt = "%lu";
                break;
        case 16:
                fmt = "%lx";
                break;
        default:
                err = -EINVAL;
                goto out;
        }

        len = snprintf(buffer, sizeof(buffer), fmt, result);
        if (len < 0 || len >= sizeof(buffer)) {
                err = -errno;
                goto out;
        }

        err = write_file(path, buffer, len);

out:
        errno = -err;
        return err;
}

void *find_auxv_entry(int type, char *auxv)
{
        ElfW(auxv_t) *p;

        p = (ElfW(auxv_t) *)auxv;

        while (p->a_type != AT_NULL) {
                if (p->a_type == type)
                        return p;

                p++;
        }

        return NULL;
}

void *get_auxv_entry(int type)
{
        ElfW(auxv_t) *p;

        if (read_auxv(auxv, sizeof(auxv)))
                return NULL;

        p = find_auxv_entry(type, auxv);
        if (p)
                return (void *)p->a_un.a_val;

        return NULL;
}

int pick_online_cpu(void)
{
        int ncpus, cpu = -1;
        cpu_set_t *mask;
        size_t size;

        ncpus = get_nprocs_conf();
        size = CPU_ALLOC_SIZE(ncpus);
        mask = CPU_ALLOC(ncpus);
        if (!mask) {
                perror("malloc");
                return -1;
        }

        CPU_ZERO_S(size, mask);

        if (sched_getaffinity(0, size, mask)) {
                perror("sched_getaffinity");
                goto done;
        }

        /* We prefer a primary thread, but skip 0 */
        for (cpu = 8; cpu < ncpus; cpu += 8)
                if (CPU_ISSET_S(cpu, size, mask))
                        goto done;

        /* Search for anything, but in reverse */
        for (cpu = ncpus - 1; cpu >= 0; cpu--)
                if (CPU_ISSET_S(cpu, size, mask))
                        goto done;

        printf("No cpus in affinity mask?!\n");

done:
        CPU_FREE(mask);
        return cpu;
}

int bind_to_cpu(int cpu)
{
        cpu_set_t mask;
        int err;

        if (cpu == BIND_CPU_ANY) {
                cpu = pick_online_cpu();
                if (cpu < 0)
                        return cpu;
        }

        printf("Binding to cpu %d\n", cpu);

        CPU_ZERO(&mask);
        CPU_SET(cpu, &mask);

        err = sched_setaffinity(0, sizeof(mask), &mask);
        if (err)
                return err;

        return cpu;
}

bool is_ppc64le(void)
{
        struct utsname uts;
        int rc;

        errno = 0;
        rc = uname(&uts);
        if (rc) {
                perror("uname");
                return false;
        }

        return strcmp(uts.machine, "ppc64le") == 0;
}

int read_sysfs_file(char *fpath, char *result, size_t result_size)
{
        char path[PATH_MAX] = "/sys/";

        strncat(path, fpath, PATH_MAX - strlen(path) - 1);

        return read_file(path, result, result_size, NULL);
}

int read_debugfs_int(const char *debugfs_file, int *result)
{
        int err;
        char value[16] = {0};

        err = read_debugfs_file(debugfs_file, value, sizeof(value) - 1);
        if (err)
                return err;

        return parse_int(value, sizeof(value), result, 10);
}

int write_debugfs_int(const char *debugfs_file, int result)
{
        char value[16];

        snprintf(value, 16, "%d", result);

        return write_debugfs_file(debugfs_file, value, strlen(value));
}

static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
                int cpu, int group_fd, unsigned long flags)
{
        return syscall(__NR_perf_event_open, hw_event, pid, cpu,
                      group_fd, flags);
}

static void perf_event_attr_init(struct perf_event_attr *event_attr,
                                        unsigned int type,
                                        unsigned long config)
{
        memset(event_attr, 0, sizeof(*event_attr));

        event_attr->type = type;
        event_attr->size = sizeof(struct perf_event_attr);
        event_attr->config = config;
        event_attr->read_format = PERF_FORMAT_GROUP;
        event_attr->disabled = 1;
        event_attr->exclude_kernel = 1;
        event_attr->exclude_hv = 1;
        event_attr->exclude_guest = 1;
}

int perf_event_open_counter(unsigned int type,
                            unsigned long config, int group_fd)
{
        int fd;
        struct perf_event_attr event_attr;

        perf_event_attr_init(&event_attr, type, config);

        fd = perf_event_open(&event_attr, 0, -1, group_fd, 0);

        if (fd < 0)
                perror("perf_event_open() failed");

        return fd;
}

int perf_event_enable(int fd)
{
        if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
                perror("error while enabling perf events");
                return -1;
        }

        return 0;
}

int perf_event_disable(int fd)
{
        if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
                perror("error disabling perf events");
                return -1;
        }

        return 0;
}

int perf_event_reset(int fd)
{
        if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
                perror("error resetting perf events");
                return -1;
        }

        return 0;
}

int using_hash_mmu(bool *using_hash)
{
        char line[128];
        FILE *f;
        int rc;

        f = fopen("/proc/cpuinfo", "r");
        FAIL_IF(!f);

        rc = 0;
        while (fgets(line, sizeof(line), f) != NULL) {
                if (!strcmp(line, "MMU          : Hash\n") ||
                    !strcmp(line, "platform     : Cell\n") ||
                    !strcmp(line, "platform     : PowerMac\n")) {
                        *using_hash = true;
                        goto out;
                }

                if (strcmp(line, "MMU           : Radix\n") == 0) {
                        *using_hash = false;
                        goto out;
                }
        }

        rc = -1;
out:
        fclose(f);
        return rc;
}

struct sigaction push_signal_handler(int sig, void (*fn)(int, siginfo_t *, void *))
{
        struct sigaction sa;
        struct sigaction old_handler;

        sa.sa_sigaction = fn;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_SIGINFO;
        FAIL_IF_EXIT_MSG(sigaction(sig, &sa, &old_handler),
                         "failed to push signal handler");

        return old_handler;
}

struct sigaction pop_signal_handler(int sig, struct sigaction old_handler)
{
        struct sigaction popped;

        FAIL_IF_EXIT_MSG(sigaction(sig, &old_handler, &popped),
                         "failed to pop signal handler");

        return popped;
}