// Part of dump1090, a Mode S message decoder for RTLSDR devices. // // sdr_rtlsdr.c: rtlsdr dongle support // // Copyright (c) 2014-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 2 of the License, or (at your // option) any later version. // // This file 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. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "dump1090.h" #include "sdr_rtlsdr.h" #include #if defined(__arm__) || defined(__aarch64__) // Assume we need to use a bounce buffer to avoid performance problems on Pis running kernel 5.x and using zerocopy # define USE_BOUNCE_BUFFER #endif static struct { rtlsdr_dev_t *dev; bool digital_agc; int ppm_error; int direct_sampling; uint8_t *bounce_buffer; iq_convert_fn converter; struct converter_state *converter_state; int *gains; int gain_steps; int current_gain; } RTLSDR; // // =============================== RTLSDR handling ========================== // void rtlsdrInitConfig() { RTLSDR.dev = NULL; RTLSDR.digital_agc = false; RTLSDR.ppm_error = 0; RTLSDR.direct_sampling = 0; RTLSDR.bounce_buffer = NULL; RTLSDR.converter = NULL; RTLSDR.converter_state = NULL; RTLSDR.gains = NULL; RTLSDR.gain_steps = 0; RTLSDR.current_gain = 0; } static void show_rtlsdr_devices() { int device_count = rtlsdr_get_device_count(); fprintf(stderr, "rtlsdr: found %d device(s):\n", device_count); for (int i = 0; i < device_count; i++) { char vendor[256], product[256], serial[256]; if (rtlsdr_get_device_usb_strings(i, vendor, product, serial) != 0) { fprintf(stderr, " %d: unable to read device details\n", i); } else { fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial); } } } static int find_device_index(char *s) { int device_count = rtlsdr_get_device_count(); if (!device_count) { return -1; } /* does string look like raw id number */ if (!strcmp(s, "0")) { return 0; } else if (s[0] != '0') { char *s2; int device = (int)strtol(s, &s2, 10); if (s2[0] == '\0' && device >= 0 && device < device_count) { return device; } } /* does string exact match a serial */ for (int i = 0; i < device_count; i++) { char serial[256]; if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && !strcmp(s, serial)) { return i; } } /* does string prefix match a serial */ for (int i = 0; i < device_count; i++) { char serial[256]; if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && !strncmp(s, serial, strlen(s))) { return i; } } /* does string suffix match a serial */ for (int i = 0; i < device_count; i++) { char serial[256]; if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && strlen(s) < strlen(serial) && !strcmp(serial + strlen(serial) - strlen(s), s)) { return i; } } return -1; } void rtlsdrShowHelp() { printf(" rtlsdr-specific options (use with --device-type rtlsdr)\n"); printf("\n"); printf("--device select device by index or serial number\n"); printf("--enable-agc enable digital AGC (not tuner AGC!)\n"); printf("--ppm set oscillator frequency correction in PPM\n"); printf("--direct <0|1|2> set direct sampling mode\n"); printf("\n"); } bool rtlsdrHandleOption(int argc, char **argv, int *jptr) { int j = *jptr; bool more = (j +1 < argc); if (!strcmp(argv[j], "--enable-agc")) { RTLSDR.digital_agc = true; } else if (!strcmp(argv[j], "--ppm") && more) { RTLSDR.ppm_error = atoi(argv[++j]); } else if (!strcmp(argv[j], "--direct") && more) { RTLSDR.direct_sampling = atoi(argv[++j]); } else { return false; } *jptr = j; return true; } // sort function to sort by increasing gain static int sort_gains(const void *left, const void *right) { const int *left_int = (const int *)left; const int *right_int = (const int *)right; return *left_int - *right_int; } bool rtlsdrOpen(void) { if (!rtlsdr_get_device_count()) { fprintf(stderr, "rtlsdr: no supported devices found.\n"); return false; } int dev_index = 0; if (Modes.dev_name) { if ((dev_index = find_device_index(Modes.dev_name)) < 0) { fprintf(stderr, "rtlsdr: no device matching '%s' found.\n", Modes.dev_name); show_rtlsdr_devices(); return false; } } char manufacturer[256]; char product[256]; char serial[256]; if (rtlsdr_get_device_usb_strings(dev_index, manufacturer, product, serial) < 0) { fprintf(stderr, "rtlsdr: error querying device #%d: %s\n", dev_index, strerror(errno)); return false; } fprintf(stderr, "rtlsdr: using device #%d: %s (%s, %s, SN %s)\n", dev_index, rtlsdr_get_device_name(dev_index), manufacturer, product, serial); if (rtlsdr_open(&RTLSDR.dev, dev_index) < 0) { fprintf(stderr, "rtlsdr: error opening the RTLSDR device: %s\n", strerror(errno)); return false; } // Set gain, frequency, sample rate, and reset the device if (RTLSDR.direct_sampling) { fprintf(stderr, "rtlsdr: direct sampling from input %d\n", RTLSDR.direct_sampling); rtlsdr_set_direct_sampling(RTLSDR.dev, RTLSDR.direct_sampling); RTLSDR.gain_steps = 0; } else { int *gains; int numgains; numgains = rtlsdr_get_tuner_gains(RTLSDR.dev, NULL); if (numgains <= 0) { fprintf(stderr, "rtlsdr: error getting tuner gains\n"); return false; } gains = malloc((numgains + 1) * sizeof(int)); if (rtlsdr_get_tuner_gains(RTLSDR.dev, gains) != numgains) { fprintf(stderr, "rtlsdr: error getting tuner gains\n"); free(gains); return false; } qsort(gains, numgains, sizeof(gains[0]), sort_gains); // Fake an entry at slightly higher than max manual gain; // we will use this for the "tuner AGC enabled" settings // which due to librtlsdr quirks behaves like a "more than // max" gain. :/ gains[numgains] = gains[numgains-1] + 90; // +9.0dB RTLSDR.gain_steps = numgains + 1; RTLSDR.gains = gains; int selected = -1; if (Modes.gain == MODES_LEGACY_AUTO_GAIN) { selected = numgains; } else if (Modes.gain == MODES_DEFAULT_GAIN) { selected = numgains - 1; } else { for (int i = 0; i <= numgains; ++i) { if (selected == -1 || fabs(gains[i]/10.0 - Modes.gain) < fabs(gains[selected]/10.0 - Modes.gain)) selected = i; } } rtlsdrSetGain(selected); } if (RTLSDR.digital_agc) { fprintf(stderr, "rtlsdr: enabling digital AGC\n"); rtlsdr_set_agc_mode(RTLSDR.dev, 1); } rtlsdr_set_freq_correction(RTLSDR.dev, RTLSDR.ppm_error); rtlsdr_set_center_freq(RTLSDR.dev, Modes.freq); rtlsdr_set_sample_rate(RTLSDR.dev, (unsigned)Modes.sample_rate); rtlsdr_reset_buffer(RTLSDR.dev); RTLSDR.converter = init_converter(INPUT_UC8, Modes.sample_rate, Modes.dc_filter, &RTLSDR.converter_state); if (!RTLSDR.converter) { fprintf(stderr, "rtlsdr: can't initialize sample converter\n"); rtlsdrClose(); return false; } #ifdef USE_BOUNCE_BUFFER if (!(RTLSDR.bounce_buffer = malloc(MODES_RTL_BUF_SIZE))) { fprintf(stderr, "rtlsdr: can't allocate bounce buffer\n"); rtlsdrClose(); return false; } #endif if (Modes.adaptive_range_target == 0) Modes.adaptive_range_target = 30.0; return true; } static void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) { static unsigned dropped = 0; static uint64_t sampleCounter = 0; MODES_NOTUSED(ctx); sdrMonitor(); if (Modes.exit) { rtlsdr_cancel_async(RTLSDR.dev); // ask our caller to exit return; } unsigned samples_read = len/2; // Drops any trailing odd sample, not much else we can do there if (!samples_read) return; // that wasn't useful struct mag_buf *outbuf = fifo_acquire(0 /* don't wait */); if (!outbuf) { // FIFO is full. Drop this block. dropped += samples_read; sampleCounter += samples_read; return; } outbuf->flags = 0; if (dropped) { // We previously dropped some samples due to no buffers being available outbuf->flags |= MAGBUF_DISCONTINUOUS; outbuf->dropped = dropped; } dropped = 0; // Compute the sample timestamp and system timestamp for the start of the block outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; sampleCounter += samples_read; // Get the approx system time for the start of this block uint64_t block_duration = 1e3 * samples_read / Modes.sample_rate; outbuf->sysTimestamp = mstime() - block_duration; // Convert the new data unsigned to_convert = samples_read; if (to_convert + outbuf->overlap > outbuf->totalLength) { // how did that happen? to_convert = outbuf->totalLength - outbuf->overlap; dropped = samples_read - to_convert; } #ifdef USE_BOUNCE_BUFFER // Work around zero-copy slowness on Pis with 5.x kernels memcpy(RTLSDR.bounce_buffer, buf, to_convert * 2); buf = RTLSDR.bounce_buffer; #endif RTLSDR.converter(buf, &outbuf->data[outbuf->overlap], to_convert, RTLSDR.converter_state, &outbuf->mean_level, &outbuf->mean_power); outbuf->validLength = outbuf->overlap + to_convert; // Push to the demodulation thread fifo_enqueue(outbuf); } void rtlsdrRun() { if (!RTLSDR.dev) { return; } rtlsdr_read_async(RTLSDR.dev, rtlsdrCallback, NULL, /* MODES_RTL_BUFFERS */ 4, MODES_RTL_BUF_SIZE); if (!Modes.exit) { fprintf(stderr, "rtlsdr: rtlsdr_read_async returned unexpectedly, probably lost the USB device, bailing out\n"); } } void rtlsdrStop() { if (!RTLSDR.dev) { return; } rtlsdr_cancel_async(RTLSDR.dev); } void rtlsdrClose() { if (RTLSDR.dev) { rtlsdr_close(RTLSDR.dev); RTLSDR.dev = NULL; } if (RTLSDR.converter) { cleanup_converter(RTLSDR.converter_state); RTLSDR.converter = NULL; RTLSDR.converter_state = NULL; } free(RTLSDR.bounce_buffer); RTLSDR.bounce_buffer = NULL; free(RTLSDR.gains); RTLSDR.gains = NULL; } int rtlsdrGetGain() { return RTLSDR.current_gain; } int rtlsdrGetMaxGain() { return RTLSDR.gain_steps - 1; } double rtlsdrGetGainDb(int step) { if (!RTLSDR.gains) return 0.0; if (step < 0) step = 0; if (step >= RTLSDR.gain_steps) step = RTLSDR.gain_steps - 1; return RTLSDR.gains[step] / 10.0; } int rtlsdrSetGain(int step) { if (!RTLSDR.gains) return -1; if (step < 0) step = 0; if (step >= RTLSDR.gain_steps) step = RTLSDR.gain_steps - 1; if (step == RTLSDR.gain_steps - 1) { if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 0) < 0) { fprintf(stderr, "rtlsdr: failed to enable tuner AGC\n"); return RTLSDR.current_gain; } fprintf(stderr, "rtlsdr: tuner gain set to about %.1f dB (gain step %d) (tuner AGC enabled)\n", RTLSDR.gains[step] / 10.0, step); } else { if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 1) < 0) { fprintf(stderr, "rtlsdr: failed to disable tuner AGC\n"); return RTLSDR.current_gain; } if (rtlsdr_set_tuner_gain(RTLSDR.dev, RTLSDR.gains[step]) < 0) { fprintf(stderr, "rtlsdr: failed to set tuner gain to %.1fdB\n", RTLSDR.gains[step] / 10.0); return RTLSDR.current_gain; } fprintf(stderr, "rtlsdr: tuner gain set to %.1f dB (gain step %d)\n", RTLSDR.gains[step] / 10.0, step); } RTLSDR.current_gain = step; return step; }