Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F160816174
D56546.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
D56546.diff
View Options
diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC
--- a/sys/amd64/conf/GENERIC
+++ b/sys/amd64/conf/GENERIC
@@ -110,6 +110,7 @@
# Make an SMP-capable kernel by default
options SMP # Symmetric MultiProcessor Kernel
+options HMP
# CPU frequency control
device cpufreq
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -220,6 +220,15 @@
# Mandatory:
options SMP # Symmetric MultiProcessor Kernel
+#####################################################################
+# HMP OPTIONS:
+#
+# HMP enables building of Heterogeneous MultiProcessor Kernel.
+# This depends on SMP and SCHED_ULE.
+
+# Mandatory:
+options HMP # Heterogeneous MultiProcessor Kernel
+
# EARLY_AP_STARTUP releases the Application Processors earlier in the
# kernel startup process (before devices are probed) rather than at the
# end. This is a temporary option for use during the transition from
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3973,6 +3973,7 @@
kern/subr_gtaskqueue.c standard
kern/subr_hash.c standard
kern/subr_hints.c standard
+kern/subr_hmp.c optional hmp
kern/subr_kdb.c standard
kern/subr_kobj.c standard
kern/subr_lock.c standard
diff --git a/sys/conf/options b/sys/conf/options
--- a/sys/conf/options
+++ b/sys/conf/options
@@ -638,6 +638,7 @@
# Standard SMP options
EARLY_AP_STARTUP opt_global.h
SMP opt_global.h
+HMP opt_global.h
NUMA opt_global.h
# Size of the kernel message buffer
diff --git a/sys/kern/subr_hmp.c b/sys/kern/subr_hmp.c
new file mode 100644
--- /dev/null
+++ b/sys/kern/subr_hmp.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2026 FreeBSD Foundation
+ *
+ * This software was developed by Minsoo Choo under sponsorship from the
+ * FreeBSD Foundation.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/systm.h>
+#include <sys/hmp.h>
+#include <sys/kernel.h>
+#include <sys/pcpu.h>
+#include <sys/smp.h>
+#include <sys/sysctl.h>
+
+/* System-wide CPU capability information */
+struct hmp hmp_state = {
+ .total_capacity = 0,
+ .has_scores = false,
+};
+
+/* Per-CPU HMP state */
+DPCPU_DEFINE(struct hmp_pcpu, hmp_pcpu);
+
+/*
+ * Initialization
+ */
+
+static void
+hmp_init(void *arg __unused)
+{
+ /* Choose and call provider */
+}
+SYSINIT(hmp, SI_SUB_SMP + 1, SI_ORDER_ANY, hmp_init, NULL);
+
+/*
+ * Helper functions for scheduler
+ */
+
+/*
+ * Fall back for hmp_best_cpu in case processor doesn't support dynamic
+ * score update. Takes O(n).
+ *
+ * TODO: Precalculate cpu with highest capacity on boot after initialization.
+ */
+int
+hmp_highest_capacity_cpu(const cpuset_t *mask)
+{
+ struct hmp_pcpu *hp;
+ hmp_score_t best_cap;
+ int best_cpu, cpu;
+
+ best_cpu = -1;
+ best_cap = 0;
+
+ CPU_FOREACH(cpu) {
+ if (mask != NULL && !CPU_ISSET(cpu, mask))
+ continue;
+ hp = DPCPU_ID_PTR(cpu, hmp_pcpu);
+ if (hp->capacity > best_cap) {
+ best_cap = hp->capacity;
+ best_cpu = cpu;
+ }
+ }
+
+ return (best_cpu);
+}
+
+int
+hmp_lowest_capacity_cpu(const cpuset_t *mask)
+{
+ struct hmp_pcpu *hp;
+ hmp_score_t best_cap;
+ int best_cpu, cpu;
+
+ best_cpu = -1;
+ best_cap = 0;
+
+ CPU_FOREACH(cpu) {
+ if (mask != NULL && !CPU_ISSET(cpu, mask))
+ continue;
+ hp = DPCPU_ID_PTR(cpu, hmp_pcpu);
+ if (best_cpu == -1 || hp->capacity < best_cap) {
+ best_cap = hp->capacity;
+ best_cpu = cpu;
+ }
+ }
+
+ return (best_cpu);
+}
+
+/*
+ * Find CPU with best score for given class and capability for thread
+ * placement. Fall backs to capacity if scores are not provided. Takes O(n).
+ *
+ * It is possible that a score of previously read cpu is updated by a provider
+ * while this function is still traversing remaining cpus, but the effect is
+ * negligible.
+ *
+ * TODO: If this brings severe performance degradation, score providers should
+ * maintain and update index everytime new information is fed and
+ * the scheduler should use the index which takes O(1).
+ */
+int
+hmp_best_cpu(const cpuset_t *mask, enum score_type st)
+{
+ struct hmp_pcpu *hp;
+ hmp_score_t best_score;
+ int best_cpu, cpu;
+
+ if (!hmp_state.has_scores) {
+ switch (st) {
+ case HMP_SCORE_PERF:
+ return (hmp_highest_capacity_cpu(mask));
+ case HMP_SCORE_EFF:
+ return (hmp_lowest_capacity_cpu(mask));
+ default:
+ panic("invalid score type");
+ }
+ }
+
+ best_cpu = -1;
+ best_score = 0;
+
+ CPU_FOREACH(cpu) {
+ if (mask != NULL && !CPU_ISSET(cpu, mask))
+ continue;
+ hp = DPCPU_ID_PTR(cpu, hmp_pcpu);
+ if (hp->scores[st] > best_score) {
+ best_score = hp->scores[st];
+ best_cpu = cpu;
+ }
+ }
+
+ return (best_cpu);
+}
+
+/*
+ * Sysctls
+ */
+
+static int
+hmp_sysctl_capacity(SYSCTL_HANDLER_ARGS)
+{
+ struct hmp_pcpu *hp;
+ unsigned int v;
+
+ hp = DPCPU_ID_PTR(arg2, hmp_pcpu);
+ v = hp->capacity;
+ return (sysctl_handle_int(oidp, &v, 0, req));
+}
+
+static int
+hmp_sysctl_capacity_percent(SYSCTL_HANDLER_ARGS)
+{
+ struct hmp_pcpu *hp;
+ unsigned int v;
+
+ hp = DPCPU_ID_PTR(arg2, hmp_pcpu);
+ v = HMP_CAPACITY_NORMAL_TO_PERCENT(hp->capacity);
+ return (sysctl_handle_int(oidp, &v, 0, req));
+}
+
+static int
+hmp_sysctl_score(SYSCTL_HANDLER_ARGS)
+{
+ struct hmp_pcpu *hp;
+ int cpu = arg2 & 0xffff;
+ enum score_type st = (arg2 >> 16) & 0xffff;
+ unsigned int v;
+
+ hp = DPCPU_ID_PTR(cpu, hmp_pcpu);
+ v = hmp_get_score(hp, st);
+ return (sysctl_handle_int(oidp, &v, 0, req));
+}
+
+static SYSCTL_NODE(_kern, OID_AUTO, hmp, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
+ "Heterogeneous multi-processing state");
+
+SYSCTL_U32(_kern_hmp, OID_AUTO, total_capacity, CTLFLAG_RD,
+ &hmp_state.total_capacity, 0,
+ "Sum of per-CPU capacities (normalized to HMP_CAPACITY_SCALE)");
+
+SYSCTL_BOOL(_kern_hmp, OID_AUTO, has_scores, CTLFLAG_RD,
+ &hmp_state.has_scores, 0,
+ "True if a score provider is supplying dynamic perf/eff scores");
+
+static void
+hmp_sysctl_init(void *arg __unused)
+{
+ struct sysctl_oid *cpu_root, *cpu_node, *cpu_score_node;
+ struct sysctl_oid_list *cpu_root_children, *cpu_children;
+ struct sysctl_oid_list *cpu_score_children;
+ char name[8];
+ int cpu;
+
+ /*
+ * Per-CPU tree: kern.hmp.cpu.<N>.{capacity,score.perf,score.eff,...}
+ */
+ cpu_root = SYSCTL_ADD_NODE(NULL, SYSCTL_STATIC_CHILDREN(_kern_hmp),
+ OID_AUTO, "cpu", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
+ "Per-CPU HMP state");
+ if (cpu_root == NULL)
+ return;
+ cpu_root_children = SYSCTL_CHILDREN(cpu_root);
+
+ CPU_FOREACH(cpu) {
+ snprintf(name, sizeof(name), "%d", cpu);
+ cpu_node = SYSCTL_ADD_NODE(NULL, cpu_root_children, OID_AUTO,
+ name, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
+ "Per-CPU HMP state");
+ if (cpu_node == NULL)
+ continue;
+ cpu_children = SYSCTL_CHILDREN(cpu_node);
+
+ SYSCTL_ADD_PROC(NULL, cpu_children, OID_AUTO, "capacity",
+ CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, cpu,
+ hmp_sysctl_capacity, "IU",
+ "Static capacity (0-HMP_CAPACITY_SCALE)");
+
+ SYSCTL_ADD_PROC(NULL, cpu_children, OID_AUTO, "capacity_percent",
+ CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, cpu,
+ hmp_sysctl_capacity_percent, "IU",
+ "Static capacity as a percentage of the scale");
+
+ cpu_score_node = SYSCTL_ADD_NODE(NULL, cpu_children, OID_AUTO,
+ "score", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
+ "Per-CPU dynamic scores");
+ if (cpu_score_node != NULL) {
+ cpu_score_children = SYSCTL_CHILDREN(cpu_score_node);
+
+ SYSCTL_ADD_PROC(NULL, cpu_score_children, OID_AUTO,
+ "perf",
+ CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
+ cpu | (HMP_SCORE_PERF << 16), hmp_sysctl_score,
+ "IU",
+ "Current performance score (dynamic)");
+
+ SYSCTL_ADD_PROC(NULL, cpu_score_children, OID_AUTO,
+ "eff",
+ CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
+ cpu | (HMP_SCORE_EFF << 16), hmp_sysctl_score,
+ "IU",
+ "Current efficiency score (dynamic)");
+ }
+ }
+}
+SYSINIT(hmp_sysctl, SI_SUB_SMP + 2, SI_ORDER_ANY, hmp_sysctl_init, NULL);
diff --git a/sys/sys/hmp.h b/sys/sys/hmp.h
new file mode 100644
--- /dev/null
+++ b/sys/sys/hmp.h
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2026 FreeBSD Foundation
+ *
+ * This software was developed by Minsoo Choo under sponsorship from the
+ * FreeBSD Foundation.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _SYS_HMP_H_
+#define _SYS_HMP_H_
+
+#ifdef _KERNEL
+#ifndef LOCORE
+
+#include <sys/types.h>
+#include <sys/pcpu.h>
+#include <sys/queue.h>
+
+#include <machine/atomic.h>
+
+#ifdef HMP
+
+#define HMP_CAPACITY_SCALE 1024
+#define HMP_CAPACITY_MAX HMP_CAPACITY_SCALE
+#define HMP_CAPACITY_DEFAULT HMP_CAPACITY_MAX
+
+/* Capacity normalization macro functions */
+#define HMP_CAPACITY_NORMAL_FROM(x, y) (((x) * HMP_CAPACITY_SCALE) / (y))
+#define HMP_CAPACITY_NORMAL_FROM_255(x) HMP_CAPACITY_NORMAL_FROM((x), 255)
+#define HMP_CAPACITY_NORMAL_FROM_1024(x) HMP_CAPACITY_NORMAL_FROM((x), 1024)
+
+#define HMP_CAPACITY_NORMAL_TO(x, y) (((x) * (y)) / HMP_CAPACITY_SCALE)
+#define HMP_CAPACITY_NORMAL_TO_PERCENT(x) HMP_CAPACITY_NORMAL_TO((x), 100)
+
+/*
+ * Score type
+ */
+enum score_type {
+ HMP_SCORE_PERF,
+ HMP_SCORE_EFF,
+};
+
+/*
+ * CPU capacity type
+ * This value should be normalized to 0-1024.
+ *
+ * Whenever there is a new capability score scheme where highest score excceds
+ * 1024, HMP_CAPACITY_SCALE should be bumped to the new highest score for
+ * fine-grained score management on a new architecture.
+ */
+typedef uint32_t hmp_capacity_t;
+
+/*
+ * CPU score type
+ * This doesn't need to be normalized.
+ */
+typedef uint32_t hmp_score_t;
+
+/*
+ * System-wide CPU capability state - initialized on boot
+ *
+ * total_capacity is populated once the capacity provider finishes init().
+ * has_scores is set to true once a score provider has won selection and
+ * finished init(); when false, the scheduler must fall back to capacity-only
+ * placement decisions.
+ */
+struct hmp {
+ hmp_capacity_t total_capacity; /* Precalculated for scheduler */
+ bool has_scores; /* Runtime updates available */
+};
+extern struct hmp hmp_state;
+
+/*
+ * Per-CPU HMP state
+ */
+struct hmp_pcpu {
+ hmp_capacity_t capacity;
+ hmp_score_t scores[2];
+};
+DPCPU_DECLARE(struct hmp_pcpu, hmp_pcpu);
+
+/*
+ * Accessors
+ */
+static inline hmp_score_t
+hmp_get_score(struct hmp_pcpu *hp, enum score_type st)
+{
+ return atomic_load_acq_32(&hp->scores[st]);
+}
+
+/*
+ * Setters
+ */
+static inline void
+hmp_set_score(struct hmp_pcpu *hp, enum score_type st, hmp_score_t score)
+{
+ atomic_store_rel_32(&hp->scores[st], score);
+}
+
+/*
+ * Provider interfaces
+ *
+ * HMP is fed by two independent provider paths:
+ *
+ * probe() must not touch hmp_pcpu state.
+ * init() is called once, only on the winning provider of each path.
+ */
+
+/*
+ * Capacity provider
+ *
+ * Populate the static per-CPU capacity field and hmp_state.total_capacity.
+ * Exactly one capacity provider wins at boot (highest priority among
+ * those whose probe() returns true).
+ */
+struct hmp_capacity_provider {
+ const char *name;
+ int priority; /* higher wins */
+ int (*probe)(void);
+ int (*init)(void);
+
+ /* Filled in by registration; do not set manually. */
+ bool probed;
+ bool active;
+
+ SLIST_ENTRY(hmp_capacity_provider) link;
+};
+
+/*
+ * Score provider
+ *
+ * Populate and maintain per-CPU perf/eff scores and flags.
+ * Exactly one score provider wins at boot. Score provider can be absent
+ * (hmp_state.has_scores stays false and the scheduler uses capacity-only
+ * placement).
+ */
+struct hmp_score_provider {
+ const char *name;
+ int priority; /* higher wins */
+ int (*probe)(void);
+ int (*init)(void);
+
+ /* Filled in by registration; do not set manually. */
+ bool probed;
+ bool active;
+
+ SLIST_ENTRY(hmp_score_provider) link;
+};
+
+void hmp_capacity_provider_register(struct hmp_capacity_provider *p);
+void hmp_score_provider_register(struct hmp_score_provider *p);
+
+#define HMP_CAPACITY_PROVIDER_DECLARE(name, provider) \
+ DATA_SET(hmp_capacity_provider_set, provider); \
+ static void hmp_cap_provider_##name##_register(void *arg __unused) \
+ { \
+ hmp_capacity_provider_register(&(provider)); \
+ } \
+ SYSINIT(hmp_cap_provider_##name, SI_SUB_SMP, SI_ORDER_ANY, \
+ hmp_cap_provider_##name##_register, NULL)
+
+#define HMP_SCORE_PROVIDER_DECLARE(name, provider) \
+ DATA_SET(hmp_score_provider_set, provider); \
+ static void hmp_score_provider_##name##_register(void *arg __unused) \
+ { \
+ hmp_score_provider_register(&(provider)); \
+ } \
+ SYSINIT(hmp_score_provider_##name, SI_SUB_SMP, SI_ORDER_ANY, \
+ hmp_score_provider_##name##_register, NULL)
+
+/*
+ * Helper functions for scheduler
+ */
+int hmp_highest_capacity_cpu(const cpuset_t *mask);
+int hmp_lowest_capacity_cpu(const cpuset_t *mask);
+int hmp_best_cpu(const cpuset_t *mask, enum score_type st);
+
+#endif /* HMP */
+#endif /* !LOCORE */
+#endif /* _KERNEL */
+#endif /* _SYS_HMP_H_ */
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Jun 29, 4:26 AM (20 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34453078
Default Alt Text
D56546.diff (12 KB)
Attached To
Mode
D56546: hmp(4): introduce Heterogeneous MultiProcessing support
Attached
Detach File
Event Timeline
Log In to Comment