From d9de20fa4bb0a36052927b55c4185caa204c5c4d Mon Sep 17 00:00:00 2001 From: pwpiwi Date: Sat, 12 Jan 2019 13:28:26 +0100 Subject: [PATCH] Fix 15 snoop (#752) * fixing hf 15: implement hf 15 snoop * rename hf 15 record to hf 15 snoop * speedup sampling / decoding: * new FPGA mode FPGA_HF_READER_RX_XCORR_AMPLITUDE implements amplitude(ci, cq) on FPGA * inlining the decoders in iso15693.c * inlining memcpy/memset in LogTrace() * giving up the moving correlator for SOF in Handle15693SamplesFromTag * decode more of EOF in Handle15693SamplesFromTag() * some refactoring --- CHANGELOG.md | 4 +- armsrc/BigBuf.c | 31 +- armsrc/appmain.c | 6 +- armsrc/apps.h | 11 - armsrc/fpgaloader.h | 63 ++-- armsrc/iclass.c | 60 ++-- armsrc/iso14443a.c | 14 +- armsrc/iso14443b.c | 36 +- armsrc/iso15693.c | 742 +++++++++++++++++++++++----------------- armsrc/iso15693.h | 24 ++ client/cmdhf.c | 11 +- client/cmdhf15.c | 88 +++-- common/iso15693tools.h | 53 --- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/fpga_hf.v | 4 +- fpga/hi_read_rx_xcorr.v | 136 +++++--- include/usb_cmd.h | 2 +- 17 files changed, 713 insertions(+), 572 deletions(-) create mode 100644 armsrc/iso15693.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 19db5195..f2c285b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac ### Fixed - AC-Mode decoding for HitagS -- Wrong UID at HitagS simulation +- Wrong UID at HitagS simulation +- 'hf 15 sim' now works as expected (piwi) ### Added - Support Standard Communication Mode in HITAG S @@ -24,6 +25,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added `hf fido` `assert` and `make` commands from fido2 protocol (authenticatorMakeCredential and authenticatorGetAssertion) (Merlok) - Added `lf paradox clone` to clone a Paradox card - Added `emv` commmands working for both contactless and smart cards (Merlok) +- Added 'hf 15 snoop' (piwi) ## [v3.1.0][2018-10-10] diff --git a/armsrc/BigBuf.c b/armsrc/BigBuf.c index 4b1264b6..e2f51311 100644 --- a/armsrc/BigBuf.c +++ b/armsrc/BigBuf.c @@ -36,8 +36,8 @@ static uint16_t BigBuf_hi = BIGBUF_SIZE; static uint8_t *emulator_memory = NULL; // trace related variables -static uint16_t traceLen = 0; -int tracing = 1; //Last global one.. todo static? +static uint32_t traceLen = 0; +static bool tracing = true; // get the address of BigBuf uint8_t *BigBuf_get_addr(void) @@ -66,7 +66,7 @@ void BigBuf_Clear(void) // clear ALL of BigBuf void BigBuf_Clear_ext(bool verbose) { - memset(BigBuf,0,BIGBUF_SIZE); + memset(BigBuf, 0, BIGBUF_SIZE); if (verbose) Dbprintf("Buffer cleared (%i bytes)",BIGBUF_SIZE); } @@ -76,7 +76,7 @@ void BigBuf_Clear_EM(void){ void BigBuf_Clear_keep_EM(void) { - memset(BigBuf,0,BigBuf_hi); + memset(BigBuf, 0, BigBuf_hi); } // allocate a chunk of memory from BigBuf. We allocate high memory first. The unallocated memory @@ -162,8 +162,8 @@ bool RAMFUNC LogTrace(const uint8_t *btBytes, uint16_t iLen, uint32_t timestamp_ uint8_t *trace = BigBuf_get_addr(); - uint16_t num_paritybytes = (iLen-1)/8 + 1; // number of valid paritybytes in *parity - uint16_t duration = timestamp_end - timestamp_start; + uint32_t num_paritybytes = (iLen-1)/8 + 1; // number of valid paritybytes in *parity + uint32_t duration = timestamp_end - timestamp_start; // Return when trace is full uint16_t max_traceLen = BigBuf_max_traceLen(); @@ -200,19 +200,23 @@ bool RAMFUNC LogTrace(const uint8_t *btBytes, uint16_t iLen, uint32_t timestamp_ // data bytes if (btBytes != NULL && iLen != 0) { - memcpy(trace + traceLen, btBytes, iLen); + for (int i = 0; i < iLen; i++) { + trace[traceLen++] = *btBytes++; + } } - traceLen += iLen; // parity bytes if (num_paritybytes != 0) { if (parity != NULL) { - memcpy(trace + traceLen, parity, num_paritybytes); + for (int i = 0; i < num_paritybytes; i++) { + trace[traceLen++] = *parity++; + } } else { - memset(trace + traceLen, 0x00, num_paritybytes); + for (int i = 0; i < num_paritybytes; i++) { + trace[traceLen++] = 0x00; + } } } - traceLen += num_paritybytes; return true; } @@ -259,8 +263,9 @@ int LogTraceHitag(const uint8_t * btBytes, int iBits, int iSamples, uint32_t dwP trace[traceLen++] = ((dwParity >> 24) & 0xff); trace[traceLen++] = iBits; - memcpy(trace + traceLen, btBytes, iLen); - traceLen += iLen; + for (int i = 0; i < iLen; i++) { + trace[traceLen++] = *btBytes++; + } return true; } diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 58c979db..1348ef04 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -24,6 +24,7 @@ #include "legicrfsim.h" #include "hitag2.h" #include "hitagS.h" +#include "iso15693.h" #include "lfsampling.h" #include "BigBuf.h" #include "mifareutil.h" @@ -1115,8 +1116,9 @@ void UsbPacketReceived(uint8_t *packet, int len) case CMD_ACQUIRE_RAW_ADC_SAMPLES_ISO_15693: AcquireRawAdcSamplesIso15693(); break; - case CMD_RECORD_RAW_ADC_SAMPLES_ISO_15693: - RecordRawAdcSamplesIso15693(); + + case CMD_SNOOP_ISO_15693: + SnoopIso15693(); break; case CMD_ISO_15693_COMMAND: diff --git a/armsrc/apps.h b/armsrc/apps.h index b9b1f3de..fad9e6eb 100644 --- a/armsrc/apps.h +++ b/armsrc/apps.h @@ -25,7 +25,6 @@ extern const uint8_t OddByteParity[256]; extern int rsamples; // = 0; -extern int tracing; // = TRUE; extern uint8_t trigger; // This may be used (sparingly) to declare a function to be copied to @@ -101,7 +100,6 @@ void RAMFUNC SnoopIso14443b(void); void SendRawCommand14443B(uint32_t, uint32_t, uint8_t, uint8_t[]); // Also used in iclass.c -bool RAMFUNC LogTrace(const uint8_t *btBytes, uint16_t len, uint32_t timestamp_start, uint32_t timestamp_end, uint8_t *parity, bool readerToTag); void GetParity(const uint8_t *pbtCmd, uint16_t len, uint8_t *parity); void RAMFUNC SniffMifare(uint8_t param); @@ -150,15 +148,6 @@ void OnSuccess(); void OnError(uint8_t reason); -/// iso15693.h -void RecordRawAdcSamplesIso15693(void); -void AcquireRawAdcSamplesIso15693(void); -void ReaderIso15693(uint32_t parameter); // Simulate an ISO15693 reader - greg -void SimTagIso15693(uint32_t parameter, uint8_t *uid); // simulate an ISO15693 tag - greg -void BruteforceIso15693Afi(uint32_t speed); // find an AFI of a tag - atrox -void DirectTag15693Command(uint32_t datalen,uint32_t speed, uint32_t recv, uint8_t data[]); // send arbitrary commands from CLI - atrox -void SetDebugIso15693(uint32_t flag); - /// iclass.h void RAMFUNC SnoopIClass(void); void SimulateIClass(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain); diff --git a/armsrc/fpgaloader.h b/armsrc/fpgaloader.h index 5aff6505..0600067e 100644 --- a/armsrc/fpgaloader.h +++ b/armsrc/fpgaloader.h @@ -24,7 +24,7 @@ void SetupSpi(int mode); bool FpgaSetupSscDma(uint8_t *buf, uint16_t sample_count); void Fpga_print_status(); int FpgaGetCurrent(); -#define FpgaDisableSscDma(void) AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTDIS; +#define FpgaDisableSscDma(void) AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTDIS; #define FpgaEnableSscDma(void) AT91C_BASE_PDC_SSC->PDC_PTCR = AT91C_PDC_RXTEN; void SetAdcMuxFor(uint32_t whichGpio); @@ -33,46 +33,47 @@ void SetAdcMuxFor(uint32_t whichGpio); #define FPGA_BITSTREAM_HF 2 // Definitions for the FPGA commands. -#define FPGA_CMD_SET_CONFREG (1<<12) -#define FPGA_CMD_SET_DIVISOR (2<<12) -#define FPGA_CMD_SET_USER_BYTE1 (3<<12) +#define FPGA_CMD_SET_CONFREG (1<<12) +#define FPGA_CMD_SET_DIVISOR (2<<12) +#define FPGA_CMD_SET_USER_BYTE1 (3<<12) // Definitions for the FPGA configuration word. // LF -#define FPGA_MAJOR_MODE_LF_ADC (0<<5) -#define FPGA_MAJOR_MODE_LF_EDGE_DETECT (1<<5) -#define FPGA_MAJOR_MODE_LF_PASSTHRU (2<<5) +#define FPGA_MAJOR_MODE_LF_ADC (0<<5) +#define FPGA_MAJOR_MODE_LF_EDGE_DETECT (1<<5) +#define FPGA_MAJOR_MODE_LF_PASSTHRU (2<<5) // HF -#define FPGA_MAJOR_MODE_HF_READER_TX (0<<5) -#define FPGA_MAJOR_MODE_HF_READER_RX_XCORR (1<<5) -#define FPGA_MAJOR_MODE_HF_SIMULATOR (2<<5) -#define FPGA_MAJOR_MODE_HF_ISO14443A (3<<5) -#define FPGA_MAJOR_MODE_HF_SNOOP (4<<5) +#define FPGA_MAJOR_MODE_HF_READER_TX (0<<5) +#define FPGA_MAJOR_MODE_HF_READER_RX_XCORR (1<<5) +#define FPGA_MAJOR_MODE_HF_SIMULATOR (2<<5) +#define FPGA_MAJOR_MODE_HF_ISO14443A (3<<5) +#define FPGA_MAJOR_MODE_HF_SNOOP (4<<5) // BOTH -#define FPGA_MAJOR_MODE_OFF (7<<5) +#define FPGA_MAJOR_MODE_OFF (7<<5) // Options for LF_ADC -#define FPGA_LF_ADC_READER_FIELD (1<<0) +#define FPGA_LF_ADC_READER_FIELD (1<<0) // Options for LF_EDGE_DETECT -#define FPGA_CMD_SET_EDGE_DETECT_THRESHOLD FPGA_CMD_SET_USER_BYTE1 -#define FPGA_LF_EDGE_DETECT_READER_FIELD (1<<0) -#define FPGA_LF_EDGE_DETECT_TOGGLE_MODE (1<<1) +#define FPGA_CMD_SET_EDGE_DETECT_THRESHOLD FPGA_CMD_SET_USER_BYTE1 +#define FPGA_LF_EDGE_DETECT_READER_FIELD (1<<0) +#define FPGA_LF_EDGE_DETECT_TOGGLE_MODE (1<<1) // Options for the HF reader, tx to tag -#define FPGA_HF_READER_TX_SHALLOW_MOD (1<<0) +#define FPGA_HF_READER_TX_SHALLOW_MOD (1<<0) // Options for the HF reader, correlating against rx from tag -#define FPGA_HF_READER_RX_XCORR_848_KHZ (1<<0) -#define FPGA_HF_READER_RX_XCORR_SNOOP (1<<1) -#define FPGA_HF_READER_RX_XCORR_QUARTER_FREQ (1<<2) +#define FPGA_HF_READER_RX_XCORR_848_KHZ (1<<0) +#define FPGA_HF_READER_RX_XCORR_SNOOP (1<<1) +#define FPGA_HF_READER_RX_XCORR_QUARTER_FREQ (1<<2) +#define FPGA_HF_READER_RX_XCORR_AMPLITUDE (1<<3) // Options for the HF simulated tag, how to modulate -#define FPGA_HF_SIMULATOR_NO_MODULATION (0<<0) -#define FPGA_HF_SIMULATOR_MODULATE_BPSK (1<<0) -#define FPGA_HF_SIMULATOR_MODULATE_212K (2<<0) -#define FPGA_HF_SIMULATOR_MODULATE_424K (4<<0) -#define FPGA_HF_SIMULATOR_MODULATE_424K_8BIT 0x5//101 +#define FPGA_HF_SIMULATOR_NO_MODULATION (0<<0) +#define FPGA_HF_SIMULATOR_MODULATE_BPSK (1<<0) +#define FPGA_HF_SIMULATOR_MODULATE_212K (2<<0) +#define FPGA_HF_SIMULATOR_MODULATE_424K (4<<0) +#define FPGA_HF_SIMULATOR_MODULATE_424K_8BIT 0x5//101 // Options for ISO14443A -#define FPGA_HF_ISO14443A_SNIFFER (0<<0) -#define FPGA_HF_ISO14443A_TAGSIM_LISTEN (1<<0) -#define FPGA_HF_ISO14443A_TAGSIM_MOD (2<<0) -#define FPGA_HF_ISO14443A_READER_LISTEN (3<<0) -#define FPGA_HF_ISO14443A_READER_MOD (4<<0) +#define FPGA_HF_ISO14443A_SNIFFER (0<<0) +#define FPGA_HF_ISO14443A_TAGSIM_LISTEN (1<<0) +#define FPGA_HF_ISO14443A_TAGSIM_MOD (2<<0) +#define FPGA_HF_ISO14443A_READER_LISTEN (3<<0) +#define FPGA_HF_ISO14443A_READER_MOD (4<<0) #endif diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 1591a062..d27fc1c6 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -751,12 +751,9 @@ void RAMFUNC SnoopIClass(void) //if(!LogTrace(Uart.output,Uart.byteCnt, rsamples, Uart.parityBits,true)) break; //if(!LogTrace(NULL, 0, Uart.endTime*16 - DELAY_READER_AIR2ARM_AS_SNIFFER, 0, true)) break; - if(tracing) { - uint8_t parity[MAX_PARITY_SIZE]; - GetParity(Uart.output, Uart.byteCnt, parity); - LogTrace(Uart.output,Uart.byteCnt, time_start, time_stop, parity, true); - } - + uint8_t parity[MAX_PARITY_SIZE]; + GetParity(Uart.output, Uart.byteCnt, parity); + LogTrace(Uart.output,Uart.byteCnt, time_start, time_stop, parity, true); /* And ready to receive another command. */ Uart.state = STATE_UNSYNCD; @@ -779,11 +776,9 @@ void RAMFUNC SnoopIClass(void) rsamples = samples - Demod.samples; LED_B_ON(); - if(tracing) { - uint8_t parity[MAX_PARITY_SIZE]; - GetParity(Demod.output, Demod.len, parity); - LogTrace(Demod.output, Demod.len, time_start, time_stop, parity, false); - } + uint8_t parity[MAX_PARITY_SIZE]; + GetParity(Demod.output, Demod.len, parity); + LogTrace(Demod.output, Demod.len, time_start, time_stop, parity, false); // And ready to receive another response. memset(&Demod, 0, sizeof(Demod)); @@ -1322,20 +1317,17 @@ int doIClassSimulation( int simulationMode, uint8_t *reader_mac_buf) t2r_time = GetCountSspClk(); } - if (tracing) { - uint8_t parity[MAX_PARITY_SIZE]; - GetParity(receivedCmd, len, parity); - LogTrace(receivedCmd,len, (r2t_time-time_0)<< 4, (r2t_time-time_0) << 4, parity, true); - - if (trace_data != NULL) { - GetParity(trace_data, trace_data_size, parity); - LogTrace(trace_data, trace_data_size, (t2r_time-time_0) << 4, (t2r_time-time_0) << 4, parity, false); - } - if(!tracing) { - DbpString("Trace full"); - //break; - } + uint8_t parity[MAX_PARITY_SIZE]; + GetParity(receivedCmd, len, parity); + LogTrace(receivedCmd,len, (r2t_time-time_0)<< 4, (r2t_time-time_0) << 4, parity, true); + if (trace_data != NULL) { + GetParity(trace_data, trace_data_size, parity); + LogTrace(trace_data, trace_data_size, (t2r_time-time_0) << 4, (t2r_time-time_0) << 4, parity, false); + } + if(!get_tracing()) { + DbpString("Trace full"); + //break; } } @@ -1509,11 +1501,9 @@ void ReaderTransmitIClass(uint8_t* frame, int len) LED_A_ON(); // Store reader command in buffer - if (tracing) { - uint8_t par[MAX_PARITY_SIZE]; - GetParity(frame, len, par); - LogTrace(frame, len, rsamples, rsamples, par, true); - } + uint8_t par[MAX_PARITY_SIZE]; + GetParity(frame, len, par); + LogTrace(frame, len, rsamples, rsamples, par, true); } //----------------------------------------------------------------------------- @@ -1569,11 +1559,9 @@ int ReaderReceiveIClass(uint8_t* receivedAnswer) int samples = 0; if (!GetIClassAnswer(receivedAnswer,160,&samples,0)) return false; rsamples += samples; - if (tracing) { - uint8_t parity[MAX_PARITY_SIZE]; - GetParity(receivedAnswer, Demod.len, parity); - LogTrace(receivedAnswer,Demod.len,rsamples,rsamples,parity,false); - } + uint8_t parity[MAX_PARITY_SIZE]; + GetParity(receivedAnswer, Demod.len, parity); + LogTrace(receivedAnswer,Demod.len,rsamples,rsamples,parity,false); if(samples == 0) return false; return Demod.len; } @@ -1715,7 +1703,7 @@ void ReaderIClass(uint8_t arg0) { // if only looking for one card try 2 times if we missed it the first time if (try_once && tryCnt > 2) break; tryCnt++; - if(!tracing) { + if(!get_tracing()) { DbpString("Trace full"); break; } @@ -1828,7 +1816,7 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { WDT_HIT(); - if(!tracing) { + if(!get_tracing()) { DbpString("Trace full"); break; } diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 7bf8f5af..f5fcc91c 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1220,7 +1220,7 @@ void SimulateIso14443aTag(int tagType, int uid_1st, int uid_2nd, byte_t* data) EmSendPrecompiledCmd(p_response); } - if (!tracing) { + if (!get_tracing()) { Dbprintf("Trace Full. Simulation stopped."); break; } @@ -1619,9 +1619,7 @@ void ReaderTransmitBitsPar(uint8_t* frame, uint16_t bits, uint8_t *par, uint32_t LED_A_ON(); // Log reader command in trace buffer - if (tracing) { - LogTrace(frame, nbytes(bits), LastTimeProxToAirStart*16 + DELAY_ARM2AIR_AS_READER, (LastTimeProxToAirStart + LastProxToAirDuration)*16 + DELAY_ARM2AIR_AS_READER, par, true); - } + LogTrace(frame, nbytes(bits), LastTimeProxToAirStart*16 + DELAY_ARM2AIR_AS_READER, (LastTimeProxToAirStart + LastProxToAirDuration)*16 + DELAY_ARM2AIR_AS_READER, par, true); } @@ -1652,9 +1650,7 @@ void ReaderTransmit(uint8_t* frame, uint16_t len, uint32_t *timing) static int ReaderReceiveOffset(uint8_t* receivedAnswer, uint16_t offset, uint8_t *parity) { if (!GetIso14443aAnswerFromTag(receivedAnswer, parity, offset)) return false; - if (tracing) { - LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false); - } + LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false); return Demod.len; } @@ -1662,9 +1658,7 @@ static int ReaderReceiveOffset(uint8_t* receivedAnswer, uint16_t offset, uint8_t int ReaderReceive(uint8_t *receivedAnswer, uint8_t *parity) { if (!GetIso14443aAnswerFromTag(receivedAnswer, parity, 0)) return false; - if (tracing) { - LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false); - } + LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false); return Demod.len; } diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index cb8567fa..76d7a075 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -386,10 +386,7 @@ void SimulateIso14443bTag(void) break; } - if (tracing) { - uint8_t parity[MAX_PARITY_SIZE]; - LogTrace(receivedCmd, len, 0, 0, parity, true); - } + LogTrace(receivedCmd, len, 0, 0, NULL, true); // Good, look at the command now. if ( (len == sizeof(cmd1) && memcmp(receivedCmd, cmd1, len) == 0) @@ -463,10 +460,7 @@ void SimulateIso14443bTag(void) } // trace the response: - if (tracing) { - uint8_t parity[MAX_PARITY_SIZE]; - LogTrace(resp, respLen, 0, 0, parity, false); - } + LogTrace(resp, respLen, 0, 0, NULL, false); } } @@ -763,9 +757,8 @@ static void GetSamplesFor14443bDemod(int n, bool quiet) if (!quiet) Dbprintf("max behindby = %d, samples = %d, gotFrame = %d, Demod.len = %d, Demod.sumI = %d, Demod.sumQ = %d", maxBehindBy, samples, gotFrame, Demod.len, Demod.sumI, Demod.sumQ); //Tracing - if (tracing && Demod.len > 0) { - uint8_t parity[MAX_PARITY_SIZE]; - LogTrace(Demod.output, Demod.len, 0, 0, parity, false); + if (Demod.len > 0) { + LogTrace(Demod.output, Demod.len, 0, 0, NULL, false); } } @@ -858,10 +851,7 @@ static void CodeAndTransmit14443bAsReader(const uint8_t *cmd, int len) { CodeIso14443bAsReader(cmd, len); TransmitFor14443b(); - if (tracing) { - uint8_t parity[MAX_PARITY_SIZE]; - LogTrace(cmd,len, 0, 0, parity, true); - } + LogTrace(cmd,len, 0, 0, NULL, true); } /* Sends an APDU to the tag @@ -1153,7 +1143,6 @@ void RAMFUNC SnoopIso14443b(void) upTo = dmaBuf; lastRxCounter = ISO14443B_DMA_BUFFER_SIZE; FpgaSetupSscDma((uint8_t*) dmaBuf, ISO14443B_DMA_BUFFER_SIZE); - uint8_t parity[MAX_PARITY_SIZE]; bool TagIsActive = false; bool ReaderIsActive = false; @@ -1198,9 +1187,7 @@ void RAMFUNC SnoopIso14443b(void) if (!TagIsActive) { // no need to try decoding reader data if the tag is sending if(Handle14443bUartBit(ci & 0x01)) { triggered = true; - if(tracing) { - LogTrace(Uart.output, Uart.byteCnt, samples, samples, parity, true); - } + LogTrace(Uart.output, Uart.byteCnt, samples, samples, NULL, true); /* And ready to receive another command. */ UartReset(); /* And also reset the demod code, which might have been */ @@ -1209,9 +1196,7 @@ void RAMFUNC SnoopIso14443b(void) } if(Handle14443bUartBit(cq & 0x01)) { triggered = true; - if(tracing) { - LogTrace(Uart.output, Uart.byteCnt, samples, samples, parity, true); - } + LogTrace(Uart.output, Uart.byteCnt, samples, samples, NULL, true); /* And ready to receive another command. */ UartReset(); /* And also reset the demod code, which might have been */ @@ -1223,13 +1208,8 @@ void RAMFUNC SnoopIso14443b(void) if(!ReaderIsActive && triggered) { // no need to try decoding tag data if the reader is sending or not yet triggered if(Handle14443bSamplesDemod(ci/2, cq/2)) { - //Use samples as a time measurement - if(tracing) - { - uint8_t parity[MAX_PARITY_SIZE]; - LogTrace(Demod.output, Demod.len, samples, samples, parity, false); - } + LogTrace(Demod.output, Demod.len, samples, samples, NULL, false); // And ready to receive another response. DemodReset(); } diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index da2aab69..f6868297 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -47,9 +47,10 @@ // *) add anti-collision support for inventory-commands // *) read security status of a block // *) sniffing and simulation do not support two subcarrier modes. -// *) remove or refactor code under "depricated" +// *) remove or refactor code under "deprecated" // *) document all the functions +#include "iso15693.h" #include "proxmark3.h" #include "util.h" @@ -58,6 +59,7 @@ #include "iso15693tools.h" #include "protocols.h" #include "cmd.h" +#include "BigBuf.h" #define arraylen(x) (sizeof(x)/sizeof((x)[0])) @@ -68,27 +70,20 @@ static int DEBUG = 0; // This section basicly contains transmission and receiving of bits /////////////////////////////////////////////////////////////////////// -#define FrameSOF Iso15693FrameSOF -#define Logic0 Iso15693Logic0 -#define Logic1 Iso15693Logic1 -#define FrameEOF Iso15693FrameEOF - #define Crc(data,datalen) Iso15693Crc(data,datalen) #define AddCrc(data,datalen) Iso15693AddCrc(data,datalen) #define sprintUID(target,uid) Iso15693sprintUID(target,uid) -// approximate amplitude=sqrt(ci^2+cq^2) by amplitude = max(|ci|,|cq|) + 1/2*min(|ci|,|cq|) -#define AMPLITUDE(ci, cq) (MAX(ABS(ci), ABS(cq)) + MIN(ABS(ci), ABS(cq))/2) - // buffers -#define ISO15693_DMA_BUFFER_SIZE 128 -#define ISO15693_MAX_RESPONSE_LENGTH 36 // allows read single block with the maximum block size of 256bits. Read multiple blocks not supported yet -#define ISO15693_MAX_COMMAND_LENGTH 45 // allows write single block with the maximum block size of 256bits. Write multiple blocks not supported yet +#define ISO15693_DMA_BUFFER_SIZE 2048 // must be a power of 2 +#define ISO15693_MAX_RESPONSE_LENGTH 36 // allows read single block with the maximum block size of 256bits. Read multiple blocks not supported yet +#define ISO15693_MAX_COMMAND_LENGTH 45 // allows write single block with the maximum block size of 256bits. Write multiple blocks not supported yet // timing. Delays in SSP_CLK ticks. #define DELAY_READER_TO_ARM 8 #define DELAY_ARM_TO_READER 1 #define DELAY_ISO15693_VCD_TO_VICC 132 // 132/423.75kHz = 311.5us from end of EOF to start of tag response +#define DELAY_ISO15693_VICC_TO_VCD 1017 // 1017/3.39MHz = 300us between end of tag response and next reader command // --------------------------- // Signal Processing @@ -271,11 +266,13 @@ static void CodeIso15693AsTag(uint8_t *cmd, int n) // Transmit the command (to the tag) that was placed in cmd[]. -static void TransmitTo15693Tag(const uint8_t *cmd, int len) +static void TransmitTo15693Tag(const uint8_t *cmd, int len, uint32_t start_time) { FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER_TX); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); + while (GetCountSspClk() < start_time); + LED_B_ON(); for(int c = 0; c < len; ) { if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { @@ -302,6 +299,7 @@ static void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t start } while (GetCountSspClk() < (start_time & 0xfffffff8)) ; + AT91C_BASE_SSC->SSC_THR = 0x00; // clear TXRDY LED_C_ON(); @@ -329,7 +327,7 @@ static void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t start //============================================================================= // An ISO 15693 decoder for tag responses (one subcarrier only). -// Uses cross correlation to identify the SOF, each bit, and EOF. +// Uses cross correlation to identify each bit and EOF. // This function is called 8 times per bit (every 2 subcarrier cycles). // Subcarrier frequency fs is 424kHz, 1/fs = 2,36us, // i.e. function is called every 4,72us @@ -341,16 +339,15 @@ static void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t start // false if we are still waiting for some more //============================================================================= -#define SUBCARRIER_DETECT_THRESHOLD 2 -#define SOF_CORRELATOR_LEN (1<<5) +#define NOISE_THRESHOLD 30 // don't try to correlate noise typedef struct DecodeTag { enum { - STATE_TAG_UNSYNCD, - STATE_TAG_AWAIT_SOF_1, - STATE_TAG_AWAIT_SOF_2, + STATE_TAG_SOF_LOW, + STATE_TAG_SOF_HIGH, + STATE_TAG_SOF_HIGH_END, STATE_TAG_RECEIVING_DATA, - STATE_TAG_AWAIT_EOF + STATE_TAG_EOF } state; int bitCount; int posCount; @@ -361,84 +358,68 @@ typedef struct DecodeTag { SOF_PART2 } lastBit; uint16_t shiftReg; + uint16_t max_len; uint8_t *output; int len; int sum1, sum2; - uint8_t SOF_low; - uint8_t SOF_high; - uint8_t SOF_last; - int32_t SOF_corr; - int32_t SOF_corr_prev; - uint8_t SOF_correlator[SOF_CORRELATOR_LEN]; } DecodeTag_t; -static int Handle15693SamplesFromTag(int8_t ci, int8_t cq, DecodeTag_t *DecodeTag) + +static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *DecodeTag) { switch(DecodeTag->state) { - case STATE_TAG_UNSYNCD: - // initialize SOF correlator. We are looking for 12 samples low and 12 samples high. - DecodeTag->SOF_low = 0; - DecodeTag->SOF_high = 12; - DecodeTag->SOF_last = 23; - memset(DecodeTag->SOF_correlator, 0x00, DecodeTag->SOF_last + 1); - DecodeTag->SOF_correlator[DecodeTag->SOF_last] = AMPLITUDE(ci,cq); - DecodeTag->SOF_corr = DecodeTag->SOF_correlator[DecodeTag->SOF_last]; - DecodeTag->SOF_corr_prev = DecodeTag->SOF_corr; - // initialize Decoder - DecodeTag->posCount = 0; - DecodeTag->bitCount = 0; - DecodeTag->len = 0; - DecodeTag->state = STATE_TAG_AWAIT_SOF_1; - break; - - case STATE_TAG_AWAIT_SOF_1: - // calculate the correlation in real time. Look at differences only. - DecodeTag->SOF_corr += DecodeTag->SOF_correlator[DecodeTag->SOF_low++]; - DecodeTag->SOF_corr -= 2*DecodeTag->SOF_correlator[DecodeTag->SOF_high++]; - DecodeTag->SOF_last++; - DecodeTag->SOF_low &= (SOF_CORRELATOR_LEN-1); - DecodeTag->SOF_high &= (SOF_CORRELATOR_LEN-1); - DecodeTag->SOF_last &= (SOF_CORRELATOR_LEN-1); - DecodeTag->SOF_correlator[DecodeTag->SOF_last] = AMPLITUDE(ci,cq); - DecodeTag->SOF_corr += DecodeTag->SOF_correlator[DecodeTag->SOF_last]; - - // if correlation increases for 10 consecutive samples, we are close to maximum correlation - if (DecodeTag->SOF_corr > DecodeTag->SOF_corr_prev + SUBCARRIER_DETECT_THRESHOLD) { + case STATE_TAG_SOF_LOW: + // waiting for 12 times low (11 times low is accepted as well) + if (amplitude < NOISE_THRESHOLD) { DecodeTag->posCount++; } else { - DecodeTag->posCount = 0; + if (DecodeTag->posCount > 10) { + DecodeTag->posCount = 1; + DecodeTag->sum1 = 0; + DecodeTag->state = STATE_TAG_SOF_HIGH; + } else { + DecodeTag->posCount = 0; + } } - - if (DecodeTag->posCount == 10) { // correlation increased 10 times - DecodeTag->state = STATE_TAG_AWAIT_SOF_2; + break; + + case STATE_TAG_SOF_HIGH: + // waiting for 10 times high. Take average over the last 8 + if (amplitude > NOISE_THRESHOLD) { + DecodeTag->posCount++; + if (DecodeTag->posCount > 2) { + DecodeTag->sum1 += amplitude; // keep track of average high value + } + if (DecodeTag->posCount == 10) { + DecodeTag->sum1 >>= 4; // calculate half of average high value (8 samples) + DecodeTag->state = STATE_TAG_SOF_HIGH_END; + } + } else { // high phase was too short + DecodeTag->posCount = 1; + DecodeTag->state = STATE_TAG_SOF_LOW; } - - DecodeTag->SOF_corr_prev = DecodeTag->SOF_corr; - break; - case STATE_TAG_AWAIT_SOF_2: - // calculate the correlation in real time. Look at differences only. - DecodeTag->SOF_corr += DecodeTag->SOF_correlator[DecodeTag->SOF_low++]; - DecodeTag->SOF_corr -= 2*DecodeTag->SOF_correlator[DecodeTag->SOF_high++]; - DecodeTag->SOF_last++; - DecodeTag->SOF_low &= (SOF_CORRELATOR_LEN-1); - DecodeTag->SOF_high &= (SOF_CORRELATOR_LEN-1); - DecodeTag->SOF_last &= (SOF_CORRELATOR_LEN-1); - DecodeTag->SOF_correlator[DecodeTag->SOF_last] = AMPLITUDE(ci,cq); - DecodeTag->SOF_corr += DecodeTag->SOF_correlator[DecodeTag->SOF_last]; - - if (DecodeTag->SOF_corr >= DecodeTag->SOF_corr_prev) { // we are looking for the maximum correlation - DecodeTag->SOF_corr_prev = DecodeTag->SOF_corr; - } else { - DecodeTag->lastBit = SOF_PART1; // detected 1st part of SOF - DecodeTag->sum1 = DecodeTag->SOF_correlator[DecodeTag->SOF_last]; + case STATE_TAG_SOF_HIGH_END: + // waiting for a falling edge + if (amplitude < DecodeTag->sum1) { // signal drops below 50% average high: a falling edge + DecodeTag->lastBit = SOF_PART1; // detected 1st part of SOF (12 samples low and 12 samples high) + DecodeTag->shiftReg = 0; + DecodeTag->bitCount = 0; + DecodeTag->len = 0; + DecodeTag->sum1 = amplitude; DecodeTag->sum2 = 0; DecodeTag->posCount = 2; DecodeTag->state = STATE_TAG_RECEIVING_DATA; LED_C_ON(); + } else { + DecodeTag->posCount++; + if (DecodeTag->posCount > 13) { // high phase too long + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; + LED_C_OFF(); + } } - break; case STATE_TAG_RECEIVING_DATA: @@ -446,23 +427,27 @@ static int Handle15693SamplesFromTag(int8_t ci, int8_t cq, DecodeTag_t *DecodeTa DecodeTag->sum1 = 0; DecodeTag->sum2 = 0; } - if (DecodeTag->posCount <= 4) { - DecodeTag->sum1 += AMPLITUDE(ci, cq); + DecodeTag->sum1 += amplitude; } else { - DecodeTag->sum2 += AMPLITUDE(ci, cq); + DecodeTag->sum2 += amplitude; } - if (DecodeTag->posCount == 8) { - int16_t corr_1 = (DecodeTag->sum2 - DecodeTag->sum1) / 4; - int16_t corr_0 = (DecodeTag->sum1 - DecodeTag->sum2) / 4; - int16_t corr_EOF = (DecodeTag->sum1 + DecodeTag->sum2) / 8; + int32_t corr_1 = DecodeTag->sum2 - DecodeTag->sum1; + int32_t corr_0 = -corr_1; + int32_t corr_EOF = (DecodeTag->sum1 + DecodeTag->sum2) / 2; if (corr_EOF > corr_0 && corr_EOF > corr_1) { - DecodeTag->state = STATE_TAG_AWAIT_EOF; + if (DecodeTag->lastBit == LOGIC0) { // this was already part of EOF + DecodeTag->state = STATE_TAG_EOF; + } else { + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; + LED_C_OFF(); + } } else if (corr_1 > corr_0) { // logic 1 if (DecodeTag->lastBit == SOF_PART1) { // still part of SOF - DecodeTag->lastBit = SOF_PART2; + DecodeTag->lastBit = SOF_PART2; // SOF completed } else { DecodeTag->lastBit = LOGIC1; DecodeTag->shiftReg >>= 1; @@ -471,6 +456,12 @@ static int Handle15693SamplesFromTag(int8_t ci, int8_t cq, DecodeTag_t *DecodeTa if (DecodeTag->bitCount == 8) { DecodeTag->output[DecodeTag->len] = DecodeTag->shiftReg; DecodeTag->len++; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; + LED_C_OFF(); + } DecodeTag->bitCount = 0; DecodeTag->shiftReg = 0; } @@ -478,7 +469,8 @@ static int Handle15693SamplesFromTag(int8_t ci, int8_t cq, DecodeTag_t *DecodeTa } else { // logic 0 if (DecodeTag->lastBit == SOF_PART1) { // incomplete SOF - DecodeTag->state = STATE_TAG_UNSYNCD; + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } else { DecodeTag->lastBit = LOGIC0; @@ -487,6 +479,12 @@ static int Handle15693SamplesFromTag(int8_t ci, int8_t cq, DecodeTag_t *DecodeTa if (DecodeTag->bitCount == 8) { DecodeTag->output[DecodeTag->len] = DecodeTag->shiftReg; DecodeTag->len++; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; + LED_C_OFF(); + } DecodeTag->bitCount = 0; DecodeTag->shiftReg = 0; } @@ -497,89 +495,106 @@ static int Handle15693SamplesFromTag(int8_t ci, int8_t cq, DecodeTag_t *DecodeTa DecodeTag->posCount++; break; - case STATE_TAG_AWAIT_EOF: - if (DecodeTag->lastBit == LOGIC0) { // this was already part of EOF - LED_C_OFF(); - return true; + case STATE_TAG_EOF: + if (DecodeTag->posCount == 1) { + DecodeTag->sum1 = 0; + DecodeTag->sum2 = 0; + } + if (DecodeTag->posCount <= 4) { + DecodeTag->sum1 += amplitude; } else { - DecodeTag->state = STATE_TAG_UNSYNCD; - LED_C_OFF(); + DecodeTag->sum2 += amplitude; } + if (DecodeTag->posCount == 8) { + int32_t corr_1 = DecodeTag->sum2 - DecodeTag->sum1; + int32_t corr_0 = -corr_1; + int32_t corr_EOF = (DecodeTag->sum1 + DecodeTag->sum2) / 2; + if (corr_EOF > corr_0 || corr_1 > corr_0) { + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; + LED_C_OFF(); + } else { + LED_C_OFF(); + return true; + } + } + DecodeTag->posCount++; break; - default: - DecodeTag->state = STATE_TAG_UNSYNCD; - LED_C_OFF(); - break; } return false; } -static void DecodeTagInit(DecodeTag_t *DecodeTag, uint8_t *data) +static void DecodeTagInit(DecodeTag_t *DecodeTag, uint8_t *data, uint16_t max_len) { + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; DecodeTag->output = data; - DecodeTag->state = STATE_TAG_UNSYNCD; + DecodeTag->max_len = max_len; +} + + +static void DecodeTagReset(DecodeTag_t *DecodeTag) +{ + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; } + /* * Receive and decode the tag response, also log to tracebuffer */ -static int GetIso15693AnswerFromTag(uint8_t* response, int timeout) +static int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, int timeout) { - int maxBehindBy = 0; - int lastRxCounter, samples = 0; - int8_t ci, cq; + int samples = 0; bool gotFrame = false; - uint16_t dmaBuf[ISO15693_DMA_BUFFER_SIZE]; - + uint16_t *dmaBuf = (uint16_t*)BigBuf_malloc(ISO15693_DMA_BUFFER_SIZE*sizeof(uint16_t)); + // the Decoder data structure - DecodeTag_t DecodeTag; - DecodeTagInit(&DecodeTag, response); + DecodeTag_t DecodeTag = { 0 }; + DecodeTagInit(&DecodeTag, response, max_len); // wait for last transfer to complete while (!(AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY)); // And put the FPGA in the appropriate mode - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_AMPLITUDE); // Setup and start DMA. FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); FpgaSetupSscDma((uint8_t*) dmaBuf, ISO15693_DMA_BUFFER_SIZE); uint16_t *upTo = dmaBuf; - lastRxCounter = ISO15693_DMA_BUFFER_SIZE; for(;;) { - int behindBy = (lastRxCounter - AT91C_BASE_PDC_SSC->PDC_RCR) & (ISO15693_DMA_BUFFER_SIZE-1); - if(behindBy > maxBehindBy) { - maxBehindBy = behindBy; - } + uint16_t behindBy = ((uint16_t*)AT91C_BASE_PDC_SSC->PDC_RPR - upTo) & (ISO15693_DMA_BUFFER_SIZE-1); - if (behindBy < 1) continue; + if (behindBy == 0) continue; - ci = (int8_t)(*upTo >> 8); - cq = (int8_t)(*upTo & 0xff); + uint16_t tagdata = *upTo++; - upTo++; - lastRxCounter--; if(upTo >= dmaBuf + ISO15693_DMA_BUFFER_SIZE) { // we have read all of the DMA buffer content. upTo = dmaBuf; // start reading the circular buffer from the beginning - lastRxCounter += ISO15693_DMA_BUFFER_SIZE; + if(behindBy > (9*ISO15693_DMA_BUFFER_SIZE/10)) { + Dbprintf("About to blow circular buffer - aborted! behindBy=%d", behindBy); + break; + } } if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_ENDRX)) { // DMA Counter Register had reached 0, already rotated. AT91C_BASE_PDC_SSC->PDC_RNPR = (uint32_t) dmaBuf; // refresh the DMA Next Buffer and AT91C_BASE_PDC_SSC->PDC_RNCR = ISO15693_DMA_BUFFER_SIZE; // DMA Next Counter registers } + samples++; - if (Handle15693SamplesFromTag(ci, cq, &DecodeTag)) { + if (Handle15693SamplesFromTag(tagdata, &DecodeTag)) { gotFrame = true; break; } - if(samples > timeout && DecodeTag.state < STATE_TAG_RECEIVING_DATA) { + if (samples > timeout && DecodeTag.state < STATE_TAG_RECEIVING_DATA) { DecodeTag.len = 0; break; } @@ -587,11 +602,12 @@ static int GetIso15693AnswerFromTag(uint8_t* response, int timeout) } FpgaDisableSscDma(); + BigBuf_free(); + + if (DEBUG) Dbprintf("samples = %d, gotFrame = %d, Decoder: state = %d, len = %d, bitCount = %d, posCount = %d", + samples, gotFrame, DecodeTag.state, DecodeTag.len, DecodeTag.bitCount, DecodeTag.posCount); - if (DEBUG) Dbprintf("max behindby = %d, samples = %d, gotFrame = %d, Decoder: state = %d, len = %d, bitCount = %d, posCount = %d", - maxBehindBy, samples, gotFrame, DecodeTag.state, DecodeTag.len, DecodeTag.bitCount, DecodeTag.posCount); - - if (tracing && DecodeTag.len > 0) { + if (DecodeTag.len > 0) { LogTrace(DecodeTag.output, DecodeTag.len, 0, 0, NULL, false); } @@ -636,14 +652,32 @@ typedef struct DecodeReader { } DecodeReader_t; -static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader) +static void DecodeReaderInit(DecodeReader_t* DecodeReader, uint8_t *data, uint16_t max_len) +{ + DecodeReader->output = data; + DecodeReader->byteCountMax = max_len; + DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReader->byteCount = 0; + DecodeReader->bitCount = 0; + DecodeReader->posCount = 1; + DecodeReader->shiftReg = 0; +} + + +static void DecodeReaderReset(DecodeReader_t* DecodeReader) +{ + DecodeReader->state = STATE_READER_UNSYNCD; +} + + +static int inline __attribute__((always_inline)) Handle15693SampleFromReader(uint8_t bit, DecodeReader_t *restrict DecodeReader) { switch(DecodeReader->state) { case STATE_READER_UNSYNCD: if(!bit) { // we went low, so this could be the beginning of a SOF - DecodeReader->state = STATE_READER_AWAIT_1ST_RISING_EDGE_OF_SOF; DecodeReader->posCount = 1; + DecodeReader->state = STATE_READER_AWAIT_1ST_RISING_EDGE_OF_SOF; } break; @@ -651,13 +685,13 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader DecodeReader->posCount++; if(bit) { // detected rising edge if(DecodeReader->posCount < 4) { // rising edge too early (nominally expected at 5) - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { // SOF DecodeReader->state = STATE_READER_AWAIT_2ND_FALLING_EDGE_OF_SOF; } } else { if(DecodeReader->posCount > 5) { // stayed low for too long - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { // do nothing, keep waiting } @@ -668,19 +702,19 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader DecodeReader->posCount++; if(!bit) { // detected a falling edge if (DecodeReader->posCount < 20) { // falling edge too early (nominally expected at 21 earliest) - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else if (DecodeReader->posCount < 23) { // SOF for 1 out of 4 coding DecodeReader->Coding = CODING_1_OUT_OF_4; DecodeReader->state = STATE_READER_AWAIT_2ND_RISING_EDGE_OF_SOF; } else if (DecodeReader->posCount < 28) { // falling edge too early (nominally expected at 29 latest) - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { // SOF for 1 out of 4 coding DecodeReader->Coding = CODING_1_OUT_OF_256; DecodeReader->state = STATE_READER_AWAIT_2ND_RISING_EDGE_OF_SOF; } } else { if(DecodeReader->posCount > 29) { // stayed high for too long - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { // do nothing, keep waiting } @@ -692,7 +726,7 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader if (bit) { // detected rising edge if (DecodeReader->Coding == CODING_1_OUT_OF_256) { if (DecodeReader->posCount < 32) { // rising edge too early (nominally expected at 33) - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { DecodeReader->posCount = 1; DecodeReader->bitCount = 0; @@ -703,7 +737,7 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader } } else { // CODING_1_OUT_OF_4 if (DecodeReader->posCount < 24) { // rising edge too early (nominally expected at 25) - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { DecodeReader->state = STATE_READER_AWAIT_END_OF_SOF_1_OUT_OF_4; } @@ -711,13 +745,13 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader } else { if (DecodeReader->Coding == CODING_1_OUT_OF_256) { if (DecodeReader->posCount > 34) { // signal stayed low for too long - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { // do nothing, keep waiting } } else { // CODING_1_OUT_OF_4 if (DecodeReader->posCount > 26) { // signal stayed low for too long - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } else { // do nothing, keep waiting } @@ -739,7 +773,7 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader // do nothing, keep waiting } } else { // unexpected falling edge - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } break; @@ -761,7 +795,7 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader int corr11 = (DecodeReader->sum1 + DecodeReader->sum2) / 2; if (corr01 > corr11 && corr01 > corr10) { // EOF LED_B_OFF(); // Finished receiving - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); if (DecodeReader->byteCount != 0) { return true; } @@ -775,9 +809,10 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader if (DecodeReader->byteCount > DecodeReader->byteCountMax) { // buffer overflow, give up LED_B_OFF(); - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } DecodeReader->bitCount = 0; + DecodeReader->shiftReg = 0; } else { DecodeReader->bitCount++; } @@ -802,7 +837,7 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader int corr11 = (DecodeReader->sum1 + DecodeReader->sum2) / 2; if (corr01 > corr11 && corr01 > corr10) { // EOF LED_B_OFF(); // Finished receiving - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); if (DecodeReader->byteCount != 0) { return true; } @@ -815,7 +850,7 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader if (DecodeReader->byteCount > DecodeReader->byteCountMax) { // buffer overflow, give up LED_B_OFF(); - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); } } DecodeReader->bitCount++; @@ -824,7 +859,7 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader default: LED_B_OFF(); - DecodeReader->state = STATE_READER_UNSYNCD; + DecodeReaderReset(DecodeReader); break; } @@ -832,18 +867,6 @@ static int Handle15693SampleFromReader(uint8_t bit, DecodeReader_t* DecodeReader } -static void DecodeReaderInit(uint8_t *data, uint16_t max_len, DecodeReader_t* DecodeReader) -{ - DecodeReader->output = data; - DecodeReader->byteCountMax = max_len; - DecodeReader->state = STATE_READER_UNSYNCD; - DecodeReader->byteCount = 0; - DecodeReader->bitCount = 0; - DecodeReader->posCount = 0; - DecodeReader->shiftReg = 0; -} - - //----------------------------------------------------------------------------- // Receive a command (from the reader to us, where we are the simulated tag), // and store it in the given buffer, up to the given maximum length. Keeps @@ -856,16 +879,15 @@ static void DecodeReaderInit(uint8_t *data, uint16_t max_len, DecodeReader_t* De static int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eof_time) { - int maxBehindBy = 0; - int lastRxCounter, samples = 0; + int samples = 0; bool gotFrame = false; uint8_t b; - uint8_t dmaBuf[ISO15693_DMA_BUFFER_SIZE]; + uint8_t *dmaBuf = BigBuf_malloc(ISO15693_DMA_BUFFER_SIZE); // the decoder data structure DecodeReader_t DecodeReader = {0}; - DecodeReaderInit(received, max_len, &DecodeReader); + DecodeReaderInit(&DecodeReader, received, max_len); // wait for last transfer to complete while (!(AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY)); @@ -883,21 +905,19 @@ static int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint3 // Setup and start DMA. FpgaSetupSscDma(dmaBuf, ISO15693_DMA_BUFFER_SIZE); uint8_t *upTo = dmaBuf; - lastRxCounter = ISO15693_DMA_BUFFER_SIZE; for(;;) { - int behindBy = (lastRxCounter - AT91C_BASE_PDC_SSC->PDC_RCR) & (ISO15693_DMA_BUFFER_SIZE-1); - if(behindBy > maxBehindBy) { - maxBehindBy = behindBy; - } + uint16_t behindBy = ((uint8_t*)AT91C_BASE_PDC_SSC->PDC_RPR - upTo) & (ISO15693_DMA_BUFFER_SIZE-1); - if (behindBy < 1) continue; + if (behindBy == 0) continue; b = *upTo++; - lastRxCounter--; if(upTo >= dmaBuf + ISO15693_DMA_BUFFER_SIZE) { // we have read all of the DMA buffer content. upTo = dmaBuf; // start reading the circular buffer from the beginning - lastRxCounter += ISO15693_DMA_BUFFER_SIZE; + if(behindBy > (9*ISO15693_DMA_BUFFER_SIZE/10)) { + Dbprintf("About to blow circular buffer - aborted! behindBy=%d", behindBy); + break; + } } if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_ENDRX)) { // DMA Counter Register had reached 0, already rotated. AT91C_BASE_PDC_SSC->PDC_RNPR = (uint32_t) dmaBuf; // refresh the DMA Next Buffer and @@ -927,11 +947,12 @@ static int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint3 FpgaDisableSscDma(); + BigBuf_free_keep_EM(); + + if (DEBUG) Dbprintf("samples = %d, gotFrame = %d, Decoder: state = %d, len = %d, bitCount = %d, posCount = %d", + samples, gotFrame, DecodeReader.state, DecodeReader.byteCount, DecodeReader.bitCount, DecodeReader.posCount); - if (DEBUG) Dbprintf("max behindby = %d, samples = %d, gotFrame = %d, Decoder: state = %d, len = %d, bitCount = %d, posCount = %d", - maxBehindBy, samples, gotFrame, DecodeReader.state, DecodeReader.byteCount, DecodeReader.bitCount, DecodeReader.posCount); - - if (tracing && DecodeReader.byteCount > 0) { + if (DecodeReader.byteCount > 0) { LogTrace(DecodeReader.output, DecodeReader.byteCount, 0, 0, NULL, true); } @@ -939,7 +960,29 @@ static int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint3 } -static void BuildIdentifyRequest(void); +// Encode (into the ToSend buffers) an identify request, which is the first +// thing that you must send to a tag to get a response. +static void BuildIdentifyRequest(void) +{ + uint8_t cmd[5]; + + uint16_t crc; + // one sub-carrier, inventory, 1 slot, fast rate + // AFI is at bit 5 (1<<4) when doing an INVENTORY + cmd[0] = (1 << 2) | (1 << 5) | (1 << 1); + // inventory command code + cmd[1] = 0x01; + // no mask + cmd[2] = 0x00; + //Now the CRC + crc = Crc(cmd, 3); + cmd[3] = crc & 0xff; + cmd[4] = crc >> 8; + + CodeIso15693AsReader(cmd, sizeof(cmd)); +} + + //----------------------------------------------------------------------------- // Start to read an ISO 15693 tag. We send an identify request, then wait // for the response. The response is not demodulated, just left in the buffer @@ -980,18 +1023,12 @@ void AcquireRawAdcSamplesIso15693(void) while (!(AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY)); FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_AMPLITUDE); for(int c = 0; c < 4000; ) { if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - uint16_t iq = AT91C_BASE_SSC->SSC_RHR; - // The samples are correlations against I and Q versions of the - // tone that the tag AM-modulates. We just want power, - // so abs(I) + abs(Q) is close to what we want. - int8_t i = (int8_t)(iq >> 8); - int8_t q = (int8_t)(iq & 0xff); - uint8_t r = AMPLITUDE(i, q); - dest[c++] = r; + uint16_t r = AT91C_BASE_SSC->SSC_RHR; + dest[c++] = r >> 5; } } @@ -1000,49 +1037,139 @@ void AcquireRawAdcSamplesIso15693(void) } -// TODO: there is no trigger condition. The 14000 samples represent a time frame of 66ms. -// It is unlikely that we get something meaningful. -// TODO: Currently we only record tag answers. Add tracing of reader commands. -// TODO: would we get something at all? The carrier is switched on... -void RecordRawAdcSamplesIso15693(void) +void SnoopIso15693(void) { - LEDsoff(); - LED_A_ON(); + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + BigBuf_free(); - uint8_t *dest = BigBuf_get_addr(); + clear_trace(); + set_tracing(true); - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - // Setup SSC - FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); - // Start from off (no field generated) - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - SpinDelay(200); + // The DMA buffer, used to stream samples from the FPGA + uint16_t* dmaBuf = (uint16_t*)BigBuf_malloc(ISO15693_DMA_BUFFER_SIZE*sizeof(uint16_t)); + uint16_t *upTo; + + // Count of samples received so far, so that we can include timing + // information in the trace buffer. + int samples = 0; + + DecodeTag_t DecodeTag = {0}; + uint8_t response[ISO15693_MAX_RESPONSE_LENGTH]; + DecodeTagInit(&DecodeTag, response, sizeof(response)); + DecodeReader_t DecodeReader = {0};; + uint8_t cmd[ISO15693_MAX_COMMAND_LENGTH]; + DecodeReaderInit(&DecodeReader, cmd, sizeof(cmd)); + + // Print some debug information about the buffer sizes + if (DEBUG) { + Dbprintf("Snooping buffers initialized:"); + Dbprintf(" Trace: %i bytes", BigBuf_max_traceLen()); + Dbprintf(" Reader -> tag: %i bytes", ISO15693_MAX_COMMAND_LENGTH); + Dbprintf(" tag -> Reader: %i bytes", ISO15693_MAX_RESPONSE_LENGTH); + Dbprintf(" DMA: %i bytes", ISO15693_DMA_BUFFER_SIZE * sizeof(uint16_t)); + } + Dbprintf("Snoop started. Press button to stop."); + + // Signal field is off, no reader signal, no tag signal + LEDsoff(); + // And put the FPGA in the appropriate mode + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_SNOOP | FPGA_HF_READER_RX_XCORR_AMPLITUDE); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); - SpinDelay(100); + // Setup for the DMA. + FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); + upTo = dmaBuf; + FpgaSetupSscDma((uint8_t*) dmaBuf, ISO15693_DMA_BUFFER_SIZE); - LED_D_ON(); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); + bool TagIsActive = false; + bool ReaderIsActive = false; + bool ExpectTagAnswer = false; - for(int c = 0; c < 14000;) { - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - uint16_t iq = AT91C_BASE_SSC->SSC_RHR; - // The samples are correlations against I and Q versions of the - // tone that the tag AM-modulates. We just want power, - // so abs(I) + abs(Q) is close to what we want. - int8_t i = (int8_t)(iq >> 8); - int8_t q = (int8_t)(iq & 0xff); - uint8_t r = AMPLITUDE(i, q); - dest[c++] = r; + // And now we loop, receiving samples. + for(;;) { + uint16_t behindBy = ((uint16_t*)AT91C_BASE_PDC_SSC->PDC_RPR - upTo) & (ISO15693_DMA_BUFFER_SIZE-1); + + if (behindBy == 0) continue; + + uint16_t snoopdata = *upTo++; + + if(upTo >= dmaBuf + ISO15693_DMA_BUFFER_SIZE) { // we have read all of the DMA buffer content. + upTo = dmaBuf; // start reading the circular buffer from the beginning + if(behindBy > (9*ISO15693_DMA_BUFFER_SIZE/10)) { + Dbprintf("About to blow circular buffer - aborted! behindBy=%d, samples=%d", behindBy, samples); + break; + } + if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_ENDRX)) { // DMA Counter Register had reached 0, already rotated. + AT91C_BASE_PDC_SSC->PDC_RNPR = (uint32_t) dmaBuf; // refresh the DMA Next Buffer and + AT91C_BASE_PDC_SSC->PDC_RNCR = ISO15693_DMA_BUFFER_SIZE; // DMA Next Counter registers + WDT_HIT(); + if(BUTTON_PRESS()) { + DbpString("Snoop stopped."); + break; + } + } + } + samples++; + + if (!TagIsActive) { // no need to try decoding reader data if the tag is sending + if (Handle15693SampleFromReader(snoopdata & 0x02, &DecodeReader)) { + FpgaDisableSscDma(); + ExpectTagAnswer = true; + LogTrace(DecodeReader.output, DecodeReader.byteCount, samples, samples, NULL, true); + /* And ready to receive another command. */ + DecodeReaderReset(&DecodeReader); + /* And also reset the demod code, which might have been */ + /* false-triggered by the commands from the reader. */ + DecodeTagReset(&DecodeTag); + upTo = dmaBuf; + FpgaSetupSscDma((uint8_t*) dmaBuf, ISO15693_DMA_BUFFER_SIZE); + } + if (Handle15693SampleFromReader(snoopdata & 0x01, &DecodeReader)) { + FpgaDisableSscDma(); + ExpectTagAnswer = true; + LogTrace(DecodeReader.output, DecodeReader.byteCount, samples, samples, NULL, true); + /* And ready to receive another command. */ + DecodeReaderReset(&DecodeReader); + /* And also reset the demod code, which might have been */ + /* false-triggered by the commands from the reader. */ + DecodeTagReset(&DecodeTag); + upTo = dmaBuf; + FpgaSetupSscDma((uint8_t*) dmaBuf, ISO15693_DMA_BUFFER_SIZE); + } + ReaderIsActive = (DecodeReader.state >= STATE_READER_AWAIT_2ND_RISING_EDGE_OF_SOF); } + + if (!ReaderIsActive && ExpectTagAnswer) { // no need to try decoding tag data if the reader is currently sending or no answer expected yet + if (Handle15693SamplesFromTag(snoopdata >> 2, &DecodeTag)) { + FpgaDisableSscDma(); + //Use samples as a time measurement + LogTrace(DecodeTag.output, DecodeTag.len, samples, samples, NULL, false); + // And ready to receive another response. + DecodeTagReset(&DecodeTag); + DecodeReaderReset(&DecodeReader); + ExpectTagAnswer = false; + upTo = dmaBuf; + FpgaSetupSscDma((uint8_t*) dmaBuf, ISO15693_DMA_BUFFER_SIZE); + } + TagIsActive = (DecodeTag.state >= STATE_TAG_RECEIVING_DATA); + } + } - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LED_D_OFF(); - Dbprintf("finished recording"); - LED_A_OFF(); + FpgaDisableSscDma(); + BigBuf_free(); + + LEDsoff(); + + DbpString("Snoop statistics:"); + Dbprintf(" ExpectTagAnswer: %d", ExpectTagAnswer); + Dbprintf(" DecodeTag State: %d", DecodeTag.state); + Dbprintf(" DecodeTag byteCnt: %d", DecodeTag.len); + Dbprintf(" DecodeReader State: %d", DecodeReader.state); + Dbprintf(" DecodeReader byteCnt: %d", DecodeReader.byteCount); + Dbprintf(" Trace length: %d", BigBuf_get_traceLen()); } @@ -1072,27 +1199,6 @@ static void Iso15693InitReader() { // This section basically contains transmission and receiving of bits /////////////////////////////////////////////////////////////////////// -// Encode (into the ToSend buffers) an identify request, which is the first -// thing that you must send to a tag to get a response. -static void BuildIdentifyRequest(void) -{ - uint8_t cmd[5]; - - uint16_t crc; - // one sub-carrier, inventory, 1 slot, fast rate - // AFI is at bit 5 (1<<4) when doing an INVENTORY - cmd[0] = (1 << 2) | (1 << 5) | (1 << 1); - // inventory command code - cmd[1] = 0x01; - // no mask - cmd[2] = 0x00; - //Now the CRC - crc = Crc(cmd, 3); - cmd[3] = crc & 0xff; - cmd[4] = crc >> 8; - - CodeIso15693AsReader(cmd, sizeof(cmd)); -} // uid is in transmission order (which is reverse of display order) static void BuildReadBlockRequest(uint8_t *uid, uint8_t blockNumber ) @@ -1100,12 +1206,11 @@ static void BuildReadBlockRequest(uint8_t *uid, uint8_t blockNumber ) uint8_t cmd[13]; uint16_t crc; - // If we set the Option_Flag in this request, the VICC will respond with the secuirty status of the block - // followed by teh block data - // one sub-carrier, inventory, 1 slot, fast rate - cmd[0] = (1 << 6)| (1 << 5) | (1 << 1); // no SELECT bit, ADDR bit, OPTION bit + // If we set the Option_Flag in this request, the VICC will respond with the security status of the block + // followed by the block data + cmd[0] = ISO15693_REQ_OPTION | ISO15693_REQ_ADDRESS | ISO15693_REQ_DATARATE_HIGH; // READ BLOCK command code - cmd[1] = 0x20; + cmd[1] = ISO15693_READBLOCK; // UID may be optionally specified here // 64-bit UID cmd[2] = uid[0]; @@ -1117,7 +1222,7 @@ static void BuildReadBlockRequest(uint8_t *uid, uint8_t blockNumber ) cmd[8] = uid[6]; cmd[9] = uid[7]; // 0xe0; // always e0 (not exactly unique) // Block number to read - cmd[10] = blockNumber;//0x00; + cmd[10] = blockNumber; //Now the CRC crc = Crc(cmd, 11); // the crc needs to be calculated over 11 bytes cmd[11] = crc & 0xff; @@ -1156,10 +1261,9 @@ static void BuildInventoryResponse(uint8_t *uid) // Universal Method for sending to and recv bytes from a tag // init ... should we initialize the reader? // speed ... 0 low speed, 1 hi speed -// **recv will return you a pointer to the received data -// If you do not need the answer use NULL for *recv[] +// *recv will contain the tag's answer // return: lenght of received data -int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t **recv) { +int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t *recv, uint16_t max_recv_len, uint32_t start_time) { LED_A_ON(); LED_B_OFF(); @@ -1168,8 +1272,6 @@ int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t **recv if (init) Iso15693InitReader(); int answerLen=0; - uint8_t *answer = BigBuf_get_addr() + 4000; - if (recv != NULL) memset(answer, 0, 100); if (!speed) { // low speed (1 out of 256) @@ -1179,11 +1281,11 @@ int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t **recv CodeIso15693AsReader(send, sendlen); } - TransmitTo15693Tag(ToSend,ToSendMax); + TransmitTo15693Tag(ToSend, ToSendMax, start_time); + // Now wait for a response - if (recv!=NULL) { - answerLen = GetIso15693AnswerFromTag(answer, 100); - *recv=answer; + if (recv != NULL) { + answerLen = GetIso15693AnswerFromTag(recv, max_recv_len, DELAY_ISO15693_VCD_TO_VICC * 2); } LED_A_OFF(); @@ -1202,46 +1304,46 @@ void DbdecodeIso15693Answer(int len, uint8_t *d) { char status[DBD15STATLEN+1]={0}; uint16_t crc; - if (len>3) { - if (d[0]&(1<<3)) - strncat(status,"ProtExt ",DBD15STATLEN); - if (d[0]&1) { + if (len > 3) { + if (d[0] & ISO15693_RES_EXT) + strncat(status,"ProtExt ", DBD15STATLEN); + if (d[0] & ISO15693_RES_ERROR) { // error - strncat(status,"Error ",DBD15STATLEN); + strncat(status,"Error ", DBD15STATLEN); switch (d[1]) { case 0x01: - strncat(status,"01:notSupp",DBD15STATLEN); + strncat(status,"01:notSupp", DBD15STATLEN); break; case 0x02: - strncat(status,"02:notRecog",DBD15STATLEN); + strncat(status,"02:notRecog", DBD15STATLEN); break; case 0x03: - strncat(status,"03:optNotSupp",DBD15STATLEN); + strncat(status,"03:optNotSupp", DBD15STATLEN); break; case 0x0f: - strncat(status,"0f:noInfo",DBD15STATLEN); + strncat(status,"0f:noInfo", DBD15STATLEN); break; case 0x10: - strncat(status,"10:dontExist",DBD15STATLEN); + strncat(status,"10:doesn'tExist", DBD15STATLEN); break; case 0x11: - strncat(status,"11:lockAgain",DBD15STATLEN); + strncat(status,"11:lockAgain", DBD15STATLEN); break; case 0x12: - strncat(status,"12:locked",DBD15STATLEN); + strncat(status,"12:locked", DBD15STATLEN); break; case 0x13: - strncat(status,"13:progErr",DBD15STATLEN); + strncat(status,"13:progErr", DBD15STATLEN); break; case 0x14: - strncat(status,"14:lockErr",DBD15STATLEN); + strncat(status,"14:lockErr", DBD15STATLEN); break; default: - strncat(status,"unknownErr",DBD15STATLEN); + strncat(status,"unknownErr", DBD15STATLEN); } - strncat(status," ",DBD15STATLEN); + strncat(status," ", DBD15STATLEN); } else { - strncat(status,"NoErr ",DBD15STATLEN); + strncat(status,"NoErr ", DBD15STATLEN); } crc=Crc(d,len-2); @@ -1266,6 +1368,7 @@ void SetDebugIso15693(uint32_t debug) { return; } + //----------------------------------------------------------------------------- // Simulate an ISO15693 reader, perform anti-collision and then attempt to read a sector // all demodulation performed in arm rather than host. - greg @@ -1275,13 +1378,14 @@ void ReaderIso15693(uint32_t parameter) LEDsoff(); LED_A_ON(); - int answerLen1 = 0; + set_tracing(true); + + int answerLen = 0; uint8_t TagUID[8] = {0x00}; FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - uint8_t *answer1 = BigBuf_get_addr() + 4000; - memset(answer1, 0x00, 200); + uint8_t answer[ISO15693_MAX_RESPONSE_LENGTH]; SetAdcMuxFor(GPIO_MUXSEL_HIPKD); // Setup SSC @@ -1295,37 +1399,39 @@ void ReaderIso15693(uint32_t parameter) LED_D_ON(); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR); SpinDelay(200); + StartCountSspClk(); + // FIRST WE RUN AN INVENTORY TO GET THE TAG UID // THIS MEANS WE CAN PRE-BUILD REQUESTS TO SAVE CPU TIME // Now send the IDENTIFY command BuildIdentifyRequest(); - - TransmitTo15693Tag(ToSend,ToSendMax); + TransmitTo15693Tag(ToSend, ToSendMax, 0); // Now wait for a response - answerLen1 = GetIso15693AnswerFromTag(answer1, 100) ; + answerLen = GetIso15693AnswerFromTag(answer, sizeof(answer), DELAY_ISO15693_VCD_TO_VICC * 2) ; + uint32_t start_time = GetCountSspClk() + DELAY_ISO15693_VICC_TO_VCD; - if (answerLen1 >=12) // we should do a better check than this + if (answerLen >=12) // we should do a better check than this { - TagUID[0] = answer1[2]; - TagUID[1] = answer1[3]; - TagUID[2] = answer1[4]; - TagUID[3] = answer1[5]; - TagUID[4] = answer1[6]; - TagUID[5] = answer1[7]; - TagUID[6] = answer1[8]; // IC Manufacturer code - TagUID[7] = answer1[9]; // always E0 + TagUID[0] = answer[2]; + TagUID[1] = answer[3]; + TagUID[2] = answer[4]; + TagUID[3] = answer[5]; + TagUID[4] = answer[6]; + TagUID[5] = answer[7]; + TagUID[6] = answer[8]; // IC Manufacturer code + TagUID[7] = answer[9]; // always E0 } - Dbprintf("%d octets read from IDENTIFY request:", answerLen1); - DbdecodeIso15693Answer(answerLen1, answer1); - Dbhexdump(answerLen1, answer1, false); + Dbprintf("%d octets read from IDENTIFY request:", answerLen); + DbdecodeIso15693Answer(answerLen, answer); + Dbhexdump(answerLen, answer, false); // UID is reverse - if (answerLen1 >= 12) + if (answerLen >= 12) Dbprintf("UID = %02hX%02hX%02hX%02hX%02hX%02hX%02hX%02hX", TagUID[7],TagUID[6],TagUID[5],TagUID[4], TagUID[3],TagUID[2],TagUID[1],TagUID[0]); @@ -1340,18 +1446,21 @@ void ReaderIso15693(uint32_t parameter) // Dbhexdump(answerLen3,answer3,true); // read all pages - if (answerLen1 >= 12 && DEBUG) { - uint8_t *answer2 = BigBuf_get_addr() + 4100; + if (answerLen >= 12 && DEBUG) { + + // debugptr = BigBuf_get_addr(); + int i = 0; while (i < 32) { // sanity check, assume max 32 pages BuildReadBlockRequest(TagUID, i); - TransmitTo15693Tag(ToSend, ToSendMax); - int answerLen2 = GetIso15693AnswerFromTag(answer2, 100); - if (answerLen2 > 0) { - Dbprintf("READ SINGLE BLOCK %d returned %d octets:", i, answerLen2); - DbdecodeIso15693Answer(answerLen2, answer2); - Dbhexdump(answerLen2, answer2, false); - if ( *((uint32_t*) answer2) == 0x07160101 ) break; // exit on NoPageErr + TransmitTo15693Tag(ToSend, ToSendMax, start_time); + int answerLen = GetIso15693AnswerFromTag(answer, sizeof(answer), DELAY_ISO15693_VCD_TO_VICC * 2); + start_time = GetCountSspClk() + DELAY_ISO15693_VICC_TO_VCD; + if (answerLen > 0) { + Dbprintf("READ SINGLE BLOCK %d returned %d octets:", i, answerLen); + DbdecodeIso15693Answer(answerLen, answer); + Dbhexdump(answerLen, answer, false); + if ( *((uint32_t*) answer) == 0x07160101 ) break; // exit on NoPageErr } i++; } @@ -1412,12 +1521,14 @@ void BruteforceIso15693Afi(uint32_t speed) LEDsoff(); LED_A_ON(); - uint8_t data[20]; - uint8_t *recv=data; + uint8_t data[6]; + uint8_t recv[ISO15693_MAX_RESPONSE_LENGTH]; + int datalen=0, recvlen=0; Iso15693InitReader(); - + StartCountSspClk(); + // first without AFI // Tags should respond without AFI and with AFI=0 even when AFI is active @@ -1425,10 +1536,11 @@ void BruteforceIso15693Afi(uint32_t speed) data[1] = ISO15693_INVENTORY; data[2] = 0; // mask length datalen = AddCrc(data,3); - recvlen = SendDataTag(data, datalen, false, speed, &recv); + recvlen = SendDataTag(data, datalen, false, speed, recv, sizeof(recv), 0); + uint32_t start_time = GetCountSspClk() + DELAY_ISO15693_VCD_TO_VICC; WDT_HIT(); if (recvlen>=12) { - Dbprintf("NoAFI UID=%s",sprintUID(NULL,&recv[2])); + Dbprintf("NoAFI UID=%s", sprintUID(NULL, &recv[2])); } // now with AFI @@ -1438,13 +1550,14 @@ void BruteforceIso15693Afi(uint32_t speed) data[2] = 0; // AFI data[3] = 0; // mask length - for (int i=0;i<256;i++) { - data[2]=i & 0xFF; - datalen=AddCrc(data,4); - recvlen=SendDataTag(data, datalen, false, speed, &recv); + for (int i = 0; i < 256; i++) { + data[2] = i & 0xFF; + datalen = AddCrc(data,4); + recvlen = SendDataTag(data, datalen, false, speed, recv, sizeof(recv), start_time); + start_time = GetCountSspClk() + DELAY_ISO15693_VCD_TO_VICC; WDT_HIT(); - if (recvlen>=12) { - Dbprintf("AFI=%i UID=%s", i, sprintUID(NULL,&recv[2])); + if (recvlen >= 12) { + Dbprintf("AFI=%i UID=%s", i, sprintUID(NULL, &recv[2])); } } Dbprintf("AFI Bruteforcing done."); @@ -1456,26 +1569,27 @@ void BruteforceIso15693Afi(uint32_t speed) // Allows to directly send commands to the tag via the client void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint8_t data[]) { - int recvlen=0; - uint8_t *recvbuf = BigBuf_get_addr(); + int recvlen = 0; + uint8_t recvbuf[ISO15693_MAX_RESPONSE_LENGTH]; LED_A_ON(); if (DEBUG) { - Dbprintf("SEND"); + Dbprintf("SEND:"); Dbhexdump(datalen, data, false); } - recvlen = SendDataTag(data, datalen, true, speed, (recv?&recvbuf:NULL)); + recvlen = SendDataTag(data, datalen, true, speed, (recv?recvbuf:NULL), sizeof(recvbuf), 0); if (recv) { - cmd_send(CMD_ACK, recvlen>48?48:recvlen, 0, 0, recvbuf, 48); - if (DEBUG) { - Dbprintf("RECV"); - DbdecodeIso15693Answer(recvlen,recvbuf); + Dbprintf("RECV:"); Dbhexdump(recvlen, recvbuf, false); + DbdecodeIso15693Answer(recvlen, recvbuf); } + + cmd_send(CMD_ACK, recvlen>ISO15693_MAX_RESPONSE_LENGTH?ISO15693_MAX_RESPONSE_LENGTH:recvlen, 0, 0, recvbuf, ISO15693_MAX_RESPONSE_LENGTH); + } // for the time being, switch field off to protect rdv4.0 diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h new file mode 100644 index 00000000..e5b78a8a --- /dev/null +++ b/armsrc/iso15693.h @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// Piwi - October 2018 +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Routines to support ISO 15693. +//----------------------------------------------------------------------------- + +#ifndef __ISO15693_H +#define __ISO15693_H + +#include + +void SnoopIso15693(void); +void AcquireRawAdcSamplesIso15693(void); +void ReaderIso15693(uint32_t parameter); +void SimTagIso15693(uint32_t parameter, uint8_t *uid); +void BruteforceIso15693Afi(uint32_t speed); +void DirectTag15693Command(uint32_t datalen,uint32_t speed, uint32_t recv, uint8_t data[]); +void SetDebugIso15693(uint32_t flag); + +#endif diff --git a/client/cmdhf.c b/client/cmdhf.c index 56b1cb3d..744a95d2 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -264,7 +264,7 @@ uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace, ui uint8_t parityBits = parityBytes[j>>3]; if (protocol != ISO_14443B && (isResponse || protocol == ISO_14443A) && (oddparity8(frame[j]) != ((parityBits >> (7-(j&0x0007))) & 0x01))) { - snprintf(line[j/16]+(( j % 16) * 4),110, "%02x! ", frame[j]); + snprintf(line[j/16]+(( j % 16) * 4), 110, " %02x!", frame[j]); } else { snprintf(line[j/16]+(( j % 16) * 4), 110, " %02x ", frame[j]); } @@ -281,14 +281,11 @@ uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace, ui } } - if(data_len == 0) - { - if(data_len == 0){ - sprintf(line[0],""); - } + if (data_len == 0) { + sprintf(line[0]," "); } - //--- Draw the CRC column + //--- Draw the CRC column char *crc = (crcStatus == 0 ? "!crc" : (crcStatus == 1 ? " ok " : " ")); EndOfTransmissionTimestamp = timestamp + duration; diff --git a/client/cmdhf15.c b/client/cmdhf15.c index e5f4af31..da83ccaf 100644 --- a/client/cmdhf15.c +++ b/client/cmdhf15.c @@ -38,15 +38,54 @@ #include "protocols.h" #include "cmdmain.h" -#define FrameSOF Iso15693FrameSOF -#define Logic0 Iso15693Logic0 -#define Logic1 Iso15693Logic1 -#define FrameEOF Iso15693FrameEOF - #define Crc(data,datalen) Iso15693Crc(data,datalen) #define AddCrc(data,datalen) Iso15693AddCrc(data,datalen) #define sprintUID(target,uid) Iso15693sprintUID(target,uid) +// SOF defined as +// 1) Unmodulated time of 56.64us +// 2) 24 pulses of 423.75khz +// 3) logic '1' (unmodulated for 18.88us followed by 8 pulses of 423.75khz) + +static const int Iso15693FrameSOF[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + -1, -1, -1, -1, + -1, -1, -1, -1, + 1, 1, 1, 1, + 1, 1, 1, 1 +}; +static const int Iso15693Logic0[] = { + 1, 1, 1, 1, + 1, 1, 1, 1, + -1, -1, -1, -1, + -1, -1, -1, -1 +}; +static const int Iso15693Logic1[] = { + -1, -1, -1, -1, + -1, -1, -1, -1, + 1, 1, 1, 1, + 1, 1, 1, 1 +}; + +// EOF defined as +// 1) logic '0' (8 pulses of 423.75khz followed by unmodulated for 18.88us) +// 2) 24 pulses of 423.75khz +// 3) Unmodulated time of 56.64us + +static const int Iso15693FrameEOF[] = { + 1, 1, 1, 1, + 1, 1, 1, 1, + -1, -1, -1, -1, + -1, -1, -1, -1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + // structure and database for uid -> tagtype lookups typedef struct { uint64_t uid; @@ -293,8 +332,8 @@ int CmdHF15Demod(const char *Cmd) // First, correlate for SOF for (i = 0; i < 200; i++) { int corr = 0; - for (j = 0; j < arraylen(FrameSOF); j += skip) { - corr += FrameSOF[j] * GraphBuffer[i + (j / skip)]; + for (j = 0; j < arraylen(Iso15693FrameSOF); j += skip) { + corr += Iso15693FrameSOF[j] * GraphBuffer[i + (j / skip)]; } if (corr > max) { max = corr; @@ -302,28 +341,28 @@ int CmdHF15Demod(const char *Cmd) } } PrintAndLog("SOF at %d, correlation %d", maxPos, - max / (arraylen(FrameSOF) / skip)); + max / (arraylen(Iso15693FrameSOF) / skip)); - i = maxPos + arraylen(FrameSOF) / skip; + i = maxPos + arraylen(Iso15693FrameSOF) / skip; int k = 0; uint8_t outBuf[20]; memset(outBuf, 0, sizeof(outBuf)); uint8_t mask = 0x01; for (;;) { int corr0 = 0, corr00 = 0, corr01 = 0, corr1 = 0, corrEOF = 0; - for(j = 0; j < arraylen(Logic0); j += skip) { - corr0 += Logic0[j]*GraphBuffer[i+(j/skip)]; + for(j = 0; j < arraylen(Iso15693Logic0); j += skip) { + corr0 += Iso15693Logic0[j]*GraphBuffer[i+(j/skip)]; } corr01 = corr00 = corr0; - for(j = 0; j < arraylen(Logic0); j += skip) { - corr00 += Logic0[j]*GraphBuffer[i+arraylen(Logic0)/skip+(j/skip)]; - corr01 += Logic1[j]*GraphBuffer[i+arraylen(Logic0)/skip+(j/skip)]; + for(j = 0; j < arraylen(Iso15693Logic0); j += skip) { + corr00 += Iso15693Logic0[j]*GraphBuffer[i+arraylen(Iso15693Logic0)/skip+(j/skip)]; + corr01 += Iso15693Logic1[j]*GraphBuffer[i+arraylen(Iso15693Logic0)/skip+(j/skip)]; } - for(j = 0; j < arraylen(Logic1); j += skip) { - corr1 += Logic1[j]*GraphBuffer[i+(j/skip)]; + for(j = 0; j < arraylen(Iso15693Logic1); j += skip) { + corr1 += Iso15693Logic1[j]*GraphBuffer[i+(j/skip)]; } - for(j = 0; j < arraylen(FrameEOF); j += skip) { - corrEOF += FrameEOF[j]*GraphBuffer[i+(j/skip)]; + for(j = 0; j < arraylen(Iso15693FrameEOF); j += skip) { + corrEOF += Iso15693FrameEOF[j]*GraphBuffer[i+(j/skip)]; } // Even things out by the length of the target waveform. corr00 *= 2; @@ -335,17 +374,17 @@ int CmdHF15Demod(const char *Cmd) PrintAndLog("EOF at %d", i); break; } else if (corr1 > corr0) { - i += arraylen(Logic1) / skip; + i += arraylen(Iso15693Logic1) / skip; outBuf[k] |= mask; } else { - i += arraylen(Logic0) / skip; + i += arraylen(Iso15693Logic0) / skip; } mask <<= 1; if (mask == 0) { k++; mask = 0x01; } - if ((i + (int)arraylen(FrameEOF)) >= GraphTraceLen) { + if ((i + (int)arraylen(Iso15693FrameEOF)) >= GraphTraceLen) { PrintAndLog("ran off end!"); break; } @@ -374,10 +413,9 @@ int CmdHF15Read(const char *Cmd) } // Record Activity without enabling carrier -// TODO: currently it DOES enable the carrier -int CmdHF15Record(const char *Cmd) +int CmdHF15Snoop(const char *Cmd) { - UsbCommand c = {CMD_RECORD_RAW_ADC_SAMPLES_ISO_15693}; + UsbCommand c = {CMD_SNOOP_ISO_15693}; SendCommand(&c); return 0; } @@ -514,7 +552,7 @@ static command_t CommandTable15[] = {"help", CmdHF15Help, 1, "This help"}, {"demod", CmdHF15Demod, 1, "Demodulate ISO15693 from tag"}, {"read", CmdHF15Read, 0, "Read HF tag (ISO 15693)"}, - {"record", CmdHF15Record, 0, "Record Samples (ISO 15693)"}, // atrox + {"snoop", CmdHF15Snoop, 0, "Eavesdrop ISO 15693 communications"}, {"reader", CmdHF15Reader, 0, "Act like an ISO15693 reader"}, {"sim", CmdHF15Sim, 0, "Fake an ISO15693 tag"}, {"cmd", CmdHF15Cmd, 0, "Send direct commands to ISO15693 tag"}, diff --git a/common/iso15693tools.h b/common/iso15693tools.h index 96095fba..d9f59cc6 100644 --- a/common/iso15693tools.h +++ b/common/iso15693tools.h @@ -14,57 +14,4 @@ int Iso15693AddCrc(uint8_t *req, int n); char* Iso15693sprintUID(char *target,uint8_t *uid); unsigned short iclass_crc16(char *data_p, unsigned short length); -//----------------------------------------------------------------------------- -// Map a sequence of octets (~layer 2 command) into the set of bits to feed -// to the FPGA, to transmit that command to the tag. -// Mode: highspeed && one subcarrier (ASK) -//----------------------------------------------------------------------------- - - // The sampling rate is 106.353 ksps/s, for T = 18.8 us - - // SOF defined as - // 1) Unmodulated time of 56.64us - // 2) 24 pulses of 423.75khz - // 3) logic '1' (unmodulated for 18.88us followed by 8 pulses of 423.75khz) - - static const int Iso15693FrameSOF[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - -1, -1, -1, -1, - -1, -1, -1, -1, - 1, 1, 1, 1, - 1, 1, 1, 1 - }; - static const int Iso15693Logic0[] = { - 1, 1, 1, 1, - 1, 1, 1, 1, - -1, -1, -1, -1, - -1, -1, -1, -1 - }; - static const int Iso15693Logic1[] = { - -1, -1, -1, -1, - -1, -1, -1, -1, - 1, 1, 1, 1, - 1, 1, 1, 1 - }; - - // EOF defined as - // 1) logic '0' (8 pulses of 423.75khz followed by unmodulated for 18.88us) - // 2) 24 pulses of 423.75khz - // 3) Unmodulated time of 56.64us - - static const int Iso15693FrameEOF[] = { - 1, 1, 1, 1, - 1, 1, 1, 1, - -1, -1, -1, -1, - -1, -1, -1, -1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 - }; - - #endif diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index bd296174f0978d3214bae003d25a7063bff60021..f16fd0614fbd2614e713a29da5f47a29094953e5 100644 GIT binary patch literal 42175 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 literal 42175 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 abs_cq) + begin + max_ci_cq <= abs_ci; + min_ci_cq <= abs_cq; + end + else + begin + max_ci_cq <= abs_cq; + min_ci_cq <= abs_ci; + end + + corr_amplitude <= max_ci_cq + min_ci_cq/2; + +end + + // The subcarrier reference signals reg subcarrier_I; reg subcarrier_Q; @@ -110,52 +145,75 @@ begin subcarrier_Q = ~(corr_i_cnt[4] ^ corr_i_cnt[3]); end end - + + // ADC data appears on the rising edge, so sample it on the falling edge always @(negedge adc_clk) begin // These are the correlators: we correlate against in-phase and quadrature - // versions of our reference signal, and keep the (signed) result to - // send out later over the SSP. + // versions of our reference signal, and keep the (signed) results or the + // resulting amplitude to send out later over the SSP. if(corr_i_cnt == 6'd0) begin if(snoop) begin - // Send 7 most significant bits of tag signal (signed), plus 1 bit reader signal - if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) - corr_i_out <= {corr_i_accum[11:5], after_hysteresis_prev_prev}; - else // truncate to maximum value - if (corr_i_accum[13] == 1'b0) - corr_i_out <= {7'b0111111, after_hysteresis_prev_prev}; - else - corr_i_out <= {7'b1000000, after_hysteresis_prev_prev}; - if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) - corr_q_out <= {corr_q_accum[11:5], after_hysteresis_prev}; - else // truncate to maximum value - if (corr_q_accum[13] == 1'b0) - corr_q_out <= {7'b0111111, after_hysteresis_prev}; - else - corr_q_out <= {7'b1000000, after_hysteresis_prev}; - after_hysteresis_prev_prev <= after_hysteresis; + if (hi_read_rx_xcorr_amplitude) + begin + // send amplitude plus 2 bits reader signal + corr_i_out <= corr_amplitude[13:6]; + corr_q_out <= {corr_amplitude[5:0], after_hysteresis_prev_prev, after_hysteresis_prev}; + end + else + begin + // Send 7 most significant bits of in phase tag signal (signed), plus 1 bit reader signal + if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) + corr_i_out <= {corr_i_accum[11:5], after_hysteresis_prev_prev}; + else // truncate to maximum value + if (corr_i_accum[13] == 1'b0) + corr_i_out <= {7'b0111111, after_hysteresis_prev_prev}; + else + corr_i_out <= {7'b1000000, after_hysteresis_prev_prev}; + // Send 7 most significant bits of quadrature phase tag signal (signed), plus 1 bit reader signal + if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) + corr_q_out <= {corr_q_accum[11:5], after_hysteresis_prev}; + else // truncate to maximum value + if (corr_q_accum[13] == 1'b0) + corr_q_out <= {7'b0111111, after_hysteresis_prev}; + else + corr_q_out <= {7'b1000000, after_hysteresis_prev}; + end end else begin - // Send 8 bits of tag signal - if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) - corr_i_out <= corr_i_accum[11:4]; - else // truncate to maximum value - if (corr_i_accum[13] == 1'b0) - corr_i_out <= 8'b01111111; - else - corr_i_out <= 8'b10000000; - if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) - corr_q_out <= corr_q_accum[11:4]; - else // truncate to maximum value - if (corr_q_accum[13] == 1'b0) - corr_q_out <= 8'b01111111; - else - corr_q_out <= 8'b10000000; + if (hi_read_rx_xcorr_amplitude) + begin + // send amplitude + corr_i_out <= {2'b00, corr_amplitude[13:8]}; + corr_q_out <= corr_amplitude[7:0]; + end + else + begin + // Send 8 bits of in phase tag signal + if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) + corr_i_out <= corr_i_accum[11:4]; + else // truncate to maximum value + if (corr_i_accum[13] == 1'b0) + corr_i_out <= 8'b01111111; + else + corr_i_out <= 8'b10000000; + // Send 8 bits of quadrature phase tag signal + if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) + corr_q_out <= corr_q_accum[11:4]; + else // truncate to maximum value + if (corr_q_accum[13] == 1'b0) + corr_q_out <= 8'b01111111; + else + corr_q_out <= 8'b10000000; + end end + + // for each Q/I pair report two reader signal samples when sniffing. Store the 1st. + after_hysteresis_prev_prev <= after_hysteresis; // Initialize next correlation. // Both I and Q reference signals are high when corr_i_nct == 0. Therefore need to accumulate. corr_i_accum <= $signed({1'b0,adc_d}); @@ -172,16 +230,16 @@ begin corr_q_accum <= corr_q_accum + $signed({1'b0,adc_d}); else corr_q_accum <= corr_q_accum - $signed({1'b0,adc_d}); - end - // for each Q/I pair report two reader signal samples when sniffing + // for each Q/I pair report two reader signal samples when sniffing. Store the 2nd. if(corr_i_cnt == 6'd32) after_hysteresis_prev <= after_hysteresis; // Then the result from last time is serialized and send out to the ARM. // We get one report each cycle, and each report is 16 bits, so the - // ssp_clk should be the adc_clk divided by 64/16 = 4. + // ssp_clk should be the adc_clk divided by 64/16 = 4. + // ssp_clk frequency = 13,56MHz / 4 = 3.39MHz if(corr_i_cnt[1:0] == 2'b10) ssp_clk <= 1'b0; diff --git a/include/usb_cmd.h b/include/usb_cmd.h index e73aa2a7..785435f0 100644 --- a/include/usb_cmd.h +++ b/include/usb_cmd.h @@ -125,7 +125,7 @@ typedef struct{ #define CMD_ISO_14443B_COMMAND 0x0305 #define CMD_READER_ISO_15693 0x0310 #define CMD_SIMTAG_ISO_15693 0x0311 -#define CMD_RECORD_RAW_ADC_SAMPLES_ISO_15693 0x0312 +#define CMD_SNOOP_ISO_15693 0x0312 #define CMD_ISO_15693_COMMAND 0x0313 #define CMD_ISO_15693_COMMAND_DONE 0x0314 #define CMD_ISO_15693_FIND_AFI 0x0315 -- 2.39.2