Issue origin:
Commit 6b7665ed5 "Added live fc/cn update to hf iclass tagsim" added a data_available() poll inside the per-byte DMA loop of GetIso15693CommandFromReader so the ARM could drop out of RF-listen and process live emulator updates.
Before that commit, that tight loop had no USB poll at all — only gotFrame / BUTTON_PRESS / WDT_HIT. Verified via git show 6b7665ed5^:armsrc/iso15693.c.
Why it shows up on sim -t 3/6/7: those are the FULL sim modes that share do_iclass_simulation. Between reader commands the decoder sits in STATE_READER_UNSYNCD, so the gated poll at iso15693.c:1570-1575 fires every byte (reading UDP peripheral registers). With DMA filling at ~1 byte / ~19 µs, the added USB register reads plus jitter occasionally push the CPU past the 90% lag threshold → behindBy 461 with DMA_BUFFER_SIZE=512.
Commit fb8f94fa2 narrowed the gate to UNSYNCD to stop mid-frame exits, but the per-byte poll itself is still what's new on that path.
Fix:
New mode constant in include/iclass_cmd.h:
#define ICLASS_SIM_MODE_FULL_LIVE 8 // FULL + allow USB interrupt for live emul updates
Treat it identically to ICLASS_SIM_MODE_FULL everywhere except for the poll gate.
Add a flag param to GetIso15693CommandFromReader — e.g. bool allow_usb_interrupt in iso15693.c:1495 and iso15693.h:42. Wrap the poll:
if (allow_usb_interrupt &&
(dr->state == STATE_READER_UNSYNCD ||
dr->state == STATE_READER_AWAIT_1ST_FALLING_EDGE_OF_SOF) &&
data_available()) { ... }
Pass true only for live mode in do_iclass_simulation iclass.c:502:
bool live = (simulationMode == ICLASS_SIM_MODE_FULL_LIVE);
len = GetIso15693CommandFromReader(receivedCmd, MAX_FRAME_SIZE, &reader_eof_time, live);
The len == -2 drain block stays but becomes dead code for non-live modes (never returns -2).
Client side: cmdhficlass.c:1687 (CmdHFiClassTagSim) sends ICLASS_SIM_MODE_FULL_LIVE. CmdHFiClassSim -t 3/6/7 keeps sending ICLASS_SIM_MODE_FULL / _GLITCH / _GLITCH_KEY.
Other callers (iso15693.c:2270, iclass.c:1121 = reader-attack sim) pass false.
Result:
hf iclass sim -t 3/6/7 → byte-inner loop is back to its pre-tagsim shape → no blow-buffer abort.
hf iclass tagsim → keeps live update ability; still has the overhead, but that's the trade-off the feature needs.
Imported updates from legbrute hashcat modules to speed up hf iclass legbrute
1. Bitslicing — biggest win (~32×)
Kernel stores each cipher register (l, r, b, t) as 32 parallel 1-bit lanes in u32s (m64000_a3-pure.cl:147-201, bs_iclass_tick) and computes 32 MACs per tick. The CPU doMAC_brute does 1 at a time. A 64-bit bitslice port would give ~64× per core; AVX2 gets 256×. This is the single largest speedup lever.
2. Early-reject after 8 output ticks
In m64000_sxx (m64000_a3-pure.cl:534-535) the kernel breaks out as soon as the first MAC byte can't match. doMAC_brute always produces the full 32 output bits before memcmp. Comparing byte-by-byte as bits are produced saves ~3× on the output phase since 255/256 keys fail after byte 0.
3. Pre-expanded y_ccnr bit array
Kernel expands the 96 input bits into a flat array once (m64000_a3-pure.cl:239-247) and reuses it for every candidate. suc_bytes in cipher.c:181 re-does b >>= 1 shifts for every key. Pre-expanding lets the inner loop be branch-free and vectorizable.
4. Widen lanes to 256/512 via AVX2/AVX-512. The bitslice code is written against a single uint64_t lane type — swapping for __m256i/__m512i (or an abstracted bs_word_t) gives 4×/8× throughput on hosts that support it, with scalar u64 fallback on ARM/older x86. NEON gives 2× for ARM.