since it still felt a bit slow, Claude came up with the idea of burst mode transfers. A new sim module firmware 'sim022.bin' and we now can auto-negotiate for TA1 = 0x95 which give us 125 000 bits/s which is almost 10x from the 10752 bits/s we were doing before. Thanks Claude!

This commit is contained in:
iceman1001
2026-08-28 01:16:11 +02:00
parent 9c962ed537
commit 377d7cd612
6 changed files with 76 additions and 30 deletions
+1
View File
@@ -3,6 +3,7 @@ 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 `sim022.bin` - enabled burst mode transfers, which allows us to do TA1=96 in speeds (@iceman1001)
- Fixed `hf felica liteauth` - empty long option names for `-c` and `-k` made argtable read past the string (found by ASAN on `--fulltext`) (@Msprg)
- 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)
+41 -9
View File
@@ -974,11 +974,17 @@ static uint8_t atr_first_proto(const uint8_t *atr, uint8_t len) {
// 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.
// Fi=372 unusable beyond Di=1 - and it must not fall below the floor.
//
// The floor is measured, not guessed. It was 8 while the module still waited
// out a turnaround guard before listening, which cost it the second byte of
// every answer above 31250 bit/s. With that guard applied only before
// transmitting (module v4.62), R=2 (125000 bit/s) is clean over repeated runs
// and R=1 still is not: at 16 clocks per etu a character is 192 instruction
// cycles, and the receive loop does not fit in that.
//
// A card with no TA1 offers nothing but the default, so nothing is proposed.
#define SC_PPS_MIN_RELOAD 8
#define SC_PPS_MIN_RELOAD 2
static uint8_t sc_pps_best_ta1(const uint8_t *atr, uint8_t len) {
@@ -1112,9 +1118,13 @@ bool GetATR(smart_card_atr_t *card_ptr, bool verbose) {
s_card_protocols = atr_protocols(card_ptr->atr, card_ptr->atr_len);
s_proto_announced = false;
if (g_dbglevel >= DBG_INFO) {
Dbprintf("SC: card offers%s%s"
// What the ATR advertises, and which of them the card actually runs
// until something negotiates otherwise. Saying only "offers T=0 T=1"
// reads like a state report when it is a capability list.
Dbprintf("SC: ATR offers%s%s, card runs T=%u"
, (s_card_protocols & SC_PROTO_T0) ? " T=0" : ""
, (s_card_protocols & SC_PROTO_T1) ? " T=1" : ""
, atr_first_proto(card_ptr->atr, card_ptr->atr_len)
);
}
@@ -1385,7 +1395,12 @@ void SmartCardPPS(const smart_card_pps_t *p) {
uint8_t req[2];
uint16_t reqlen = 1;
req[0] = (uint8_t)(p->protocol & 0x0F);
uint8_t want_proto = p->protocol;
if (want_proto == SC_PPS_PROTO_CARD_DEFAULT) {
want_proto = atr_first_proto(card.atr, card.atr_len);
}
req[0] = (uint8_t)(want_proto & 0x0F);
if (p->use_ta1) {
req[1] = p->ta1;
reqlen = 2;
@@ -1406,14 +1421,31 @@ 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".
// resp is [ok][active protocol][ta1 in force].
//
// Only a rate is worth remembering. A protocol override is a deliberate
// one-off - `smart pps` defaults to T=1, so asking for a rate alone
// switches the card's framing - and making that stick across every later
// ATR breaks anything that builds T=0 APDUs, the SAM commands included.
// Leave it to this session and do not cache it.
if (resp[0] == 1) {
if (resp[2] != 0x11) {
uint8_t card_proto = atr_first_proto(card.atr, card.atr_len);
if (resp[2] == 0x11) {
sc_pps_forget(); // back to the default rate
} else if (resp[1] == card_proto) {
sc_pps_remember(card.atr, card.atr_len, resp[1], resp[2]);
} else {
// rate negotiated alongside a protocol change: honour it now, but
// do not restore it later behind the user's back
sc_pps_forget();
if (g_dbglevel >= DBG_ERROR) {
Dbprintf("SC: T=%u selected, rate not remembered (card offers T=%u first)",
resp[1], card_proto);
}
}
}
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
9714ecc830e0dfb9180147c7c2300f0e8e1a5ce5a4b126f929010dd180b278a508dde0312eb2271e866650baf9ccaa6cc7f5cc9612c914a31cbc8efd83471c90 *client/resources/sim022.bin
+27 -20
View File
@@ -476,10 +476,10 @@ static int CmdSmartRaw(const char *Cmd) {
CLIParserInit(&ctx, "smart raw",
"Sends raw bytes to card",
"smart raw -s -0 -d 00a404000e315041592e5359532e4444463031 -> `1PAY.SYS.DDF01` PPSE directory with get ATR\n"
"smart raw -0 -d 00a404000e325041592e5359532e4444463031 -> `2PAY.SYS.DDF01` PPSE directory\n"
"smart raw -0 -t -d 00a4040007a0000000041010 -> Mastercard\n"
"smart raw -0 -t -d 00a4040007a0000000031010 -> Visa\n"
"smart raw -1 -s -d 00a404000e325041592e5359532e444446303100 -> PPSE over T=1\n"
"smart raw --t0 -d 00a404000e325041592e5359532e4444463031 -> `2PAY.SYS.DDF01` PPSE directory\n"
"smart raw --t0 -t -d 00a4040007a0000000041010 -> Mastercard\n"
"smart raw --t0 -t -d 00a4040007a0000000031010 -> Visa\n"
"smart raw --t1 -s -d 00a404000e325041592e5359532e444446303100 -> PPSE over T=1\n"
" (T=1 carries the whole APDU, so case 4 needs its Le)"
);
@@ -489,8 +489,8 @@ static int CmdSmartRaw(const char *Cmd) {
arg_lit0("a", NULL, "active smartcard without select (reset sc module)"),
arg_lit0("s", NULL, "active smartcard with select (get ATR)"),
arg_lit0("t", "tlv", "executes TLV decoder if it possible"),
arg_lit0("0", NULL, "use protocol T=0"),
arg_lit0("1", NULL, "use protocol T=1 (needs SIM module fw v4.51+)"),
arg_lit0(NULL, "t0", "use protocol T=0"),
arg_lit0(NULL, "t1", "use protocol T=1 (needs SIM module fw v4.51+)"),
arg_int0(NULL, "timeout", "<ms>", "Timeout in MS waiting for SIM to respond. (def 337ms)"),
arg_str1("d", "data", "<hex>", "bytes to send"),
arg_param_end
@@ -921,29 +921,34 @@ static int CmdSmartPPS(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "smart pps",
"Run an ISO 7816-3 protocol and parameter selection exchange.\n"
"Run an ISO 7816-3 protocol and parameter selection exchange.\n""\n"
"With neither --t0 nor --t1 the card keeps the protocol its ATR\n"
"says it runs, so --ta1 on its own changes the rate and nothing else.\n"
"The card is reset and its ATR read first, because PPS is only\n"
"legal in the window straight after the ATR.\n"
"\n"
"Note `smart raw -1` already switches a card to T=1 by itself when\n"
"Note `smart raw --t1` 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.\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"
"A negotiated rate is remembered against this card's ATR and put\n"
"back after every later ATR, so it survives resets and reconnects.\n"
"A different card drops it, and `--ta1 11` forgets it.\n"
"\n"
"A protocol change is not remembered: it applies until the next\n"
"reset only, so it cannot silently break commands that build for\n"
"the other protocol.",
"smart pps --ta1 93 -> rate only, framing left alone\n"
"smart pps --ta1 11 -> back to the default rate\n"
"smart pps --t1 -> select T=1\n"
"smart pps --t0 -> select T=0\n"
"smart pps --t1 --ta1 96 -> select T=1 and F=512 / D=32"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("0", NULL, "select protocol T=0"),
arg_lit0("1", NULL, "select protocol T=1 (default)"),
arg_lit0(NULL, "t0", "select protocol T=0"),
arg_lit0(NULL, "t1", "select protocol T=1"),
arg_str0(NULL, "ta1", "<hex>", "also negotiate this TA1 (FI << 4 | DI)"),
arg_param_end
};
@@ -968,7 +973,9 @@ static int CmdSmartPPS(const char *Cmd) {
}
smart_card_pps_t payload = {
.protocol = use_t0 ? 0 : 1,
// Neither flag: negotiate the rate and leave the framing alone. The
// device resolves this to whatever the card's ATR says it runs.
.protocol = use_t0 ? 0 : (use_t1 ? 1 : SC_PPS_PROTO_CARD_DEFAULT),
.ta1 = (ta1len > 0) ? ta1[0] : 0x11,
.use_ta1 = (ta1len > 0) ? 1 : 0,
};
+6 -1
View File
@@ -513,8 +513,13 @@ typedef struct {
uint8_t data[];
} PACKED smart_card_raw_t;
// A PPS has to name a protocol - PPS0 has no "leave it alone" encoding - so
// when the caller only wants a rate, ask for the one the card already runs and
// the exchange changes nothing but Fi/Di.
#define SC_PPS_PROTO_CARD_DEFAULT 0xFF
typedef struct {
uint8_t protocol; // T to select, 0 or 1
uint8_t protocol; // T to select, 0 or 1, or SC_PPS_PROTO_CARD_DEFAULT
uint8_t ta1; // FI << 4 | DI, only read when use_ta1 is set
uint8_t use_ta1;
} PACKED smart_card_pps_t;