root/arch/riscv/kernel/vdso/hwprobe.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright 2023 Rivos, Inc
 */

#include <linux/string.h>
#include <linux/types.h>
#include <vdso/datapage.h>
#include <vdso/helpers.h>

extern int riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
                         size_t cpusetsize, unsigned long *cpus,
                         unsigned int flags);

static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count,
                                 size_t cpusetsize, unsigned long *cpus,
                                 unsigned int flags)
{
        const struct vdso_arch_data *avd = &vdso_u_arch_data;
        bool all_cpus = !cpusetsize && !cpus;
        struct riscv_hwprobe *p = pairs;
        struct riscv_hwprobe *end = pairs + pair_count;

        /*
         * Defer to the syscall for exotic requests. The vdso has answers
         * stashed away only for the "all cpus" case. If all CPUs are
         * homogeneous, then this function can handle requests for arbitrary
         * masks.
         */
        if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready))
                return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags);

        /* This is something we can handle, fill out the pairs. */
        while (p < end) {
                if (riscv_hwprobe_key_is_valid(p->key)) {
                        p->value = avd->all_cpu_hwprobe_values[p->key];

                } else {
                        p->key = -1;
                        p->value = 0;
                }

                p++;
        }

        return 0;
}

static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count,
                               size_t cpusetsize, unsigned long *cpus,
                               unsigned int flags)
{
        const struct vdso_arch_data *avd = &vdso_u_arch_data;
        struct riscv_hwprobe *p = pairs;
        struct riscv_hwprobe *end = pairs + pair_count;
        unsigned char *c = (unsigned char *)cpus;
        bool empty_cpus = true;
        bool clear_all = false;
        int i;

        if (!cpusetsize || !cpus)
                return -EINVAL;

        for (i = 0; i < cpusetsize; i++) {
                if (c[i]) {
                        empty_cpus = false;
                        break;
                }
        }

        if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || !avd->homogeneous_cpus)
                return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags);

        while (p < end) {
                if (riscv_hwprobe_key_is_valid(p->key)) {
                        struct riscv_hwprobe t = {
                                .key = p->key,
                                .value = avd->all_cpu_hwprobe_values[p->key],
                        };

                        if (!riscv_hwprobe_pair_cmp(&t, p))
                                clear_all = true;
                } else {
                        clear_all = true;
                        p->key = -1;
                        p->value = 0;
                }
                p++;
        }

        if (clear_all) {
                for (i = 0; i < cpusetsize; i++)
                        c[i] = 0;
        }

        return 0;
}

/* Add a prototype to avoid -Wmissing-prototypes warning. */
int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
                         size_t cpusetsize, unsigned long *cpus,
                         unsigned int flags);

int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
                         size_t cpusetsize, unsigned long *cpus,
                         unsigned int flags)
{
        if (flags & RISCV_HWPROBE_WHICH_CPUS)
                return riscv_vdso_get_cpus(pairs, pair_count, cpusetsize,
                                           cpus, flags);

        return riscv_vdso_get_values(pairs, pair_count, cpusetsize,
                                     cpus, flags);
}