2157 lines
51 KiB
C
2157 lines
51 KiB
C
/* $OpenBSD: cpu.c,v 1.114 2024/04/13 14:19:39 kettenis Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2016 Dale Rahn <drahn@dalerahn.com>
|
|
* Copyright (c) 2017 Mark Kettenis <kettenis@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "kstat.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/device.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/task.h>
|
|
#include <sys/user.h>
|
|
#include <sys/kstat.h>
|
|
|
|
#include <uvm/uvm.h>
|
|
|
|
#include <machine/fdt.h>
|
|
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <dev/ofw/ofw_clock.h>
|
|
#include <dev/ofw/ofw_regulator.h>
|
|
#include <dev/ofw/ofw_thermal.h>
|
|
#include <dev/ofw/fdt.h>
|
|
|
|
#include <machine/cpufunc.h>
|
|
|
|
#include "psci.h"
|
|
#if NPSCI > 0
|
|
#include <dev/fdt/pscivar.h>
|
|
#endif
|
|
|
|
/* CPU Identification */
|
|
#define CPU_IMPL_ARM 0x41
|
|
#define CPU_IMPL_CAVIUM 0x43
|
|
#define CPU_IMPL_AMCC 0x50
|
|
#define CPU_IMPL_QCOM 0x51
|
|
#define CPU_IMPL_APPLE 0x61
|
|
#define CPU_IMPL_AMPERE 0xc0
|
|
|
|
/* ARM */
|
|
#define CPU_PART_CORTEX_A34 0xd02
|
|
#define CPU_PART_CORTEX_A53 0xd03
|
|
#define CPU_PART_CORTEX_A35 0xd04
|
|
#define CPU_PART_CORTEX_A55 0xd05
|
|
#define CPU_PART_CORTEX_A65 0xd06
|
|
#define CPU_PART_CORTEX_A57 0xd07
|
|
#define CPU_PART_CORTEX_A72 0xd08
|
|
#define CPU_PART_CORTEX_A73 0xd09
|
|
#define CPU_PART_CORTEX_A75 0xd0a
|
|
#define CPU_PART_CORTEX_A76 0xd0b
|
|
#define CPU_PART_NEOVERSE_N1 0xd0c
|
|
#define CPU_PART_CORTEX_A77 0xd0d
|
|
#define CPU_PART_CORTEX_A76AE 0xd0e
|
|
#define CPU_PART_NEOVERSE_V1 0xd40
|
|
#define CPU_PART_CORTEX_A78 0xd41
|
|
#define CPU_PART_CORTEX_A78AE 0xd42
|
|
#define CPU_PART_CORTEX_A65AE 0xd43
|
|
#define CPU_PART_CORTEX_X1 0xd44
|
|
#define CPU_PART_CORTEX_A510 0xd46
|
|
#define CPU_PART_CORTEX_A710 0xd47
|
|
#define CPU_PART_CORTEX_X2 0xd48
|
|
#define CPU_PART_NEOVERSE_N2 0xd49
|
|
#define CPU_PART_NEOVERSE_E1 0xd4a
|
|
#define CPU_PART_CORTEX_A78C 0xd4b
|
|
#define CPU_PART_CORTEX_X1C 0xd4c
|
|
#define CPU_PART_CORTEX_A715 0xd4d
|
|
#define CPU_PART_CORTEX_X3 0xd4e
|
|
#define CPU_PART_NEOVERSE_V2 0xd4f
|
|
#define CPU_PART_CORTEX_A520 0xd80
|
|
#define CPU_PART_CORTEX_A720 0xd81
|
|
#define CPU_PART_CORTEX_X4 0xd82
|
|
#define CPU_PART_NEOVERSE_V3 0xd84
|
|
#define CPU_PART_CORTEX_A520AE 0xd88
|
|
#define CPU_PART_CORTEX_A720AE 0xd89
|
|
#define CPU_PART_NEOVERSE_N3 0xd8e
|
|
|
|
/* Cavium */
|
|
#define CPU_PART_THUNDERX_T88 0x0a1
|
|
#define CPU_PART_THUNDERX_T81 0x0a2
|
|
#define CPU_PART_THUNDERX_T83 0x0a3
|
|
#define CPU_PART_THUNDERX2_T99 0x0af
|
|
|
|
/* Applied Micro */
|
|
#define CPU_PART_X_GENE 0x000
|
|
|
|
/* Qualcomm */
|
|
#define CPU_PART_KRYO400_GOLD 0x804
|
|
#define CPU_PART_KRYO400_SILVER 0x805
|
|
|
|
/* Apple */
|
|
#define CPU_PART_ICESTORM 0x022
|
|
#define CPU_PART_FIRESTORM 0x023
|
|
#define CPU_PART_ICESTORM_PRO 0x024
|
|
#define CPU_PART_FIRESTORM_PRO 0x025
|
|
#define CPU_PART_ICESTORM_MAX 0x028
|
|
#define CPU_PART_FIRESTORM_MAX 0x029
|
|
#define CPU_PART_BLIZZARD 0x032
|
|
#define CPU_PART_AVALANCHE 0x033
|
|
#define CPU_PART_BLIZZARD_PRO 0x034
|
|
#define CPU_PART_AVALANCHE_PRO 0x035
|
|
#define CPU_PART_BLIZZARD_MAX 0x038
|
|
#define CPU_PART_AVALANCHE_MAX 0x039
|
|
|
|
/* Ampere */
|
|
#define CPU_PART_AMPERE1 0xac3
|
|
|
|
#define CPU_IMPL(midr) (((midr) >> 24) & 0xff)
|
|
#define CPU_PART(midr) (((midr) >> 4) & 0xfff)
|
|
#define CPU_VAR(midr) (((midr) >> 20) & 0xf)
|
|
#define CPU_REV(midr) (((midr) >> 0) & 0xf)
|
|
|
|
struct cpu_cores {
|
|
int id;
|
|
char *name;
|
|
};
|
|
|
|
struct cpu_cores cpu_cores_none[] = {
|
|
{ 0, NULL },
|
|
};
|
|
|
|
struct cpu_cores cpu_cores_arm[] = {
|
|
{ CPU_PART_CORTEX_A34, "Cortex-A34" },
|
|
{ CPU_PART_CORTEX_A35, "Cortex-A35" },
|
|
{ CPU_PART_CORTEX_A53, "Cortex-A53" },
|
|
{ CPU_PART_CORTEX_A55, "Cortex-A55" },
|
|
{ CPU_PART_CORTEX_A57, "Cortex-A57" },
|
|
{ CPU_PART_CORTEX_A65, "Cortex-A65" },
|
|
{ CPU_PART_CORTEX_A65AE, "Cortex-A65AE" },
|
|
{ CPU_PART_CORTEX_A72, "Cortex-A72" },
|
|
{ CPU_PART_CORTEX_A73, "Cortex-A73" },
|
|
{ CPU_PART_CORTEX_A75, "Cortex-A75" },
|
|
{ CPU_PART_CORTEX_A76, "Cortex-A76" },
|
|
{ CPU_PART_CORTEX_A76AE, "Cortex-A76AE" },
|
|
{ CPU_PART_CORTEX_A77, "Cortex-A77" },
|
|
{ CPU_PART_CORTEX_A78, "Cortex-A78" },
|
|
{ CPU_PART_CORTEX_A78AE, "Cortex-A78AE" },
|
|
{ CPU_PART_CORTEX_A78C, "Cortex-A78C" },
|
|
{ CPU_PART_CORTEX_A510, "Cortex-A510" },
|
|
{ CPU_PART_CORTEX_A520, "Cortex-A520" },
|
|
{ CPU_PART_CORTEX_A520AE, "Cortex-A520AE" },
|
|
{ CPU_PART_CORTEX_A710, "Cortex-A710" },
|
|
{ CPU_PART_CORTEX_A715, "Cortex-A715" },
|
|
{ CPU_PART_CORTEX_A720, "Cortex-A720" },
|
|
{ CPU_PART_CORTEX_A720AE, "Cortex-A720AE" },
|
|
{ CPU_PART_CORTEX_X1, "Cortex-X1" },
|
|
{ CPU_PART_CORTEX_X1C, "Cortex-X1C" },
|
|
{ CPU_PART_CORTEX_X2, "Cortex-X2" },
|
|
{ CPU_PART_CORTEX_X3, "Cortex-X3" },
|
|
{ CPU_PART_CORTEX_X4, "Cortex-X4" },
|
|
{ CPU_PART_NEOVERSE_E1, "Neoverse E1" },
|
|
{ CPU_PART_NEOVERSE_N1, "Neoverse N1" },
|
|
{ CPU_PART_NEOVERSE_N2, "Neoverse N2" },
|
|
{ CPU_PART_NEOVERSE_N3, "Neoverse N3" },
|
|
{ CPU_PART_NEOVERSE_V1, "Neoverse V1" },
|
|
{ CPU_PART_NEOVERSE_V2, "Neoverse V2" },
|
|
{ CPU_PART_NEOVERSE_V3, "Neoverse V3" },
|
|
{ 0, NULL },
|
|
};
|
|
|
|
struct cpu_cores cpu_cores_cavium[] = {
|
|
{ CPU_PART_THUNDERX_T88, "ThunderX T88" },
|
|
{ CPU_PART_THUNDERX_T81, "ThunderX T81" },
|
|
{ CPU_PART_THUNDERX_T83, "ThunderX T83" },
|
|
{ CPU_PART_THUNDERX2_T99, "ThunderX2 T99" },
|
|
{ 0, NULL },
|
|
};
|
|
|
|
struct cpu_cores cpu_cores_amcc[] = {
|
|
{ CPU_PART_X_GENE, "X-Gene" },
|
|
{ 0, NULL },
|
|
};
|
|
|
|
struct cpu_cores cpu_cores_qcom[] = {
|
|
{ CPU_PART_KRYO400_GOLD, "Kryo 400 Gold" },
|
|
{ CPU_PART_KRYO400_SILVER, "Kryo 400 Silver" },
|
|
{ 0, NULL },
|
|
};
|
|
|
|
struct cpu_cores cpu_cores_apple[] = {
|
|
{ CPU_PART_ICESTORM, "Icestorm" },
|
|
{ CPU_PART_FIRESTORM, "Firestorm" },
|
|
{ CPU_PART_ICESTORM_PRO, "Icestorm Pro" },
|
|
{ CPU_PART_FIRESTORM_PRO, "Firestorm Pro" },
|
|
{ CPU_PART_ICESTORM_MAX, "Icestorm Max" },
|
|
{ CPU_PART_FIRESTORM_MAX, "Firestorm Max" },
|
|
{ CPU_PART_BLIZZARD, "Blizzard" },
|
|
{ CPU_PART_AVALANCHE, "Avalanche" },
|
|
{ CPU_PART_BLIZZARD_PRO, "Blizzard Pro" },
|
|
{ CPU_PART_AVALANCHE_PRO, "Avalanche Pro" },
|
|
{ CPU_PART_BLIZZARD_MAX, "Blizzard Max" },
|
|
{ CPU_PART_AVALANCHE_MAX, "Avalanche Max" },
|
|
{ 0, NULL },
|
|
};
|
|
|
|
struct cpu_cores cpu_cores_ampere[] = {
|
|
{ CPU_PART_AMPERE1, "AmpereOne" },
|
|
{ 0, NULL },
|
|
};
|
|
|
|
/* arm cores makers */
|
|
const struct implementers {
|
|
int id;
|
|
char *name;
|
|
struct cpu_cores *corelist;
|
|
} cpu_implementers[] = {
|
|
{ CPU_IMPL_ARM, "ARM", cpu_cores_arm },
|
|
{ CPU_IMPL_CAVIUM, "Cavium", cpu_cores_cavium },
|
|
{ CPU_IMPL_AMCC, "Applied Micro", cpu_cores_amcc },
|
|
{ CPU_IMPL_QCOM, "Qualcomm", cpu_cores_qcom },
|
|
{ CPU_IMPL_APPLE, "Apple", cpu_cores_apple },
|
|
{ CPU_IMPL_AMPERE, "Ampere", cpu_cores_ampere },
|
|
{ 0, NULL },
|
|
};
|
|
|
|
char cpu_model[64];
|
|
int cpu_node;
|
|
|
|
uint64_t cpu_id_aa64isar0;
|
|
uint64_t cpu_id_aa64isar1;
|
|
uint64_t cpu_id_aa64isar2;
|
|
uint64_t cpu_id_aa64pfr0;
|
|
uint64_t cpu_id_aa64pfr1;
|
|
|
|
#ifdef CRYPTO
|
|
int arm64_has_aes;
|
|
#endif
|
|
|
|
extern char trampoline_vectors_none[];
|
|
extern char trampoline_vectors_loop_8[];
|
|
extern char trampoline_vectors_loop_11[];
|
|
extern char trampoline_vectors_loop_24[];
|
|
extern char trampoline_vectors_loop_32[];
|
|
#if NPSCI > 0
|
|
extern char trampoline_vectors_psci_hvc[];
|
|
extern char trampoline_vectors_psci_smc[];
|
|
#endif
|
|
extern char trampoline_vectors_clrbhb[];
|
|
|
|
struct cpu_info *cpu_info_list = &cpu_info_primary;
|
|
|
|
int cpu_match(struct device *, void *, void *);
|
|
void cpu_attach(struct device *, struct device *, void *);
|
|
|
|
const struct cfattach cpu_ca = {
|
|
sizeof(struct device), cpu_match, cpu_attach
|
|
};
|
|
|
|
struct cfdriver cpu_cd = {
|
|
NULL, "cpu", DV_DULL
|
|
};
|
|
|
|
void cpu_opp_init(struct cpu_info *, uint32_t);
|
|
void cpu_psci_init(struct cpu_info *);
|
|
|
|
void cpu_flush_bp_noop(void);
|
|
void cpu_flush_bp_psci(void);
|
|
void cpu_serror_apple(void);
|
|
|
|
#if NKSTAT > 0
|
|
void cpu_kstat_attach(struct cpu_info *ci);
|
|
void cpu_opp_kstat_attach(struct cpu_info *ci);
|
|
#endif
|
|
|
|
/*
|
|
* Enable mitigation for Spectre-V2 branch target injection
|
|
* vulnerabilities (CVE-2017-5715).
|
|
*/
|
|
void
|
|
cpu_mitigate_spectre_v2(struct cpu_info *ci)
|
|
{
|
|
uint64_t id;
|
|
|
|
/*
|
|
* By default we let the firmware decide what mitigation is
|
|
* necessary.
|
|
*/
|
|
ci->ci_flush_bp = cpu_flush_bp_psci;
|
|
|
|
/* Some specific CPUs are known not to be vulnerable. */
|
|
switch (CPU_IMPL(ci->ci_midr)) {
|
|
case CPU_IMPL_ARM:
|
|
switch (CPU_PART(ci->ci_midr)) {
|
|
case CPU_PART_CORTEX_A35:
|
|
case CPU_PART_CORTEX_A53:
|
|
case CPU_PART_CORTEX_A55:
|
|
/* Not vulnerable. */
|
|
ci->ci_flush_bp = cpu_flush_bp_noop;
|
|
break;
|
|
}
|
|
break;
|
|
case CPU_IMPL_QCOM:
|
|
switch (CPU_PART(ci->ci_midr)) {
|
|
case CPU_PART_KRYO400_SILVER:
|
|
/* Not vulnerable. */
|
|
ci->ci_flush_bp = cpu_flush_bp_noop;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The architecture has been updated to explicitly tell us if
|
|
* we're not vulnerable to Spectre-V2.
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
if (ID_AA64PFR0_CSV2(id) >= ID_AA64PFR0_CSV2_IMPL)
|
|
ci->ci_flush_bp = cpu_flush_bp_noop;
|
|
}
|
|
|
|
/*
|
|
* Enable mitigation for Spectre-BHB branch history injection
|
|
* vulnerabilities (CVE-2022-23960).
|
|
*/
|
|
void
|
|
cpu_mitigate_spectre_bhb(struct cpu_info *ci)
|
|
{
|
|
uint64_t id;
|
|
|
|
/*
|
|
* If we know the CPU, we can add a branchy loop that cleans
|
|
* the BHB.
|
|
*/
|
|
switch (CPU_IMPL(ci->ci_midr)) {
|
|
case CPU_IMPL_ARM:
|
|
switch (CPU_PART(ci->ci_midr)) {
|
|
case CPU_PART_CORTEX_A57:
|
|
case CPU_PART_CORTEX_A72:
|
|
ci->ci_trampoline_vectors =
|
|
(vaddr_t)trampoline_vectors_loop_8;
|
|
break;
|
|
case CPU_PART_CORTEX_A76:
|
|
case CPU_PART_CORTEX_A76AE:
|
|
case CPU_PART_CORTEX_A77:
|
|
case CPU_PART_NEOVERSE_N1:
|
|
ci->ci_trampoline_vectors =
|
|
(vaddr_t)trampoline_vectors_loop_24;
|
|
break;
|
|
case CPU_PART_CORTEX_A78:
|
|
case CPU_PART_CORTEX_A78AE:
|
|
case CPU_PART_CORTEX_A78C:
|
|
case CPU_PART_CORTEX_X1:
|
|
case CPU_PART_CORTEX_X2:
|
|
case CPU_PART_CORTEX_A710:
|
|
case CPU_PART_NEOVERSE_N2:
|
|
case CPU_PART_NEOVERSE_V1:
|
|
ci->ci_trampoline_vectors =
|
|
(vaddr_t)trampoline_vectors_loop_32;
|
|
break;
|
|
}
|
|
break;
|
|
case CPU_IMPL_AMPERE:
|
|
switch (CPU_PART(ci->ci_midr)) {
|
|
case CPU_PART_AMPERE1:
|
|
ci->ci_trampoline_vectors =
|
|
(vaddr_t)trampoline_vectors_loop_11;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If we're not using a loop, let firmware decide. This also
|
|
* covers the original Spectre-V2 in addition to Spectre-BHB.
|
|
*/
|
|
#if NPSCI > 0
|
|
if (ci->ci_trampoline_vectors == (vaddr_t)trampoline_vectors_none &&
|
|
smccc_needs_arch_workaround_3()) {
|
|
ci->ci_flush_bp = cpu_flush_bp_noop;
|
|
if (psci_method() == PSCI_METHOD_HVC)
|
|
ci->ci_trampoline_vectors =
|
|
(vaddr_t)trampoline_vectors_psci_hvc;
|
|
if (psci_method() == PSCI_METHOD_SMC)
|
|
ci->ci_trampoline_vectors =
|
|
(vaddr_t)trampoline_vectors_psci_smc;
|
|
}
|
|
#endif
|
|
|
|
/* Prefer CLRBHB to mitigate Spectre-BHB. */
|
|
id = READ_SPECIALREG(id_aa64isar2_el1);
|
|
if (ID_AA64ISAR2_CLRBHB(id) >= ID_AA64ISAR2_CLRBHB_IMPL)
|
|
ci->ci_trampoline_vectors = (vaddr_t)trampoline_vectors_clrbhb;
|
|
|
|
/* ECBHB tells us Spectre-BHB is mitigated. */
|
|
id = READ_SPECIALREG(id_aa64mmfr1_el1);
|
|
if (ID_AA64MMFR1_ECBHB(id) >= ID_AA64MMFR1_ECBHB_IMPL)
|
|
ci->ci_trampoline_vectors = (vaddr_t)trampoline_vectors_none;
|
|
|
|
/*
|
|
* The architecture has been updated to explicitly tell us if
|
|
* we're not vulnerable to Spectre-BHB.
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
if (ID_AA64PFR0_CSV2(id) >= ID_AA64PFR0_CSV2_HCXT)
|
|
ci->ci_trampoline_vectors = (vaddr_t)trampoline_vectors_none;
|
|
}
|
|
|
|
/*
|
|
* Enable mitigation for Spectre-V4 speculative store bypass
|
|
* vulnerabilities (CVE-2018-3639).
|
|
*/
|
|
void
|
|
cpu_mitigate_spectre_v4(struct cpu_info *ci)
|
|
{
|
|
uint64_t id;
|
|
|
|
switch (CPU_IMPL(ci->ci_midr)) {
|
|
case CPU_IMPL_ARM:
|
|
switch (CPU_PART(ci->ci_midr)) {
|
|
case CPU_PART_CORTEX_A35:
|
|
case CPU_PART_CORTEX_A53:
|
|
case CPU_PART_CORTEX_A55:
|
|
/* Not vulnerable. */
|
|
return;
|
|
}
|
|
break;
|
|
case CPU_IMPL_QCOM:
|
|
switch (CPU_PART(ci->ci_midr)) {
|
|
case CPU_PART_KRYO400_SILVER:
|
|
/* Not vulnerable. */
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* SSBS tells us Spectre-V4 is mitigated. */
|
|
id = READ_SPECIALREG(id_aa64pfr1_el1);
|
|
if (ID_AA64PFR1_SSBS(id) >= ID_AA64PFR1_SSBS_PSTATE)
|
|
return;
|
|
|
|
/* Enable firmware workaround if required. */
|
|
smccc_enable_arch_workaround_2();
|
|
}
|
|
|
|
void
|
|
cpu_identify(struct cpu_info *ci)
|
|
{
|
|
static uint64_t prev_id_aa64isar0;
|
|
static uint64_t prev_id_aa64isar1;
|
|
static uint64_t prev_id_aa64isar2;
|
|
static uint64_t prev_id_aa64mmfr0;
|
|
static uint64_t prev_id_aa64mmfr1;
|
|
static uint64_t prev_id_aa64pfr0;
|
|
static uint64_t prev_id_aa64pfr1;
|
|
uint64_t midr, impl, part;
|
|
uint64_t clidr, ccsidr, id;
|
|
uint32_t ctr, sets, ways, line;
|
|
const char *impl_name = NULL;
|
|
const char *part_name = NULL;
|
|
const char *il1p_name = NULL;
|
|
const char *sep;
|
|
struct cpu_cores *coreselecter = cpu_cores_none;
|
|
int ccidx;
|
|
int i;
|
|
|
|
midr = READ_SPECIALREG(midr_el1);
|
|
impl = CPU_IMPL(midr);
|
|
part = CPU_PART(midr);
|
|
ci->ci_midr = midr;
|
|
|
|
for (i = 0; cpu_implementers[i].name; i++) {
|
|
if (impl == cpu_implementers[i].id) {
|
|
impl_name = cpu_implementers[i].name;
|
|
coreselecter = cpu_implementers[i].corelist;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; coreselecter[i].name; i++) {
|
|
if (part == coreselecter[i].id) {
|
|
part_name = coreselecter[i].name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (impl_name && part_name) {
|
|
printf(" %s %s r%llup%llu", impl_name, part_name, CPU_VAR(midr),
|
|
CPU_REV(midr));
|
|
|
|
if (CPU_IS_PRIMARY(ci))
|
|
snprintf(cpu_model, sizeof(cpu_model),
|
|
"%s %s r%llup%llu", impl_name, part_name,
|
|
CPU_VAR(midr), CPU_REV(midr));
|
|
} else {
|
|
printf(" Unknown, MIDR 0x%llx", midr);
|
|
|
|
if (CPU_IS_PRIMARY(ci))
|
|
snprintf(cpu_model, sizeof(cpu_model), "Unknown");
|
|
}
|
|
|
|
/* Print cache information. */
|
|
|
|
ctr = READ_SPECIALREG(ctr_el0);
|
|
switch (ctr & CTR_IL1P_MASK) {
|
|
case CTR_IL1P_AIVIVT:
|
|
il1p_name = "AIVIVT ";
|
|
break;
|
|
case CTR_IL1P_VIPT:
|
|
il1p_name = "VIPT ";
|
|
break;
|
|
case CTR_IL1P_PIPT:
|
|
il1p_name = "PIPT ";
|
|
break;
|
|
}
|
|
|
|
id = READ_SPECIALREG(id_aa64mmfr2_el1);
|
|
clidr = READ_SPECIALREG(clidr_el1);
|
|
if (ID_AA64MMFR2_CCIDX(id) > ID_AA64MMFR2_CCIDX_IMPL) {
|
|
/* Reserved value. Don't print cache information. */
|
|
clidr = 0;
|
|
} else if (ID_AA64MMFR2_CCIDX(id) == ID_AA64MMFR2_CCIDX_IMPL) {
|
|
/* CCSIDR_EL1 uses the new 64-bit format. */
|
|
ccidx = 1;
|
|
} else {
|
|
/* CCSIDR_EL1 uses the old 32-bit format. */
|
|
ccidx = 0;
|
|
}
|
|
for (i = 0; i < 7; i++) {
|
|
if ((clidr & CLIDR_CTYPE_MASK) == 0)
|
|
break;
|
|
printf("\n%s:", ci->ci_dev->dv_xname);
|
|
sep = "";
|
|
if (clidr & CLIDR_CTYPE_INSN) {
|
|
WRITE_SPECIALREG(csselr_el1,
|
|
i << CSSELR_LEVEL_SHIFT | CSSELR_IND);
|
|
__asm volatile("isb");
|
|
ccsidr = READ_SPECIALREG(ccsidr_el1);
|
|
if (ccidx) {
|
|
sets = CCSIDR_CCIDX_SETS(ccsidr);
|
|
ways = CCSIDR_CCIDX_WAYS(ccsidr);
|
|
line = CCSIDR_CCIDX_LINE_SIZE(ccsidr);
|
|
} else {
|
|
sets = CCSIDR_SETS(ccsidr);
|
|
ways = CCSIDR_WAYS(ccsidr);
|
|
line = CCSIDR_LINE_SIZE(ccsidr);
|
|
}
|
|
printf("%s %dKB %db/line %d-way L%d %sI-cache", sep,
|
|
(sets * ways * line) / 1024, line, ways, (i + 1),
|
|
il1p_name);
|
|
il1p_name = "";
|
|
sep = ",";
|
|
}
|
|
if (clidr & CLIDR_CTYPE_DATA) {
|
|
WRITE_SPECIALREG(csselr_el1, i << CSSELR_LEVEL_SHIFT);
|
|
__asm volatile("isb");
|
|
ccsidr = READ_SPECIALREG(ccsidr_el1);
|
|
if (ccidx) {
|
|
sets = CCSIDR_CCIDX_SETS(ccsidr);
|
|
ways = CCSIDR_CCIDX_WAYS(ccsidr);
|
|
line = CCSIDR_CCIDX_LINE_SIZE(ccsidr);
|
|
} else {
|
|
sets = CCSIDR_SETS(ccsidr);
|
|
ways = CCSIDR_WAYS(ccsidr);
|
|
line = CCSIDR_LINE_SIZE(ccsidr);
|
|
}
|
|
printf("%s %dKB %db/line %d-way L%d D-cache", sep,
|
|
(sets * ways * line) / 1024, line, ways, (i + 1));
|
|
sep = ",";
|
|
}
|
|
if (clidr & CLIDR_CTYPE_UNIFIED) {
|
|
WRITE_SPECIALREG(csselr_el1, i << CSSELR_LEVEL_SHIFT);
|
|
__asm volatile("isb");
|
|
ccsidr = READ_SPECIALREG(ccsidr_el1);
|
|
if (ccidx) {
|
|
sets = CCSIDR_CCIDX_SETS(ccsidr);
|
|
ways = CCSIDR_CCIDX_WAYS(ccsidr);
|
|
line = CCSIDR_CCIDX_LINE_SIZE(ccsidr);
|
|
} else {
|
|
sets = CCSIDR_SETS(ccsidr);
|
|
ways = CCSIDR_WAYS(ccsidr);
|
|
line = CCSIDR_LINE_SIZE(ccsidr);
|
|
}
|
|
printf("%s %dKB %db/line %d-way L%d cache", sep,
|
|
(sets * ways * line) / 1024, line, ways, (i + 1));
|
|
}
|
|
clidr >>= 3;
|
|
}
|
|
|
|
cpu_mitigate_spectre_v2(ci);
|
|
cpu_mitigate_spectre_bhb(ci);
|
|
cpu_mitigate_spectre_v4(ci);
|
|
|
|
/*
|
|
* Apple CPUs provide detailed information for SError.
|
|
*/
|
|
if (impl == CPU_IMPL_APPLE)
|
|
ci->ci_serror = cpu_serror_apple;
|
|
|
|
/*
|
|
* Skip printing CPU features if they are identical to the
|
|
* previous CPU.
|
|
*/
|
|
if (READ_SPECIALREG(id_aa64isar0_el1) == prev_id_aa64isar0 &&
|
|
READ_SPECIALREG(id_aa64isar1_el1) == prev_id_aa64isar1 &&
|
|
READ_SPECIALREG(id_aa64isar2_el1) == prev_id_aa64isar2 &&
|
|
READ_SPECIALREG(id_aa64mmfr0_el1) == prev_id_aa64mmfr0 &&
|
|
READ_SPECIALREG(id_aa64mmfr1_el1) == prev_id_aa64mmfr1 &&
|
|
READ_SPECIALREG(id_aa64pfr0_el1) == prev_id_aa64pfr0 &&
|
|
READ_SPECIALREG(id_aa64pfr1_el1) == prev_id_aa64pfr1)
|
|
return;
|
|
|
|
/*
|
|
* Print CPU features encoded in the ID registers.
|
|
*/
|
|
|
|
if (READ_SPECIALREG(id_aa64isar0_el1) != cpu_id_aa64isar0) {
|
|
printf("\n%s: mismatched ID_AA64ISAR0_EL1",
|
|
ci->ci_dev->dv_xname);
|
|
}
|
|
if (READ_SPECIALREG(id_aa64isar1_el1) != cpu_id_aa64isar1) {
|
|
printf("\n%s: mismatched ID_AA64ISAR1_EL1",
|
|
ci->ci_dev->dv_xname);
|
|
}
|
|
if (READ_SPECIALREG(id_aa64isar2_el1) != cpu_id_aa64isar2) {
|
|
printf("\n%s: mismatched ID_AA64ISAR2_EL1",
|
|
ci->ci_dev->dv_xname);
|
|
}
|
|
id = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
/* Allow CSV2/CVS3 to be different. */
|
|
id &= ~ID_AA64PFR0_CSV2_MASK;
|
|
id &= ~ID_AA64PFR0_CSV3_MASK;
|
|
/* Ignore 32-bit support in all exception levels. */
|
|
id &= ~ID_AA64PFR0_EL0_MASK;
|
|
id &= ~ID_AA64PFR0_EL1_MASK;
|
|
id &= ~ID_AA64PFR0_EL2_MASK;
|
|
id &= ~ID_AA64PFR0_EL3_MASK;
|
|
if (id != cpu_id_aa64pfr0) {
|
|
printf("\n%s: mismatched ID_AA64PFR0_EL1",
|
|
ci->ci_dev->dv_xname);
|
|
}
|
|
if (READ_SPECIALREG(id_aa64pfr1_el1) != cpu_id_aa64pfr1) {
|
|
printf("\n%s: mismatched ID_AA64PFR1_EL1",
|
|
ci->ci_dev->dv_xname);
|
|
}
|
|
|
|
printf("\n%s: ", ci->ci_dev->dv_xname);
|
|
|
|
/*
|
|
* ID_AA64ISAR0
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64isar0_el1);
|
|
sep = "";
|
|
|
|
if (ID_AA64ISAR0_RNDR(id) >= ID_AA64ISAR0_RNDR_IMPL) {
|
|
printf("%sRNDR", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_TLB(id) >= ID_AA64ISAR0_TLB_IOS) {
|
|
printf("%sTLBIOS", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64ISAR0_TLB(id) >= ID_AA64ISAR0_TLB_IRANGE)
|
|
printf("+IRANGE");
|
|
|
|
if (ID_AA64ISAR0_TS(id) >= ID_AA64ISAR0_TS_BASE) {
|
|
printf("%sTS", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64ISAR0_TS(id) >= ID_AA64ISAR0_TS_AXFLAG)
|
|
printf("+AXFLAG");
|
|
|
|
if (ID_AA64ISAR0_FHM(id) >= ID_AA64ISAR0_FHM_IMPL) {
|
|
printf("%sFHM", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_DP(id) >= ID_AA64ISAR0_DP_IMPL) {
|
|
printf("%sDP", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_SM4(id) >= ID_AA64ISAR0_SM4_IMPL) {
|
|
printf("%sSM4", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_SM3(id) >= ID_AA64ISAR0_SM3_IMPL) {
|
|
printf("%sSM3", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_SHA3(id) >= ID_AA64ISAR0_SHA3_IMPL) {
|
|
printf("%sSHA3", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_RDM(id) >= ID_AA64ISAR0_RDM_IMPL) {
|
|
printf("%sRDM", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_ATOMIC(id) >= ID_AA64ISAR0_ATOMIC_IMPL) {
|
|
printf("%sAtomic", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_CRC32(id) >= ID_AA64ISAR0_CRC32_BASE) {
|
|
printf("%sCRC32", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_SHA2(id) >= ID_AA64ISAR0_SHA2_BASE) {
|
|
printf("%sSHA2", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64ISAR0_SHA2(id) >= ID_AA64ISAR0_SHA2_512)
|
|
printf("+SHA512");
|
|
|
|
if (ID_AA64ISAR0_SHA1(id) >= ID_AA64ISAR0_SHA1_BASE) {
|
|
printf("%sSHA1", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR0_AES(id) >= ID_AA64ISAR0_AES_BASE) {
|
|
printf("%sAES", sep);
|
|
sep = ",";
|
|
#ifdef CRYPTO
|
|
arm64_has_aes = 1;
|
|
#endif
|
|
}
|
|
if (ID_AA64ISAR0_AES(id) >= ID_AA64ISAR0_AES_PMULL)
|
|
printf("+PMULL");
|
|
|
|
/*
|
|
* ID_AA64ISAR1
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64isar1_el1);
|
|
|
|
if (ID_AA64ISAR1_SPECRES(id) >= ID_AA64ISAR1_SPECRES_IMPL) {
|
|
printf("%sSPECRES", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR1_SB(id) >= ID_AA64ISAR1_SB_IMPL) {
|
|
printf("%sSB", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR1_FRINTTS(id) >= ID_AA64ISAR1_FRINTTS_IMPL) {
|
|
printf("%sFRINTTS", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR1_GPI(id) >= ID_AA64ISAR1_GPI_IMPL) {
|
|
printf("%sGPI", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR1_GPA(id) >= ID_AA64ISAR1_GPA_IMPL) {
|
|
printf("%sGPA", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR1_LRCPC(id) >= ID_AA64ISAR1_LRCPC_BASE) {
|
|
printf("%sLRCPC", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64ISAR1_LRCPC(id) >= ID_AA64ISAR1_LRCPC_LDAPUR)
|
|
printf("+LDAPUR");
|
|
|
|
if (ID_AA64ISAR1_FCMA(id) >= ID_AA64ISAR1_FCMA_IMPL) {
|
|
printf("%sFCMA", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR1_JSCVT(id) >= ID_AA64ISAR1_JSCVT_IMPL) {
|
|
printf("%sJSCVT", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64ISAR1_API(id) >= ID_AA64ISAR1_API_BASE) {
|
|
printf("%sAPI", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64ISAR1_API(id) >= ID_AA64ISAR1_API_PAC)
|
|
printf("+PAC");
|
|
|
|
if (ID_AA64ISAR1_APA(id) >= ID_AA64ISAR1_APA_BASE) {
|
|
printf("%sAPA", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64ISAR1_APA(id) >= ID_AA64ISAR1_APA_PAC)
|
|
printf("+PAC");
|
|
|
|
if (ID_AA64ISAR1_DPB(id) >= ID_AA64ISAR1_DPB_IMPL) {
|
|
printf("%sDPB", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
/*
|
|
* ID_AA64ISAR2
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64isar2_el1);
|
|
|
|
if (ID_AA64ISAR2_CLRBHB(id) >= ID_AA64ISAR2_CLRBHB_IMPL) {
|
|
printf("%sCLRBHB", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
/*
|
|
* ID_AA64MMFR0
|
|
*
|
|
* We only print ASIDBits for now.
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64mmfr0_el1);
|
|
|
|
if (ID_AA64MMFR0_ASID_BITS(id) == ID_AA64MMFR0_ASID_BITS_16) {
|
|
printf("%sASID16", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
/*
|
|
* ID_AA64MMFR1
|
|
*
|
|
* We omit printing most virtualization related fields for now.
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64mmfr1_el1);
|
|
|
|
if (ID_AA64MMFR1_SPECSEI(id) >= ID_AA64MMFR1_SPECSEI_IMPL) {
|
|
printf("%sSpecSEI", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64MMFR1_PAN(id) >= ID_AA64MMFR1_PAN_IMPL) {
|
|
printf("%sPAN", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64MMFR1_PAN(id) >= ID_AA64MMFR1_PAN_ATS1E1)
|
|
printf("+ATS1E1");
|
|
if (ID_AA64MMFR1_PAN(id) >= ID_AA64MMFR1_PAN_EPAN)
|
|
printf("+EPAN");
|
|
|
|
if (ID_AA64MMFR1_LO(id) >= ID_AA64MMFR1_LO_IMPL) {
|
|
printf("%sLO", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64MMFR1_HPDS(id) >= ID_AA64MMFR1_HPDS_IMPL) {
|
|
printf("%sHPDS", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64MMFR1_VH(id) >= ID_AA64MMFR1_VH_IMPL) {
|
|
printf("%sVH", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64MMFR1_HAFDBS(id) >= ID_AA64MMFR1_HAFDBS_AF) {
|
|
printf("%sHAF", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64MMFR1_HAFDBS(id) >= ID_AA64MMFR1_HAFDBS_AF_DBS)
|
|
printf("DBS");
|
|
|
|
if (ID_AA64MMFR1_ECBHB(id) >= ID_AA64MMFR1_ECBHB_IMPL) {
|
|
printf("%sECBHB", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
/*
|
|
* ID_AA64PFR0
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
|
|
if (ID_AA64PFR0_CSV3(id) >= ID_AA64PFR0_CSV3_IMPL) {
|
|
printf("%sCSV3", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64PFR0_CSV2(id) >= ID_AA64PFR0_CSV2_IMPL) {
|
|
printf("%sCSV2", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64PFR0_CSV2(id) >= ID_AA64PFR0_CSV2_SCXT)
|
|
printf("+SCXT");
|
|
if (ID_AA64PFR0_CSV2(id) >= ID_AA64PFR0_CSV2_HCXT)
|
|
printf("+HCXT");
|
|
|
|
if (ID_AA64PFR0_DIT(id) >= ID_AA64PFR0_DIT_IMPL) {
|
|
printf("%sDIT", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
/*
|
|
* ID_AA64PFR1
|
|
*/
|
|
id = READ_SPECIALREG(id_aa64pfr1_el1);
|
|
|
|
if (ID_AA64PFR1_BT(id) >= ID_AA64PFR1_BT_IMPL) {
|
|
printf("%sBT", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
if (ID_AA64PFR1_SSBS(id) >= ID_AA64PFR1_SSBS_PSTATE) {
|
|
printf("%sSSBS", sep);
|
|
sep = ",";
|
|
}
|
|
if (ID_AA64PFR1_SSBS(id) >= ID_AA64PFR1_SSBS_PSTATE_MSR)
|
|
printf("+MSR");
|
|
|
|
if (ID_AA64PFR1_MTE(id) >= ID_AA64PFR1_MTE_IMPL) {
|
|
printf("%sMTE", sep);
|
|
sep = ",";
|
|
}
|
|
|
|
prev_id_aa64isar0 = READ_SPECIALREG(id_aa64isar0_el1);
|
|
prev_id_aa64isar1 = READ_SPECIALREG(id_aa64isar1_el1);
|
|
prev_id_aa64isar2 = READ_SPECIALREG(id_aa64isar2_el1);
|
|
prev_id_aa64mmfr0 = READ_SPECIALREG(id_aa64mmfr0_el1);
|
|
prev_id_aa64mmfr1 = READ_SPECIALREG(id_aa64mmfr1_el1);
|
|
prev_id_aa64pfr0 = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
prev_id_aa64pfr1 = READ_SPECIALREG(id_aa64pfr1_el1);
|
|
|
|
#ifdef CPU_DEBUG
|
|
id = READ_SPECIALREG(id_aa64afr0_el1);
|
|
printf("\nID_AA64AFR0_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64afr1_el1);
|
|
printf("\nID_AA64AFR1_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64dfr0_el1);
|
|
printf("\nID_AA64DFR0_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64dfr1_el1);
|
|
printf("\nID_AA64DFR1_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64isar0_el1);
|
|
printf("\nID_AA64ISAR0_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64isar1_el1);
|
|
printf("\nID_AA64ISAR1_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64isar2_el1);
|
|
printf("\nID_AA64ISAR2_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64mmfr0_el1);
|
|
printf("\nID_AA64MMFR0_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64mmfr1_el1);
|
|
printf("\nID_AA64MMFR1_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64mmfr2_el1);
|
|
printf("\nID_AA64MMFR2_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
printf("\nID_AA64PFR0_EL1: 0x%016llx", id);
|
|
id = READ_SPECIALREG(id_aa64pfr1_el1);
|
|
printf("\nID_AA64PFR1_EL1: 0x%016llx", id);
|
|
#endif
|
|
}
|
|
|
|
void cpu_init(void);
|
|
int cpu_start_secondary(struct cpu_info *ci, int, uint64_t);
|
|
int cpu_clockspeed(int *);
|
|
|
|
int
|
|
cpu_match(struct device *parent, void *cfdata, void *aux)
|
|
{
|
|
struct fdt_attach_args *faa = aux;
|
|
uint64_t mpidr = READ_SPECIALREG(mpidr_el1);
|
|
char buf[32];
|
|
|
|
if (OF_getprop(faa->fa_node, "device_type", buf, sizeof(buf)) <= 0 ||
|
|
strcmp(buf, "cpu") != 0)
|
|
return 0;
|
|
|
|
if (ncpus < MAXCPUS || faa->fa_reg[0].addr == (mpidr & MPIDR_AFF))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
cpu_attach(struct device *parent, struct device *dev, void *aux)
|
|
{
|
|
struct fdt_attach_args *faa = aux;
|
|
struct cpu_info *ci;
|
|
void *kstack;
|
|
#ifdef MULTIPROCESSOR
|
|
uint64_t mpidr = READ_SPECIALREG(mpidr_el1);
|
|
#endif
|
|
uint32_t opp;
|
|
|
|
KASSERT(faa->fa_nreg > 0);
|
|
|
|
#ifdef MULTIPROCESSOR
|
|
if (faa->fa_reg[0].addr == (mpidr & MPIDR_AFF)) {
|
|
ci = &cpu_info_primary;
|
|
ci->ci_flags |= CPUF_RUNNING | CPUF_PRESENT | CPUF_PRIMARY;
|
|
} else {
|
|
ci = malloc(sizeof(*ci), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
cpu_info[dev->dv_unit] = ci;
|
|
ci->ci_next = cpu_info_list->ci_next;
|
|
cpu_info_list->ci_next = ci;
|
|
ci->ci_flags |= CPUF_AP;
|
|
ncpus++;
|
|
}
|
|
#else
|
|
ci = &cpu_info_primary;
|
|
#endif
|
|
|
|
ci->ci_dev = dev;
|
|
ci->ci_cpuid = dev->dv_unit;
|
|
ci->ci_mpidr = faa->fa_reg[0].addr;
|
|
ci->ci_node = faa->fa_node;
|
|
ci->ci_self = ci;
|
|
|
|
printf(" mpidr %llx:", ci->ci_mpidr);
|
|
|
|
kstack = km_alloc(USPACE, &kv_any, &kp_zero, &kd_waitok);
|
|
ci->ci_el1_stkend = (vaddr_t)kstack + USPACE - 16;
|
|
ci->ci_trampoline_vectors = (vaddr_t)trampoline_vectors_none;
|
|
|
|
#ifdef MULTIPROCESSOR
|
|
if (ci->ci_flags & CPUF_AP) {
|
|
char buf[32];
|
|
uint64_t spinup_data = 0;
|
|
int spinup_method = 0;
|
|
int timeout = 10000;
|
|
int len;
|
|
|
|
len = OF_getprop(ci->ci_node, "enable-method",
|
|
buf, sizeof(buf));
|
|
if (strcmp(buf, "psci") == 0) {
|
|
spinup_method = 1;
|
|
} else if (strcmp(buf, "spin-table") == 0) {
|
|
spinup_method = 2;
|
|
spinup_data = OF_getpropint64(ci->ci_node,
|
|
"cpu-release-addr", 0);
|
|
}
|
|
|
|
clockqueue_init(&ci->ci_queue);
|
|
sched_init_cpu(ci);
|
|
if (cpu_start_secondary(ci, spinup_method, spinup_data)) {
|
|
atomic_setbits_int(&ci->ci_flags, CPUF_IDENTIFY);
|
|
__asm volatile("dsb sy; sev" ::: "memory");
|
|
|
|
while ((ci->ci_flags & CPUF_IDENTIFIED) == 0 &&
|
|
--timeout)
|
|
delay(1000);
|
|
if (timeout == 0) {
|
|
printf(" failed to identify");
|
|
ci->ci_flags = 0;
|
|
}
|
|
} else {
|
|
printf(" failed to spin up");
|
|
ci->ci_flags = 0;
|
|
}
|
|
} else {
|
|
#endif
|
|
cpu_id_aa64isar0 = READ_SPECIALREG(id_aa64isar0_el1);
|
|
cpu_id_aa64isar1 = READ_SPECIALREG(id_aa64isar1_el1);
|
|
cpu_id_aa64isar2 = READ_SPECIALREG(id_aa64isar2_el1);
|
|
cpu_id_aa64pfr0 = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
cpu_id_aa64pfr1 = READ_SPECIALREG(id_aa64pfr1_el1);
|
|
|
|
/*
|
|
* The CSV2/CSV3 "features" are handled on a
|
|
* per-processor basis. So it is fine if these fields
|
|
* differ between CPU cores. Mask off these fields to
|
|
* prevent exporting these to userland.
|
|
*/
|
|
cpu_id_aa64pfr0 &= ~ID_AA64PFR0_CSV2_MASK;
|
|
cpu_id_aa64pfr0 &= ~ID_AA64PFR0_CSV3_MASK;
|
|
|
|
/*
|
|
* We only support 64-bit mode, so we don't care about
|
|
* differences in support for 32-bit mode between
|
|
* cores. Mask off these fields as well.
|
|
*/
|
|
cpu_id_aa64pfr0 &= ~ID_AA64PFR0_EL0_MASK;
|
|
cpu_id_aa64pfr0 &= ~ID_AA64PFR0_EL1_MASK;
|
|
cpu_id_aa64pfr0 &= ~ID_AA64PFR0_EL2_MASK;
|
|
cpu_id_aa64pfr0 &= ~ID_AA64PFR0_EL3_MASK;
|
|
|
|
/*
|
|
* Lenovo X13s ships with broken EL2 firmware that
|
|
* hangs the machine if we enable PAuth.
|
|
*/
|
|
if (hw_vendor && hw_prod && strcmp(hw_vendor, "LENOVO") == 0) {
|
|
if (strncmp(hw_prod, "21BX", 4) == 0 ||
|
|
strncmp(hw_prod, "21BY", 4) == 0) {
|
|
cpu_id_aa64isar1 &= ~ID_AA64ISAR1_APA_MASK;
|
|
cpu_id_aa64isar1 &= ~ID_AA64ISAR1_GPA_MASK;
|
|
}
|
|
}
|
|
|
|
cpu_identify(ci);
|
|
|
|
if (OF_getproplen(ci->ci_node, "clocks") > 0) {
|
|
cpu_node = ci->ci_node;
|
|
cpu_cpuspeed = cpu_clockspeed;
|
|
}
|
|
|
|
cpu_init();
|
|
#ifdef MULTIPROCESSOR
|
|
}
|
|
#endif
|
|
|
|
#if NKSTAT > 0
|
|
cpu_kstat_attach(ci);
|
|
#endif
|
|
|
|
opp = OF_getpropint(ci->ci_node, "operating-points-v2", 0);
|
|
if (opp)
|
|
cpu_opp_init(ci, opp);
|
|
|
|
cpu_psci_init(ci);
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
void
|
|
cpu_init(void)
|
|
{
|
|
uint64_t id_aa64mmfr1, sctlr;
|
|
uint64_t id_aa64pfr0;
|
|
uint64_t tcr;
|
|
|
|
WRITE_SPECIALREG(ttbr0_el1, pmap_kernel()->pm_pt0pa);
|
|
__asm volatile("isb");
|
|
tcr = READ_SPECIALREG(tcr_el1);
|
|
tcr &= ~TCR_T0SZ(0x3f);
|
|
tcr |= TCR_T0SZ(64 - USER_SPACE_BITS);
|
|
tcr |= TCR_A1;
|
|
WRITE_SPECIALREG(tcr_el1, tcr);
|
|
cpu_tlb_flush();
|
|
|
|
/* Enable PAN. */
|
|
id_aa64mmfr1 = READ_SPECIALREG(id_aa64mmfr1_el1);
|
|
if (ID_AA64MMFR1_PAN(id_aa64mmfr1) >= ID_AA64MMFR1_PAN_IMPL) {
|
|
sctlr = READ_SPECIALREG(sctlr_el1);
|
|
sctlr &= ~SCTLR_SPAN;
|
|
WRITE_SPECIALREG(sctlr_el1, sctlr);
|
|
}
|
|
|
|
/* Enable DIT. */
|
|
id_aa64pfr0 = READ_SPECIALREG(id_aa64pfr0_el1);
|
|
if (ID_AA64PFR0_DIT(id_aa64pfr0) >= ID_AA64PFR0_DIT_IMPL)
|
|
__asm volatile (".arch armv8.4-a; msr dit, #1");
|
|
|
|
/* Enable PAuth. */
|
|
if (ID_AA64ISAR1_APA(cpu_id_aa64isar1) >= ID_AA64ISAR1_APA_BASE ||
|
|
ID_AA64ISAR1_API(cpu_id_aa64isar1) >= ID_AA64ISAR1_API_BASE) {
|
|
sctlr = READ_SPECIALREG(sctlr_el1);
|
|
sctlr |= SCTLR_EnIA | SCTLR_EnDA;
|
|
sctlr |= SCTLR_EnIB | SCTLR_EnDB;
|
|
WRITE_SPECIALREG(sctlr_el1, sctlr);
|
|
}
|
|
|
|
/* Enable strict BTI compatibility for PACIASP and PACIBSP. */
|
|
if (ID_AA64PFR1_BT(cpu_id_aa64pfr1) >= ID_AA64PFR1_BT_IMPL) {
|
|
sctlr = READ_SPECIALREG(sctlr_el1);
|
|
sctlr |= SCTLR_BT0 | SCTLR_BT1;
|
|
WRITE_SPECIALREG(sctlr_el1, sctlr);
|
|
}
|
|
|
|
/* Initialize debug registers. */
|
|
WRITE_SPECIALREG(mdscr_el1, DBG_MDSCR_TDCC);
|
|
WRITE_SPECIALREG(oslar_el1, 0);
|
|
}
|
|
|
|
void
|
|
cpu_flush_bp_noop(void)
|
|
{
|
|
}
|
|
|
|
void
|
|
cpu_flush_bp_psci(void)
|
|
{
|
|
#if NPSCI > 0
|
|
psci_flush_bp();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
cpu_serror_apple(void)
|
|
{
|
|
__asm volatile("dsb sy; isb" ::: "memory");
|
|
printf("l2c_err_sts 0x%llx\n", READ_SPECIALREG(s3_3_c15_c8_0));
|
|
printf("l2c_err_adr 0x%llx\n", READ_SPECIALREG(s3_3_c15_c9_0));
|
|
printf("l2c_err_inf 0x%llx\n", READ_SPECIALREG(s3_3_c15_c10_0));
|
|
}
|
|
|
|
int
|
|
cpu_clockspeed(int *freq)
|
|
{
|
|
*freq = clock_get_frequency(cpu_node, NULL) / 1000000;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MULTIPROCESSOR
|
|
|
|
void cpu_boot_secondary(struct cpu_info *ci);
|
|
void cpu_hatch_secondary(void);
|
|
void cpu_hatch_secondary_spin(void);
|
|
|
|
void cpu_suspend_cycle(void);
|
|
|
|
void
|
|
cpu_boot_secondary_processors(void)
|
|
{
|
|
struct cpu_info *ci;
|
|
CPU_INFO_ITERATOR cii;
|
|
|
|
CPU_INFO_FOREACH(cii, ci) {
|
|
if ((ci->ci_flags & CPUF_AP) == 0)
|
|
continue;
|
|
if (ci->ci_flags & CPUF_PRIMARY)
|
|
continue;
|
|
|
|
ci->ci_randseed = (arc4random() & 0x7fffffff) + 1;
|
|
cpu_boot_secondary(ci);
|
|
}
|
|
}
|
|
|
|
void
|
|
cpu_start_spin_table(struct cpu_info *ci, uint64_t start, uint64_t data)
|
|
{
|
|
extern paddr_t cpu_hatch_ci;
|
|
|
|
pmap_extract(pmap_kernel(), (vaddr_t)ci, &cpu_hatch_ci);
|
|
cpu_dcache_wb_range((vaddr_t)&cpu_hatch_ci, sizeof(paddr_t));
|
|
|
|
/* this reuses the zero page for the core */
|
|
vaddr_t start_pg = zero_page + (PAGE_SIZE * ci->ci_cpuid);
|
|
paddr_t pa = trunc_page(data);
|
|
uint64_t offset = data - pa;
|
|
uint64_t *startvec = (uint64_t *)(start_pg + offset);
|
|
|
|
pmap_kenter_cache(start_pg, pa, PROT_READ|PROT_WRITE, PMAP_CACHE_CI);
|
|
|
|
*startvec = start;
|
|
__asm volatile("dsb sy; sev" ::: "memory");
|
|
|
|
pmap_kremove(start_pg, PAGE_SIZE);
|
|
}
|
|
|
|
int
|
|
cpu_start_secondary(struct cpu_info *ci, int method, uint64_t data)
|
|
{
|
|
vaddr_t start_va;
|
|
paddr_t ci_pa, start_pa;
|
|
uint64_t ttbr1;
|
|
int32_t status;
|
|
|
|
__asm("mrs %x0, ttbr1_el1": "=r"(ttbr1));
|
|
ci->ci_ttbr1 = ttbr1;
|
|
cpu_dcache_wb_range((vaddr_t)ci, sizeof(*ci));
|
|
|
|
switch (method) {
|
|
#if NPSCI > 0
|
|
case 1:
|
|
/* psci */
|
|
start_va = (vaddr_t)cpu_hatch_secondary;
|
|
pmap_extract(pmap_kernel(), start_va, &start_pa);
|
|
pmap_extract(pmap_kernel(), (vaddr_t)ci, &ci_pa);
|
|
status = psci_cpu_on(ci->ci_mpidr, start_pa, ci_pa);
|
|
return (status == PSCI_SUCCESS);
|
|
#endif
|
|
case 2:
|
|
/* spin-table */
|
|
start_va = (vaddr_t)cpu_hatch_secondary_spin;
|
|
pmap_extract(pmap_kernel(), start_va, &start_pa);
|
|
cpu_start_spin_table(ci, start_pa, data);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
cpu_boot_secondary(struct cpu_info *ci)
|
|
{
|
|
atomic_setbits_int(&ci->ci_flags, CPUF_GO);
|
|
__asm volatile("dsb sy; sev" ::: "memory");
|
|
|
|
/*
|
|
* Send an interrupt as well to make sure the CPU wakes up
|
|
* regardless of whether it is in a WFE or a WFI loop.
|
|
*/
|
|
arm_send_ipi(ci, ARM_IPI_NOP);
|
|
|
|
while ((ci->ci_flags & CPUF_RUNNING) == 0)
|
|
__asm volatile("wfe");
|
|
}
|
|
|
|
void
|
|
cpu_init_secondary(struct cpu_info *ci)
|
|
{
|
|
struct proc *p;
|
|
struct pcb *pcb;
|
|
struct trapframe *tf;
|
|
struct switchframe *sf;
|
|
int s;
|
|
|
|
ci->ci_flags |= CPUF_PRESENT;
|
|
__asm volatile("dsb sy" ::: "memory");
|
|
|
|
if ((ci->ci_flags & CPUF_IDENTIFIED) == 0) {
|
|
while ((ci->ci_flags & CPUF_IDENTIFY) == 0)
|
|
__asm volatile("wfe");
|
|
|
|
cpu_identify(ci);
|
|
atomic_setbits_int(&ci->ci_flags, CPUF_IDENTIFIED);
|
|
__asm volatile("dsb sy" ::: "memory");
|
|
}
|
|
|
|
while ((ci->ci_flags & CPUF_GO) == 0)
|
|
__asm volatile("wfe");
|
|
|
|
cpu_init();
|
|
|
|
/*
|
|
* Start from a clean slate regardless of whether this is the
|
|
* initial power up or a wakeup of a suspended CPU.
|
|
*/
|
|
|
|
ci->ci_curproc = NULL;
|
|
ci->ci_curpcb = NULL;
|
|
ci->ci_curpm = NULL;
|
|
ci->ci_cpl = IPL_NONE;
|
|
ci->ci_ipending = 0;
|
|
ci->ci_idepth = 0;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
ci->ci_mutex_level = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Re-create the switchframe for this CPUs idle process.
|
|
*/
|
|
|
|
p = ci->ci_schedstate.spc_idleproc;
|
|
pcb = &p->p_addr->u_pcb;
|
|
|
|
tf = (struct trapframe *)((u_long)p->p_addr
|
|
+ USPACE
|
|
- sizeof(struct trapframe)
|
|
- 0x10);
|
|
|
|
tf = (struct trapframe *)STACKALIGN(tf);
|
|
pcb->pcb_tf = tf;
|
|
|
|
sf = (struct switchframe *)tf - 1;
|
|
sf->sf_x19 = (uint64_t)sched_idle;
|
|
sf->sf_x20 = (uint64_t)ci;
|
|
sf->sf_lr = (uint64_t)proc_trampoline;
|
|
pcb->pcb_sp = (uint64_t)sf;
|
|
|
|
s = splhigh();
|
|
arm_intr_cpu_enable();
|
|
cpu_startclock();
|
|
|
|
atomic_setbits_int(&ci->ci_flags, CPUF_RUNNING);
|
|
__asm volatile("dsb sy; sev" ::: "memory");
|
|
|
|
spllower(IPL_NONE);
|
|
|
|
sched_toidle();
|
|
}
|
|
|
|
void
|
|
cpu_halt(void)
|
|
{
|
|
struct cpu_info *ci = curcpu();
|
|
vaddr_t start_va;
|
|
paddr_t ci_pa, start_pa;
|
|
int count = 0;
|
|
u_long psw;
|
|
int32_t status;
|
|
|
|
KERNEL_ASSERT_UNLOCKED();
|
|
SCHED_ASSERT_UNLOCKED();
|
|
|
|
start_va = (vaddr_t)cpu_hatch_secondary;
|
|
pmap_extract(pmap_kernel(), start_va, &start_pa);
|
|
pmap_extract(pmap_kernel(), (vaddr_t)ci, &ci_pa);
|
|
|
|
psw = intr_disable();
|
|
|
|
atomic_clearbits_int(&ci->ci_flags,
|
|
CPUF_RUNNING | CPUF_PRESENT | CPUF_GO);
|
|
|
|
#if NPSCI > 0
|
|
if (psci_can_suspend())
|
|
psci_cpu_off();
|
|
#endif
|
|
|
|
/*
|
|
* If we failed to turn ourselves off using PSCI, declare that
|
|
* we're still present and spin in a low power state until
|
|
* we're told to wake up again by the primary CPU.
|
|
*/
|
|
|
|
atomic_setbits_int(&ci->ci_flags, CPUF_PRESENT);
|
|
|
|
/* Mask clock interrupts. */
|
|
WRITE_SPECIALREG(cntv_ctl_el0,
|
|
READ_SPECIALREG(cntv_ctl_el0) | CNTV_CTL_IMASK);
|
|
|
|
while ((ci->ci_flags & CPUF_GO) == 0) {
|
|
#if NPSCI > 0
|
|
if (ci->ci_psci_suspend_param) {
|
|
status = psci_cpu_suspend(ci->ci_psci_suspend_param,
|
|
start_pa, ci_pa);
|
|
if (status != PSCI_SUCCESS)
|
|
ci->ci_psci_suspend_param = 0;
|
|
} else
|
|
#endif
|
|
cpu_suspend_cycle();
|
|
count++;
|
|
}
|
|
|
|
atomic_setbits_int(&ci->ci_flags, CPUF_RUNNING);
|
|
__asm volatile("dsb sy; sev" ::: "memory");
|
|
|
|
intr_restore(psw);
|
|
|
|
/* Unmask clock interrupts. */
|
|
WRITE_SPECIALREG(cntv_ctl_el0,
|
|
READ_SPECIALREG(cntv_ctl_el0) & ~CNTV_CTL_IMASK);
|
|
}
|
|
|
|
void
|
|
cpu_kick(struct cpu_info *ci)
|
|
{
|
|
/* force cpu to enter kernel */
|
|
if (ci != curcpu())
|
|
arm_send_ipi(ci, ARM_IPI_NOP);
|
|
}
|
|
|
|
void
|
|
cpu_unidle(struct cpu_info *ci)
|
|
{
|
|
/*
|
|
* This could send IPI or SEV depending on if the other
|
|
* processor is sleeping (WFI or WFE), in userland, or if the
|
|
* cpu is in other possible wait states?
|
|
*/
|
|
if (ci != curcpu())
|
|
arm_send_ipi(ci, ARM_IPI_NOP);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SUSPEND
|
|
|
|
void cpu_hatch_primary(void);
|
|
|
|
void (*cpu_suspend_cycle_fcn)(void) = cpu_wfi;
|
|
label_t cpu_suspend_jmpbuf;
|
|
int cpu_suspended;
|
|
|
|
void
|
|
cpu_suspend_cycle(void)
|
|
{
|
|
cpu_suspend_cycle_fcn();
|
|
}
|
|
|
|
void
|
|
cpu_init_primary(void)
|
|
{
|
|
cpu_init();
|
|
|
|
cpu_startclock();
|
|
|
|
longjmp(&cpu_suspend_jmpbuf);
|
|
}
|
|
|
|
int
|
|
cpu_suspend_primary(void)
|
|
{
|
|
struct cpu_info *ci = curcpu();
|
|
vaddr_t start_va;
|
|
paddr_t ci_pa, start_pa;
|
|
uint64_t ttbr1;
|
|
int32_t status;
|
|
int count = 0;
|
|
|
|
__asm("mrs %x0, ttbr1_el1": "=r"(ttbr1));
|
|
ci->ci_ttbr1 = ttbr1;
|
|
cpu_dcache_wb_range((vaddr_t)ci, sizeof(*ci));
|
|
|
|
start_va = (vaddr_t)cpu_hatch_primary;
|
|
pmap_extract(pmap_kernel(), start_va, &start_pa);
|
|
pmap_extract(pmap_kernel(), (vaddr_t)ci, &ci_pa);
|
|
|
|
#if NPSCI > 0
|
|
if (psci_can_suspend()) {
|
|
if (setjmp(&cpu_suspend_jmpbuf)) {
|
|
/* XXX wait for debug output on Allwinner A64 */
|
|
delay(200000);
|
|
return 0;
|
|
}
|
|
|
|
psci_system_suspend(start_pa, ci_pa);
|
|
|
|
return EOPNOTSUPP;
|
|
}
|
|
#endif
|
|
|
|
if (setjmp(&cpu_suspend_jmpbuf))
|
|
goto resume;
|
|
|
|
/*
|
|
* If PSCI doesn't support SYSTEM_SUSPEND, spin in a low power
|
|
* state waiting for an interrupt that wakes us up again.
|
|
*/
|
|
|
|
/* Mask clock interrupts. */
|
|
WRITE_SPECIALREG(cntv_ctl_el0,
|
|
READ_SPECIALREG(cntv_ctl_el0) | CNTV_CTL_IMASK);
|
|
|
|
/*
|
|
* All non-wakeup interrupts should be masked at this point;
|
|
* re-enable interrupts such that wakeup interrupts actually
|
|
* wake us up. Set a flag such that drivers can tell we're
|
|
* suspended and change their behaviour accordingly. They can
|
|
* wake us up by clearing the flag.
|
|
*/
|
|
cpu_suspended = 1;
|
|
intr_enable_wakeup();
|
|
|
|
while (cpu_suspended) {
|
|
#if NPSCI > 0
|
|
if (ci->ci_psci_suspend_param) {
|
|
status = psci_cpu_suspend(ci->ci_psci_suspend_param,
|
|
start_pa, ci_pa);
|
|
if (status != PSCI_SUCCESS)
|
|
ci->ci_psci_suspend_param = 0;
|
|
} else
|
|
#endif
|
|
cpu_suspend_cycle();
|
|
count++;
|
|
}
|
|
|
|
resume:
|
|
intr_disable_wakeup();
|
|
|
|
/* Unmask clock interrupts. */
|
|
WRITE_SPECIALREG(cntv_ctl_el0,
|
|
READ_SPECIALREG(cntv_ctl_el0) & ~CNTV_CTL_IMASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MULTIPROCESSOR
|
|
|
|
void
|
|
cpu_resume_secondary(struct cpu_info *ci)
|
|
{
|
|
int timeout = 10000;
|
|
|
|
if (ci->ci_flags & CPUF_PRESENT)
|
|
return;
|
|
|
|
cpu_start_secondary(ci, 1, 0);
|
|
while ((ci->ci_flags & CPUF_PRESENT) == 0 && --timeout)
|
|
delay(1000);
|
|
if (timeout == 0) {
|
|
printf("%s: failed to spin up\n",
|
|
ci->ci_dev->dv_xname);
|
|
ci->ci_flags = 0;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Dynamic voltage and frequency scaling implementation.
|
|
*/
|
|
|
|
extern int perflevel;
|
|
|
|
struct opp {
|
|
uint64_t opp_hz;
|
|
uint32_t opp_microvolt;
|
|
};
|
|
|
|
struct opp_table {
|
|
LIST_ENTRY(opp_table) ot_list;
|
|
uint32_t ot_phandle;
|
|
|
|
struct opp *ot_opp;
|
|
u_int ot_nopp;
|
|
uint64_t ot_opp_hz_min;
|
|
uint64_t ot_opp_hz_max;
|
|
|
|
struct cpu_info *ot_master;
|
|
};
|
|
|
|
LIST_HEAD(, opp_table) opp_tables = LIST_HEAD_INITIALIZER(opp_tables);
|
|
struct task cpu_opp_task;
|
|
|
|
void cpu_opp_mountroot(struct device *);
|
|
void cpu_opp_dotask(void *);
|
|
void cpu_opp_setperf(int);
|
|
|
|
uint32_t cpu_opp_get_cooling_level(void *, uint32_t *);
|
|
void cpu_opp_set_cooling_level(void *, uint32_t *, uint32_t);
|
|
|
|
void
|
|
cpu_opp_init(struct cpu_info *ci, uint32_t phandle)
|
|
{
|
|
struct opp_table *ot;
|
|
struct cooling_device *cd;
|
|
int count, node, child;
|
|
uint32_t opp_hz, opp_microvolt;
|
|
uint32_t values[3];
|
|
int i, j, len;
|
|
|
|
LIST_FOREACH(ot, &opp_tables, ot_list) {
|
|
if (ot->ot_phandle == phandle) {
|
|
ci->ci_opp_table = ot;
|
|
return;
|
|
}
|
|
}
|
|
|
|
node = OF_getnodebyphandle(phandle);
|
|
if (node == 0)
|
|
return;
|
|
|
|
if (!OF_is_compatible(node, "operating-points-v2"))
|
|
return;
|
|
|
|
count = 0;
|
|
for (child = OF_child(node); child != 0; child = OF_peer(child)) {
|
|
if (OF_getproplen(child, "turbo-mode") == 0)
|
|
continue;
|
|
count++;
|
|
}
|
|
if (count == 0)
|
|
return;
|
|
|
|
ot = malloc(sizeof(struct opp_table), M_DEVBUF, M_ZERO | M_WAITOK);
|
|
ot->ot_phandle = phandle;
|
|
ot->ot_opp = mallocarray(count, sizeof(struct opp),
|
|
M_DEVBUF, M_ZERO | M_WAITOK);
|
|
ot->ot_nopp = count;
|
|
|
|
count = 0;
|
|
for (child = OF_child(node); child != 0; child = OF_peer(child)) {
|
|
if (OF_getproplen(child, "turbo-mode") == 0)
|
|
continue;
|
|
opp_hz = OF_getpropint64(child, "opp-hz", 0);
|
|
len = OF_getpropintarray(child, "opp-microvolt",
|
|
values, sizeof(values));
|
|
opp_microvolt = 0;
|
|
if (len == sizeof(uint32_t) || len == 3 * sizeof(uint32_t))
|
|
opp_microvolt = values[0];
|
|
|
|
/* Insert into the array, keeping things sorted. */
|
|
for (i = 0; i < count; i++) {
|
|
if (opp_hz < ot->ot_opp[i].opp_hz)
|
|
break;
|
|
}
|
|
for (j = count; j > i; j--)
|
|
ot->ot_opp[j] = ot->ot_opp[j - 1];
|
|
ot->ot_opp[i].opp_hz = opp_hz;
|
|
ot->ot_opp[i].opp_microvolt = opp_microvolt;
|
|
count++;
|
|
}
|
|
|
|
ot->ot_opp_hz_min = ot->ot_opp[0].opp_hz;
|
|
ot->ot_opp_hz_max = ot->ot_opp[count - 1].opp_hz;
|
|
|
|
if (OF_getproplen(node, "opp-shared") == 0)
|
|
ot->ot_master = ci;
|
|
|
|
LIST_INSERT_HEAD(&opp_tables, ot, ot_list);
|
|
|
|
ci->ci_opp_table = ot;
|
|
ci->ci_opp_max = ot->ot_nopp - 1;
|
|
ci->ci_cpu_supply = OF_getpropint(ci->ci_node, "cpu-supply", 0);
|
|
|
|
cd = malloc(sizeof(struct cooling_device), M_DEVBUF, M_ZERO | M_WAITOK);
|
|
cd->cd_node = ci->ci_node;
|
|
cd->cd_cookie = ci;
|
|
cd->cd_get_level = cpu_opp_get_cooling_level;
|
|
cd->cd_set_level = cpu_opp_set_cooling_level;
|
|
cooling_device_register(cd);
|
|
|
|
/*
|
|
* Do additional checks at mountroot when all the clocks and
|
|
* regulators are available.
|
|
*/
|
|
config_mountroot(ci->ci_dev, cpu_opp_mountroot);
|
|
}
|
|
|
|
void
|
|
cpu_opp_mountroot(struct device *self)
|
|
{
|
|
struct cpu_info *ci;
|
|
CPU_INFO_ITERATOR cii;
|
|
int count = 0;
|
|
int level = 0;
|
|
|
|
if (cpu_setperf)
|
|
return;
|
|
|
|
CPU_INFO_FOREACH(cii, ci) {
|
|
struct opp_table *ot = ci->ci_opp_table;
|
|
uint64_t curr_hz;
|
|
uint32_t curr_microvolt;
|
|
int error;
|
|
|
|
if (ot == NULL)
|
|
continue;
|
|
|
|
#if NKSTAT > 0
|
|
cpu_opp_kstat_attach(ci);
|
|
#endif
|
|
|
|
/* Skip if this table is shared and we're not the master. */
|
|
if (ot->ot_master && ot->ot_master != ci)
|
|
continue;
|
|
|
|
/* PWM regulators may need to be explicitly enabled. */
|
|
regulator_enable(ci->ci_cpu_supply);
|
|
|
|
curr_hz = clock_get_frequency(ci->ci_node, NULL);
|
|
curr_microvolt = regulator_get_voltage(ci->ci_cpu_supply);
|
|
|
|
/* Disable if clock isn't implemented. */
|
|
error = ENODEV;
|
|
if (curr_hz != 0)
|
|
error = clock_set_frequency(ci->ci_node, NULL, curr_hz);
|
|
if (error) {
|
|
ci->ci_opp_table = NULL;
|
|
printf("%s: clock not implemented\n",
|
|
ci->ci_dev->dv_xname);
|
|
continue;
|
|
}
|
|
|
|
/* Disable if regulator isn't implemented. */
|
|
error = ci->ci_cpu_supply ? ENODEV : 0;
|
|
if (ci->ci_cpu_supply && curr_microvolt != 0)
|
|
error = regulator_set_voltage(ci->ci_cpu_supply,
|
|
curr_microvolt);
|
|
if (error) {
|
|
ci->ci_opp_table = NULL;
|
|
printf("%s: regulator not implemented\n",
|
|
ci->ci_dev->dv_xname);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Initialize performance level based on the current
|
|
* speed of the first CPU that supports DVFS.
|
|
*/
|
|
if (level == 0) {
|
|
uint64_t min, max;
|
|
uint64_t level_hz;
|
|
|
|
min = ot->ot_opp_hz_min;
|
|
max = ot->ot_opp_hz_max;
|
|
level_hz = clock_get_frequency(ci->ci_node, NULL);
|
|
level = howmany(100 * (level_hz - min), (max - min));
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
if (count > 0) {
|
|
task_set(&cpu_opp_task, cpu_opp_dotask, NULL);
|
|
cpu_setperf = cpu_opp_setperf;
|
|
|
|
perflevel = (level > 0) ? level : 0;
|
|
cpu_setperf(perflevel);
|
|
}
|
|
}
|
|
|
|
void
|
|
cpu_opp_dotask(void *arg)
|
|
{
|
|
struct cpu_info *ci;
|
|
CPU_INFO_ITERATOR cii;
|
|
|
|
CPU_INFO_FOREACH(cii, ci) {
|
|
struct opp_table *ot = ci->ci_opp_table;
|
|
uint64_t curr_hz, opp_hz;
|
|
uint32_t curr_microvolt, opp_microvolt;
|
|
int opp_idx;
|
|
int error = 0;
|
|
|
|
if (ot == NULL)
|
|
continue;
|
|
|
|
/* Skip if this table is shared and we're not the master. */
|
|
if (ot->ot_master && ot->ot_master != ci)
|
|
continue;
|
|
|
|
opp_idx = MIN(ci->ci_opp_idx, ci->ci_opp_max);
|
|
opp_hz = ot->ot_opp[opp_idx].opp_hz;
|
|
opp_microvolt = ot->ot_opp[opp_idx].opp_microvolt;
|
|
|
|
curr_hz = clock_get_frequency(ci->ci_node, NULL);
|
|
curr_microvolt = regulator_get_voltage(ci->ci_cpu_supply);
|
|
|
|
if (error == 0 && opp_hz < curr_hz)
|
|
error = clock_set_frequency(ci->ci_node, NULL, opp_hz);
|
|
if (error == 0 && ci->ci_cpu_supply &&
|
|
opp_microvolt != 0 && opp_microvolt != curr_microvolt) {
|
|
error = regulator_set_voltage(ci->ci_cpu_supply,
|
|
opp_microvolt);
|
|
}
|
|
if (error == 0 && opp_hz > curr_hz)
|
|
error = clock_set_frequency(ci->ci_node, NULL, opp_hz);
|
|
|
|
if (error)
|
|
printf("%s: DVFS failed\n", ci->ci_dev->dv_xname);
|
|
}
|
|
}
|
|
|
|
void
|
|
cpu_opp_setperf(int level)
|
|
{
|
|
struct cpu_info *ci;
|
|
CPU_INFO_ITERATOR cii;
|
|
|
|
CPU_INFO_FOREACH(cii, ci) {
|
|
struct opp_table *ot = ci->ci_opp_table;
|
|
uint64_t min, max;
|
|
uint64_t level_hz, opp_hz;
|
|
int opp_idx = -1;
|
|
int i;
|
|
|
|
if (ot == NULL)
|
|
continue;
|
|
|
|
/* Skip if this table is shared and we're not the master. */
|
|
if (ot->ot_master && ot->ot_master != ci)
|
|
continue;
|
|
|
|
min = ot->ot_opp_hz_min;
|
|
max = ot->ot_opp_hz_max;
|
|
level_hz = min + (level * (max - min)) / 100;
|
|
opp_hz = min;
|
|
for (i = 0; i < ot->ot_nopp; i++) {
|
|
if (ot->ot_opp[i].opp_hz <= level_hz &&
|
|
ot->ot_opp[i].opp_hz >= opp_hz)
|
|
opp_hz = ot->ot_opp[i].opp_hz;
|
|
}
|
|
|
|
/* Find index of selected operating point. */
|
|
for (i = 0; i < ot->ot_nopp; i++) {
|
|
if (ot->ot_opp[i].opp_hz == opp_hz) {
|
|
opp_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
KASSERT(opp_idx >= 0);
|
|
|
|
ci->ci_opp_idx = opp_idx;
|
|
}
|
|
|
|
/*
|
|
* Update the hardware from a task since setting the
|
|
* regulators might need process context.
|
|
*/
|
|
task_add(systq, &cpu_opp_task);
|
|
}
|
|
|
|
uint32_t
|
|
cpu_opp_get_cooling_level(void *cookie, uint32_t *cells)
|
|
{
|
|
struct cpu_info *ci = cookie;
|
|
struct opp_table *ot = ci->ci_opp_table;
|
|
|
|
return ot->ot_nopp - ci->ci_opp_max - 1;
|
|
}
|
|
|
|
void
|
|
cpu_opp_set_cooling_level(void *cookie, uint32_t *cells, uint32_t level)
|
|
{
|
|
struct cpu_info *ci = cookie;
|
|
struct opp_table *ot = ci->ci_opp_table;
|
|
int opp_max;
|
|
|
|
if (level > (ot->ot_nopp - 1))
|
|
level = ot->ot_nopp - 1;
|
|
|
|
opp_max = (ot->ot_nopp - level - 1);
|
|
if (ci->ci_opp_max != opp_max) {
|
|
ci->ci_opp_max = opp_max;
|
|
task_add(systq, &cpu_opp_task);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
cpu_psci_init(struct cpu_info *ci)
|
|
{
|
|
uint32_t *domains;
|
|
uint32_t *domain;
|
|
uint32_t *states;
|
|
uint32_t ncells;
|
|
uint32_t cluster;
|
|
int idx, len, node;
|
|
|
|
/*
|
|
* Hunt for the deepest idle state for this CPU. This is
|
|
* fairly complicated as it requires traversing quite a few
|
|
* nodes in the device tree. The first step is to look up the
|
|
* "psci" power domain for this CPU.
|
|
*/
|
|
|
|
idx = OF_getindex(ci->ci_node, "psci", "power-domain-names");
|
|
if (idx < 0)
|
|
return;
|
|
|
|
len = OF_getproplen(ci->ci_node, "power-domains");
|
|
if (len <= 0)
|
|
return;
|
|
|
|
domains = malloc(len, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(ci->ci_node, "power-domains", domains, len);
|
|
|
|
domain = domains;
|
|
while (domain && domain < domains + (len / sizeof(uint32_t))) {
|
|
if (idx == 0)
|
|
break;
|
|
|
|
node = OF_getnodebyphandle(domain[0]);
|
|
if (node == 0)
|
|
break;
|
|
|
|
ncells = OF_getpropint(node, "#power-domain-cells", 0);
|
|
domain = domain + ncells + 1;
|
|
idx--;
|
|
}
|
|
|
|
node = idx == 0 ? OF_getnodebyphandle(domain[0]) : 0;
|
|
free(domains, M_TEMP, len);
|
|
if (node == 0)
|
|
return;
|
|
|
|
/*
|
|
* We found the "psci" power domain. If this power domain has
|
|
* a parent power domain, stash its phandle away for later.
|
|
*/
|
|
|
|
cluster = OF_getpropint(node, "power-domains", 0);
|
|
|
|
/*
|
|
* Get the deepest idle state for the CPU; this should be the
|
|
* last one that is listed.
|
|
*/
|
|
|
|
len = OF_getproplen(node, "domain-idle-states");
|
|
if (len < sizeof(uint32_t))
|
|
return;
|
|
|
|
states = malloc(len, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(node, "domain-idle-states", states, len);
|
|
|
|
node = OF_getnodebyphandle(states[len / sizeof(uint32_t) - 1]);
|
|
free(states, M_TEMP, len);
|
|
if (node == 0)
|
|
return;
|
|
|
|
ci->ci_psci_suspend_param =
|
|
OF_getpropint(node, "arm,psci-suspend-param", 0);
|
|
|
|
/*
|
|
* Qualcomm Snapdragon always seem to operate in OS Initiated
|
|
* mode. This means that the last CPU to suspend can pick the
|
|
* idle state that powers off the entire cluster. In our case
|
|
* that will always be the primary CPU.
|
|
*/
|
|
|
|
#ifdef MULTIPROCESSOR
|
|
if (ci->ci_flags & CPUF_AP)
|
|
return;
|
|
#endif
|
|
|
|
node = OF_getnodebyphandle(cluster);
|
|
if (node == 0)
|
|
return;
|
|
|
|
/*
|
|
* Get the deepest idle state for the cluster; this should be
|
|
* the last one that is listed.
|
|
*/
|
|
|
|
states = malloc(len, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(node, "domain-idle-states", states, len);
|
|
|
|
node = OF_getnodebyphandle(states[len / sizeof(uint32_t) - 1]);
|
|
free(states, M_TEMP, len);
|
|
if (node == 0)
|
|
return;
|
|
|
|
ci->ci_psci_suspend_param =
|
|
OF_getpropint(node, "arm,psci-suspend-param", 0);
|
|
}
|
|
|
|
#if NKSTAT > 0
|
|
|
|
struct cpu_kstats {
|
|
struct kstat_kv ck_impl;
|
|
struct kstat_kv ck_part;
|
|
struct kstat_kv ck_rev;
|
|
};
|
|
|
|
void
|
|
cpu_kstat_attach(struct cpu_info *ci)
|
|
{
|
|
struct kstat *ks;
|
|
struct cpu_kstats *ck;
|
|
uint64_t impl, part;
|
|
const char *impl_name = NULL, *part_name = NULL;
|
|
const struct cpu_cores *coreselecter = cpu_cores_none;
|
|
int i;
|
|
|
|
ks = kstat_create(ci->ci_dev->dv_xname, 0, "mach", 0, KSTAT_T_KV, 0);
|
|
if (ks == NULL) {
|
|
printf("%s: unable to create cpu kstats\n",
|
|
ci->ci_dev->dv_xname);
|
|
return;
|
|
}
|
|
|
|
ck = malloc(sizeof(*ck), M_DEVBUF, M_WAITOK);
|
|
|
|
impl = CPU_IMPL(ci->ci_midr);
|
|
part = CPU_PART(ci->ci_midr);
|
|
|
|
for (i = 0; cpu_implementers[i].name; i++) {
|
|
if (impl == cpu_implementers[i].id) {
|
|
impl_name = cpu_implementers[i].name;
|
|
coreselecter = cpu_implementers[i].corelist;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (impl_name) {
|
|
kstat_kv_init(&ck->ck_impl, "impl", KSTAT_KV_T_ISTR);
|
|
strlcpy(kstat_kv_istr(&ck->ck_impl), impl_name,
|
|
sizeof(kstat_kv_istr(&ck->ck_impl)));
|
|
} else
|
|
kstat_kv_init(&ck->ck_impl, "impl", KSTAT_KV_T_NULL);
|
|
|
|
for (i = 0; coreselecter[i].name; i++) {
|
|
if (part == coreselecter[i].id) {
|
|
part_name = coreselecter[i].name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (part_name) {
|
|
kstat_kv_init(&ck->ck_part, "part", KSTAT_KV_T_ISTR);
|
|
strlcpy(kstat_kv_istr(&ck->ck_part), part_name,
|
|
sizeof(kstat_kv_istr(&ck->ck_part)));
|
|
} else
|
|
kstat_kv_init(&ck->ck_part, "part", KSTAT_KV_T_NULL);
|
|
|
|
kstat_kv_init(&ck->ck_rev, "rev", KSTAT_KV_T_ISTR);
|
|
snprintf(kstat_kv_istr(&ck->ck_rev), sizeof(kstat_kv_istr(&ck->ck_rev)),
|
|
"r%llup%llu", CPU_VAR(ci->ci_midr), CPU_REV(ci->ci_midr));
|
|
|
|
ks->ks_softc = ci;
|
|
ks->ks_data = ck;
|
|
ks->ks_datalen = sizeof(*ck);
|
|
ks->ks_read = kstat_read_nop;
|
|
|
|
kstat_install(ks);
|
|
|
|
/* XXX should we have a ci->ci_kstat = ks? */
|
|
}
|
|
|
|
struct cpu_opp_kstats {
|
|
struct kstat_kv coppk_freq;
|
|
struct kstat_kv coppk_supply_v;
|
|
};
|
|
|
|
int
|
|
cpu_opp_kstat_read(struct kstat *ks)
|
|
{
|
|
struct cpu_info *ci = ks->ks_softc;
|
|
struct cpu_opp_kstats *coppk = ks->ks_data;
|
|
|
|
struct opp_table *ot = ci->ci_opp_table;
|
|
struct cpu_info *oci;
|
|
struct timespec now, diff;
|
|
|
|
/* rate limit */
|
|
getnanouptime(&now);
|
|
timespecsub(&now, &ks->ks_updated, &diff);
|
|
if (diff.tv_sec < 1)
|
|
return (0);
|
|
|
|
if (ot == NULL)
|
|
return (0);
|
|
|
|
oci = ot->ot_master;
|
|
if (oci == NULL)
|
|
oci = ci;
|
|
|
|
kstat_kv_freq(&coppk->coppk_freq) =
|
|
clock_get_frequency(oci->ci_node, NULL);
|
|
|
|
if (oci->ci_cpu_supply) {
|
|
kstat_kv_volts(&coppk->coppk_supply_v) =
|
|
regulator_get_voltage(oci->ci_cpu_supply);
|
|
}
|
|
|
|
ks->ks_updated = now;
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
cpu_opp_kstat_attach(struct cpu_info *ci)
|
|
{
|
|
struct kstat *ks;
|
|
struct cpu_opp_kstats *coppk;
|
|
struct opp_table *ot = ci->ci_opp_table;
|
|
struct cpu_info *oci = ot->ot_master;
|
|
|
|
if (oci == NULL)
|
|
oci = ci;
|
|
|
|
ks = kstat_create(ci->ci_dev->dv_xname, 0, "dt-opp", 0,
|
|
KSTAT_T_KV, 0);
|
|
if (ks == NULL) {
|
|
printf("%s: unable to create cpu dt-opp kstats\n",
|
|
ci->ci_dev->dv_xname);
|
|
return;
|
|
}
|
|
|
|
coppk = malloc(sizeof(*coppk), M_DEVBUF, M_WAITOK);
|
|
|
|
kstat_kv_init(&coppk->coppk_freq, "freq", KSTAT_KV_T_FREQ);
|
|
kstat_kv_init(&coppk->coppk_supply_v, "supply",
|
|
oci->ci_cpu_supply ? KSTAT_KV_T_VOLTS_DC : KSTAT_KV_T_NULL);
|
|
|
|
ks->ks_softc = oci;
|
|
ks->ks_data = coppk;
|
|
ks->ks_datalen = sizeof(*coppk);
|
|
ks->ks_read = cpu_opp_kstat_read;
|
|
|
|
kstat_install(ks);
|
|
|
|
/* XXX should we have a ci->ci_opp_kstat = ks? */
|
|
}
|
|
|
|
#endif /* NKSTAT > 0 */
|