riscv: Rework CPU identification (second part)

Modify when and how we perform parsing and reporting. Most notably,
everything now executes on CPU 0.

The de-facto standard way to enumerate CPU features (ISA extensions) on
RISC-V is by parsing each CPU's ISA string. We currently obtain this
information from the device tree, and in the future will be able to pull
it from ACPI tables.

Eliminate the SYSINIT from identcpu.c. We still need to walk the /cpus
list in the device tree, but now do this one CPU at a time, as a step in
the identify_cpu() procedure. This is slightly less error prone, and
allows us to parse ISA features for CPU 0 much earlier.

Make use of the SMP hooks cpu_mp_start() and cpu_mp_announce() to
identify and print secondary CPU info, respectively. This causes
secondary processor identification to be printed much earlier in boot;
everything is done by SI_SUB_CPU, SI_ORDER_THIRD. Adjust some other
printf() calls so that we get enough useful info to debug under
bootverbose.

Reviewed by:	markj (slightly earlier version)
MFC after:	2 weeks
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D39811
This commit is contained in:
Mitchell Horne 2023-05-22 20:51:44 -03:00
parent b0d45b023e
commit b37dc09033
4 changed files with 112 additions and 57 deletions

View File

@ -89,8 +89,8 @@ extern char etext[];
void cpu_halt(void) __dead2;
void cpu_reset(void) __dead2;
void fork_trampoline(void);
void identify_cpu(void);
void printcpuinfo(void);
void identify_cpu(u_int cpu);
void printcpuinfo(u_int cpu);
static __inline uint64_t
get_cyclecount(void)

View File

@ -48,7 +48,6 @@ __FBSDID("$FreeBSD$");
#include <machine/cpufunc.h>
#include <machine/elf.h>
#include <machine/md_var.h>
#include <machine/trap.h>
#ifdef FDT
#include <dev/fdt/fdt_common.h>
@ -69,6 +68,7 @@ register_t mimpid; /* The implementation ID */
struct cpu_desc {
const char *cpu_mvendor_name;
const char *cpu_march_name;
u_int isa_extensions; /* Single-letter extensions. */
};
struct cpu_desc cpu_desc[MAXCPU];
@ -128,7 +128,7 @@ static const struct {
#define ISA_PREFIX_LEN (sizeof(ISA_PREFIX) - 1)
static __inline int
parse_ext_s(char *isa, int idx, int len)
parse_ext_s(struct cpu_desc *desc __unused, char *isa, int idx, int len)
{
/*
* Proceed to the next multi-letter extension or the end of the
@ -144,7 +144,7 @@ parse_ext_s(char *isa, int idx, int len)
}
static __inline int
parse_ext_x(char *isa, int idx, int len)
parse_ext_x(struct cpu_desc *desc __unused, char *isa, int idx, int len)
{
/*
* Proceed to the next multi-letter extension or the end of the
@ -158,7 +158,7 @@ parse_ext_x(char *isa, int idx, int len)
}
static __inline int
parse_ext_z(char *isa, int idx, int len)
parse_ext_z(struct cpu_desc *desc __unused, char *isa, int idx, int len)
{
/*
* Proceed to the next multi-letter extension or the end of the
@ -196,13 +196,17 @@ parse_ext_version(char *isa, int idx, u_int *majorp __unused,
/*
* Parse the ISA string, building up the set of HWCAP bits as they are found.
*/
static void
parse_riscv_isa(char *isa, int len, u_long *hwcapp)
static int
parse_riscv_isa(struct cpu_desc *desc, char *isa, int len)
{
u_long hwcap;
int i;
hwcap = 0;
/* Check the string prefix. */
if (strncmp(isa, ISA_PREFIX, ISA_PREFIX_LEN) != 0) {
printf("%s: Unrecognized ISA string: %s\n", __func__, isa);
return (-1);
}
i = ISA_PREFIX_LEN;
while (i < len) {
switch(isa[i]) {
@ -212,11 +216,11 @@ parse_riscv_isa(char *isa, int len, u_long *hwcapp)
case 'f':
case 'i':
case 'm':
hwcap |= HWCAP_ISA_BIT(isa[i]);
desc->isa_extensions |= HWCAP_ISA_BIT(isa[i]);
i++;
break;
case 'g':
hwcap |= HWCAP_ISA_G;
desc->isa_extensions |= HWCAP_ISA_G;
i++;
break;
case 's':
@ -234,20 +238,20 @@ parse_riscv_isa(char *isa, int len, u_long *hwcapp)
/*
* Supervisor-level extension namespace.
*/
i = parse_ext_s(isa, i, len);
i = parse_ext_s(desc, isa, i, len);
break;
case 'x':
/*
* Custom extension namespace. For now, we ignore
* these.
*/
i = parse_ext_x(isa, i, len);
i = parse_ext_x(desc, isa, i, len);
break;
case 'z':
/*
* Multi-letter standard extension namespace.
*/
i = parse_ext_z(isa, i, len);
i = parse_ext_z(desc, isa, i, len);
break;
case '_':
i++;
@ -261,48 +265,46 @@ parse_riscv_isa(char *isa, int len, u_long *hwcapp)
i = parse_ext_version(isa, i, NULL, NULL);
}
if (hwcapp != NULL)
*hwcapp = hwcap;
return (0);
}
#ifdef FDT
static void
fill_elf_hwcap(void *dummy __unused)
identify_cpu_features_fdt(u_int cpu, struct cpu_desc *desc)
{
char isa[1024];
u_long hwcap;
phandle_t node;
ssize_t len;
pcell_t reg;
u_int hart;
node = OF_finddevice("/cpus");
if (node == -1) {
if (bootverbose)
printf("fill_elf_hwcap: Can't find cpus node\n");
printf("%s: could not find /cpus node in FDT\n", __func__);
return;
}
hart = pcpu_find(cpu)->pc_hart;
/*
* Iterate through the CPUs and examine their ISA string. While we
* could assign elf_hwcap to be whatever the boot CPU supports, to
* handle the (unusual) case of running a system with hetergeneous
* ISAs, keep only the extension bits that are common to all harts.
* Locate our current CPU's node in the device-tree, and parse its
* contents to detect supported CPU/ISA features and extensions.
*/
for (node = OF_child(node); node > 0; node = OF_peer(node)) {
/* Skip any non-CPU nodes, such as cpu-map. */
if (!ofw_bus_node_is_compatible(node, "riscv"))
continue;
/* Find this CPU */
if (OF_getencprop(node, "reg", &reg, sizeof(reg)) <= 0 ||
reg != hart)
continue;
len = OF_getprop(node, "riscv,isa", isa, sizeof(isa));
KASSERT(len <= sizeof(isa), ("ISA string truncated"));
if (len == -1) {
if (bootverbose)
printf("fill_elf_hwcap: "
"Can't find riscv,isa property\n");
return;
} else if (strncmp(isa, ISA_PREFIX, ISA_PREFIX_LEN) != 0) {
if (bootverbose)
printf("fill_elf_hwcap: "
"Unsupported ISA string: %s\n", isa);
printf("%s: could not find 'riscv,isa' property "
"for CPU %d, hart %u\n", __func__, cpu, hart);
return;
}
@ -312,18 +314,51 @@ fill_elf_hwcap(void *dummy __unused)
*/
for (int i = 0; i < len; i++)
isa[i] = tolower(isa[i]);
parse_riscv_isa(isa, len, &hwcap);
if (parse_riscv_isa(desc, isa, len) != 0)
return;
if (elf_hwcap != 0)
elf_hwcap &= hwcap;
else
elf_hwcap = hwcap;
/* We are done. */
break;
}
if (node <= 0) {
printf("%s: could not find FDT node for CPU %u, hart %u\n",
__func__, cpu, hart);
}
}
SYSINIT(identcpu, SI_SUB_CPU, SI_ORDER_ANY, fill_elf_hwcap, NULL);
#endif
static void
identify_cpu_features(u_int cpu, struct cpu_desc *desc)
{
#ifdef FDT
identify_cpu_features_fdt(cpu, desc);
#endif
}
/*
* Update kernel/user global state based on the feature parsing results, stored
* in desc.
*
* We keep only the subset of values common to all CPUs.
*/
static void
update_global_capabilities(u_int cpu, struct cpu_desc *desc)
{
#define UPDATE_CAP(t, v) \
do { \
if (cpu == 0) { \
(t) = (v); \
} else { \
(t) &= (v); \
} \
} while (0)
/* Update the capabilities exposed to userspace via AT_HWCAP. */
UPDATE_CAP(elf_hwcap, (u_long)desc->isa_extensions);
#undef UPDATE_CAP
}
static void
identify_cpu_ids(struct cpu_desc *desc)
{
@ -364,22 +399,28 @@ identify_cpu_ids(struct cpu_desc *desc)
}
void
identify_cpu(void)
identify_cpu(u_int cpu)
{
struct cpu_desc *desc = &cpu_desc[PCPU_GET(cpuid)];
struct cpu_desc *desc = &cpu_desc[cpu];
identify_cpu_ids(desc);
identify_cpu_features(cpu, desc);
update_global_capabilities(cpu, desc);
}
void
printcpuinfo(void)
printcpuinfo(u_int cpu)
{
struct cpu_desc *desc;
u_int cpu, hart;
u_int hart;
cpu = PCPU_GET(cpuid);
hart = PCPU_GET(hart);
desc = &cpu_desc[cpu];
hart = pcpu_find(cpu)->pc_hart;
/* XXX: check this here so we are guaranteed to have console output. */
KASSERT(desc->isa_extensions != 0,
("Empty extension set for CPU %u, did parsing fail?", cpu));
/* Print details for boot CPU or if we want verbose output */
if (cpu == 0 || bootverbose) {

View File

@ -128,7 +128,7 @@ cpu_startup(void *dummy)
{
sbi_print_version();
printcpuinfo();
printcpuinfo(0);
printf("real memory = %ju (%ju MB)\n", ptoa((uintmax_t)realmem),
ptoa((uintmax_t)realmem) / (1024 * 1024));
@ -539,7 +539,7 @@ initriscv(struct riscv_bootparams *rvbp)
/*
* Identify CPU/ISA features.
*/
identify_cpu();
identify_cpu(0);
/* Do basic tuning, hz etc */
init_param1();

View File

@ -249,14 +249,6 @@ init_secondary(uint64_t hart)
pcpup->pc_curthread = pcpup->pc_idlethread;
schedinit_ap();
/*
* Identify current CPU. This is necessary to setup
* affinity registers and to provide support for
* runtime chip identification.
*/
identify_cpu();
printcpuinfo();
/* Enable software interrupts */
riscv_unmask_ipi();
@ -285,6 +277,9 @@ init_secondary(uint64_t hart)
mtx_unlock_spin(&ap_boot_mtx);
if (bootverbose)
printf("Secondary CPU %u fully online\n", cpuid);
/* Enter the scheduler */
sched_ap_entry();
@ -494,7 +489,8 @@ cpu_init_fdt(u_int id, phandle_t node, u_int addr_size, pcell_t *reg)
naps = atomic_load_int(&aps_started);
bootstack = (char *)bootstacks[cpuid] + MP_BOOTSTACK_SIZE;
printf("Starting CPU %u (hart %lx)\n", cpuid, hart);
if (bootverbose)
printf("Starting CPU %u (hart %lx)\n", cpuid, hart);
atomic_store_32(&__riscv_boot_ap[hart], 1);
/* Wait for the AP to switch to its boot stack. */
@ -512,6 +508,7 @@ cpu_init_fdt(u_int id, phandle_t node, u_int addr_size, pcell_t *reg)
void
cpu_mp_start(void)
{
u_int cpu;
mtx_init(&ap_boot_mtx, "ap boot", NULL, MTX_SPIN);
@ -527,12 +524,29 @@ cpu_mp_start(void)
case CPUS_UNKNOWN:
break;
}
CPU_FOREACH(cpu) {
/* Already identified. */
if (cpu == 0)
continue;
identify_cpu(cpu);
}
}
/* Introduce rest of cores to the world */
void
cpu_mp_announce(void)
{
u_int cpu;
CPU_FOREACH(cpu) {
/* Already announced. */
if (cpu == 0)
continue;
printcpuinfo(cpu);
}
}
void