| /* |
| * OsmocomBB <-> SDR connection bridge |
| * Transceiver interface handlers |
| * |
| * Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> |
| * Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> |
| * |
| * 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <netinet/in.h> |
| |
| #include <sys/eventfd.h> |
| |
| #include <osmocom/core/logging.h> |
| #include <osmocom/core/select.h> |
| #include <osmocom/core/socket.h> |
| #include <osmocom/core/talloc.h> |
| #include <osmocom/core/bits.h> |
| #include <osmocom/core/fsm.h> |
| |
| #include <osmocom/gsm/gsm_utils.h> |
| |
| #include "l1ctl.h" |
| #include "trxcon.h" |
| #include "trx_if.h" |
| #include "logging.h" |
| #include "scheduler.h" |
| |
| #ifdef IPCIF |
| #include "../Transceiver52M/l1if.h" |
| #endif |
| |
| static struct value_string trx_evt_names[] = { |
| { 0, NULL } /* no events? */ |
| }; |
| |
| static struct osmo_fsm_state trx_fsm_states[] = { |
| [TRX_STATE_OFFLINE] = { |
| .out_state_mask = ( |
| GEN_MASK(TRX_STATE_IDLE) | |
| GEN_MASK(TRX_STATE_RSP_WAIT)), |
| .name = "OFFLINE", |
| }, |
| [TRX_STATE_IDLE] = { |
| .out_state_mask = UINT32_MAX, |
| .name = "IDLE", |
| }, |
| [TRX_STATE_ACTIVE] = { |
| .out_state_mask = ( |
| GEN_MASK(TRX_STATE_IDLE) | |
| GEN_MASK(TRX_STATE_RSP_WAIT)), |
| .name = "ACTIVE", |
| }, |
| [TRX_STATE_RSP_WAIT] = { |
| .out_state_mask = ( |
| GEN_MASK(TRX_STATE_IDLE) | |
| GEN_MASK(TRX_STATE_ACTIVE) | |
| GEN_MASK(TRX_STATE_OFFLINE)), |
| .name = "RSP_WAIT", |
| }, |
| }; |
| |
| static struct osmo_fsm trx_fsm = { |
| .name = "trx_interface_fsm", |
| .states = trx_fsm_states, |
| .num_states = ARRAY_SIZE(trx_fsm_states), |
| .log_subsys = DTRX, |
| .event_names = trx_evt_names, |
| }; |
| |
| static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local, |
| uint16_t port_local, const char *host_remote, uint16_t port_remote, |
| int (*cb)(struct osmo_fd *fd, unsigned int what)) |
| { |
| int rc; |
| |
| ofd->data = priv; |
| ofd->fd = -1; |
| ofd->cb = cb; |
| |
| /* Init UDP Connection */ |
| rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local, |
| host_remote, port_remote, |
| OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); |
| return rc; |
| } |
| |
| static void trx_udp_close(struct osmo_fd *ofd) |
| { |
| if (ofd->fd > 0) { |
| osmo_fd_unregister(ofd); |
| close(ofd->fd); |
| ofd->fd = -1; |
| } |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Control (CTRL) interface handlers */ |
| /* ------------------------------------------------------------------------ */ |
| /* Commands on the Per-ARFCN Control Interface */ |
| /* */ |
| /* The per-ARFCN control interface uses a command-response protocol. */ |
| /* Commands are NULL-terminated ASCII strings, one per UDP socket. */ |
| /* Each command has a corresponding response. */ |
| /* Every command is of the form: */ |
| /* */ |
| /* CMD <cmdtype> [params] */ |
| /* */ |
| /* The <cmdtype> is the actual command. */ |
| /* Parameters are optional depending on the commands type. */ |
| /* Every response is of the form: */ |
| /* */ |
| /* RSP <cmdtype> <status> [result] */ |
| /* */ |
| /* The <status> is 0 for success and a non-zero error code for failure. */ |
| /* Successful responses may include results, depending on the command type. */ |
| /* ------------------------------------------------------------------------ */ |
| |
| static void trx_ctrl_timer_cb(void *data); |
| |
| /* Send first CTRL message and start timer */ |
| static void trx_ctrl_send(struct trx_instance *trx) |
| { |
| struct trx_ctrl_msg *tcm; |
| |
| if (llist_empty(&trx->trx_ctrl_list)) |
| return; |
| tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list); |
| |
| #ifdef IPCIF |
| char* cmd = malloc(TRXC_BUF_SIZE); |
| memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE); |
| |
| /* Send command */ |
| LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd); |
| trxif_to_trx_c(cmd); |
| |
| #else |
| /* Send command */ |
| LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd); |
| send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0); |
| #endif |
| |
| /* Trigger state machine */ |
| if (trx->fsm->state != TRX_STATE_RSP_WAIT) { |
| trx->prev_state = trx->fsm->state; |
| osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0); |
| } |
| |
| /* Start expire timer */ |
| trx->trx_ctrl_timer.data = trx; |
| trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb; |
| osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0); |
| } |
| |
| static void trx_ctrl_timer_cb(void *data) |
| { |
| struct trx_instance *trx = (struct trx_instance *) data; |
| struct trx_ctrl_msg *tcm; |
| |
| /* Queue may be cleaned at this moment */ |
| if (llist_empty(&trx->trx_ctrl_list)) |
| return; |
| |
| LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n"); |
| |
| tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list); |
| if (++tcm->retry_cnt > 3) { |
| LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n"); |
| osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0); |
| osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx); |
| return; |
| } |
| |
| /* Attempt to send a command again */ |
| trx_ctrl_send(trx); |
| } |
| |
| /* Add a new CTRL command to the trx_ctrl_list */ |
| static int trx_ctrl_cmd(struct trx_instance *trx, int critical, |
| const char *cmd, const char *fmt, ...) |
| { |
| struct trx_ctrl_msg *tcm; |
| int len, pending = 0; |
| va_list ap; |
| |
| /* TODO: make sure that transceiver online */ |
| |
| if (!llist_empty(&trx->trx_ctrl_list)) |
| pending = 1; |
| |
| /* Allocate a message */ |
| tcm = talloc_zero(trx, struct trx_ctrl_msg); |
| if (!tcm) |
| return -ENOMEM; |
| |
| /* Fill in command arguments */ |
| if (fmt && fmt[0]) { |
| len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd); |
| va_start(ap, fmt); |
| vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap); |
| va_end(ap); |
| } else { |
| snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd); |
| } |
| |
| tcm->cmd_len = strlen(cmd); |
| tcm->critical = critical; |
| llist_add_tail(&tcm->list, &trx->trx_ctrl_list); |
| LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd); |
| |
| /* Send message, if no pending messages */ |
| if (!pending) |
| trx_ctrl_send(trx); |
| |
| return 0; |
| } |
| |
| /* |
| * Power Control |
| * |
| * ECHO is used to check transceiver availability. |
| * CMD ECHO |
| * RSP ECHO <status> |
| * |
| * POWEROFF shuts off transmitter power and stops the demodulator. |
| * CMD POWEROFF |
| * RSP POWEROFF <status> |
| * |
| * POWERON starts the transmitter and starts the demodulator. |
| * Initial power level is very low. |
| * This command fails if the transmitter and receiver are not yet tuned. |
| * This command fails if the transmit or receive frequency creates a conflict |
| * with another ARFCN that is already running. |
| * If the transceiver is already on, it response with success to this command. |
| * CMD POWERON |
| * RSP POWERON <status> |
| */ |
| |
| int trx_if_cmd_sync(struct trx_instance *trx) |
| { |
| return trx_ctrl_cmd(trx, 1, "SYNC", ""); |
| } |
| |
| int trx_if_cmd_echo(struct trx_instance *trx) |
| { |
| return trx_ctrl_cmd(trx, 1, "ECHO", ""); |
| } |
| |
| int trx_if_cmd_poweroff(struct trx_instance *trx) |
| { |
| return trx_ctrl_cmd(trx, 1, "POWEROFF", ""); |
| } |
| |
| int trx_if_cmd_poweron(struct trx_instance *trx) |
| { |
| if (trx->powered_up) { |
| /* FIXME: this should be handled by the FSM, not here! */ |
| LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n"); |
| return -EAGAIN; |
| } |
| return trx_ctrl_cmd(trx, 1, "POWERON", ""); |
| } |
| |
| /* |
| * Timeslot Control |
| * |
| * SETSLOT sets the format of the uplink timeslots in the ARFCN. |
| * The <timeslot> indicates the timeslot of interest. |
| * The <chantype> indicates the type of channel that occupies the timeslot. |
| * A chantype of zero indicates the timeslot is off. |
| * CMD SETSLOT <timeslot> <chantype> |
| * RSP SETSLOT <status> <timeslot> <chantype> |
| */ |
| |
| int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type) |
| { |
| return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type); |
| } |
| |
| /* |
| * Tuning Control |
| * |
| * (RX/TX)TUNE tunes the receiver to a given frequency in kHz. |
| * This command fails if the receiver is already running. |
| * (To re-tune you stop the radio, re-tune, and restart.) |
| * This command fails if the transmit or receive frequency |
| * creates a conflict with another ARFCN that is already running. |
| * CMD (RX/TX)TUNE <kHz> |
| * RSP (RX/TX)TUNE <status> <kHz> |
| */ |
| |
| int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn) |
| { |
| uint16_t freq10; |
| |
| /* RX is downlink on MS side */ |
| freq10 = gsm_arfcn2freq10(band_arfcn, 0); |
| if (freq10 == 0xffff) { |
| LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn); |
| return -ENOTSUP; |
| } |
| |
| return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100); |
| } |
| |
| int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn) |
| { |
| uint16_t freq10; |
| |
| /* TX is uplink on MS side */ |
| freq10 = gsm_arfcn2freq10(band_arfcn, 1); |
| if (freq10 == 0xffff) { |
| LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn); |
| return -ENOTSUP; |
| } |
| |
| return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100); |
| } |
| |
| /* |
| * Power measurement |
| * |
| * MEASURE instructs the transceiver to perform a power |
| * measurement on specified frequency. After receiving this |
| * request, transceiver should quickly re-tune to requested |
| * frequency, measure power level and re-tune back to the |
| * previous frequency. |
| * CMD MEASURE <kHz> |
| * RSP MEASURE <status> <kHz> <dB> |
| */ |
| |
| int trx_if_cmd_measure(struct trx_instance *trx, |
| uint16_t band_arfcn_start, uint16_t band_arfcn_stop) |
| { |
| uint16_t freq10; |
| |
| /* Update ARFCN range for measurement */ |
| trx->pm_band_arfcn_start = band_arfcn_start; |
| trx->pm_band_arfcn_stop = band_arfcn_stop; |
| |
| /* Calculate a frequency for current ARFCN (DL) */ |
| freq10 = gsm_arfcn2freq10(band_arfcn_start, 0); |
| if (freq10 == 0xffff) { |
| LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start); |
| return -ENOTSUP; |
| } |
| |
| return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100); |
| } |
| |
| static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp) |
| { |
| unsigned int freq10; |
| uint16_t band_arfcn; |
| int dbm; |
| |
| /* Parse freq. and power level */ |
| sscanf(resp, "%u %d", &freq10, &dbm); |
| freq10 /= 100; |
| |
| /* Check received ARFCN against expected */ |
| band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0); |
| if (band_arfcn != trx->pm_band_arfcn_start) { |
| LOGP(DTRX, LOGL_ERROR, "Power measurement error: " |
| "response ARFCN=%u doesn't match expected ARFCN=%u\n", |
| band_arfcn &~ ARFCN_FLAG_MASK, |
| trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK); |
| return; |
| } |
| |
| /* Send L1CTL_PM_CONF */ |
| l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm, |
| band_arfcn == trx->pm_band_arfcn_stop); |
| |
| /* Schedule a next measurement */ |
| if (band_arfcn != trx->pm_band_arfcn_stop) |
| trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop); |
| } |
| |
| /* |
| * Timing Advance control |
| * |
| * SETTA instructs the transceiver to transmit bursts in |
| * advance calculated from requested TA value. This value is |
| * normally between 0 and 63, with each step representing |
| * an advance of one bit period (about 3.69 microseconds). |
| * Since OsmocomBB has a special feature, which allows one |
| * to spoof the distance from BTS, the range is extended. |
| * CMD SETTA <-128..127> |
| * RSP SETTA <status> <TA> |
| */ |
| |
| int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta) |
| { |
| return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta); |
| } |
| |
| /* |
| * Frequency Hopping parameters indication. |
| * |
| * SETFH instructs transceiver to enable frequency hopping mode |
| * using the given HSN, MAIO, and Mobile Allocation parameters. |
| * |
| * CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>] |
| * |
| * where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz) |
| * corresponding to one ARFCN the Mobile Allocation. Note that the |
| * channel list is expected to be sorted in ascending order. |
| */ |
| |
| int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, |
| uint8_t maio, uint16_t *ma, size_t ma_len) |
| { |
| /* Reserve some room for CMD SETFH <HSN> <MAIO> */ |
| char ma_buf[TRXC_BUF_SIZE - 24]; |
| size_t ma_buf_len = sizeof(ma_buf) - 1; |
| uint16_t rx_freq, tx_freq; |
| char *ptr; |
| int i, rc; |
| |
| /* Make sure that Mobile Allocation has at least one ARFCN */ |
| if (!ma_len || ma == NULL) { |
| LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n"); |
| return -EINVAL; |
| } |
| |
| /* Compose a sequence of Rx/Tx frequencies (mobile allocation) */ |
| for (i = 0, ptr = ma_buf; i < ma_len; i++) { |
| /* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */ |
| rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */ |
| tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */ |
| if (rx_freq == 0xffff || tx_freq == 0xffff) { |
| LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u " |
| "to a pair of Rx/Tx frequencies\n", |
| ma[i] & ~ARFCN_FLAG_MASK); |
| return -EINVAL; |
| } |
| |
| /* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */ |
| rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100); |
| if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */ |
| LOGP(DTRX, LOGL_ERROR, "Not enough room to encode " |
| "Mobile Allocation (N=%zu)\n", ma_len); |
| return -ENOSPC; |
| } |
| |
| /* Move pointer */ |
| ma_buf_len -= rc; |
| ptr += rc; |
| } |
| |
| /* Overwrite the last space */ |
| *(ptr - 1) = '\0'; |
| |
| return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf); |
| } |
| |
| /* Get response from CTRL socket */ |
| static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) |
| { |
| struct trx_instance *trx = ofd->data; |
| struct trx_ctrl_msg *tcm; |
| int resp, rsp_len; |
| char buf[TRXC_BUF_SIZE], *p; |
| ssize_t read_len; |
| |
| #ifdef IPCIF |
| char* response = trxif_from_trx_c(); |
| if (!response) { |
| LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response); |
| goto rsp_error; |
| } |
| memcpy(buf, response, TRXC_BUF_SIZE); |
| free(response); |
| #else |
| read_len = read(ofd->fd, buf, sizeof(buf) - 1); |
| if (read_len <= 0) { |
| LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len); |
| return read_len; |
| } |
| buf[read_len] = '\0'; |
| #endif |
| |
| if (!!strncmp(buf, "RSP ", 4)) { |
| LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf); |
| return 0; |
| } |
| |
| /* Calculate the length of response item */ |
| p = strchr(buf + 4, ' '); |
| rsp_len = p ? p - buf - 4 : strlen(buf) - 4; |
| |
| LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf); |
| |
| /* Abort expire timer */ |
| if (osmo_timer_pending(&trx->trx_ctrl_timer)) |
| osmo_timer_del(&trx->trx_ctrl_timer); |
| |
| /* Get command for response message */ |
| if (llist_empty(&trx->trx_ctrl_list)) { |
| LOGP(DTRX, LOGL_NOTICE, "Response message without command\n"); |
| return -EINVAL; |
| } |
| |
| tcm = llist_entry(trx->trx_ctrl_list.next, |
| struct trx_ctrl_msg, list); |
| |
| /* Check if response matches command */ |
| if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) { |
| LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR, |
| "Response message '%s' does not match command " |
| "message '%s'\n", buf, tcm->cmd); |
| goto rsp_error; |
| } |
| |
| /* Check for response code */ |
| sscanf(p + 1, "%d", &resp); |
| if (resp) { |
| LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR, |
| "Transceiver rejected TRX command with " |
| "response: '%s'\n", buf); |
| |
| if (tcm->critical) |
| goto rsp_error; |
| } |
| |
| /* Trigger state machine */ |
| if (!strncmp(tcm->cmd + 4, "POWERON", 7)) { |
| trx->powered_up = true; |
| osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0); |
| } |
| else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) { |
| trx->powered_up = false; |
| osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0); |
| } |
| else if (!strncmp(tcm->cmd + 4, "MEASURE", 7)) |
| trx_if_measure_rsp_cb(trx, buf + 14); |
| else if (!strncmp(tcm->cmd + 4, "ECHO", 4)) |
| osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0); |
| else |
| osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0); |
| |
| /* Remove command from list */ |
| llist_del(&tcm->list); |
| talloc_free(tcm); |
| |
| /* Send next message, if any */ |
| trx_ctrl_send(trx); |
| |
| return 0; |
| |
| rsp_error: |
| /* Notify higher layers about the problem */ |
| osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx); |
| return -EIO; |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Data interface handlers */ |
| /* ------------------------------------------------------------------------ */ |
| /* DATA interface */ |
| /* */ |
| /* Messages on the data interface carry one radio burst per UDP message. */ |
| /* */ |
| /* Received Data Burst: */ |
| /* 1 byte timeslot index */ |
| /* 4 bytes GSM frame number, BE */ |
| /* 1 byte RSSI in -dBm */ |
| /* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */ |
| /* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */ |
| /* 2 bytes are not used, but being sent by OsmoTRX */ |
| /* */ |
| /* Transmit Data Burst: */ |
| /* 1 byte timeslot index */ |
| /* 4 bytes GSM frame number, BE */ |
| /* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */ |
| /* 148 bytes output symbol values, 0 & 1 */ |
| /* ------------------------------------------------------------------------ */ |
| |
| static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what) |
| { |
| struct trx_instance *trx = ofd->data; |
| struct trx_meas_set meas; |
| uint8_t buf[TRXD_BUF_SIZE]; |
| sbit_t bits[148]; |
| int8_t rssi, tn; |
| int16_t toa256; |
| uint32_t fn; |
| ssize_t read_len; |
| |
| #ifdef IPCIF |
| struct trxd_from_trx* rcvd = trxif_from_trx_d(); |
| if (!rcvd) { |
| LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd); |
| return rcvd; |
| } |
| |
| tn = rcvd->ts; |
| fn = rcvd->fn; |
| rssi = -(int8_t) rcvd->rssi; |
| toa256 = (int16_t) rcvd->toa; |
| |
| /* Copy and convert bits {254..0} to sbits {-127..127} */ |
| //osmo_ubit2sbit(bits, rcvd->symbols, 148); |
| memcpy(bits, rcvd->symbols, 148); |
| |
| free(rcvd); |
| #else |
| read_len = read(ofd->fd, buf, sizeof(buf)); |
| if (read_len <= 0) { |
| LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len); |
| return read_len; |
| } |
| |
| if (read_len != 158) { |
| LOGP(DTRXD, LOGL_ERROR, |
| "Got data message with invalid " |
| "length '%zd'\n", |
| read_len); |
| return -EINVAL; |
| } |
| #endif |
| tn = buf[0]; |
| fn = osmo_load32be(buf + 1); |
| rssi = -(int8_t)buf[5]; |
| toa256 = ((int16_t)(buf[6] << 8) | buf[7]); |
| |
| /* Copy and convert bits {254..0} to sbits {-127..127} */ |
| //osmo_ubit2sbit(bits, buf + 8, 148); |
| memcpy(bits, buf + 8, 148); |
| |
| if (tn >= 8) { |
| LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn); |
| return -EINVAL; |
| } |
| |
| if (fn >= 2715648) { |
| LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn); |
| return -EINVAL; |
| } |
| |
| LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n", |
| tn, fn, rssi, toa256); |
| |
| /* Group the measurements together */ |
| meas = (struct trx_meas_set) { |
| .toa256 = toa256, |
| .rssi = rssi, |
| .fn = fn, |
| }; |
| |
| /* Poke scheduler */ |
| sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas); |
| |
| /* Correct local clock counter */ |
| if (fn % 51 == 0) |
| sched_clck_handle(&trx->sched, fn); |
| |
| return 0; |
| } |
| |
| int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn, |
| uint8_t pwr, const ubit_t *bits) |
| { |
| #ifdef IPCIF |
| struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx)); |
| t->ts = tn; |
| t->fn = fn; |
| t->txlev = pwr; |
| memcpy(t->symbols, bits, 148); |
| trxif_to_trx_d(t); |
| #else |
| uint8_t buf[TRXD_BUF_SIZE]; |
| |
| /** |
| * We must be sure that we have clock, |
| * and we have sent all control data |
| * |
| * TODO: introduce proper state machines for both |
| * transceiver and its TRXC interface. |
| */ |
| #if 0 |
| if (trx->fsm->state != TRX_STATE_ACTIVE) { |
| LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, " |
| "transceiver isn't ready\n"); |
| return -EAGAIN; |
| } |
| #endif |
| |
| LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr); |
| |
| buf[0] = tn; |
| osmo_store32be(fn, buf + 1); |
| buf[5] = pwr; |
| |
| /* Copy ubits {0,1} */ |
| memcpy(buf + 6, bits, 148); |
| |
| /* Send data to transceiver */ |
| send(trx->trx_ofd_data.fd, buf, 154, 0); |
| |
| #endif |
| return 0; |
| } |
| |
| /* Init TRX interface (TRXC, TRXD sockets and FSM) */ |
| struct trx_instance *trx_if_open(void *tall_ctx, |
| const char *local_host, const char *remote_host, |
| uint16_t base_port) |
| { |
| struct trx_instance *trx; |
| int rc; |
| |
| LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface " |
| "(%s:%u)\n", remote_host, base_port); |
| |
| /* Try to allocate memory */ |
| trx = talloc_zero(tall_ctx, struct trx_instance); |
| if (!trx) { |
| LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n"); |
| return NULL; |
| } |
| |
| /* Allocate a new dedicated state machine */ |
| trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx, |
| NULL, LOGL_DEBUG, "trx_interface"); |
| if (trx->fsm == NULL) { |
| LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance " |
| "of FSM '%s'\n", trx_fsm.name); |
| talloc_free(trx); |
| return NULL; |
| } |
| |
| /* Initialize CTRL queue */ |
| INIT_LLIST_HEAD(&trx->trx_ctrl_list); |
| |
| #ifdef IPCIF |
| rc = eventfd(0, 0); |
| osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0); |
| osmo_fd_register(get_c_fd()); |
| |
| rc = eventfd(0, 0); |
| osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0); |
| osmo_fd_register(get_d_fd()); |
| #else |
| /* Open sockets */ |
| rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host, base_port + 101, remote_host, base_port + 1, |
| trx_ctrl_read_cb); |
| if (rc < 0) |
| goto udp_error; |
| |
| rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host, base_port + 102, remote_host, base_port + 2, |
| trx_data_rx_cb); |
| if (rc < 0) |
| goto udp_error; |
| #endif |
| return trx; |
| |
| udp_error: |
| LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n"); |
| osmo_fsm_inst_free(trx->fsm); |
| talloc_free(trx); |
| return NULL; |
| } |
| |
| /* Flush pending control messages */ |
| void trx_if_flush_ctrl(struct trx_instance *trx) |
| { |
| struct trx_ctrl_msg *tcm; |
| |
| /* Reset state machine */ |
| osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0); |
| |
| /* Clear command queue */ |
| while (!llist_empty(&trx->trx_ctrl_list)) { |
| tcm = llist_entry(trx->trx_ctrl_list.next, |
| struct trx_ctrl_msg, list); |
| llist_del(&tcm->list); |
| talloc_free(tcm); |
| } |
| } |
| |
| void trx_if_close(struct trx_instance *trx) |
| { |
| /* May be unallocated due to init error */ |
| if (!trx) |
| return; |
| |
| LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n"); |
| |
| /* Flush CTRL message list */ |
| trx_if_flush_ctrl(trx); |
| |
| /* Close sockets */ |
| #ifdef IPCIF |
| close(get_c_fd()->fd); |
| close(get_d_fd()->fd); |
| #else |
| trx_udp_close(&trx->trx_ofd_ctrl); |
| trx_udp_close(&trx->trx_ofd_data); |
| #endif |
| |
| /* Free memory */ |
| osmo_fsm_inst_free(trx->fsm); |
| talloc_free(trx); |
| } |
| |
| static __attribute__((constructor)) void on_dso_load(void) |
| { |
| OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0); |
| } |