From 24e11e3d7253a9c19916b37021fba6ff622e6b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Sun, 27 Sep 2026 01:04:40 +0800 Subject: [PATCH 1/2] hf felica: support polling a selected system Add --sys to the FeliCa reader so repeated polling can activate an Express Transit card while keeping the RF field on. Co-Authored-By: Codex (GPT-6 Sol) Co-Authored-By: Codex (GPT-6) --- client/src/cmdhffelica.c | 55 +++++++++++++++++++++++++++++++++++++++- doc/commands.json | 10 +++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index da81bff04..115a6cd7b 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -66,6 +66,8 @@ #define FELICA_OPTIONAL_CMD_RETRIES 3U // Per FeliCa spec, Polling max response at 16 timeslots is ~25ms; keep extra margin. #define FELICA_POLLING_TIMEOUT_MS 100U +#define FELICA_READER_SYSTEM_POLL_TIMEOUT_MS 1000U +#define FELICA_READER_SYSTEM_POLL_ATTEMPTS 4U #define FELICA_SEAC_POLL_TIMEOUT_MS 200U #define FELICA_SEAC_POLL_RETRY_COUNT 5U #define FELICA_SEAC_POLL_FRAME_LEN 6U @@ -2685,27 +2687,78 @@ int read_felica_uid(bool loop, bool verbose) { return res; } +static int read_felica_system(uint16_t system_code, bool loop, bool verbose) { + uint8_t idm[8]; + uint8_t flags = FELICA_CONNECT | FELICA_CLEARTRACE | FELICA_NO_SELECT | + FELICA_NO_DISCONNECT | FELICA_APPEND_CRC | FELICA_RAW; + int res = PM3_ETIMEOUT; + + for (uint32_t attempt = 0; loop || attempt < FELICA_READER_SYSTEM_POLL_ATTEMPTS; attempt++) { + int status = send_polling(flags, system_code, FELICA_POLLING_REQUEST_SYSTEM_CODE, + FELICA_READER_SYSTEM_POLL_TIMEOUT_MS, 0, false, + idm, NULL, NULL); + flags &= ~(FELICA_CONNECT | FELICA_CLEARTRACE); + + if (status == PM3_SUCCESS) { + if (verbose) { + PrintAndLogEx(SUCCESS, "IDm: " _GREEN_("%s"), sprint_hex_inrow(idm, sizeof(idm))); + } + res = PM3_SUCCESS; + if (loop == false) { + break; + } + } + if (kbd_enter_pressed()) { + if (res != PM3_SUCCESS) { + res = PM3_EOPABORTED; + } + break; + } + } + + DropField(); + return res; +} + static int CmdHFFelicaReader(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf felica reader", "Act as a ISO 18092 / FeliCa reader. Look for FeliCa tags until Enter or the pm3 button is pressed", - "hf felica reader -@ -> Continuous mode"); + "hf felica reader -@\n" + "hf felica reader --sys 0003 -@ -> Continuous polling for system 0003"); void *argtable[] = { arg_param_begin, arg_lit0("s", "silent", "silent (no messages)"), arg_lit0("@", NULL, "optional - continuous reader mode"), + arg_str0(NULL, "sys", "", "poll a specific 2-byte system code (4 attempts, or continuous with -@)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); bool verbose = (arg_get_lit(ctx, 1) == false); bool cm = arg_get_lit(ctx, 2); + struct arg_str *system_arg = arg_get_str(ctx, 3); + bool has_system = system_arg->count > 0; + uint8_t system_bytes[2] = {0}; + int system_len = 0; + int parse_status = has_system ? CLIParamHexToBuf(system_arg, system_bytes, sizeof(system_bytes), &system_len) : PM3_SUCCESS; + bool invalid_system = parse_status != PM3_SUCCESS || + (has_system && (strlen(system_arg->sval[0]) != 4 || system_len != 2)); CLIParserFree(ctx); + if (invalid_system) { + PrintAndLogEx(ERR, "System code must be exactly 4 hex digits"); + return PM3_EINVARG; + } + if (cm) { PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); } + if (has_system) { + uint16_t system_code = felica_system_code_from_bytes(system_bytes); + return read_felica_system(system_code, cm, verbose); + } return read_felica_uid(cm, verbose); } diff --git a/doc/commands.json b/doc/commands.json index 00d9339b0..ec6be70da 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -3208,15 +3208,17 @@ "command": "hf felica reader", "description": "Act as a ISO 18092 / FeliCa reader. Look for FeliCa tags until Enter or the pm3 button is pressed", "notes": [ - "hf felica reader -@ -> Continuous mode" + "hf felica reader -@", + "hf felica reader --sys 0003 -@ -> Continuous polling for system 0003" ], "offline": false, "options": [ "-h, --help This help", "-s, --silent silent (no messages)", - "-@ optional - continuous reader mode" + "-@ optional - continuous reader mode", + "--sys poll a specific 2-byte system code (4 attempts, or continuous with -@)" ], - "usage": "hf felica reader [-hs@]" + "usage": "hf felica reader [-hs@] [--sys ]" }, "hf felica resetmode": { "command": "hf felica resetmode", @@ -16784,6 +16786,6 @@ "metadata": { "commands_extracted": 935, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-09-24T08:32:19+00:00" + "extracted_on": "2026-09-26T16:59:35+00:00" } } From a991b20a51d08d7b45fa08af999ec21070d432ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Sun, 27 Sep 2026 01:27:38 +0800 Subject: [PATCH 2/2] hf felica: handle system polling in ARM select Pass the requested system code to firmware selection so default and system-specific reader modes share one polling path. Keep the RF field active across retries for the system-specific mode. Co-Authored-By: Codex (GPT-6) --- armsrc/felica.c | 30 +++++++++++++++++++++--------- client/src/cmdhffelica.c | 24 +++++++++++++----------- include/iso18.h | 2 ++ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/armsrc/felica.c b/armsrc/felica.c index 2848d33e9..ed1bda1e9 100644 --- a/armsrc/felica.c +++ b/armsrc/felica.c @@ -29,7 +29,7 @@ #include "iso18.h" #define AddCrc(data, len) compute_crc(CRC_FELICA, (data), (len), (data)+(len)+1, (data)+(len)) -static uint8_t felica_select_card(felica_card_select_t *card); +static uint8_t felica_select_card(felica_card_select_t *card, uint16_t system_code, uint8_t request_code); //structure to hold outgoing NFC frame static uint8_t frameSpace[FELICA_MAX_RF_FRAME_SIZE]; @@ -244,15 +244,15 @@ void Process18092Byte(felica_frame_t *f, uint8_t bt, uint32_t byte_start_time) { * It expects 0-1 cards in the device's range. * return 0 if selection was successful */ -static uint8_t felica_select_card(felica_card_select_t *card) { +static uint8_t felica_select_card(felica_card_select_t *card, uint16_t system_code, uint8_t request_code) { // POLL command // 0xB2 0x4B = sync code // 0x06 = len // 0x00 = rfu - // 0xff = system code service - // 0xff = system code service - // 0x00 = request code + // system code service (high byte) + // system code service (low byte) + // request code // b7 = automatic switching of data rate // b6-b2 = reserved // b1 = fc/32 (414kbps) @@ -260,6 +260,9 @@ static uint8_t felica_select_card(felica_card_select_t *card) { // 0x00 = timeslot // 0x09 0x21 = crc uint8_t poll[10] = {0xb2, 0x4d, 0x06, FELICA_POLLING_REQ, 0xFF, 0xFF, 0x00, 0x00, 0x09, 0x21}; + poll[4] = (uint8_t)(system_code >> 8); + poll[5] = (uint8_t)system_code; + poll[6] = request_code; // Number of time slots offered to the card(s), the FeliCa polling TSN field. // Slot 0 only is what a single card wants, but when several cards share the @@ -692,8 +695,14 @@ void felica_sendraw(const PacketCommandNG *c) { bool do_connect = ((param & FELICA_CONNECT) == FELICA_CONNECT); bool no_disconnect = ((param & FELICA_NO_DISCONNECT) == FELICA_NO_DISCONNECT); + bool system_select = ((param & FELICA_SYSTEM_SELECT) == FELICA_SYSTEM_SELECT); bool replied = false; + if (system_select && (len != 2 || !do_connect || (param & (FELICA_RAW | FELICA_NO_SELECT)))) { + reply_ng(CMD_HF_FELICA_COMMAND, PM3_EINVARG, NULL, 0); + return; + } + // Signal probe. The FPGA hands us envelope peak-to-peak per 8 bit periods // instead of demodulated bits, so nothing will decode while this is on. // It is how you tell "tag out of range" apart from "demodulator not @@ -708,16 +717,17 @@ void felica_sendraw(const PacketCommandNG *c) { // Preserve compatibility with existing commands that do not send CONNECT: // set up reader path when starting from field-off state. - if (do_connect || felica_field_is_active() == false) { + if (!felica_field_is_active() || (do_connect && !system_select)) { iso18092_setup(FPGA_HF_ISO18092_FLAG_READER | FPGA_HF_ISO18092_FLAG_NOMOD); } if (do_connect && ((param & FELICA_NO_SELECT) != FELICA_NO_SELECT)) { // notify client selecting status. - // if failed selecting, turn off antenna and quit. + // on failed selection, retain the field only for system-specific polling. felica_card_select_t card = {0}; - uint8_t select_result = felica_select_card(&card); + uint16_t system_code = system_select ? ((uint16_t)payload[0] << 8) | payload[1] : 0xFFFF; + uint8_t select_result = felica_select_card(&card, system_code, system_select ? 0x01 : 0x00); int select_status = PM3_SUCCESS; switch (select_result) { @@ -744,7 +754,9 @@ void felica_sendraw(const PacketCommandNG *c) { reply_ng(CMD_HF_FELICA_COMMAND, select_status, (uint8_t *)&card, sizeof(felica_card_select_t)); replied = true; if (select_status != PM3_SUCCESS) { - felica_reset_frame_mode(); + if (!system_select || !no_disconnect) { + felica_reset_frame_mode(); + } return; } } diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 115a6cd7b..ef90a90e6 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -66,7 +66,7 @@ #define FELICA_OPTIONAL_CMD_RETRIES 3U // Per FeliCa spec, Polling max response at 16 timeslots is ~25ms; keep extra margin. #define FELICA_POLLING_TIMEOUT_MS 100U -#define FELICA_READER_SYSTEM_POLL_TIMEOUT_MS 1000U +#define FELICA_READER_SYSTEM_POLL_TIMEOUT_MS 2500U #define FELICA_READER_SYSTEM_POLL_ATTEMPTS 4U #define FELICA_SEAC_POLL_TIMEOUT_MS 200U #define FELICA_SEAC_POLL_RETRY_COUNT 5U @@ -468,6 +468,7 @@ typedef enum { static int CmdHelp(const char *Cmd); static void clear_and_send_command(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose); +static void clear_and_send_command_ex(uint8_t flags, uint16_t datalen, uint8_t *data, uint16_t numbits, bool verbose, bool normalize_frame); static int send_felica_payload_with_retries(uint8_t flags, uint16_t datalen, uint8_t *data, bool verbose, int expected_response_cmd, uint32_t timeout_ms, uint32_t retries, uint32_t backoff_ms, bool logging, PacketResponseNG *resp, const char *request_name); @@ -2688,20 +2689,21 @@ int read_felica_uid(bool loop, bool verbose) { } static int read_felica_system(uint16_t system_code, bool loop, bool verbose) { - uint8_t idm[8]; - uint8_t flags = FELICA_CONNECT | FELICA_CLEARTRACE | FELICA_NO_SELECT | - FELICA_NO_DISCONNECT | FELICA_APPEND_CRC | FELICA_RAW; + uint8_t system_bytes[2]; + felica_system_code_to_bytes(system_code, system_bytes); + const uint8_t flags = FELICA_CONNECT | FELICA_CLEARTRACE | FELICA_NO_DISCONNECT | FELICA_SYSTEM_SELECT; int res = PM3_ETIMEOUT; for (uint32_t attempt = 0; loop || attempt < FELICA_READER_SYSTEM_POLL_ATTEMPTS; attempt++) { - int status = send_polling(flags, system_code, FELICA_POLLING_REQUEST_SYSTEM_CODE, - FELICA_READER_SYSTEM_POLL_TIMEOUT_MS, 0, false, - idm, NULL, NULL); - flags &= ~(FELICA_CONNECT | FELICA_CLEARTRACE); - - if (status == PM3_SUCCESS) { + clear_and_send_command_ex(flags, sizeof(system_bytes), system_bytes, 0, false, false); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_FELICA_COMMAND, &resp, FELICA_READER_SYSTEM_POLL_TIMEOUT_MS) && + resp.status == PM3_SUCCESS && resp.length >= sizeof(felica_card_select_t)) { + felica_card_select_t card; + memcpy(&card, resp.data.asBytes, sizeof(card)); + set_last_known_card(card); if (verbose) { - PrintAndLogEx(SUCCESS, "IDm: " _GREEN_("%s"), sprint_hex_inrow(idm, sizeof(idm))); + PrintAndLogEx(SUCCESS, "IDm: " _GREEN_("%s"), sprint_hex_inrow(card.IDm, sizeof(card.IDm))); } res = PM3_SUCCESS; if (loop == false) { diff --git a/include/iso18.h b/include/iso18.h index 1fe78de64..b4b2918ca 100644 --- a/include/iso18.h +++ b/include/iso18.h @@ -90,6 +90,8 @@ typedef enum FELICA_COMMAND { // so reading distance and coupling can be measured rather than guessed FELICA_PROBE = (1 << 2), FELICA_RAW = (1 << 3), + // With CONNECT, raw[] holds the two-byte polling system code. + FELICA_SYSTEM_SELECT = (1 << 4), FELICA_APPEND_CRC = (1 << 5), FELICA_NO_SELECT = (1 << 6), FELICA_CLEARTRACE = (1 << 7),