diff --git a/.gitignore b/.gitignore index 128f803e..4baa72a8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ Transceiver52M/osmo-trx-uhd Transceiver52M/osmo-trx-usrp1 Transceiver52M/osmo-trx-lms +Transceiver52M/osmo-trx-ipc + +.clang-format # tests tests/CommonLibs/BitVectorTest @@ -19,6 +22,7 @@ tests/CommonLibs/VectorTest tests/CommonLibs/PRBSTest tests/Transceiver52M/convolve_test tests/Transceiver52M/LMSDeviceTest +Transceiver52M/device/ipc/ipc-driver-test # automake/autoconf *.in @@ -66,3 +70,5 @@ doc/manuals/build contrib/osmo-trx.spec !contrib/osmo-trx.spec.in + +utils/osmo-prbs-tool diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am index ade4e30b..7dad1599 100644 --- a/Transceiver52M/Makefile.am +++ b/Transceiver52M/Makefile.am @@ -104,3 +104,13 @@ osmo_trx_lms_LDADD = \ $(LMS_LIBS) osmo_trx_lms_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS) endif + +if DEVICE_IPC +bin_PROGRAMS += osmo-trx-ipc +osmo_trx_ipc_SOURCES = osmo-trx.cpp +osmo_trx_ipc_LDADD = \ + $(builddir)/device/ipc/libdevice.la \ + $(COMMON_LDADD) +osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS) +endif + diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am index 369e877a..93ba7e3e 100644 --- a/Transceiver52M/device/Makefile.am +++ b/Transceiver52M/device/Makefile.am @@ -2,6 +2,10 @@ include $(top_srcdir)/Makefile.common SUBDIRS = common +if DEVICE_IPC +SUBDIRS += ipc +endif + if DEVICE_USRP1 SUBDIRS += usrp1 endif diff --git a/Transceiver52M/device/ipc/IPCDevice.cpp b/Transceiver52M/device/ipc/IPCDevice.cpp new file mode 100644 index 00000000..4a1f8b68 --- /dev/null +++ b/Transceiver52M/device/ipc/IPCDevice.cpp @@ -0,0 +1,1267 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: AGPL-3.0+ +* +* 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 . +* See the COPYING file in the main directory for details. +*/ + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "Logger.h" +#include "Threads.h" +#include "IPCDevice.h" +#include "smpl_buf.h" + +extern "C" { +#include +#include /* For mode constants */ +#include /* For O_* constants */ + +#include "osmo_signal.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipc_shm.h" +} + +#define SAMPLE_BUF_SZ (1 << 20) + +using namespace std; + +static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags); + +IPCDevice::IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, + const std::vector &tx_paths, const std::vector &rx_paths) + : RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), tx_attenuation(), + tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), started(false) +{ + LOGC(DDEV, INFO) << "creating IPC device..."; + + rx_gains.resize(chans); + tx_gains.resize(chans); + + rx_buffers.resize(chans); + + sk_chan_state.resize(chans, ipc_per_trx_sock_state()); + + /* Set up per-channel Rx timestamp based Ring buffers */ + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i] = new smpl_buf(SAMPLE_BUF_SZ / sizeof(uint32_t)); +} + +IPCDevice::~IPCDevice() +{ + LOGC(DDEV, INFO) << "Closing IPC device"; + /* disable all channels */ + + for (size_t i = 0; i < rx_buffers.size(); i++) + delete rx_buffers[i]; + + ipc_sock_close(&master_sk_state); + + for (unsigned int i = 0; i < sk_chan_state.size(); i++) + ipc_sock_close(&sk_chan_state[i]); + + for (auto i : shm_io_rx_streams) + ipc_shm_close(i); + for (auto i : shm_io_tx_streams) + ipc_shm_close(i); + + if (shm_dec) + talloc_free(shm_dec); +} + +int IPCDevice::ipc_shm_connect(const char *shm_name) +{ + int fd; + size_t shm_len; + int rc; + + LOGP(DDEV, LOGL_NOTICE, "Opening shm path %s\n", shm_name); + if ((fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) { + LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_shm_open; + } + + // Get size of the allocated memory + struct stat shm_stat; + if (fstat(fd, &shm_stat) < 0) { + LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + + shm_len = shm_stat.st_size; + + LOGP(DDEV, LOGL_NOTICE, "mmaping shared memory fd %d (size=%zu)\n", fd, shm_len); + if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + LOGP(DDEV, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm); + // LOGP(DDEV, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *)shm, 80)); + /* After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping. */ + close(fd); + return 0; +err_mmap: + shm_unlink(shm_name); + close(fd); +err_shm_open: + return rc; +} + +static int ipc_sock_send(struct ipc_per_trx_sock_state *state, struct msgb *msg); + +static struct msgb *ipc_msgb_alloc(uint8_t msg_type) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + msg = msgb_alloc(sizeof(struct ipc_sk_if) + 1000, "ipc_sock_tx"); + if (!msg) + return NULL; + msgb_put(msg, sizeof(struct ipc_sk_if) + 1000); + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->msg_type = msg_type; + + return msg; +} + +static int ipc_tx_greeting_req(struct ipc_per_trx_sock_state *state, uint8_t req_version) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + LOGC(DDEV, NOTICE) << "Tx Greeting Req (" << IPC_IF_MSG_GREETING_REQ << ")\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_REQ); + if (!msg) { + LOGC(DDEV, INFO) << "ipc_msgb_alloc() returns NULL!"; + return -ENOMEM; + } + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->u.greeting_req.req_version = req_version; + + return ipc_sock_send(state, msg); +} + +static int ipc_tx_info_req(struct ipc_per_trx_sock_state *state) +{ + struct msgb *msg; + //struct ipc_sk_if *ipc_prim; + + LOGC(DDEV, NOTICE) << "Tx INFO Req\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_REQ); + if (!msg) + return -ENOMEM; + + //ipc_prim = (struct ipc_sk_if *) msg->data; + + return ipc_sock_send(state, msg); +} + +int IPCDevice::ipc_tx_open_req(struct ipc_per_trx_sock_state *state, uint32_t num_chans, uint32_t ref) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + struct ipc_sk_if_open_req_chan *chan_info; + + LOGC(DDEV, NOTICE) << "Tx Open Req\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_REQ); + if (!msg) { + return -ENOMEM; + } + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->u.open_req.num_chans = num_chans; + + /* FIXME: this is actually the sps value, not the sample rate! + * sample rate is looked up according to the sps rate by uhd backend */ + ipc_prim->u.open_req.rx_sample_freq_num = rx_sps; + ipc_prim->u.open_req.rx_sample_freq_den = 1; + ipc_prim->u.open_req.tx_sample_freq_num = tx_sps; + ipc_prim->u.open_req.tx_sample_freq_den = 1; + + switch (ref) { + case ReferenceType::REF_EXTERNAL: + ipc_prim->u.open_req.clockref = FEATURE_MASK_CLOCKREF_EXTERNAL; + break; + case ReferenceType::REF_INTERNAL: + case ReferenceType::REF_GPS: + ipc_prim->u.open_req.clockref = FEATURE_MASK_CLOCKREF_INTERNAL; + break; + } + + /* FIXME: clock ref part of config, not open */ + ipc_prim->u.open_req.clockref = FEATURE_MASK_CLOCKREF_EXTERNAL; + + for (unsigned int i = 0; i < num_chans; i++) { + chan_info = &ipc_prim->u.open_req.chan_info[i]; + OSMO_STRLCPY_ARRAY(chan_info->rx_path, rx_paths[i].c_str()); + OSMO_STRLCPY_ARRAY(chan_info->tx_path, tx_paths[i].c_str()); + } + + return ipc_sock_send(state, msg); +} + +static void ipc_sock_timeout(void *_priv) +{ + LOGC(DDEV, INFO) << "UNIX SOCKET TIMEOUT!"; + exit(1); +} + +int IPCDevice::ipc_rx_greeting_cnf(const struct ipc_sk_if_greeting *greeting_cnf) +{ + if (greeting_cnf->req_version == IPC_SOCK_API_VERSION) { + LOGC(DDEV, NOTICE) << "Rx Greeting CNF: correct sock API version" << greeting_cnf->req_version; + tmp_state = IPC_IF_MSG_GREETING_CNF; + } else { + LOGC(DDEV, ERROR) << "Wrong IPC SOCK API VERSION RECEIVED!" << greeting_cnf->req_version; + exit(1); + } + return 0; +} + +int IPCDevice::ipc_rx_info_cnf(const struct ipc_sk_if_info_cnf *info_cnf) +{ + current_info_cnf = *info_cnf; + unsigned int i; + + if (info_cnf->max_num_chans < chans) { + LOGC(DDEV, ERROR) << "chan num mismatch:" << info_cnf->max_num_chans << " vs " << chans; + return -1; + } + + /* Here: + * verify info_cnf->max_num_chans >= requested chans + * verify supports setting reflock as asked by user looking in info_cnf->feature_mask + * cache locally min/max tx/rxGain values from info_cnf + * do whatever validations or print info_cnf->dev_desc + * cache rx/tx paths per channel, and make sure it matches the one the user wants to set + */ + + LOGC(DDEV, NOTICE) + << "Rx Info CNF:" + << " name=" << info_cnf->dev_desc << std::endl + << " max_num_chans=" << info_cnf->max_num_chans << " feature_mask=" << info_cnf->feature_mask; + for (i = 0; i < info_cnf->max_num_chans; i++) { + int j = 0; + bool rx_found = false, tx_found = false; + while (strcmp(info_cnf->chan_info[i].rx_path[j], "") != 0) { + LOGC(DDEV, NOTICE) + << "chan " << i << ": RxPath[" << j << "]: " << info_cnf->chan_info[i].rx_path[j] + << " min_rx_gain=" << info_cnf->chan_info[i].min_rx_gain + << " max_rx_gain=" << info_cnf->chan_info[i].max_rx_gain + << " min_tx_gain=" << info_cnf->chan_info[i].min_tx_gain + << " max_tx_gain=" << info_cnf->chan_info[i].max_tx_gain; + + if (rx_paths.size() < (i + 1) || + strcmp(rx_paths[i].c_str(), info_cnf->chan_info[i].rx_path[j]) == 0) { + rx_found = true; + break; + } + j++; + } + j = 0; + while (strcmp(info_cnf->chan_info[i].tx_path[j], "") != 0) { + LOGC(DDEV, NOTICE) + << "chan " << i << ": TxPath[" << j << "]: " << info_cnf->chan_info[i].tx_path[j]; + if (tx_paths.size() < (i + 1) || + strcmp(tx_paths[i].c_str(), info_cnf->chan_info[i].tx_path[j]) == 0) { + tx_found = true; + break; + } + j++; + } + + if (!rx_found) { + LOGC(DDEV, ERROR) << "rx antenna not found: " << rx_paths[i]; + exit(0); + } + if (!tx_found) { + LOGC(DDEV, ERROR) << "tx antenna not found: " << rx_paths[i]; + exit(0); + } + } + tmp_state = IPC_IF_MSG_INFO_CNF; + return 0; +} + +int IPCDevice::ipc_rx_open_cnf(const struct ipc_sk_if_open_cnf *open_cnf) +{ + unsigned int i; + current_open_cnf = *open_cnf; + + LOGC(DDEV, NOTICE) + << "Rx Open CNF:" + << " return_code=" << (unsigned int)open_cnf->return_code << " shm_name=" << open_cnf->shm_name; + LOGC(DDEV, NOTICE) << "Rx Open CNF:" + << " ipc device path delay: " << (unsigned int)open_cnf->path_delay; + for (i = 0; i < chans; i++) { + int rc; + LOGC(DDEV, NOTICE) << "chan " << i << ": sk_path=" << open_cnf->chan_info[i].chan_ipc_sk_path; + + /* FIXME: current limit IPC_MAX_NUM_TRX chans, make dynamic */ + if (i < IPC_MAX_NUM_TRX) { + struct ipc_per_trx_sock_state *state = &sk_chan_state[i]; + + INIT_LLIST_HEAD(&state->upqueue); + rc = osmo_sock_unix_init_ofd(&state->conn_bfd, SOCK_SEQPACKET, 0, + open_cnf->chan_info[i].chan_ipc_sk_path, OSMO_SOCK_F_CONNECT); + if (rc < 0) { + LOGC(DDEV, ERROR) << "Failed to connect to the BTS (" + << open_cnf->chan_info[i].chan_ipc_sk_path << "). " + << "Retrying...\n"; + osmo_timer_setup(&state->timer, ipc_sock_timeout, NULL); + osmo_timer_schedule(&state->timer, 5, 0); + return -1; + } + state->conn_bfd.cb = ipc_chan_sock_cb; + state->conn_bfd.data = this; + state->conn_bfd.priv_nr = i; + } + } + + OSMO_STRLCPY_ARRAY(shm_name, open_cnf->shm_name); + if (ipc_shm_connect(shm_name) < 0) + return -1; + shm_dec = ipc_shm_decode_region(NULL, (ipc_shm_raw_region *)shm); + LOGC(DDEV, NOTICE) << "shm: num_chans=" << shm_dec->num_chans; + + /* server inits both producers */ + for (unsigned int i = 0; i < shm_dec->num_chans; i++) { + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/dl: num_buffers=" << shm_dec->channels[i]->dl_stream->num_buffers; + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/dl: buffer_size=" << shm_dec->channels[i]->dl_stream->buffer_size; + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/ul: num_buffers=" << shm_dec->channels[i]->ul_stream->num_buffers; + LOGC(DDEV, NOTICE) + << "shm: chan" << i << "/ul: buffer_size=" << shm_dec->channels[i]->ul_stream->buffer_size; + shm_io_rx_streams.push_back(ipc_shm_init_consumer(shm_dec->channels[i]->ul_stream)); + shm_io_tx_streams.push_back(ipc_shm_init_consumer(shm_dec->channels[i]->dl_stream)); + // we should init a producer here, but delegating all producers and therefore lock management + // to the other side is the reasonable approach to circumvent shutdown issues + } + + tmp_state = IPC_IF_MSG_OPEN_CNF; + return 0; +} + +int IPCDevice::ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_GREETING_CNF: + rc = ipc_rx_greeting_cnf(&ipc_prim->u.greeting_cnf); + break; + case IPC_IF_MSG_INFO_CNF: + rc = ipc_rx_info_cnf(&ipc_prim->u.info_cnf); + break; + case IPC_IF_MSG_OPEN_CNF: + rc = ipc_rx_open_cnf(&ipc_prim->u.open_cnf); + break; + default: + LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type %d\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +int IPCDevice::ipc_rx_chan_start_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal start response for chan #" << chan_nr << " ?!?"; + return 0; + } + return 0; +} +int IPCDevice::ipc_rx_chan_stop_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal stop response for chan #" << chan_nr << " ?!?"; + return 0; + } + return 0; +} +int IPCDevice::ipc_rx_chan_setgain_cnf(ipc_sk_chan_if_gain *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal setgain response for chan #" << chan_nr << " ?!?"; + return 0; + } + + ret->is_tx ? tx_gains[chan_nr] = ret->gain : rx_gains[chan_nr] = ret->gain; + return 0; +} +int IPCDevice::ipc_rx_chan_settxattn_cnf(ipc_sk_chan_if_tx_attenuation *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal tx attn response for chan #" << chan_nr << " ?!?"; + return 0; + } + + tx_attenuation[chan_nr] = ret->attenuation; + return 0; +} +int IPCDevice::ipc_rx_chan_setfreq_cnf(ipc_sk_chan_if_freq_cnf *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal setfreq response for chan #" << chan_nr << " ?!?"; + return 0; + } + + return 0; +} +int IPCDevice::ipc_rx_chan_notify_underflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal underfloww notification for chan #" << chan_nr << " ?!?"; + return 0; + } + + m_ctr[chan_nr].tx_underruns += 1; + osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan_nr]); + + return 0; +} +int IPCDevice::ipc_rx_chan_notify_overflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr) +{ + if (chan_nr >= chans) { + LOGC(DDEV, NOTICE) << "shm: illegal overflow notification for chan #" << chan_nr << " ?!?"; + return 0; + } + + m_ctr[chan_nr].rx_overruns += 1; + osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan_nr]); + return 0; +} + +int IPCDevice::ipc_chan_rx(uint8_t msg_type, struct ipc_sk_chan_if *ipc_prim, uint8_t chan_nr) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_START_CNF: + rc = ipc_rx_chan_start_cnf(&ipc_prim->u.start_cnf, chan_nr); + break; + case IPC_IF_MSG_STOP_CNF: + rc = ipc_rx_chan_stop_cnf(&ipc_prim->u.stop_cnf, chan_nr); + break; + case IPC_IF_MSG_SETGAIN_CNF: + rc = ipc_rx_chan_setgain_cnf(&ipc_prim->u.set_gain_cnf, chan_nr); + break; + case IPC_IF_MSG_SETFREQ_CNF: + rc = ipc_rx_chan_setfreq_cnf(&ipc_prim->u.set_freq_cnf, chan_nr); + break; + case IPC_IF_NOTIFY_UNDERFLOW: + rc = ipc_rx_chan_notify_underflow(&ipc_prim->u.notify, chan_nr); + break; + case IPC_IF_NOTIFY_OVERFLOW: + rc = ipc_rx_chan_notify_overflow(&ipc_prim->u.notify, chan_nr); + break; + case IPC_IF_MSG_SETTXATTN_CNF: + rc = ipc_rx_chan_settxattn_cnf(&ipc_prim->u.txatten_cnf, chan_nr); + break; + default: + LOGP(DMAIN, LOGL_ERROR, "Received unknown IPC msg type %d\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +static int ipc_sock_send(struct ipc_per_trx_sock_state *state, struct msgb *msg) +{ + struct osmo_fd *conn_bfd; + + if (!state) { + LOGP(DMAIN, LOGL_INFO, + "IPC socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DMAIN, LOGL_NOTICE, + "IPC socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + conn_bfd->when |= BSC_FD_WRITE; + + return 0; +} + +void IPCDevice::ipc_sock_close(struct ipc_per_trx_sock_state *state) +{ + if (state == 0) + return; + + struct osmo_fd *bfd = &state->conn_bfd; + + if (bfd->fd <= 0) + return; + + LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n"); + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +int IPCDevice::ipc_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sk_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if ((size_t)rc < sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_rx(ipc_prim->msg_type, ipc_prim); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(&master_sk_state); + return -1; +} + +int IPCDevice::ipc_chan_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sk_chan_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_chan_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_chan_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if ((size_t)rc < sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + /* store mask of last received messages so we can check later */ + sk_chan_state[bfd->priv_nr].messages_processed_mask |= (1 << (ipc_prim->msg_type - IPC_IF_CHAN_MSG_OFFSET)); + + rc = ipc_chan_rx(ipc_prim->msg_type, ipc_prim, bfd->priv_nr); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(&sk_chan_state[bfd->priv_nr]); + return -1; +} + +int IPCDevice::ipc_sock_write(struct osmo_fd *bfd) +{ + int rc; + + while (!llist_empty(&master_sk_state.upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(master_sk_state.upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_if *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&master_sk_state.upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(&master_sk_state); + return -1; +} + +int IPCDevice::ipc_chan_sock_write(struct osmo_fd *bfd) +{ + int rc; + + while (!llist_empty(&sk_chan_state[bfd->priv_nr].upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_chan_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(sk_chan_state[bfd->priv_nr].upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + bfd->when &= ~BSC_FD_WRITE; + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can dequeue */ + msg2 = msgb_dequeue(&sk_chan_state[bfd->priv_nr].upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(&sk_chan_state[bfd->priv_nr]); + return -1; +} + +static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + IPCDevice *device = static_cast(bfd->data); + int rc = 0; + + if (flags & BSC_FD_READ) + rc = device->ipc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = device->ipc_sock_write(bfd); + + return rc; +} + +static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + IPCDevice *device = static_cast(bfd->data); + int rc = 0; + + if (flags & BSC_FD_READ) + rc = device->ipc_chan_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = device->ipc_chan_sock_write(bfd); + + return rc; +} + +int IPCDevice::open(const std::string &args, int ref, bool swap_channels) +{ + std::string k, v; + std::string::size_type keyend; + int rc; + + if ((keyend = args.find('=')) != std::string::npos) { + k = args.substr(0, keyend++); + v = args.substr(keyend); + } + if (k != "ipc_msock" || !v.length()) { + LOGC(DDEV, ERROR) << "Invalid device args provided, expected \"dev-args ipc_msock=/path/to/socket\"\n"; + return -1; + } + + LOGC(DDEV, INFO) << "Opening IPC device" << v << ".."; + + INIT_LLIST_HEAD(&master_sk_state.upqueue); + rc = osmo_sock_unix_init_ofd(&master_sk_state.conn_bfd, SOCK_SEQPACKET, 0, v.c_str(), OSMO_SOCK_F_CONNECT); + if (rc < 0) { + LOGC(DDEV, ERROR) << "Failed to connect to the BTS (" << v << "). " + << "Retrying...\n"; + osmo_timer_setup(&master_sk_state.timer, ipc_sock_timeout, NULL); + osmo_timer_schedule(&master_sk_state.timer, 5, 0); + return -1; + } + master_sk_state.conn_bfd.cb = ipc_sock_cb; + master_sk_state.conn_bfd.data = this; + + ipc_tx_greeting_req(&master_sk_state, IPC_SOCK_API_VERSION); + /* Wait until confirmation is recieved */ + while (tmp_state != IPC_IF_MSG_GREETING_CNF) + osmo_select_main(0); + + ipc_tx_info_req(&master_sk_state); + /* Wait until confirmation is recieved */ + while (tmp_state != IPC_IF_MSG_INFO_CNF) + osmo_select_main(0); + + ipc_tx_open_req(&master_sk_state, chans, ref); + /* Wait until confirmation is recieved */ + while (tmp_state != IPC_IF_MSG_OPEN_CNF) + osmo_select_main(0); + LOGC(DDEV, NOTICE) << "Device driver opened successfuly!"; + + /* configure antennas */ + if (!set_antennas()) { + LOGC(DDEV, FATAL) << "IPC antenna setting failed"; + goto out_close; + } + + return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL; + +out_close: + LOGC(DDEV, FATAL) << "Error in IPC open, closing"; + return -1; +} + +void IPCDevice::manually_poll_sock_fds() +{ + struct timeval wait = { 0, 100000 }; + fd_set crfds, cwfds; + int max_fd = 0; + + FD_ZERO(&crfds); + FD_ZERO(&cwfds); + for (unsigned int i = 0; i < chans; i++) { + struct osmo_fd *curr_fd = &sk_chan_state[i].conn_bfd; + max_fd = curr_fd->fd > max_fd ? curr_fd->fd : max_fd; + + if (curr_fd->when & OSMO_FD_READ) + FD_SET(curr_fd->fd, &crfds); + if (curr_fd->when & OSMO_FD_WRITE) + FD_SET(curr_fd->fd, &cwfds); + } + + select(max_fd + 1, &crfds, &cwfds, 0, &wait); + + for (unsigned int i = 0; i < chans; i++) { + int flags = 0; + struct osmo_fd *ofd = &sk_chan_state[i].conn_bfd; + + if (FD_ISSET(ofd->fd, &crfds)) { + flags |= OSMO_FD_READ; + FD_CLR(ofd->fd, &crfds); + } + + if (FD_ISSET(ofd->fd, &cwfds)) { + flags |= OSMO_FD_WRITE; + FD_CLR(ofd->fd, &cwfds); + } + if (flags) + ipc_chan_sock_cb(ofd, flags); + } +} + +bool IPCDevice::send_chan_wait_rsp(uint32_t chan, struct msgb *msg_to_send, uint32_t expected_rsp_msg_id) +{ + struct timeval timer_now, timeout; + + sk_chan_state[chan].messages_processed_mask = 0; + ipc_sock_send(&sk_chan_state[chan], msg_to_send); + + gettimeofday(&timeout, 0); + timeout.tv_sec += 2; + + while (!(sk_chan_state[chan].messages_processed_mask & (1 << (expected_rsp_msg_id - IPC_IF_CHAN_MSG_OFFSET)))) { + /* just poll here, we're already in select, so there is no other way to drive + * the fds and "wait" for a response or retry */ + manually_poll_sock_fds(); + + gettimeofday(&timer_now, 0); + if (timercmp(&timer_now, &timeout, >)) + return false; + } + return true; +} + +bool IPCDevice::send_all_chan_wait_rsp(uint32_t msgid_to_send, uint32_t msgid_to_expect) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + struct timeval timer_now, timeout; + + for (unsigned int i = 0; i < chans; i++) { + msg = ipc_msgb_alloc(msgid_to_send); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.start_req.dummy = 0; + + sk_chan_state[i].messages_processed_mask = 0; + ipc_sock_send(&sk_chan_state[i], msg); + } + + gettimeofday(&timeout, 0); + timeout.tv_sec += 2; + + unsigned int msg_received_count = 0; + while (msg_received_count != chans) { + msg_received_count = 0; + + /* just poll here, we're already in select, so there is no other way to drive + * the fds and "wait" for a response or retry */ + manually_poll_sock_fds(); + + for (unsigned int i = 0; i < sk_chan_state.size(); i++) + if (sk_chan_state[i].messages_processed_mask & + (1 << (msgid_to_expect - IPC_IF_CHAN_MSG_OFFSET))) + msg_received_count++; + + gettimeofday(&timer_now, 0); + if (timercmp(&timer_now, &timeout, >)) + return false; + } + + return true; +} + +/* the call stack is rather difficult here, we're already in select: +>~"#0 IPCDevice::start (this=) at IPCDevice.cpp:789\n" +>~"#1 in RadioInterface::start (this=0x614000001640) at radioInterface.cpp:187\n" +>~"#2 in Transceiver::start (this=) at Transceiver.cpp:293\n" +>~"#3 in Transceiver::ctrl_sock_handle_rx (this=0x61600000b180, chan=0) at Transceiver.cpp:838\n" +>~"#4 in Transceiver::ctrl_sock_cb (bfd=, flags=1) at Transceiver.cpp:168\n" +>~"#5 in osmo_fd_disp_fds (_rset=, _wset=, _eset=) at select.c:227\n" +>~"#6 _osmo_select_main (polling=) at select.c:265\n" +>~"#7 in osmo_select_main (polling=128) at select.c:274\n" +>~"#8 in main (argc=, argv=) at osmo-trx.cpp:649\n" + * */ +bool IPCDevice::start() +{ + LOGC(DDEV, INFO) << "starting IPC..."; + + if (started) { + LOGC(DDEV, ERR) << "Device already started"; + return true; + } + + if (!(send_all_chan_wait_rsp(IPC_IF_MSG_START_REQ, IPC_IF_MSG_START_CNF))) { + LOGC(DDEV, ERR) << "start timeout!"; + return false; + } + + int max_bufs_to_flush = 0; + for (unsigned int i = 0; i < shm_dec->num_chans; i++) { + int buf_per_chan = shm_dec->channels[i]->ul_stream->num_buffers; + max_bufs_to_flush = max_bufs_to_flush < buf_per_chan ? buf_per_chan : max_bufs_to_flush; + } + flush_recv(max_bufs_to_flush); + + started = true; + return true; +} + +bool IPCDevice::stop() +{ + if (!started) + return true; + + if (!(send_all_chan_wait_rsp(IPC_IF_MSG_STOP_REQ, IPC_IF_MSG_STOP_CNF))) { + LOGC(DDEV, ERR) << "stop timeout!"; + return false; + } + + LOGC(DDEV, NOTICE) << "All channels stopped, terminating..."; + + started = false; + return true; +} + +double IPCDevice::maxRxGain() +{ + return current_info_cnf.chan_info[0].max_rx_gain; +} + +double IPCDevice::minRxGain() +{ + return current_info_cnf.chan_info[0].min_rx_gain; +} + +int IPCDevice::getNominalTxPower(size_t chan) +{ + return current_info_cnf.chan_info[chan].nominal_tx_power; +} + +double IPCDevice::setPowerAttenuation(int atten, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + + if (chan >= chans) + return 0; + + LOGCHAN(chan, DDEV, NOTICE) << "Setting TX attenuation to " << atten << " dB" + << " chan " << chan; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETTXATTN_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.txatten_req.attenuation = atten; + + if (!send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETTXATTN_CNF)) + LOGCHAN(chan, DDEV, ERROR) << "Setting TX attenuation timeout! "; + + return atten; +} + +double IPCDevice::getPowerAttenuation(size_t chan) +{ + if (chan >= chans) + return 0; + + return tx_attenuation[chan]; +} + +double IPCDevice::setRxGain(double dB, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + + if (dB > maxRxGain()) + dB = maxRxGain(); + if (dB < minRxGain()) + dB = minRxGain(); + + LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETGAIN_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_gain_req.is_tx = 0; + ipc_prim->u.set_gain_req.gain = dB; + + if (!send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETGAIN_CNF)) + LOGCHAN(chan, DDEV, ERROR) << "Setting RX gain timeout! "; + + return rx_gains[chan]; +} + +bool IPCDevice::flush_recv(size_t num_pkts) +{ + std::vector tmp(4096); + uint64_t tmps; + uint32_t read = 0; + + for (uint32_t j = 0; j < num_pkts; j++) { + for (unsigned int i = 0; i < chans; i++) + read = ipc_shm_read(shm_io_rx_streams[i], (uint16_t *)&tmp.front(), 4096 / 2, &tmps, 3); + } + ts_initial = tmps + read; + + LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl; + return true; +} + +bool IPCDevice::setRxAntenna(const std::string &ant, size_t chan) +{ + return true; +} + +std::string IPCDevice::getRxAntenna(size_t chan) +{ + return ""; +} + +bool IPCDevice::setTxAntenna(const std::string &ant, size_t chan) +{ + return true; +} + +std::string IPCDevice::getTxAntenna(size_t chan) +{ + return ""; +} + +bool IPCDevice::requiresRadioAlign() +{ + return false; +} + +GSM::Time IPCDevice::minLatency() +{ + /* UNUSED */ + return GSM::Time(0, 0); +} + +/** Returns the starting write Timestamp*/ +TIMESTAMP IPCDevice::initialWriteTimestamp(void) +{ + return ts_initial; +} + +/** Returns the starting read Timestamp*/ +TIMESTAMP IPCDevice::initialReadTimestamp(void) +{ + return ts_initial; +} + +// NOTE: Assumes sequential reads +int IPCDevice::readSamples(std::vector &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun) +{ + int rc, num_smpls; //, expect_smpls; + ssize_t avail_smpls; + TIMESTAMP expect_timestamp; + unsigned int i; + + if (bufs.size() != chans) { + LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size(); + return -1; + } + + *overrun = false; + *underrun = false; + + timestamp += current_open_cnf.path_delay; + + /* Check that timestamp is valid */ + rc = rx_buffers[0]->avail_smpls(timestamp); + if (rc < 0) { + LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc); + LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp); + return 0; + } + + for (i = 0; i < chans; i++) { + /* Receive samples from HW until we have enough */ + while ((avail_smpls = rx_buffers[i]->avail_smpls(timestamp)) < len) { + uint64_t recv_timestamp = 0; + + thread_enable_cancel(false); + num_smpls = ipc_shm_read(shm_io_rx_streams[i], (uint16_t *)bufs[i], len - avail_smpls, + &recv_timestamp, 1); + expect_timestamp = timestamp + avail_smpls; + thread_enable_cancel(true); + + if (num_smpls == -ETIMEDOUT) + continue; + + LOGCHAN(i, DDEV, DEBUG) + "Received timestamp = " << (TIMESTAMP)recv_timestamp << " (" << num_smpls << ")"; + + if (expect_timestamp != (TIMESTAMP)recv_timestamp) + LOGCHAN(i, DDEV, ERROR) << "Unexpected recv buffer timestamp: expect " + << expect_timestamp << " got " << recv_timestamp << ", diff=" + << ((uint64_t)recv_timestamp > expect_timestamp ? + (uint64_t)recv_timestamp - expect_timestamp : + expect_timestamp - recv_timestamp); + + rc = rx_buffers[i]->write(bufs[i], num_smpls, (TIMESTAMP)recv_timestamp); + if (rc < 0) { + LOGCHAN(i, DDEV, ERROR) + << rx_buffers[i]->str_code(rc) << " num smpls: " << num_smpls << " chan: " << i; + LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_status(timestamp); + if (rc != smpl_buf::ERROR_OVERFLOW) + return 0; + } + } + } + + /* We have enough samples */ + for (size_t i = 0; i < rx_buffers.size(); i++) { + rc = rx_buffers[i]->read(bufs[i], len, timestamp); + if ((rc < 0) || (rc != len)) { + LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". " + << rx_buffers[i]->str_status(timestamp) << ", (len=" << len << ")"; + return 0; + } + } + + return len; +} + +int IPCDevice::writeSamples(std::vector &bufs, int len, bool *underrun, unsigned long long timestamp) +{ + int rc = 0; + unsigned int i; + + if (bufs.size() != chans) { + LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size(); + return -1; + } + + *underrun = false; + + for (i = 0; i < chans; i++) { + LOGCHAN(i, DDEV, DEBUG) << "send buffer of len " << len << " timestamp " << std::hex << timestamp; + thread_enable_cancel(false); + rc = ipc_shm_enqueue(shm_io_tx_streams[i], timestamp, len, (uint16_t *)bufs[i]); + thread_enable_cancel(true); + + if (rc != len) { + LOGCHAN(i, DDEV, ERROR) << "LMS: Device Tx timed out (" << rc << " vs exp " << len << ")."; + return -1; + } + } + + return rc; +} + +bool IPCDevice::updateAlignment(TIMESTAMP timestamp) +{ + return true; +} + +bool IPCDevice::setTxFreq(double wFreq, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_freq_req.is_tx = 1; + ipc_prim->u.set_freq_req.freq = wFreq; + + return send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETFREQ_CNF); +} + +bool IPCDevice::setRxFreq(double wFreq, size_t chan) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_REQ); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_freq_req.is_tx = 0; + ipc_prim->u.set_freq_req.freq = wFreq; + + return send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETFREQ_CNF); +} + +RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset, + const std::vector &tx_paths, const std::vector &rx_paths) +{ + if (tx_sps != rx_sps) { + LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps"; + return NULL; + } + if (lo_offset != 0.0) { + LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset"; + return NULL; + } + return new IPCDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); +} diff --git a/Transceiver52M/device/ipc/IPCDevice.h b/Transceiver52M/device/ipc/IPCDevice.h new file mode 100644 index 00000000..a7618f90 --- /dev/null +++ b/Transceiver52M/device/ipc/IPCDevice.h @@ -0,0 +1,239 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: AGPL-3.0+ +* +* 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 . +* See the COPYING file in the main directory for details. +*/ + +#ifndef _IPC_DEVICE_H_ +#define _IPC_DEVICE_H_ + +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +extern "C" { +#include +#include +#include "shm.h" +} + +#include "radioDevice.h" + +class smpl_buf; + +#define IPC_MAX_NUM_TRX 8 + +struct ipc_per_trx_sock_state { + struct osmo_fd conn_bfd; /* fd for connection to the BTS */ + struct osmo_timer_list timer; /* socket connect retry timer */ + struct llist_head upqueue; /* queue for sending messages */ + uint32_t messages_processed_mask; // (=| IPC_IF_MSG_xxx-IPC_IF_CHAN_MSG_OFFSET) bitmask + ipc_per_trx_sock_state() : conn_bfd(), timer(), upqueue(), messages_processed_mask() + { + conn_bfd.fd = -1; + } +}; + +class IPCDevice : public RadioDevice { + protected: + struct ipc_per_trx_sock_state master_sk_state; + + std::vector sk_chan_state; + + uint32_t tx_attenuation[IPC_MAX_NUM_TRX]; + uint8_t tmp_state; + char shm_name[SHM_NAME_MAX]; + int ipc_shm_connect(const char *shm_name); + void *shm; + struct ipc_shm_region *shm_dec; + + std::vector rx_buffers; + double actualSampleRate; + + bool started; + + TIMESTAMP ts_initial, ts_offset; + + std::vector tx_gains, rx_gains; + + struct ipc_sk_if_info_req current_info_req; + struct ipc_sk_if_info_cnf current_info_cnf; + struct ipc_sk_if_open_cnf current_open_cnf; + + std::vector shm_io_rx_streams; + std::vector shm_io_tx_streams; + + bool flush_recv(size_t num_pkts); + void update_stream_stats_rx(size_t chan, bool *overrun); + void update_stream_stats_tx(size_t chan, bool *underrun); + void manually_poll_sock_fds(); + + void ipc_sock_close(ipc_per_trx_sock_state *state); + + int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim); + int ipc_rx_greeting_cnf(const struct ipc_sk_if_greeting *greeting_cnf); + int ipc_rx_info_cnf(const struct ipc_sk_if_info_cnf *info_cnf); + int ipc_rx_open_cnf(const struct ipc_sk_if_open_cnf *open_cnf); + int ipc_tx_open_req(struct ipc_per_trx_sock_state *state, uint32_t num_chans, uint32_t ref); + + int ipc_chan_rx(uint8_t msg_type, ipc_sk_chan_if *ipc_prim, uint8_t chan_nr); + int ipc_rx_chan_start_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr); + int ipc_rx_chan_stop_cnf(ipc_sk_chan_if_op_rc *ret, uint8_t chan_nr); + int ipc_rx_chan_setgain_cnf(ipc_sk_chan_if_gain *ret, uint8_t chan_nr); + int ipc_rx_chan_setfreq_cnf(ipc_sk_chan_if_freq_cnf *ret, uint8_t chan_nr); + int ipc_rx_chan_notify_underflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr); + int ipc_rx_chan_notify_overflow(ipc_sk_chan_if_notfiy *ret, uint8_t chan_nr); + int ipc_rx_chan_settxattn_cnf(ipc_sk_chan_if_tx_attenuation *ret, uint8_t chan_nr); + + bool send_chan_wait_rsp(uint32_t chan, struct msgb *msg_to_send, uint32_t expected_rsp_msg_id); + bool send_all_chan_wait_rsp(uint32_t msgid_to_send, uint32_t msgid_to_expect); + + public: + int ipc_sock_read(struct osmo_fd *bfd); + int ipc_sock_write(struct osmo_fd *bfd); + int ipc_chan_sock_read(osmo_fd *bfd); + int ipc_chan_sock_write(osmo_fd *bfd); + + /** Object constructor */ + IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, + const std::vector &tx_paths, const std::vector &rx_paths); + virtual ~IPCDevice() override; + + /** Instantiate the IPC */ + virtual int open(const std::string &args, int ref, bool swap_channels) override; + + /** Start the IPC */ + virtual bool start() override; + + /** Stop the IPC */ + virtual bool stop() override; + + /* FIXME: any != USRP1 will do for now... */ + enum TxWindowType getWindowType() override + { + return TX_WINDOW_LMS1; + } + + /** + Read samples from the IPC. + @param buf preallocated buf to contain read result + @param len number of samples desired + @param overrun Set if read buffer has been overrun, e.g. data not being read fast enough + @param timestamp The timestamp of the first samples to be read + @param underrun Set if IPC does not have data to transmit, e.g. data not being sent fast enough + @return The number of samples actually read + */ + virtual int readSamples(std::vector &buf, int len, bool *overrun, TIMESTAMP timestamp = 0xffffffff, + bool *underrun = NULL) override; + /** + Write samples to the IPC. + @param buf Contains the data to be written. + @param len number of samples to write. + @param underrun Set if IPC does not have data to transmit, e.g. data not being sent fast enough + @param timestamp The timestamp of the first sample of the data buffer. + @return The number of samples actually written + */ + virtual int writeSamples(std::vector &bufs, int len, bool *underrun, + TIMESTAMP timestamp = 0xffffffff) override; + + /** Update the alignment between the read and write timestamps */ + virtual bool updateAlignment(TIMESTAMP timestamp) override; + + /** Set the transmitter frequency */ + virtual bool setTxFreq(double wFreq, size_t chan = 0) override; + + /** Set the receiver frequency */ + virtual bool setRxFreq(double wFreq, size_t chan = 0) override; + + /** Returns the starting write Timestamp*/ + virtual TIMESTAMP initialWriteTimestamp(void) override; + + /** Returns the starting read Timestamp*/ + virtual TIMESTAMP initialReadTimestamp(void) override; + + /** returns the full-scale transmit amplitude **/ + virtual double fullScaleInputValue() override + { + return (double)SHRT_MAX * current_info_cnf.iq_scaling_val_rx; + } + + /** returns the full-scale receive amplitude **/ + virtual double fullScaleOutputValue() override + { + return (double)SHRT_MAX * current_info_cnf.iq_scaling_val_tx; + } + + /** sets the receive chan gain, returns the gain setting **/ + virtual double setRxGain(double dB, size_t chan = 0) override; + + /** get the current receive gain */ + virtual double getRxGain(size_t chan = 0) override + { + return rx_gains[chan]; + } + + /** return maximum Rx Gain **/ + virtual double maxRxGain(void) override; + + /** return minimum Rx Gain **/ + virtual double minRxGain(void) override; + + double setPowerAttenuation(int atten, size_t chan) override; + double getPowerAttenuation(size_t chan = 0) override; + + virtual int getNominalTxPower(size_t chan = 0) override; + + /** sets the RX path to use, returns true if successful and false otherwise */ + virtual bool setRxAntenna(const std::string &ant, size_t chan = 0) override; + + /* return the used RX path */ + virtual std::string getRxAntenna(size_t chan = 0) override; + + /** sets the RX path to use, returns true if successful and false otherwise */ + virtual bool setTxAntenna(const std::string &ant, size_t chan = 0) override; + + /* return the used RX path */ + virtual std::string getTxAntenna(size_t chan = 0) override; + + /** return whether user drives synchronization of Tx/Rx of USRP */ + virtual bool requiresRadioAlign() override; + + /** return whether user drives synchronization of Tx/Rx of USRP */ + virtual GSM::Time minLatency() override; + + /** Return internal status values */ + virtual inline double getTxFreq(size_t chan = 0) override + { + return 0; + } + virtual inline double getRxFreq(size_t chan = 0) override + { + return 0; + } + virtual inline double getSampleRate() override + { + return actualSampleRate; + } +}; + +#endif // _IPC_DEVICE_H_ diff --git a/Transceiver52M/device/ipc/Makefile.am b/Transceiver52M/device/ipc/Makefile.am new file mode 100644 index 00000000..e4dc51a9 --- /dev/null +++ b/Transceiver52M/device/ipc/Makefile.am @@ -0,0 +1,42 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common +AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) +AM_LDFLAGS = -lpthread -lrt + +noinst_HEADERS = IPCDevice.h shm.h ipc_shm.h ipc_chan.h ipc_sock.h + +if DEVICE_UHD +noinst_HEADERS += ../uhd/UHDDevice.h uhdwrap.h ipc-driver-test.h +endif + +noinst_LTLIBRARIES = libdevice.la + +libdevice_la_SOURCES = IPCDevice.cpp shm.c ipc_shm.c ipc_chan.c ipc_sock.c +libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la +libdevice_la_CXXFLAGS = $(AM_CXXFLAGS) -DIPCMAGIC + +if DEVICE_UHD + +#work around distclean issue on older autotools vers: +#a direct build of ../uhd/UHDDevice.cpp tries to clean +#../uhd/.dep/UHDDevice.Plo twice and fails +uhddev_ipc.cpp: + echo "#include \"../uhd/UHDDevice.cpp\"" >$@ +CLEANFILES= uhddev_ipc.cpp + +bin_PROGRAMS = ipc-driver-test +#ipc_driver_test_SHORTNAME = drvt +ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ipc_shm.c ipc_chan.c ipc_sock.c uhddev_ipc.cpp +ipc_driver_test_LDADD = \ + shm.lo \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOVTY_LIBS) +ipc_driver_test_CXXFLAGS = $(AM_CXXFLAGS) $(UHD_CFLAGS) +ipc_driver_test_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) +ipc_driver_test_CFLAGS = $(AM_CFLAGS) $(UHD_CFLAGS) +ipc_driver_test_LDFLAGS = $(AM_LDFLAGS) $(UHD_LIBS) +ipc_driver_test_LDADD += $(top_builddir)/Transceiver52M/device/common/libdevice_common.la $(top_builddir)/CommonLibs/libcommon.la +endif diff --git a/Transceiver52M/device/ipc/ipc-driver-test.c b/Transceiver52M/device/ipc/ipc-driver-test.c new file mode 100644 index 00000000..79d49d68 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc-driver-test.c @@ -0,0 +1,477 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define _GNU_SOURCE +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For mode constants */ +#include /* For O_* constants */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shm.h" +#include "ipc_shm.h" +#include "ipc_chan.h" +#include "ipc_sock.h" + +#define DEFAULT_SHM_NAME "/osmo-trx-ipc-driver-shm2" + +static void *tall_ctx; +struct ipc_sock_state *global_ipc_sock_state; + +/* 8 channels are plenty */ +struct ipc_sock_state *global_ctrl_socks[8]; +struct ipc_shm_io *ios_tx_to_device[8]; +struct ipc_shm_io *ios_rx_from_device[8]; + +void *shm; +void *global_dev; + +static struct ipc_shm_region *decoded_region; + +static const struct log_info_cat default_categories[] = { + [DMAIN] = { + .name = "DMAIN", + .color = NULL, + .description = "Main generic category", + .loglevel = LOGL_DEBUG,.enabled = 1, + }, + [DDEV] = { + .name = "DDEV", + .description = "Device/Driver specific code", + .color = NULL, + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +const struct log_info log_infox = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +#include "uhdwrap.h" + +volatile int ipc_exit_requested = 0; + +static int ipc_shm_setup(const char *shm_name, size_t shm_len) +{ + int fd; + int rc; + + LOGP(DMAIN, LOGL_NOTICE, "Opening shm path %s\n", shm_name); + if ((fd = shm_open(shm_name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) { + LOGP(DMAIN, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_shm_open; + } + + LOGP(DMAIN, LOGL_NOTICE, "Truncating %d to size %zu\n", fd, shm_len); + if (ftruncate(fd, shm_len) < 0) { + LOGP(DMAIN, LOGL_ERROR, "ftruncate %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + + LOGP(DMAIN, LOGL_NOTICE, "mmaping shared memory fd %d\n", fd); + if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + LOGP(DMAIN, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + + LOGP(DMAIN, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm); + /* After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping. */ + close(fd); + return 0; +err_mmap: + shm_unlink(shm_name); + close(fd); +err_shm_open: + return rc; +} + +struct msgb *ipc_msgb_alloc(uint8_t msg_type) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + msg = msgb_alloc(sizeof(struct ipc_sk_if) + 1000, "ipc_sock_tx"); + if (!msg) + return NULL; + msgb_put(msg, sizeof(struct ipc_sk_if) + 1000); + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->msg_type = msg_type; + + return msg; +} + +static int ipc_tx_greeting_cnf(uint8_t req_version) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->u.greeting_cnf.req_version = req_version; + + return ipc_sock_send(msg); +} + +static int ipc_tx_info_cnf() +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_if *)msg->data; + + uhdwrap_fill_info_cnf(ipc_prim); + + return ipc_sock_send(msg); +} + +static int ipc_tx_open_cnf(int rc, uint32_t num_chans, int32_t timingoffset) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + struct ipc_sk_if_open_cnf_chan *chan_info; + unsigned int i; + + msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_if *)msg->data; + ipc_prim->u.open_cnf.return_code = rc; + ipc_prim->u.open_cnf.path_delay = timingoffset; // 6.18462e-5 * 1625e3 / 6; + OSMO_STRLCPY_ARRAY(ipc_prim->u.open_cnf.shm_name, DEFAULT_SHM_NAME); + + chan_info = ipc_prim->u.open_cnf.chan_info; + for (i = 0; i < num_chans; i++) { + snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path), "%s_%d", + IPC_SOCK_PATH_PREFIX, i); + /* FIXME: dynamc chan limit, currently 8 */ + if (i < 8) + ipc_sock_init(chan_info->chan_ipc_sk_path, &global_ctrl_socks[i], ipc_chan_sock_accept, i); + chan_info++; + } + + return ipc_sock_send(msg); +} + +int ipc_rx_greeting_req(struct ipc_sk_if_greeting *greeting_req) +{ + if (greeting_req->req_version == IPC_SOCK_API_VERSION) + ipc_tx_greeting_cnf(IPC_SOCK_API_VERSION); + else + ipc_tx_greeting_cnf(0); + return 0; +} + +int ipc_rx_info_req(struct ipc_sk_if_info_req *info_req) +{ + ipc_tx_info_cnf(); + return 0; +} + +int ipc_rx_open_req(struct ipc_sk_if_open_req *open_req) +{ + /* calculate size needed */ + unsigned int len; + + global_dev = uhdwrap_open(open_req); + + /* b210 packet size is 2040, but our tx size is 2500, so just do *2 */ + int shmbuflen = uhdwrap_get_bufsizerx(global_dev) * 2; + + len = ipc_shm_encode_region(NULL, open_req->num_chans, 4, shmbuflen); + /* Here we verify num_chans, rx_path, tx_path, clockref, etc. */ + int rc = ipc_shm_setup(DEFAULT_SHM_NAME, len); + len = ipc_shm_encode_region((struct ipc_shm_raw_region *)shm, open_req->num_chans, 4, shmbuflen); + // LOGP(DMAIN, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *)shm, 80)); + + /* set up our own copy of the decoded area, we have to do it here, + * since the uhd wrapper does not allow starting single channels + * additionally go for the producer init for both, so only we are responsible for the init, instead + * of splitting it with the client and causing potential races if one side uses it too early */ + decoded_region = ipc_shm_decode_region(0, (struct ipc_shm_raw_region *)shm); + for (unsigned int i = 0; i < open_req->num_chans; i++) { + // ios_tx_to_device[i] = ipc_shm_init_consumer(decoded_region->channels[i]->dl_stream); + ios_tx_to_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->dl_stream); + ios_rx_from_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->ul_stream); + } + + ipc_tx_open_cnf(-rc, open_req->num_chans, uhdwrap_get_timingoffset(global_dev)); + return 0; +} + +volatile bool ul_running = false; +volatile bool dl_running = false; + +void *uplink_thread(void *x_void_ptr) +{ + uint32_t chann = decoded_region->num_chans; + ul_running = true; + pthread_setname_np(pthread_self(), "uplink_rx"); + + while (!ipc_exit_requested) { + int32_t read = uhdwrap_read(global_dev, chann); + if (read < 0) + return 0; + } + return 0; +} + +void *downlink_thread(void *x_void_ptr) +{ + int chann = decoded_region->num_chans; + dl_running = true; + pthread_setname_np(pthread_self(), "downlink_tx"); + + while (!ipc_exit_requested) { + bool underrun; + uhdwrap_write(global_dev, chann, &underrun); + } + return 0; +} + +int ipc_rx_chan_start_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + int rc = 0; + + rc = uhdwrap_start(global_dev, chan_nr); + + /* no per-chan start/stop */ + if (!dl_running || !ul_running) { + /* chan != first chan start will "fail", which is fine, usrp can't start/stop chans independently */ + if (rc) { + LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads.. req for chan:%d\n", chan_nr); + pthread_t rx, tx; + pthread_create(&rx, NULL, uplink_thread, 0); + pthread_create(&tx, NULL, downlink_thread, 0); + } + } else + LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads request ignored.. req for chan:%d\n", chan_nr); + + msg = ipc_msgb_alloc(IPC_IF_MSG_START_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.start_cnf.return_code = rc ? 0 : -1; + + return ipc_chan_sock_send(msg, chan_nr); +} +int ipc_rx_chan_stop_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + int rc; + + /* no per-chan start/stop */ + rc = uhdwrap_stop(global_dev, chan_nr); + + msg = ipc_msgb_alloc(IPC_IF_MSG_STOP_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.stop_cnf.return_code = rc ? 0 : -1; + + return ipc_chan_sock_send(msg, chan_nr); +} +int ipc_rx_chan_setgain_req(struct ipc_sk_chan_if_gain *req, uint8_t chan_nr) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + double rv; + + rv = uhdwrap_set_gain(global_dev, req->gain, chan_nr, req->is_tx); + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETGAIN_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_gain_cnf.is_tx = req->is_tx; + ipc_prim->u.set_gain_cnf.gain = rv; + + return ipc_chan_sock_send(msg, chan_nr); +} + +int ipc_rx_chan_setfreq_req(struct ipc_sk_chan_if_freq_req *req, uint8_t chan_nr) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + bool rv; + + rv = uhdwrap_set_freq(global_dev, req->freq, chan_nr, req->is_tx); + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.set_freq_cnf.return_code = rv ? 0 : 1; + + return ipc_chan_sock_send(msg, chan_nr); +} + +int ipc_rx_chan_settxatten_req(struct ipc_sk_chan_if_tx_attenuation *req, uint8_t chan_nr) +{ + struct msgb *msg; + struct ipc_sk_chan_if *ipc_prim; + double rv; + + rv = uhdwrap_set_txatt(global_dev, req->attenuation, chan_nr); + + msg = ipc_msgb_alloc(IPC_IF_MSG_SETTXATTN_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + ipc_prim->u.txatten_cnf.attenuation = rv; + + return ipc_chan_sock_send(msg, chan_nr); +} + +int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var, + int (*sock_callback_fn)(struct osmo_fd *fd, unsigned int what), int n) +{ + struct ipc_sock_state *state; + struct osmo_fd *bfd; + int rc; + + state = talloc_zero(NULL, struct ipc_sock_state); + if (!state) + return -ENOMEM; + *global_state_var = state; + + INIT_LLIST_HEAD(&state->upqueue); + state->conn_bfd.fd = -1; + + bfd = &state->listen_bfd; + + bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND); + if (bfd->fd < 0) { + LOGP(DMAIN, LOGL_ERROR, "Could not create %s unix socket: %s\n", path, strerror(errno)); + talloc_free(state); + return -1; + } + + bfd->when = BSC_FD_READ; + bfd->cb = sock_callback_fn; + bfd->data = state; + bfd->priv_nr = n; + + rc = osmo_fd_register(bfd); + if (rc < 0) { + LOGP(DMAIN, LOGL_ERROR, "Could not register listen fd: %d\n", rc); + close(bfd->fd); + talloc_free(state); + return rc; + } + + LOGP(DMAIN, LOGL_INFO, "Started listening on IPC socket: %s\n", path); + + return 0; +} + +static void print_help(void) +{ + printf("ipc-driver-test Usage:\n" + " -h --help This message\n" + " -n --sock-num NR Master socket suffix number NR\n"); +} + +static int msocknum = 0; + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + const struct option long_options[] = { { "help", 0, 0, 'h' }, + { "sock-num", 1, 0, 'n' }, + { 0, 0, 0, 0 } }; + + c = getopt_long(argc, argv, "hn:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + break; + case 'n': + msocknum = atoi(optarg); + break; + default: + exit(2); + break; + } + } + + if (argc > optind) { + fprintf(stderr, "Unsupported positional arguments on command line\n"); + exit(2); + } +} + +int main(int argc, char **argv) +{ + char ipc_msock_path[sizeof(IPC_SOCK_PATH_PREFIX) + 3]; + tall_ctx = talloc_named_const(NULL, 0, "OsmoTRX"); + msgb_talloc_ctx_init(tall_ctx, 0); + osmo_init_logging2(tall_ctx, &log_infox); + log_enable_multithread(); + + handle_options(argc, argv); + snprintf(ipc_msock_path, sizeof(ipc_msock_path), "%s%d", IPC_SOCK_PATH_PREFIX, msocknum); + + LOGP(DMAIN, LOGL_INFO, "Starting %s\n", argv[0]); + ipc_sock_init(ipc_msock_path, &global_ipc_sock_state, ipc_sock_accept, 0); + while (!ipc_exit_requested) + osmo_select_main(0); + + if (global_dev) + for (unsigned int i = 0; i < decoded_region->num_chans; i++) + uhdwrap_stop(global_dev, i); + + ipc_sock_close(global_ipc_sock_state); + return 0; +} diff --git a/Transceiver52M/device/ipc/ipc-driver-test.h b/Transceiver52M/device/ipc/ipc-driver-test.h new file mode 100644 index 00000000..d5b4591a --- /dev/null +++ b/Transceiver52M/device/ipc/ipc-driver-test.h @@ -0,0 +1,45 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#pragma once + +#include +#include "shm.h" + +extern struct ipc_sock_state *global_ipc_sock_state; + +/* 8 channels are plenty */ +extern struct ipc_sock_state *global_ctrl_socks[8]; +extern struct ipc_shm_io *ios_tx_to_device[8]; +extern struct ipc_shm_io *ios_rx_from_device[8]; + +struct ipc_sock_state { + struct osmo_fd listen_bfd; /* fd for listen socket */ + struct osmo_fd conn_bfd; /* fd for connection */ + struct llist_head upqueue; /* queue for sending messages */ +}; + +int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var, + int (*sock_callback_fn)(struct osmo_fd *fd, unsigned int what), int n); + +int ipc_rx_greeting_req(struct ipc_sk_if_greeting *greeting_req); +int ipc_rx_info_req(struct ipc_sk_if_info_req *info_req); +int ipc_rx_open_req(struct ipc_sk_if_open_req *open_req); + +int ipc_rx_chan_start_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr); +int ipc_rx_chan_stop_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr); +int ipc_rx_chan_setgain_req(struct ipc_sk_chan_if_gain *req, uint8_t chan_nr); +int ipc_rx_chan_setfreq_req(struct ipc_sk_chan_if_freq_req *req, uint8_t chan_nr); +int ipc_rx_chan_settxatten_req(struct ipc_sk_chan_if_tx_attenuation *req, uint8_t chan_nr); diff --git a/Transceiver52M/device/ipc/ipc_chan.c b/Transceiver52M/device/ipc/ipc_chan.c new file mode 100644 index 00000000..9e35fb22 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_chan.c @@ -0,0 +1,259 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For mode constants */ +#include /* For O_* constants */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shm.h" +#include "ipc-driver-test.h" +#include "ipc_chan.h" +#include "ipc_sock.h" + +static int ipc_chan_rx(uint8_t msg_type, struct ipc_sk_chan_if *ipc_prim, uint8_t chan_nr) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_START_REQ: + rc = ipc_rx_chan_start_req(&ipc_prim->u.start_req, chan_nr); + break; + case IPC_IF_MSG_STOP_REQ: + rc = ipc_rx_chan_stop_req(&ipc_prim->u.stop_req, chan_nr); + break; + case IPC_IF_MSG_SETGAIN_REQ: + rc = ipc_rx_chan_setgain_req(&ipc_prim->u.set_gain_req, chan_nr); + break; + case IPC_IF_MSG_SETFREQ_REQ: + rc = ipc_rx_chan_setfreq_req(&ipc_prim->u.set_freq_req, chan_nr); + break; + case IPC_IF_MSG_SETTXATTN_REQ: + rc = ipc_rx_chan_settxatten_req(&ipc_prim->u.txatten_req, chan_nr); + break; + default: + LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type 0x%02x on chan %d\n", msg_type, chan_nr); + rc = -EINVAL; + } + + return rc; +} + +static int ipc_chan_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct ipc_sk_chan_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_chan_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_chan_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if (rc < (int)sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_chan_rx(ipc_prim->msg_type, ipc_prim, bfd->priv_nr); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(state); + return -1; +} + +int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr) +{ + struct ipc_sock_state *state = global_ctrl_socks[chan_nr]; + struct osmo_fd *conn_bfd; + + if (!state) + return -EINVAL; + + if (!state) { + LOGP(DDEV, LOGL_INFO, + "IPC socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DDEV, LOGL_NOTICE, + "IPC socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + conn_bfd->when |= BSC_FD_WRITE; + + return 0; +} + +static int ipc_chan_sock_write(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_chan_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_chan_if *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can dequeue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(state); + return -1; +} + +static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = ipc_chan_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = ipc_chan_sock_write(bfd); + + return rc; +} + +int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *)&un_addr, &len); + if (rc < 0) { + LOGP(DDEV, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DDEV, LOGL_NOTICE, + "osmo-trx connects but we already have " + "another active connection ?!?\n"); + /* We already have one IPC connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = ipc_chan_sock_cb; + conn_bfd->data = state; + + /* copy chan nr, required for proper bfd<->chan # mapping */ + conn_bfd->priv_nr = bfd->priv_nr; + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DDEV, LOGL_ERROR, + "Failed to register new connection " + "fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DDEV, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n"); + + return 0; +} diff --git a/Transceiver52M/device/ipc/ipc_chan.h b/Transceiver52M/device/ipc/ipc_chan.h new file mode 100644 index 00000000..d606cce6 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_chan.h @@ -0,0 +1,25 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#ifndef IPC_CHAN_H +#define IPC_CHAN_H + +#include "shm.h" +#include "ipc-driver-test.h" + +int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr); +int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags); + +#endif // IPC_CHAN_H diff --git a/Transceiver52M/device/ipc/ipc_shm.c b/Transceiver52M/device/ipc/ipc_shm.c new file mode 100644 index 00000000..ff70975c --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_shm.c @@ -0,0 +1,200 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Eric Wild +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "ipc_shm.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +} +#endif + +#define SAMPLE_SIZE_BYTE (sizeof(uint16_t) * 2) + +struct ipc_shm_io *ipc_shm_init_consumer(struct ipc_shm_stream *s) +{ + unsigned int i; + + struct ipc_shm_io *r = (struct ipc_shm_io *)malloc(sizeof(struct ipc_shm_io)); + r->this_stream = s->raw; + r->buf_ptrs = + (volatile struct ipc_shm_raw_smpl_buf **)malloc(sizeof(struct ipc_shm_raw_smpl_buf *) * s->num_buffers); + + /* save actual ptrs */ + for (i = 0; i < s->num_buffers; i++) + r->buf_ptrs[i] = s->buffers[i]; + + r->partial_read_begin_ptr = 0; + return r; +} + +struct ipc_shm_io *ipc_shm_init_producer(struct ipc_shm_stream *s) +{ + int rv; + pthread_mutexattr_t att; + pthread_condattr_t t1, t2; + struct ipc_shm_io *r = ipc_shm_init_consumer(s); + rv = pthread_mutexattr_init(&att); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_mutexattr_setrobust(&att, PTHREAD_MUTEX_ROBUST); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_mutexattr_setpshared(&att, PTHREAD_PROCESS_SHARED); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_mutex_init((pthread_mutex_t *)&r->this_stream->lock, &att); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + pthread_mutexattr_destroy(&att); + + rv = pthread_condattr_setpshared(&t1, PTHREAD_PROCESS_SHARED); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_condattr_setpshared(&t2, PTHREAD_PROCESS_SHARED); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->cf, &t1); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->ce, &t2); + if (rv != 0) { + osmo_panic("%s:%d rv:%d", __FILE__, __LINE__, rv); + } + + pthread_condattr_destroy(&t1); + pthread_condattr_destroy(&t2); + + r->this_stream->read_next = 0; + r->this_stream->write_next = 0; + return r; +} + +void ipc_shm_close(struct ipc_shm_io *r) +{ + if (r) { + free(r->buf_ptrs); + free(r); + } +} + +int32_t ipc_shm_enqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data) +{ + volatile struct ipc_shm_raw_smpl_buf *buf; + int32_t rv; + struct timespec tv; + clock_gettime(CLOCK_REALTIME, &tv); + tv.tv_sec += 1; + + rv = pthread_mutex_timedlock((pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + while (((r->this_stream->write_next + 1) & (r->this_stream->num_buffers - 1)) == r->this_stream->read_next && + rv == 0) + rv = pthread_cond_timedwait((pthread_cond_t *)&r->this_stream->ce, + (pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + buf = r->buf_ptrs[r->this_stream->write_next]; + buf->timestamp = timestamp; + + rv = len_in_sps <= r->this_stream->buffer_size ? len_in_sps : r->this_stream->buffer_size; + + memcpy((void *)buf->samples, data, SAMPLE_SIZE_BYTE * rv); + buf->data_len = rv; + + r->this_stream->write_next = (r->this_stream->write_next + 1) & (r->this_stream->num_buffers - 1); + + pthread_cond_signal((pthread_cond_t *)&r->this_stream->cf); + pthread_mutex_unlock((pthread_mutex_t *)&r->this_stream->lock); + + return rv; +} + +int32_t ipc_shm_read(struct ipc_shm_io *r, uint16_t *out_buf, uint32_t num_samples, uint64_t *timestamp, + uint32_t timeout_seconds) +{ + volatile struct ipc_shm_raw_smpl_buf *buf; + int32_t rv; + uint8_t freeflag = 0; + struct timespec tv; + clock_gettime(CLOCK_REALTIME, &tv); + tv.tv_sec += timeout_seconds; + + rv = pthread_mutex_timedlock((pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + while (r->this_stream->write_next == r->this_stream->read_next && rv == 0) + rv = pthread_cond_timedwait((pthread_cond_t *)&r->this_stream->cf, + (pthread_mutex_t *)&r->this_stream->lock, &tv); + if (rv != 0) + return -rv; + + buf = r->buf_ptrs[r->this_stream->read_next]; + if (buf->data_len <= num_samples) { + memcpy(out_buf, (void *)&buf->samples[r->partial_read_begin_ptr * 2], SAMPLE_SIZE_BYTE * buf->data_len); + r->partial_read_begin_ptr = 0; + rv = buf->data_len; + buf->data_len = 0; + r->this_stream->read_next = (r->this_stream->read_next + 1) & (r->this_stream->num_buffers - 1); + freeflag = 1; + + } else /*if (buf->data_len > num_samples)*/ { + memcpy(out_buf, (void *)&buf->samples[r->partial_read_begin_ptr * 2], SAMPLE_SIZE_BYTE * num_samples); + r->partial_read_begin_ptr += num_samples; + buf->data_len -= num_samples; + rv = num_samples; + } + + *timestamp = buf->timestamp; + buf->timestamp += rv; + + if (freeflag) + pthread_cond_signal((pthread_cond_t *)&r->this_stream->ce); + + pthread_mutex_unlock((pthread_mutex_t *)&r->this_stream->lock); + + return rv; +} diff --git a/Transceiver52M/device/ipc/ipc_shm.h b/Transceiver52M/device/ipc/ipc_shm.h new file mode 100644 index 00000000..348e7038 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_shm.h @@ -0,0 +1,45 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Eric Wild +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#ifndef IPC_SHM_H +#define IPC_SHM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef __cplusplus +} +#endif + +struct ipc_shm_io { + volatile struct ipc_shm_raw_stream *this_stream; + volatile struct ipc_shm_raw_smpl_buf **volatile buf_ptrs; + uint32_t partial_read_begin_ptr; +}; + +struct ipc_shm_io *ipc_shm_init_consumer(struct ipc_shm_stream *s); +struct ipc_shm_io *ipc_shm_init_producer(struct ipc_shm_stream *s); +void ipc_shm_close(struct ipc_shm_io *r); +int32_t ipc_shm_enqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data); +int32_t ipc_shm_tryenqueue(struct ipc_shm_io *r, uint64_t timestamp, uint32_t len_in_sps, uint16_t *data); +volatile struct ipc_shm_raw_smpl_buf *ipc_shm_dequeue(struct ipc_shm_io *r); +int32_t ipc_shm_read(struct ipc_shm_io *r, uint16_t *out_buf, uint32_t num_samples, uint64_t *timestamp, + uint32_t timeout_seconds); + +#endif // IPC_SHM_H diff --git a/Transceiver52M/device/ipc/ipc_sock.c b/Transceiver52M/device/ipc/ipc_sock.c new file mode 100644 index 00000000..435dc3c3 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_sock.c @@ -0,0 +1,269 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For mode constants */ +#include /* For O_* constants */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shm.h" +#include "ipc-driver-test.h" + +extern volatile int ipc_exit_requested; +static int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_GREETING_REQ: + rc = ipc_rx_greeting_req(&ipc_prim->u.greeting_req); + break; + case IPC_IF_MSG_INFO_REQ: + rc = ipc_rx_info_req(&ipc_prim->u.info_req); + break; + case IPC_IF_MSG_OPEN_REQ: + rc = ipc_rx_open_req(&ipc_prim->u.open_req); + break; + default: + LOGP(DDEV, LOGL_ERROR, "Received unknown IPC msg type 0x%02x\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +int ipc_sock_send(struct msgb *msg) +{ + struct ipc_sock_state *state = global_ipc_sock_state; + struct osmo_fd *conn_bfd; + + if (!state) { + LOGP(DDEV, LOGL_INFO, + "IPC socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DDEV, LOGL_NOTICE, + "IPC socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + conn_bfd->when |= BSC_FD_WRITE; + + return 0; +} + +void ipc_sock_close(struct ipc_sock_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + + LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n"); + + ipc_exit_requested = 1; + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + state->listen_bfd.when |= BSC_FD_READ; + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +int ipc_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct ipc_sk_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_if *)msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if (rc < (int)sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, + "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", + rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_rx(ipc_prim->msg_type, ipc_prim); + + /* as we always synchronously process the message in IPC_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(state); + return -1; +} + +static int ipc_sock_write(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_if *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, + "message type (%d) with ZERO " + "bytes!\n", + ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + + dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(state); + return -1; +} + +static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = ipc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = ipc_sock_write(bfd); + + return rc; +} + +/* accept connection coming from IPC */ +int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *)&un_addr, &len); + if (rc < 0) { + LOGP(DDEV, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DDEV, LOGL_NOTICE, + "ip clent connects but we already have " + "another active connection ?!?\n"); + /* We already have one IPC connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = ipc_sock_cb; + conn_bfd->data = state; + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DDEV, LOGL_ERROR, + "Failed to register new connection " + "fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DDEV, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n"); + + return 0; +} diff --git a/Transceiver52M/device/ipc/ipc_sock.h b/Transceiver52M/device/ipc/ipc_sock.h new file mode 100644 index 00000000..918e70a1 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc_sock.h @@ -0,0 +1,26 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#ifndef IPC_SOCK_H +#define IPC_SOCK_H + +#include "shm.h" +#include "ipc-driver-test.h" + +int ipc_sock_send(struct msgb *msg); +int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags); +void ipc_sock_close(struct ipc_sock_state *state); + +#endif // IPC_SOCK_H diff --git a/Transceiver52M/device/ipc/shm.c b/Transceiver52M/device/ipc/shm.c new file mode 100644 index 00000000..1e78b974 --- /dev/null +++ b/Transceiver52M/device/ipc/shm.c @@ -0,0 +1,149 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include +#include +#include + +#include "shm.h" + +#define ENCDECDEBUG(...) //fprintf(stderr, __VA_ARGS__) + +/* Convert offsets to pointers */ +struct ipc_shm_stream *ipc_shm_decode_stream(void *tall_ctx, struct ipc_shm_raw_region *root_raw, + struct ipc_shm_raw_stream *stream_raw) +{ + unsigned int i; + struct ipc_shm_stream *stream; + stream = talloc_zero(tall_ctx, struct ipc_shm_stream); + stream = talloc_zero_size(tall_ctx, sizeof(struct ipc_shm_stream) + + sizeof(struct ipc_shm_raw_smpl_buf *) * stream_raw->num_buffers); + if (!stream) + return NULL; + stream->num_buffers = stream_raw->num_buffers; + stream->buffer_size = stream_raw->buffer_size; + stream->raw = stream_raw; + for (i = 0; i < stream->num_buffers; i++) { + ENCDECDEBUG("decode: smpl_buf %d at offset %u\n", i, stream_raw->buffer_offset[i]); + stream->buffers[i] = + (struct ipc_shm_raw_smpl_buf *)(((uint8_t *)root_raw) + stream_raw->buffer_offset[i]); + } + return stream; +} + +struct ipc_shm_channel *ipc_shm_decode_channel(void *tall_ctx, struct ipc_shm_raw_region *root_raw, + struct ipc_shm_raw_channel *chan_raw) +{ + struct ipc_shm_channel *chan; + chan = talloc_zero(tall_ctx, struct ipc_shm_channel); + if (!chan) + return NULL; + ENCDECDEBUG("decode: streams at offset %u and %u\n", chan_raw->dl_buf_offset, chan_raw->ul_buf_offset); + chan->dl_stream = ipc_shm_decode_stream( + chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t *)root_raw) + chan_raw->dl_buf_offset)); + chan->ul_stream = ipc_shm_decode_stream( + chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t *)root_raw) + chan_raw->ul_buf_offset)); + return chan; +} +struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw) +{ + unsigned int i; + struct ipc_shm_region *root; + root = talloc_zero_size(tall_ctx, + sizeof(struct ipc_shm_region) + sizeof(struct ipc_shm_channel *) * root_raw->num_chans); + if (!root) + return NULL; + + root->num_chans = root_raw->num_chans; + for (i = 0; i < root->num_chans; i++) { + ENCDECDEBUG("decode: channel %d at offset %u\n", i, root_raw->chan_offset[i]); + root->channels[i] = ipc_shm_decode_channel( + root, root_raw, + (struct ipc_shm_raw_channel *)(((uint8_t *)root_raw) + root_raw->chan_offset[i])); + } + return root; +} + +unsigned int ipc_shm_encode_smpl_buf(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_smpl_buf *smpl_buf_raw, + uint32_t buffer_size) +{ + unsigned int offset = sizeof(struct ipc_shm_raw_smpl_buf); + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + ENCDECDEBUG("encode: smpl_buf at offset %u\n", offset); + offset += buffer_size * sizeof(uint16_t) * 2; /* samples */ + return offset; +} + +unsigned int ipc_shm_encode_stream(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_stream *stream_raw, + uint32_t num_buffers, uint32_t buffer_size) +{ + unsigned int i; + ptrdiff_t start = (ptrdiff_t)stream_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_stream) + sizeof(uint32_t) * num_buffers; + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + ENCDECDEBUG("encode: stream at offset %lu\n", (start - (ptrdiff_t)root_raw)); + if (root_raw) { + stream_raw->num_buffers = num_buffers; + stream_raw->buffer_size = buffer_size; + stream_raw->read_next = 0; + stream_raw->write_next = 0; + } + for (i = 0; i < num_buffers; i++) { + if (root_raw) + stream_raw->buffer_offset[i] = (start + offset - (ptrdiff_t)root_raw); + offset += + ipc_shm_encode_smpl_buf(root_raw, (struct ipc_shm_raw_smpl_buf *)(start + offset), buffer_size); + } + return offset; +} +unsigned int ipc_shm_encode_channel(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_channel *chan_raw, + uint32_t num_buffers, uint32_t buffer_size) +{ + uint8_t *start = (uint8_t *)chan_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_channel); + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + ENCDECDEBUG("encode: channel at offset %lu\n", (start - (uint8_t *)root_raw)); + if (root_raw) + chan_raw->dl_buf_offset = (start + offset - (uint8_t *)root_raw); + offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers, + buffer_size); + if (root_raw) + chan_raw->ul_buf_offset = (start + offset - (uint8_t *)root_raw); + offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers, + buffer_size); + return offset; +} +/* if root_raw is NULL, then do a dry run, aka only calculate final offset */ +unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers, + uint32_t buffer_size) +{ + unsigned i; + uintptr_t start = (uintptr_t)root_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_region) + sizeof(uint32_t) * num_chans; + offset = (((uintptr_t)offset + 7) & ~0x07ULL); + + if (root_raw) + root_raw->num_chans = num_chans; + for (i = 0; i < num_chans; i++) { + if (root_raw) + root_raw->chan_offset[i] = (start + offset - (uintptr_t)root_raw); + ENCDECDEBUG("encode: channel %d chan_offset[i]=%lu\n", i, start + offset - (uintptr_t)root_raw); + offset += ipc_shm_encode_channel(root_raw, (struct ipc_shm_raw_channel *)(start + offset), num_buffers, + buffer_size); + } + //TODO: pass maximum size and verify we didn't go through + return offset; +} diff --git a/Transceiver52M/device/ipc/shm.h b/Transceiver52M/device/ipc/shm.h new file mode 100644 index 00000000..fcddd548 --- /dev/null +++ b/Transceiver52M/device/ipc/shm.h @@ -0,0 +1,236 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Pau Espin Pedrol +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#pragma once + +#include +#include +#include +#include +#include + +/* RAW structures */ +struct ipc_shm_raw_smpl_buf { + uint64_t timestamp; + uint32_t data_len; /* In samples */ + uint16_t samples[0]; +}; + +struct ipc_shm_raw_stream { + pthread_mutex_t lock; /* protects this struct */ + pthread_cond_t cf; /* signals fill to reader */ + pthread_cond_t ce; /* signals empty nbuf to writer */ + uint32_t num_buffers; + uint32_t buffer_size; /* In samples */ + uint32_t read_next; + uint32_t write_next; + uint32_t buffer_offset[0]; + //struct ipc_shm_smpl_buf buffers[0]; +}; + +struct ipc_shm_raw_channel { + uint32_t dl_buf_offset; + uint32_t ul_buf_offset; +}; + +struct ipc_shm_raw_region { + uint32_t num_chans; + uint32_t chan_offset[0]; +}; + +/* non-raw, Pointer converted structures */ +struct ipc_shm_stream { + uint32_t num_buffers; + uint32_t buffer_size; + volatile struct ipc_shm_raw_stream *raw; + volatile struct ipc_shm_raw_smpl_buf *buffers[0]; +}; + +struct ipc_shm_channel { + struct ipc_shm_stream *dl_stream; + struct ipc_shm_stream *ul_stream; +}; + +/* Pointer converted structures */ +struct ipc_shm_region { + uint32_t num_chans; + struct ipc_shm_channel *channels[0]; +}; + +unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers, + uint32_t buffer_size); +struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw); +/****************************************/ +/* UNIX SOCKET API */ +/****************************************/ + +////////////////// +// Master socket +////////////////// + +#define IPC_SOCK_PATH_PREFIX "/tmp/ipc_sock" +#define IPC_SOCK_API_VERSION 1 + +/* msg_type */ +#define IPC_IF_MSG_GREETING_REQ 0x00 +#define IPC_IF_MSG_GREETING_CNF 0x01 +#define IPC_IF_MSG_INFO_REQ 0x02 +#define IPC_IF_MSG_INFO_CNF 0x03 +#define IPC_IF_MSG_OPEN_REQ 0x04 +#define IPC_IF_MSG_OPEN_CNF 0x05 + +#define MAX_NUM_CHANS 30 +#define RF_PATH_NAME_SIZE 25 +#define MAX_NUM_RF_PATHS 10 +#define SHM_NAME_MAX NAME_MAX /* 255 */ + +#define FEATURE_MASK_CLOCKREF_INTERNAL (0x1 << 0) +#define FEATURE_MASK_CLOCKREF_EXTERNAL (0x1 << 1) +struct ipc_sk_if_info_chan { + char tx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE]; + char rx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE]; + double min_rx_gain; + double max_rx_gain; + double min_tx_gain; + double max_tx_gain; + double nominal_tx_power; /* dBm */ +} __attribute__((packed)); + +struct ipc_sk_if_open_req_chan { + char tx_path[RF_PATH_NAME_SIZE]; + char rx_path[RF_PATH_NAME_SIZE]; +} __attribute__((packed)); + +struct ipc_sk_if_open_cnf_chan { + char chan_ipc_sk_path[108]; +} __attribute__((packed)); + +struct ipc_sk_if_greeting { + uint8_t req_version; +} __attribute__((packed)); + +struct ipc_sk_if_info_req { + uint8_t spare; +} __attribute__((packed)); + +struct ipc_sk_if_info_cnf { + uint32_t feature_mask; + double iq_scaling_val_rx; /* for scaling, sample format is 16 bit, but adc/dac might be less */ + double iq_scaling_val_tx; + uint32_t max_num_chans; + char dev_desc[200]; + struct ipc_sk_if_info_chan chan_info[MAX_NUM_CHANS]; +} __attribute__((packed)); + +struct ipc_sk_if_open_req { + uint32_t num_chans; + uint32_t clockref; /* One of FEATUER_MASK_CLOCKREF_* */ + uint32_t rx_sample_freq_num; + uint32_t rx_sample_freq_den; + uint32_t tx_sample_freq_num; + uint32_t tx_sample_freq_den; + uint32_t bandwidth; + struct ipc_sk_if_open_req_chan chan_info[MAX_NUM_CHANS]; +} __attribute__((packed)); + +struct ipc_sk_if_open_cnf { + uint8_t return_code; + uint32_t path_delay; + char shm_name[SHM_NAME_MAX]; + struct ipc_sk_if_open_cnf_chan chan_info[MAX_NUM_CHANS]; +} __attribute__((packed)); + +struct ipc_sk_if { + uint8_t msg_type; /* message type */ + uint8_t spare[2]; + + union { + struct ipc_sk_if_greeting greeting_req; + struct ipc_sk_if_greeting greeting_cnf; + struct ipc_sk_if_info_req info_req; + struct ipc_sk_if_info_cnf info_cnf; + struct ipc_sk_if_open_req open_req; + struct ipc_sk_if_open_cnf open_cnf; + } u; +} __attribute__((packed)); + +////////////////// +// Channel socket +////////////////// +#define IPC_IF_CHAN_MSG_OFFSET 50 +#define IPC_IF_MSG_START_REQ IPC_IF_CHAN_MSG_OFFSET + 0 +#define IPC_IF_MSG_START_CNF IPC_IF_CHAN_MSG_OFFSET + 1 +#define IPC_IF_MSG_STOP_REQ IPC_IF_CHAN_MSG_OFFSET + 2 +#define IPC_IF_MSG_STOP_CNF IPC_IF_CHAN_MSG_OFFSET + 3 +#define IPC_IF_MSG_SETGAIN_REQ IPC_IF_CHAN_MSG_OFFSET + 4 +#define IPC_IF_MSG_SETGAIN_CNF IPC_IF_CHAN_MSG_OFFSET + 5 +#define IPC_IF_MSG_SETFREQ_REQ IPC_IF_CHAN_MSG_OFFSET + 6 +#define IPC_IF_MSG_SETFREQ_CNF IPC_IF_CHAN_MSG_OFFSET + 7 + +#define IPC_IF_NOTIFY_UNDERFLOW IPC_IF_CHAN_MSG_OFFSET + 8 +#define IPC_IF_NOTIFY_OVERFLOW IPC_IF_CHAN_MSG_OFFSET + 9 + +#define IPC_IF_MSG_SETTXATTN_REQ IPC_IF_CHAN_MSG_OFFSET + 10 +#define IPC_IF_MSG_SETTXATTN_CNF IPC_IF_CHAN_MSG_OFFSET + 11 + +struct ipc_sk_chan_if_op_void { + // at least one dummy byte, to allow c/c++ compatibility + uint8_t dummy; +} __attribute__((packed)); + +struct ipc_sk_chan_if_op_rc { + uint8_t return_code; +} __attribute__((packed)); + +struct ipc_sk_chan_if_gain { + double gain; + uint8_t is_tx; +} __attribute__((packed)); + +struct ipc_sk_chan_if_freq_req { + double freq; + uint8_t is_tx; +} __attribute__((packed)); + +struct ipc_sk_chan_if_freq_cnf { + uint8_t return_code; +} __attribute__((packed)); + +struct ipc_sk_chan_if_notfiy { + uint8_t dummy; +} __attribute__((packed)); + +struct ipc_sk_chan_if_tx_attenuation { + double attenuation; +} __attribute__((packed)); + +struct ipc_sk_chan_if { + uint8_t msg_type; /* message type */ + uint8_t spare[2]; + + union { + struct ipc_sk_chan_if_op_void start_req; + struct ipc_sk_chan_if_op_rc start_cnf; + struct ipc_sk_chan_if_op_void stop_req; + struct ipc_sk_chan_if_op_rc stop_cnf; + struct ipc_sk_chan_if_gain set_gain_req; + struct ipc_sk_chan_if_gain set_gain_cnf; + struct ipc_sk_chan_if_freq_req set_freq_req; + struct ipc_sk_chan_if_freq_cnf set_freq_cnf; + struct ipc_sk_chan_if_notfiy notify; + struct ipc_sk_chan_if_tx_attenuation txatten_req; + struct ipc_sk_chan_if_tx_attenuation txatten_cnf; + } u; +} __attribute__((packed)); diff --git a/Transceiver52M/device/ipc/uhdwrap.cpp b/Transceiver52M/device/ipc/uhdwrap.cpp new file mode 100644 index 00000000..130f80a4 --- /dev/null +++ b/Transceiver52M/device/ipc/uhdwrap.cpp @@ -0,0 +1,256 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Eric Wild +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shm.h" +#include "ipc_shm.h" +#include "ipc-driver-test.h" +} +#include "../uhd/UHDDevice.h" +#include "uhdwrap.h" + +#include "trx_vty.h" +#include "Logger.h" +#include "Threads.h" +#include "Utils.h" + +int uhd_wrap::open(const std::string &args, int ref, bool swap_channels) +{ + int rv = uhd_device::open(args, ref, swap_channels); + samps_per_buff_rx = rx_stream->get_max_num_samps(); + samps_per_buff_tx = tx_stream->get_max_num_samps(); + channel_count = usrp_dev->get_rx_num_channels(); + + wrap_rx_buffs = std::vector >(channel_count, std::vector(2 * samps_per_buff_rx)); + for (size_t i = 0; i < wrap_rx_buffs.size(); i++) + wrap_rx_buf_ptrs.push_back(&wrap_rx_buffs[i].front()); + + wrap_tx_buffs = std::vector >(channel_count, std::vector(2 * 5000)); + for (size_t i = 0; i < wrap_tx_buffs.size(); i++) + wrap_tx_buf_ptrs.push_back(&wrap_tx_buffs[i].front()); + + return rv; +} + +uhd_wrap::~uhd_wrap() +{ + // drvtest::gshutdown = 1; + //t->join(); +} + +size_t uhd_wrap::bufsizerx() +{ + return samps_per_buff_rx; +} + +size_t uhd_wrap::bufsizetx() +{ + return samps_per_buff_tx; +} + +int uhd_wrap::chancount() +{ + return channel_count; +} + +int uhd_wrap::wrap_read(TIMESTAMP *timestamp) +{ + uhd::rx_metadata_t md; + size_t num_rx_samps = rx_stream->recv(wrap_rx_buf_ptrs, samps_per_buff_rx, md, 0.1, true); + *timestamp = md.time_spec.to_ticks(rx_rate); + return num_rx_samps; //uhd_device::readSamples(bufs, len, overrun, timestamp, underrun); +} + +extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req) +{ + unsigned int rx_sps, tx_sps; + + /* FIXME: dev arg string* */ + /* FIXME: rx frontend bw? */ + /* FIXME: tx frontend bw? */ + ReferenceType cref; + switch (open_req->clockref) { + case FEATURE_MASK_CLOCKREF_EXTERNAL: + cref = ReferenceType::REF_EXTERNAL; + break; + case FEATURE_MASK_CLOCKREF_INTERNAL: + default: + cref = ReferenceType::REF_INTERNAL; + break; + } + + std::vector tx_paths; + std::vector rx_paths; + for (unsigned int i = 0; i < open_req->num_chans; i++) { + tx_paths.push_back(open_req->chan_info[i].tx_path); + rx_paths.push_back(open_req->chan_info[i].rx_path); + } + + /* FIXME: this is actually the sps value, not the sample rate! + * sample rate is looked up according to the sps rate by uhd backend */ + rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den; + tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den; + uhd_wrap *uhd_wrap_dev = + new uhd_wrap(tx_sps, rx_sps, RadioDevice::NORMAL, open_req->num_chans, 0.0, tx_paths, rx_paths); + uhd_wrap_dev->open("", cref, false); + + return uhd_wrap_dev; +} +extern "C" int32_t uhdwrap_get_bufsizerx(void *dev) +{ + uhd_wrap *d = (uhd_wrap *)dev; + return d->bufsizerx(); +} + +extern "C" int32_t uhdwrap_get_timingoffset(void *dev) +{ + uhd_wrap *d = (uhd_wrap *)dev; + return d->getTimingOffset(); +} + +extern "C" int32_t uhdwrap_read(void *dev, uint32_t num_chans) +{ + TIMESTAMP t; + uhd_wrap *d = (uhd_wrap *)dev; + + if (num_chans != d->wrap_rx_buf_ptrs.size()) { + perror("omg chans?!"); + } + + int32_t read = d->wrap_read(&t); + + /* multi channel rx on b210 will return 0 due to alignment adventures, do not put 0 samples into a ipc buffer... */ + if (read <= 0) + return read; + + for (uint32_t i = 0; i < num_chans; i++) { + ipc_shm_enqueue(ios_rx_from_device[i], t, read, (uint16_t *)&d->wrap_rx_buffs[i].front()); + } + return read; +} + +extern "C" int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun) +{ + uhd_wrap *d = (uhd_wrap *)dev; + + uint64_t timestamp; + int32_t len = -1; + for (uint32_t i = 0; i < num_chans; i++) { + len = ipc_shm_read(ios_tx_to_device[i], (uint16_t *)&d->wrap_tx_buffs[i].front(), 5000, ×tamp, 1); + if (len < 0) + return 0; + } + + return d->writeSamples(d->wrap_tx_buf_ptrs, len, underrun, timestamp); +} + +extern "C" double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx) +{ + uhd_wrap *d = (uhd_wrap *)dev; + if (for_tx) + return d->setTxFreq(f, chan); + else + return d->setRxFreq(f, chan); +} + +extern "C" double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx) +{ + uhd_wrap *d = (uhd_wrap *)dev; + // if (for_tx) + // return d->setTxGain(f, chan); + // else + return d->setRxGain(f, chan); +} + +extern "C" double uhdwrap_set_txatt(void *dev, double a, size_t chan) +{ + uhd_wrap *d = (uhd_wrap *)dev; + return d->setPowerAttenuation(a, chan); +} + +extern "C" int32_t uhdwrap_start(void *dev, int chan) +{ + uhd_wrap *d = (uhd_wrap *)dev; + return d->start(); +} + +extern "C" int32_t uhdwrap_stop(void *dev, int chan) +{ + uhd_wrap *d = (uhd_wrap *)dev; + return d->stop(); +} + +extern "C" void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim) +{ + struct ipc_sk_if_info_chan *chan_info; + + uhd::device_addr_t args(""); + uhd::device_addrs_t devs_found = uhd::device::find(args); + if (devs_found.size() < 1) { + std::cout << "\n No device found!"; + exit(0); + } + + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(devs_found[0]); + auto rxchans = usrp->get_rx_num_channels(); + auto txchans = usrp->get_tx_num_channels(); + auto rx_range = usrp->get_rx_gain_range(); + auto tx_range = usrp->get_tx_gain_range(); + + //auto nboards = usrp->get_num_mboards(); + auto refs = usrp->get_clock_sources(0); + auto devname = usrp->get_mboard_name(0); + + ipc_prim->u.info_cnf.feature_mask = 0; + if (std::find(refs.begin(), refs.end(), "internal") != refs.end()) + ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_INTERNAL; + if (std::find(refs.begin(), refs.end(), "external") != refs.end()) + ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_EXTERNAL; + + // at least one duplex channel + auto num_chans = rxchans == txchans ? txchans : 1; + + ipc_prim->u.info_cnf.iq_scaling_val_rx = 0.3; + ipc_prim->u.info_cnf.iq_scaling_val_tx = 1; + ipc_prim->u.info_cnf.max_num_chans = num_chans; + OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, devname.c_str()); + chan_info = ipc_prim->u.info_cnf.chan_info; + for (unsigned int i = 0; i < ipc_prim->u.info_cnf.max_num_chans; i++) { + auto rxant = usrp->get_rx_antennas(i); + auto txant = usrp->get_tx_antennas(i); + for (unsigned int j = 0; j < txant.size(); j++) { + OSMO_STRLCPY_ARRAY(chan_info->tx_path[j], txant[j].c_str()); + } + for (unsigned int j = 0; j < rxant.size(); j++) { + OSMO_STRLCPY_ARRAY(chan_info->rx_path[j], rxant[j].c_str()); + } + chan_info->min_rx_gain = rx_range.start(); + chan_info->max_rx_gain = rx_range.stop(); + chan_info->min_tx_gain = tx_range.start(); + chan_info->max_tx_gain = tx_range.stop(); + chan_info->nominal_tx_power = 7.5; // FIXME: would require uhd dev + freq info + chan_info++; + } +} diff --git a/Transceiver52M/device/ipc/uhdwrap.h b/Transceiver52M/device/ipc/uhdwrap.h new file mode 100644 index 00000000..e235fe7e --- /dev/null +++ b/Transceiver52M/device/ipc/uhdwrap.h @@ -0,0 +1,83 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH +* Author: Eric Wild +* +* SPDX-License-Identifier: 0BSD +* +* Permission to use, copy, modify, and/or distribute this software for any purpose +* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE +* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +* USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#ifndef IPC_UHDWRAP_H +#define IPC_UHDWRAP_H + +#ifdef __cplusplus +#include "../uhd/UHDDevice.h" + +class uhd_wrap : public uhd_device { + public: + // std::thread *t; + size_t samps_per_buff_rx; + size_t samps_per_buff_tx; + int channel_count; + + std::vector > wrap_rx_buffs; + std::vector > wrap_tx_buffs; + std::vector wrap_rx_buf_ptrs; + std::vector wrap_tx_buf_ptrs; + + template uhd_wrap(Args... args) : uhd_device(args...) + { + // t = new std::thread(magicthread); + // give the thread some time to start and set up + // std::this_thread::sleep_for(std::chrono::seconds(1)); + } + virtual ~uhd_wrap(); + + // void ipc_sock_close() override {}; + int wrap_read(TIMESTAMP *timestamp); + virtual int open(const std::string &args, int ref, bool swap_channels) override; + + // bool start() override; + // bool stop() override; + // virtual TIMESTAMP initialWriteTimestamp() override; + // virtual TIMESTAMP initialReadTimestamp() override; + + int getTimingOffset() + { + return ts_offset; + } + size_t bufsizerx(); + size_t bufsizetx(); + int chancount(); +}; +#else +void *uhdwrap_open(struct ipc_sk_if_open_req *open_req); + +int32_t uhdwrap_get_bufsizerx(void *dev); + +int32_t uhdwrap_get_timingoffset(void *dev); + +int32_t uhdwrap_read(void *dev, uint32_t num_chans); + +int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun); + +double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx); + +double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx); + +int32_t uhdwrap_start(void *dev, int chan); + +int32_t uhdwrap_stop(void *dev, int chan); + +void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim); + +double uhdwrap_set_txatt(void *dev, double a, size_t chan); +#endif + +#endif // IPC_B210_H diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp index b5dda28f..43b9f9cc 100644 --- a/Transceiver52M/device/uhd/UHDDevice.cpp +++ b/Transceiver52M/device/uhd/UHDDevice.cpp @@ -152,7 +152,7 @@ static const std::map dev_band_nom_power_param_map void *async_event_loop(uhd_device *dev) { set_selfthread_name("UHDAsyncEvent"); - OSMO_ASSERT(osmo_cpu_sched_vty_apply_localthread() == 0); + osmo_cpu_sched_vty_apply_localthread(); while (1) { dev->recv_async_msg(); @@ -1316,6 +1316,7 @@ std::string uhd_device::str_code(uhd::async_metadata_t metadata) return ost.str(); } +#ifndef IPCMAGIC RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset, const std::vector& tx_paths, @@ -1323,3 +1324,4 @@ RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, { return new uhd_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); } +#endif diff --git a/Transceiver52M/device/uhd/UHDDevice.h b/Transceiver52M/device/uhd/UHDDevice.h index 22a0948f..c159e639 100644 --- a/Transceiver52M/device/uhd/UHDDevice.h +++ b/Transceiver52M/device/uhd/UHDDevice.h @@ -133,7 +133,7 @@ public: ERROR_UNHANDLED = -4, }; -private: +protected: uhd::usrp::multi_usrp::sptr usrp_dev; uhd::tx_streamer::sptr tx_stream; uhd::rx_streamer::sptr rx_stream; diff --git a/configure.ac b/configure.ac index 07d4bf43..4ad344d8 100644 --- a/configure.ac +++ b/configure.ac @@ -140,6 +140,11 @@ AC_ARG_WITH(lms, [ [enable LimeSuite based transceiver]) ]) +AC_ARG_WITH(ipc, [ + AS_HELP_STRING([--with-ipc], + [enable IPC]) +]) + AC_ARG_WITH(singledb, [ AS_HELP_STRING([--with-singledb], [enable single daughterboard use on USRP1]) @@ -179,7 +184,7 @@ AS_IF([test "x$with_lms" = "xyes"], [ PKG_CHECK_MODULES(LMS, LimeSuite) ]) -AS_IF([test "x$with_uhd" != "xno"],[ +AS_IF([test "x$with_uhd" = "xyes"],[ PKG_CHECK_MODULES(UHD, uhd >= 003.011, [AC_DEFINE(USE_UHD_3_11, 1, UHD version 3.11.0 or higher)], [PKG_CHECK_MODULES(UHD, uhd >= 003.009, @@ -238,9 +243,10 @@ AS_IF([test "x$osmo_cv_cc_has___sync_fetch_and_and" = "xyes" && test "x$osmo_cv_ AC_DEFINE(HAVE_ATOMIC_OPS, 1, [Support all required atomic operations], [AC_MSG_WARN("At least one aotmic operation missing, will use mutex")]) ]) -AM_CONDITIONAL(DEVICE_UHD, [test "x$with_uhd" != "xno"]) +AM_CONDITIONAL(DEVICE_UHD, [test "x$with_uhd" = "xyes"]) AM_CONDITIONAL(DEVICE_USRP1, [test "x$with_usrp1" = "xyes"]) AM_CONDITIONAL(DEVICE_LMS, [test "x$with_lms" = "xyes"]) +AM_CONDITIONAL(DEVICE_IPC, [test "x$with_ipc" = "xyes"]) AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"]) AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"]) @@ -325,6 +331,7 @@ AC_CONFIG_FILES([\ Transceiver52M/device/uhd/Makefile \ Transceiver52M/device/usrp1/Makefile \ Transceiver52M/device/lms/Makefile \ + Transceiver52M/device/ipc/Makefile \ tests/Makefile \ tests/CommonLibs/Makefile \ tests/Transceiver52M/Makefile \ diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh index df444ca8..95ea1285 100755 --- a/contrib/jenkins.sh +++ b/contrib/jenkins.sh @@ -85,7 +85,7 @@ export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" export LD_LIBRARY_PATH="$inst/lib" export PATH="$inst/bin:$PATH" -CONFIG="--enable-sanitize --enable-werror --with-uhd --with-usrp1 --with-lms $INSTR" +CONFIG="--enable-sanitize --enable-werror --with-uhd --with-usrp1 --with-lms --with-ipc $INSTR" # Additional configure options and depends if [ "$WITH_MANUALS" = "1" ]; then diff --git a/contrib/osmo-trx.spec.in b/contrib/osmo-trx.spec.in index aebd355e..ace72123 100644 --- a/contrib/osmo-trx.spec.in +++ b/contrib/osmo-trx.spec.in @@ -129,6 +129,26 @@ between different telecommunication associations for developing new generations of mobile phone networks. (post-2G/GSM) %endif +%package ipc +Summary: SDR transceiver that implements Layer 1 of a GSM BTS (IPC) +Group: Productivity/Telephony/Servers + +%description ipc +OsmoTRX is a software-defined radio transceiver that implements the Layer 1 +physical layer of a BTS comprising the following 3GPP specifications: + +TS 05.01 "Physical layer on the radio path" +TS 05.02 "Multiplexing and Multiple Access on the Radio Path" +TS 05.04 "Modulation" +TS 05.10 "Radio subsystem synchronization" + +In this context, BTS is "Base transceiver station". It's the stations that +connect mobile phones to the mobile network. + +3GPP is the "3rd Generation Partnership Project" which is the collaboration +between different telecommunication associations for developing new +generations of mobile phone networks. (post-2G/GSM) + %prep %setup -q @@ -142,14 +162,16 @@ autoreconf -fi --with-systemdsystemunitdir=%{_unitdir} \ --without-lms \ --with-uhd \ - --without-usrp1 + --without-usrp1 \ + --with-ipc %else %configure \ --docdir=%{_docdir}/%{name} \ --with-systemdsystemunitdir=%{_unitdir} \ --with-lms \ --with-uhd \ - --with-usrp1 + --with-usrp1 \ + --with-ipc %endif make %{?_smp_mflags} V=1 @@ -174,6 +196,10 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +) %post usrp1 %service_add_post osmo-trx-usrp1.service %preun usrp1 %service_del_preun osmo-trx-usrp1.service %postun usrp1 %service_del_postun osmo-trx-usrp1.service +%pre ipc %service_add_pre osmo-trx-ipc.service +%post ipc %service_add_post osmo-trx-ipc.service +%preun ipc %service_del_preun osmo-trx-ipc.service +%postun ipc %service_del_postun osmo-trx-ipc.service %endif %files @@ -206,4 +232,11 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +) %{_unitdir}/osmo-trx-usrp1.service %endif +%files ipc +%{_bindir}/osmo-trx-ipc +%{_bindir}/ipc-driver-test +%dir %{_sysconfdir}/osmocom +%config(noreplace) %{_sysconfdir}/osmocom/osmo-trx-ipc.cfg +%{_unitdir}/osmo-trx-ipc.service + %changelog diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am index 800b1e1c..6566b978 100644 --- a/contrib/systemd/Makefile.am +++ b/contrib/systemd/Makefile.am @@ -1,7 +1,8 @@ EXTRA_DIST = \ osmo-trx-lms.service \ osmo-trx-uhd.service \ - osmo-trx-usrp1.service + osmo-trx-usrp1.service \ + osmo-trx-ipc.service if HAVE_SYSTEMD SYSTEMD_SERVICES = @@ -18,5 +19,9 @@ if DEVICE_LMS SYSTEMD_SERVICES += osmo-trx-lms.service endif +if DEVICE_IPC +SYSTEMD_SERVICES += osmo-trx-ipc.service +endif + systemdsystemunit_DATA = $(SYSTEMD_SERVICES) endif # HAVE_SYSTEMD diff --git a/contrib/systemd/osmo-trx-ipc.service b/contrib/systemd/osmo-trx-ipc.service new file mode 100644 index 00000000..c886ed76 --- /dev/null +++ b/contrib/systemd/osmo-trx-ipc.service @@ -0,0 +1,11 @@ +[Unit] +Description=Osmocom SDR BTS L1 Transceiver (IPC Backend) + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/osmo-trx-ipc -C /etc/osmocom/osmo-trx-ipc.cfg +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/debian/control b/debian/control index 31c738e0..12d9af51 100644 --- a/debian/control +++ b/debian/control @@ -30,7 +30,7 @@ Package: osmo-trx-dbg Architecture: any Section: debug Priority: extra -Depends: osmo-trx-uhd (= ${binary:Version}), osmo-trx-usrp1 (= ${binary:Version}), osmo-trx-lms (= ${binary:Version}), ${misc:Depends} +Depends: osmo-trx-uhd (= ${binary:Version}), osmo-trx-usrp1 (= ${binary:Version}), osmo-trx-lms (= ${binary:Version}), osmo-trx-ipc (= ${binary:Version}), ${misc:Depends} Description: Debug symbols for the osmo-trx-* Make debugging possible @@ -91,6 +91,25 @@ Description: SDR transceiver that implements Layer 1 of a GSM BTS (LimeSuite) between different telecommunication associations for developing new generations of mobile phone networks. (post-2G/GSM) +Package: osmo-trx-ipc +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: SDR transceiver that implements Layer 1 of a GSM BTS (generic IPC) + OsmoTRX is a software-defined radio transceiver that implements the Layer 1 + physical layer of a BTS comprising the following 3GPP specifications: + . + TS 05.01 "Physical layer on the radio path" + TS 05.02 "Multiplexing and Multiple Access on the Radio Path" + TS 05.04 "Modulation" + TS 05.10 "Radio subsystem synchronization" + . + In this context, BTS is "Base transceiver station". It's the stations that + connect mobile phones to the mobile network. + . + 3GPP is the "3rd Generation Partnership Project" which is the collaboration + between different telecommunication associations for developing new + generations of mobile phone networks. (post-2G/GSM) + Package: osmo-trx-doc Architecture: all Section: doc diff --git a/debian/osmo-trx-ipc.install b/debian/osmo-trx-ipc.install new file mode 100644 index 00000000..db64623e --- /dev/null +++ b/debian/osmo-trx-ipc.install @@ -0,0 +1,4 @@ +etc/osmocom/osmo-trx-ipc.cfg +lib/systemd/system/osmo-trx-ipc.service +/usr/bin/osmo-trx-ipc +/usr/share/doc/osmo-trx/examples/osmo-trx-ipc/osmo-trx-ipc.cfg /usr/share/doc/osmo-trx/examples/osmo-trx-ipc/ diff --git a/debian/rules b/debian/rules index 627c0c85..5795643c 100755 --- a/debian/rules +++ b/debian/rules @@ -9,7 +9,7 @@ override_dh_shlibdeps: dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info override_dh_auto_configure: - dh_auto_configure -- --with-uhd --with-usrp1 --with-lms --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals + dh_auto_configure -- --with-uhd --with-usrp1 --with-lms --with-ipc --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals override_dh_strip: dh_strip --dbg-package=osmo-trx-dbg diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am index 88d91421..0b061300 100644 --- a/doc/examples/Makefile.am +++ b/doc/examples/Makefile.am @@ -14,6 +14,10 @@ if DEVICE_LMS OSMOCONF_FILES += osmo-trx-lms/osmo-trx-lms.cfg endif +if DEVICE_IPC +OSMOCONF_FILES += osmo-trx-ipc/osmo-trx-ipc.cfg +endif + osmoconf_DATA = $(OSMOCONF_FILES) EXTRA_DIST = $(OSMOCONF_FILES) diff --git a/doc/examples/osmo-trx-ipc/osmo-trx-ipc.cfg b/doc/examples/osmo-trx-ipc/osmo-trx-ipc.cfg new file mode 100644 index 00000000..e3047c09 --- /dev/null +++ b/doc/examples/osmo-trx-ipc/osmo-trx-ipc.cfg @@ -0,0 +1,32 @@ +log stderr + logging filter all 1 + logging color 1 + logging print category 1 + logging timestamp 1 + logging print file basename + logging level set-all notice +! +line vty + no login +! +cpu-sched + policy rr 18 +trx + bind-ip 127.0.0.1 + remote-ip 127.0.0.1 + ! 28 dB offset below is valid only for the B2xx in 1800 MHz band, see + ! https://osmocom.org/issues/4468 for more details + rssi-offset 28.000000 + tx-sps 4 + rx-sps 4 + clock-ref external + egprs disable + ext-rach disable + dev-args ipc_msock=/tmp/ipc_sock0 + multi-arfcn disable + chan 0 + tx-path TX/RX + rx-path RX2 + chan 1 + tx-path TX/RX + rx-path RX2