Using NFC Library in 'raw' APDU mode : how to set FWI/FWTX?

Custom device using nrf5340, with NFC card emulation functionality.

To use the NFC, I understand that I should use the supplied nfc_t4t library (binary) and its api. I have this working ok for type 4 card emulation using the NDEF application functionality built in to the library..

I now need to create an application that will emulate other functions, and so I am trying to the use the library in 'raw APDU' mode. This is basically working, but I have run into a timeout issue : certain elements of my APDU processing can take some 10's of ms, and this is apparently causing my nfc reader to timeout its request. This causes the library to have some issues : I get logs like this:

[00:05:13.375,488] <err> nfc_platform: Tried to read data pointer: 4 bytes, read 0.
[00:05:13.375,488] <err> nfc_platform: Reading nfc ring buffer failed, resetting ring buffer.

 I think these are basically when I do the nfc_t4t_response_pdu_send() call to send the response PDU. Any ideas on what this means for my operation?

The reader never manages to get a valid response, this seems to be related to the time it takes for my code to process the APDU (around 35ms for a basic select operation, due to it being sent to a ISO7816 contact card for processing...). In the NFC spec I see this:

"When the Card Emulator needs more time than the defined FWT to process the received block, it uses an S(WTX) request for a waiting time extension. An S(WTX) request contains a 1-byte INF field as specified in Table 84"

How can I tell the library that I want it to send this S(WTX) frame? It seems it needs to be sent for each received command? The only param I see to set is the FWI (which sets the max FWT as I understand it). When I try to set this to the max (before doing nfc_t4t_setup()) :

                uint32_t fwi = NFC_T4T_FWI_MAX_VAL_EMV;
                if ((ret=nfc_t4t_parameter_set(NFC_T4T_PARAM_FWI, &fwi, sizeof(fwi)))!=0) {
                    log_warn("nfcmgr: t4t fwi set fails (%d)",ret);
                }
Then it gives me an error -22
Doing a get of the FWI_MAX says its set to 4.
Is there a way to get the library to tell the reader that I want to increase the waiting timeout for a specific received command?
thanks
Parents
  • Then it gives me an error -22

    Ok, I have fixed my problem with nfc_t4t_parameter_set(), it needs the parameter value to be a uint8_t.

    So now I can set the FWI to be 8 max. By my calculation, this should make the reader apply a FWT of around 77ms

    (FWT = (256 x 16/Fc) x 2^FWI, Fc=13.56MHz, FWI=8 -> 77ms)

    The FWI is correctly taken into acount on the reader (it reports it as 8, and calculates the FWT=77ms)

    However, any delay in responding that is over 2-3ms fail to work.... and gets the same logs:

    [00:05:13.375,488] <err> nfc_platform: Tried to read data pointer: 4 bytes, read 0.
    [00:05:13.375,488] <err> nfc_platform: Reading nfc ring buffer failed, resetting ring buffer.

    So, maybe its not the reader timing out, but the library? Can anyone shed some light on the this lib? As its supplied only as a binary blob, I can't see what is actually happening....

  • Hi,

    Great to hear that you fixed the problem with nfc_t4t_parameter_set().

    I will ask about the other issue internally. Can you upload the complete device log?

    Best regards,
    Marte

  • Ok, I see that this option essentially calls my code in the IRQ context directly using platform_internal_irq.c - not surprising it ends up in an assert().

    By creating a 'test' callback function which is IRQ safe, and just makes a fixed response to the DATA_IND event using nfc_t4t_response_pdu_send() , the reader operates as expected.

    However, if there is any delay (more than a couple of ms) in generating the response (eg I use the system queue to schedule the response outside of the callback) then the reader does not manage to see the response correctly.

    I think this demonstrates that the problem is not in the platform.c or platform_internal.c code but in the library (as already hinted at in your previous responses).

    My callback code for the build case with CONFIG_NFC_THREAD_CALLBACK=n:

    static uint8_t _nok_resp[] = { 0x6a, 0x82};
    static void _apdu_cmd_test_resp(void* p_ctx) {
        struct _nfc_ctx* ctx = (struct _nfc_ctx*)p_ctx;
        if (ctx==NULL) {
            return;
        }
        int ret;
        if ((ret=nfc_t4t_response_pdu_send(_nok_resp, sizeof(_nok_resp)))!=0) {
            log_warn("nfcmgr: t4t apdu test resp NOT sent after %dms error (%d)", (gettime_ms()-ctx->lastRead), ret);
        } else {
            log_warn("nfcmgr:t4t apdu test resp sent after %dms", (gettime_ms()-ctx->lastRead));
        }
    }

    static void _apdu_cmd_test_print(void* p_ctx) {
        log_warn("nfcmgr: test event %d", (int32_t)p_ctx);
    }

    // ADPU type operation test callback, called directly on IRQ, just always responds with 'no'
    static void _nfc_t4t_test_cb_from_irq(void *context, nfc_t4t_event_t event, const uint8_t *data, size_t data_length, uint32_t flags) {
        struct _nfc_ctx* ctx = (struct _nfc_ctx*)context;
        if (ctx==NULL) {
            return;
        }
        switch(event) {        
            case NFC_T4T_EVENT_FIELD_ON: {
                ctx->nfc_active = true;
                wsched_request(_apdu_cmd_test_print, (void*)NFC_T4T_EVENT_FIELD_ON);
                break;
            }
            case NFC_T4T_EVENT_FIELD_OFF: {
                ///< External Reader polling stopped
                ctx->nfc_active = false;
                wsched_request(_apdu_cmd_test_print, (void*)NFC_T4T_EVENT_FIELD_OFF);
                break;
            }
            case NFC_T4T_EVENT_DATA_IND: {
                ctx->lastRead = gettime_ms();
                int ret=nfc_t4t_response_pdu_send(_nok_resp, sizeof(_nok_resp));
                wsched_request(_apdu_cmd_test_print, (void*)ret);
    //            wsched_request(_apdu_cmd_test_print, (void*)NFC_T4T_EVENT_DATA_IND);
                // will generate a response
    //            wsched_request(_apdu_cmd_test_resp, ctx);
                break;
            }
            case NFC_T4T_EVENT_DATA_TRANSMITTED: {
                // just to log
                wsched_request(_apdu_cmd_test_print, (void*)NFC_T4T_EVENT_DATA_TRANSMITTED);
                break;
            }
            default: {
                wsched_request(_apdu_cmd_test_print, (void*)event);
                break;
            }
        }
    }
    Note if the case for DATA_IND uses the  system work queue to schedule the  function _apdu_cmd_test_resp()  then the problem occurs, if it directly does pdu_send() then it does not.
  • any word back on this?

    I have rewritten my code to process the callback from the IRQ directly (CONFIG_NFC_THREAD_CALLBACK=n) instead of using platform_internal_thread.c and always get the same result (works if response sent immediately, bad NFC if takes > 3-4ms).

  • Hi, I'd really like to make some progress on this as its blocking my POCs for digital wallet solutions (access control, authentication etc) using the NFC channel.

    Any ideas on the way to move forward?

  • Hi,

    I am sorry for little feedback from our end. We are looking into a couple of alternatives, but unfortunately no conclusions yet.

    Regards,
    Terje

  • Hi

    Any progress? I'm worried this is going to turn out to be a hardware issue and need to know before we commit to mass production....

Reply Children
  • This is dragging on...

    To help you check it with the developers, I have taken the NCS2.9.0 sample from nrf/samples/nfc/record_text", and updated the main.c to the minimal code that will demonstrate this behaviour. 

    The code now:

    - uses T4T library, in APDU passing mode

    - sets FWI to 8

     - uses the "no thread callback" api by setting CONFIG_NFC_THREAD_CALLBACK=n in prj.conf (which configures the library to use the wrapper nrf/subsys/nfc/lib/platform_internal_irq.c, which calls the application callback function _nfc_t4t_apdu_cb() directly on the ISR from the NFC lib)

    - handles received cmd and generates response on the sysworkq in _process_apdu_cmd()

    - the response is just hardcoded to 0x6A82 to make the reader go to its next cmd.

    - before sending the response it waits for N ms (k_msleep()) to simulate the processing delays. 

    I have tried with 2 readers : an HID Omnikey 5032CL, and a Elatec TWNN4 Slim. With Windows 11, as soon as a tag is detected, windows automatically tries to interrogate it (security service) so its easy to see if it works or not. I used wireshark with the USB pcap plugin to see if the request responses are ok or not.

    If the delay is 0 -> all is ok.

    Anything more and the reader fails to get the response (and will retry the command). The HID shows the card as 'not present, failed' after each attempt, the elatec just sends nothing. 

    I also tried using the Android app TagInfo (from NXP) : this also fails to get responses but eventually gives up and reports just the basic ATS info.

    To test this, build the record_text sample, replacing the src/main.c and prj.conf with the attached files.... It should work on a nrf5340 dev board, although I haven't tried : I have our custom nrf5340 board, but the NFC part is straight from the dev kit schema with an on-PCB RF loop.

    0647.prj.conf

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <zephyr/kernel.h>
    #include <zephyr/sys/reboot.h>
    
    #include <nrfx_nfct.h>
    #include <nfc_t4t_lib.h>
    
    #define MAXLEN_APDU_CMD (512)
    static struct k_work _worker;
    static bool _cmd_buffer_in_use = false;
    static uint8_t _cmd_buffer[MAXLEN_APDU_CMD];
    static uint32_t _cmd_len=0;
    static int64_t _cmd_rx_ts = 0;
    static int64_t _cmd_proc_ts = 0;
    static int64_t _resp_tx_ts = 0;
    static uint8_t _resp_buffer[MAXLEN_APDU_CMD];
    static uint32_t _resp_len=0;
    
    // Process ADPU cmd off the ISR callback
    static void _process_apdu_cmd(struct k_work* work) {
    	_cmd_proc_ts = k_uptime_get();
    	// Wait a little to simulate card processing delay
    	// - 0 : no issues seen
    	// - 10 : fails with HID reader, android TagInfo shows the ATR but no other info?
    
    	k_msleep(0);
    	// Generate a 'ok' reply
    	_resp_buffer[0]=0x6a;
    	_resp_buffer[1]=0x82;
    	_resp_len = 2;
    	// Response ready, make debug string to print before 'releasing' cmd buffer
    	char cmd[128];
    	sprintf(cmd, "%02X:%02X:%02X:%02X lc=%d, %d bytes", _cmd_buffer[0],_cmd_buffer[1],_cmd_buffer[2],_cmd_buffer[3], _cmd_buffer[4], _cmd_len);
    
        // Command processed one way or the other, buffer can receive another one on ISR
        _cmd_len = 0;
        _cmd_buffer_in_use=false;       // can receive another command
    	int ret;
    	// So how long since rx of cmd until we send response?
    	_resp_tx_ts = k_uptime_get();
    	int32_t dt = (int32_t)(_resp_tx_ts - _cmd_rx_ts);
    	if ((ret=nfc_t4t_response_pdu_send(_resp_buffer, _resp_len))!=0) {
    		printk("nfcmgr: t4t apdu cmd [%s] error (%d) sending response len %d after %dms\n", cmd, ret, _resp_len, dt);
    	} else {
    		printk("nfcmgr: t4t apdu cmd [%s] sent response len %d after %dms\n", cmd, _resp_len, dt);		
    	}
    }
    
    // nfc_t4t callback for APDU operation (pdus are handled by specific registered APDU hanlding layer)
    // This callback is called directly from the nfc lib's ISR, so must assume its in IRQ context
    static void _nfc_t4t_apdu_cb(void *context, nfc_t4t_event_t event, const uint8_t *data, size_t data_length, uint32_t flags) {
        switch(event) {        
            case NFC_T4T_EVENT_FIELD_ON: {
                ///< External Reader polling detected.
    			//printk("FIELD ON\n");
                break;
            }
            case NFC_T4T_EVENT_FIELD_OFF: {
                ///< External Reader polling stopped
    			//printk("FIELD OFF\n");
                break;
            }
            case NFC_T4T_EVENT_DATA_IND: {
    			// Check buffer is not in use already
    			if (_cmd_buffer_in_use==false) {
    				// New APDU fragment received. Add to buffer if not at max
    				if ((_cmd_len+data_length)<MAXLEN_APDU_CMD) {
    					memcpy(&(_cmd_buffer[_cmd_len]), data, data_length);
    				} else {
    					// oopsie
    					printk("CMD TOO BIG!!!\n");
    				}
    				_cmd_len += data_length;
    				if ((flags & NFC_T4T_DI_FLAG_MORE)==0) {
    					//printk("CMD rxd, schedule processing\n");
    					// all of the command received, schedule to process this msg
    					_cmd_rx_ts = k_uptime_get();
    					_cmd_buffer_in_use=true;        // Only 1 command at a time
    					k_work_init(&_worker, &_process_apdu_cmd);
    					k_work_submit(&_worker);
    				}
    			} else {
    				printk("CMD when buffer in use!!!\n");
    			}
    			break;
            }
            case NFC_T4T_EVENT_DATA_TRANSMITTED: {
                // So now we can release the response buffer... but not reliable        
                // just to log
    			//printk("response sent!\n");
                break;
            }
            default: {
    			printk("unhandled event %d!\n", event);
                break;
            }
        }
    }
    
    bool _nfc_t4t_apdu_start() {
    	int ret;
    	//uint8_t selres = 0x00;      // TBD do we need to play with SEL_RES (aka SAK)?
    	//nfc_t4t_parameter_set(NFC_T4T_PARAM_SELRES, &selres, 1);
    
    	// Increase the wait time for any NFC readers as APDU processing may be slower (especially if talking to ISO7816 card)
    	uint8_t fwi = 8;   // NFC_T4T_FWI_MAX_VAL_EMV;     // ==7 for EMV spec. 8 is the max the lib will accept.
    /* Fails if done before emulation_start()???
    	if ((ret=nfc_t4t_parameter_set(NFC_T4T_PARAM_FWI, &fwi, sizeof(fwi)))!=0) {
    		printk("nfcmgr: t4t fwi set fails (%d)\n",ret);
    	}
    */
    	// register with nfc_t4t
    	if ((ret = nfc_t4t_setup(_nfc_t4t_apdu_cb, NULL))!=0) {
    //                if ((ret = nfc_t4t_setup(_nfc_t4t_test_cb_from_irq, &_ctx))!=0) {
    		printk("nfcmgr : t4t fail setup for APDU (%d)\n", ret);
    		// This is fatal error
    		return false;
    	}
    	if ((ret = nfc_t4t_emulation_start())!=0) {
    		printk("nfcmgr: t4t apdu emul start fails (%d)\n",ret);
    	}
    	// Try again
    	if ((ret=nfc_t4t_parameter_set(NFC_T4T_PARAM_FWI, &fwi, sizeof(fwi)))!=0) {
    		printk("nfcmgr: t4t fwi set after start fails (%d)\n",ret);
    	}
    	size_t plen=sizeof(fwi);
    	if ((ret = nfc_t4t_parameter_get(NFC_T4T_PARAM_FWI, &fwi, &plen))!=0) {
    		printk("nfcmgr: t4t fwi get fails (%d)\n",ret);
    	}
    	printk("nfcmgr : type 4 APDU started, fwi=%d\n", fwi);
    	return true;
    }
    
    int main(void)
    {
    	if (_nfc_t4t_apdu_start()) {
    		printk("NFC configuration done\n");
    	} else {
    		printk("NFC configuration FAILED\n");
    	}
    	for(;;) {
    		k_msleep(1000);
    	}
    }
    

Related