blob: f9fbf8bb1d843fba3852268863fd646b2a45459a [file] [log] [blame]
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include "spectral.h"
static void load_default_config(spectral_config* config);
static int load_config(spectral_config* config);
static void usage(const char* progname);
static int parse_args(spectral_config* config, int argc, char* const argv[]);
static void verify_config(spectral_config* config);
static int execute_full_scan(spectral_config* config, bucket_results* buckets);
static int send_control(const char* message);
static int go_offchannel(const spectral_config* config, int freq);
static char* collect_results(ssize_t* total);
static void append_results(const char* raw_data, ssize_t size,
bucket_results* buckets);
static void post_buckets(const bucket_results* result);
static int calc_square_sum(const uint8_t* data, int num, uint8_t exp);
static void dump_raw_data(spectral_config* config, char* data, ssize_t size);
// Only for reading non-negative numbers, returns -1 if file doesn't exist.
static int32_t read_file_as_int(char *file_path);
int main(int argc, char* const argv[]) {
spectral_config config;
bucket_results buckets;
int rv;
int ignore_fs_config = 0;
setvbuf(stdout, (char *) NULL, _IOLBF, 0);
printf("Starting background spectral scanner\n");
load_default_config(&config);
if (argc > 1) {
if (parse_args(&config, argc, argv) < 0) {
usage(argv[0]);
return 1;
}
// This allows for using it in a way that overrides what's
// configured as the settings in the filesystem.
ignore_fs_config = 1;
verify_config(&config);
}
while (1) {
if (!ignore_fs_config) {
if (load_config(&config))
verify_config(&config);
}
if (config.scan_period_millis == 0) {
// This is how we can disable it, but setting the overall
// scan period to zero. We then recheck our config in 6 minutes.
usleep(360000);
continue;
}
rv = execute_full_scan(&config, &buckets);
if (rv >= 0) {
post_buckets(&buckets);
} else {
fprintf(stderr, "Failure with full scan\n");
}
usleep(config.scan_period_millis * 1000);
}
return 0;
}
static void load_default_config(spectral_config* config) {
config->offchan_dur_millis = 100;
config->scan_period_millis = 300000;
config->channel_switch_delay_millis = 1000;
config->dump_dir[0] = '\0';
config->dump_count = 0;
}
static int load_config(spectral_config* config) {
int rv = 0;
int32_t config_value = read_file_as_int("/tmp/spectral_offchannel_duration");
if (config_value >= 0) {
config->offchan_dur_millis = config_value;
rv =1;
}
config_value = read_file_as_int("/tmp/spectral_offchannel_switch_delay");
if (config_value >= 0) {
config->channel_switch_delay_millis = config_value;
rv = 1;
}
config_value = read_file_as_int("/tmp/spectral_scan_period");
if (config_value >= 0) {
config->scan_period_millis = config_value;
rv = 1;
}
return rv;
}
static void usage(const char* progname) {
printf("%s [--offchan_dur dur] [--scan_period period] "
"[--dump_dir dir] [--channel_switch_delay delay]\n", progname);
exit(1);
}
static int parse_args(spectral_config* config, int argc, char* const argv[]) {
int opt = 0, len;
static struct option long_options[] = {
{ "offchan_dur", required_argument, 0, 'o' },
{ "scan_period", required_argument, 0, 's' },
{ "channel_switch_delay", required_argument, 0, 'c' },
{ "dump_dir", required_argument, 0, 'd' },
{ 0, 0, 0, 0}
};
while (1) {
opt = getopt_long(argc, argv, "", long_options, NULL);
if (opt == -1)
break;
switch (opt) {
case 'o':
config->offchan_dur_millis = atoi(optarg);
break;
case 's':
config->scan_period_millis = atoi(optarg);
break;
case 'c':
config->channel_switch_delay_millis = atoi(optarg);
break;
case 'd':
len = snprintf(config->dump_dir, sizeof(config->dump_dir) - 1,
"%s", optarg);
if (len >= sizeof(config->dump_dir) - 1) {
fprintf(stderr, "Dump path is too long\n");
return -1;
}
if (config->dump_dir[len - 1] != '/') {
config->dump_dir[len] = '/';
config->dump_dir[len + 1] = '\0';
}
break;
default:
return -1;
}
}
if (optind < argc)
return -1; // extraneous non-option arguments
return 0;
}
static void verify_config(spectral_config* config) {
if (config->offchan_dur_millis <= 0) {
fprintf(stderr, "Invalid offchan_dur_millis in spectral config %d\n",
config->offchan_dur_millis);
config->offchan_dur_millis = 100;
}
if (config->scan_period_millis < 0) {
fprintf(stderr,
"Invalid scan_period_millis in spectral config %d\n",
config->scan_period_millis);
config->scan_period_millis = 300000;
}
if (config->channel_switch_delay_millis <= 0) {
fprintf(stderr,
"Invalid channel_switch_delay in spectral config %d\n",
config->channel_switch_delay_millis);
config->channel_switch_delay_millis = 1000;
}
printf("Loaded spectral config offchan_dur %d scan_period %d"
" channel_switch %d dump_dir %s\n",
config->offchan_dur_millis, config->scan_period_millis,
config->channel_switch_delay_millis, config->dump_dir);
}
static int execute_full_scan(spectral_config* config, bucket_results* buckets) {
int freq, rv;
ssize_t total_size;
char* raw_data;
if (buckets)
memset(buckets, 0, sizeof(*buckets));
for (freq = MIN_SCAN_FREQ; freq <= MAX_SCAN_FREQ; freq += FREQ_STEP) {
// First we need to enable background scanning.
rv = send_control("background");
if (rv < 0) {
fprintf(stderr, "Failed to set background scanning\n");
return -1;
}
// Switch the AP into offchannel mode briefly.
rv = go_offchannel(config, freq);
if (rv < 0) {
fprintf(stderr, "Failed to do offchannel scan\n");
return -1;
}
// Trigger the actual scan.
rv = send_control("trigger");
if (rv < 0) {
fprintf(stderr, "Failure triggering the spectral scan\n");
return -1;
}
// Wait for the scan to complete.
usleep(config->offchan_dur_millis * 1000);
// Disable scanning.
rv = send_control("disable");
if (rv < 0) {
fprintf(stderr, "Failure disabling the spectral scan\n");
return -1;
}
total_size = 0;
raw_data = collect_results(&total_size);
if (!raw_data) {
fprintf(stderr, "Failure collecting scan data results\n");
return -1;
}
append_results(raw_data, total_size, buckets);
if (config->dump_dir[0]) {
dump_raw_data(config, raw_data, total_size);
}
free(raw_data);
// Wait to do another cycle
usleep(config->channel_switch_delay_millis * 1000);
}
return 0;
}
static void dump_raw_data(spectral_config* config, char* data, ssize_t size) {
int dump_fd;
ssize_t written;
char target_file[MAX_PATH + 20];
snprintf(target_file, sizeof(target_file), "%sspectral-%d", config->dump_dir,
config->dump_count);
config->dump_count++;
dump_fd = open(target_file, O_WRONLY | O_TRUNC | O_CREAT, 0666);
if (dump_fd < 0) {
fprintf(stderr, "Could not open dump file at %s errno %d\n",
target_file, errno);
return;
}
written = write(dump_fd, data, size);
if (size != written) {
fprintf(stderr, "Incomplete write to dump file %s wrote %zd length %zd\n",
target_file, written, size);
}
if (close(dump_fd) < 0) {
fprintf(stderr, "Failure closing dump file %s\n", target_file);
}
}
static int send_control(const char* message) {
int len, scan_ctl_fd, rv;
ssize_t count;
scan_ctl_fd = open(
"/sys/kernel/debug/ieee80211/phy0/ath9k/spectral_scan_ctl",
O_WRONLY | O_TRUNC);
if (scan_ctl_fd < 0) {
fprintf(stderr, "Failure opening spectral_scan_ctl: %d\n", errno);
return -1;
}
len = strlen(message);
count = write(scan_ctl_fd, message, len);
rv = close(scan_ctl_fd);
if (len != count) {
fprintf(stderr, "Incomplete write to spectral_scan_ctl written %zd len %d",
count, len);
return -1;
}
if (rv < 0) {
fprintf(stderr, "Failure closing spectral_scan_ctl: %d\n", errno);
return -1;
}
return 0;
}
static int go_offchannel(const spectral_config* config, int freq) {
char* iwcmd[7];
char freq_str[16];
char dwell_str[16];
pid_t pid;
int fd, maxfd;
printf("Performing wifi spectral scan at freq %d\n", freq);
iwcmd[0] = "iw";
iwcmd[1] = "dev";
iwcmd[2] = "wlan0";
iwcmd[3] = "offchannel";
snprintf(freq_str, sizeof(freq_str), "%d", freq);
iwcmd[4] = freq_str;
snprintf(dwell_str, sizeof(dwell_str), "%d", config->offchan_dur_millis);
iwcmd[5] = dwell_str;
iwcmd[6] = NULL;
pid = fork();
if (pid != -1) {
if (!pid) { // child
// Close all descriptors except stdin, stdout, stderr
maxfd = sysconf(_SC_OPEN_MAX);
for (fd = 3; fd < maxfd; ++fd) {
close(fd);
}
execvp(iwcmd[0], (char* const *)iwcmd);
// It won't reach this unless there was an error
fprintf(stderr, "Error executing %s errno: %d\n", iwcmd[0], errno);
exit(1);
} else {
// Wait for it to finish
waitpid(pid, NULL, 0);
return 0;
}
} else {
fprintf(stderr, "Error executing child process errno: %d\n", errno);
return -1;
}
}
static char* collect_results(ssize_t* total) {
char *buf = NULL, *buf_tmp = NULL;
const ssize_t read_size = 4096;
ssize_t count;
int result_fd = open(
"/sys/kernel/debug/ieee80211/phy0/ath9k/spectral_scan0",
O_RDONLY);
if (result_fd < 0) {
fprintf(stderr, "Error opening spectral_scan0 of %d\n", errno);
return NULL;
}
*total = 0;
while (1) {
buf_tmp = realloc(buf, *total + read_size);
if (!buf_tmp) {
fprintf(stderr, "Failure reallocating read buffer\n");
if (buf)
free(buf);
*total = 0;
close(result_fd);
return NULL;
}
buf = buf_tmp;
count = read(result_fd, buf + *total, read_size);
*total += count;
if (count < 0) {
fprintf(stderr, "Failure reading spectral_scan0 of %d\n", errno);
*total = 0;
free(buf);
close(result_fd);
return NULL;
}
if (count < read_size)
break;
}
close(result_fd);
//printf("Succesfully read %d bytes of scan data\n", *total);
return buf;
}
static void append_results(const char* raw_data, ssize_t size,
bucket_results* buckets) {
uint16_t freq;
uint64_t timestamp;
int i, bin_offset, square_sum, base_value;
int power_bucket_index, freq_bucket_index;
float bucket_max = -200, curr_signal;
fft_data_tlv* tlv;
fft_data* curr_fft_data;
size_t data_len;
const char* pos = raw_data;
while (pos - raw_data < size) {
tlv = (fft_data_tlv*) pos;
data_len = sizeof(fft_data_tlv) + be16toh(tlv->len);
pos += data_len;
if (tlv->type != 1) {
// 1 is the type code for the HT20 data we understand.
fprintf(stderr, "Invalid type code in scan data of %d\n", tlv->type);
continue;
}
if (data_len != sizeof(fft_data)) {
fprintf(stderr, "Invalid data length in scan data of %zd\n", data_len);
continue;
}
curr_fft_data = (fft_data*) tlv;
// Fix the byte ordering on the data fields larger than 8 bits.
freq = be16toh(curr_fft_data->freq);
timestamp = be64toh(curr_fft_data->timestamp);
if (buckets) {
buckets->timestamp = timestamp;
}
bin_offset = FREQ_STEP_BIN_OFFSET * (freq - MIN_SCAN_FREQ) / FREQ_STEP;
if (bin_offset < 0 || bin_offset >= NUM_OVERALL_BINS) {
fprintf(stderr, "Invalid frequency bin %d from freq %d\n",
bin_offset, freq);
continue;
}
square_sum = calc_square_sum(curr_fft_data->fft_values,
NUM_SAMPLE_BINS,
curr_fft_data->max_exponent);
if (square_sum == 0) {
// Sometimes the data is all empty so the square sum is zero,
// we can't do anything correct in this case since this is just
// bad data coming from the driver so just skip it.
// This happens every so often..no need to log it.
//fprintf(stderr, "Skipping data chunk due to zero sum\n");
continue;
}
for (i = 0; i < NUM_SAMPLE_BINS; ++i) {
base_value = curr_fft_data->fft_values[i] << curr_fft_data->max_exponent;
if (base_value == 0)
base_value = 1;
curr_signal = curr_fft_data->noise + curr_fft_data->rssi +
20 * log10f(base_value) - 10 * log10f(square_sum);
if (buckets) {
if (i % BINS_PER_BUCKET == 0)
bucket_max = -200;
if (curr_signal > bucket_max)
bucket_max = curr_signal;
if ((i + 1) % BINS_PER_BUCKET == 0) {
// We have the max for this bucket, update the info
freq_bucket_index = (bin_offset + i + 1 - BINS_PER_BUCKET) /
BINS_PER_BUCKET;
if (bucket_max <= LOWER_POWER_BUCKET_MIN)
power_bucket_index = 0;
else if (bucket_max >= UPPER_POWER_BUCKET_MAX)
power_bucket_index = NUM_POWER_BUCKETS - 1;
else
power_bucket_index = (((int) bucket_max) - LOWER_POWER_BUCKET_MIN) /
POWER_BUCKET_STEP;
buckets->bucket_count[freq_bucket_index][power_bucket_index]++;
buckets->total[freq_bucket_index]++;
}
}
}
}
}
static int calc_square_sum(const uint8_t* data, int num, uint8_t exp) {
int i, curr, rv = 0;
for (i = 0; i < num; ++i) {
curr = data[i] << exp;
rv += (curr * curr);
}
return rv;
}
static void post_buckets(const bucket_results* result) {
int i, j, k, perct, overall_total, bucket_total;
// This prints out the buckets as 5 MHz intervals and doesn't coalesce
// the data into each Wifi channel itself. It may be useful again so I'm
// going to leave it here just in case.
if (0) {
for (i = 0; i < NUM_FREQ_BUCKETS; i++) {
if (result->total[i]) {
printf("%d MHz\n", MIN_OVERALL_FREQ + (i * FREQ_BUCKET_STEP));
for (j = 0; j < NUM_POWER_BUCKETS; ++j) {
perct = result->bucket_count[i][j] * 100 / result->total[i];
if (perct > 0)
printf(" %3d", perct);
else
printf(" ");
}
printf("\n");
}
}
}
// We start at 2 because we are going over all the wifi channels
// here and the first two will get added into the first channel.
// We stop one short of the end because that one will get included in
// channel 11.
for (i = 2; i < NUM_FREQ_BUCKETS - 1; i++) {
printf("fft-%2d:", i - 1);
overall_total = 0;
for (k = -2; k < 2; k++) {
overall_total += result->total[i + k];
}
for (j = 0; j < NUM_POWER_BUCKETS; ++j) {
bucket_total = 0;
for (k = -2; k < 2; k++) {
bucket_total += result->bucket_count[i + k][j];
}
perct = bucket_total * 100 / overall_total;
if (perct > 0)
printf(" %3d", perct);
else
printf(" ");
}
printf("\n");
}
}
int32_t read_file_as_int(char* file_path) {
char buf[64];
int num_read, fd = open(file_path, O_RDONLY);
if (fd < 0)
return -1;
num_read = read(fd, buf, sizeof(buf) -1);
if (num_read > 0) {
buf[num_read] = '\0';
return atoi(buf);
}
return -1;
}