proxmark3/armsrc/lfsampling.c
2024-08-27 23:45:42 +08:00

808 lines
24 KiB
C

//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Miscellaneous routines for low frequency sampling.
//-----------------------------------------------------------------------------
#include "lfsampling.h"
#include "proxmark3_arm.h"
#include "BigBuf.h"
#include "fpgaloader.h"
#include "ticks.h"
#include "dbprint.h"
#include "util.h"
#include "lfdemod.h"
#include "string.h" // memset
#include "appmain.h" // print stack
#include "usb_cdc.h" // real-time sampling
/*
Default LF config is set to:
decimation = 1 (we keep 1 out of 1 samples)
bits_per_sample = 8
averaging = YES
divisor = 95 (125kHz)
trigger_threshold = 0
samples_to_skip = 0
verbose = YES
*/
static const sample_config def_config = {
.decimation = 1,
.bits_per_sample = 8,
.averaging = 1,
.divisor = LF_DIVISOR_125,
.trigger_threshold = 0,
.samples_to_skip = 0,
.verbose = false,
};
static sample_config config = { 1, 8, 1, LF_DIVISOR_125, 0, 0, true} ;
// Holds bit packed struct of samples.
static BitstreamOut_t data = {0, 0, 0};
// internal struct to keep track of samples gathered
static sampling_t samples = {0, 0, 0, 0};
void printLFConfig(void) {
uint32_t d = config.divisor;
DbpString(_CYAN_("LF Sampling config"));
Dbprintf(" [q] divisor............. %d ( "_GREEN_("%d.%02d kHz")" )", d, 12000 / (d + 1), ((1200000 + (d + 1) / 2) / (d + 1)) - ((12000 / (d + 1)) * 100));
Dbprintf(" [b] bits per sample..... %d", config.bits_per_sample);
Dbprintf(" [d] decimation.......... %d", config.decimation);
Dbprintf(" [a] averaging........... %s", (config.averaging) ? "yes" : "no");
Dbprintf(" [t] trigger threshold... %d", config.trigger_threshold);
Dbprintf(" [s] samples to skip..... %d ", config.samples_to_skip);
DbpString("");
}
void printSamples(void) {
DbpString(_CYAN_("LF Sampling memory usage"));
// Dbprintf(" decimation counter...%d", samples.dec_counter);
// Dbprintf(" sum..................%u", samples.sum);
Dbprintf(" counter.............. " _YELLOW_("%u"), samples.counter);
Dbprintf(" total saved.......... " _YELLOW_("%u"), samples.total_saved);
print_stack_usage();
}
void setDefaultSamplingConfig(void) {
setSamplingConfig(&def_config);
}
/**
* Called from the USB-handler to set the sampling configuration
* The sampling config is used for standard reading and sniffing.
*
* Other functions may read samples and ignore the sampling config,
* such as functions to read the UID from a prox tag or similar.
*
* Values set to '-1' implies no change
* @brief setSamplingConfig
* @param sc
*/
void setSamplingConfig(const sample_config *sc) {
// decimation (1-8) how many bits of adc sample value to save
if (sc->decimation > 0 && sc->decimation < 9)
config.decimation = sc->decimation;
// bits per sample (1-8)
if (sc->bits_per_sample > 0 && sc->bits_per_sample < 9)
config.bits_per_sample = sc->bits_per_sample;
//
if (sc->averaging > -1)
config.averaging = (sc->averaging > 0) ? 1 : 0;
// Frequency divisor (19 - 255)
if (sc->divisor > 18 && sc->divisor < 256)
config.divisor = sc->divisor;
// Start saving samples when adc value larger than trigger_threshold
if (sc->trigger_threshold > -1)
config.trigger_threshold = sc->trigger_threshold;
// Skip n adc samples before saving
if (sc->samples_to_skip > -1)
config.samples_to_skip = sc->samples_to_skip;
if (sc->verbose)
printLFConfig();
}
sample_config *getSamplingConfig(void) {
return &config;
}
void initSampleBuffer(uint32_t *sample_size) {
initSampleBufferEx(sample_size, false);
}
void initSampleBufferEx(uint32_t *sample_size, bool use_malloc) {
if (sample_size == NULL) {
return;
}
BigBuf_free_keep_EM();
// We can't erase the buffer now, it would drastically delay the acquisition
if (use_malloc) {
if (*sample_size == 0) {
*sample_size = BigBuf_max_traceLen();
data.buffer = BigBuf_get_addr();
} else {
*sample_size = MIN(*sample_size, BigBuf_max_traceLen());
data.buffer = BigBuf_malloc(*sample_size);
}
} else {
if (*sample_size == 0) {
*sample_size = BigBuf_max_traceLen();
} else {
*sample_size = MIN(*sample_size, BigBuf_max_traceLen());
}
data.buffer = BigBuf_get_addr();
}
// reset data stream
data.numbits = 0;
data.position = 0;
// reset samples
samples.dec_counter = 0;
samples.sum = 0;
samples.counter = *sample_size;
samples.total_saved = 0;
}
uint32_t getSampleCounter(void) {
return samples.total_saved;
}
void logSampleSimple(uint8_t sample) {
logSample(sample, config.decimation, config.bits_per_sample, config.averaging);
}
void logSample(uint8_t sample, uint8_t decimation, uint8_t bits_per_sample, bool avg) {
if (!data.buffer) {
return;
}
// keep track of total gather samples regardless how many was discarded.
if (samples.counter-- == 0) {
return;
}
if (bits_per_sample == 0) {
bits_per_sample = 1;
}
if (bits_per_sample > 8) {
bits_per_sample = 8;
}
if (decimation == 0) {
decimation = 1;
}
if (avg) {
samples.sum += sample;
}
// check decimation
if (decimation > 1) {
samples.dec_counter++;
if (samples.dec_counter < decimation) {
return;
}
samples.dec_counter = 0;
}
// averaging
if (avg && decimation > 1) {
sample = samples.sum / decimation;
samples.sum = 0;
}
// store the sample
samples.total_saved++;
if (bits_per_sample == 8) {
data.buffer[samples.total_saved - 1] = sample;
// add number of bits.
data.numbits = samples.total_saved << 3;
} else {
// truncate trailing data
sample >>= 8 - bits_per_sample;
sample <<= 8 - bits_per_sample;
uint8_t bits_offset = data.numbits & 0x7;
uint8_t bits_cap = 8 - bits_offset;
// write the current byte
data.buffer[data.numbits >> 3] |= sample >> bits_offset;
uint32_t numbits = data.numbits + bits_cap;
// write the remaining bits to the next byte
data.buffer[numbits >> 3] |= sample << (bits_cap);
data.numbits += bits_per_sample;
}
}
/**
* Setup the FPGA to listen for samples. This method downloads the FPGA bitstream
* if not already loaded, sets divisor and starts up the antenna.
* @param divisor : 1, 88> 255 or negative ==> 134.8 kHz
* 0 or 95 ==> 125 kHz
*
**/
void LFSetupFPGAForADC(int divisor, bool reader_field) {
FpgaDownloadAndGo(FPGA_BITSTREAM_LF);
if ((divisor == 1) || (divisor < 0) || (divisor > 255)) {
FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_134); //~134kHz
} else if (divisor == 0) {
FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz
} else {
FpgaSendCommand(FPGA_CMD_SET_DIVISOR, divisor);
}
FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_READER | (reader_field ? FPGA_LF_ADC_READER_FIELD : 0));
// Connect the A/D to the peak-detected low-frequency path.
SetAdcMuxFor(GPIO_MUXSEL_LOPKD);
// Now set up the SSC to get the ADC samples that are now streaming at us.
FpgaSetupSsc(FPGA_MAJOR_MODE_LF_READER);
// start a 1.5ticks is 1us
StartTicks();
// 50ms for the resonant antenna to settle.
if (reader_field) {
WaitMS(50);
} else {
WaitMS(1);
}
}
/**
* Does the sample acquisition. If threshold is specified, the actual sampling
* is not commenced until the threshold has been reached.
* This method implements decimation and quantization in order to
* be able to provide longer sample traces.
* Uses the following global settings:
* @param decimation - how much should the signal be decimated. A decimation of N means we keep 1 in N samples, etc.
* @param bits_per_sample - bits per sample. Max 8, min 1 bit per sample.
* @param averaging If set to true, decimation will use averaging, so that if e.g. decimation is 3, the sample
* value that will be used is the average value of the three samples.
* @param trigger_threshold - a threshold. The sampling won't commence until this threshold has been reached. Set
* to -1 to ignore threshold.
* @param verbose - is true, dbprints the status, else no outputs
* @return the number of bits occupied by the samples.
*/
uint32_t DoAcquisition(uint8_t decimation, uint8_t bits_per_sample, bool avg, int16_t trigger_threshold,
bool verbose, uint32_t sample_size, uint32_t cancel_after, int32_t samples_to_skip, bool ledcontrol) {
initSampleBuffer(&sample_size); // sample size in bytes
sample_size <<= 3; // sample size in bits
sample_size /= bits_per_sample; // sample count
if (g_dbglevel >= DBG_DEBUG) {
printSamples();
}
bool trigger_hit = false;
uint32_t cancel_counter = 0;
int16_t checked = 0;
while (BUTTON_PRESS() == false) {
// only every 4000th times, in order to save time when collecting samples.
// interruptible only when logging not yet triggered
if (trigger_hit == false && (checked >= 4000)) {
if (data_available()) {
checked = -1;
break;
} else {
checked = 0;
}
}
++checked;
WDT_HIT();
if (ledcontrol && (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY)) {
LED_D_ON();
}
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
// (RDV4) Test point 8 (TP8) can be used to trigger oscilloscope
if (ledcontrol) LED_D_OFF();
// threshold either high or low values 128 = center 0. if trigger = 178
if (trigger_hit == false) {
if ((trigger_threshold > 0) && (sample < (trigger_threshold + 128)) && (sample > (128 - trigger_threshold))) {
if (cancel_after > 0) {
cancel_counter++;
if (cancel_after == cancel_counter)
break;
}
continue;
}
trigger_hit = true;
}
if (samples_to_skip > 0) {
samples_to_skip--;
continue;
}
logSample(sample, decimation, bits_per_sample, avg);
if (samples.total_saved >= sample_size) break;
}
}
if (verbose) {
if (checked == -1) {
Dbprintf("lf sampling aborted");
} else if ((cancel_counter == cancel_after) && (cancel_after > 0)) {
Dbprintf("lf sampling cancelled after %u", cancel_counter);
}
Dbprintf("Done, saved " _YELLOW_("%d")" out of " _YELLOW_("%d")" seen samples at " _YELLOW_("%d")" bits/sample", samples.total_saved, samples.counter, bits_per_sample);
}
// Ensure that DC offset removal and noise check is performed for any device-side processing
if (bits_per_sample == 8) {
// these functions only consider bps==8
removeSignalOffset(data.buffer, samples.total_saved);
computeSignalProperties(data.buffer, samples.total_saved);
}
return data.numbits;
}
/**
* @brief Does sample acquisition, ignoring the config values set in the sample_config.
* This method is typically used by tag-specific readers who just wants to read the samples
* the normal way
* @param trigger_threshold
* @param verbose
* @return number of bits sampled
*/
uint32_t DoAcquisition_default(int trigger_threshold, bool verbose, bool ledcontrol) {
return DoAcquisition(1, 8, 0, trigger_threshold, verbose, 0, 0, 0, ledcontrol);
}
uint32_t DoAcquisition_config(bool verbose, uint32_t sample_size, bool ledcontrol) {
return DoAcquisition(config.decimation
, config.bits_per_sample
, config.averaging
, config.trigger_threshold
, verbose
, sample_size
, 0 // cancel_after
, config.samples_to_skip
, ledcontrol);
}
uint32_t DoPartialAcquisition(int trigger_threshold, bool verbose, uint32_t sample_size, uint32_t cancel_after, bool ledcontrol) {
return DoAcquisition(config.decimation
, config.bits_per_sample
, config.averaging
, trigger_threshold
, verbose
, sample_size
, cancel_after
, 0
, ledcontrol); // samples to skip
}
static uint32_t ReadLF(bool reader_field, bool verbose, uint32_t sample_size, bool ledcontrol) {
if (verbose)
printLFConfig();
LFSetupFPGAForADC(config.divisor, reader_field);
uint32_t ret = DoAcquisition_config(verbose, sample_size, ledcontrol);
StopTicks();
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
return ret;
}
/**
* Initializes the FPGA for reader-mode (field on), and acquires the samples.
* @return number of bits sampled
**/
uint32_t SampleLF(bool verbose, uint32_t sample_size, bool ledcontrol) {
BigBuf_Clear_ext(false);
return ReadLF(true, verbose, sample_size, ledcontrol);
}
/**
* Do LF sampling and send samples to the USB
*
* Uses parameters in config. Only bits_per_sample = 8 is working now
*
* @param reader_field - true for reading tags, false for sniffing
* @return sampling result
**/
int ReadLF_realtime(bool reader_field) {
// parameters from config and constants
const uint8_t bits_per_sample = config.bits_per_sample;
const int16_t trigger_threshold = config.trigger_threshold;
int32_t samples_to_skip = config.samples_to_skip;
const uint8_t decimation = config.decimation;
const int8_t size_threshold_table[9] = {0, 64, 64, 60, 64, 60, 60, 56, 64};
const int8_t size_threshold = size_threshold_table[bits_per_sample];
// DoAcquisition() start
uint8_t last_byte = 0;
uint8_t curr_byte = 0;
int return_value = PM3_SUCCESS;
uint32_t sample_buffer_len = AT91C_USB_EP_IN_SIZE;
initSampleBuffer(&sample_buffer_len);
if (sample_buffer_len != AT91C_USB_EP_IN_SIZE) {
return PM3_EFAILED;
}
bool trigger_hit = false;
int16_t checked = 0;
return_value = async_usb_write_start();
if (return_value != PM3_SUCCESS) {
return return_value;
}
BigBuf_Clear_ext(false);
LFSetupFPGAForADC(config.divisor, reader_field);
while (BUTTON_PRESS() == false) {
// only every 4000th times, in order to save time when collecting samples.
// interruptible only when logging not yet triggered
if (trigger_hit == false && (checked >= 4000)) {
if (data_available()) {
checked = -1;
break;
} else {
checked = 0;
}
}
++checked;
WDT_HIT();
if ((AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY)) {
LED_D_ON();
}
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
// (RDV4) Test point 8 (TP8) can be used to trigger oscilloscope
LED_D_OFF();
// threshold either high or low values 128 = center 0. if trigger = 178
if (trigger_hit == false) {
if ((trigger_threshold > 0) && (sample < (trigger_threshold + 128)) && (sample > (128 - trigger_threshold))) {
continue;
}
trigger_hit = true;
}
if (samples_to_skip > 0) {
samples_to_skip--;
continue;
}
logSample(sample, decimation, bits_per_sample, false);
// Write to USB FIFO if byte changed
curr_byte = data.numbits >> 3;
if (curr_byte > last_byte) {
async_usb_write_pushByte(data.buffer[last_byte]);
}
last_byte = curr_byte;
if (samples.total_saved == size_threshold) {
// Request USB transmission and change FIFO bank
if (async_usb_write_requestWrite() == false) {
return_value = PM3_EIO;
goto out;
}
// Reset sample
last_byte = 0;
data.numbits = 0;
samples.counter = size_threshold;
samples.total_saved = 0;
} else if (samples.total_saved == 1) {
// Check if there is any data from client
if (data_available_fast()) {
break;
}
}
}
}
return_value = async_usb_write_stop();
out:
LED_D_OFF();
// DoAcquisition() end
StopTicks();
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
return return_value;
}
/**
* Initializes the FPGA for sniffer-mode (field off), and acquires the samples.
* @return number of bits sampled
**/
uint32_t SniffLF(bool verbose, uint32_t sample_size, bool ledcontrol) {
BigBuf_Clear_ext(false);
return ReadLF(false, verbose, sample_size, ledcontrol);
}
/**
* acquisition of T55x7 LF signal. Similar to other LF, but adjusted with @marshmellows thresholds
* the data is collected in BigBuf.
**/
void doT55x7Acquisition(size_t sample_size, bool ledcontrol) {
#define T55xx_READ_UPPER_THRESHOLD 128+60 // 60 grph
#define T55xx_READ_LOWER_THRESHOLD 128-60 // -60 grph
#define T55xx_READ_TOL 5
uint8_t *dest = BigBuf_get_addr();
uint16_t bufsize = BigBuf_max_traceLen();
if (bufsize > sample_size)
bufsize = sample_size;
uint8_t lastSample = 0;
uint16_t i = 0, skipCnt = 0;
bool startFound = false;
bool highFound = false;
bool lowFound = false;
uint16_t checker = 0;
if (g_dbglevel >= DBG_DEBUG) {
Dbprintf("doT55x7Acquisition - after init");
print_stack_usage();
}
while (skipCnt < 1000 && (i < bufsize)) {
if (BUTTON_PRESS())
break;
if (checker == 4000) {
if (data_available())
break;
else
checker = 0;
} else {
++checker;
}
WDT_HIT();
if (ledcontrol && (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY)) {
LED_D_ON();
}
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
if (ledcontrol) LED_D_OFF();
// skip until the first high sample above threshold
if (!startFound && sample > T55xx_READ_UPPER_THRESHOLD) {
highFound = true;
} else if (!highFound) {
skipCnt++;
continue;
}
// skip until the first low sample below threshold
if (!startFound && sample < T55xx_READ_LOWER_THRESHOLD) {
lastSample = sample;
lowFound = true;
} else if (!lowFound) {
skipCnt++;
continue;
}
// skip until first high samples begin to change
if (startFound || sample > T55xx_READ_LOWER_THRESHOLD + T55xx_READ_TOL) {
// if just found start - recover last sample
if (startFound == false) {
dest[i++] = lastSample;
startFound = true;
}
// collect samples
if (i < bufsize) {
dest[i++] = sample;
}
}
}
}
}
/**
* acquisition of Cotag LF signal. Similart to other LF, since the Cotag has such long datarate RF/384
* and is Manchester?, we directly gather the manchester data into bigbuff
**/
#define COTAG_T1 384
#define COTAG_T2 (COTAG_T1 >> 1)
#define COTAG_ONE_THRESHOLD 127+5
#define COTAG_ZERO_THRESHOLD 127-5
#ifndef COTAG_BITS
#define COTAG_BITS 264
#endif
void doCotagAcquisition(void) {
uint16_t bufsize = BigBuf_max_traceLen();
uint8_t *dest = BigBuf_malloc(bufsize);
dest[0] = 0;
bool firsthigh = false, firstlow = false;
uint16_t i = 0, noise_counter = 0;
uint16_t checker = 0;
while ((i < bufsize - 1) && (noise_counter < COTAG_T1 << 1)) {
if (BUTTON_PRESS())
break;
if (checker == 4000) {
if (data_available())
break;
else
checker = 0;
} else {
++checker;
}
WDT_HIT();
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
// find first peak
if (firsthigh == false) {
if (sample < COTAG_ONE_THRESHOLD) {
noise_counter++;
continue;
}
noise_counter = 0;
firsthigh = true;
}
if (firstlow == false) {
if (sample > COTAG_ZERO_THRESHOLD) {
noise_counter++;
continue;
}
noise_counter = 0;
firstlow = true;
}
if (sample > COTAG_ONE_THRESHOLD) {
dest[i] = 255;
++i;
} else if (sample < COTAG_ZERO_THRESHOLD) {
dest[i] = 0;
++i;
} else {
dest[i] = dest[i - 1];
++i;
}
}
}
// Ensure that DC offset removal and noise check is performed for any device-side processing
removeSignalOffset(dest, i);
computeSignalProperties(dest, i);
}
uint16_t doCotagAcquisitionManchester(uint8_t *dest, uint16_t destlen) {
if (dest == NULL)
return 0;
dest[0] = 0;
bool firsthigh = false, firstlow = false;
uint8_t curr = 0, prev = 0;
uint16_t i = 0;
uint16_t period = 0, checker = 0;
while ((i < destlen) && BUTTON_PRESS() == false) {
WDT_HIT();
if (checker == 4000) {
if (data_available())
break;
else
checker = 0;
} else {
++checker;
}
if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
// find first peak
if (firsthigh == false) {
if (sample < COTAG_ONE_THRESHOLD) {
continue;
}
firsthigh = true;
}
if (firstlow == false) {
if (sample > COTAG_ZERO_THRESHOLD) {
continue;
}
firstlow = true;
}
// set sample 255, 0, or previous
if (sample > COTAG_ONE_THRESHOLD) {
prev = curr;
curr = 1;
} else if (sample < COTAG_ZERO_THRESHOLD) {
prev = curr;
curr = 0;
} else {
curr = prev;
}
// full T1 periods,
if (period > 0) {
--period;
continue;
}
dest[i] = curr;
++i;
period = COTAG_T1;
}
}
return i;
}