#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/sbuf.h>
#include <sys/cpu_topology.h>
#include <machine/smp.h>
#ifndef NAPICID
#define NAPICID 256
#endif
#define INDENT_BUF_SIZE LEVEL_NO*3
#define INVALID_ID -1
struct per_cpu_sysctl_info {
struct sysctl_ctx_list sysctl_ctx;
struct sysctl_oid *sysctl_tree;
char cpu_name[32];
int physical_id;
int core_id;
int ht_id;
char physical_siblings[8*MAXCPU];
char core_siblings[8*MAXCPU];
};
typedef struct per_cpu_sysctl_info per_cpu_sysctl_info_t;
__read_frequently static cpu_node_t cpu_topology_nodes[MAXCPU];
__read_frequently static cpu_node_t *cpu_root_node;
static struct sysctl_ctx_list cpu_topology_sysctl_ctx;
static struct sysctl_oid *cpu_topology_sysctl_tree;
static char cpu_topology_members[8*MAXCPU];
static per_cpu_sysctl_info_t *pcpu_sysctl;
static void sbuf_print_cpuset(struct sbuf *sb, cpumask_t *mask);
__read_frequently int cpu_topology_levels_number = 1;
__read_frequently int cpu_topology_ht_ids;
__read_frequently int cpu_topology_core_ids;
__read_frequently int cpu_topology_phys_ids;
__read_frequently cpu_node_t *root_cpu_node;
MALLOC_DEFINE(M_PCPUSYS, "pcpusys", "pcpu sysctl topology");
SYSCTL_INT(_hw, OID_AUTO, cpu_topology_ht_ids, CTLFLAG_RW,
&cpu_topology_ht_ids, 0, "# of logical cores per real core");
SYSCTL_INT(_hw, OID_AUTO, cpu_topology_core_ids, CTLFLAG_RW,
&cpu_topology_core_ids, 0, "# of real cores per package");
SYSCTL_INT(_hw, OID_AUTO, cpu_topology_phys_ids, CTLFLAG_RW,
&cpu_topology_phys_ids, 0, "# of physical packages");
static int
get_next_valid_apicid(int curr_apicid)
{
int next_apicid = curr_apicid;
do {
next_apicid++;
}
while(get_cpuid_from_apicid(next_apicid) == -1 &&
next_apicid < NAPICID);
if (next_apicid == NAPICID) {
kprintf("Warning: No next valid APICID found. Returning -1\n");
return -1;
}
return next_apicid;
}
static void
build_topology_tree(int *children_no_per_level,
uint8_t *level_types,
int cur_level,
cpu_node_t *node,
cpu_node_t **last_free_node,
int *apicid)
{
int i;
node->child_no = children_no_per_level[cur_level];
node->type = level_types[cur_level];
CPUMASK_ASSZERO(node->members);
node->compute_unit_id = -1;
if (node->child_no == 0) {
*apicid = get_next_valid_apicid(*apicid);
CPUMASK_ASSBIT(node->members, get_cpuid_from_apicid(*apicid));
return;
}
if (node->parent_node == NULL)
root_cpu_node = node;
for (i = 0; i < node->child_no; i++) {
node->child_node[i] = *last_free_node;
(*last_free_node)++;
node->child_node[i]->parent_node = node;
build_topology_tree(children_no_per_level,
level_types,
cur_level + 1,
node->child_node[i],
last_free_node,
apicid);
CPUMASK_ORMASK(node->members, node->child_node[i]->members);
}
}
#if defined(__x86_64__) && !defined(_KERNEL_VIRTUAL)
static void
migrate_elements(cpu_node_t **a, int n, int pos)
{
int i;
for (i = pos; i < n - 1 ; i++) {
a[i] = a[i+1];
}
a[i] = NULL;
}
#endif
static void
build_cpu_topology(int assumed_ncpus)
{
int i;
int BSPID = 0;
int threads_per_core = 0;
int cores_per_chip = 0;
int chips_per_package = 0;
int children_no_per_level[LEVEL_NO];
uint8_t level_types[LEVEL_NO];
int apicid = -1;
cpu_node_t *root = &cpu_topology_nodes[0];
cpu_node_t *last_free_node = root + 1;
detect_cpu_topology();
for (i = 0; i < assumed_ncpus; i++) {
cpumask_t mask;
CPUMASK_ASSBIT(mask, i);
#if 0
if (CPUMASK_TESTMASK(mask, smp_active_mask) == 0)
continue;
#endif
if (get_chip_ID(BSPID) != get_chip_ID(i))
continue;
++cores_per_chip;
if (get_core_number_within_chip(BSPID) ==
get_core_number_within_chip(i)) {
++threads_per_core;
}
}
cores_per_chip /= threads_per_core;
chips_per_package = assumed_ncpus / (cores_per_chip * threads_per_core);
kprintf("CPU Topology: cores_per_chip: %d; threads_per_core: %d; "
"chips_per_package: %d;\n",
cores_per_chip, threads_per_core, chips_per_package);
if (threads_per_core > 1) {
children_no_per_level[0] = chips_per_package;
children_no_per_level[1] = cores_per_chip;
children_no_per_level[2] = threads_per_core;
children_no_per_level[3] = 0;
level_types[0] = PACKAGE_LEVEL;
level_types[1] = CHIP_LEVEL;
level_types[2] = CORE_LEVEL;
level_types[3] = THREAD_LEVEL;
build_topology_tree(children_no_per_level,
level_types,
0,
root,
&last_free_node,
&apicid);
cpu_topology_levels_number = 4;
} else if (cores_per_chip > 1) {
children_no_per_level[0] = chips_per_package;
children_no_per_level[1] = cores_per_chip;
children_no_per_level[2] = 0;
level_types[0] = PACKAGE_LEVEL;
level_types[1] = CHIP_LEVEL;
level_types[2] = CORE_LEVEL;
build_topology_tree(children_no_per_level,
level_types,
0,
root,
&last_free_node,
&apicid);
cpu_topology_levels_number = 3;
} else {
children_no_per_level[0] = chips_per_package;
children_no_per_level[1] = 0;
level_types[0] = PACKAGE_LEVEL;
level_types[1] = CHIP_LEVEL;
build_topology_tree(children_no_per_level,
level_types,
0,
root,
&last_free_node,
&apicid);
cpu_topology_levels_number = 2;
}
cpu_root_node = root;
#if defined(__x86_64__) && !defined(_KERNEL_VIRTUAL)
if (fix_amd_topology() == 0) {
int visited[MAXCPU], i, j, pos, cpuid;
cpu_node_t *leaf, *parent;
bzero(visited, MAXCPU * sizeof(int));
for (i = 0; i < assumed_ncpus; i++) {
if (visited[i] == 0) {
pos = 0;
visited[i] = 1;
leaf = get_cpu_node_by_cpuid(i);
KASSERT(leaf != NULL, ("cpu %d NULL node", i));
if (leaf->type == CORE_LEVEL) {
parent = leaf->parent_node;
last_free_node->child_node[0] = leaf;
last_free_node->child_no = 1;
last_free_node->members = leaf->members;
last_free_node->compute_unit_id = leaf->compute_unit_id;
last_free_node->parent_node = parent;
last_free_node->type = CORE_LEVEL;
for (j = 0; j < parent->child_no; j++) {
if (parent->child_node[j] != leaf) {
cpuid = BSFCPUMASK(parent->child_node[j]->members);
if (visited[cpuid] == 0 &&
parent->child_node[j]->compute_unit_id == leaf->compute_unit_id) {
last_free_node->child_node[last_free_node->child_no] = parent->child_node[j];
last_free_node->child_no++;
CPUMASK_ORMASK(last_free_node->members, parent->child_node[j]->members);
parent->child_node[j]->type = THREAD_LEVEL;
parent->child_node[j]->parent_node = last_free_node;
visited[cpuid] = 1;
migrate_elements(parent->child_node, parent->child_no, j);
parent->child_no--;
j--;
}
} else {
pos = j;
}
}
if (last_free_node->child_no > 1) {
parent->child_node[pos] = last_free_node;
leaf->type = THREAD_LEVEL;
leaf->parent_node = last_free_node;
last_free_node++;
}
}
}
}
}
#endif
}
static void
print_cpu_topology_tree_sysctl_helper(cpu_node_t *node,
struct sbuf *sb,
char * buf,
int buf_len,
int last)
{
int i;
int bsr_member;
sbuf_bcat(sb, buf, buf_len);
if (last) {
sbuf_printf(sb, "\\-");
buf[buf_len] = ' ';buf_len++;
buf[buf_len] = ' ';buf_len++;
} else {
sbuf_printf(sb, "|-");
buf[buf_len] = '|';buf_len++;
buf[buf_len] = ' ';buf_len++;
}
bsr_member = BSRCPUMASK(node->members);
if (node->type == PACKAGE_LEVEL) {
sbuf_printf(sb,"PACKAGE MEMBERS: ");
} else if (node->type == CHIP_LEVEL) {
sbuf_printf(sb,"CHIP ID %d: ",
get_chip_ID(bsr_member));
} else if (node->type == CORE_LEVEL) {
if (node->compute_unit_id != (uint8_t)-1) {
sbuf_printf(sb,"Compute Unit ID %d: ",
node->compute_unit_id);
} else {
sbuf_printf(sb,"CORE ID %d: ",
get_core_number_within_chip(bsr_member));
}
} else if (node->type == THREAD_LEVEL) {
if (node->compute_unit_id != (uint8_t)-1) {
sbuf_printf(sb,"THREAD ID %d: ",
get_core_number_within_chip(bsr_member));
} else {
sbuf_printf(sb,"THREAD ID %d: ",
get_logical_CPU_number_within_core(bsr_member));
}
} else {
sbuf_printf(sb,"UNKNOWN: ");
}
sbuf_print_cpuset(sb, &node->members);
sbuf_printf(sb,"\n");
for (i = 0; i < node->child_no; i++) {
print_cpu_topology_tree_sysctl_helper(node->child_node[i],
sb, buf, buf_len, i == (node->child_no -1));
}
}
static int
print_cpu_topology_tree_sysctl(SYSCTL_HANDLER_ARGS)
{
struct sbuf *sb;
int ret;
char buf[INDENT_BUF_SIZE];
KASSERT(cpu_root_node != NULL, ("cpu_root_node isn't initialized"));
sb = sbuf_new(NULL, NULL, 500, SBUF_AUTOEXTEND);
if (sb == NULL) {
return (ENOMEM);
}
sbuf_printf(sb,"\n");
print_cpu_topology_tree_sysctl_helper(cpu_root_node, sb, buf, 0, 1);
sbuf_finish(sb);
ret = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb));
sbuf_delete(sb);
return ret;
}
static int
print_cpu_topology_level_description_sysctl(SYSCTL_HANDLER_ARGS)
{
struct sbuf *sb;
int ret;
sb = sbuf_new(NULL, NULL, 500, SBUF_AUTOEXTEND);
if (sb == NULL)
return (ENOMEM);
if (cpu_topology_levels_number == 4)
sbuf_printf(sb, "0 - thread; 1 - core; 2 - socket; 3 - anything");
else if (cpu_topology_levels_number == 3)
sbuf_printf(sb, "0 - core; 1 - socket; 2 - anything");
else if (cpu_topology_levels_number == 2)
sbuf_printf(sb, "0 - socket; 1 - anything");
else
sbuf_printf(sb, "Unknown");
sbuf_finish(sb);
ret = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb));
sbuf_delete(sb);
return ret;
}
static cpu_node_t *
get_cpu_node_by_cpumask(cpu_node_t * node,
cpumask_t mask) {
cpu_node_t * found = NULL;
int i;
if (CPUMASK_CMPMASKEQ(node->members, mask))
return node;
for (i = 0; i < node->child_no; i++) {
found = get_cpu_node_by_cpumask(node->child_node[i], mask);
if (found != NULL) {
return found;
}
}
return NULL;
}
cpu_node_t *
get_cpu_node_by_cpuid(int cpuid) {
cpumask_t mask;
CPUMASK_ASSBIT(mask, cpuid);
KASSERT(cpu_root_node != NULL, ("cpu_root_node isn't initialized"));
return get_cpu_node_by_cpumask(cpu_root_node, mask);
}
cpumask_t
get_cpumask_from_level(int cpuid,
uint8_t level_type)
{
cpu_node_t * node;
cpumask_t mask;
CPUMASK_ASSBIT(mask, cpuid);
KASSERT(cpu_root_node != NULL, ("cpu_root_node isn't initialized"));
node = get_cpu_node_by_cpumask(cpu_root_node, mask);
if (node == NULL) {
CPUMASK_ASSZERO(mask);
return mask;
}
while (node != NULL) {
if (node->type == level_type) {
return node->members;
}
node = node->parent_node;
}
CPUMASK_ASSZERO(mask);
return mask;
}
static const cpu_node_t *
get_cpu_node_by_chipid2(const cpu_node_t *node, int chip_id)
{
int cpuid;
if (node->type != CHIP_LEVEL) {
const cpu_node_t *ret = NULL;
int i;
for (i = 0; i < node->child_no; ++i) {
ret = get_cpu_node_by_chipid2(node->child_node[i],
chip_id);
if (ret != NULL)
break;
}
return ret;
}
cpuid = BSRCPUMASK(node->members);
if (get_chip_ID(cpuid) == chip_id)
return node;
return NULL;
}
const cpu_node_t *
get_cpu_node_by_chipid(int chip_id)
{
KASSERT(cpu_root_node != NULL, ("cpu_root_node isn't initialized"));
return get_cpu_node_by_chipid2(cpu_root_node, chip_id);
}
static void
init_pcpu_topology_sysctl(int assumed_ncpus)
{
struct sbuf sb;
cpumask_t mask;
int min_id = -1;
int max_id = -1;
int i;
int phys_id;
pcpu_sysctl = kmalloc(sizeof(*pcpu_sysctl) * MAXCPU, M_PCPUSYS,
M_INTWAIT | M_ZERO);
for (i = 0; i < assumed_ncpus; i++) {
sbuf_new(&sb, pcpu_sysctl[i].cpu_name,
sizeof(pcpu_sysctl[i].cpu_name), SBUF_FIXEDLEN);
sbuf_printf(&sb,"cpu%d", i);
sbuf_finish(&sb);
mask = get_cpumask_from_level(i, CHIP_LEVEL);
if (CPUMASK_TESTZERO(mask)) {
pcpu_sysctl[i].physical_id = INVALID_ID;
continue;
}
sbuf_new(&sb, pcpu_sysctl[i].physical_siblings,
sizeof(pcpu_sysctl[i].physical_siblings), SBUF_FIXEDLEN);
sbuf_print_cpuset(&sb, &mask);
sbuf_trim(&sb);
sbuf_finish(&sb);
phys_id = get_chip_ID(i);
pcpu_sysctl[i].physical_id = phys_id;
if (min_id < 0 || min_id > phys_id)
min_id = phys_id;
if (max_id < 0 || max_id < phys_id)
max_id = phys_id;
mask = get_cpumask_from_level(i, CORE_LEVEL);
if (CPUMASK_TESTZERO(mask)) {
pcpu_sysctl[i].core_id = INVALID_ID;
continue;
}
sbuf_new(&sb, pcpu_sysctl[i].core_siblings,
sizeof(pcpu_sysctl[i].core_siblings), SBUF_FIXEDLEN);
sbuf_print_cpuset(&sb, &mask);
sbuf_trim(&sb);
sbuf_finish(&sb);
pcpu_sysctl[i].core_id = get_core_number_within_chip(i);
if (cpu_topology_core_ids < pcpu_sysctl[i].core_id + 1)
cpu_topology_core_ids = pcpu_sysctl[i].core_id + 1;
pcpu_sysctl[i].ht_id = get_logical_CPU_number_within_core(i);
if (cpu_topology_ht_ids < pcpu_sysctl[i].ht_id + 1)
cpu_topology_ht_ids = pcpu_sysctl[i].ht_id + 1;
}
cpu_topology_phys_ids = max_id - min_id + 1;
if (cpu_topology_phys_ids <= 0)
cpu_topology_phys_ids = 1;
for (i = 0; i < assumed_ncpus; i++) {
pcpu_sysctl[i].physical_id %= cpu_topology_phys_ids;
}
}
static void
build_sysctl_cpu_topology(int assumed_ncpus)
{
int i;
struct sbuf sb;
sysctl_ctx_init(&cpu_topology_sysctl_ctx);
cpu_topology_sysctl_tree = SYSCTL_ADD_NODE(&cpu_topology_sysctl_ctx,
SYSCTL_STATIC_CHILDREN(_hw),
OID_AUTO,
"cpu_topology",
CTLFLAG_RD, 0, "");
SYSCTL_ADD_PROC(&cpu_topology_sysctl_ctx,
SYSCTL_CHILDREN(cpu_topology_sysctl_tree),
OID_AUTO, "tree", CTLTYPE_STRING | CTLFLAG_RD,
NULL, 0, print_cpu_topology_tree_sysctl, "A",
"Tree print of CPU topology");
SYSCTL_ADD_PROC(&cpu_topology_sysctl_ctx,
SYSCTL_CHILDREN(cpu_topology_sysctl_tree),
OID_AUTO, "level_description", CTLTYPE_STRING | CTLFLAG_RD,
NULL, 0, print_cpu_topology_level_description_sysctl, "A",
"Level description of CPU topology");
sbuf_new(&sb, cpu_topology_members,
sizeof(cpu_topology_members), SBUF_FIXEDLEN);
sbuf_print_cpuset(&sb, &cpu_root_node->members);
sbuf_trim(&sb);
sbuf_finish(&sb);
SYSCTL_ADD_STRING(&cpu_topology_sysctl_ctx,
SYSCTL_CHILDREN(cpu_topology_sysctl_tree),
OID_AUTO, "members", CTLFLAG_RD,
cpu_topology_members, 0,
"Members of the CPU Topology");
for (i = 0; i < assumed_ncpus; i++) {
sysctl_ctx_init(&pcpu_sysctl[i].sysctl_ctx);
pcpu_sysctl[i].sysctl_tree = SYSCTL_ADD_NODE(&pcpu_sysctl[i].sysctl_ctx,
SYSCTL_CHILDREN(cpu_topology_sysctl_tree),
OID_AUTO,
pcpu_sysctl[i].cpu_name,
CTLFLAG_RD, 0, "");
if (pcpu_sysctl[i].physical_id == INVALID_ID) {
continue;
}
SYSCTL_ADD_INT(&pcpu_sysctl[i].sysctl_ctx,
SYSCTL_CHILDREN(pcpu_sysctl[i].sysctl_tree),
OID_AUTO, "physical_id", CTLFLAG_RD,
&pcpu_sysctl[i].physical_id, 0,
"Physical ID");
SYSCTL_ADD_STRING(&pcpu_sysctl[i].sysctl_ctx,
SYSCTL_CHILDREN(pcpu_sysctl[i].sysctl_tree),
OID_AUTO, "physical_siblings", CTLFLAG_RD,
pcpu_sysctl[i].physical_siblings, 0,
"Physical siblings");
if (pcpu_sysctl[i].core_id == INVALID_ID) {
continue;
}
SYSCTL_ADD_INT(&pcpu_sysctl[i].sysctl_ctx,
SYSCTL_CHILDREN(pcpu_sysctl[i].sysctl_tree),
OID_AUTO, "core_id", CTLFLAG_RD,
&pcpu_sysctl[i].core_id, 0,
"Core ID");
SYSCTL_ADD_STRING(&pcpu_sysctl[i].sysctl_ctx,
SYSCTL_CHILDREN(pcpu_sysctl[i].sysctl_tree),
OID_AUTO, "core_siblings", CTLFLAG_RD,
pcpu_sysctl[i].core_siblings, 0,
"Core siblings");
}
}
static
void
sbuf_print_cpuset(struct sbuf *sb, cpumask_t *mask)
{
int i;
int b = -1;
int e = -1;
int more = 0;
sbuf_printf(sb, "cpus(");
CPUSET_FOREACH(i, *mask) {
if (b < 0) {
b = i;
e = b + 1;
continue;
}
if (e == i) {
++e;
continue;
}
if (more)
sbuf_printf(sb, ", ");
if (b == e - 1) {
sbuf_printf(sb, "%d", b);
} else {
sbuf_printf(sb, "%d-%d", b, e - 1);
}
more = 1;
b = i;
e = b + 1;
}
if (more)
sbuf_printf(sb, ", ");
if (b >= 0) {
if (b == e - 1) {
sbuf_printf(sb, "%d", b);
} else {
sbuf_printf(sb, "%d-%d", b, e - 1);
}
}
sbuf_printf(sb, ") ");
}
int
get_cpu_ht_id(int cpuid)
{
if (pcpu_sysctl)
return(pcpu_sysctl[cpuid].ht_id);
return(0);
}
int
get_cpu_core_id(int cpuid)
{
if (pcpu_sysctl)
return(pcpu_sysctl[cpuid].core_id);
return(0);
}
int
get_cpu_phys_id(int cpuid)
{
if (pcpu_sysctl)
return(pcpu_sysctl[cpuid].physical_id);
return(0);
}
long
get_highest_node_memory(void)
{
long highest = 0;
if (cpu_root_node && cpu_root_node->type == PACKAGE_LEVEL &&
cpu_root_node->child_node[1]) {
cpu_node_t *cpup;
int i;
for (i = 0 ; i < MAXCPU && cpu_root_node->child_node[i]; ++i) {
cpup = cpu_root_node->child_node[i];
if (highest < cpup->phys_mem)
highest = cpup->phys_mem;
}
}
return highest;
}
extern int naps;
static void
init_cpu_topology(void)
{
int assumed_ncpus;
assumed_ncpus = naps + 1;
build_cpu_topology(assumed_ncpus);
init_pcpu_topology_sysctl(assumed_ncpus);
build_sysctl_cpu_topology(assumed_ncpus);
}
SYSINIT(cpu_topology, SI_BOOT2_CPU_TOPOLOGY, SI_ORDER_FIRST,
init_cpu_topology, NULL);