From e779f06c5eee888dadcdefb87b2217b242f90ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sun, 29 Jul 2018 11:50:37 +0200 Subject: [PATCH 01/14] change: clean up Legic interface I see no adventage in poluting all sources that include legicrf.h with our internal depedencies (includes) and function names. --- armsrc/legicrf.h | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/armsrc/legicrf.h b/armsrc/legicrf.h index 5c3bd81b9..4bcf04899 100644 --- a/armsrc/legicrf.h +++ b/armsrc/legicrf.h @@ -11,38 +11,11 @@ #ifndef __LEGICRF_H #define __LEGICRF_H -#include "proxmark3.h" // -#include "apps.h" -#include "util.h" // -#include "string.h" -#include "legic_prng.h" // legic PRNG impl -#include "crc.h" // legic crc-4 -#include "ticks.h" // timers -#include "legic.h" // legic_card_select_t struct +#include "proxmark3.h" -extern void LegicRfSimulate(int phase, int frame, int reqresp); -extern int LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv); -extern void LegicRfWriter(uint16_t offset, uint16_t byte, uint8_t iv, uint8_t *data); extern void LegicRfInfo(void); - -uint32_t get_key_stream(int skip, int count); -void frame_send_tag(uint16_t response, uint8_t bits); -void frame_sendAsReader(uint32_t data, uint8_t bits); - -int legic_read_byte( uint16_t index, uint8_t cmd_sz); -bool legic_write_byte(uint16_t index, uint8_t byte, uint8_t addr_sz); - -int legic_select_card(legic_card_select_t *p_card); -int legic_select_card_iv(legic_card_select_t *p_card, uint8_t iv); - -void LegicCommonInit(bool clear_mem); - -// emulator mem -void LegicEMemSet(uint32_t arg0, uint32_t arg1, uint8_t *data); -void LegicEMemGet(uint32_t arg0, uint32_t arg1); -void legic_emlset_mem(uint8_t *data, int offset, int numofbytes); -void legic_emlget_mem(uint8_t *data, int offset, int numofbytes); - -void ice_legic_setup(); +extern void LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv); +extern void LegicRfWriter(uint16_t offset, uint16_t byte, uint8_t iv, uint8_t *data); +extern void LegicRfSimulate(int phase, int frame, int reqresp); #endif /* __LEGICRF_H */ From 8a53137ab0bf0070776db843eaf81cec6ac2f3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sun, 29 Jul 2018 11:57:24 +0200 Subject: [PATCH 02/14] change: remove dead legic code This code was either disabled or never reached. --- armsrc/legicrf.c | 762 +---------------------------------------------- 1 file changed, 1 insertion(+), 761 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index dc4c8d4f6..035e01e98 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -30,43 +30,8 @@ static int legic_phase_drift; static int legic_frame_drift; static int legic_reqresp_drift; -AT91PS_TC timer; -AT91PS_TC prng_timer; -/* -static void setup_timer(void) { - // Set up Timer 1 to use for measuring time between pulses. Since we're bit-banging - // this it won't be terribly accurate but should be good enough. - // - AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC1); - timer = AT91C_BASE_TC1; - timer->TC_CCR = AT91C_TC_CLKDIS; - timer->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; - timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - // - // Set up Timer 2 to use for measuring time between frames in - // tag simulation mode. Runs 4x faster as Timer 1 - // - AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC2); - prng_timer = AT91C_BASE_TC2; - prng_timer->TC_CCR = AT91C_TC_CLKDIS; - prng_timer->TC_CMR = AT91C_TC_CLKS_TIMER_DIV2_CLOCK; - prng_timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; -} - - AT91C_BASE_PMC->PMC_PCER |= (0x1 << 12) | (0x1 << 13) | (0x1 << 14); - AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_NONE | AT91C_TCB_TC1XC1S_TIOA0 | AT91C_TCB_TC2XC2S_NONE; - - // fast clock - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; // timer disable - AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK | // MCK(48MHz)/32 -- tick=1.5mks - AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_ACPA_CLEAR | - AT91C_TC_ACPC_SET | AT91C_TC_ASWTRG_SET; - AT91C_BASE_TC0->TC_RA = 1; - AT91C_BASE_TC0->TC_RC = 0xBFFF + 1; // 0xC000 - -*/ // At TIMER_CLOCK3 (MCK/32) // testing calculating in ticks. 1.5ticks = 1us @@ -126,20 +91,9 @@ static void frame_clean(struct legic_frame * const f) { f->bits = 0; } -// Prng works when waiting in 99.1us cycles. -// and while sending/receiving in bit frames (100, 60) -/*static void CalibratePrng( uint32_t time){ - // Calculate Cycles based on timer 100us - uint32_t i = (time - sendFrameStop) / 100 ; - // substract cycles of finished frames - int k = i - legic_prng_count()+1; - // substract current frame length, rewind to beginning - if ( k > 0 ) - legic_prng_forward(k); } -*/ /* Generate Keystream */ uint32_t get_key_stream(int skip, int count) { @@ -575,36 +529,7 @@ int legic_select_card(legic_card_select_t *p_card){ } //----------------------------------------------------------------------------- -// Work with emulator memory -// -// Note: we call FpgaDownloadAndGo(FPGA_BITSTREAM_HF) here although FPGA is not -// involved in dealing with emulator memory. But if it is called later, it might -// destroy the Emulator Memory. //----------------------------------------------------------------------------- -// arg0 = offset -// arg1 = num of bytes -void LegicEMemSet(uint32_t arg0, uint32_t arg1, uint8_t *data) { - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - legic_emlset_mem(data, arg0, arg1); -} -// arg0 = offset -// arg1 = num of bytes -void LegicEMemGet(uint32_t arg0, uint32_t arg1) { - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - uint8_t buf[USB_CMD_DATA_SIZE] = {0x00}; - legic_emlget_mem(buf, arg0, arg1); - LED_B_ON(); - cmd_send(CMD_ACK, arg0, arg1, 0, buf, USB_CMD_DATA_SIZE); - LED_B_OFF(); -} -void legic_emlset_mem(uint8_t *data, int offset, int numofbytes) { - cardmem = BigBuf_get_EM_addr(); - memcpy(cardmem + offset, data, numofbytes); -} -void legic_emlget_mem(uint8_t *data, int offset, int numofbytes) { - cardmem = BigBuf_get_EM_addr(); - memcpy(data, cardmem + offset, numofbytes); -} void LegicRfInfo(void){ @@ -932,691 +857,6 @@ void LegicRfSimulate(int phase, int frame, int reqresp) LEDsoff(); cmd_send(CMD_ACK, 1, 0, 0, 0, 0); } - - -//----------------------------------------------------------------------------- -// Code up a string of octets at layer 2 (including CRC, we don't generate -// that here) so that they can be transmitted to the reader. Doesn't transmit -// them yet, just leaves them ready to send in ToSend[]. -//----------------------------------------------------------------------------- -// static void CodeLegicAsTag(const uint8_t *cmd, int len) -// { - // int i; - - // ToSendReset(); - - // // Transmit a burst of ones, as the initial thing that lets the - // // reader get phase sync. This (TR1) must be > 80/fs, per spec, - // // but tag that I've tried (a Paypass) exceeds that by a fair bit, - // // so I will too. - // for(i = 0; i < 20; i++) { - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // } - - // // Send SOF. - // for(i = 0; i < 10; i++) { - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // } - // for(i = 0; i < 2; i++) { - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // } - - // for(i = 0; i < len; i++) { - // int j; - // uint8_t b = cmd[i]; - - // // Start bit - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - - // // Data bits - // for(j = 0; j < 8; j++) { - // if(b & 1) { - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // } else { - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // } - // b >>= 1; - // } - - // // Stop bit - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // } - - // // Send EOF. - // for(i = 0; i < 10; i++) { - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // ToSendStuffBit(0); - // } - // for(i = 0; i < 2; i++) { - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // ToSendStuffBit(1); - // } - - // // Convert from last byte pos to length - // ToSendMax++; -// } - -//----------------------------------------------------------------------------- -// The software UART that receives commands from the reader, and its state -// variables. -//----------------------------------------------------------------------------- -/* -static struct { - enum { - STATE_UNSYNCD, - STATE_GOT_FALLING_EDGE_OF_SOF, - STATE_AWAITING_START_BIT, - STATE_RECEIVING_DATA - } state; - uint16_t shiftReg; - int bitCnt; - int byteCnt; - int byteCntMax; - int posCnt; - uint8_t *output; -} Uart; -*/ -/* Receive & handle a bit coming from the reader. - * - * This function is called 4 times per bit (every 2 subcarrier cycles). - * Subcarrier frequency fs is 212kHz, 1/fs = 4,72us, i.e. function is called every 9,44us - * - * LED handling: - * LED A -> ON once we have received the SOF and are expecting the rest. - * LED A -> OFF once we have received EOF or are in error state or unsynced - * - * Returns: true if we received a EOF - * false if we are still waiting for some more - */ -// static RAMFUNC int HandleLegicUartBit(uint8_t bit) -// { - // switch(Uart.state) { - // case STATE_UNSYNCD: - // if(!bit) { - // // we went low, so this could be the beginning of an SOF - // Uart.state = STATE_GOT_FALLING_EDGE_OF_SOF; - // Uart.posCnt = 0; - // Uart.bitCnt = 0; - // } - // break; - - // case STATE_GOT_FALLING_EDGE_OF_SOF: - // Uart.posCnt++; - // if(Uart.posCnt == 2) { // sample every 4 1/fs in the middle of a bit - // if(bit) { - // if(Uart.bitCnt > 9) { - // // we've seen enough consecutive - // // zeros that it's a valid SOF - // Uart.posCnt = 0; - // Uart.byteCnt = 0; - // Uart.state = STATE_AWAITING_START_BIT; - // LED_A_ON(); // Indicate we got a valid SOF - // } else { - // // didn't stay down long enough - // // before going high, error - // Uart.state = STATE_UNSYNCD; - // } - // } else { - // // do nothing, keep waiting - // } - // Uart.bitCnt++; - // } - // if(Uart.posCnt >= 4) Uart.posCnt = 0; - // if(Uart.bitCnt > 12) { - // // Give up if we see too many zeros without - // // a one, too. - // LED_A_OFF(); - // Uart.state = STATE_UNSYNCD; - // } - // break; - - // case STATE_AWAITING_START_BIT: - // Uart.posCnt++; - // if(bit) { - // if(Uart.posCnt > 50/2) { // max 57us between characters = 49 1/fs, max 3 etus after low phase of SOF = 24 1/fs - // // stayed high for too long between - // // characters, error - // Uart.state = STATE_UNSYNCD; - // } - // } else { - // // falling edge, this starts the data byte - // Uart.posCnt = 0; - // Uart.bitCnt = 0; - // Uart.shiftReg = 0; - // Uart.state = STATE_RECEIVING_DATA; - // } - // break; - - // case STATE_RECEIVING_DATA: - // Uart.posCnt++; - // if(Uart.posCnt == 2) { - // // time to sample a bit - // Uart.shiftReg >>= 1; - // if(bit) { - // Uart.shiftReg |= 0x200; - // } - // Uart.bitCnt++; - // } - // if(Uart.posCnt >= 4) { - // Uart.posCnt = 0; - // } - // if(Uart.bitCnt == 10) { - // if((Uart.shiftReg & 0x200) && !(Uart.shiftReg & 0x001)) - // { - // // this is a data byte, with correct - // // start and stop bits - // Uart.output[Uart.byteCnt] = (Uart.shiftReg >> 1) & 0xff; - // Uart.byteCnt++; - - // if(Uart.byteCnt >= Uart.byteCntMax) { - // // Buffer overflowed, give up - // LED_A_OFF(); - // Uart.state = STATE_UNSYNCD; - // } else { - // // so get the next byte now - // Uart.posCnt = 0; - // Uart.state = STATE_AWAITING_START_BIT; - // } - // } else if (Uart.shiftReg == 0x000) { - // // this is an EOF byte - // LED_A_OFF(); // Finished receiving - // Uart.state = STATE_UNSYNCD; - // if (Uart.byteCnt != 0) { - // return TRUE; - // } - // } else { - // // this is an error - // LED_A_OFF(); - // Uart.state = STATE_UNSYNCD; - // } - // } - // break; - - // default: - // LED_A_OFF(); - // Uart.state = STATE_UNSYNCD; - // break; - // } - - // return false; -// } -/* - -static void UartReset() { - Uart.byteCntMax = 3; - Uart.state = STATE_UNSYNCD; - Uart.byteCnt = 0; - Uart.bitCnt = 0; - Uart.posCnt = 0; - memset(Uart.output, 0x00, 3); -} -*/ -// static void UartInit(uint8_t *data) { - // Uart.output = data; - // UartReset(); -// } - -//============================================================================= -// An LEGIC reader. We take layer two commands, code them -// appropriately, and then send them to the tag. We then listen for the -// tag's response, which we leave in the buffer to be demodulated on the -// PC side. -//============================================================================= -/* -static struct { - enum { - DEMOD_UNSYNCD, - DEMOD_PHASE_REF_TRAINING, - DEMOD_AWAITING_FALLING_EDGE_OF_SOF, - DEMOD_GOT_FALLING_EDGE_OF_SOF, - DEMOD_AWAITING_START_BIT, - DEMOD_RECEIVING_DATA - } state; - int bitCount; - int posCount; - int thisBit; - uint16_t shiftReg; - uint8_t *output; - int len; - int sumI; - int sumQ; -} Demod; -*/ -/* - * Handles reception of a bit from the tag - * - * This function is called 2 times per bit (every 4 subcarrier cycles). - * Subcarrier frequency fs is 212kHz, 1/fs = 4,72us, i.e. function is called every 9,44us - * - * LED handling: - * LED C -> ON once we have received the SOF and are expecting the rest. - * LED C -> OFF once we have received EOF or are unsynced - * - * Returns: true if we received a EOF - * false if we are still waiting for some more - * - */ - -/* -static RAMFUNC int HandleLegicSamplesDemod(int ci, int cq) -{ - int v = 0; - int ai = ABS(ci); - int aq = ABS(cq); - int halfci = (ai >> 1); - int halfcq = (aq >> 1); - - switch(Demod.state) { - case DEMOD_UNSYNCD: - - CHECK_FOR_SUBCARRIER() - - if(v > SUBCARRIER_DETECT_THRESHOLD) { // subcarrier detected - Demod.state = DEMOD_PHASE_REF_TRAINING; - Demod.sumI = ci; - Demod.sumQ = cq; - Demod.posCount = 1; - } - break; - - case DEMOD_PHASE_REF_TRAINING: - if(Demod.posCount < 8) { - - CHECK_FOR_SUBCARRIER() - - if (v > SUBCARRIER_DETECT_THRESHOLD) { - // set the reference phase (will code a logic '1') by averaging over 32 1/fs. - // note: synchronization time > 80 1/fs - Demod.sumI += ci; - Demod.sumQ += cq; - ++Demod.posCount; - } else { - // subcarrier lost - Demod.state = DEMOD_UNSYNCD; - } - } else { - Demod.state = DEMOD_AWAITING_FALLING_EDGE_OF_SOF; - } - break; - - case DEMOD_AWAITING_FALLING_EDGE_OF_SOF: - - MAKE_SOFT_DECISION() - - //Dbprintf("ICE: %d %d %d %d %d", v, Demod.sumI, Demod.sumQ, ci, cq ); - // logic '0' detected - if (v <= 0) { - - Demod.state = DEMOD_GOT_FALLING_EDGE_OF_SOF; - - // start of SOF sequence - Demod.posCount = 0; - } else { - // maximum length of TR1 = 200 1/fs - if(Demod.posCount > 25*2) Demod.state = DEMOD_UNSYNCD; - } - ++Demod.posCount; - break; - - case DEMOD_GOT_FALLING_EDGE_OF_SOF: - ++Demod.posCount; - - MAKE_SOFT_DECISION() - - if(v > 0) { - // low phase of SOF too short (< 9 etu). Note: spec is >= 10, but FPGA tends to "smear" edges - if(Demod.posCount < 10*2) { - Demod.state = DEMOD_UNSYNCD; - } else { - LED_C_ON(); // Got SOF - Demod.state = DEMOD_AWAITING_START_BIT; - Demod.posCount = 0; - Demod.len = 0; - } - } else { - // low phase of SOF too long (> 12 etu) - if(Demod.posCount > 13*2) { - Demod.state = DEMOD_UNSYNCD; - LED_C_OFF(); - } - } - break; - - case DEMOD_AWAITING_START_BIT: - ++Demod.posCount; - - MAKE_SOFT_DECISION() - - if(v > 0) { - // max 19us between characters = 16 1/fs, max 3 etu after low phase of SOF = 24 1/fs - if(Demod.posCount > 3*2) { - Demod.state = DEMOD_UNSYNCD; - LED_C_OFF(); - } - } else { - // start bit detected - Demod.bitCount = 0; - Demod.posCount = 1; // this was the first half - Demod.thisBit = v; - Demod.shiftReg = 0; - Demod.state = DEMOD_RECEIVING_DATA; - } - break; - - case DEMOD_RECEIVING_DATA: - - MAKE_SOFT_DECISION() - - if(Demod.posCount == 0) { - // first half of bit - Demod.thisBit = v; - Demod.posCount = 1; - } else { - // second half of bit - Demod.thisBit += v; - Demod.shiftReg >>= 1; - // logic '1' - if(Demod.thisBit > 0) - Demod.shiftReg |= 0x200; - - ++Demod.bitCount; - - if(Demod.bitCount == 10) { - - uint16_t s = Demod.shiftReg; - - if((s & 0x200) && !(s & 0x001)) { - // stop bit == '1', start bit == '0' - uint8_t b = (s >> 1); - Demod.output[Demod.len] = b; - ++Demod.len; - Demod.state = DEMOD_AWAITING_START_BIT; - } else { - Demod.state = DEMOD_UNSYNCD; - LED_C_OFF(); - - if(s == 0x000) { - // This is EOF (start, stop and all data bits == '0' - return true; - } - } - } - Demod.posCount = 0; - } - break; - - default: - Demod.state = DEMOD_UNSYNCD; - LED_C_OFF(); - break; - } - return false; -} -*/ -/* -// Clear out the state of the "UART" that receives from the tag. -static void DemodReset() { - Demod.len = 0; - Demod.state = DEMOD_UNSYNCD; - Demod.posCount = 0; - Demod.sumI = 0; - Demod.sumQ = 0; - Demod.bitCount = 0; - Demod.thisBit = 0; - Demod.shiftReg = 0; - memset(Demod.output, 0x00, 3); } -static void DemodInit(uint8_t *data) { - Demod.output = data; - DemodReset(); -} -*/ - -/* - * Demodulate the samples we received from the tag, also log to tracebuffer - * quiet: set to 'TRUE' to disable debug output - */ - - /* - #define LEGIC_DMA_BUFFER_SIZE 256 - - static void GetSamplesForLegicDemod(int n, bool quiet) -{ - int max = 0; - bool gotFrame = false; - int lastRxCounter = LEGIC_DMA_BUFFER_SIZE; - int ci, cq, samples = 0; - - BigBuf_free(); - - // And put the FPGA in the appropriate mode - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_QUARTER_FREQ); - - // The response (tag -> reader) that we're receiving. - // Set up the demodulator for tag -> reader responses. - DemodInit(BigBuf_malloc(MAX_FRAME_SIZE)); - - // The DMA buffer, used to stream samples from the FPGA - int8_t *dmaBuf = (int8_t*) BigBuf_malloc(LEGIC_DMA_BUFFER_SIZE); - int8_t *upTo = dmaBuf; - - // Setup and start DMA. - if ( !FpgaSetupSscDma((uint8_t*) dmaBuf, LEGIC_DMA_BUFFER_SIZE) ){ - if (MF_DBGLEVEL > 1) Dbprintf("FpgaSetupSscDma failed. Exiting"); - return; - } - - // Signal field is ON with the appropriate LED: - LED_D_ON(); - for(;;) { - int behindBy = lastRxCounter - AT91C_BASE_PDC_SSC->PDC_RCR; - if(behindBy > max) max = behindBy; - - while(((lastRxCounter-AT91C_BASE_PDC_SSC->PDC_RCR) & (LEGIC_DMA_BUFFER_SIZE-1)) > 2) { - ci = upTo[0]; - cq = upTo[1]; - upTo += 2; - if(upTo >= dmaBuf + LEGIC_DMA_BUFFER_SIZE) { - upTo = dmaBuf; - AT91C_BASE_PDC_SSC->PDC_RNPR = (uint32_t) upTo; - AT91C_BASE_PDC_SSC->PDC_RNCR = LEGIC_DMA_BUFFER_SIZE; - } - lastRxCounter -= 2; - if(lastRxCounter <= 0) - lastRxCounter = LEGIC_DMA_BUFFER_SIZE; - - samples += 2; - - gotFrame = HandleLegicSamplesDemod(ci , cq ); - if ( gotFrame ) - break; - } - - if(samples > n || gotFrame) - break; - } - - FpgaDisableSscDma(); - - if (!quiet && Demod.len == 0) { - Dbprintf("max behindby = %d, samples = %d, gotFrame = %d, Demod.len = %d, Demod.sumI = %d, Demod.sumQ = %d", - max, - samples, - gotFrame, - Demod.len, - Demod.sumI, - Demod.sumQ - ); - } - - //Tracing - if (Demod.len > 0) { - uint8_t parity[MAX_PARITY_SIZE] = {0x00}; - LogTrace(Demod.output, Demod.len, 0, 0, parity, false); - } -} - -*/ - -//----------------------------------------------------------------------------- -// Transmit the command (to the tag) that was placed in ToSend[]. -//----------------------------------------------------------------------------- -/* -static void TransmitForLegic(void) -{ - int c; - - FpgaSetupSsc(); - - while(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) - AT91C_BASE_SSC->SSC_THR = 0xff; - - // Signal field is ON with the appropriate Red LED - LED_D_ON(); - - // Signal we are transmitting with the Green LED - LED_B_ON(); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX | FPGA_HF_READER_TX_SHALLOW_MOD); - - for(c = 0; c < 10;) { - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0xff; - c++; - } - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - volatile uint32_t r = AT91C_BASE_SSC->SSC_RHR; - (void)r; - } - WDT_HIT(); - } - - c = 0; - for(;;) { - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = ToSend[c]; - legic_prng_forward(1); // forward the lfsr - c++; - if(c >= ToSendMax) { - break; - } - } - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - volatile uint32_t r = AT91C_BASE_SSC->SSC_RHR; - (void)r; - } - WDT_HIT(); - } - LED_B_OFF(); -} -*/ - -//----------------------------------------------------------------------------- -// Code a layer 2 command (string of octets, including CRC) into ToSend[], -// so that it is ready to transmit to the tag using TransmitForLegic(). -//----------------------------------------------------------------------------- -/* -static void CodeLegicBitsAsReader(const uint8_t *cmd, uint8_t cmdlen, int bits) -{ - int i, j; - uint8_t b; - - ToSendReset(); - - // Send SOF - for(i = 0; i < 7; i++) - ToSendStuffBit(1); - - - for(i = 0; i < cmdlen; i++) { - // Start bit - ToSendStuffBit(0); - - // Data bits - b = cmd[i]; - for(j = 0; j < bits; j++) { - if(b & 1) { - ToSendStuffBit(1); - } else { - ToSendStuffBit(0); - } - b >>= 1; - } - } - - // Convert from last character reference to length - ++ToSendMax; -} -*/ -/** - Convenience function to encode, transmit and trace Legic comms - **/ -/* - static void CodeAndTransmitLegicAsReader(const uint8_t *cmd, uint8_t cmdlen, int bits) -{ - CodeLegicBitsAsReader(cmd, cmdlen, bits); - TransmitForLegic(); - if (tracing) { - uint8_t parity[1] = {0x00}; - LogTrace(cmd, cmdlen, 0, 0, parity, true); - } -} - -*/ -// Set up LEGIC communication -/* -void ice_legic_setup() { - - // standard things. - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - BigBuf_free(); BigBuf_Clear_ext(false); - clear_trace(); - set_tracing(true); - DemodReset(); - UartReset(); - - // Set up the synchronous serial port - FpgaSetupSsc(); - - // connect Demodulated Signal to ADC: - SetAdcMuxFor(GPIO_MUXSEL_HIPKD); - - // Signal field is on with the appropriate LED - LED_D_ON(); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX | FPGA_HF_READER_TX_SHALLOW_MOD); - SpinDelay(20); - // Start the timer - //StartCountSspClk(); - - // initalize CRC - crc_init(&legic_crc, 4, 0x19 >> 1, 0x5, 0); - - // initalize prng - legic_prng_init(0); -} -*/ \ No newline at end of file +} \ No newline at end of file From db70ab8f7d0fac21228f345f238b1527f1e15a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sun, 29 Jul 2018 12:07:54 +0200 Subject: [PATCH 03/14] change: remove broken legic simulator It will be rewritten in a later commit --- armsrc/legicrf.c | 338 +---------------------------------------------- 1 file changed, 2 insertions(+), 336 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 035e01e98..e0475bc9b 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -41,37 +41,8 @@ static int legic_reqresp_drift; #define TAG_BIT_PERIOD 142 // 100us == 100 * 1.5 == 150ticks #define TAG_FRAME_WAIT 495 // 330us from READER frame end to TAG frame start. 330 * 1.5 == 495 -#define RWD_TIME_FUZZ 20 // rather generous 13us, since the peak detector + hysteresis fuzz quite a bit - -#define SIM_DIVISOR 586 /* prng_time/SIM_DIVISOR count prng needs to be forwared */ -#define SIM_SHIFT 900 /* prng_time+SIM_SHIFT shift of delayed start */ - #define OFFSET_LOG 1024 -#define FUZZ_EQUAL(value, target, fuzz) ((value) > ((target)-(fuzz)) && (value) < ((target)+(fuzz))) - -#ifndef SHORT_COIL -# define SHORT_COIL LOW(GPIO_SSC_DOUT); -#endif -#ifndef OPEN_COIL -# define OPEN_COIL HIGH(GPIO_SSC_DOUT); -#endif -#ifndef LINE_IN -# define LINE_IN AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DIN; -#endif -// Pause pulse, off in 20us / 30ticks, -// ONE / ZERO bit pulse, -// one == 80us / 120ticks -// zero == 40us / 60ticks -#ifndef COIL_PULSE -# define COIL_PULSE(x) \ - do { \ - SHORT_COIL; \ - WaitTicks( (RWD_TIME_PAUSE) ); \ - OPEN_COIL; \ - WaitTicks((x)); \ - } while (0); -#endif // ToDo: define a meaningful maximum size for auth_table. The bigger this is, the lower will be the available memory for traces. // Historically it used to be FREE_BUFFER_SIZE, which was 2744. @@ -121,33 +92,8 @@ uint32_t get_key_stream(int skip, int count) { return legic_prng_get_bits(count); } -/* Send a frame in tag mode, the FPGA must have been set up by - * LegicRfSimulate - */ -void frame_send_tag(uint16_t response, uint8_t bits) { - uint16_t mask = 1; - - /* Bitbang the response */ - SHORT_COIL; - AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; - /* TAG_FRAME_WAIT -> shift by 2 */ - legic_prng_forward(3); - response ^= legic_prng_get_bits(bits); - - /* Wait for the frame start */ - WaitTicks( TAG_FRAME_WAIT ); - - for (; mask < BITMASK(bits); mask <<= 1) { - if (response & mask) - OPEN_COIL - else - SHORT_COIL - WaitTicks(TAG_BIT_PERIOD); - } - SHORT_COIL; } /* Send a frame in reader mode, the FPGA must have been set up by @@ -571,292 +517,12 @@ OUT: LEDsoff(); } -/* Handle (whether to respond) a frame in tag mode - * Only called when simulating a tag. - */ -static void frame_handle_tag(struct legic_frame const * const f) -{ - // log - //uint8_t cmdbytes[] = {bits, BYTEx(data, 0), BYTEx(data, 1)}; - //LogTrace(cmdbytes, sizeof(cmdbytes), starttime, GET_TICKS, NULL, false); - //Dbprintf("ICE: enter frame_handle_tag: %02x ", f->bits); - - /* First Part of Handshake (IV) */ - if(f->bits == 7) { - - LED_C_ON(); - - // Reset prng timer - //ResetTimer(prng_timer); - ResetTicks(); - - // IV from reader. - legic_prng_init(f->data); - - Dbprintf("ICE: IV: %02x ", f->data); - - // We should have three tagtypes with three different answers. - legic_prng_forward(2); - //frame_send_tag(0x3d, 6); /* MIM1024 0x3d^0x26 = 0x1B */ - frame_send_tag(0x1d, 6); // MIM256 - - legic_state = STATE_IV; - legic_read_count = 0; - legic_prng_bc = 0; - legic_prng_iv = f->data; - - //ResetTimer(timer); - //WaitUS(280); - WaitTicks(388); - return; - } - - /* 0x19==??? */ - if(legic_state == STATE_IV) { - uint32_t local_key = get_key_stream(3, 6); - int xored = 0x39 ^ local_key; - if((f->bits == 6) && (f->data == xored)) { - legic_state = STATE_CON; - - ResetTimer(timer); - WaitTicks(300); - return; - - } else { - legic_state = STATE_DISCON; - LED_C_OFF(); - Dbprintf("iv: %02x frame: %02x key: %02x xored: %02x", legic_prng_iv, f->data, local_key, xored); - return; - } - } - - /* Read */ - if(f->bits == 11) { - if(legic_state == STATE_CON) { - uint32_t key = get_key_stream(2, 11); //legic_phase_drift, 11); - uint16_t addr = f->data ^ key; - addr >>= 1; - uint8_t data = cardmem[addr]; - - uint32_t crc = legic4Crc(LEGIC_READ, addr, data, 11) << 8; - - //legic_read_count++; - //legic_prng_forward(legic_reqresp_drift); - - frame_send_tag(crc | data, 12); - //ResetTimer(timer); - legic_prng_forward(2); - WaitTicks(330); - return; - } - } - - /* Write */ - if (f->bits == 23 || f->bits == 21 ) { - uint32_t key = get_key_stream(-1, 23); //legic_frame_drift, 23); - uint16_t addr = f->data ^ key; - addr >>= 1; - addr &= 0x3ff; - uint32_t data = f->data ^ key; - data >>= 11; - data &= 0xff; - - cardmem[addr] = data; - /* write command */ - legic_state = STATE_DISCON; - LED_C_OFF(); - Dbprintf("write - addr: %x, data: %x", addr, data); - // should send a ACK after 3.6ms - return; - } - - if(legic_state != STATE_DISCON) { - Dbprintf("Unexpected: sz:%u, Data:%03.3x, State:%u, Count:%u", f->bits, f->data, legic_state, legic_read_count); - Dbprintf("IV: %03.3x", legic_prng_iv); - } - - legic_state = STATE_DISCON; - legic_read_count = 0; - WaitMS(10); - LED_C_OFF(); - return; } -/* Read bit by bit untill full frame is received - * Call to process frame end answer - */ -static void emit(int bit) { - - switch (bit) { - case 1: - frame_append_bit(¤t_frame, 1); - break; - case 0: - frame_append_bit(¤t_frame, 0); - break; - default: - if(current_frame.bits <= 4) { - frame_clean(¤t_frame); - } else { - frame_handle_tag(¤t_frame); - frame_clean(¤t_frame); - } - WDT_HIT(); - break; - } } -void LegicRfSimulate(int phase, int frame, int reqresp) -{ - /* ADC path high-frequency peak detector, FPGA in high-frequency simulator mode, - * modulation mode set to 212kHz subcarrier. We are getting the incoming raw - * envelope waveform on DIN and should send our response on DOUT. - * - * The LEGIC RF protocol is pulse-pause-encoding from reader to card, so we'll - * measure the time between two rising edges on DIN, and no encoding on the - * subcarrier from card to reader, so we'll just shift out our verbatim data - * on DOUT, 1 bit is 100us. The time from reader to card frame is still unclear, - * seems to be 330us. - */ - - int old_level = 0, active = 0; - volatile int32_t level = 0; - - legic_state = STATE_DISCON; - legic_phase_drift = phase; - legic_frame_drift = frame; - legic_reqresp_drift = reqresp; - - - /* to get the stream of bits from FPGA in sim mode.*/ - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - // Set up the synchronous serial port - //FpgaSetupSsc(); - // connect Demodulated Signal to ADC: - SetAdcMuxFor(GPIO_MUXSEL_HIPKD); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_212K); - //FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_NO_MODULATION); - - #define LEGIC_DMA_BUFFER 256 - // The DMA buffer, used to stream samples from the FPGA - //uint8_t *dmaBuf = BigBuf_malloc(LEGIC_DMA_BUFFER); - //uint8_t *data = dmaBuf; - // Setup and start DMA. - // if ( !FpgaSetupSscDma((uint8_t*) dmaBuf, LEGIC_DMA_BUFFER) ){ - // if (MF_DBGLEVEL > 1) Dbprintf("FpgaSetupSscDma failed. Exiting"); - // return; - // } - - //StartCountSspClk(); - /* Bitbang the receiver */ - AT91C_BASE_PIOA->PIO_ODR = GPIO_SSC_DIN; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DIN; - - // need a way to determine which tagtype we are simulating - - // hook up emulator memory - cardmem = BigBuf_get_EM_addr(); - - clear_trace(); - set_tracing(true); - - crc_init(&legic_crc, 4, 0x19 >> 1, 0x5, 0); - - StartTicks(); - - LED_B_ON(); - DbpString("Starting Legic emulator, press button to end"); - - /* - * The mode FPGA_HF_SIMULATOR_MODULATE_212K works like this. - * - A 1-bit input to the FPGA becomes 8 pulses on 212kHz (fc/64) (18.88us). - * - A 0-bit input to the FPGA becomes an unmodulated time of 18.88us - * - * In this mode the SOF can be written as 00011101 = 0x1D - * The EOF can be written as 10111000 = 0xb8 - * A logic 1 is 01 - * A logic 0 is 10 - volatile uint8_t b; - uint8_t i = 0; - while( !BUTTON_PRESS() ) { - WDT_HIT(); - - // not sending anything. - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0x00; - } - - // receive - if ( AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY ) { - b = (uint8_t) AT91C_BASE_SSC->SSC_RHR; - bd[i] = b; - ++i; - // if(OutOfNDecoding(b & 0x0f)) - // *len = Uart.byteCnt; - } - - } - */ - - while(!BUTTON_PRESS() && !usb_poll_validate_length()) { - - level = !!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN); - - uint32_t time = GET_TICKS; - - if (level != old_level) { - if (level == 1) { - - //Dbprintf("start, %u ", time); - StartTicks(); - // did we get a signal - if (FUZZ_EQUAL(time, RWD_TIME_1, RWD_TIME_FUZZ)) { - // 1 bit - emit(1); - active = 1; - LED_A_ON(); - } else if (FUZZ_EQUAL(time, RWD_TIME_0, RWD_TIME_FUZZ)) { - // 0 bit - emit(0); - active = 1; - LED_A_ON(); - } else if (active) { - // invalid - emit(-1); - active = 0; - LED_A_OFF(); - } - } - } - - - /* Frame end */ - if(time >= (RWD_TIME_1 + RWD_TIME_FUZZ) && active) { - emit(-1); - active = 0; - LED_A_OFF(); - } - - /* - * Disable the counter, Then wait for the clock to acknowledge the - * shutdown in its status register. Reading the SR has the - * side-effect of clearing any pending state in there. - */ - //if(time >= (20*RWD_TIME_1) && (timer->TC_SR & AT91C_TC_CLKSTA)) - if(time >= (20 * RWD_TIME_1) ) - StopTicks(); - - old_level = level; - WDT_HIT(); } - WDT_HIT(); - DbpString("LEGIC Prime emulator stopped"); - switch_off_tag_rwd(); - FpgaDisableSscDma(); - LEDsoff(); - cmd_send(CMD_ACK, 1, 0, 0, 0, 0); +void LegicRfSimulate(int phase, int frame, int reqresp) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); //TODO Implement } -} - -} \ No newline at end of file From bf123082e89459e72b23639f2ac6c535b7ba164c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sun, 29 Jul 2018 12:23:35 +0200 Subject: [PATCH 04/14] change: remove jerry-riged hysteresis based receiver from hi_read_tx This future got obsolete by a x-correlation based receiver. This reverts commit 24fe4dffb49ca5c50983c54f1b1d51028c06390d. --- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/hi_read_tx.v | 17 +++-------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index 37ce028b01a5bf206f7aa690b5ae7f1968388d82..00e71c432289cab51d023861a43828767f15d241 100644 GIT binary patch literal 42175 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 SDJOpiTT13LR@j#EGx-nlfX!L} literal 42175 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 diff --git a/fpga/hi_read_tx.v b/fpga/hi_read_tx.v index bede40965..756683cdd 100644 --- a/fpga/hi_read_tx.v +++ b/fpga/hi_read_tx.v @@ -71,19 +71,8 @@ always @(negedge ssp_clk) assign ssp_frame = (hi_byte_div == 3'b000); -// Implement a hysteresis to give out the received signal on -// ssp_din. Sample at fc. -assign adc_clk = ck_1356meg; +assign ssp_din = 1'b0; -// ADC data appears on the rising edge, so sample it on the falling edge -reg after_hysteresis; -always @(negedge adc_clk) -begin - if(& adc_d[6:4]) after_hysteresis <= 1'b1; - else if(~(| adc_d[6:4])) after_hysteresis <= 1'b0; -end +assign dbg = ssp_frame; -assign ssp_din = after_hysteresis; -assign dbg = after_hysteresis; - -endmodule +endmodule \ No newline at end of file From c59150657ccfe9f9b3d331c6334aff0de2c91c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sun, 29 Jul 2018 11:47:54 +0200 Subject: [PATCH 05/14] add: xcorr 211.875 kHz option The FPGA supported this frequency for a long time, just the ARM code had no define to enable it. --- armsrc/fpgaloader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/armsrc/fpgaloader.h b/armsrc/fpgaloader.h index 66cca0510..21731b571 100644 --- a/armsrc/fpgaloader.h +++ b/armsrc/fpgaloader.h @@ -74,6 +74,7 @@ extern void switch_off(void); // 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 (1<<2) // Options for the HF simulated tag, how to modulate #define FPGA_HF_SIMULATOR_NO_MODULATION (0<<0) // 0000 #define FPGA_HF_SIMULATOR_MODULATE_BPSK (1<<0) // 0001 From 30292231584a014138915c3ba6a8d67e935204a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sun, 29 Jul 2018 12:18:08 +0200 Subject: [PATCH 06/14] change: legic reader now uses xcorrelation and ssc based io - Even tough legic tags transmit just AM using xcorrelation results in a significantly better signal quality. - Switching from bit bang to a hardware based ssc frees up CPU time for other tasks e.g. demodulation --- armsrc/legicrf.c | 902 +++++++++++++++++++++-------------------------- 1 file changed, 408 insertions(+), 494 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index e0475bc9b..7837322e0 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -1,6 +1,7 @@ //----------------------------------------------------------------------------- // (c) 2009 Henryk Plötz // 2016 Iceman +// 2018 AntiCat (rwd rewritten) // // 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 @@ -10,517 +11,430 @@ //----------------------------------------------------------------------------- #include "legicrf.h" -static struct legic_frame { - uint8_t bits; - uint32_t data; -} current_frame; +#include "ticks.h" /* timers */ +#include "crc.h" /* legic crc-4 */ +#include "legic_prng.h" /* legic PRNG impl */ +#include "legic.h" /* legic_card_select_t struct */ -static enum { - STATE_DISCON, - STATE_IV, - STATE_CON, -} legic_state; +struct legic_frame { + uint32_t bits; /* length of frame */ + uint8_t data[24]; /* preprocessed bits */ +}; -static crc_t legic_crc; -static int legic_read_count; -static uint32_t legic_prng_bc; -static uint32_t legic_prng_iv; +union frame_encoder { + uint32_t uint32; /* SAM7S512 does not support unaligned access as a */ + uint8_t uint8[4]; /* workaround 32bit values are converted to 4x8bit */ +}; -static int legic_phase_drift; -static int legic_frame_drift; -static int legic_reqresp_drift; +static uint8_t* legic_mem; /* card memory, used for read, write and sim */ +static legic_card_select_t card;/* metadata of currently selected card */ +static crc_t legic_crc; +static int32_t input_threshold; /* values > threshold are 1 else 0 */ +// LEGIC RF is using the common timer functions: StartCountUS() and GetCountUS() +#define RWD_TIME_PAUSE 20 /* 20us */ +#define RWD_TIME_1 100 /* READER_TIME_PAUSE 20us off + 80us on = 100us */ +#define RWD_TIME_0 60 /* READER_TIME_PAUSE 20us off + 40us on = 60us */ +#define TAG_FRAME_WAIT 330 /* 330us from READER frame end to TAG frame start */ +#define TAG_BIT_PERIOD 100 /* 100us */ +#define LEGIC_CARD_MEMSIZE 1024 /* The largest Legic Prime card is 1k */ +//----------------------------------------------------------------------------- +// I/O interface abstraction (ARM <-> FPGA) +//----------------------------------------------------------------------------- -// At TIMER_CLOCK3 (MCK/32) -// testing calculating in ticks. 1.5ticks = 1us -#define RWD_TIME_1 120 // READER_TIME_PAUSE 20us off, 80us on = 100us 80 * 1.5 == 120ticks -#define RWD_TIME_0 60 // READER_TIME_PAUSE 20us off, 40us on = 60us 40 * 1.5 == 60ticks -#define RWD_TIME_PAUSE 30 // 20us == 20 * 1.5 == 30ticks */ -#define TAG_BIT_PERIOD 142 // 100us == 100 * 1.5 == 150ticks -#define TAG_FRAME_WAIT 495 // 330us from READER frame end to TAG frame start. 330 * 1.5 == 495 +static inline uint8_t rx_byte_from_fpga() { + for(;;) { + WDT_HIT(); -#define OFFSET_LOG 1024 - - -// ToDo: define a meaningful maximum size for auth_table. The bigger this is, the lower will be the available memory for traces. -// Historically it used to be FREE_BUFFER_SIZE, which was 2744. -#define LEGIC_CARD_MEMSIZE 1024 -static uint8_t* cardmem; - -static void frame_append_bit(struct legic_frame * const f, uint8_t bit) { - // Overflow, won't happen - if (f->bits >= 31) return; - - f->data |= (bit << f->bits); - f->bits++; -} - -static void frame_clean(struct legic_frame * const f) { - f->data = 0; - f->bits = 0; -} - - - -} - -/* Generate Keystream */ -uint32_t get_key_stream(int skip, int count) { - - int i; - - // Use int to enlarge timer tc to 32bit - legic_prng_bc += prng_timer->TC_CV; - - // reset the prng timer. - - /* If skip == -1, forward prng time based */ - if(skip == -1) { - i = (legic_prng_bc + SIM_SHIFT)/SIM_DIVISOR; /* Calculate Cycles based on timer */ - i -= legic_prng_count(); /* substract cycles of finished frames */ - i -= count; /* substract current frame length, rewind to beginning */ - legic_prng_forward(i); - } else { - legic_prng_forward(skip); - } - - i = (count == 6) ? -1 : legic_read_count; - - /* Generate KeyStream */ - return legic_prng_get_bits(count); -} - - - -} - -/* Send a frame in reader mode, the FPGA must have been set up by - * LegicRfReader - */ -void frame_sendAsReader(uint32_t data, uint8_t bits){ - - uint32_t starttime = GET_TICKS, send = 0, mask = 1; - - // xor lsfr onto data. - send = data ^ legic_prng_get_bits(bits); - - for (; mask < BITMASK(bits); mask <<= 1) { - if (send & mask) - COIL_PULSE(RWD_TIME_1) - else - COIL_PULSE(RWD_TIME_0) - } - - // Final pause to mark the end of the frame - COIL_PULSE(0); - - // log - uint8_t cmdbytes[] = {bits, BYTEx(data,0), BYTEx(data,1), BYTEx(data,2), BYTEx(send,0), BYTEx(send,1), BYTEx(send,2)}; - LogTrace(cmdbytes, sizeof(cmdbytes), starttime, GET_TICKS, NULL, true); -} - -/* Receive a frame from the card in reader emulation mode, the FPGA and - * timer must have been set up by LegicRfReader and frame_sendAsReader. - * - * The LEGIC RF protocol from card to reader does not include explicit - * frame start/stop information or length information. The reader must - * know beforehand how many bits it wants to receive. (Notably: a card - * sending a stream of 0-bits is indistinguishable from no card present.) - * - * Receive methodology: There is a fancy correlator in hi_read_rx_xcorr, but - * I'm not smart enough to use it. Instead I have patched hi_read_tx to output - * the ADC signal with hysteresis on SSP_DIN. Bit-bang that signal and look - * for edges. Count the edges in each bit interval. If they are approximately - * 0 this was a 0-bit, if they are approximately equal to the number of edges - * expected for a 212kHz subcarrier, this was a 1-bit. For timing we use the - * timer that's still running from frame_sendAsReader in order to get a synchronization - * with the frame that we just sent. - * - * FIXME: Because we're relying on the hysteresis to just do the right thing - * the range is severely reduced (and you'll probably also need a good antenna). - * So this should be fixed some time in the future for a proper receiver. - */ -static void frame_receiveAsReader(struct legic_frame * const f, uint8_t bits) { - - if ( bits > 32 ) return; - - uint8_t i = bits, edges = 0; - uint32_t the_bit = 1, next_bit_at = 0, data = 0; - uint32_t old_level = 0; - volatile uint32_t level = 0; - - frame_clean(f); - - // calibrate the prng. - legic_prng_forward(2); - data = legic_prng_get_bits(bits); - - //FIXED time between sending frame and now listening frame. 330us - uint32_t starttime = GET_TICKS; - // its about 9+9 ticks delay from end-send to here. - WaitTicks( 477 ); - - next_bit_at = GET_TICKS + TAG_BIT_PERIOD; - - while ( i-- ){ - edges = 0; - while ( GET_TICKS < next_bit_at) { - - level = (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN); - - if (level != old_level) - ++edges; - - old_level = level; - } - - next_bit_at += TAG_BIT_PERIOD; - - // We expect 42 edges (ONE) - if ( edges > 20 ) - data ^= the_bit; - - the_bit <<= 1; - } - - // output - f->data = data; - f->bits = bits; - - // log - uint8_t cmdbytes[] = {bits, BYTEx(data, 0), BYTEx(data, 1)}; - LogTrace(cmdbytes, sizeof(cmdbytes), starttime, GET_TICKS, NULL, false); -} - -// Setup pm3 as a Legic Reader -static uint32_t setup_phase_reader(uint8_t iv) { - - // Switch on carrier and let the tag charge for 5ms - HIGH(GPIO_SSC_DOUT); - WaitUS(5000); - - ResetTicks(); - - legic_prng_init(0); - - // send IV handshake - frame_sendAsReader(iv, 7); - - // tag and reader has same IV. - legic_prng_init(iv); - - frame_receiveAsReader(¤t_frame, 6); - - // 292us (438t) - fixed delay before sending ack. - // minus log and stuff 100tick? - WaitTicks(338); - legic_prng_forward(3); - - // Send obsfuscated acknowledgment frame. - // 0x19 = 0x18 MIM22, 0x01 LSB READCMD - // 0x39 = 0x38 MIM256, MIM1024 0x01 LSB READCMD - switch ( current_frame.data ) { - case 0x0D: frame_sendAsReader(0x19, 6); break; - case 0x1D: - case 0x3D: frame_sendAsReader(0x39, 6); break; - default: break; - } - - legic_prng_forward(2); - return current_frame.data; -} - -void LegicCommonInit(bool clear_mem) { - - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); - SetAdcMuxFor(GPIO_MUXSEL_HIPKD); - - /* Bitbang the transmitter */ - SHORT_COIL; - AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_ODR = GPIO_SSC_DIN; - - // reserve a cardmem, meaning we can use the tracelog function in bigbuff easier. - cardmem = BigBuf_get_EM_addr(); - if ( clear_mem ) - memset(cardmem, 0x00, LEGIC_CARD_MEMSIZE); - - clear_trace(); - set_tracing(true); - crc_init(&legic_crc, 4, 0x19 >> 1, 0x5, 0); - - StartTicks(); -} - -// Switch off carrier, make sure tag is reset -static void switch_off_tag_rwd(void) { - SHORT_COIL; - WaitUS(20); - WDT_HIT(); -} - -// calculate crc4 for a legic READ command -static uint32_t legic4Crc(uint8_t cmd, uint16_t byte_index, uint8_t value, uint8_t cmd_sz) { - crc_clear(&legic_crc); - uint32_t temp = (value << cmd_sz) | (byte_index << 1) | cmd; - crc_update(&legic_crc, temp, cmd_sz + 8 ); - return crc_finish(&legic_crc); -} - -int legic_read_byte( uint16_t index, uint8_t cmd_sz) { - - uint8_t byte, crc, calcCrc = 0; - uint32_t cmd = (index << 1) | LEGIC_READ; - - // 90ticks = 60us (should be 100us but crc calc takes time.) - //WaitTicks(330); // 330ticks prng(4) - works - WaitTicks(240); // 240ticks prng(3) - works - - frame_sendAsReader(cmd, cmd_sz); - frame_receiveAsReader(¤t_frame, 12); - - // CRC check. - byte = BYTEx(current_frame.data, 0); - crc = BYTEx(current_frame.data, 1); - calcCrc = legic4Crc(LEGIC_READ, index, byte, cmd_sz); - - if( calcCrc != crc ) { - Dbprintf("!!! crc mismatch: %x != %x !!!", calcCrc, crc); - return -1; - } - - legic_prng_forward(3); - return byte; -} - -/* - * - assemble a write_cmd_frame with crc and send it - * - wait until the tag sends back an ACK ('1' bit unencrypted) - * - forward the prng based on the timing - */ -bool legic_write_byte(uint16_t index, uint8_t byte, uint8_t addr_sz) { - - bool isOK = false; - int8_t i = 40; - uint8_t edges = 0; - uint8_t cmd_sz = addr_sz+1+8+4; //crc+data+cmd; - uint32_t steps = 0, next_bit_at, start, crc, old_level = 0; - - crc = legic4Crc(LEGIC_WRITE, index, byte, addr_sz+1); - - // send write command - uint32_t cmd = LEGIC_WRITE; - cmd |= index << 1; // index - cmd |= byte << (addr_sz+1); // Data - cmd |= (crc & 0xF ) << (addr_sz+1+8); // CRC - - WaitTicks(240); - - frame_sendAsReader(cmd, cmd_sz); - - LINE_IN; - - start = GET_TICKS; - - // ACK, - one single "1" bit after 3.6ms - // 3.6ms = 3600us * 1.5 = 5400ticks. - WaitTicks(5400); - - next_bit_at = GET_TICKS + TAG_BIT_PERIOD; - - while ( i-- ) { - WDT_HIT(); - edges = 0; - while ( GET_TICKS < next_bit_at) { - - volatile uint32_t level = (AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN); - - if (level != old_level) - ++edges; - - old_level = level; - } - - next_bit_at += TAG_BIT_PERIOD; - - // We expect 42 edges (ONE) - if(edges > 20 ) { - steps = ( (GET_TICKS - start) / TAG_BIT_PERIOD); - legic_prng_forward(steps); - isOK = true; - goto OUT; - } + // wait for byte be become available in rx holding register + if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { + return AT91C_BASE_SSC->SSC_RHR; } - -OUT: ; - legic_prng_forward(1); - - uint8_t cmdbytes[] = {1, isOK, BYTEx(steps, 0), BYTEx(steps, 1) }; - LogTrace(cmdbytes, sizeof(cmdbytes), start, GET_TICKS, NULL, false); - return isOK; + } } -int LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv) { - - uint16_t i = 0; - uint8_t isOK = 1; - legic_card_select_t card; - - LegicCommonInit(true); - - if ( legic_select_card_iv(&card, iv) ) { - isOK = 0; - goto OUT; - } +static inline void tx_byte_to_fpga(uint8_t byte) { + for(;;) { + WDT_HIT(); - if (len + offset > card.cardsize) - len = card.cardsize - offset; + // put byte into tx holding register as soon as it is ready + if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { + AT91C_BASE_SSC->SSC_THR = byte; + return; + } + } +} - LED_B_ON(); - while (i < len) { - int r = legic_read_byte(offset + i, card.cmdsize); - - if (r == -1 || BUTTON_PRESS()) { - if ( MF_DBGLEVEL >= 2) DbpString("operation aborted"); - isOK = 0; - goto OUT; - } - cardmem[i++] = r; - WDT_HIT(); - } +//----------------------------------------------------------------------------- +// Demodulation +//----------------------------------------------------------------------------- -OUT: - WDT_HIT(); - switch_off_tag_rwd(); - LEDsoff(); - cmd_send(CMD_ACK, isOK, len, 0, cardmem, len); - return 0; +// Returns am aproximated power measurement +// +// The FPGA running on the xcorrelation kernel samples the subcarrier at ~3 MHz. +// The kernel was initialy designed to receive BSPK/2-PSK. Hance, it reports an +// I/Q pair every 18.9us (8 bits i and 8 bits q). +// +// The subcarrier amplitude can be calculated using Pythagoras sqrt(i^2 + q^2). +// To reduce CPU time the amplitude is approximated by using linear functions: +// am = MAX(ABS(i),ABS(q)) + 1/2*MIN(ABS(i),ABSq)) +// +// Note: The SSC receiver is never synchronized the calculation my be performed +// on a i/q pair from two subsequent correlations, but does not matter. +static inline int32_t sample_power() { + int32_t q = (int8_t)rx_byte_from_fpga(); q = ABS(q); + int32_t i = (int8_t)rx_byte_from_fpga(); i = ABS(i); + + return MAX(i, q) + (MIN(i, q) >> 1); +} + +// Returns a demedulated bit +// +// An aproximated power measurement is available every 18.9us. The bit time +// is 100us. The code samples 5 times and uses samples 3 and 4. +// +// Note: The demodulator is drifting (18.9us * 5 = 94.5us), since the longest +// respons is 12 bits, the demodulator will stay in sync with a margin of +// error of 20us left. Sending the next request will resync the card. +static inline bool rx_bit() { + static int32_t p[5]; + for(size_t i = 0; i<5; ++i) { + p[i] = sample_power(); + } + + if((p[2] > input_threshold) && (p[3] > input_threshold)) { + return true; + } + if((p[2] < input_threshold) && (p[3] < input_threshold)) { + return false; + } + + Dbprintf("rx_bit failed %i vs %i (threshold %i)", p[2], p[3], input_threshold); + return false; +} + +//----------------------------------------------------------------------------- +// Modulation +// +// Modulation is a little bit more tricky as demedulation ssp_clk is running at +// 105.4 kHz resulting in a ssc bit periode of 9.4us and ssc frame periode of +// 76us. Legic has a strange pause-puls modulation with a 60us 0-bit and 100us +// 1-bit periode. The following functions use bit stuffing to aproximate the +// modulation. The default state of all bits is 1 the. The code adds pauses: +// - A 1 is aproximated by b1100000000 = 0x0300 +// - A 0 is aproximated by b110000 = 0x0030 +// +// Note: The modulator expect to be run on a little-endian system and the frame +// length to not exeed 11 bits. The frame length is not checked. +//----------------------------------------------------------------------------- + +static void clean_frame(struct legic_frame *f) { + memset(f->data, 0xff, sizeof(f->data)); + + // add end of frame pause + f->data[0] ^= 0x03; + f->bits = 2; +} + +static void append_to_frame(struct legic_frame *f, uint8_t bit) { + uint8_t bit_pos = f->bits % 8; // calculate bits used in partially used byte + uint8_t byte_pos = f->bits / 8; // calculate next free or partially used byte + + static union frame_encoder frame_encoder; + + if(bit) { + frame_encoder.uint32 = 0x0300 << bit_pos; // appended bits at bit_pos + f->bits += 10; // store amount of bits appended + } else { + frame_encoder.uint32 = 0x0030 << bit_pos; // appended bits at bit_pos + f->bits += 6; // store amount of bits appended + } + + // Move data from encoder to frame. This d-tour is necessary bacause the uC + // does not support unaligned access. We use bitwise not and xor to flip bits. + for(uint8_t i = 0; i < sizeof(frame_encoder); ++i) { + f->data[i + byte_pos] ^= frame_encoder.uint8[i]; + } +} + +static uint8_t finalize_frame(struct legic_frame *f) { + // convert bits into full bytes + return (f->bits + 7) / 8; +} + +//----------------------------------------------------------------------------- +// Frame Handling +// +// The LEGIC RF protocol from card to reader does not include explicit frame +// start/stop information or length information. The reader must know beforehand +// how many bits it wants to receive. +// Notably: a card sending a stream of 0-bits is indistinguishable from no card +// present. +//----------------------------------------------------------------------------- + +static void tx_frame(uint32_t frame, uint8_t len) { + static struct legic_frame legic_frame; + clean_frame(&legic_frame); + + // add bit by bit to frame, MSB (last bit on air) first, this reverses the order + // reverse order keeps last pause aligned to byte boundry and in sync with ret + // of last tx_byte_to_fpga call. this in turn syncs our rx phase perfectly. + while(len > 0) { + uint8_t lsb = (frame >> --len) & 0x01; + append_to_frame(&legic_frame, lsb ^ legic_prng_get_bit()); + legic_prng_forward(1); + } + + // finalize frame, returns length in bytes + len = finalize_frame(&legic_frame); + + // start tx with first frame preloaded + tx_byte_to_fpga(legic_frame.data[--len]); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); + + // transmit frame, MSB first + while(len > 0) { + tx_byte_to_fpga(legic_frame.data[--len]); + } + + // tx queue has 2 cycles, add 2 empty frames to leave function in sync + tx_byte_to_fpga(0xff); // blocks until last frame is loaded into shift register + tx_byte_to_fpga(0xff); // blocks until last frame is done +} + +static uint32_t rx_frame(uint8_t len) { + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR + | FPGA_HF_READER_RX_XCORR_848_KHZ + | FPGA_HF_READER_RX_XCORR_QUARTER); + + uint32_t frame = 0; + for(uint8_t i = 0; i < len; i++) { + frame |= (rx_bit() ^ legic_prng_get_bit()) << i; + legic_prng_forward(1); + } + + return frame; +} + +//----------------------------------------------------------------------------- +// Legic Reader +//----------------------------------------------------------------------------- + +int init_card(uint8_t cardtype, legic_card_select_t *p_card) { + p_card->tagtype = cardtype; + + switch(p_card->tagtype) { + case 0x0d: + p_card->cmdsize = 6; + p_card->addrsize = 5; + p_card->cardsize = 22; + break; + case 0x1d: + p_card->cmdsize = 9; + p_card->addrsize = 8; + p_card->cardsize = 256; + break; + case 0x3d: + p_card->cmdsize = 11; + p_card->addrsize = 10; + p_card->cardsize = 1024; + break; + default: + p_card->cmdsize = 0; + p_card->addrsize = 0; + p_card->cardsize = 0; + return 2; + } + return 0; +} + +static void init_reader(bool clear_mem) { + // configure FPGA + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR + | FPGA_HF_READER_RX_XCORR_848_KHZ + | FPGA_HF_READER_RX_XCORR_QUARTER); + SetAdcMuxFor(GPIO_MUXSEL_HIPKD); + + // configure SSC with defaults (note: defaults are MSB first - Legic has LSB first, + // the rx stream is bit stuff in reverse to fix this. However, reversing the order + // will align the last pause to a byte boundry and we want that for synchronisation. + FpgaSetupSsc(); + + // and additonaly set Data Default to 1, to prevent glitches when switching to tx. + AT91C_BASE_SSC->SSC_TFMR |= AT91C_SSC_DATDEF; + + // reserve a cardmem, meaning we can use the tracelog function in bigbuff easier. + legic_mem = BigBuf_get_EM_addr(); + if(legic_mem) { + memset(legic_mem, 0x00, LEGIC_CARD_MEMSIZE); + } + + // start trace + clear_trace(); + set_tracing(true); + + // init crc calculator + crc_init(&legic_crc, 4, 0x19 >> 1, 0x05, 0); + + // start us timer + StartCountUS(); +} + +// Setup reader to card connection +// +// The setup consists of a three way handshake: +// - Transmit initialisation vector 7 bits +// - Receive card type 6 bits +// - Acknowledge frame 6 bits +static uint32_t setup_phase_reader(uint8_t iv) { + uint32_t ts = GetCountUS(); + + // Switch on carrier and let the card charge for 5ms. + // Use the time to calibrate the treshhold. + input_threshold = 8; // heuristically determined + do { + int32_t sample = sample_power(); + if(sample > input_threshold) { + input_threshold = sample; + } + } while(GetCountUS() < ts + 5000); + + // Set threshold to noise floor * 2 + input_threshold <<= 1; + + legic_prng_init(0); + tx_frame(iv, 7); + ts = GetCountUS(); + + // configure iv + legic_prng_init(iv); + legic_prng_forward(2); + + // wait until card is expect to respond + while(GetCountUS() < ts + TAG_FRAME_WAIT) { }; + + // receive card type + int32_t card_type = rx_frame(6); + + // send obsfuscated acknowledgment frame + switch (card_type) { + case 0x0D: + tx_frame(0x19, 6); // MIM22 | READCMD = 0x18 | 0x01 + break; + case 0x1D: + case 0x3D: + tx_frame(0x39, 6); // MIM256 | READCMD = 0x38 | 0x01 + break; + } + + return card_type; +} + +static uint8_t calc_crc4(uint16_t cmd, uint8_t cmd_sz, uint8_t value) { + crc_clear(&legic_crc); + crc_update(&legic_crc, (value << cmd_sz) | cmd, 8 + cmd_sz); + return crc_finish(&legic_crc); +} + +static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { + uint16_t cmd = (index << 1) | LEGIC_READ; + + // read one byte + tx_frame(cmd, cmd_sz); + uint32_t frame = rx_frame(12); + + // split frame into data and crc + uint8_t byte = BYTEx(frame, 0); + uint8_t crc = BYTEx(frame, 1); + + // check received against calculated crc + uint8_t calc_crc = calc_crc4(cmd, cmd_sz, byte); + if(calc_crc != crc) { + Dbprintf("!!! crc mismatch: %x != %x !!!", calc_crc, crc); + return -1; + } + + return byte; +} + +//----------------------------------------------------------------------------- +// Command Line Interface +// +// Only this functions are public / called from appmain.c +//----------------------------------------------------------------------------- +void LegicRfInfo(void) { + // configure ARM and FPGA + init_reader(false); + + // establish shared secret and detect card type + uint8_t card_type = setup_phase_reader(0x01); + if(init_card(card_type, &card) != 0) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + + // read UID + for(uint8_t i = 0; i < sizeof(card.uid); ++i) { + int16_t byte = read_byte(i, card.cmdsize); + if(byte == -1) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + card.uid[i] = byte & 0xFF; + } + + // read MCC and check against UID + int16_t mcc = read_byte(4, card.cmdsize); + int16_t calc_mcc = CRC8Legic(card.uid, 4);; + if(mcc != calc_mcc) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + + // OK + cmd_send(CMD_ACK, 1, 0, 0, (uint8_t*)&card, sizeof(legic_card_select_t)); + +OUT: + switch_off(); +} + +void LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv) { + // configure ARM and FPGA + init_reader(false); + + // establish shared secret and detect card type + uint8_t card_type = setup_phase_reader(iv); + if(init_card(card_type, &card) != 0) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + + // do not read beyond card memory + if(len + offset > card.cardsize) { + len = card.cardsize - offset; + } + + for(uint16_t i = 0; i < len; ++i) { + int16_t byte = read_byte(offset + i, card.cmdsize); + if(byte == -1) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + legic_mem[i] = byte; + } + + // OK + cmd_send(CMD_ACK, 1, len, 0, legic_mem, len); + +OUT: + switch_off(); } void LegicRfWriter(uint16_t offset, uint16_t len, uint8_t iv, uint8_t *data) { - - #define LOWERLIMIT 4 - uint8_t isOK = 1, msg = 0; - legic_card_select_t card; - - // uid NOT is writeable. - if ( offset <= LOWERLIMIT ) { - isOK = 0; - goto OUT; - } - - LegicCommonInit(false); - - if ( legic_select_card_iv(&card, iv) ) { - isOK = 0; - msg = 1; - goto OUT; - } - - if ( len + offset > card.cardsize) - len = card.cardsize - offset; - - LED_B_ON(); - while( len > 0 ) { - --len; - if ( !legic_write_byte( len + offset, data[len], card.addrsize) ) { - Dbprintf("operation failed | %02X | %02X | %02X", len + offset, len, data[len] ); - isOK = 0; - goto OUT; - } - WDT_HIT(); - } -OUT: - cmd_send(CMD_ACK, isOK, msg,0,0,0); - switch_off_tag_rwd(); - LEDsoff(); -} - -int legic_select_card_iv(legic_card_select_t *p_card, uint8_t iv){ - - if ( p_card == NULL ) return 1; - - p_card->tagtype = setup_phase_reader(iv); - - switch(p_card->tagtype) { - case 0x0d: - p_card->cmdsize = 6; - p_card->addrsize = 5; - p_card->cardsize = 22; - break; - case 0x1d: - p_card->cmdsize = 9; - p_card->addrsize = 8; - p_card->cardsize = 256; - break; - case 0x3d: - p_card->cmdsize = 11; - p_card->addrsize = 10; - p_card->cardsize = 1024; - break; - default: - p_card->cmdsize = 0; - p_card->addrsize = 0; - p_card->cardsize = 0; - return 2; - } - return 0; -} -int legic_select_card(legic_card_select_t *p_card){ - return legic_select_card_iv(p_card, 0x01); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -void LegicRfInfo(void){ - - int r; - - uint8_t buf[sizeof(legic_card_select_t)] = {0x00}; - legic_card_select_t *card = (legic_card_select_t*) buf; - - LegicCommonInit(false); - - if ( legic_select_card(card) ) { - cmd_send(CMD_ACK,0,0,0,0,0); - goto OUT; - } - - // read UID bytes - for ( uint8_t i = 0; i < sizeof(card->uid); ++i) { - r = legic_read_byte(i, card->cmdsize); - if ( r == -1 ) { - cmd_send(CMD_ACK,0,0,0,0,0); - goto OUT; - } - card->uid[i] = r & 0xFF; - } - - // MCC byte. - r = legic_read_byte(4, card->cmdsize); - uint32_t calc_mcc = CRC8Legic(card->uid, 4);; - if ( r != calc_mcc) { - cmd_send(CMD_ACK,0,0,0,0,0); - goto OUT; - } - - // OK - cmd_send(CMD_ACK, 1, 0, 0, buf, sizeof(legic_card_select_t)); - -OUT: - switch_off_tag_rwd(); - LEDsoff(); -} - -} - -} - + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); //TODO Implement } void LegicRfSimulate(int phase, int frame, int reqresp) { From 7244f5825dd88744a62318f3b32b1478f92c67b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sun, 29 Jul 2018 23:58:43 +0200 Subject: [PATCH 07/14] change: legic reader tx back to bigbang I've tried to modulate the Legic specific pause-puls using ssc and the default ssc clock of 105.4 kHz (bit periode of 9.4us) - previous commit. However, the timing was not precise enough. By increasing the ssc clock this could be circumvented, but the adventage over bitbang would be little. --- armsrc/legicrf.c | 117 ++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 88 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 7837322e0..e9f05a5a7 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -16,16 +16,6 @@ #include "legic_prng.h" /* legic PRNG impl */ #include "legic.h" /* legic_card_select_t struct */ -struct legic_frame { - uint32_t bits; /* length of frame */ - uint8_t data[24]; /* preprocessed bits */ -}; - -union frame_encoder { - uint32_t uint32; /* SAM7S512 does not support unaligned access as a */ - uint8_t uint8[4]; /* workaround 32bit values are converted to 4x8bit */ -}; - static uint8_t* legic_mem; /* card memory, used for read, write and sim */ static legic_card_select_t card;/* metadata of currently selected card */ static crc_t legic_crc; @@ -41,7 +31,7 @@ static int32_t input_threshold; /* values > threshold are 1 else 0 */ #define LEGIC_CARD_MEMSIZE 1024 /* The largest Legic Prime card is 1k */ //----------------------------------------------------------------------------- -// I/O interface abstraction (ARM <-> FPGA) +// I/O interface abstraction (FPGA -> ARM) //----------------------------------------------------------------------------- static inline uint8_t rx_byte_from_fpga() { @@ -55,18 +45,6 @@ static inline uint8_t rx_byte_from_fpga() { } } -static inline void tx_byte_to_fpga(uint8_t byte) { - for(;;) { - WDT_HIT(); - - // put byte into tx holding register as soon as it is ready - if(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = byte; - return; - } - } -} - //----------------------------------------------------------------------------- // Demodulation //----------------------------------------------------------------------------- @@ -118,50 +96,26 @@ static inline bool rx_bit() { //----------------------------------------------------------------------------- // Modulation // -// Modulation is a little bit more tricky as demedulation ssp_clk is running at -// 105.4 kHz resulting in a ssc bit periode of 9.4us and ssc frame periode of -// 76us. Legic has a strange pause-puls modulation with a 60us 0-bit and 100us -// 1-bit periode. The following functions use bit stuffing to aproximate the -// modulation. The default state of all bits is 1 the. The code adds pauses: -// - A 1 is aproximated by b1100000000 = 0x0300 -// - A 0 is aproximated by b110000 = 0x0030 -// -// Note: The modulator expect to be run on a little-endian system and the frame -// length to not exeed 11 bits. The frame length is not checked. +// I've tried to modulate the Legic specific pause-puls using ssc and the default +// ssc clock of 105.4 kHz (bit periode of 9.4us) - previous commit. However, +// the timing was not precise enough. By increasing the ssc clock this could +// be circumvented, but the adventage over bitbang would be little. //----------------------------------------------------------------------------- -static void clean_frame(struct legic_frame *f) { - memset(f->data, 0xff, sizeof(f->data)); +static inline void tx_bit(bool bit) { + uint32_t ts = GetCountUS(); - // add end of frame pause - f->data[0] ^= 0x03; - f->bits = 2; -} - -static void append_to_frame(struct legic_frame *f, uint8_t bit) { - uint8_t bit_pos = f->bits % 8; // calculate bits used in partially used byte - uint8_t byte_pos = f->bits / 8; // calculate next free or partially used byte - - static union frame_encoder frame_encoder; + // insert pause + LOW(GPIO_SSC_DOUT); + while(GetCountUS() < ts + RWD_TIME_PAUSE) { }; + HIGH(GPIO_SSC_DOUT); + // return to high, wait for bit periode to end if(bit) { - frame_encoder.uint32 = 0x0300 << bit_pos; // appended bits at bit_pos - f->bits += 10; // store amount of bits appended + while(GetCountUS() < ts + RWD_TIME_1) { }; } else { - frame_encoder.uint32 = 0x0030 << bit_pos; // appended bits at bit_pos - f->bits += 6; // store amount of bits appended + while(GetCountUS() < ts + RWD_TIME_0) { }; } - - // Move data from encoder to frame. This d-tour is necessary bacause the uC - // does not support unaligned access. We use bitwise not and xor to flip bits. - for(uint8_t i = 0; i < sizeof(frame_encoder); ++i) { - f->data[i + byte_pos] ^= frame_encoder.uint8[i]; - } -} - -static uint8_t finalize_frame(struct legic_frame *f) { - // convert bits into full bytes - return (f->bits + 7) / 8; } //----------------------------------------------------------------------------- @@ -175,33 +129,20 @@ static uint8_t finalize_frame(struct legic_frame *f) { //----------------------------------------------------------------------------- static void tx_frame(uint32_t frame, uint8_t len) { - static struct legic_frame legic_frame; - clean_frame(&legic_frame); - - // add bit by bit to frame, MSB (last bit on air) first, this reverses the order - // reverse order keeps last pause aligned to byte boundry and in sync with ret - // of last tx_byte_to_fpga call. this in turn syncs our rx phase perfectly. - while(len > 0) { - uint8_t lsb = (frame >> --len) & 0x01; - append_to_frame(&legic_frame, lsb ^ legic_prng_get_bit()); - legic_prng_forward(1); - } - - // finalize frame, returns length in bytes - len = finalize_frame(&legic_frame); - - // start tx with first frame preloaded - tx_byte_to_fpga(legic_frame.data[--len]); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); // transmit frame, MSB first - while(len > 0) { - tx_byte_to_fpga(legic_frame.data[--len]); - } + for(uint8_t i = 0; i < len; ++i) { + bool bit = (frame >> i) & 0x01; + tx_bit(bit ^ legic_prng_get_bit()); + legic_prng_forward(1); + }; - // tx queue has 2 cycles, add 2 empty frames to leave function in sync - tx_byte_to_fpga(0xff); // blocks until last frame is loaded into shift register - tx_byte_to_fpga(0xff); // blocks until last frame is done + // add pause to mark end of the frame + uint32_t ts = GetCountUS(); + LOW(GPIO_SSC_DOUT); + while(GetCountUS() < ts + RWD_TIME_PAUSE) { }; + HIGH(GPIO_SSC_DOUT); } static uint32_t rx_frame(uint8_t len) { @@ -258,13 +199,13 @@ static void init_reader(bool clear_mem) { | FPGA_HF_READER_RX_XCORR_QUARTER); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); - // configure SSC with defaults (note: defaults are MSB first - Legic has LSB first, - // the rx stream is bit stuff in reverse to fix this. However, reversing the order - // will align the last pause to a byte boundry and we want that for synchronisation. + // configure SSC with defaults FpgaSetupSsc(); - // and additonaly set Data Default to 1, to prevent glitches when switching to tx. - AT91C_BASE_SSC->SSC_TFMR |= AT91C_SSC_DATDEF; + // re-claim GPIO_SSC_DOUT as GPIO and enable output + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; + HIGH(GPIO_SSC_DOUT); // reserve a cardmem, meaning we can use the tracelog function in bigbuff easier. legic_mem = BigBuf_get_EM_addr(); From e0adc976e0c0b12c810fa85b80ca532f1215fb8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sat, 4 Aug 2018 23:22:57 +0200 Subject: [PATCH 08/14] change: added rx/tx coordination timestamp --- armsrc/legicrf.c | 67 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index e9f05a5a7..54e11ce46 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -21,10 +21,28 @@ static legic_card_select_t card;/* metadata of currently selected card */ static crc_t legic_crc; static int32_t input_threshold; /* values > threshold are 1 else 0 */ -// LEGIC RF is using the common timer functions: StartCountUS() and GetCountUS() +//----------------------------------------------------------------------------- +// Frame timing and pseudorandom number generator +// +// The Prng is forwarded every 100us (TAG_BIT_PERIOD), except when the reader is +// transmitting. In that case the prng has to be forwarded every bit transmitted: +// - 60us for a 0 (RWD_TIME_0) +// - 100us for a 1 (RWD_TIME_1) +// +// The data dependent timing makes writing comprehensible code significantly +// harder. The current aproach forwards the prng data based if there is data on +// air and time based, using GetCountUS(), during computational and wait periodes. +// +// To not have the necessity to calculate/guess exection time dependend timeouts +// tx_frame and rx_frame use a shared timestamp to coordinate tx and rx timeslots. +//----------------------------------------------------------------------------- + +static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ + #define RWD_TIME_PAUSE 20 /* 20us */ #define RWD_TIME_1 100 /* READER_TIME_PAUSE 20us off + 80us on = 100us */ #define RWD_TIME_0 60 /* READER_TIME_PAUSE 20us off + 40us on = 60us */ +#define RWD_FRAME_WAIT 220 /* 220us from TAG frame end to READER frame start */ #define TAG_FRAME_WAIT 330 /* 330us from READER frame end to TAG frame start */ #define TAG_BIT_PERIOD 100 /* 100us */ @@ -73,9 +91,8 @@ static inline int32_t sample_power() { // An aproximated power measurement is available every 18.9us. The bit time // is 100us. The code samples 5 times and uses samples 3 and 4. // -// Note: The demodulator is drifting (18.9us * 5 = 94.5us), since the longest -// respons is 12 bits, the demodulator will stay in sync with a margin of -// error of 20us left. Sending the next request will resync the card. +// Note: The demodulator would be drifting (18.9us * 5 != 100us), rx_frame +// has a delay loop that aligns rx_bit calls to the TAG tx timeslots. static inline bool rx_bit() { static int32_t p[5]; for(size_t i = 0; i<5; ++i) { @@ -103,19 +120,15 @@ static inline bool rx_bit() { //----------------------------------------------------------------------------- static inline void tx_bit(bool bit) { - uint32_t ts = GetCountUS(); - // insert pause LOW(GPIO_SSC_DOUT); - while(GetCountUS() < ts + RWD_TIME_PAUSE) { }; + last_frame_end += RWD_TIME_PAUSE; + while(GetCountUS() < last_frame_end) { }; HIGH(GPIO_SSC_DOUT); // return to high, wait for bit periode to end - if(bit) { - while(GetCountUS() < ts + RWD_TIME_1) { }; - } else { - while(GetCountUS() < ts + RWD_TIME_0) { }; - } + last_frame_end += (bit ? RWD_TIME_1 : RWD_TIME_0) - RWD_TIME_PAUSE; + while(GetCountUS() < last_frame_end) { }; } //----------------------------------------------------------------------------- @@ -131,6 +144,10 @@ static inline void tx_bit(bool bit) { static void tx_frame(uint32_t frame, uint8_t len) { FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); + // wait for next tx timeslot + last_frame_end += RWD_FRAME_WAIT; + while(GetCountUS() < last_frame_end) { }; + // transmit frame, MSB first for(uint8_t i = 0; i < len; ++i) { bool bit = (frame >> i) & 0x01; @@ -139,9 +156,9 @@ static void tx_frame(uint32_t frame, uint8_t len) { }; // add pause to mark end of the frame - uint32_t ts = GetCountUS(); LOW(GPIO_SSC_DOUT); - while(GetCountUS() < ts + RWD_TIME_PAUSE) { }; + last_frame_end += RWD_TIME_PAUSE; + while(GetCountUS() < last_frame_end) { }; HIGH(GPIO_SSC_DOUT); } @@ -150,10 +167,18 @@ static uint32_t rx_frame(uint8_t len) { | FPGA_HF_READER_RX_XCORR_848_KHZ | FPGA_HF_READER_RX_XCORR_QUARTER); + // hold sampling until card is expected to respond + last_frame_end += TAG_FRAME_WAIT; + while(GetCountUS() < last_frame_end) { }; + uint32_t frame = 0; for(uint8_t i = 0; i < len; i++) { frame |= (rx_bit() ^ legic_prng_get_bit()) << i; legic_prng_forward(1); + + // rx_bit runs only 95us, resync to TAG_BIT_PERIOD + last_frame_end += TAG_BIT_PERIOD; + while(GetCountUS() < last_frame_end) { }; } return frame; @@ -231,7 +256,8 @@ static void init_reader(bool clear_mem) { // - Receive card type 6 bits // - Acknowledge frame 6 bits static uint32_t setup_phase_reader(uint8_t iv) { - uint32_t ts = GetCountUS(); + // init coordination timestamp + last_frame_end = GetCountUS(); // Switch on carrier and let the card charge for 5ms. // Use the time to calibrate the treshhold. @@ -241,24 +267,21 @@ static uint32_t setup_phase_reader(uint8_t iv) { if(sample > input_threshold) { input_threshold = sample; } - } while(GetCountUS() < ts + 5000); + } while(GetCountUS() < last_frame_end + 5000); // Set threshold to noise floor * 2 input_threshold <<= 1; legic_prng_init(0); tx_frame(iv, 7); - ts = GetCountUS(); // configure iv legic_prng_init(iv); legic_prng_forward(2); - // wait until card is expect to respond - while(GetCountUS() < ts + TAG_FRAME_WAIT) { }; - // receive card type int32_t card_type = rx_frame(6); + legic_prng_forward(3); // send obsfuscated acknowledgment frame switch (card_type) { @@ -284,7 +307,9 @@ static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { uint16_t cmd = (index << 1) | LEGIC_READ; // read one byte + legic_prng_forward(2); tx_frame(cmd, cmd_sz); + legic_prng_forward(2); uint32_t frame = rx_frame(12); // split frame into data and crc @@ -298,6 +323,8 @@ static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { return -1; } + legic_prng_forward(1); + return byte; } From c06f0af7f3085e98623a3a6adc1e6aa9e5ff10d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Thu, 9 Aug 2018 09:32:14 +0200 Subject: [PATCH 09/14] change: switched from timestamps (us) to ticks GetCountUS() has a jitter of +/- 7us this is not precise enough to keep the PRNG in sync. 1.5 * GET_TICKS on the other hand is spot on. --- armsrc/legicrf.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 54e11ce46..a6edb6944 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -31,7 +31,7 @@ static int32_t input_threshold; /* values > threshold are 1 else 0 */ // // The data dependent timing makes writing comprehensible code significantly // harder. The current aproach forwards the prng data based if there is data on -// air and time based, using GetCountUS(), during computational and wait periodes. +// air and time based, using GET_TICKS, during computational and wait periodes. // // To not have the necessity to calculate/guess exection time dependend timeouts // tx_frame and rx_frame use a shared timestamp to coordinate tx and rx timeslots. @@ -39,12 +39,12 @@ static int32_t input_threshold; /* values > threshold are 1 else 0 */ static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ -#define RWD_TIME_PAUSE 20 /* 20us */ -#define RWD_TIME_1 100 /* READER_TIME_PAUSE 20us off + 80us on = 100us */ -#define RWD_TIME_0 60 /* READER_TIME_PAUSE 20us off + 40us on = 60us */ -#define RWD_FRAME_WAIT 220 /* 220us from TAG frame end to READER frame start */ -#define TAG_FRAME_WAIT 330 /* 330us from READER frame end to TAG frame start */ -#define TAG_BIT_PERIOD 100 /* 100us */ +#define RWD_TIME_PAUSE 30 /* 20us */ +#define RWD_TIME_1 150 /* READER_TIME_PAUSE 20us off + 80us on = 100us */ +#define RWD_TIME_0 90 /* READER_TIME_PAUSE 20us off + 40us on = 60us */ +#define RWD_FRAME_WAIT 330 /* 220us from TAG frame end to READER frame start */ +#define TAG_FRAME_WAIT 495 /* 330us from READER frame end to TAG frame start */ +#define TAG_BIT_PERIOD 150 /* 100us */ #define LEGIC_CARD_MEMSIZE 1024 /* The largest Legic Prime card is 1k */ @@ -123,12 +123,12 @@ static inline void tx_bit(bool bit) { // insert pause LOW(GPIO_SSC_DOUT); last_frame_end += RWD_TIME_PAUSE; - while(GetCountUS() < last_frame_end) { }; + while(GET_TICKS < last_frame_end) { }; HIGH(GPIO_SSC_DOUT); // return to high, wait for bit periode to end last_frame_end += (bit ? RWD_TIME_1 : RWD_TIME_0) - RWD_TIME_PAUSE; - while(GetCountUS() < last_frame_end) { }; + while(GET_TICKS < last_frame_end) { }; } //----------------------------------------------------------------------------- @@ -146,7 +146,7 @@ static void tx_frame(uint32_t frame, uint8_t len) { // wait for next tx timeslot last_frame_end += RWD_FRAME_WAIT; - while(GetCountUS() < last_frame_end) { }; + while(GET_TICKS < last_frame_end) { }; // transmit frame, MSB first for(uint8_t i = 0; i < len; ++i) { @@ -158,7 +158,7 @@ static void tx_frame(uint32_t frame, uint8_t len) { // add pause to mark end of the frame LOW(GPIO_SSC_DOUT); last_frame_end += RWD_TIME_PAUSE; - while(GetCountUS() < last_frame_end) { }; + while(GET_TICKS < last_frame_end) { }; HIGH(GPIO_SSC_DOUT); } @@ -169,7 +169,7 @@ static uint32_t rx_frame(uint8_t len) { // hold sampling until card is expected to respond last_frame_end += TAG_FRAME_WAIT; - while(GetCountUS() < last_frame_end) { }; + while(GET_TICKS < last_frame_end) { }; uint32_t frame = 0; for(uint8_t i = 0; i < len; i++) { @@ -178,7 +178,7 @@ static uint32_t rx_frame(uint8_t len) { // rx_bit runs only 95us, resync to TAG_BIT_PERIOD last_frame_end += TAG_BIT_PERIOD; - while(GetCountUS() < last_frame_end) { }; + while(GET_TICKS < last_frame_end) { }; } return frame; @@ -246,7 +246,7 @@ static void init_reader(bool clear_mem) { crc_init(&legic_crc, 4, 0x19 >> 1, 0x05, 0); // start us timer - StartCountUS(); + StartTicks(); } // Setup reader to card connection @@ -257,7 +257,7 @@ static void init_reader(bool clear_mem) { // - Acknowledge frame 6 bits static uint32_t setup_phase_reader(uint8_t iv) { // init coordination timestamp - last_frame_end = GetCountUS(); + last_frame_end = GET_TICKS; // Switch on carrier and let the card charge for 5ms. // Use the time to calibrate the treshhold. @@ -267,7 +267,7 @@ static uint32_t setup_phase_reader(uint8_t iv) { if(sample > input_threshold) { input_threshold = sample; } - } while(GetCountUS() < last_frame_end + 5000); + } while(GET_TICKS < last_frame_end + 7500); // Set threshold to noise floor * 2 input_threshold <<= 1; @@ -367,6 +367,7 @@ void LegicRfInfo(void) { OUT: switch_off(); + StopTicks(); } void LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv) { @@ -399,6 +400,7 @@ void LegicRfReader(uint16_t offset, uint16_t len, uint8_t iv) { OUT: switch_off(); + StopTicks(); } void LegicRfWriter(uint16_t offset, uint16_t len, uint8_t iv, uint8_t *data) { From 9d330dde87975fcc2682b3f4011e6bbeadffe590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Sat, 11 Aug 2018 21:53:29 +0200 Subject: [PATCH 10/14] fix: 32bit tick timer based on TC0 and TC1 TC1 counts the number of TC0 overflows (carry bits). In random conditions TC1 would return or stay at zero, instead of counting up. This due to the behavior of the reset signal. SAM7S Series Datasheet, 33.5.6 Trigger: Regardless of the trigger used, it will be taken into account at the following active edge of the selected clock. This means that the counter value can be read differently from zero just after a trigger, especially when a low frequency signal is selected as the clock. The new code first prepares TC1 and asserts TC1 trigger and then prepares TC0 and asserts TC0 trigger. The TC0 start-up will reset TC1. --- armsrc/ticks.c | 81 ++++++++++++++++++++++++++++---------------------- armsrc/ticks.h | 16 +++++----- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/armsrc/ticks.c b/armsrc/ticks.c index f084e3364..0a1796896 100644 --- a/armsrc/ticks.c +++ b/armsrc/ticks.c @@ -169,59 +169,70 @@ uint32_t RAMFUNC GetCountSspClk(void) { } // ------------------------------------------------------------------------- -// Timer for bitbanging, or LF stuff when you need a very precis timer +// Timer for bitbanging, or LF stuff when you need a very precis timer // 1us = 1.5ticks // ------------------------------------------------------------------------- void StartTicks(void){ - //initialization of the timer - // tc1 is higher 0xFFFF0000 - // tc0 is lower 0x0000FFFF + // initialization of the timer AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0) | (1 << AT91C_ID_TC1); - AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_NONE | AT91C_TCB_TC1XC1S_TIOA0 | AT91C_TCB_TC2XC2S_NONE; - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; - AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK | // MCK(48MHz) / 32 - AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_ACPA_CLEAR | - AT91C_TC_ACPC_SET | AT91C_TC_ASWTRG_SET; - AT91C_BASE_TC0->TC_RA = 1; - AT91C_BASE_TC0->TC_RC = 0; + AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_NONE | AT91C_TCB_TC1XC1S_TIOA0 | AT91C_TCB_TC2XC2S_NONE; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // timer disable - AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_XC1; // from TC0 - - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - AT91C_BASE_TCB->TCB_BCR = 1; - - // wait until timer becomes zero. - while (AT91C_BASE_TC1->TC_CV > 0); + // disable TC0 and TC1 for re-configuration + AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + + // first configure TC1 (higher, 0xFFFF0000) 16 bit counter + AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_XC1; // just connect to TIOA0 from TC0 + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; // re-enable timer and wait for TC0 + + // second configure TC0 (lower, 0x0000FFFF) 16 bit counter + AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK | // MCK(48MHz) / 32 + AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | + AT91C_TC_ACPA_CLEAR | // RA comperator clears TIOA (carry bit) + AT91C_TC_ACPC_SET | // RC comperator sets TIOA (carry bit) + AT91C_TC_ASWTRG_SET; // SWTriger sets TIOA (carry bit) + AT91C_BASE_TC0->TC_RC = 0; // set TIOA (carry bit) on overflow, return to zero + AT91C_BASE_TC0->TC_RA = 1; // clear carry bit on next clock cycle + AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; // reset and re-enable timer + + // synchronized startup procedure + while (AT91C_BASE_TC0->TC_CV > 0); // wait until TC0 returned to zero + while (AT91C_BASE_TC0->TC_CV < 2); // and has started (TC_CV > TC_RA, now TC1 is cleared) + + // return to zero + AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG; + AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG; + while (AT91C_BASE_TC0->TC_CV > 0); } + +uint32_t GetTicks(void) { + uint32_t hi, lo; + + do { + hi = AT91C_BASE_TC1->TC_CV; + lo = AT91C_BASE_TC0->TC_CV; + } while(hi != AT91C_BASE_TC1->TC_CV); + + return (hi << 16) | lo; +} + // Wait - Spindelay in ticks. // if called with a high number, this will trigger the WDT... void WaitTicks(uint32_t ticks){ if ( ticks == 0 ) return; - ticks += GET_TICKS; - while (GET_TICKS < ticks); + ticks += GetTicks(); + while (GetTicks() < ticks); } + // Wait / Spindelay in us (microseconds) // 1us = 1.5ticks. void WaitUS(uint16_t us){ - if ( us == 0 ) return; - WaitTicks( (uint32_t)us * 3/2 ); + WaitTicks( (uint32_t)us * 3/2 ); } void WaitMS(uint16_t ms){ - if (ms == 0) return; WaitTicks( (uint32_t)ms * 1500 ); } -// Starts Clock and waits until its reset -void ResetTicks(void){ - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - while (AT91C_BASE_TC1->TC_CV > 0); -} -void ResetTimer(AT91PS_TC timer){ - timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - while(timer->TC_CV > 0) ; -} + // stop clock void StopTicks(void){ AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; diff --git a/armsrc/ticks.h b/armsrc/ticks.h index 8ad4bc679..727a40d2f 100644 --- a/armsrc/ticks.h +++ b/armsrc/ticks.h @@ -19,7 +19,7 @@ #include "proxmark3.h" #ifndef GET_TICKS -# define GET_TICKS (uint32_t)((AT91C_BASE_TC1->TC_CV << 16) | AT91C_BASE_TC0->TC_CV) +#define GET_TICKS GetTicks() #endif void SpinDelay(int ms); @@ -32,17 +32,15 @@ void StartCountUS(void); uint32_t RAMFUNC GetCountUS(void); void ResetUSClock(void); void SpinDelayCountUs(uint32_t us); -//uint32_t RAMFUNC GetDeltaCountUS(void); void StartCountSspClk(); void ResetSspClk(void); uint32_t RAMFUNC GetCountSspClk(); -extern void StartTicks(void); -extern void WaitTicks(uint32_t ticks); -extern void WaitUS(uint16_t us); -extern void WaitMS(uint16_t ms); -extern void ResetTicks(); -extern void ResetTimer(AT91PS_TC timer); -extern void StopTicks(void); +void StartTicks(void); +uint32_t GetTicks(void); +void WaitTicks(uint32_t ticks); +void WaitUS(uint16_t us); +void WaitMS(uint16_t ms); +void StopTicks(void); #endif \ No newline at end of file From e052fbc433ff1c228b1fe1dca732fe2fa752469b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Thu, 9 Aug 2018 09:33:32 +0200 Subject: [PATCH 11/14] change: re-added legic write support --- armsrc/legicrf.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index a6edb6944..19ea87bd2 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -45,8 +45,10 @@ static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define RWD_FRAME_WAIT 330 /* 220us from TAG frame end to READER frame start */ #define TAG_FRAME_WAIT 495 /* 330us from READER frame end to TAG frame start */ #define TAG_BIT_PERIOD 150 /* 100us */ +#define TAG_WRITE_TIMEOUT 60 /* 40 * 100us (write should take at most 3.6ms) */ #define LEGIC_CARD_MEMSIZE 1024 /* The largest Legic Prime card is 1k */ +#define WRITE_LOWERLIMIT 4 /* UID and MCC are not writable */ //----------------------------------------------------------------------------- // I/O interface abstraction (FPGA -> ARM) @@ -184,6 +186,35 @@ static uint32_t rx_frame(uint8_t len) { return frame; } +static bool rx_ack() { + // change fpga into rx mode + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR + | FPGA_HF_READER_RX_XCORR_848_KHZ + | FPGA_HF_READER_RX_XCORR_QUARTER); + + // hold sampling until card is expected to respond + last_frame_end += TAG_FRAME_WAIT; + while(GET_TICKS < last_frame_end) { }; + + uint32_t ack = 0; + for(uint8_t i = 0; i < TAG_WRITE_TIMEOUT; ++i) { + // sample bit + ack = rx_bit(); + legic_prng_forward(1); + + // rx_bit runs only 95us, resync to TAG_BIT_PERIOD + last_frame_end += TAG_BIT_PERIOD; + while(GET_TICKS < last_frame_end) { }; + + // check if it was an ACK + if(ack) { + break; + } + } + + return ack; +} + //----------------------------------------------------------------------------- // Legic Reader //----------------------------------------------------------------------------- @@ -328,6 +359,23 @@ static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { return byte; } +// Transmit write command, wait until (3.6ms) the tag sends back an unencrypted +// ACK ('1' bit) and forward the prng time based. +bool write_byte(uint16_t index, uint8_t byte, uint8_t addr_sz) { + uint32_t cmd = index << 1 | LEGIC_WRITE; // prepare command + uint8_t crc = calc_crc4(cmd, addr_sz + 1, byte); // calculate crc + cmd |= byte << (addr_sz + 1); // append value + cmd |= (crc & 0xF) << (addr_sz + 1 + 8); // and crc + + // send write command + legic_prng_forward(2); + tx_frame(cmd, addr_sz + 1 + 8 + 4); // cmd_sz = addr_sz + cmd + data + crc + legic_prng_forward(3); + + // wait for ack + return rx_ack(); +} + //----------------------------------------------------------------------------- // Command Line Interface // @@ -404,7 +452,42 @@ OUT: } void LegicRfWriter(uint16_t offset, uint16_t len, uint8_t iv, uint8_t *data) { - cmd_send(CMD_ACK, 0, 0, 0, 0, 0); //TODO Implement + // configure ARM and FPGA + init_reader(false); + + // uid is not writeable + if(offset <= WRITE_LOWERLIMIT) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + + // establish shared secret and detect card type + uint8_t card_type = setup_phase_reader(iv); + if(init_card(card_type, &card) != 0) { + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + + // do not write beyond card memory + if(len + offset > card.cardsize) { + len = card.cardsize - offset; + } + + // write in reverse order, only then is DCF (decremental field) writable + while(len-- > 0 && !BUTTON_PRESS()) { + if(!write_byte(len + offset, data[len], card.addrsize)) { + Dbprintf("operation failed | %02X | %02X | %02X", len + offset, len, data[len]); + cmd_send(CMD_ACK, 0, 0, 0, 0, 0); + goto OUT; + } + } + + // OK + cmd_send(CMD_ACK, 1, len, 0, legic_mem, len); + +OUT: + switch_off(); + StopTicks(); } void LegicRfSimulate(int phase, int frame, int reqresp) { From ff5b04690351ba04f3a15a0b725fbc41bc638ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Wed, 8 Aug 2018 00:11:14 +0200 Subject: [PATCH 12/14] change: re-added status LEDs - LED_A: FPGA and 13.56MHz carrier is active - LED_B: Reading Byte - LED_C: Writing Byte --- armsrc/legicrf.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 19ea87bd2..f2e80eee2 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -97,6 +97,7 @@ static inline int32_t sample_power() { // has a delay loop that aligns rx_bit calls to the TAG tx timeslots. static inline bool rx_bit() { static int32_t p[5]; + for(size_t i = 0; i<5; ++i) { p[i] = sample_power(); } @@ -254,6 +255,7 @@ static void init_reader(bool clear_mem) { | FPGA_HF_READER_RX_XCORR_848_KHZ | FPGA_HF_READER_RX_XCORR_QUARTER); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); + LED_A_ON(); // configure SSC with defaults FpgaSetupSsc(); @@ -338,10 +340,12 @@ static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { uint16_t cmd = (index << 1) | LEGIC_READ; // read one byte + LED_B_ON(); legic_prng_forward(2); tx_frame(cmd, cmd_sz); legic_prng_forward(2); uint32_t frame = rx_frame(12); + LED_B_OFF(); // split frame into data and crc uint8_t byte = BYTEx(frame, 0); @@ -368,9 +372,11 @@ bool write_byte(uint16_t index, uint8_t byte, uint8_t addr_sz) { cmd |= (crc & 0xF) << (addr_sz + 1 + 8); // and crc // send write command + LED_C_ON(); legic_prng_forward(2); tx_frame(cmd, addr_sz + 1 + 8 + 4); // cmd_sz = addr_sz + cmd + data + crc legic_prng_forward(3); + LED_C_OFF(); // wait for ack return rx_ack(); From 0d0b651246b0fc68b8d33f6800a960de5547cdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Wed, 8 Aug 2018 15:40:49 +0200 Subject: [PATCH 13/14] change: re-added trace log --- armsrc/legicrf.c | 25 ++++++++++++++++++++++++- client/cmdtrace.c | 4 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index f2e80eee2..209c757b3 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -151,6 +151,9 @@ static void tx_frame(uint32_t frame, uint8_t len) { last_frame_end += RWD_FRAME_WAIT; while(GET_TICKS < last_frame_end) { }; + // backup ts for trace log + uint32_t last_frame_start = last_frame_end; + // transmit frame, MSB first for(uint8_t i = 0; i < len; ++i) { bool bit = (frame >> i) & 0x01; @@ -163,6 +166,10 @@ static void tx_frame(uint32_t frame, uint8_t len) { last_frame_end += RWD_TIME_PAUSE; while(GET_TICKS < last_frame_end) { }; HIGH(GPIO_SSC_DOUT); + + // log + uint8_t cmdbytes[] = {len, BYTEx(frame, 0), BYTEx(frame, 1), BYTEx(frame, 2)}; + LogTrace(cmdbytes, sizeof(cmdbytes), last_frame_start, last_frame_end, NULL, true); } static uint32_t rx_frame(uint8_t len) { @@ -174,6 +181,9 @@ static uint32_t rx_frame(uint8_t len) { last_frame_end += TAG_FRAME_WAIT; while(GET_TICKS < last_frame_end) { }; + // backup ts for trace log + uint32_t last_frame_start = last_frame_end; + uint32_t frame = 0; for(uint8_t i = 0; i < len; i++) { frame |= (rx_bit() ^ legic_prng_get_bit()) << i; @@ -184,6 +194,10 @@ static uint32_t rx_frame(uint8_t len) { while(GET_TICKS < last_frame_end) { }; } + // log + uint8_t cmdbytes[] = {len, BYTEx(frame, 0), BYTEx(frame, 1)}; + LogTrace(cmdbytes, sizeof(cmdbytes), last_frame_start, last_frame_end, NULL, false); + return frame; } @@ -197,6 +211,9 @@ static bool rx_ack() { last_frame_end += TAG_FRAME_WAIT; while(GET_TICKS < last_frame_end) { }; + // backup ts for trace log + uint32_t last_frame_start = last_frame_end; + uint32_t ack = 0; for(uint8_t i = 0; i < TAG_WRITE_TIMEOUT; ++i) { // sample bit @@ -213,6 +230,10 @@ static bool rx_ack() { } } + // log + uint8_t cmdbytes[] = {1, BYTEx(ack, 0)}; + LogTrace(cmdbytes, sizeof(cmdbytes), last_frame_start, last_frame_end, NULL, false); + return ack; } @@ -293,6 +314,8 @@ static uint32_t setup_phase_reader(uint8_t iv) { last_frame_end = GET_TICKS; // Switch on carrier and let the card charge for 5ms. + last_frame_end += 7500; + // Use the time to calibrate the treshhold. input_threshold = 8; // heuristically determined do { @@ -300,7 +323,7 @@ static uint32_t setup_phase_reader(uint8_t iv) { if(sample > input_threshold) { input_threshold = sample; } - } while(GET_TICKS < last_frame_end + 7500); + } while(GET_TICKS < last_frame_end); // Set threshold to noise floor * 2 input_threshold <<= 1; diff --git a/client/cmdtrace.c b/client/cmdtrace.c index 8e5a439c9..f0b27bc0f 100644 --- a/client/cmdtrace.c +++ b/client/cmdtrace.c @@ -518,9 +518,9 @@ int CmdTraceList(const char *Cmd) { if ( protocol == ISO_14443A || protocol == PROTO_MIFARE) PrintAndLogEx(NORMAL, "iso14443a - All times are in carrier periods (1/13.56Mhz)"); if ( protocol == ICLASS ) - PrintAndLogEx(NORMAL, "iClass - Timings are not as accurate"); + PrintAndLogEx(NORMAL, "iClass - Timings are not as accurate"); if ( protocol == LEGIC ) - PrintAndLogEx(NORMAL, "LEGIC - Timings are in ticks (1us == 1.5ticks)"); + PrintAndLogEx(NORMAL, "LEGIC - Timings are in ticks (1us == 1.5ticks)"); if ( protocol == ISO_15693 ) PrintAndLogEx(NORMAL, "ISO15693 - Timings are not as accurate"); if ( protocol == FELICA ) From 9ba20b590ae09a812834391fbea7f2a993350c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Dr=C3=B6scher?= Date: Thu, 9 Aug 2018 17:01:28 +0200 Subject: [PATCH 14/14] change: reduced demodulator to bare minimum The initial code added complexity without improving reading distance. Thankfully the peak detection signal path has a low noise floor. --- armsrc/legicrf.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 209c757b3..3a45c4c67 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -19,7 +19,6 @@ static uint8_t* legic_mem; /* card memory, used for read, write and sim */ static legic_card_select_t card;/* metadata of currently selected card */ static crc_t legic_crc; -static int32_t input_threshold; /* values > threshold are 1 else 0 */ //----------------------------------------------------------------------------- // Frame timing and pseudorandom number generator @@ -50,6 +49,9 @@ static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define LEGIC_CARD_MEMSIZE 1024 /* The largest Legic Prime card is 1k */ #define WRITE_LOWERLIMIT 4 /* UID and MCC are not writable */ +#define INPUT_THRESHOLD 8 /* heuristically determined, lower values */ + /* lead to detecting false ack during write */ + //----------------------------------------------------------------------------- // I/O interface abstraction (FPGA -> ARM) //----------------------------------------------------------------------------- @@ -91,26 +93,18 @@ static inline int32_t sample_power() { // Returns a demedulated bit // // An aproximated power measurement is available every 18.9us. The bit time -// is 100us. The code samples 5 times and uses samples 3 and 4. +// is 100us. The code samples 5 times and uses the last (most stable) sample. // // Note: The demodulator would be drifting (18.9us * 5 != 100us), rx_frame // has a delay loop that aligns rx_bit calls to the TAG tx timeslots. static inline bool rx_bit() { - static int32_t p[5]; + int32_t power; for(size_t i = 0; i<5; ++i) { - p[i] = sample_power(); + power = sample_power(); } - if((p[2] > input_threshold) && (p[3] > input_threshold)) { - return true; - } - if((p[2] < input_threshold) && (p[3] < input_threshold)) { - return false; - } - - Dbprintf("rx_bit failed %i vs %i (threshold %i)", p[2], p[3], input_threshold); - return false; + return (power > INPUT_THRESHOLD); } //----------------------------------------------------------------------------- @@ -315,18 +309,7 @@ static uint32_t setup_phase_reader(uint8_t iv) { // Switch on carrier and let the card charge for 5ms. last_frame_end += 7500; - - // Use the time to calibrate the treshhold. - input_threshold = 8; // heuristically determined - do { - int32_t sample = sample_power(); - if(sample > input_threshold) { - input_threshold = sample; - } - } while(GET_TICKS < last_frame_end); - - // Set threshold to noise floor * 2 - input_threshold <<= 1; + while(GET_TICKS < last_frame_end) { }; legic_prng_init(0); tx_frame(iv, 7);