Skip to content

gpiostep: busy-wait sub-tick delays; correct the timing claim - #2394

Merged
openipc-ai merged 4 commits into
OpenIPC:masterfrom
phedoreanu:gpiostep-subtick-busywait
Sep 11, 2026
Merged

gpiostep: busy-wait sub-tick delays; correct the timing claim#2394
openipc-ai merged 4 commits into
OpenIPC:masterfrom
phedoreanu:gpiostep-subtick-busywait

Conversation

@phedoreanu

@phedoreanu phedoreanu commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

The separate gpiostep PR discussed on #2247.

The claim was wrong. Config.in promised "steadier timing, lower CPU than the userspace gpio-motors tool", but usleep_range() runs on hrtimers, and without CONFIG_HIGH_RES_TIMERS those expire with jiffy granularity — a sub-tick sleep rounds up to the next tick exactly like a userspace usleep(). All three defconfigs that ship this package (gk7205v500_lite, gk7205v500_ultimate, gk7205v510_lite) build HZ=100 kernels without high-resolution timers, so both drivers were quantised to the same 10ms and the kernel module's only real advantage was skipping syscall traffic.

The fix. step_delay() now busy-waits with udelay() while the requested delay is under a quarter tick — the same materiality threshold as the gpio-motors side (#2393): below it, sleeping would at least quadruple the step period; above it, keep sleeping and accept the rounding, since the busy-wait cost grows with the delay while its benefit shrinks. Details:

  • the udelay() is chunked at 1ms because ARM bounds a single call at ~2ms;
  • a cond_resched() per micro-step keeps a move from monopolising the core — these kernels are !SMP and !PREEMPT, so without it nothing else (the encoder included) would run until the whole move finished. That was the one hazard the userspace spin never had: the scheduler still preempts userspace at every tick, but kernel code on a !PREEMPT uniprocessor runs until it yields;
  • IS_ENABLED(CONFIG_HIGH_RES_TIMERS) keeps the old behaviour on any future kernel that turns hrtimers on — usleep_range() is strictly better there.

Config.in, Readme.md and the file header now claim what the module actually provides: no per-write syscall cost, same tick-bounded granularity, sub-quarter-tick delays busy-waited between scheduler yields — which holds on an idle core, not under load.

Compile-verified against the real target: make BOARD=gk7205v500_lite br-gpiostep-openipc builds the module clean against the goke 4.9.37 tree.

Measured on a real stepper, on a Hi3518EV200 (same kernel class: HZ=100, no high-resolution timers, no preempt) with the module driving the camera's own pan coil and majestic streaming: the old module takes 10 ms per micro-step at every delay from 1 to 9 ms (320 micro-steps = 3.2 s regardless of the ask); this branch honours 1 ms at 1.4 ms per micro-step and 2 ms at ~3 ms under that load, at ~70% / ~55% sys, and hands 3, 4 and 9 ms to the tick at the old cost. Delay 0 has never had a floor — usleep_range(0, 1) returns at once on the old module too — and this PR leaves that as it was. Full table, raw output and test script: #2394 (comment)

Whether the V510's motor tracks the coil at 1.4 ms per micro-step is the part this cannot answer; that needs a gk7205v500/v510 owner.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 11, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Honor sub-tick gpiostep delays and correct timing claims

🐞 Bug fix 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Honors sub-tick motor delays with bounded busy-waits and scheduler yields.
• Preserves timer sleeps for high-resolution kernels and longer delays.
• Corrects timing claims and rejects unsafe CLI delay conversions.
Diagram

graph TD
  CLI["gpiostep-ctl"] --> VALID{"Valid delay?"} -->|"Yes"| IOCTL["Motor ioctl"] --> AXIS["Axis stepping"] --> MODE{"Delay mode?"}
  VALID -->|"No"| REJECT["Reject input"]
  MODE -->|"HRT or longer"| SLEEP["Timer sleep"]
  MODE -->|"Sub-tick"| BUSY["Busy wait + yield"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Enable high-resolution timers
  • ➕ Provides precise sleeping without consuming a core in busy-waits.
  • ➕ Retains scheduler responsiveness under sustained motor movement.
  • ➖ Requires kernel-wide defconfig changes and validation across supported camera SoCs.
  • ➖ May add platform cost or depend on unavailable timer support in legacy kernels.
2. Use hardware-timed stepping
  • ➕ Can provide timing stability under load with low CPU consumption.
  • ➕ Avoids dependence on scheduler and jiffy granularity.
  • ➖ Requires platform-specific timer or PWM integration.
  • ➖ Significantly expands driver complexity and hardware validation scope.

Recommendation: The PR's hybrid delay policy is the best targeted fix for existing HZ=100 kernels: it limits busy-waiting to delays where tick rounding is materially harmful, yields between micro-steps, and preserves efficient sleeps elsewhere. High-resolution timers are the preferable long-term option if all target kernels and SoCs can support them consistently.

Files changed (4) +70 / -12

Bug fix (2) +55 / -7
gpiostep-ctl.cValidate delay conversion before issuing ioctl +8/-2

Validate delay conversion before issuing ioctl

• Rejects negative delays and millisecond values that would overflow when converted to microseconds. Reports the accepted delay range to the caller.

general/package/gpiostep-openipc/src/gpiostep-ctl.c

gpiostep.cAdd capability-aware sub-tick delay handling +47/-5

Add capability-aware sub-tick delay handling

• Introduces step_delay(), which uses chunked udelay calls below a quarter tick when high-resolution timers are unavailable, then yields after each micro-step. Zero, longer, and high-resolution-timer delays continue through usleep_range().

general/package/gpiostep-openipc/src/gpiostep.c

Documentation (2) +15 / -5
Config.inCorrect package timing and CPU claims +6/-2

Correct package timing and CPU claims

• Replaces the unsupported steadier-timing claim with the actual syscall-saving benefit. Documents tick-bounded sleeps, sub-tick busy-waiting, scheduler yields, and load sensitivity.

general/package/gpiostep-openipc/Config.in

Readme.mdDocument the hybrid step-delay behavior +9/-3

Document the hybrid step-delay behavior

• Explains why both implementations have tick-granular sleeps on shipped kernels and when gpiostep busy-waits. Clarifies that scheduler yields can disrupt sub-tick pacing under load.

general/package/gpiostep-openipc/Readme.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Motor timing lacks real-board proof 📘 Rule violation ☼ Reliability
Description
step_delay() replaces scheduler-based sub-tick sleeps with repeated udelay() calls and
conditional rescheduling after every GPIO phase, changing motor timing, scheduler behavior, and CPU
usage in shipped Goke images. Because the PR states that this path was not measured on hardware, its
phase accuracy, pan/tilt operation, encoder responsiveness, CPU impact, and boot and streaming
behavior remain unverified across all three enabled camera images.
Code

general/package/gpiostep-openipc/src/gpiostep.c[R68-69]

+	if (!IS_ENABLED(CONFIG_HIGH_RES_TIMERS) &&
+	    (unsigned int)delay_us < jiffies_to_usecs(1) / 4) {
Evidence
Rules 1 and 43 require real-hardware evidence for changes that alter camera behavior. The cited
helper introduces busy-waiting and explicit rescheduling into each sub-tick motor step and is
invoked after every GPIO phase, while the three cited production defconfigs include the kernel
module in their images; the PR description acknowledges that no measurement was performed on an
affected target board.

Rule 1: Hardware evidence is present and honest
general/package/gpiostep-openipc/src/gpiostep.c[66-80]
general/package/gpiostep-openipc/src/gpiostep.c[68-80]
general/package/gpiostep-openipc/src/gpiostep.c[93-102]
br-ext-chip-goke/configs/gk7205v500_lite_defconfig[50-54]
br-ext-chip-goke/configs/gk7205v500_ultimate_defconfig[57-62]
br-ext-chip-goke/configs/gk7205v510_lite_defconfig[50-54]
Best Practice: Repository guidelines

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new busy-wait path changes camera motor timing and scheduling in three production Goke images but has only been compile-tested. The PR explicitly states that it was not measured on hardware, so hardware evidence is required before the image-reaching change can be reviewed safely.
## Fix Focus Areas
- general/package/gpiostep-openipc/src/gpiostep.c[68-80]
- br-ext-chip-goke/configs/gk7205v500_lite_defconfig[50-54]
- br-ext-chip-goke/configs/gk7205v500_ultimate_defconfig[57-62]
- br-ext-chip-goke/configs/gk7205v510_lite_defconfig[50-54]
## Recommended Fix
Run before-and-after tests on an affected supported GK7205V500-family Goke camera. Record the board name, GPIO phase intervals and measured step timing for delays below and above the threshold, successful pan and tilt movement, encoder responsiveness during a long move, CPU usage, and observable boot and streaming results; attach the measured output to the PR description, and adjust or remove the busy-wait path if the results do not demonstrate the intended improvement without regressions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Package guide still overstates timing 🐞 Bug ⚙ Maintainability
Description
Readme.md still says direct kernel GPIO provides “steadier timing and far lower CPU,” while the
corrected Config.in limits the established advantage to avoided syscall traffic and documents the
new busy-wait cost. Readers evaluating the package through its guide therefore receive the exact
timing and CPU claim this PR establishes was wrong.
Code

general/package/gpiostep-openipc/Config.in[R9-12]

+	  context, avoiding the per-write syscall cost of the userspace
+	  gpio-motors tool. Sub-tick step delays busy-wait, because the
+	  kernels that ship this package have no high-resolution timers
+	  and would otherwise round every sleep up to a whole 10ms tick.
Evidence
The changed Config.in text now makes the narrower claim, but the package's adjacent README still
makes the old broad timing and CPU claim verbatim.

general/package/gpiostep-openipc/Config.in[9-12]
general/package/gpiostep-openipc/Readme.md[7-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The package README retains the disproven timing and CPU claim even though Config.in and the source header were corrected. This leaves contradictory documentation for the same driver.
## Fix Focus Areas
- general/package/gpiostep-openipc/Readme.md[7-12]
- general/package/gpiostep-openipc/Config.in[9-12]
## Recommended Fix
Update the README to describe avoided per-write syscall traffic, tick-bounded sleeping without high-resolution timers, and busy-waiting for sub-tick delays without claiming generally steadier timing or far lower CPU usage.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread general/package/gpiostep-openipc/src/gpiostep.c Outdated
Comment thread general/package/gpiostep-openipc/Config.in Outdated
@phedoreanu
phedoreanu force-pushed the gpiostep-subtick-busywait branch from 1816816 to 1a1035d Compare September 11, 2026 09:18

@openipc-ai openipc-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the PR I asked for, and the honesty correction is the right one: usleep_range() is hrtimer-backed, those kernels have no hrtimers, so the old "steadier timing, lower CPU" claim was not true on any defconfig that ships the package. IS_ENABLED(CONFIG_HIGH_RES_TIMERS) is the better test here - the module is built against the board kernel, so this resolves at compile time instead of repeating the userspace clock_getres() dance. The 1 ms chunking is right and genuinely needed: ARM's MAX_UDELAY_MS is 2, and the quarter-tick threshold admits 2001-2499 us.

Three things before this lands. Two are inline; the third is in a file this PR does not touch:

gpiostep-ctl still has the overflow nit you fixed on the other side. gpiostep-ctl.c:37 is m.delay_us = atoi(argv[3]) * 1000; with no clamp - the same signed-overflow UB I raised on #2247 and that you fixed in gpio-motors.c:243 with delay_ms > INT_MAX / 1000. This PR is explicitly the gpiostep counterpart of that work; carry the clamp across.

On Qodo - I got this wrong in the first version of this review and have corrected it. I wrote that neither finding had been answered and that finding 2 was a false positive. Both were wrong:

  • You answered all three threads at 09:18, within about twelve minutes of the review.
  • Finding 2 (README overstates timing) was correct when filed. I only checked Readme.md at the current head and missed the force-push at 09:18:46 - the pre-amend commit touched just Config.in and gpiostep.c, matching Qodo's own "Files changed (2)". You had already fixed it in the amend. The retraction is on the Readme.md thread; disregard the ask to rebut it.
  • Finding 1 (hardware evidence) is answered but not resolved, and I am holding it - see #2393 for how I want to split the evidence ask. Short version: the delay primitive is measurable on a gk7205v200-class kernel, the motor behaviour needs a v500/v510 owner, and neither of us has one.

Compile-verifying the module against the goke 4.9.37 tree is the right check for a module and I'll take it, but it is not the evidence this needs. Please mark this draft until the three items above are in and a timing measurement is attached. CI has not run on this branch either.

Comment thread general/package/gpiostep-openipc/src/gpiostep.c Outdated
Comment thread general/package/gpiostep-openipc/src/gpiostep.c
Comment thread general/package/gpiostep-openipc/Config.in Outdated
Comment thread general/package/gpiostep-openipc/Readme.md
@phedoreanu
phedoreanu marked this pull request as draft September 11, 2026 13:58
usleep_range() runs on hrtimers, and without CONFIG_HIGH_RES_TIMERS
those expire with jiffy granularity - a sub-tick sleep rounds up to the
next tick exactly like a userspace usleep. All three defconfigs that
ship this package (gk7205v500_lite, gk7205v500_ultimate,
gk7205v510_lite) build HZ=100 kernels without high-resolution timers,
so the "steadier timing" this package claimed over the userspace
gpio-motors tool did not hold: both were quantised to the same 10ms.

Busy-wait with udelay() while the requested delay is under a quarter
tick, where the rounding would at least quadruple the step period; from
a quarter tick up, keep sleeping and accept the rounding, since the
busy-wait cost grows with the delay while its benefit shrinks. The
udelay is chunked because ARM bounds a single call at ~2ms, and a
cond_resched() per micro-step keeps a move from monopolising the core -
these kernels are !SMP and !PREEMPT, so without it nothing else,
the encoder included, would run until the whole move finished.

Reword Config.in, Readme.md and the file header to claim what the
module actually provides: no per-write syscall cost, same tick-bounded
granularity.
The busy-wait branch admitted delay 0: (unsigned int)0 < 2500 is true,
so it ran udelay(0) with only the cond_resched() between micro-steps.
Route 0 back to usleep_range() as before this series and drop the
unsigned cast, which had turned a negative into a huge value that fell
through to usleep_range(negative, ...) - the ioctl already rejects
negatives, so the guard is the plain signed comparison.

For the record, usleep_range(0, 1) is not a floor either: it is an
already-expired hrtimer and returns at once. Measured on a Hi3518EV200
(HZ=100, no hrtimers), 320 micro-steps at delay 0 complete in under
10ms on both the old and the new module. Zero has never paced the coil;
this change only keeps that as it was instead of routing it through a
path whose guard is the only thing between a negative and udelay().

gpiostep-ctl still multiplied atoi(argv[3]) by 1000 unchecked, the same
signed overflow gpio-motors clamps with INT_MAX / 1000. Carry the clamp
across, with the same message.
The cond_resched() after each busy-waited micro-step yields to whatever
is runnable, and on a !PREEMPT !SMP kernel that can hand the core away
for several ticks before the next micro-step. So what the module
delivers is a busy-wait between scheduler yields, not sub-tick pacing in
general. Say so in Config.in, Readme.md and the file header, and
explain the trade in the step_delay() comment.
@phedoreanu
phedoreanu force-pushed the gpiostep-subtick-busywait branch from 0e6424e to d446b61 Compare September 11, 2026 14:15
@phedoreanu

Copy link
Copy Markdown
Contributor Author

Measured — and on a real stepper this time, since the board I have drives its pan/tilt coils the same way.

Board: Hi3518EV200 (ARM926EJ-S), OpenIPC 4.9.37, CONFIG_HZ=100, # CONFIG_HIGH_RES_TIMERS is not set, # CONFIG_PREEMPT is not set, # CONFIG_SMP is not set — the same kernel class as the gk7205v500/v510 defconfigs that ship this package. The pan coil is a 4-wire stepper on GPIO 31-34, so the module ran with pan_gpios=31,32,33,34 tilt_gpios=58,59,60,61 and the moves went through the actual motor. majestic was streaming throughout (load 1.0-1.2).

Method: both modules built by make BOARD=hi3518ev200_lite br-gpiostep-openipcold from master (usleep_range() for every delay), new from this branch — and loaded in turn (vermagic 4.9.37 mod_unload ARMv5 p2v8 matched). Each row is gpiostep-ctl 40 0 <delay> then gpiostep-ctl -40 0 <delay>: 320 micro-steps per move, out and back, timed with busybox time. sys is the CPU the calling process spent inside the ioctl, i.e. in step_delay().

delay old: move, per micro-step, sys new: move, per micro-step, sys
1 ms 3.20 s, 10.0 ms, 0.06 s 0.45 s, 1.4 ms, 0.32 s
2 ms 3.20 s, 10.0 ms, 0.05 s 0.92-0.97 s, 2.9-3.0 ms, 0.52 s
3 ms 3.20 s, 10.0 ms, 0.05 s 3.21 s, 10.0 ms, 0.05 s
4 ms 3.20-3.22 s, 10.0 ms, 0.05 s 3.21-3.22 s, 10.0 ms, 0.05 s
9 ms 3.36 s, 10.5 ms, 0.05 s 3.34-3.39 s, 10.5 ms, 0.05 s
0 ms 0.00-0.01 s 0.00-0.01 s

Reading it:

  • old lands on 10 ms per micro-step at every delay from 1 to 9 ms. That is the usleep_range() quantisation the PR is about, reproduced in the kernel path on this class: 320 micro-steps take 3.2 s whether you ask for 1 ms or 9.
  • new honours 1 ms at 1.4 ms per micro-step and 2 ms at ~3 ms, then hands 3, 4 and 9 to the tick — the quarter-tick threshold, same as gpio-motors: spin only while the rounding error is material #2393.
  • The overrun on the busy-wait rows (1.4 for 1, ~3 for 2) is the cond_resched() handing the core to majestic between micro-steps. That is the "holds on an idle core, not under load" caveat, measured: the pacing degrades under load, but to 1.4-3 ms, not to the tick.
  • sys on the busy-wait rows is 0.32 s of a 0.45 s move (~70%) and 0.52 of 0.95 (~55%); on every sleeping row it is 0.05 s. So the busy-wait costs what it claims, and only where it claims.

Delay 0 — the review's premise doesn't hold, and neither did my first fix's wording. The inline comment said usleep_range(0, 1) forced a scheduler round trip per micro-step "before this PR", so a zero delay had a floor that the busy-wait branch removed. Measured: the old module finishes 320 micro-steps at delay 0 in 0.00-0.01 s too. usleep_range(0, 1) is an already-expired hrtimer and returns at once; zero has never paced the coil. The guard is still right — it keeps 0 out of the busy-wait path, where the signed comparison is the only thing between a negative and udelay() — but the commit message and the step_delay() comment now say what was measured instead of what was assumed (b9bc8be). If a real floor at 0 is wanted, that is a pre-existing behaviour and a separate decision (reject 0 in the ioctl, or clamp it to 1); I have not made it in this PR.

What I could not observe: whether the head actually tracked the coil at 1.4 ms per micro-step. Nobody was at the camera, and it is a different motor from the V510's anyway — so the motor-behaviour half stays with a v500/v510 owner, as agreed.

Raw output
1.15 1.19 0.71 2/83 3298
===== OLD
gpiostep: ready, pan=31,32,33,34 tilt=58,59,60,61 via /dev/motorDev
module=/tmp/gpiostep-old.ko steps=40 (x8 micro-steps per step, +/-)
--- delay 1 ms
real	0m 3.20s
user	0m 0.00s
sys	0m 0.06s
real	0m 3.20s
user	0m 0.06s
sys	0m 0.00s
--- delay 2 ms
real	0m 3.20s
user	0m 0.05s
sys	0m 0.00s
real	0m 3.20s
user	0m 0.05s
sys	0m 0.00s
--- delay 3 ms
real	0m 3.20s
user	0m 0.00s
sys	0m 0.05s
real	0m 3.20s
user	0m 0.00s
sys	0m 0.05s
--- delay 4 ms
real	0m 3.20s
user	0m 0.05s
sys	0m 0.00s
real	0m 3.22s
user	0m 0.06s
sys	0m 0.00s
--- delay 9 ms
real	0m 3.36s
user	0m 0.05s
sys	0m 0.00s
real	0m 3.36s
user	0m 0.06s
sys	0m 0.00s
--- delay 0 ms
real	0m 0.01s
user	0m 0.00s
sys	0m 0.00s
real	0m 0.00s
user	0m 0.00s
sys	0m 0.00s
===== NEW
gpiostep: ready, pan=31,32,33,34 tilt=58,59,60,61 via /dev/motorDev
module=/tmp/gpiostep-new.ko steps=40 (x8 micro-steps per step, +/-)
--- delay 1 ms
real	0m 0.45s
user	0m 0.00s
sys	0m 0.32s
real	0m 0.45s
user	0m 0.00s
sys	0m 0.31s
--- delay 2 ms
real	0m 0.97s
user	0m 0.00s
sys	0m 0.52s
real	0m 0.92s
user	0m 0.00s
sys	0m 0.51s
--- delay 3 ms
real	0m 3.21s
user	0m 0.05s
sys	0m 0.00s
real	0m 3.21s
user	0m 0.05s
sys	0m 0.00s
--- delay 4 ms
real	0m 3.22s
user	0m 0.05s
sys	0m 0.00s
real	0m 3.21s
user	0m 0.06s
sys	0m 0.00s
--- delay 9 ms
real	0m 3.39s
user	0m 0.06s
sys	0m 0.00s
real	0m 3.34s
user	0m 0.05s
sys	0m 0.00s
--- delay 0 ms
real	0m 0.00s
user	0m 0.00s
sys	0m 0.00s
real	0m 0.01s
user	0m 0.00s
sys	0m 0.00s
===== DONE
Test script (runs on the board)
#!/bin/sh
# Runs on the cam. Loads gpiostep.ko (old or new), drives the real pan
# stepper +N then -N at each delay, and prints wall / user / sys per move.
# sys time is the CPU the ioctl burnt in step_delay(); real is the move time.
KO=$1; STEPS=${2:-20}
PINS="pan_gpios=31,32,33,34 tilt_gpios=58,59,60,61"
rmmod gpiostep 2>/dev/null
insmod "$KO" $PINS || exit 1
dmesg | grep gpiostep | tail -1
echo "module=$KO steps=$STEPS (x8 micro-steps per step, +/-)"
for d in 1 2 3 4 9 0; do
	echo "--- delay $d ms"
	time gpiostep-ctl $STEPS 0 $d
	time gpiostep-ctl -$STEPS 0 $d
done 2>&1
rmmod gpiostep

All three items from the review are in: the gpiostep-ctl clamp and the delay-0 guard in b9bc8be, the "between scheduler yields" wording in Config.in, Readme.md and the header in d446b61. Rebased onto current master. On CI: the build, lint, shell-tests, gcc-compat and qodo-gate runs for this head all exist but sit at action_required — the fork-PR approval gate — so they need a maintainer's "Approve and run" before anything reports. Marking ready for review.

@phedoreanu
phedoreanu marked this pull request as ready for review September 11, 2026 14:16
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit d446b61

@openipc-ai openipc-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything from the review is in, and the measurements settle it.

  • delay 0 guard, unsigned cast gone, gpiostep-ctl clamped to INT_MAX / 1000 to match gpio-motors.c:243.
  • The idle-core caveat is in Config.in, the Readme and the file header, not just the comment.
  • The Hi3518EV200 run is the evidence I asked for: old pinned at 10.0 ms/micro-step across delays 1-9 ms, new delivering 1.4 ms and ~3 ms at 1-2 ms and handing 3/4/9 to the tick at 0.05 s sys. The cond_resched() overrun is visible and bounded, which is exactly the claim the docs now make.

And thank you for measuring the delay 0 premise instead of taking it from me — you were right, usleep_range(0, 1) is an already-expired hrtimer and zero never had a floor. Keeping the guard for the negative-vs-udelay() reason and leaving a real floor at 0 to a separate, explicit change is the right split.

Approving. The V510 motor-behaviour half stays open for an owner of that board, as agreed - it is not a condition on this.

@openipc-ai
openipc-ai merged commit 8563a80 into OpenIPC:master Sep 11, 2026
20 checks passed
openipc-ai pushed a commit that referenced this pull request Sep 11, 2026
delay_us() spun for any sub-tick delay, so its CPU cost rose exactly as its
benefit fell: at delay 9 on an HZ=100 kernel that is ~14s of pinned core per
200-step move to shave an 11% timing error, with every clock_gettime in the
spin a real syscall on cores where CONFIG_ARM_ARCH_TIMER_VCT_ACCESS is not set.

The sleep threshold is now a quarter tick instead of a whole one. Below a
quarter tick (delay 1-2 at HZ=100) sleeping would at least quadruple the step
period, so the spin stays - that is the range it was added for in #2247. From a
quarter tick up, usleep() and accept the rounding. That gives delay 3 and 4 back
to the 10ms tick, turning a 4.8s and 6.4s move into 16s, in exchange for not
pinning the core for the duration.

clock_gettime() is now checked inside the spin loop. An unchecked failure would
leave now stale and the loop would never terminate; on failure it falls back to
usleep() for the full delay, matching the pre-loop failure path. Over-waiting is
the safe direction for a stepper.

Measured on a Hi3518EV200 (HZ=100, no hrtimers, no preempt, no SMP - the same
kernel class as the gk7205v500/v510 boards that ship the package), majestic
streaming throughout, 1600 calls per row:

  delay   usleep      before (#2247)        after
  1 ms    9.996 ms    1.294 ms, 76% CPU     1.300 ms, 76% CPU
  2 ms    9.999 ms    2.504 ms, 76% CPU     2.689 ms, 70% CPU
  3 ms   10.013 ms    3.836 ms, 72% CPU    10.013 ms, 1.7% CPU
  4 ms   10.006 ms    4.915 ms, 73% CPU    10.010 ms, 1.7% CPU
  9 ms   10.400 ms    9.980 ms, 74% CPU    10.426 ms, 1.7% CPU

The spin rows overrun the requested delay because a spinning process is still
preempted at each tick by the encoder; that is the contrast with the kernel-side
busy-wait in #2394, which carries a cond_resched() for the opposite reason.

Motor behaviour on a GK7205V500/V510 board itself is still unmeasured; nobody in
the thread owns one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants