## armsrc: smartcard / I2C correctness + SAM compatibility fixes
A focused pass over `armsrc/i2c.{c,h}` and `armsrc/i2c_direct.c` addressing correctness bugs (NULL deref on alloc failure, unbounded recursion, write-failure silently falling through to read), and removing two long-standing SAM-compatibility blockers (`SmartCardSetBaud` was a stub; float in firmware hot path).
### Fixed
- **`SmartCardRaw`: BigBuf_calloc result was unchecked.** NULL deref on memory pressure crashed the firmware. Now bails with `PM3_EMALLOC` before touching `resp[]`.
- **`SmartCardRaw`: `I2C_BufferWrite` failure was only acted on at `dbglevel > 3`.** Lower debug levels silently fell through to `sc_rx_bytes()` reading from a card we never wrote to. The `reply_ng` / `goto OUT` path now runs on any write failure; the `Dbprintf` is the only thing gated on dbglevel.
- **`SmartCardRaw`: replaced float division** `(p->wait_delay * 1000) / 3.07` with integer math via `uint64_t`. Avoids pulling `__aeabi_fdiv` into the ARM image and removes overflow at large `wait_delay` values: `wait = (ms * 100000 + 153) / 307`.
- **`SmartCardUpgrade`: BigBuf_calloc result was unchecked.** Now bails with `PM3_EMALLOC`. Renamed local `verfiydata` typo to `verifydata`. Failure messages now include the offset where the upgrade aborted (`Writing failed at offset 0x%04X` instead of `Writing failed`).
- **`I2C_BufferRead`: rejected `len == 0` but accepted `len == 1`**, which cannot represent the SIM module's 2-byte BE length header (returned -1 cast through int16_t). Tightened to `len < 2`.
- **`WaitSCL_L_timeout`: `return (delay == 0)` was dead code** — post-decrement underflows `delay` to `UINT32_MAX` after the loop, so the expression was always false. Replaced with explicit `return false` and corrected the comment that claimed an 1800 ms cap (actual cap is 1200 ms).
- **`i2c.h`: SIM_WAIT_DELAY comment was wrong** — `150000 * 3.07us = 460 ms`, not 270 ms. Updated comment to match the actual value; constant unchanged.
- **`SmartCardDirectSend`: BigBuf_calloc result for `resp[]` was unchecked.** The next `resp[0] = prepend` would NULL-deref on alloc failure. Now bails.
- **`SmartCardDirectSend`: GET RESPONSE (61xx) chain recursed without bound**, allocating a fresh `smart_card_raw_t` from BigBuf each round. A misbehaving card returning 61xx every time would wedge the device. Added `depth` parameter capped at `SC_DIRECT_MAX_DEPTH` (8); both call sites updated. The GET RESPONSE inner allocation is also now NULL-checked.
- **`SmartCardDirectSend`: same write-failure-silent fix** as `SmartCardRaw`.
- **`SmartCardDirectSend`: same float → integer-math conversion** as `SmartCardRaw`.
### Added
- **`SmartCardSetBaud` is now functional.** Was an empty stub (wired through `pm3_cmd.h` / `appmain.c` dispatch but did nothing). Now sends `I2C_DEVICE_CMD_SETBAUD` (0x04) to the SIM module's main address.
### Notes for follow-up
- `SmartCardSetBaud` is now functional on the firmware side, but no client command exposes it — `client/src/cmdsmartcard.c` has no `setbaud` subcommand. A separate change can wire `CMD_SMART_SETBAUD` into the client if needed; leaving it out of this patch keeps it firmware-only and safe to backport.
- The proxmark3 SIM module's stock asm firmware (`sim013`) silently drops `CMD_SETBAUD`. gentilkiwi's `sim_c` v4.50+ also currently dispatches it to a no-op. Full PPS follow-through requires a SIM-firmware-side patch in addition to this commit.
- Mid-byte SCL stretch timeouts in `I2C_SendByte` / `I2C_ReadByte` are still swallowed (return void / -1, both ignored at most callers). Fixing this requires changing those signatures and verifying every caller — out of scope here, merits its own PR.
Debian Forky's gcc-arm-none-eabi uses picolibc as the default C library, replacing the old newlib-nano.
Picolibc registers itself with GCC via a .specs file.
That specs file automatically injects its own picolibc.ld into every link step — unless GCC sees a user-supplied -T flag at the GCC driver level.
https://github.com/picolibc/picolibc/blob/main/doc/linking.md
The selectaid_response buffer in `hf 14a simaid` was capped at 100
bytes, which is well below the ISO 14443A frame maximum of 256 bytes
and prevents users from emulating tags whose SELECT AID response is
larger than a trivial status word.
This bumps selectaid_response from 100 to 256 in all three locations
(ARM packet handler, client locals, client payload struct) along with
the argparse help text. getdata_response is left at 100. The payload
struct overlays PM3_CMD_DATA_SIZE (512) via packet->data.asBytes;
with the new sizes it totals 435 bytes, so it still fits.
Pairs with the earlier tosend bounds check / DYNAMIC_MODULATION_BUFFER2
fix, which raised the actual transmit ceiling to the 256-byte frame
limit — this change lets callers supply a response up to that limit.
Currently there is no bounds checking on the to send buffer which
results in larger requests overflowing the buffer and corrupting the
CRC. This fixes that by adding bounds checking. It also increases
DYNAMIC_MODULATION_BUFFER2_SIZE to support frames up to 256 bytes
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.
Improved hf secc to use default apdu reply from the json file and not the hardcoded one (used as fallback now).
Removed old hardcoded functions no longer in use.