From 37867fbf3b68221db3fa2ee3e06e34b1b9fa9257 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 1/8] 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 33eb2f5fa08358ced56322b90236fa02b02baa55 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 2/8] 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 1adff322b1a467cd14fcfd2b60b398f16e83b76e 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 3/8] 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 7517c894d29e3bbabb6832a29a3e160bd6787250 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 4/8] 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/hi_read_tx.v | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) 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 d7c57dbc08e7d4d256d3bbd337e44a04cd451c1d 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 5/8] 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 78d5188922c6a9bf0e6d22e54308c7ff0932d7aa 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 6/8] 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 +++++++++++++++++++++-------------------------- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes 2 files 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) { diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index f86820525980ac5282b8c35eb476d489ad7d7c98..549ae0297b0391dbccebc28e88fa7353bf0d7651 100644 GIT binary patch literal 42175 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 literal 42175 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 z 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 NKcA7(vQ&`Ce*xCU&(i<^ From 8f797d1388b439f5216baa03fbf80e192eb05f7d 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 7/8] 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 058426fa17eacaceb82b04be667785c297329243 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 8/8] change: added rx/tx coordination timestamp --- armsrc/legicrf.c | 57 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index e9f05a5a7..c04f742e0 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 int32_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) { @@ -131,6 +148,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; @@ -143,6 +164,9 @@ static void tx_frame(uint32_t frame, uint8_t len) { LOW(GPIO_SSC_DOUT); while(GetCountUS() < ts + RWD_TIME_PAUSE) { }; HIGH(GPIO_SSC_DOUT); + + // update coordination timestamp + last_frame_end = GetCountUS(); } static uint32_t rx_frame(uint8_t len) { @@ -150,12 +174,23 @@ 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 expect 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) { }; } + // update coordination timestamp + last_frame_end = GetCountUS(); + return frame; } @@ -231,7 +266,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 +277,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 +317,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 +333,8 @@ static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { return -1; } + legic_prng_forward(1); + return byte; }