From d9de20fa4bb0a36052927b55c4185caa204c5c4d Mon Sep 17 00:00:00 2001 From: pwpiwi <pwpiwi@users.noreply.github.com> 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 <stdint.h> + +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],"<empty trace - possible error>"); - } + if (data_len == 0) { + sprintf(line[0]," <empty trace - possible error>"); } - //--- 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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 ZK6Bx<ugQ|lzsD5?`1jCETFQIL{{yB^19<=d diff --git a/fpga/fpga_hf.v b/fpga/fpga_hf.v index f99a43dd..5d55cb89 100644 --- a/fpga/fpga_hf.v +++ b/fpga/fpga_hf.v @@ -75,6 +75,8 @@ wire hi_read_rx_xcorr_848 = conf_word[0]; wire hi_read_rx_xcorr_snoop = conf_word[1]; // divide subcarrier frequency by 4 wire hi_read_rx_xcorr_quarter = conf_word[2]; +// send amplitude only instead of ci/cq pair +wire hi_read_rx_xcorr_amplitude = conf_word[3]; // For the high-frequency simulated tag: what kind of modulation to use. wire [2:0] hi_simulate_mod_type = conf_word[2:0]; @@ -102,7 +104,7 @@ hi_read_rx_xcorr hrxc( hrxc_ssp_frame, hrxc_ssp_din, ssp_dout, hrxc_ssp_clk, cross_hi, cross_lo, hrxc_dbg, - hi_read_rx_xcorr_848, hi_read_rx_xcorr_snoop, hi_read_rx_xcorr_quarter + hi_read_rx_xcorr_848, hi_read_rx_xcorr_snoop, hi_read_rx_xcorr_quarter, hi_read_rx_xcorr_amplitude ); hi_simulate hs( diff --git a/fpga/hi_read_rx_xcorr.v b/fpga/hi_read_rx_xcorr.v index 8233960f..503c8d67 100644 --- a/fpga/hi_read_rx_xcorr.v +++ b/fpga/hi_read_rx_xcorr.v @@ -10,7 +10,7 @@ module hi_read_rx_xcorr( ssp_frame, ssp_din, ssp_dout, ssp_clk, cross_hi, cross_lo, dbg, - xcorr_is_848, snoop, xcorr_quarter_freq + xcorr_is_848, snoop, xcorr_quarter_freq, hi_read_rx_xcorr_amplitude ); input pck0, ck_1356meg, ck_1356megb; output pwr_lo, pwr_hi, pwr_oe1, pwr_oe2, pwr_oe3, pwr_oe4; @@ -20,7 +20,7 @@ module hi_read_rx_xcorr( output ssp_frame, ssp_din, ssp_clk; input cross_hi, cross_lo; output dbg; - input xcorr_is_848, snoop, xcorr_quarter_freq; + input xcorr_is_848, snoop, xcorr_quarter_freq, hi_read_rx_xcorr_amplitude; // Carrier is steady on through this, unless we're snooping. assign pwr_hi = ck_1356megb & (~snoop); @@ -83,11 +83,46 @@ reg signed [13:0] corr_q_accum; // we will report maximum 8 significant bits reg signed [7:0] corr_i_out; reg signed [7:0] corr_q_out; + // clock and frame signal for communication to ARM reg ssp_clk; reg ssp_frame; + +// the amplitude of the subcarrier is sqrt(ci^2 + cq^2). +// approximate by amplitude = max(|ci|,|cq|) + 1/2*min(|ci|,|cq|) +reg [13:0] corr_amplitude, abs_ci, abs_cq, max_ci_cq, min_ci_cq; + + +always @(corr_i_accum or corr_q_accum) +begin + if (corr_i_accum[13] == 1'b0) + abs_ci <= corr_i_accum; + else + abs_ci <= -corr_i_accum; + + if (corr_q_accum[13] == 1'b0) + abs_cq <= corr_q_accum; + else + abs_cq <= -corr_q_accum; + + if (abs_ci > 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.5