Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
diff --git a/klippy/extras/mlock.py b/klippy/extras/mlock.py
new file mode 100644
index 0000000..ee7bac9
--- /dev/null
+++ b/klippy/extras/mlock.py
@@ -0,0 +1,66 @@
+# Keep klippy resident in RAM.
+#
+# Klipper has hard real-time deadlines towards the MCUs (trsync during
+# homing, the move queue during a print). On a host with little RAM and an
+# aggressive swap policy the kernel is free to page klippy out, and a major
+# page fault at the wrong moment shows up as "Timer too close" or "Unable to
+# obtain 'trsync_state' response". Locking the process in memory takes the
+# kernel out of that decision.
+#
+# [mlock]
+#
+# Copyright (C) 2026 COSMOS contributors
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+import ctypes
+import logging
+import resource
+
+MCL_CURRENT = 1
+MCL_FUTURE = 2
+# Lock pages as they are first touched instead of faulting the whole address
+# space in at once (Linux 4.4+). Falls back to a plain lock if unsupported.
+MCL_ONFAULT = 4
+
+
+class MemLock:
+ def __init__(self, config):
+ printer = config.get_printer()
+ try:
+ resource.setrlimit(
+ resource.RLIMIT_MEMLOCK,
+ (resource.RLIM_INFINITY, resource.RLIM_INFINITY),
+ )
+ except (ValueError, OSError) as e:
+ logging.warning("mlock: could not raise RLIMIT_MEMLOCK: %s", e)
+ libc = ctypes.CDLL(None, use_errno=True)
+ flags = MCL_CURRENT | MCL_FUTURE
+ rc = libc.mlockall(flags | MCL_ONFAULT)
+ if rc != 0 and ctypes.get_errno() == 22: # EINVAL: no MCL_ONFAULT
+ rc = libc.mlockall(flags)
+ if rc != 0:
+ err = ctypes.get_errno()
+ logging.warning("mlock: mlockall failed: errno %d", err)
+ return
+ printer.register_event_handler("klippy:ready", self._report)
+
+ def _report(self):
+ try:
+ with open("/proc/self/status") as f:
+ st = dict(
+ l.split(":", 1)
+ for l in f
+ if l.startswith(("VmLck", "VmRSS", "VmSwap"))
+ )
+ logging.info(
+ "mlock: locked %s resident %s swapped %s",
+ st.get("VmLck", "?").strip(),
+ st.get("VmRSS", "?").strip(),
+ st.get("VmSwap", "?").strip(),
+ )
+ except OSError:
+ pass
+
+
+def load_config(config):
+ return MemLock(config)
5 changes: 5 additions & 0 deletions meta-opencentauri/recipes-apps/klipper/files/machine.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ path: /etc/klipper/gcodes
[danger_options]
multi_mcu_trsync_timeout: 0.03

# Keep klippy resident in RAM: with 117 MB and swappiness 150 the kernel
# otherwise pages it out, and a page fault during homing or a print ends in
# "Timer too close".
[mlock]

[stepper_x]
step_pin: PE8
dir_pin: !PE7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ SRC_URI = "git://github.com/OpenCentauri/kalico.git;protocol=https;branch=hx711s
file://0001-force-specific-input-shaper.patch \
file://0003-drv8833-motor-control.patch \
file://0004-allow-config-exclude.patch \
file://0005-mlock-keep-klippy-resident.patch \
"
SRCREV = "3f166182f82f95584112571b4f7b2258922be60d"

Expand Down