root/arch/s390/kernel/processor.c
// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright IBM Corp. 2008
 *  Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
 */

#define pr_fmt(fmt) "cpu: " fmt

#include <linux/stop_machine.h>
#include <linux/cpufeature.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/random.h>
#include <linux/sched/mm.h>
#include <linux/init.h>
#include <linux/seq_file.h>
#include <linux/mm_types.h>
#include <linux/delay.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <asm/text-patching.h>
#include <asm/machine.h>
#include <asm/diag.h>
#include <asm/facility.h>
#include <asm/elf.h>
#include <asm/lowcore.h>
#include <asm/param.h>
#include <asm/sclp.h>
#include <asm/smp.h>

unsigned long __read_mostly elf_hwcap;
char elf_platform[ELF_PLATFORM_SIZE];

struct cpu_info {
        unsigned int cpu_mhz_dynamic;
        unsigned int cpu_mhz_static;
        struct cpuid cpu_id;
};

static DEFINE_PER_CPU(struct cpu_info, cpu_info);
static DEFINE_PER_CPU(int, cpu_relax_retry);

static bool machine_has_cpu_mhz;

void __init cpu_detect_mhz_feature(void)
{
        if (test_facility(34) && __ecag(ECAG_CPU_ATTRIBUTE, 0) != -1UL)
                machine_has_cpu_mhz = true;
}

static void update_cpu_mhz(void *arg)
{
        unsigned long mhz;
        struct cpu_info *c;

        mhz = __ecag(ECAG_CPU_ATTRIBUTE, 0);
        c = this_cpu_ptr(&cpu_info);
        c->cpu_mhz_dynamic = mhz >> 32;
        c->cpu_mhz_static = mhz & 0xffffffff;
}

void s390_update_cpu_mhz(void)
{
        s390_adjust_jiffies();
        if (machine_has_cpu_mhz)
                on_each_cpu(update_cpu_mhz, NULL, 0);
}

void notrace stop_machine_yield(const struct cpumask *cpumask)
{
        int cpu, this_cpu;

        this_cpu = smp_processor_id();
        if (__this_cpu_inc_return(cpu_relax_retry) >= spin_retry) {
                __this_cpu_write(cpu_relax_retry, 0);
                cpu = cpumask_next_wrap(this_cpu, cpumask);
                if (cpu >= nr_cpu_ids)
                        return;
                if (arch_vcpu_is_preempted(cpu))
                        smp_yield_cpu(cpu);
        }
}

static void do_sync_core(void *info)
{
        sync_core();
}

void text_poke_sync(void)
{
        on_each_cpu(do_sync_core, NULL, 1);
}

void text_poke_sync_lock(void)
{
        cpus_read_lock();
        text_poke_sync();
        cpus_read_unlock();
}

/*
 * cpu_init - initializes state that is per-CPU.
 */
void cpu_init(void)
{
        struct cpuid *id = this_cpu_ptr(&cpu_info.cpu_id);

        get_cpu_id(id);
        if (machine_has_cpu_mhz)
                update_cpu_mhz(NULL);
        mmgrab(&init_mm);
        current->active_mm = &init_mm;
        BUG_ON(current->mm);
        enter_lazy_tlb(&init_mm, current);
}

static void show_facilities(struct seq_file *m)
{
        unsigned int bit;

        seq_puts(m, "facilities      :");
        for_each_set_bit_inv(bit, (long *)&stfle_fac_list, MAX_FACILITY_BIT)
                seq_printf(m, " %d", bit);
        seq_putc(m, '\n');
}

static void show_cpu_summary(struct seq_file *m, void *v)
{
        static const char *hwcap_str[] = {
                [HWCAP_NR_ESAN3]        = "esan3",
                [HWCAP_NR_ZARCH]        = "zarch",
                [HWCAP_NR_STFLE]        = "stfle",
                [HWCAP_NR_MSA]          = "msa",
                [HWCAP_NR_LDISP]        = "ldisp",
                [HWCAP_NR_EIMM]         = "eimm",
                [HWCAP_NR_DFP]          = "dfp",
                [HWCAP_NR_HPAGE]        = "edat",
                [HWCAP_NR_ETF3EH]       = "etf3eh",
                [HWCAP_NR_HIGH_GPRS]    = "highgprs",
                [HWCAP_NR_TE]           = "te",
                [HWCAP_NR_VXRS]         = "vx",
                [HWCAP_NR_VXRS_BCD]     = "vxd",
                [HWCAP_NR_VXRS_EXT]     = "vxe",
                [HWCAP_NR_GS]           = "gs",
                [HWCAP_NR_VXRS_EXT2]    = "vxe2",
                [HWCAP_NR_VXRS_PDE]     = "vxp",
                [HWCAP_NR_SORT]         = "sort",
                [HWCAP_NR_DFLT]         = "dflt",
                [HWCAP_NR_VXRS_PDE2]    = "vxp2",
                [HWCAP_NR_NNPA]         = "nnpa",
                [HWCAP_NR_PCI_MIO]      = "pcimio",
                [HWCAP_NR_SIE]          = "sie",
        };
        int i, cpu;

        BUILD_BUG_ON(ARRAY_SIZE(hwcap_str) != HWCAP_NR_MAX);
        seq_printf(m, "vendor_id       : IBM/S390\n"
                   "# processors    : %i\n"
                   "bogomips per cpu: %lu.%02lu\n",
                   num_online_cpus(), loops_per_jiffy/(500000/HZ),
                   (loops_per_jiffy/(5000/HZ))%100);
        seq_printf(m, "max thread id   : %d\n", smp_cpu_mtid);
        seq_puts(m, "features\t: ");
        for (i = 0; i < ARRAY_SIZE(hwcap_str); i++)
                if (hwcap_str[i] && (elf_hwcap & (1UL << i)))
                        seq_printf(m, "%s ", hwcap_str[i]);
        seq_puts(m, "\n");
        show_facilities(m);
        show_cacheinfo(m);
        for_each_online_cpu(cpu) {
                struct cpuid *id = &per_cpu(cpu_info.cpu_id, cpu);

                seq_printf(m, "processor %d: "
                           "version = %02X,  "
                           "identification = %06X,  "
                           "machine = %04X\n",
                           cpu, id->version, id->ident, id->machine);
        }
}

static int __init setup_hwcaps(void)
{
        /* instructions named N3, "backported" to esa-mode */
        elf_hwcap |= HWCAP_ESAN3;

        /* z/Architecture mode active */
        elf_hwcap |= HWCAP_ZARCH;

        /* store-facility-list-extended */
        if (test_facility(7))
                elf_hwcap |= HWCAP_STFLE;

        /* message-security assist */
        if (test_facility(17))
                elf_hwcap |= HWCAP_MSA;

        /* long-displacement */
        if (test_facility(19))
                elf_hwcap |= HWCAP_LDISP;

        /* extended-immediate */
        elf_hwcap |= HWCAP_EIMM;

        /* extended-translation facility 3 enhancement */
        if (test_facility(22) && test_facility(30))
                elf_hwcap |= HWCAP_ETF3EH;

        /* decimal floating point & perform floating point operation */
        if (test_facility(42) && test_facility(44))
                elf_hwcap |= HWCAP_DFP;

        /* huge page support */
        if (cpu_has_edat1())
                elf_hwcap |= HWCAP_HPAGE;

        /* 64-bit register support for 31-bit processes */
        elf_hwcap |= HWCAP_HIGH_GPRS;

        /* transactional execution */
        if (machine_has_tx())
                elf_hwcap |= HWCAP_TE;

        /* vector */
        if (test_facility(129)) {
                elf_hwcap |= HWCAP_VXRS;
                if (test_facility(134))
                        elf_hwcap |= HWCAP_VXRS_BCD;
                if (test_facility(135))
                        elf_hwcap |= HWCAP_VXRS_EXT;
                if (test_facility(148))
                        elf_hwcap |= HWCAP_VXRS_EXT2;
                if (test_facility(152))
                        elf_hwcap |= HWCAP_VXRS_PDE;
                if (test_facility(192))
                        elf_hwcap |= HWCAP_VXRS_PDE2;
        }

        if (test_facility(150))
                elf_hwcap |= HWCAP_SORT;

        if (test_facility(151))
                elf_hwcap |= HWCAP_DFLT;

        if (test_facility(165))
                elf_hwcap |= HWCAP_NNPA;

        /* guarded storage */
        if (cpu_has_gs())
                elf_hwcap |= HWCAP_GS;

        if (test_machine_feature(MFEATURE_PCI_MIO))
                elf_hwcap |= HWCAP_PCI_MIO;

        /* virtualization support */
        if (sclp.has_sief2)
                elf_hwcap |= HWCAP_SIE;

        return 0;
}
arch_initcall(setup_hwcaps);

static int __init setup_elf_platform(void)
{
        struct cpuid cpu_id;

        get_cpu_id(&cpu_id);
        add_device_randomness(&cpu_id, sizeof(cpu_id));
        switch (cpu_id.machine) {
        default:        /* Use "z10" as default. */
                strscpy(elf_platform, "z10");
                break;
        case 0x2817:
        case 0x2818:
                strscpy(elf_platform, "z196");
                break;
        case 0x2827:
        case 0x2828:
                strscpy(elf_platform, "zEC12");
                break;
        case 0x2964:
        case 0x2965:
                strscpy(elf_platform, "z13");
                break;
        case 0x3906:
        case 0x3907:
                strscpy(elf_platform, "z14");
                break;
        case 0x8561:
        case 0x8562:
                strscpy(elf_platform, "z15");
                break;
        case 0x3931:
        case 0x3932:
                strscpy(elf_platform, "z16");
                break;
        case 0x9175:
        case 0x9176:
                strscpy(elf_platform, "z17");
                break;
        }
        return 0;
}
arch_initcall(setup_elf_platform);

static void show_cpu_topology(struct seq_file *m, unsigned long n)
{
#ifdef CONFIG_SCHED_TOPOLOGY
        seq_printf(m, "physical id     : %d\n", topology_physical_package_id(n));
        seq_printf(m, "core id         : %d\n", topology_core_id(n));
        seq_printf(m, "book id         : %d\n", topology_book_id(n));
        seq_printf(m, "drawer id       : %d\n", topology_drawer_id(n));
        seq_printf(m, "dedicated       : %d\n", topology_cpu_dedicated(n));
        seq_printf(m, "address         : %d\n", smp_cpu_get_cpu_address(n));
        seq_printf(m, "siblings        : %d\n", cpumask_weight(topology_core_cpumask(n)));
        seq_printf(m, "cpu cores       : %d\n", topology_booted_cores(n));
#endif /* CONFIG_SCHED_TOPOLOGY */
}

static void show_cpu_ids(struct seq_file *m, unsigned long n)
{
        struct cpuid *id = &per_cpu(cpu_info.cpu_id, n);

        seq_printf(m, "version         : %02X\n", id->version);
        seq_printf(m, "identification  : %06X\n", id->ident);
        seq_printf(m, "machine         : %04X\n", id->machine);
}

static void show_cpu_mhz(struct seq_file *m, unsigned long n)
{
        struct cpu_info *c = per_cpu_ptr(&cpu_info, n);

        if (!machine_has_cpu_mhz)
                return;
        seq_printf(m, "cpu MHz dynamic : %d\n", c->cpu_mhz_dynamic);
        seq_printf(m, "cpu MHz static  : %d\n", c->cpu_mhz_static);
}

/*
 * show_cpuinfo - Get information on one CPU for use by procfs.
 */
static int show_cpuinfo(struct seq_file *m, void *v)
{
        unsigned long n = (unsigned long) v - 1;
        unsigned long first = cpumask_first(cpu_online_mask);

        if (n == first)
                show_cpu_summary(m, v);
        seq_printf(m, "\ncpu number      : %ld\n", n);
        show_cpu_topology(m, n);
        show_cpu_ids(m, n);
        show_cpu_mhz(m, n);
        return 0;
}

static inline void *c_update(loff_t *pos)
{
        if (*pos)
                *pos = cpumask_next(*pos - 1, cpu_online_mask);
        else
                *pos = cpumask_first(cpu_online_mask);
        return *pos < nr_cpu_ids ? (void *)*pos + 1 : NULL;
}

static void *c_start(struct seq_file *m, loff_t *pos)
{
        cpus_read_lock();
        return c_update(pos);
}

static void *c_next(struct seq_file *m, void *v, loff_t *pos)
{
        ++*pos;
        return c_update(pos);
}

static void c_stop(struct seq_file *m, void *v)
{
        cpus_read_unlock();
}

const struct seq_operations cpuinfo_op = {
        .start  = c_start,
        .next   = c_next,
        .stop   = c_stop,
        .show   = show_cpuinfo,
};