sim module firmware is now v4.60, (sim020.bin) it adds a better drain when T=0 exits abnormal (abort). Better waiting time floor, with faster ETU which doesnt shrink with WWT.\n Changes to i2c with SDA timings, also modified the fixed msleep(100) with an idle time waiting which leads to 107 ms off from every iteration.\n Added a ATR cache to remember which TA1 was autonegotiated. This impact i2c comms speed with sim module for same card. 'smart pps' is better documented.\n The sam_common.c now also logs 61xx and GET_RESPONSE :) and generic long-form ASN-1 lengths. trace list -t 7816 now also shows rdr frames in us. \n All in all, this should lead for 'emv reader -w' to go from 5.87sec to 2.78sec. 'hf seos sam' is down to 1.92sec. 'hw ping' is down to 0.57sec. A USB round trip is down from 20.8ms to 1.3ms. It should give a snappier end user experience

This commit is contained in:
iceman1001
2026-08-27 22:37:32 +02:00
parent d996b75578
commit 76181dfb8e
12 changed files with 283 additions and 17 deletions
+3
View File
@@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Added `sim020.bin` - v4.60 of sim module firmware, better T=0 handling and clock etu handling (@iceman1001)
- Fixed `hf seos sam` - now have a invalid pacs guard (@iceman1001)
- Changed i2c comms to auto-negotiation and use ATR-keyed rate cache for speedier smart card comms (@iceman1001)
- Changed `emv search` - now has better table output (@iceman1001)
- Changed client side polling. Should make client more spiffy (@iceman1001)
- Fixed `trace list -t 7816` - now handles contact vs contactless annotation better (@iceman1001)
+200 -4
View File
@@ -53,6 +53,24 @@ static uint8_t s_card_protocols = 0;
// sc_raw_device_cmd() runs per APDU, so report the choice once per card
static bool s_proto_announced = false;
// A negotiated rate lives in two places that reset independently: the module's
// UART divisor, which any I2C_Reset_EnterMainProgram() wipes, and the card,
// which only an RST pulse clears. Left alone the two drift apart and every
// exchange fails until something resets the card.
//
// So remember what was negotiated, keyed by the ATR it was negotiated against,
// and put it back after each ATR - the one window where PPS is legal
// (ISO/IEC 7816-3 clause 9). A different card brings a different ATR and drops
// the entry.
static struct {
uint8_t atr[sizeof(((smart_card_atr_t *)0)->atr)]; // what it was negotiated against
uint8_t atr_len;
uint8_t ta1; // 0 = nothing negotiated
uint8_t proto;
bool reapply; // off while SmartCardPPS negotiates
bool tried; // already negotiated against this ATR
} s_pps = { {0}, 0, 0, 0, true, false };
// try i2c bus recovery at 100kHz = 5us high, 5us low
void I2C_recovery(void) {
@@ -818,6 +836,21 @@ int I2C_get_version(uint8_t *major, uint8_t *minor) {
}
// Will read response from smart card module, retries 3 times to get the data.
static uint32_t s_trace_tick = 0;
void sc_log_trace_reset(void) {
s_trace_tick = GetTicks();
}
void sc_log_trace(const uint8_t *d, uint16_t len, bool reader2tag) {
uint32_t now = GetTicks();
if (s_trace_tick == 0) {
s_trace_tick = now;
}
LogTrace(d, len, s_trace_tick, now, NULL, reader2tag);
s_trace_tick = now;
}
bool sc_rx_bytes(uint8_t *dest, uint16_t *destlen, uint32_t wait) {
uint8_t i = 10;
@@ -920,6 +953,107 @@ uint8_t sc_raw_device_cmd(smartcard_command_t flags) {
return I2C_DEVICE_CMD_SEND;
}
// The protocol of TD1, which is what the card runs if nothing is negotiated.
static uint8_t atr_first_proto(const uint8_t *atr, uint8_t len) {
if ((len < 2) || ((atr[1] & 0x80) == 0)) {
return 0;
}
uint8_t i = 2;
if (atr[1] & 0x10) i++;
if (atr[1] & 0x20) i++;
if (atr[1] & 0x40) i++;
return (i < len) ? (uint8_t)(atr[i] & 0x0F) : 0;
}
// The fastest rate worth proposing to a card, or 0 for none.
//
// Two rules keep this safe, both learned on the bench rather than assumed:
//
// - Keep the Fi the card advertised and only lower Di. Proposing a different
// Fi is refused: a SAM advertising Fi=512 took the whole Fi=512 family and
// rejected every Fi=768/1024/1536/2048 offer.
// - R = Fi / (16 * Di) is the module's UART reload. It has to be a whole
// number or the sampling point drifts - that is the +3.2% which makes
// Fi=372 unusable beyond Di=1 - and it must not fall below the floor: R=8
// (31250 bit/s) transfers cleanly here, R=4 does not.
//
// A card with no TA1 offers nothing but the default, so nothing is proposed.
#define SC_PPS_MIN_RELOAD 8
static uint8_t sc_pps_best_ta1(const uint8_t *atr, uint8_t len) {
static const uint16_t fi_tab[16] = {372, 372, 558, 744, 1116, 1488, 1860, 0,
0, 512, 768, 1024, 1536, 2048, 0, 0
};
static const uint8_t di_tab[16] = {0, 1, 2, 4, 8, 16, 32, 64, 12, 20, 0, 0, 0, 0, 0, 0};
if ((len < 3) || ((atr[1] & 0x10) == 0)) {
return 0; // no TA1 - default only
}
uint8_t fi_idx = (uint8_t)((atr[2] >> 4) & 0x0F);
uint16_t f = fi_tab[fi_idx];
if (f == 0) {
return 0; // RFU
}
uint8_t best = 0;
uint16_t best_clocks = 372; // has to beat the default to be worth it
for (uint8_t di_idx = 1; di_idx < 16; di_idx++) {
uint8_t d = di_tab[di_idx];
if (d == 0) {
continue;
}
if ((f % (uint16_t)(16u * d)) != 0) {
continue; // divisor is not exact, the etu would drift
}
if ((f / (uint16_t)(16u * d)) < SC_PPS_MIN_RELOAD) {
continue; // faster than the module can receive
}
uint16_t clocks = (uint16_t)(f / d);
if (clocks >= best_clocks) {
continue;
}
best_clocks = clocks;
best = (uint8_t)((fi_idx << 4) | di_idx);
}
return best;
}
static bool sc_pps(uint8_t proto, uint8_t ta1) {
uint8_t req[2] = { (uint8_t)(proto & 0x0F), ta1 };
if (I2C_BufferWrite(req, sizeof(req), I2C_DEVICE_CMD_PPS, I2C_DEVICE_ADDRESS_MAIN) == false) {
return false;
}
uint8_t resp[8] = {0};
uint16_t len = sizeof(resp);
if ((sc_rx_bytes(resp, &len, SIM_WAIT_DELAY) == false) || (len < 3)) {
return false;
}
// resp is [ok][active protocol][ta1 in force]
return ((resp[0] == 1) && (resp[2] == ta1));
}
void sc_pps_remember(const uint8_t *atr, uint8_t atr_len, uint8_t proto, uint8_t ta1) {
if ((atr_len == 0) || (atr_len > sizeof(s_pps.atr))) {
return;
}
memcpy(s_pps.atr, atr, atr_len);
s_pps.atr_len = atr_len;
s_pps.ta1 = ta1;
s_pps.proto = proto;
}
void sc_pps_forget(void) {
s_pps.atr_len = 0;
s_pps.ta1 = 0;
s_pps.tried = false;
}
bool GetATR(smart_card_atr_t *card_ptr, bool verbose) {
if (card_ptr == NULL) {
@@ -985,7 +1119,55 @@ bool GetATR(smart_card_atr_t *card_ptr, bool verbose) {
}
if (verbose) {
LogTrace(card_ptr->atr, card_ptr->atr_len, 0, 0, NULL, false);
sc_log_trace(card_ptr->atr, card_ptr->atr_len, false);
}
// Same card as the one a rate was negotiated for? Put it back. This is the
// only moment a PPS is legal, and the module has just come up at the
// default, so card and module move together.
if (s_pps.reapply && s_pps.ta1 && (s_pps.atr_len == card_ptr->atr_len) &&
(memcmp(s_pps.atr, card_ptr->atr, s_pps.atr_len) == 0)) {
if (sc_pps(s_pps.proto, s_pps.ta1)) {
if (g_dbglevel >= DBG_INFO) {
Dbprintf("SC: rate restored, TA1 %02X", s_pps.ta1);
}
} else {
// Refused or lost: the card stays at the default per 9.1, so drop
// the entry rather than keep failing on every ATR from now on.
if (g_dbglevel >= DBG_ERROR) {
Dbprintf("SC: could not restore TA1 %02X, back to the default", s_pps.ta1);
}
sc_pps_forget();
}
} else {
bool same_card = (s_pps.atr_len == card_ptr->atr_len) &&
(memcmp(s_pps.atr, card_ptr->atr, s_pps.atr_len) == 0);
if (same_card == false) {
sc_pps_forget(); // different card, start over
}
// First sight of this card: ask for the best rate its ATR allows. Only
// once - a refusal is remembered so every later ATR does not retry it.
if (s_pps.reapply && (s_pps.tried == false)) {
uint8_t want = sc_pps_best_ta1(card_ptr->atr, card_ptr->atr_len);
memcpy(s_pps.atr, card_ptr->atr, card_ptr->atr_len);
s_pps.atr_len = card_ptr->atr_len;
s_pps.tried = true;
if (want && sc_pps(atr_first_proto(card_ptr->atr, card_ptr->atr_len), want)) {
s_pps.ta1 = want;
s_pps.proto = atr_first_proto(card_ptr->atr, card_ptr->atr_len);
if (g_dbglevel >= DBG_INFO) {
Dbprintf("SC: negotiated TA1 %02X", want);
}
}
}
}
return true;
@@ -1057,7 +1239,7 @@ void SmartCardRaw(const smart_card_raw_t *p) {
wait = I2C_ITERS_FOR_MS(ms);
}
LogTrace(p->data, p->len, 0, 0, NULL, true);
sc_log_trace(p->data, p->len, true);
bool res = I2C_BufferWrite(
p->data,
@@ -1078,7 +1260,7 @@ void SmartCardRaw(const smart_card_raw_t *p) {
len = ISO7816_MAX_FRAME;
res = sc_rx_bytes(resp, &len, wait);
if (res) {
LogTrace(resp, len, 0, 0, NULL, false);
sc_log_trace(resp, len, false);
} else {
len = 0;
}
@@ -1193,7 +1375,10 @@ void SmartCardPPS(const smart_card_pps_t *p) {
I2C_Reset_EnterMainProgram();
smart_card_atr_t card;
if (GetATR(&card, true) == false) {
s_pps.reapply = false; // this call is the negotiation
bool got_atr = GetATR(&card, true);
s_pps.reapply = true;
if (got_atr == false) {
reply_ng(CMD_SMART_PPS, PM3_ETIMEOUT, NULL, 0);
goto out;
}
@@ -1221,6 +1406,17 @@ void SmartCardPPS(const smart_card_pps_t *p) {
goto out;
}
// resp is [ok][active protocol][ta1 in force]. Remember a rate that is
// actually faster than the default so later ATRs can put it back; 0x11 is
// the default and means "forget what we had".
if (resp[0] == 1) {
if (resp[2] != 0x11) {
sc_pps_remember(card.atr, card.atr_len, resp[1], resp[2]);
} else {
sc_pps_forget();
}
}
reply_ng(CMD_SMART_PPS, PM3_SUCCESS, resp, 3);
out:
+7 -1
View File
@@ -66,7 +66,7 @@
// these were simply scaled down together. The others are padding - hold after
// SCL falls (spec 0.3 us) and SCL high width (spec 4 us) - so they get the
// standard mode minimum with margin instead of a full clock.
#define I2C_DELAY_SDA_US I2C_DELAY_1CLK_US
#define I2C_DELAY_SDA_US 15
#define I2C_DELAY_HOLD_US 2
#define I2C_DELAY_HIGH_US 6
@@ -112,6 +112,12 @@ bool I2C_WriteFW(const uint8_t *data, uint8_t len, uint8_t msb, uint8_t lsb, uin
// Which SIM module opcode a set of SC_RAW* flags asks for.
uint8_t sc_raw_device_cmd(smartcard_command_t flags);
// Log one smartcard frame, timestamped from the tick counter. Start is where
// the previous frame ended, so a Tag frame's span is how long the card took to
// answer and a Rdr frame's is how long the host took to ask.
void sc_log_trace(const uint8_t *d, uint16_t len, bool reader2tag);
void sc_log_trace_reset(void);
bool sc_rx_bytes(uint8_t *dest, uint16_t *destlen, uint32_t wait);
//
bool GetATR(smart_card_atr_t *card_ptr, bool verbose);
+21 -8
View File
@@ -79,7 +79,19 @@ uint16_t sam_bd_offset(const uint8_t *response, uint16_t response_len) {
// what tells a real response node apart from a 0xBD that happens to sit
// in the routing tail - without it a SAM whose scFlag were 0xBD would
// resolve to the wrong offset.
if ((uint16_t)(ofs + 2 + response[ofs + 1] + 2) == response_len) {
// Length is short form, or long form with one length byte (0x81 <len>),
// which is what an SNMP shaped reply over 127 bytes uses.
uint16_t hdr = 2;
uint16_t node_len = response[ofs + 1];
if (node_len == 0x81) {
if ((uint16_t)(ofs + 2) >= response_len) {
continue;
}
hdr = 3;
node_len = response[ofs + 2];
}
if ((uint16_t)(ofs + hdr + node_len + 2) == response_len) {
return ofs;
}
if (fallback == 0) {
@@ -168,9 +180,10 @@ uint16_t sam_response_payload(const uint8_t *rx, uint16_t rx_len, uint16_t *payl
*payload_len = 0;
// SNMP shaped reply carries a long form length: bd 81 <len> 8a 81 ...
if (((uint16_t)(ofs + 4) < rx_len) &&
(rx[ofs + 1] == 0x81) && (rx[ofs + 3] == 0x8a) && (rx[ofs + 4] == 0x81)) {
// An SNMP shaped reply over 127 bytes carries a long form length,
// bd 81 <len>. Read it from the length byte itself rather than matching one
// known inner tag, or any other node in that form parses as 0x81 + 2.
if (((uint16_t)(ofs + 2) < rx_len) && (rx[ofs + 1] == 0x81)) {
*payload_len = (uint16_t)(rx[ofs + 2] + 3);
@@ -230,7 +243,7 @@ int sam_rxtx(const uint8_t *data, uint16_t n, uint8_t *resp, uint16_t *resplen)
// The GET RESPONSE round below is ours, not the caller's, so log both
// halves of it. Without this the trace shows a case 3 command coming
// back with a full data answer, which T=0 cannot do.
LogTrace(resp, *resplen, 0, 0, NULL, false);
sc_log_trace(resp, *resplen, false);
} else {
// we done, return
goto out;
@@ -244,7 +257,7 @@ int sam_rxtx(const uint8_t *data, uint16_t n, uint8_t *resp, uint16_t *resplen)
}
uint8_t cmd_getresp[] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00, more_len};
LogTrace(cmd_getresp, sizeof(cmd_getresp), 0, 0, NULL, true);
sc_log_trace(cmd_getresp, sizeof(cmd_getresp), true);
res = I2C_BufferWrite(cmd_getresp, sizeof(cmd_getresp), I2C_DEVICE_CMD_SEND_T0, I2C_DEVICE_ADDRESS_MAIN);
if (res == false) {
@@ -347,7 +360,7 @@ int sam_send_payload_ex(
uint16_t length = SAM_TX_ASN1_PREFIX_LENGTH + SAM_TX_APDU_PREFIX_LENGTH + (uint8_t) * payload_len;
LogTrace(buf, length, 0, 0, NULL, true);
sc_log_trace(buf, length, true);
if (g_dbglevel >= DBG_INFO) {
DbpString("SAM REQUEST APDU: ");
Dbhexdump(length, buf, false);
@@ -360,7 +373,7 @@ int sam_send_payload_ex(
goto out;
}
LogTrace(response, *response_len, 0, 0, NULL, false);
sc_log_trace(response, *response_len, false);
if (g_dbglevel >= DBG_INFO) {
DbpString("SAM RESPONSE APDU: ");
Dbhexdump(*response_len, response, false);
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
41a0ec1cdd0036e44b28b3462f3de64f04bbf6d7456c7e969490fe6c8c6b5c8feb5746244ba8100d4d223760431cb6d9da5ffe824c1dcd7ef096869f8c0d93ba *client/resources/sim020.bin
+15
View File
@@ -2370,6 +2370,21 @@ static int CmdHfSeosSAM(const char *Cmd) {
// 07
} else if (d[0] == 0xbd && d[2] == 0xb3 && d[4] == 0xa0) {
const uint8_t *pacs = d + 6;
// The a0 content element normally starts with 80 <len> <PACS bits>.
// Some SAMs / cards return a status-only element with no access-bits
// field, e.g. a0 03 82 01 03 - here the first inner tag is 82, not 80.
// Don't parse the status byte as PACS (that yields a bogus
// "Invalid PACS value"); report that the card has no readable PACS.
if (pacs[0] != 0x80) {
PrintAndLogEx(WARNING, "No PACS/SIO access-bits returned by the SAM");
if (pacs[0] == 0x82 && pacs[1] == 0x01) {
PrintAndLogEx(INFO, "SAM content status: " _YELLOW_("0x%02X") " (no physicalAccessBits field)", pacs[2]);
}
if (verbose) {
print_hex(d, resp.length);
}
return PM3_ENOPACS;
}
const uint8_t pacs_length = pacs[1];
const uint8_t *pacs_data = pacs + 2;
int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
+9 -2
View File
@@ -622,7 +622,7 @@ static int CmdSmartUpgrade(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "smart upgrade",
"Upgrade RDV4 sim module firmware",
"smart upgrade -f sim017.bin"
"smart upgrade -f sim020.bin"
);
void *argtable[] = {
@@ -927,7 +927,14 @@ static int CmdSmartPPS(const char *Cmd) {
"\n"
"Note `smart raw -1` already switches a card to T=1 by itself when\n"
"the ATR offers it; this is for negotiating Fi/Di explicitly.\n"
"Needs SIM module firmware v4.51 or newer.",
"Needs SIM module firmware v4.51 or newer.\n"
"\n"
"The negotiated rate only holds for the rest of this session, and\n"
"only for commands that do not reset the card. Connecting a client\n"
"reads the module version, which reboots the module back to the\n"
"default rate while the card stays at the negotiated one - the next\n"
"command then fails until something resets the card. `smart info`,\n"
"or any `smart raw -s`, puts both back to the default.",
"smart pps -1 -> select T=1\n"
"smart pps -0 -> select T=0\n"
"smart pps -1 --ta1 96 -> select T=1 and F=512 / D=32"
+1 -1
View File
@@ -1519,7 +1519,7 @@ int CmdTraceList(const char *Cmd) {
}
if (protocol == ISO_7816_4)
PrintAndLogEx(INFO, _YELLOW_("ISO7816-4 / Smartcard") " - Timings n/a");
PrintAndLogEx(INFO, _YELLOW_("ISO7816-4 / Smartcard") " - Timings in ticks (1/1.5MHz == 0.67us)");
if (protocol == PROTO_CALYPSO)
PrintAndLogEx(INFO, _YELLOW_("Calypso") " - Timings n/a");
+24
View File
@@ -80,6 +80,30 @@ static uint64_t last_packet_time;
static bool dl_it(uint8_t *dest, uint32_t bytes, PacketResponseNG *response, size_t ms_timeout, bool show_warning, uint32_t rec_cmd);
// Wait until the comm thread has actually put a queued command on the wire.
// Callers used to sleep a fixed guess instead, which was reasonable when a
// command could sit in the buffer for a whole receive timeout; it is now sent
// within a millisecond or so.
bool WaitForTxIdle(uint32_t ms_timeout) {
uint64_t start = msclock();
for (;;) {
pthread_mutex_lock(&txBufferMutex);
bool pending = txBuffer_pending;
pthread_mutex_unlock(&txBufferMutex);
if (pending == false) {
return true;
}
if (msclock() - start >= ms_timeout) {
return false;
}
msleep(1);
}
}
// Simple alias to track usages linked to the Bootloader, these commands must not be migrated.
// - commands sent to enter bootloader mode as we might have to talk to old firmwares
// - commands sent to the bootloader as it only supports OLD frames (which will always be the case for old BL)
+1
View File
@@ -113,6 +113,7 @@ void StartReconnectProxmark(void);
size_t WaitForRawDataTimeout(uint8_t *buffer, size_t len, size_t ms_timeout, bool show_process, bool keep_raw_mode);
bool WaitForResponseTimeoutW(uint32_t cmd, PacketResponseNG *response, size_t ms_timeout, bool show_warning);
bool WaitForResponseTimeout(uint32_t cmd, PacketResponseNG *response, size_t ms_timeout);
bool WaitForTxIdle(uint32_t ms_timeout);
bool WaitForResponse(uint32_t cmd, PacketResponseNG *response);
int SetHfFieldTimeout(uint32_t timeout_sec, bool quiet);
+1 -1
View File
@@ -687,7 +687,7 @@ check_script:
if (g_session.pm3_present) {
clearCommandBuffer();
SendCommandNG(CMD_QUIT_SESSION, NULL, 0);
msleep(100); // Make sure command is sent before killing client
WaitForTxIdle(100); // make sure it really went out before killing the client
}
while (current_cmdscriptfile()) {