[sane-devel] gl124: the fast-feed and go-home motor tables are written but never used

Tero Kankaanperä tero at terokankaanpera.fi
Thu Sep 3 19:22:08 BST 2026


Hello list,

This a report my AI-assistant wanted me to forward to the list. I am
continuing the work on the Plustek OpticPro A320E backend and it found
something that it claims is a potential trap for gl124-based models.

Here goes:

Three observations about the gl124 branch, found while matching the Plustek
OpticPro A320E (07b3:1826) against its Windows driver on the USB bus. They
are not A320E-specific: all three are in code that every gl124 device runs,
which is why I am reporting them rather than just carrying a local patch.

Line numbers are from master at fcaa30a7.

Nothing here is urgent. Two of the three are dead writes today; the third is
latent and only bites if someone enables AUTO_GO_HOME on a gl124 device.


WHICH DEVICES THIS CONCERNS

All five models the gl124 branch serves, and all three findings apply to
every one of them: none of the code involved is behind a model condition,
and gl124_init_motor_regs_scan sets fast_dpi = yres unconditionally, which
is what makes the fast table a copy of the scan table everywhere.

     canon-lide-110    Canon LiDE 110
     canon-lide-120    Canon LiDE 120
     canon-lide-210    Canon LiDE 210
     canon-lide-220    Canon LiDE 220     (tables_model.cpp:820 notes
                                           "GL124 // or a compatible one")
     plustek-opticpro-a320e  Plustek OpticPro A320E   -- the one I have

The four Canon models are untested by me for every claim below; the A320E is
where the measurements come from. I would expect the LiDE models to behave
the same, because the code is the same and none of it consults the model,
but expecting is not measuring.


1. FASTFED IS NEVER SET, SO TABLE 4 IS NEVER RUN

gl124_init_motor_regs_scan builds a fast table, sends it to STOP_TABLE and
FAST_TABLE (gl124.cpp:530-531) and writes its length to FASTNO, FSHDEC and
FMOVNO (:533-535). But REG_0x02_FASTFED does not appear in gl124.cpp at all.
The other branches all take a position on that bit:

     gl842.cpp:301,303   set or clear it depending on ScanFlag::FEEDING
     gl843.cpp:702,704   the same
     gl846.cpp:363       clears it explicitly
     gl847.cpp:327       clears it explicitly
     gl124               never mentions it

The value therefore comes from the initialisation table and no scan phase
carries it. Table 4 is the fast-feed table, so it is never engaged, and
FMOVNO describes a table the ASIC does not run.

I measured this rather than only reading it. The vendor's driver loads its
own profile into tables 4 and 5 on this scanner (55555 -> 625 over 510
entries before the image scan; 41666 -> 666 at initialisation; the same
bytes at 200, 600 and 800 dpi). I put that profile into table 4, set FMOVNO
to its length (512, which is exactly the vendor's value), verified on the
bus that both reached the device, and ran an interleaved A/B with the
carriage under audio recording. Nothing changed: the feed before the image
took 517 ms against 516 ms, the return timeline was identical to the
millisecond, and the image did not shift by a pixel in any of four bands
over 2204 rows.

The bus shows why. The vendor's image phase starts at REG_0x02 = 0x18
(FASTFED|MTRPWR) and only then goes to 0x30: its feed is a separate fast
move, run from table 4. In this backend the feed happens inside the scan
session (feedl) and REG_0x02 is 0x90/0x94 throughout, so table 1 does the
work. That is a structural difference and not a bug in itself -- but it does
mean the fast table and FMOVNO are, on gl124, three writes and a 384-byte
transfer per scan that nothing consumes.


2. HOME_TABLE IS NEVER UPLOADED, BUT FMOVDEC IS WRITTEN

gl124.cpp:573 writes FMOVDEC, which is documented in gl843.cpp:321 as "the
number of deceleration steps in table 5 for auto-go-home". gl124 never sends
anything to HOME_TABLE. The other four branches do:

     gl842.cpp:348   scanner_send_slope_table(dev, sensor, HOME_TABLE, 
fast_table.table);
     gl843.cpp:749   the same
     gl846.cpp:401   the same
     gl847.cpp:366   the same

Today this is latent: nothing in master sets ScanFlag::AUTO_GO_HOME on a
gl124 path, so table 5 is never read either. It stops being latent the
moment someone does. If AGOHOME is armed on a gl124 device, the ASIC would
decelerate from a table that has never been written -- whatever the previous
session left at 0x10010000, or power-on state.

I have a reason to care: on the A320E the vendor drives the carriage home
with AGOHOME on every scan, so it is the natural thing to try, and my own
experiment with it (arming AGOHOME while NOTHOME was still set) drove the
carriage into the end stop and needed a power cycle. I traced that failure
to the two REG_0x02 bits being contradictory rather than to the missing
table, so I am not claiming the empty table 5 caused it -- only that
anyone who arms AGOHOME on gl124 will be relying on a table this backend
does not load.

The one-line fix mirrors the other branches:

     scanner_send_slope_table(dev, sensor, HOME_TABLE, fast_table.table);

I have not applied it, because on my device AGOHOME stays off and the change
would be untested in the only configuration where it matters.


3. FASTNO AND FSHDEC ARE TIED TO THE FAST TABLE, NOT THE SCAN TABLE

gl124.cpp:533-534 take FASTNO and FSHDEC from fast_table. Everywhere else
they come from the scan table:

     gl842.cpp:335, gl843.cpp:736, gl846.cpp:388, gl847.cpp:437

The vendor agrees with the majority: on this scanner FASTNO is 192, 6 and 4
at 200, 600 and 800 dpi -- that is STEPNO, the scan table's length -- while
FMOVNO is a constant 512, the fast table's. FSHDEC is documented at
gl843.cpp:359 as the deceleration after scanning, i.e. table 3, which is
built from the scan table on every branch including this one.

This is harmless in master because gl124 sets fast_dpi = yres, which makes
the two tables the same object. It stops being harmless the moment they
differ -- which is exactly what happens if anyone gives the fast move its
own profile, as the vendor does. I have changed it locally to follow the
scan table; no value changes on my device today.


A NOTE ON THE PADDING COMMENT AT genesys.cpp:527

scanner_send_slope_table pads the table to the full hardware size only for
GL841 and the LiDE 90, with the comment "BUG: do this on all gl842
scanners". On gl124 the addresses are fixed (0x10000000 + 0x4000 * table_nr)
and only table.size() bytes are written, so everything past the entries this
session uses is whatever the previous session left: a 200 dpi scan writes
192 entries, a 600 dpi scan after it writes 6 and leaves 186 of the older
ones standing. The vendor writes all five tables at their full 512 entries,
every time. Nothing reads the tail today, because every length register the
backend writes is table.size() -- but that is the same "nothing reads it
today" as items 1 and 2, and it is the one the comment already predicts.

I pad to 512 entries for the A320E locally and measured no change, which is
what one would expect.


WHAT I AM NOT CLAIMING

I have exactly one gl124 device, and it is the odd one out: the four Canon
models are what the branch was written for. Everything above is either a
code observation or a measurement on an A320E, so items 1 and 2 want a
second pair of eyes from someone with a LiDE 110, 120, 210 or 220 before
anything is changed in master. I am happy to prepare patches if the
direction looks right, but I would rather not push motor-path changes to
devices I cannot run.

The A320E work itself has progressed considerably and I am using the
scanner with the backend for actual work. The patch and package, is at

   https://terokankaanpera.fi/a320e/

Tero Kankaanpera

-- 
---
Tero Kankaanperä
https://terokankaanpera.fi




More information about the sane-devel mailing list