Skip to content

The kernel patch

If you take one thing from this site, take this.

--- a/drivers/pps/clients/pps-gpio.c
+++ b/drivers/pps/clients/pps-gpio.c
@@ -156,6 +156,13 @@ get_irqf_trigger_flags(const struct pps_gpio_device_data *data)
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING);
}
+ /* The handler timestamps the pulse, so it has to run in hard-irq
+ * context. Under PREEMPT_RT it would otherwise be force-threaded and
+ * the timestamp taken after thread wakeup latency, adding microseconds
+ * of jitter to an edge that should be good to nanoseconds.
+ */
+ flags |= IRQF_NO_THREAD;
+
return flags;
}

pps-gpio requests its interrupt with only the trigger flags — no IRQF_NO_THREAD. On a stock kernel that’s fine, because handlers run in hard-IRQ context anyway. Under PREEMPT_RT, the kernel force-threads it, and since the handler is where the timestamp is taken, the measurement moves behind the scheduler.

IRQF_NO_THREAD tells the kernel: not this one. The handler stays in hard-IRQ context; everything else keeps RT’s preemptibility.

RMS offsetraw PPS jitter
PREEMPT_RT, unpatched2468 ns6947 ns
PREEMPT_RT, patched199 ns2568 ns

As far as we can tell this is not applied anywhere. Which means every person running GPIO-based PPS on a PREEMPT_RT kernel is, right now, silently eating microseconds of jitter — and has no reason to suspect it, because nothing looks broken. chrony still reports Stratum 1. The dashboard still says locked. The number is just quietly, invisibly worse.

If that’s you, this patch is free accuracy.

See Patch pps-gpio — it’s a module, so you can rebuild just the one .ko in about a minute rather than the whole kernel.