Skip to content

Commit 99cd075

Browse files
committed
tuner: switch CPU cores on and off live, like the GPU's CUs
Adds a CPU-core grid to both the native Tuner and the web one: each box is a physical core with its two SMT threads, click to toggle, then Apply. Presets for all/6/4 cores and a global SMT switch. No reboot, and nothing persists — a fresh boot brings every core back. Core 0 is drawn locked because this kernel is built without BOOTPARAM_HOTPLUG_CPU0, so cpu0 genuinely cannot go offline. Every other core can, including the six that were always there — not just the two we unlocked. The helper refuses a request that would leave no core running and never writes to cpu0, so a bad call can't strand the machine. Why it's useful: switching cores off frees thermal and power headroom for the remaining ones, which matters on a board where the ceiling turned out to be the heatsink rather than the silicon. Verified on hardware end to end: clicking two cores and pressing Apply took the box from 16 to 12 threads (online 0-11), the All preset brought it back to 16, zero MCEs.
1 parent 2d425a8 commit 99cd075

5 files changed

Lines changed: 326 additions & 3 deletions

File tree

apps/dashboard/skillfish-dashboardd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,11 @@ class Handler(BaseHTTPRequestHandler):
14401440
if not self._guard("tuner"):
14411441
return
14421442
return self._json(200, cpu_coremap())
1443+
if path == "/api/tuner/cpu-cores":
1444+
# GET reads the live core map; the POST route below changes it
1445+
if not self._guard("tuner"):
1446+
return
1447+
return self._json(200, tuner_cmd([{"cmd": "cpu-cores"}]))
14431448
if path == "/api/tuner/fan-curve":
14441449
if not self._guard("tuner"):
14451450
return
@@ -1558,6 +1563,15 @@ class Handler(BaseHTTPRequestHandler):
15581563
if not self._guard("tuner"):
15591564
return
15601565
return self._json(200, tuner_cmd([{"cmd": "gov-mode", "mode": data.get("mode", "balanced")}]))
1566+
if path == "/api/tuner/cpu-cores":
1567+
# live CPU hotplug — same idea as the CU grid, but for the CPU side
1568+
if not self._guard("tuner"):
1569+
return
1570+
if "smt" in data:
1571+
return self._json(200, tuner_cmd([{"cmd": "cpu-smt", "on": bool(data["smt"])}]))
1572+
if "cores" in data:
1573+
return self._json(200, tuner_cmd([{"cmd": "cpu-cores-set", "cores": data["cores"]}]))
1574+
return self._json(200, tuner_cmd([{"cmd": "cpu-cores"}]))
15611575
if path == "/api/tuner/fan":
15621576
if not self._guard("tuner"):
15631577
return

apps/dashboard/web/tuner.html

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,18 @@
101101
<pre class="log" id="cutestlog"></pre>
102102
</div>
103103

104-
<div class="panel"><h3>🧩 Mappa core CPU</h3>
105-
<div class="stub">Layout fisico dei core (per affinità/pinning dei giochi). MHz live per core.</div>
104+
<div class="panel"><h3>🧩 Core CPU — a caldo, senza riavvio</h3>
105+
<div class="brow">
106+
<span class="pill" id="cpucount">– core attivi</span>
107+
<button class="btn sec" data-cores="99">Tutti</button>
108+
<button class="btn sec" data-cores="6">6 core</button>
109+
<button class="btn sec" data-cores="4">4 core</button>
110+
<label class="pill" style="cursor:pointer"><input type="checkbox" id="smtcb" checked style="vertical-align:-2px"> SMT</label>
111+
</div>
112+
<div class="cugrid" id="cpugrid" style="grid-template-columns:repeat(8,1fr)"></div>
113+
<div class="brow"><button class="btn" id="cpucoresapply">Applica core</button></div>
114+
<div class="stub">Ogni riquadro = 1 core fisico con i suoi 2 thread. Il <b>core 0</b> resta sempre acceso: ospita la CPU di avvio, il kernel non può spegnerlo. Spegnere core libera budget termico per far salire la frequenza dei rimanenti. Al riavvio tornano tutti accesi.</div>
115+
<div class="stub" style="margin-top:10px">Layout fisico dei core (per affinità/pinning dei giochi). MHz live per core.</div>
106116
<div id="coremap" style="margin-top:8px"></div>
107117
</div>
108118

@@ -160,6 +170,37 @@
160170
$("#govbal").classList.toggle("on", m==="balanced"); $("#govperf").classList.toggle("on", m!=="balanced"); }
161171
function renderFanMode(mode){ $("#fanauto").classList.toggle("on", mode!=="manual"); $("#fanman").classList.toggle("on", mode==="manual"); }
162172

173+
// ---- CPU cores (live hotplug) ----
174+
let CPU_CORES = []; // [{core, online, removable, cpus:[...]}]
175+
let cpuWant = {}; // core -> desired state, applied on "Applica core"
176+
177+
function renderCPUCores(){
178+
const g = $("#cpugrid"); if(!g) return;
179+
g.innerHTML = "";
180+
CPU_CORES.forEach(c=>{
181+
const on = cpuWant[c.core];
182+
const locked = !c.removable; // core 0: holds the boot CPU
183+
const d = document.createElement('div');
184+
d.className = 'wgp'+(on?' on':'')+(locked?' locked':'');
185+
d.innerHTML = '<div class="cu"></div><div class="cu"></div>';
186+
d.title = locked ? 'core di avvio — sempre acceso' : (on?'acceso':'spento');
187+
if(!locked) d.onclick = ()=>{ cpuWant[c.core] = !cpuWant[c.core]; renderCPUCores(); };
188+
g.appendChild(d);
189+
});
190+
const n = CPU_CORES.filter(c=>cpuWant[c.core]).length;
191+
const thr = n * ($("#smtcb") && $("#smtcb").checked ? 2 : 1);
192+
$("#cpucount").textContent = n+" / "+CPU_CORES.length+" core · "+thr+" thread";
193+
}
194+
195+
async function loadCPUCores(){
196+
const r = await api("/api/tuner/cpu-cores");
197+
if(!r || !r.cores) return;
198+
CPU_CORES = r.cores;
199+
cpuWant = {}; CPU_CORES.forEach(c=> cpuWant[c.core] = c.online);
200+
if($("#smtcb")) $("#smtcb").checked = (r.smt || "on") === "on";
201+
renderCPUCores();
202+
}
203+
163204
function renderCU(){
164205
const g = $("#cugrid"); g.innerHTML = '<div class="cuh"></div>' +
165206
[0,1,2,3,4].map(i=>'<div class="cuh">WGP'+i+'</div>').join('');
@@ -248,6 +289,20 @@
248289
else lg("gpulog","Nessun passo stabile."); busy(false); };
249290
$("#govbal").onclick=async()=>{ await api("/api/tuner/govmode",{mode:"balanced"}); STATE.gpu.gov_mode="balanced"; renderGov(); toast("Governor: bilanciato"); };
250291
$("#govperf").onclick=async()=>{ await api("/api/tuner/govmode",{mode:"performance"}); STATE.gpu.gov_mode="performance"; renderGov(); toast("Governor: prestazioni"); };
292+
document.querySelectorAll("[data-cores]").forEach(b=> b.onclick=()=>{
293+
const k=+b.dataset.cores;
294+
CPU_CORES.forEach(c=> cpuWant[c.core] = (k>=CPU_CORES.length) ? true : (c.core<k || !c.removable));
295+
renderCPUCores(); });
296+
$("#cpucoresapply").onclick=async()=>{ toast("Applico i core…");
297+
const cores=CPU_CORES.map(c=>({core:c.core, online:!!cpuWant[c.core]}));
298+
const x=await api("/api/tuner/cpu-cores",{cores});
299+
if(x && x.ok){ CPU_CORES=x.cores||CPU_CORES; cpuWant={}; CPU_CORES.forEach(c=>cpuWant[c.core]=c.online);
300+
renderCPUCores(); toast("Core applicati — "+(x.nproc||"?")+" thread attivi"); }
301+
else toast("Errore core: "+((x&&x.err)||"")); };
302+
$("#smtcb").onchange=async()=>{ const on=$("#smtcb").checked; toast("SMT "+(on?"acceso":"spento")+"…");
303+
const x=await api("/api/tuner/cpu-cores",{smt:on});
304+
if(x && x.ok){ toast("SMT "+(on?"acceso":"spento")+" — "+(x.nproc||"?")+" thread"); await loadCPUCores(); }
305+
else toast("SMT: "+((x&&x.err)||"errore")); };
251306
document.querySelectorAll("[data-cu]").forEach(b=> b.onclick=()=>{ const m=+b.dataset.cu; CU_ROWS.forEach(([k])=>cuMask[k]=m|cuFloor); renderCU(); });
252307
$("#cuapply").onclick=async()=>{ toast("Applico CU a caldo…"); const rows=CU_ROWS.map(([k])=>cuMask[k]|cuFloor);
253308
const x=await api("/api/tuner/cu",{rows}); toast(x.ok?("CU applicate: "+(x.active||cuActive())+" attive"):("Errore CU: "+(x.err||x.error||""))); setTimeout(load,500); };
@@ -317,6 +372,6 @@
317372
$("#fcon").onclick=()=>{FC.enabled=true;fcRenderMode();fcSave();};
318373
$("#fcoff").onclick=()=>{FC.enabled=false;fcRenderMode();fcSave();};
319374

320-
load(); loadProfiles(); loadCoremap(); loadFanCurve();
375+
load(); loadProfiles(); loadCoremap(); loadFanCurve(); loadCPUCores();
321376
</script>
322377
</body></html>

apps/tuner/skillfish-tuner

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ HELP = {
5454
"vram": (L("VRAM (memoria grafica)", "VRAM (graphics memory)"),
5555
L("Quantità di RAM di sistema riservata alla GPU (UMA).\n\n⚠ Scrive nel CMOS del BIOS e richiede un RIAVVIO per applicare. Reversibile azzerando il CMOS (jumper sulla scheda).\n\nNon superare valori che lascino troppo poca RAM al sistema.",
5656
"Amount of system RAM reserved for the GPU (UMA).\n\n⚠ Writes to the BIOS CMOS and requires a REBOOT to apply. Reversible by clearing the CMOS (jumper on the board).\n\nDon't exceed values that leave too little RAM for the system.")),
57+
"cpucores": (L("Core CPU", "CPU cores"),
58+
L("Accendi e spegni i core della CPU A CALDO, senza riavvio — come per le CU della GPU.\n\n• Ogni riquadro = 1 core fisico con i suoi 2 thread: VERDE = acceso, ROSSO = spento.\n• CLIC su un core per accenderlo/spegnerlo, poi «Applica».\n• Il core 0 è VERDE FISSO: ospita la CPU di avvio e il kernel non può spegnerlo.\n• «SMT» accende/spegne il multi-threading: da 2 thread per core a 1.\n\nA cosa serve: spegnere core libera budget termico e di consumo per far salire la frequenza dei core rimasti — utile nei giochi che usano pochi thread. Meno core = meno calore, ma meno potenza nei carichi multi-thread.\n\nLa scelta NON è permanente: al riavvio tornano tutti accesi.",
59+
"Turn CPU cores on and off LIVE, no reboot — just like the GPU's CUs.\n\n• Each box = 1 physical core with its 2 threads: GREEN = on, RED = off.\n• CLICK a core to toggle it, then «Apply».\n• Core 0 is FIXED GREEN: it holds the boot CPU and the kernel cannot offline it.\n• «SMT» toggles multi-threading: 2 threads per core, or 1.\n\nWhy: switching cores off frees thermal and power budget so the remaining cores can clock higher — handy for games that use few threads. Fewer cores = less heat, but less multi-threaded power.\n\nThe choice is NOT permanent: everything comes back on at reboot.")),
5760
"cu": (L("Compute Unit (CU)", "Compute Units (CU)"),
5861
L("Le CU sono i core di calcolo della GPU. Qui le attivi/disattivi A CALDO, senza riavvio.\n\n• Ogni quadratino = 1 CU: VERDE = attiva, ROSSO = spenta.\n• CLIC su una coppia per accenderla/spegnerla manualmente (le coppie si gestiscono a 2 CU per volta = 1 WGP).\n• Oppure usa i PRESET (24 / 32 / 40 CU).\n• Le prime 3 coppie per fila (24 CU) sono il minimo del driver e restano sempre attive.\n• Premi «Applica a caldo» per rendere effettiva la scelta.\n\nPiù CU = più potenza grafica ma anche più calore e consumo.\n\nUsa «Test CU» per verificare che le CU attive siano stabili e senza difetti (lotteria del silicio).",
5962
"CUs are the GPU's compute cores. Here you enable/disable them LIVE, no reboot.\n\n• Each square = 1 CU: GREEN = active, RED = off.\n• CLICK a pair to toggle it manually (pairs are managed 2 CU at a time = 1 WGP).\n• Or use the PRESETS (24 / 32 / 40 CU).\n• The first 3 pairs per row (24 CU) are the driver minimum and stay always on.\n• Hit «Apply live» to make the choice effective.\n\nMore CU = more graphics power but also more heat and power draw.\n\nUse «Test CU» to verify the active CUs are stable and defect-free (silicon lottery).")),
@@ -148,6 +151,38 @@ class CuWgp(QFrame):
148151
self.on = not self.on; self._paint(); self.on_toggle(self.on)
149152

150153

154+
class CpuCore(QFrame):
155+
"""One physical CPU core drawn as its two SMT threads, styled like the CU grid.
156+
157+
Core 0 is locked: it holds cpu0, the boot CPU, which this kernel cannot offline.
158+
"""
159+
def __init__(self, core, on, locked, on_toggle):
160+
super().__init__()
161+
self.core = core; self.on = on; self.locked = locked; self.on_toggle = on_toggle
162+
lay = QVBoxLayout(self); lay.setContentsMargins(1, 1, 1, 1); lay.setSpacing(2)
163+
row = QHBoxLayout(); row.setSpacing(2)
164+
self.sq = [QFrame(), QFrame()]
165+
for s in self.sq:
166+
s.setFixedSize(16, 16); row.addWidget(s)
167+
rw = QWidget(); rw.setLayout(row); lay.addWidget(rw)
168+
cap = QLabel("C%d" % core); cap.setAlignment(Qt.AlignmentFlag.AlignHCenter)
169+
cap.setStyleSheet("color:#8f7c52;font-size:9px;"); lay.addWidget(cap)
170+
if not self.locked:
171+
self.setCursor(Qt.CursorShape.PointingHandCursor)
172+
self._paint()
173+
def _paint(self):
174+
if self.locked: col, bd = "#3f7a39", "#2c571f"
175+
elif self.on: col, bd = "#5fd24f", "#36862c"
176+
else: col, bd = "#7c2424", "#4d1414"
177+
for s in self.sq:
178+
s.setStyleSheet("background:%s;border:1px solid %s;border-radius:3px;" % (col, bd))
179+
self.setToolTip(L("core di avvio (sempre attivo)", "boot core (always on)") if self.locked
180+
else (L("attivo", "on") if self.on else L("spento", "off")))
181+
def mousePressEvent(self, e):
182+
if self.locked: return
183+
self.on = not self.on; self._paint(); self.on_toggle(self.core, self.on)
184+
185+
151186
class Chart(QWidget):
152187
"""A live, custom-painted line chart panel (brass / steampunk look)."""
153188
def __init__(self, title, unit, series):
@@ -274,6 +309,7 @@ class TunerWindow(QMainWindow):
274309
self.box.addWidget(self._gpu())
275310
self.box.addWidget(self._vram())
276311
self.box.addWidget(self._cu())
312+
self.box.addWidget(self._sec_cpu_cores())
277313
self.box.addWidget(self._fan())
278314
self.box.addStretch(1)
279315

@@ -654,6 +690,74 @@ class TunerWindow(QMainWindow):
654690
pw = QWidget(); pw.setLayout(pr); v.addWidget(pw)
655691
return g
656692

693+
def _sec_cpu_cores(self):
694+
g, v = self._section(L("Core CPU — a caldo", "CPU cores — live"))
695+
st = self.d.cmd(cmd="cpu-cores") or {}
696+
self.cpu_cores = st.get("cores", [])
697+
self.cpu_want = {c["core"]: c["online"] for c in self.cpu_cores}
698+
grid = QGridLayout(); grid.setHorizontalSpacing(8); grid.setVerticalSpacing(5)
699+
self.cpu_cells = {}
700+
for i, c in enumerate(self.cpu_cores):
701+
cell = CpuCore(c["core"], c["online"], not c.get("removable", True), self._cpu_toggle)
702+
self.cpu_cells[c["core"]] = cell
703+
grid.addWidget(cell, i // 8, i % 8)
704+
gw = QWidget(); gw.setLayout(grid); v.addWidget(gw)
705+
self.cpu_count_lbl = QLabel(); self.cpu_count_lbl.setStyleSheet("color:#d8a849;font-size:13px;")
706+
v.addWidget(self.cpu_count_lbl); self._cpu_update_count()
707+
pr = QHBoxLayout()
708+
for lbl, n in ((L("Tutti", "All"), 99), ("6 core", 6), ("4 core", 4)):
709+
b = QPushButton(lbl); b.clicked.connect(lambda _=False, k=n: self._cpu_preset(k)); pr.addWidget(b)
710+
self.smt_cb = QCheckBox("SMT")
711+
self.smt_cb.setChecked((st.get("smt") or "on") == "on")
712+
self.smt_cb.setToolTip(L("Multi-threading: 2 thread per core. Spento = 1 thread per core.",
713+
"Multi-threading: 2 threads per core. Off = 1 thread per core."))
714+
self.smt_cb.toggled.connect(self._cpu_smt)
715+
if not st.get("smt"): self.smt_cb.setEnabled(False)
716+
pr.addWidget(self.smt_cb)
717+
pr.addWidget(self._help_btn("cpucores"))
718+
pr.addStretch(1)
719+
ba = QPushButton(L("Applica", "Apply")); ba.clicked.connect(self._apply_cpu_cores); pr.addWidget(ba)
720+
pw = QWidget(); pw.setLayout(pr); v.addWidget(pw)
721+
return g
722+
723+
def _cpu_toggle(self, core, on):
724+
self.cpu_want[core] = on; self._cpu_update_count()
725+
726+
def _cpu_update_count(self):
727+
n = sum(1 for v in self.cpu_want.values() if v)
728+
thr = n * (2 if getattr(self, "smt_cb", None) is None or self.smt_cb.isChecked() else 1)
729+
self.cpu_count_lbl.setText(L("Core attivi: %d / %d · %d thread", "Active cores: %d / %d · %d threads")
730+
% (n, len(self.cpu_want) or 8, thr))
731+
732+
def _cpu_preset(self, keep):
733+
for c in self.cpu_cores:
734+
want = True if keep >= len(self.cpu_cores) else (c["core"] < keep)
735+
if not c.get("removable", True): want = True # core 0 stays
736+
self.cpu_want[c["core"]] = want
737+
cell = self.cpu_cells.get(c["core"])
738+
if cell and not cell.locked:
739+
cell.on = want; cell._paint()
740+
self._cpu_update_count()
741+
742+
def _cpu_smt(self, on):
743+
res = self.d.cmd(cmd="cpu-smt", on=bool(on)) or {}
744+
if res.get("ok"):
745+
self.toast(L("SMT %s — %d thread", "SMT %s — %d threads")
746+
% (L("acceso", "on") if on else L("spento", "off"), res.get("nproc", 0)), 4)
747+
else:
748+
self.toast(L("SMT: %s", "SMT: %s") % res.get("err", "errore"), 6)
749+
self._cpu_update_count()
750+
751+
def _apply_cpu_cores(self):
752+
req = [{"core": k, "online": bool(v)} for k, v in self.cpu_want.items()]
753+
res = self.d.cmd(cmd="cpu-cores-set", cores=req) or {}
754+
if res.get("ok"):
755+
self.cpu_cores = res.get("cores", self.cpu_cores)
756+
self.toast(L("Core applicati — %d thread attivi", "Cores applied — %d active threads")
757+
% res.get("nproc", 0), 5)
758+
else:
759+
self.toast(L("Errore core: %s", "Core error: %s") % res.get("err", "apply failed"), 7)
760+
657761
def _cu_toggle(self, rk, wgp, on):
658762
m = self.cu_rows.get(rk, 7)
659763
m = (m | (1 << wgp)) if on else (m & ~(1 << wgp))

apps/tuner/skillfish-tuner-helper

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,78 @@ def cu_apply(rows):
269269
except Exception as e:
270270
return {"ok":False,"err":str(e)}
271271

272+
def cpu_cores_get():
273+
"""Live CPU-core map: one entry per physical core with its SMT siblings.
274+
275+
Every logical CPU can be taken offline except cpu0 — it is the boot CPU and
276+
this kernel is built without BOOTPARAM_HOTPLUG_CPU0 — so core 0 can drop its
277+
second thread but never disappear entirely.
278+
"""
279+
cores = {}
280+
for p in sorted(glob.glob("/sys/devices/system/cpu/cpu[0-9]*")):
281+
n = os.path.basename(p)[3:]
282+
if not n.isdigit(): continue
283+
n = int(n)
284+
try:
285+
core = int(_rd("%s/topology/core_id" % p).strip())
286+
except Exception:
287+
continue # offline CPUs hide their topology; folded in below
288+
online = True if not os.path.exists(p + "/online") else _rd(p + "/online").strip() == "1"
289+
cores.setdefault(core, {"core": core, "cpus": [], "online": False, "removable": True})
290+
cores[core]["cpus"].append({"cpu": n, "online": online,
291+
"removable": os.path.exists(p + "/online")})
292+
if online: cores[core]["online"] = True
293+
if not os.path.exists(p + "/online"): cores[core]["removable"] = False
294+
# an offline CPU loses topology/core_id, so recover it from the sibling numbering
295+
for p in sorted(glob.glob("/sys/devices/system/cpu/cpu[0-9]*")):
296+
n = os.path.basename(p)[3:]
297+
if not n.isdigit(): continue
298+
n = int(n)
299+
if any(c["cpu"] == n for e in cores.values() for c in e["cpus"]): continue
300+
core = n // 2 # this APU pairs threads as (2k, 2k+1)
301+
cores.setdefault(core, {"core": core, "cpus": [], "online": False, "removable": True})
302+
cores[core]["cpus"].append({"cpu": n, "online": False, "removable": True})
303+
smt = None
304+
try: smt = _rd("/sys/devices/system/cpu/smt/control").strip()
305+
except Exception: pass
306+
out = [cores[k] for k in sorted(cores)]
307+
for e in out: e["cpus"].sort(key=lambda c: c["cpu"])
308+
return {"ok": True, "cores": out, "nproc": os.cpu_count(), "smt": smt}
309+
310+
311+
def cpu_cores_set(states):
312+
"""states: list of {core, online}. Toggles both SMT siblings of each core.
313+
314+
Refuses to leave the machine with no cores and never touches cpu0, so a bad
315+
request can't strand the box — the worst case is one core left running.
316+
"""
317+
want = {}
318+
for s in states or []:
319+
try: want[int(s["core"])] = bool(s["online"])
320+
except Exception: pass
321+
if not want: return {"ok": False, "err": "nessun core specificato"}
322+
cur = cpu_cores_get()["cores"]
323+
if not any(want.get(e["core"], e["online"]) for e in cur):
324+
return {"ok": False, "err": "almeno un core deve restare acceso"}
325+
for e in cur:
326+
tgt = want.get(e["core"])
327+
if tgt is None: continue
328+
for c in e["cpus"]:
329+
if not c["removable"]: continue # cpu0
330+
_wr("/sys/devices/system/cpu/cpu%d/online" % c["cpu"], "1" if tgt else "0")
331+
time.sleep(1)
332+
return cpu_cores_get()
333+
334+
335+
def cpu_smt_set(on):
336+
"""Global SMT switch — halves or restores the thread count in one go."""
337+
p = "/sys/devices/system/cpu/smt/control"
338+
if not os.path.exists(p): return {"ok": False, "err": "SMT non controllabile su questo kernel"}
339+
_wr(p, "on" if on else "off")
340+
time.sleep(1)
341+
return cpu_cores_get()
342+
343+
272344
def set_cu(unlock):
273345
"""Abilita/disabilita lo sblocco 40 CU via parametro di boot GRUB. Richiede riavvio."""
274346
grub="/etc/default/grub"; param="amdgpu.bc250_cc_write_mode=3"
@@ -416,6 +488,9 @@ def handle(req):
416488
if c=="set-vram": return {"ok":set_vram(req["mb"]),"reboot":True}
417489
if c=="set-cu": return {"ok":set_cu(req["unlock"]),"reboot":True}
418490
if c=="cu-apply": return cu_apply(req.get("rows",[]))
491+
if c=="cpu-cores": return cpu_cores_get()
492+
if c=="cpu-cores-set": return cpu_cores_set(req.get("cores",[]))
493+
if c=="cpu-smt": return cpu_smt_set(bool(req.get("on",True)))
419494
if c=="cu-test": return cu_test()
420495
if c=="thermal-guard": return {"ok":thermal_guard(req["limit"])}
421496
if c=="test-cpu": return test_cpu(req["mhz"],req["scale"],req["temp"])

0 commit comments

Comments
 (0)