From bef6eae05b959b2ce395b3f949675d2a93053f0d Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 16 Jan 2018 15:42:48 +0100 Subject: Move sysmobts-calib into osmo-bts-sysmo It's prerequisite for jenkins tests fix after migration to stow. The sysmobts-calib uses hand-coded Makefile instead of automake which makes it hard to properly propagate build flags. Also, make it optional to enable excluding it from certain jenkins tests. Change-Id: I3b54bfa5ef1d89092f6cf13dc27de10874b31b18 --- src/osmo-bts-sysmo/misc/sysmobts-calib.c | 537 ++++++++++++++++++++ src/osmo-bts-sysmo/misc/sysmobts-layer1.c | 800 ++++++++++++++++++++++++++++++ src/osmo-bts-sysmo/misc/sysmobts-layer1.h | 45 ++ 3 files changed, 1382 insertions(+) create mode 100644 src/osmo-bts-sysmo/misc/sysmobts-calib.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts-layer1.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts-layer1.h (limited to 'src/osmo-bts-sysmo/misc') diff --git a/src/osmo-bts-sysmo/misc/sysmobts-calib.c b/src/osmo-bts-sysmo/misc/sysmobts-calib.c new file mode 100644 index 00000000..a111d1d5 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-calib.c @@ -0,0 +1,537 @@ +/* OCXO/TCXO based calibration utility */ + +/* + * (C) 2012-2013 Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include + +#include + +#include + +#include "sysmobts-layer1.h" + +enum actions { + ACTION_SCAN, + ACTION_CALIB, + ACTION_BCCH, + ACTION_BCCH_CCCH, +}; + +static const char *modes[] = { + [ACTION_SCAN] = "scan", + [ACTION_CALIB] = "calibrate", + [ACTION_BCCH] = "bcch", + [ACTION_BCCH_CCCH] = "bcch_ccch", +}; + +static const char *bands[] = { + [GsmL1_FreqBand_850] = "850", + [GsmL1_FreqBand_900] = "900", + [GsmL1_FreqBand_1800] = "1800", + [GsmL1_FreqBand_1900] = "1900", +}; + +struct channel_pair { + int min; + int max; +}; + +static const struct channel_pair arfcns[] = { + [GsmL1_FreqBand_850] = { .min = 128, .max = 251 }, + [GsmL1_FreqBand_900] = { .min = 1, .max = 124 }, + [GsmL1_FreqBand_1800] = { .min = 512, .max = 885 }, + [GsmL1_FreqBand_1900] = { .min = 512, .max = 810 }, + +}; + +static const char *clk_source[] = { + [SuperFemto_ClkSrcId_Ocxo] = "ocxo", + [SuperFemto_ClkSrcId_Tcxo] = "tcxo", + [SuperFemto_ClkSrcId_External] = "external", + [SuperFemto_ClkSrcId_GpsPps] = "gps", + [SuperFemto_ClkSrcId_Trx] = "trx", + [SuperFemto_ClkSrcId_Rx] = "rx", + [SuperFemto_ClkSrcId_Edge] = "edge", + [SuperFemto_ClkSrcId_NetList] = "netlisten", +}; + +static const struct value_string sapi_names[GsmL1_Sapi_NUM+1] = { + { GsmL1_Sapi_Fcch, "FCCH" }, + { GsmL1_Sapi_Sch, "SCH" }, + { GsmL1_Sapi_Sacch, "SACCH" }, + { GsmL1_Sapi_Sdcch, "SDCCH" }, + { GsmL1_Sapi_Bcch, "BCCH" }, + { GsmL1_Sapi_Pch, "PCH" }, + { GsmL1_Sapi_Agch, "AGCH" }, + { GsmL1_Sapi_Cbch, "CBCH" }, + { GsmL1_Sapi_Rach, "RACH" }, + { GsmL1_Sapi_TchF, "TCH/F" }, + { GsmL1_Sapi_FacchF, "FACCH/F" }, + { GsmL1_Sapi_TchH, "TCH/H" }, + { GsmL1_Sapi_FacchH, "FACCH/H" }, + { GsmL1_Sapi_Nch, "NCH" }, + { GsmL1_Sapi_Pdtch, "PDTCH" }, + { GsmL1_Sapi_Pacch, "PACCH" }, + { GsmL1_Sapi_Pbcch, "PBCCH" }, + { GsmL1_Sapi_Pagch, "PAGCH" }, + { GsmL1_Sapi_Ppch, "PPCH" }, + { GsmL1_Sapi_Pnch, "PNCH" }, + { GsmL1_Sapi_Ptcch, "PTCCH" }, + { GsmL1_Sapi_Prach, "PRACH" }, + { 0, NULL } +}; + +static int action = ACTION_SCAN; +static int band = GsmL1_FreqBand_900; +static int calib = SuperFemto_ClkSrcId_Ocxo; +static int source = SuperFemto_ClkSrcId_NetList; +static int dsp_flags = 0x0; +static int cal_arfcn = 0; +static int initial_cor = 0; +static int steps = -1; + +static void print_usage(void) +{ + printf("Usage: sysmobts-calib ARGS\n"); +} + +static void print_help(void) +{ + printf(" -h --help this text\n"); + printf(" -c --clock " + "ocxo|tcxo|external|gps|trx|rx|edge\n"); + printf(" -s --calibration-source " + "ocxo|tcxo|external|gps|trx|rx|edge|netlisten\n"); + printf(" -b --band 850|900|1800|1900\n"); + printf(" -m --mode scan|calibrate|bcch|bcch_ccch\n"); + printf(" -a --arfcn NR arfcn for calibration\n"); + printf(" -d --dsp-flags NR dsp mask for debug log\n"); + printf(" -t --threshold level\n"); + printf(" -i --initial-clock-correction COR.\n"); + printf(" -t --steps STEPS\n"); +} + +static int find_value(const char **array, int size, char *value) +{ + int i = 0; + for (i = 0; i < size; ++i) { + if (array[i] == NULL) + continue; + if (strcmp(value, array[i]) == 0) + return i; + } + + printf("Failed to find: '%s'\n", value); + exit(-2); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"calibration-source", 1, 0, 's'}, + {"clock", 1, 0, 'c'}, + {"mode", 1, 0, 'm'}, + {"band", 1, 0, 'b'}, + {"dsp-flags", 1, 0, 'd'}, + {"arfcn", 1, 0, 'a'}, + {"initial-clock-correction", 1, 0, 'i'}, + {"steps", 1, 0, 't'}, + {0, 0, 0, 0}, + }; + + c = getopt_long(argc, argv, "hs:c:m:b:d:a:i:t:", + long_options, &option_index); + if (c == -1) + break; + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + source = find_value(clk_source, + ARRAY_SIZE(clk_source), optarg); + break; + case 'c': + calib = find_value(clk_source, + ARRAY_SIZE(clk_source), optarg); + break; + case 'm': + action = find_value(modes, + ARRAY_SIZE(modes), optarg); + break; + case 'b': + band = find_value(bands, + ARRAY_SIZE(bands), optarg); + break; + case 'd': + dsp_flags = strtol(optarg, NULL, 16); + break; + case 'a': + cal_arfcn = atoi(optarg); + break; + case 'i': + initial_cor = atoi(optarg); + break; + case 't': + steps = atoi(optarg); + break; + default: + printf("Unhandled option, terminating.\n"); + exit(-1); + } + } + + if (source == calib) { + printf("Clock source and reference clock may not be the same.\n"); + exit(-3); + } + + if (calib == SuperFemto_ClkSrcId_NetList) { + printf("Clock may not be network listen.\n"); + exit(-4); + } + + if (action == ACTION_CALIB && source == SuperFemto_ClkSrcId_NetList) { + if (cal_arfcn == 0) { + printf("Please specify the reference ARFCN.\n"); + exit(-5); + } + + if (cal_arfcn < arfcns[band].min || cal_arfcn > arfcns[band].max) { + printf("ARFCN(%d) is not in the given band.\n", cal_arfcn); + exit(-6); + } + } +} + +#define CHECK_RC(rc) \ + if (rc != 0) \ + return EXIT_FAILURE; + +#define CHECK_RC_MSG(rc, msg) \ + if (rc != 0) { \ + printf("%s: %d\n", msg, rc); \ + return EXIT_FAILURE; \ + } +#define CHECK_COND_MSG(cond, rc, msg) \ + if (cond) { \ + printf("%s: %d\n", msg, rc); \ + return EXIT_FAILURE; \ + } + +struct scan_result +{ + uint16_t arfcn; + float rssi; +}; + +static int scan_cmp(const void *arg1, const void *arg2) +{ + struct scan_result *elem1 = (struct scan_result *) arg1; + struct scan_result *elem2 = (struct scan_result * )arg2; + + float diff = elem1->rssi - elem2->rssi; + if (diff > 0.0) + return 1; + else if (diff < 0.0) + return -1; + else + return 0; +} + +static int scan_band() +{ + int arfcn, rc, i; + + /* Scan results.. at most 400 items */ + struct scan_result results[400]; + memset(&results, 0, sizeof(results)); + int num_scan_results = 0; + + printf("Going to scan bands.\n"); + + for (arfcn = arfcns[band].min; arfcn <= arfcns[band].max; ++arfcn) { + float mean_rssi; + + printf("."); + fflush(stdout); + rc = power_scan(band, arfcn, 10, &mean_rssi); + CHECK_RC_MSG(rc, "Power Measurement failed"); + + results[num_scan_results].arfcn = arfcn; + results[num_scan_results].rssi = mean_rssi; + num_scan_results++; + } + + qsort(results, num_scan_results, sizeof(struct scan_result), scan_cmp); + printf("\nSorted scan results (weakest first):\n"); + for (i = 0; i < num_scan_results; ++i) + printf("ARFCN %3d: %.4f\n", results[i].arfcn, results[i].rssi); + + return 0; +} + +static int calib_get_clock_error(void) +{ + int rc, clkErr, clkErrRes; + + printf("Going to determine the clock offset.\n"); + + rc = rf_clock_info(&clkErr, &clkErrRes); + CHECK_RC_MSG(rc, "Clock info failed.\n"); + + if (clkErr == 0 && clkErrRes == 0) { + printf("Failed to get the clock info. Are both clocks present?\n"); + return -1; + } + + /* + * Empiric gps error determination. With revE and firmware v3.3 + * the clock error for TCXO to GPS appears to have a different + * sign. The device in question doesn't have a networklisten mode + * so it is impossible to verify that this only applies to GPS. + */ + if (source == SuperFemto_ClkSrcId_GpsPps) + clkErr *= -1; + + + /* this is an absolute clock error */ + printf("The calibration value is: %d\n", clkErr); + return 0; +} + +static int calib_clock_after_sync(void) +{ + int rc, clkErr, clkErrRes, iteration, cor; + + iteration = 0; + cor = initial_cor; + + printf("Trying to calibrate now and reducing clock error.\n"); + + for (iteration = 0; iteration < steps || steps <= 0; ++iteration) { + if (steps > 0) + printf("Iteration %d/%d with correction: %d\n", iteration, steps, cor); + else + printf("Iteration %d with correction: %d\n", iteration, cor); + + rc = rf_clock_info(&clkErr, &clkErrRes); + CHECK_RC_MSG(rc, "Clock info failed.\n"); + + /* + * TODO: use the clock error resolution here, implement it as a + * a PID controller.. + */ + + /* Picocell class requires 0.1ppm.. but that is 'too easy' */ + if (fabs(clkErr / 1000.0f) <= 0.05f) { + printf("The calibration value is: %d\n", cor); + return 1; + } + + cor -= clkErr / 2; + rc = set_clock_cor(cor, calib, source); + CHECK_RC_MSG(rc, "Clock correction failed.\n"); + } + + return -1; +} + +static int find_initial_clock(HANDLE layer1, int *clock) +{ + int i; + + printf("Trying to find an initial clock value.\n"); + + for (i = 0; i < 1000; ++i) { + int rc; + int cor = i * 150; + rc = wait_for_sync(layer1, cor, calib, source); + if (rc == 1) { + printf("Found initial clock offset: %d\n", cor); + *clock = cor; + break; + } else { + CHECK_RC_MSG(rc, "Failed to set new clock value.\n"); + } + + cor = i * -150; + rc = wait_for_sync(layer1, cor, calib, source); + if (rc == 1) { + printf("Found initial clock offset: %d\n", cor); + *clock = cor; + break; + } else { + CHECK_RC_MSG(rc, "Failed to set new clock value.\n"); + } + } + + return 0; +} + +static int calib_clock_netlisten(void) +{ + int rc, cor = initial_cor; + float mean_rssi; + HANDLE layer1; + + rc = power_scan(band, cal_arfcn, 10, &mean_rssi); + CHECK_RC_MSG(rc, "ARFCN measurement scan failed"); + if (mean_rssi < -118.0f) + printf("ARFCN has weak signal for calibration: %f\n", mean_rssi); + + /* initial lock */ + rc = follow_sch(band, cal_arfcn, calib, source, &layer1); + if (rc == -23) + rc = find_initial_clock(layer1, &cor); + CHECK_RC_MSG(rc, "Following SCH failed"); + + /* now try to calibrate it */ + rc = set_clock_cor(cor, calib, source); + CHECK_RC_MSG(rc, "Clock setup failed."); + + calib_clock_after_sync(); + + rc = mph_close(layer1); + CHECK_RC_MSG(rc, "MPH-Close"); + + return EXIT_SUCCESS; +} + +static int calib_clock(void) +{ + int rc; + + /* now try to calibrate it */ + rc = set_clock_cor(initial_cor, calib, source); + CHECK_RC_MSG(rc, "Clock setup failed."); + + calib_get_clock_error(); + + return EXIT_SUCCESS; +} + +static int bcch_follow(void) +{ + int rc, cor = initial_cor; + float mean_rssi; + HANDLE layer1; + + rc = power_scan(band, cal_arfcn, 10, &mean_rssi); + CHECK_RC_MSG(rc, "ARFCN measurement scan failed"); + if (mean_rssi < -118.0f) + printf("ARFCN has weak signal for calibration: %f\n", mean_rssi); + + /* initial lock */ + rc = follow_sch(band, cal_arfcn, calib, source, &layer1); + if (rc == -23) + rc = find_initial_clock(layer1, &cor); + CHECK_RC_MSG(rc, "Following SCH failed"); + + /* identify the BSIC and set it as TSC */ + rc = find_bsic(); + CHECK_COND_MSG(rc < 0, rc, "Identifying the BSIC failed"); + rc = set_tsc_from_bsic(layer1, rc); + CHECK_RC_MSG(rc, "Setting the TSC failed"); + + + /* follow the bcch */ + rc = follow_bcch(layer1); + CHECK_RC_MSG(rc, "Follow BCCH"); + + /* follow the pch */ + if (action == ACTION_BCCH_CCCH) { + rc = follow_pch(layer1); + CHECK_RC_MSG(rc, "Follow BCCH/CCCH"); + } + + /* now wait for the PhDataInd */ + for (;;) { + uint32_t fn; + uint8_t block; + uint8_t data[23]; + size_t size; + struct gsm_time gsmtime; + GsmL1_Sapi_t sapi; + + rc = wait_for_data(data, &size, &fn, &block, &sapi); + if (rc == 1) + continue; + CHECK_RC_MSG(rc, "No Data Indication"); + + gsm_fn2gsmtime(&gsmtime, fn); + printf("%02u/%02u/%02u %6s %s\n", + gsmtime.t1, gsmtime.t2, gsmtime.t3, + get_value_string(sapi_names, sapi), + osmo_hexdump(data, size)); + } + + rc = mph_close(layer1); + CHECK_RC_MSG(rc, "MPH-Close"); + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) +{ + int rc; + + handle_options(argc, argv); + printf("Initializing the Layer1\n"); + rc = initialize_layer1(dsp_flags); + CHECK_RC(rc); + + printf("Fetching system info.\n"); + rc = print_system_info(); + CHECK_RC(rc); + + printf("Opening RF frontend with clock(%d) and correction(%d)\n", + calib, initial_cor); + rc = activate_rf_frontend(calib, initial_cor); + CHECK_RC(rc); + + if (action == ACTION_SCAN) + return scan_band(); + else if (action == ACTION_BCCH || action == ACTION_BCCH_CCCH) + return bcch_follow(); + else { + if (source == SuperFemto_ClkSrcId_NetList) + return calib_clock_netlisten(); + return calib_clock(); + } + + return EXIT_SUCCESS; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c new file mode 100644 index 00000000..4b34f50e --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c @@ -0,0 +1,800 @@ +/* Layer1 handling for the DSP/FPGA */ +/* + * (C) 2012-2013 Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sysmobts-layer1.h" + +#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof((ar)[0])) + +#define BTS_DSP2ARM "/dev/msgq/superfemto_dsp2arm" +#define BTS_ARM2DSP "/dev/msgq/superfemto_arm2dsp" +#define L1_SIG_ARM2DSP "/dev/msgq/gsml1_sig_arm2dsp" +#define L1_SIG_DSP2ARM "/dev/msgq/gsml1_sig_dsp2arm" + +int set_clock_cor(int clock_cor, int calib, int source); +static int wait_read_ignore(int seconds); + +static int sys_dsp2arm = -1, + sys_arm2dsp = -1, + sig_dsp2arm = -1, + sig_arm2dsp = -1; + +static int sync_indicated = 0; +static int time_indicated = 0; + +static int open_devices() +{ + sys_dsp2arm = open(BTS_DSP2ARM, O_RDONLY); + if (sys_dsp2arm == -1) { + perror("Failed to open dsp2arm system queue"); + return -1; + } + + sys_arm2dsp = open(BTS_ARM2DSP, O_WRONLY); + if (sys_arm2dsp == -1) { + perror("Failed to open arm2dsp system queue"); + return -2; + } + + sig_dsp2arm = open(L1_SIG_DSP2ARM, O_RDONLY); + if (sig_dsp2arm == -1) { + perror("Failed to open dsp2arm sig queue"); + return -3; + } + + sig_arm2dsp = open(L1_SIG_ARM2DSP, O_WRONLY); + if (sig_arm2dsp == -1) { + perror("Failed to open arm2dsp sig queue"); + return -4; + } + + return 0; +} + +/** + * Send a primitive to the system queue + */ +static int send_primitive(int primitive, SuperFemto_Prim_t *prim) +{ + prim->id = primitive; + return write(sys_arm2dsp, prim, sizeof(*prim)) != sizeof(*prim); +} + +/** + * Wait for a confirmation + */ +static int wait_primitive(int wait_for, SuperFemto_Prim_t *prim) +{ + memset(prim, 0, sizeof(*prim)); + int rc = read(sys_dsp2arm, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Short read in %s: %d\n", __func__, rc); + return -1; + } + + if (prim->id != wait_for) { + printf("Got primitive %d but waited for %d\n", + prim->id, wait_for); + return -2; + } + + return 0; +} + +/* The Cnf for the Req, assume it is a +1 */ +static int answer_for(int primitive) +{ + return primitive + 1; +} + +static int send_recv_primitive(int p, SuperFemto_Prim_t *prim) +{ + int rc; + rc = send_primitive(p, prim); + if (rc != 0) + return -1; + + rc = wait_primitive(answer_for(p), prim); + if (rc != 0) + return -2; + return 0; +} + +static int answer_for_sig(int prim) +{ + static const GsmL1_PrimId_t cnf[] = { + [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf, + [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf, + [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf, + [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf, + [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf, + [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf, + }; + + if (prim < 0 || prim >= ARRAY_SIZE(cnf)) { + printf("Unknown primitive: %d\n", prim); + exit(-3); + } + + return cnf[prim]; +} + +static int is_indication(int prim) +{ + return + prim == GsmL1_PrimId_MphTimeInd || + prim == GsmL1_PrimId_MphSyncInd || + prim == GsmL1_PrimId_PhConnectInd || + prim == GsmL1_PrimId_PhReadyToSendInd || + prim == GsmL1_PrimId_PhDataInd || + prim == GsmL1_PrimId_PhRaInd; +} + + +static int send_recv_sig_prim(int p, GsmL1_Prim_t *prim) +{ + int rc; + prim->id = p; + rc = write(sig_arm2dsp, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Failed to write: %d\n", rc); + return -1; + } + + do { + rc = read(sig_dsp2arm, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Failed to read: %d\n", rc); + return -2; + } + } while (is_indication(prim->id)); + + if (prim->id != answer_for_sig(p)) { + printf("Wrong L1 result got %d wanted %d for prim: %d\n", + prim->id, answer_for_sig(p), p); + return -3; + } + + return 0; +} + +static int wait_for_indication(int p, GsmL1_Prim_t *prim) +{ + int rc; + memset(prim, 0, sizeof(*prim)); + + struct timespec start_time, now_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + + /* + * TODO: select.... with timeout. The below will work 99% as we will + * get time indications very soonish after the connect + */ + for (;;) { + clock_gettime(CLOCK_MONOTONIC, &now_time); + if (now_time.tv_sec - start_time.tv_sec > 10) { + printf("Timeout waiting for indication.\n"); + return -4; + } + + rc = read(sig_dsp2arm, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Failed to read.\n"); + return -1; + } + + if (!is_indication(prim->id)) { + printf("No indication: %d\n", prim->id); + return -2; + } + + if (p != prim->id && prim->id == GsmL1_PrimId_MphSyncInd) { + printf("Got sync.\n"); + sync_indicated = 1; + continue; + } + if (p != prim->id && prim->id == GsmL1_PrimId_MphTimeInd) { + time_indicated = 1; + continue; + } + + if (p != prim->id) { + printf("Wrong indication got %d wanted %d\n", + prim->id, p); + return -3; + } + + break; + } + + return 0; +} + +static int set_trace_flags(uint32_t dsp) +{ + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.setTraceFlagsReq.u32Tf = dsp; + return send_primitive(SuperFemto_PrimId_SetTraceFlagsReq, &prim); +} + +static int reset_and_wait() +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + rc = send_recv_primitive(SuperFemto_PrimId_Layer1ResetReq, &prim); + if (rc != 0) + return -1; + if (prim.u.layer1ResetCnf.status != GsmL1_Status_Success) + return -2; + return 0; +} + +/** + * Open the message queues and (re-)initialize the DSP and FPGA + */ +int initialize_layer1(uint32_t dsp_flags) +{ + if (open_devices() != 0) { + printf("Failed to open devices.\n"); + return -1; + } + + if (set_trace_flags(dsp_flags) != 0) { + printf("Failed to set dsp flags.\n"); + return -2; + } + if (reset_and_wait() != 0) { + printf("Failed to reset the firmware.\n"); + return -3; + } + return 0; +} + +/** + * Print systems infos + */ +int print_system_info() +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + rc = send_recv_primitive(SuperFemto_PrimId_SystemInfoReq, &prim); + if (rc != 0) { + printf("Failed to send SystemInfoRequest.\n"); + return -1; + } + + if (prim.u.systemInfoCnf.status != GsmL1_Status_Success) { + printf("Failed to request SystemInfoRequest.\n"); + return -2; + } + +#define INFO_DSP(x) x.u.systemInfoCnf.dspVersion +#define INFO_FPGA(x) x.u.systemInfoCnf.fpgaVersion +#ifdef FEMTOBTS_NO_BOARD_VERSION +#define BOARD_REV(x) -1 +#define BOARD_OPT(x) -1 +#define COMPILED_MAJOR (FEMTOBTS_API_VERSION >> 16) +#define COMPILED_MINOR ((FEMTOBTS_API_VERSION >> 8) & 0xff) +#define COMPILED_BUILD (FEMTOBTS_API_VERSION & 0xff) +#else +#define BOARD_REV(x) x.u.systemInfoCnf.boardVersion.rev +#define BOARD_OPT(x) x.u.systemInfoCnf.boardVersion.option +#define COMPILED_MAJOR (SUPERFEMTO_API_VERSION >> 16) +#define COMPILED_MINOR ((SUPERFEMTO_API_VERSION >> 8) & 0xff) +#define COMPILED_BUILD (SUPERFEMTO_API_VERSION & 0xff) +#endif + + printf("Compiled against: v%u.%u.%u\n", + COMPILED_MAJOR, COMPILED_MINOR, COMPILED_BUILD); + printf("Running DSP v%d.%d.%d FPGA v%d.%d.%d Rev: %d Option: %d\n", + INFO_DSP(prim).major, INFO_DSP(prim).minor, INFO_DSP(prim).build, + INFO_FPGA(prim).major, INFO_FPGA(prim).minor, INFO_FPGA(prim).build, + BOARD_REV(prim), BOARD_OPT(prim)); + + if (COMPILED_MAJOR != INFO_DSP(prim).major || COMPILED_MINOR != INFO_DSP(prim).minor) { + printf("WARNING! WARNING! WARNING! WARNING! WARNING\n"); + printf("You might run this against an incompatible firmware.\n"); + printf("Continuing anyway but the result might be broken\n"); + } +#undef INFO_DSP +#undef INFO_FPGA +#undef BOARD_REV +#undef BOARD_OPT +#undef COMPILED_MAJOR +#undef COMPILED_MINOR +#undef COMPILED_BUILD + return 0; +} + +int activate_rf_frontend(int clock_source, int initial_cor) +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.activateRfReq.timing.u8TimSrc = 1; + prim.u.activateRfReq.msgq.u8UseTchMsgq = 0; + prim.u.activateRfReq.msgq.u8UsePdtchMsgq = 0; + + prim.u.activateRfReq.rfTrx.iClkCor = initial_cor; + prim.u.activateRfReq.rfTrx.clkSrc = clock_source; +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) + prim.u.activateRfReq.rfRx.iClkCor = initial_cor; + prim.u.activateRfReq.rfRx.clkSrc = clock_source; +#endif + + rc = send_recv_primitive(SuperFemto_PrimId_ActivateRfReq, &prim); + return rc; +} + +static int mph_init(int band, int arfcn, HANDLE *layer1) +{ + int rc; + GsmL1_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.mphInitReq.deviceParam.devType = GsmL1_DevType_Rxd; + prim.u.mphInitReq.deviceParam.freqBand = band; + prim.u.mphInitReq.deviceParam.u16Arfcn = arfcn; + prim.u.mphInitReq.deviceParam.u16BcchArfcn = arfcn; + prim.u.mphInitReq.deviceParam.fRxPowerLevel = -75.f; + prim.u.mphInitReq.deviceParam.u8AutoTA = 1; + + rc = send_recv_sig_prim(GsmL1_PrimId_MphInitReq, &prim); + if (rc != 0) { + printf("Failed to initialize the physical channel.\n"); + return -1; + } + + if (prim.u.mphInitCnf.status != GsmL1_Status_Success) { + printf("MPH Init failed.\n"); + return -2; + } + +#if 0 + if (prim.u.mphInitCnf.freqBand != band) { + printf("Layer1 ignored the band: %d\n", + prim.u.mphInitCnf.freqBand); + return -3; + } +#endif + + *layer1 = prim.u.mphInitCnf.hLayer1; + return 0; +} + +int mph_close(HANDLE layer1) +{ + int rc; + GsmL1_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.mphCloseReq.hLayer1 = layer1; + rc = send_recv_sig_prim(GsmL1_PrimId_MphCloseReq, &prim); + if (rc != 0) { + printf("Failed to close the MPH\n"); + return -6; + } + if (prim.u.mphCloseCnf.status != GsmL1_Status_Success) { + printf("MPH Close failed.\n"); + return -7; + } + + return 0; +} + +int follow_sch(int band, int arfcn, int clock, int ref, HANDLE *layer1) +{ + int rc; + GsmL1_Prim_t prim; + + time_indicated = 0; + sync_indicated = 0; + + rc = mph_init(band, arfcn, layer1); + if (rc != 0) + return rc; + + /* 1.) Connect */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphConnectReq.hLayer1 = *layer1; + prim.u.mphConnectReq.u8Tn = 0; + prim.u.mphConnectReq.logChComb = GsmL1_LogChComb_IV; + rc = send_recv_sig_prim(GsmL1_PrimId_MphConnectReq, &prim); + if (rc != 0) { + printf("Failed to connect.\n"); + return -1; + } + if (prim.u.mphConnectCnf.status != GsmL1_Status_Success) { + printf("Connect failed.\n"); + return -2; + } + if (prim.u.mphConnectCnf.u8Tn != 0) { + printf("Wrong timeslot.\n"); + return -3; + } + + /* 2.) Activate */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphActivateReq.hLayer1 = *layer1; + prim.u.mphActivateReq.u8Tn = 0; + prim.u.mphActivateReq.sapi = GsmL1_Sapi_Sch; + prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink; + rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim); + if (rc != 0) { + printf("Activation failed.\n"); + return -4; + } + if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) { + printf("Activation not successful.\n"); + return -5; + } + + /* 3.) Wait for indication... TODO: check... */ + printf("Waiting for connect indication.\n"); + rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim); + if (rc != 0) { + printf("Didn't get a connect indication.\n"); + return rc; + } + + /* 4.) Indication Syndication TODO: check... */ + if (!sync_indicated) { + printf("Waiting for sync indication.\n"); + rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim); + if (rc < 0) { + printf("Didn't get a sync indication.\n"); + return -23; + } else if (rc == 0) { + if (!prim.u.mphSyncInd.u8Synced) { + printf("Failed to get sync.\n"); + return -23; + } else { + printf("Synced.\n"); + } + } + } else { + printf("Already synced.\n"); + } + + return 0; +} + +static int follow_sapi(HANDLE layer1, const GsmL1_Sapi_t sapi) +{ + int rc; + GsmL1_Prim_t prim; + + /* 1.) Activate BCCH or such... */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphActivateReq.hLayer1 = layer1; + prim.u.mphActivateReq.u8Tn = 0; + prim.u.mphActivateReq.sapi = sapi; + prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink; + + rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim); + if (rc != 0) { + printf("Activation failed.\n"); + return -4; + } + if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) { + printf("Activation not successful.\n"); + return -5; + } + + /* 2.) Wait for indication... */ + printf("Waiting for connect indication.\n"); + rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim); + if (rc != 0) { + printf("Didn't get a connect indication.\n"); + return rc; + } + + if (prim.u.phConnectInd.sapi != sapi) { + printf("Got a connect indication for the wrong type: %d\n", + prim.u.phConnectInd.sapi); + return -6; + } + + /* 3.) Wait for PhDataInd... */ + printf("Waiting for data.\n"); + rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); + if (rc != 0) { + printf("Didn't get data.\n"); + return rc; + } + + return 0; +} + +int follow_bcch(HANDLE layer1) +{ + return follow_sapi(layer1, GsmL1_Sapi_Bcch); +} + +int follow_pch(HANDLE layer1) +{ + return follow_sapi(layer1, GsmL1_Sapi_Pch); +} + +int find_bsic(void) +{ + int rc, i; + GsmL1_Prim_t prim; + + printf("Waiting for SCH data.\n"); + for (i = 0; i < 10; ++i) { + uint8_t bsic; + rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); + if (rc < 0) { + printf("Didn't get SCH data.\n"); + return rc; + } + if (prim.u.phDataInd.sapi != GsmL1_Sapi_Sch) + continue; + + bsic = (prim.u.phDataInd.msgUnitParam.u8Buffer[0] >> 2) & 0xFF; + return bsic; + } + + printf("Giving up finding the SCH\n"); + return -1; +} + +int set_tsc_from_bsic(HANDLE layer1, int bsic) +{ + int rc; + int tsc = bsic & 0x7; + GsmL1_Prim_t prim; + + memset(&prim, 0, sizeof(prim)); + prim.u.mphConfigReq.hLayer3 = 0x23; + prim.u.mphConfigReq.hLayer1 = layer1; + prim.u.mphConfigReq.cfgParamId = GsmL1_ConfigParamId_SetNbTsc; + prim.u.mphConfigReq.cfgParams.setNbTsc.u8NbTsc = tsc; + rc = send_recv_sig_prim(GsmL1_PrimId_MphConfigReq, &prim); + if (rc != 0) { + printf("Failed to send configure.\n"); + } + + if (prim.u.mphConfigCnf.status != GsmL1_Status_Success) { + printf("Failed to set the config cnf.\n"); + return -1; + } + + return 0; +} + +int set_clock_cor(int clock_cor, int calib, int source) +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.rfClockSetupReq.rfTrx.iClkCor = clock_cor; + prim.u.rfClockSetupReq.rfTrx.clkSrc = calib; +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) + prim.u.rfClockSetupReq.rfRx.iClkCor = clock_cor; + prim.u.rfClockSetupReq.rfRx.clkSrc = calib; +#endif + prim.u.rfClockSetupReq.rfTrxClkCal.clkSrc = source; + + rc = send_recv_primitive(SuperFemto_PrimId_RfClockSetupReq, &prim); + if (rc != 0) { + printf("Failed to set the clock setup.\n"); + return -1; + } + if (prim.u.rfClockSetupCnf.status != GsmL1_Status_Success) { + printf("Clock setup was not successfull.\n"); + return -2; + } + + return 0; +} + +int rf_clock_info(int *clkErr, int *clkErrRes) +{ + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + int rc; + + /* reset the counter */ + prim.u.rfClockInfoReq.u8RstClkCal = 1; + rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim); + if (rc != 0) { + printf("Failed to reset the clock info.\n"); + return -1; + } + + /* wait for a value */ + wait_read_ignore(15); + + /* ask for the current counter/error */ + memset(&prim, 0, sizeof(prim)); + prim.u.rfClockInfoReq.u8RstClkCal = 0; + rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim); + if (rc != 0) { + printf("Failed to get the clock info.\n"); + return -2; + } + + printf("Error: %d Res: %d\n", + prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr, + prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes); + *clkErr = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr; + *clkErrRes = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes; + return 0; +} + +int power_scan(int band, int arfcn, int duration, float *mean_rssi) +{ + int rc; + HANDLE layer1; + GsmL1_Prim_t prim; + + /* init */ + rc = mph_init(band, arfcn, &layer1); + if (rc != 0) + return rc; + + /* mph measure request */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphMeasureReq.hLayer1 = layer1; + prim.u.mphMeasureReq.u32Duration = duration; + rc = send_recv_sig_prim(GsmL1_PrimId_MphMeasureReq, &prim); + if (rc != 0) { + printf("Failed to send measurement request.\n"); + return -4; + } + + if (prim.u.mphMeasureCnf.status != GsmL1_Status_Success) { + printf("MphMeasureReq was not confirmed.\n"); + return -5; + } + + *mean_rssi = prim.u.mphMeasureCnf.fMeanRssi; + + /* close */ + rc = mph_close(layer1); + return rc; +} + +/** + * Wait for indication... + */ +int wait_for_sync(HANDLE layer1, int cor, int calib, int source) +{ + GsmL1_Prim_t prim; + int rc; + + rc = set_clock_cor(cor, calib, source); + if (rc != 0) { + printf("Failed to set the clock correction.\n"); + return -1; + } + + sync_indicated = 0; + rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim); + if (rc < 0 && rc != -4) { + return rc; + } else if (rc == 0) { + if (!prim.u.mphSyncInd.u8Synced) { + printf("Failed to get sync.\n"); + return 0; + } + printf("Synced.\n"); + return 1; + } + + return 0; +} + +int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sap) +{ + GsmL1_Prim_t prim; + int rc; + + rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); + if (rc < 0) + return rc; + if (prim.u.phDataInd.sapi == GsmL1_Sapi_Sch) + return 1; + + *size = prim.u.phDataInd.msgUnitParam.u8Size; + *fn = prim.u.phDataInd.u32Fn; + *block = prim.u.phDataInd.u8BlockNbr; + *sap = prim.u.phDataInd.sapi; + memcpy(data, prim.u.phDataInd.msgUnitParam.u8Buffer, *size); + return 0; +} + +/** + * Make sure the pipe is not running full. + * + */ +static int wait_read_ignore(int seconds) +{ + int max, rc; + fd_set fds; + struct timeval timeout; + + max = sys_dsp2arm > sig_dsp2arm ? sys_dsp2arm : sig_dsp2arm; + + timeout.tv_sec = seconds; + timeout.tv_usec = 0; + + while (1) { + FD_ZERO(&fds); + FD_SET(sys_dsp2arm, &fds); + FD_SET(sig_dsp2arm, &fds); + + + rc = select(max + 1, &fds, NULL, NULL, &timeout); + if (rc == -1) { + printf("Failed to select.\n"); + return -1; + } else if (rc) { + if (FD_ISSET(sys_dsp2arm, &fds)) { + SuperFemto_Prim_t prim; + rc = read(sys_dsp2arm, &prim, sizeof(prim)); + if (rc != sizeof(prim)) { + perror("Failed to read system primitive"); + return -2; + } + } + if (FD_ISSET(sig_dsp2arm, &fds)) { + GsmL1_Prim_t prim; + rc = read(sig_dsp2arm, &prim, sizeof(prim)); + if (rc != sizeof(prim)) { + perror("Failed to read signal primitiven"); + return -3; + } + } + } else if (timeout.tv_sec <= 0 && timeout.tv_usec <= 0) { + break; + } + +#ifndef __linux__ +#error "Non portable code" +#endif + } + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.h b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h new file mode 100644 index 00000000..e7d59c94 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h @@ -0,0 +1,45 @@ +#ifndef SYSMOBTS_LAYER_H +#define SYSMOBTS_LAYER_H + +#include + +#ifdef FEMTOBTS_API_VERSION +#define SuperFemto_PrimId_t FemtoBts_PrimId_t +#define SuperFemto_Prim_t FemtoBts_Prim_t +#define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq +#define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf +#define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t +#define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd +#define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq +#define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf +#define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq +#define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf +#define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq +#define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq +#define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf +#define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq +#define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf +#define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq +#define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf +#define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM +#define HW_SYSMOBTS_V1 1 +#define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z) +#endif + +extern int initialize_layer1(uint32_t dsp_flags); +extern int print_system_info(); +extern int activate_rf_frontend(int clock_source, int clock_cor); +extern int power_scan(int band, int arfcn, int duration, float *mean_rssi); +extern int follow_sch(int band, int arfcn, int calib, int reference, HANDLE *layer1); +extern int follow_bch(HANDLE layer1); +extern int find_bsic(void); +extern int set_tsc_from_bsic(HANDLE layer1, int bsic); +extern int set_clock_cor(int clock_corr, int calib, int source); +extern int rf_clock_info(int *clkErr, int *clkErrRes); +extern int mph_close(HANDLE layer1); +extern int wait_for_sync(HANDLE layer1, int cor, int calib, int source); +extern int follow_bcch(HANDLE layer1); +extern int follow_pch(HANDLE layer1); +extern int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sapi); + +#endif -- cgit v1.2.3