aboutsummaryrefslogtreecommitdiffstats
path: root/Transceiver52M/device/ipc
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2020-03-24 17:19:27 +0100
committerEric <ewild@sysmocom.de>2020-04-15 22:43:15 +0200
commit87d6f3c8e1d0fc9d39520f7f2d25552d497c883b (patch)
tree84e26fb58578b9e197789af3312e411ef055cf80 /Transceiver52M/device/ipc
parenta7143bf7a12e6794253dd970c41badbbae8ec0b7 (diff)
WIP: osmo-trx-ipc
Diffstat (limited to 'Transceiver52M/device/ipc')
-rw-r--r--Transceiver52M/device/ipc/IPCDevice.cpp741
-rw-r--r--Transceiver52M/device/ipc/IPCDevice.h218
-rw-r--r--Transceiver52M/device/ipc/Makefile.am25
-rw-r--r--Transceiver52M/device/ipc/ipc-driver-test.c507
-rw-r--r--Transceiver52M/device/ipc/shm.c121
-rw-r--r--Transceiver52M/device/ipc/shm.h207
6 files changed, 1819 insertions, 0 deletions
diff --git a/Transceiver52M/device/ipc/IPCDevice.cpp b/Transceiver52M/device/ipc/IPCDevice.cpp
new file mode 100644
index 0000000..5b05e3d
--- /dev/null
+++ b/Transceiver52M/device/ipc/IPCDevice.cpp
@@ -0,0 +1,741 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+* Author: Pau Espin Pedrol <pespin@sysmocom.de>
+*
+* 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h> /* For mode constants */
+#include <fcntl.h> /* For O_* constants */
+
+#include <map>
+
+#include "trx_vty.h"
+#include "Logger.h"
+#include "Threads.h"
+#include "Utils.h"
+#include "IPCDevice.h"
+
+extern "C" {
+#include "osmo_signal.h"
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+}
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define SAMPLE_BUF_SZ (1 << 20)
+
+using namespace std;
+
+IPCDevice::IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
+ const std::vector<std::string>& tx_paths,
+ const std::vector<std::string>& rx_paths):
+ RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
+ tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), started(false)
+{
+ LOGC(DDEV, INFO) << "creating IPC device...";
+
+ //m_IPC_stream_rx.resize(chans);
+ //m_IPC_stream_tx.resize(chans);
+ rx_gains.resize(chans);
+ tx_gains.resize(chans);
+
+ rx_buffers.resize(chans);
+
+ /* 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()
+{
+ //unsigned int i;
+ LOGC(DDEV, INFO) << "Closing IPC device";
+ /* disable all channels */
+
+ for (size_t i = 0; i < rx_buffers.size(); i++)
+ delete rx_buffers[i];
+}
+
+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_sock_state* state, struct msgb *msg);
+
+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;
+}
+
+int ipc_tx_greeting_req(struct ipc_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);
+}
+
+int ipc_tx_info_req(struct ipc_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 ipc_tx_open_req(struct ipc_sock_state* state, uint32_t num_chans)
+{
+ 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;
+ chan_info = ipc_prim->u.open_req.chan_info;
+ OSMO_STRLCPY_ARRAY(chan_info->rx_path, "RxAntenna1");
+ OSMO_STRLCPY_ARRAY(chan_info->tx_path, "TxAntenna2");
+
+ 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)
+{
+ /* 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
+ */
+ unsigned int i;
+ LOGC(DDEV, NOTICE) << "Rx Info CNF:"
+ << " max_num_chans=" << info_cnf->max_num_chans
+ << " feature_mask=" << info_cnf->feature_mask
+ << " min_rx_gain=" << info_cnf->min_rx_gain
+ << " max_rx_gain=" << info_cnf->max_rx_gain;
+ for (i = 0; i < info_cnf->max_num_chans; i++) {
+ int j = 0;
+ 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];
+ 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];
+ j++;
+ }
+ }
+ 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;
+ LOGC(DDEV, NOTICE) << "Rx Open CNF:"
+ << " return_code=" << (unsigned int)open_cnf->return_code
+ << " shm_name=" << open_cnf->shm_name;
+ for (i = 0; i < chans; i++) {
+ LOGC(DDEV, NOTICE) << "chan " << i << ": sk_path=" << open_cnf->chan_info[i].chan_ipc_sk_path;
+ }
+
+ 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;
+ LOGC(DDEV, NOTICE) << "shm: chan0/dl: num_buffers=" << shm_dec->channels[0]->dl_stream->num_buffers;
+ LOGC(DDEV, NOTICE) << "shm: chan0/dl: buffer_size=" << shm_dec->channels[0]->dl_stream->buffer_size;
+
+ 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 PCU msg type %d\n",
+ msg_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int ipc_sock_send(struct ipc_sock_state* state, struct msgb *msg)
+{
+ struct osmo_fd *conn_bfd;
+ //struct ipc_sk_if *ipc_prim = (struct ipc_sk_if *) msg->data;
+
+ if (!state) {
+ LOGP(DMAIN, LOGL_INFO, "PCU 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, "PCU 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 osmo_fd *bfd = &sk_state.conn_bfd;
+
+ LOGP(DDEV, LOGL_NOTICE, "PCU socket has LOST connection\n");
+
+ close(bfd->fd);
+ bfd->fd = -1;
+ osmo_fd_unregister(bfd);
+
+ /* flush the queue */
+ while (!llist_empty(&sk_state.upqueue)) {
+ struct msgb *msg = msgb_dequeue(&sk_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 pcu_rx() and
+ * its callbacks, we can free the message here. */
+ msgb_free(msg);
+
+ return rc;
+
+close:
+ msgb_free(msg);
+ ipc_sock_close();
+ return -1;
+}
+
+int IPCDevice::ipc_sock_write(struct osmo_fd *bfd)
+{
+ int rc;
+
+ while (!llist_empty(&sk_state.upqueue)) {
+ struct msgb *msg, *msg2;
+ struct ipc_sk_if *ipc_prim;
+
+ /* peek at the beginning of the queue */
+ msg = llist_entry(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(&sk_state.upqueue);
+ assert(msg == msg2);
+ msgb_free(msg);
+ }
+ return 0;
+
+close:
+ ipc_sock_close();
+ return -1;
+}
+
+static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+ IPCDevice *device = (IPCDevice *)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;
+}
+
+int IPCDevice::open(const std::string &args, int ref, bool swap_channels)
+{
+ //float_type sr_host, sr_rf;
+ //unsigned int i, n;
+ //int rc, dev_id;
+ int rc;
+
+ LOGC(DDEV, INFO) << "Opening IPC device..";
+
+ memset(&sk_state, 0x00, sizeof(sk_state));
+ INIT_LLIST_HEAD(&sk_state.upqueue);
+ rc = osmo_sock_unix_init_ofd(&sk_state.conn_bfd, SOCK_SEQPACKET, 0,
+ IPC_SOCK_PATH, OSMO_SOCK_F_CONNECT);
+ if (rc < 0) {
+ LOGC(DDEV, ERROR) << "Failed to connect to the BTS (" << IPC_SOCK_PATH << "). " <<
+ "Retrying...\n";
+ osmo_timer_setup(&sk_state.timer, ipc_sock_timeout, NULL);
+ osmo_timer_schedule(&sk_state.timer, 5, 0);
+ return -1;
+ }
+ sk_state.conn_bfd.cb = ipc_sock_cb;
+ sk_state.conn_bfd.data = this;
+
+ ipc_tx_greeting_req(&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(&sk_state);
+ /* Wait until confirmation is recieved */
+ while(tmp_state != IPC_IF_MSG_INFO_CNF)
+ osmo_select_main(0);
+
+ ipc_tx_open_req(&sk_state, chans);
+ /* 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;
+}
+
+bool IPCDevice::start()
+{
+ LOGC(DDEV, INFO) << "starting IPC...";
+
+ unsigned int i;
+
+ if (started) {
+ LOGC(DDEV, ERR) << "Device already started";
+ return false;
+ }
+
+ /* configure the channels/streams */
+ for (i=0; i<chans; i++) {
+ /* Set gains for calibration/filter setup */
+ /* TX gain to maximum */
+ setTxGain(maxTxGain(), i);
+ /* RX gain to midpoint */
+ setRxGain((minRxGain() + maxRxGain()) / 2, i);
+
+ /* set up Rx and Tx filters */
+ if (!do_filters(i))
+ return false;
+ /* Perform Rx and Tx calibration */
+ if (!do_calib(i))
+ return false;
+
+ /* configure Streams */
+ }
+
+ /* now start the streams in a second loop, as we can no longer call
+ * IPC_SetupStream() after IPC_StartStream() of the first stream */
+ //for (i = 0; i < chans; i++) {
+ //}
+
+ flush_recv(10);
+
+ started = true;
+ return true;
+}
+
+bool IPCDevice::stop()
+{
+ //unsigned int i;
+
+ if (!started)
+ return true;
+
+ /*
+ for (i=0; i<chans; i++) {
+ IPC_StopStream(&m_IPC_stream_tx[i]);
+ IPC_StopStream(&m_IPC_stream_rx[i]);
+ }
+
+ for (i=0; i<chans; i++) {
+ IPC_DestroyStream(m_IPC_dev, &m_IPC_stream_tx[i]);
+ IPC_DestroyStream(m_IPC_dev, &m_IPC_stream_rx[i]);
+ }*/
+
+ started = false;
+ return true;
+}
+
+/* do rx/tx calibration - depends on gain, freq and bw */
+bool IPCDevice::do_calib(size_t chan)
+{
+ LOGCHAN(chan, DDEV, INFO) << "Calibrating";
+ return true;
+}
+
+/* do rx/tx filter config - depends on bw only? */
+bool IPCDevice::do_filters(size_t chan)
+{
+ LOGCHAN(chan, DDEV, INFO) << "Setting filters";
+ return true;
+}
+
+
+double IPCDevice::maxTxGain()
+{
+ //return dev_param_map.at(m_dev_type).max_tx_gain;
+ return 70.0;
+}
+
+double IPCDevice::minTxGain()
+{
+ return 0.0;
+}
+
+double IPCDevice::maxRxGain()
+{
+ return 73.0;
+}
+
+double IPCDevice::minRxGain()
+{
+ return 0.0;
+}
+
+double IPCDevice::setTxGain(double dB, size_t chan)
+{
+ if (dB > maxTxGain())
+ dB = maxTxGain();
+ if (dB < minTxGain())
+ dB = minTxGain();
+
+ LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB";
+
+ //if (IPC_SetGaindB(m_IPC_dev, IPC_CH_TX, chan, dB) < 0)
+ // LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB";
+ //else
+ tx_gains[chan] = dB;
+ return tx_gains[chan];
+}
+
+double IPCDevice::setRxGain(double dB, size_t chan)
+{
+ if (dB > maxRxGain())
+ dB = maxRxGain();
+ if (dB < minRxGain())
+ dB = minRxGain();
+
+ LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB";
+
+ //if (IPC_SetGaindB(m_IPC_dev, IPC_CH_RX, chan, dB) < 0)
+ // LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB";
+ //else
+ rx_gains[chan] = dB;
+ return rx_gains[chan];
+}
+
+
+void IPCDevice::log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os)
+{
+ int num_names = 0;
+ int i;
+ for (i = 0; i < num_names; i++) {
+ if (i)
+ os << ", ";
+ os << "''";
+ }
+}
+
+bool IPCDevice::flush_recv(size_t num_pkts)
+{/*
+ #define CHUNK 625
+ int len = CHUNK * tx_sps;
+ short *buffer = (short*) alloca(sizeof(short) * len * 2);
+ int rc;
+ IPC_stream_meta_t rx_metadata = {};
+ rx_metadata.flushPartialPacket = false;
+ rx_metadata.waitForTimestamp = false;
+
+ ts_initial = 0;
+
+ while (!ts_initial || (num_pkts-- > 0)) {
+ rc = IPC_RecvStream(&m_IPC_stream_rx[0], &buffer[0], len, &rx_metadata, 100);
+ LOGC(DDEV, DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp;
+ if (rc != len) {
+ LOGC(DDEV, ERROR) << "Flush: Device receive timed out";
+ return false;
+ }
+
+ ts_initial = rx_metadata.timestamp + len;
+ }
+*/
+ 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);
+}
+
+// NOTE: Assumes sequential reads
+int IPCDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
+ TIMESTAMP timestamp, bool * underrun)
+{
+ return len;
+}
+
+int IPCDevice::writeSamples(std::vector < short *>&bufs, int len,
+ bool * underrun, unsigned long long timestamp)
+{
+ return len;
+}
+
+bool IPCDevice::updateAlignment(TIMESTAMP timestamp)
+{
+ return true;
+}
+
+bool IPCDevice::setTxFreq(double wFreq, size_t chan)
+{
+ LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz";
+
+ return true;
+}
+
+bool IPCDevice::setRxFreq(double wFreq, size_t chan)
+{
+ LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
+
+ return true;
+}
+
+RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
+ InterfaceType iface, size_t chans, double lo_offset,
+ const std::vector < std::string > &tx_paths,
+ const std::vector < std::string > &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 0000000..934bc54
--- /dev/null
+++ b/Transceiver52M/device/ipc/IPCDevice.h
@@ -0,0 +1,218 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+* Author: Pau Espin Pedrol <pespin@sysmocom.de>
+*
+* SPDX-License-Identifier: AGPL-3.0+
+*
+* This software is distributed under multiple licenses; see the COPYING file in
+* the main directory for licensing information for this specific distribution.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+#ifndef _IPC_DEVICE_H_
+#define _IPC_DEVICE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+extern "C" {
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include "shm.h"
+}
+
+#include "radioDevice.h"
+#include "smpl_buf.h"
+
+#include <sys/time.h>
+#include <math.h>
+#include <limits.h>
+#include <string>
+#include <iostream>
+#include <lime/LimeSuite.h>
+
+struct ipc_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 */
+};
+
+
+/** A class to handle a LimeSuite supported device */
+class IPCDevice:public RadioDevice {
+
+private:
+ struct ipc_sock_state sk_state;
+ 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<smpl_buf *> rx_buffers;
+ double actualSampleRate; ///< the actual USRP sampling rate
+
+ bool started; ///< flag indicates LMS has started
+ bool skipRx; ///< set if LMS is transmit-only.
+
+ TIMESTAMP ts_initial, ts_offset;
+
+ std::vector<double> tx_gains, rx_gains;
+
+ bool do_calib(size_t chan);
+ bool do_filters(size_t chan);
+ void log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os);
+ int get_ant_idx(const std::string & name, bool dir_tx, size_t chan);
+ 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);
+ bool do_clock_src_freq(enum ReferenceType ref, double freq);
+
+public:
+ void ipc_sock_close();
+ int ipc_sock_read(struct osmo_fd *bfd);
+ int ipc_sock_write(struct osmo_fd *bfd);
+ 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);
+
+ /** Object constructor */
+ IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
+ const std::vector<std::string>& tx_paths,
+ const std::vector<std::string>& rx_paths);
+ ~IPCDevice();
+
+ /** Instantiate the LMS */
+ int open(const std::string &args, int ref, bool swap_channels);
+
+ /** Start the LMS */
+ bool start();
+
+ /** Stop the LMS */
+ bool stop();
+
+ enum TxWindowType getWindowType() {
+ return TX_WINDOW_LMS1;
+ }
+
+ /**
+ Read samples from the LMS.
+ @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 LMS does not have data to transmit, e.g. data not being sent fast enough
+ @return The number of samples actually read
+ */
+ int readSamples(std::vector < short *>&buf, int len, bool * overrun,
+ TIMESTAMP timestamp = 0xffffffff, bool * underrun =
+ NULL);
+ /**
+ Write samples to the LMS.
+ @param buf Contains the data to be written.
+ @param len number of samples to write.
+ @param underrun Set if LMS 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
+ */
+ int writeSamples(std::vector < short *>&bufs, int len, bool * underrun,
+ TIMESTAMP timestamp = 0xffffffff);
+
+ /** Update the alignment between the read and write timestamps */
+ bool updateAlignment(TIMESTAMP timestamp);
+
+ /** Set the transmitter frequency */
+ bool setTxFreq(double wFreq, size_t chan = 0);
+
+ /** Set the receiver frequency */
+ bool setRxFreq(double wFreq, size_t chan = 0);
+
+ /** Returns the starting write Timestamp*/
+ TIMESTAMP initialWriteTimestamp(void) {
+ return ts_initial;
+ }
+
+ /** Returns the starting read Timestamp*/
+ TIMESTAMP initialReadTimestamp(void) {
+ return ts_initial;
+ }
+
+ /** returns the full-scale transmit amplitude **/
+ double fullScaleInputValue() {
+ #define LIMESDR_TX_AMPL 0.3
+ return(double) SHRT_MAX * LIMESDR_TX_AMPL;
+ }
+
+ /** returns the full-scale receive amplitude **/
+ double fullScaleOutputValue() {
+ return (double) SHRT_MAX;
+ }
+
+ /** sets the receive chan gain, returns the gain setting **/
+ double setRxGain(double dB, size_t chan = 0);
+
+ /** get the current receive gain */
+ double getRxGain(size_t chan = 0) {
+ return rx_gains[chan];
+ }
+
+ /** return maximum Rx Gain **/
+ double maxRxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minRxGain(void);
+
+ /** sets the transmit chan gain, returns the gain setting **/
+ double setTxGain(double dB, size_t chan = 0);
+
+ /** get transmit gain */
+ double getTxGain(size_t chan = 0) {
+ return tx_gains[chan];
+ }
+
+ /** return maximum Tx Gain **/
+ double maxTxGain(void);
+
+ /** return minimum Rx Gain **/
+ double minTxGain(void);
+
+ /** sets the RX path to use, returns true if successful and false otherwise */
+ bool setRxAntenna(const std::string & ant, size_t chan = 0);
+
+ /* return the used RX path */
+ std::string getRxAntenna(size_t chan = 0);
+
+ /** sets the RX path to use, returns true if successful and false otherwise */
+ bool setTxAntenna(const std::string & ant, size_t chan = 0);
+
+ /* return the used RX path */
+ std::string getTxAntenna(size_t chan = 0);
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ bool requiresRadioAlign();
+
+ /** return whether user drives synchronization of Tx/Rx of USRP */
+ virtual GSM::Time minLatency();
+
+ /** Return internal status values */
+ inline double getTxFreq(size_t chan = 0) {
+ return 0;
+ }
+ inline double getRxFreq(size_t chan = 0) {
+ return 0;
+ }
+ inline double getSampleRate() {
+ 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 0000000..84f8602
--- /dev/null
+++ b/Transceiver52M/device/ipc/Makefile.am
@@ -0,0 +1,25 @@
+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
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = IPCDevice.cpp shm.c
+libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
+
+
+bin_PROGRAMS = ipc-driver-test
+ipc_driver_test_SOURCES = ipc-driver-test.c
+ipc_driver_test_LDADD = \
+ shm.lo \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOVTY_LIBS)
+ipc_driver_test_CPPFLAGS = $(AM_CPPFLAGS)
+ipc_driver_test_CFLAGS = $(AM_CFLAGS)
+ipc_driver_test_LDFLAGS = $(AM_LDFLAGS)
diff --git a/Transceiver52M/device/ipc/ipc-driver-test.c b/Transceiver52M/device/ipc/ipc-driver-test.c
new file mode 100644
index 0000000..ec04446
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc-driver-test.c
@@ -0,0 +1,507 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+* Author: Pau Espin Pedrol <pespin@sysmocom.de>
+*
+* 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <inttypes.h>
+#include <sys/mman.h>
+#include <sys/stat.h> /* For mode constants */
+#include <fcntl.h> /* For O_* constants */
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include "shm.h"
+
+#define DEFAULT_SHM_NAME "/osmo-trx-ipc-driver-shm2"
+
+struct ipc_sock_state {
+ struct osmo_fd listen_bfd; /* fd for listen socket */
+ struct osmo_fd conn_bfd; /* fd for connection to lcr */
+ struct llist_head upqueue; /* queue for sending messages */
+};
+
+static void *tall_ctx;
+static struct ipc_sock_state *global_ipc_sock_state;
+
+void *shm;
+
+
+/* Debug Areas of the code */
+enum {
+ DMAIN,
+};
+static const struct log_info_cat default_categories[] = {
+ [DMAIN] = {
+ .name = "DMAIN",
+ .description = "Main generic category",
+ .color = NULL,
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+};
+
+const struct log_info log_info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
+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;
+}
+
+static int ipc_sock_send(struct msgb *msg);
+
+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;
+ struct ipc_sk_if_info_chan *chan_info;
+ int i;
+
+ msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_CNF);
+ if (!msg)
+ return -ENOMEM;
+ ipc_prim = (struct ipc_sk_if *) msg->data;
+ ipc_prim->u.info_cnf.feature_mask = FEATURE_MASK_CLOCKREF_INTERNAL |
+ FEATURE_MASK_CLOCKREF_EXTERNAL;
+ ipc_prim->u.info_cnf.min_rx_gain = 0.0;
+ ipc_prim->u.info_cnf.max_rx_gain = 70.0;
+ ipc_prim->u.info_cnf.min_tx_gain = 0.0;
+ ipc_prim->u.info_cnf.max_tx_gain = 63.0;
+ ipc_prim->u.info_cnf.max_num_chans = 2;
+ OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, "Hello To my Virtual device!");
+ chan_info = ipc_prim->u.info_cnf.chan_info;
+ for (i = 0; i < ipc_prim->u.info_cnf.max_num_chans; i++) {
+ OSMO_STRLCPY_ARRAY(chan_info->tx_path[0], "TxAntenna1");
+ OSMO_STRLCPY_ARRAY(chan_info->tx_path[1], "TxAntenna2");
+ OSMO_STRLCPY_ARRAY(chan_info->tx_path[2], "TxAntenna3");
+ OSMO_STRLCPY_ARRAY(chan_info->rx_path[0], "RxAntenna1");
+ OSMO_STRLCPY_ARRAY(chan_info->rx_path[1], "RxAntenna2");
+ chan_info++;
+ }
+
+ return ipc_sock_send(msg);
+}
+
+static int ipc_tx_open_cnf(int rc, uint32_t num_chans)
+{
+ struct msgb *msg;
+ struct ipc_sk_if *ipc_prim;
+ struct ipc_sk_if_open_cnf_chan *chan_info;
+ 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;
+ 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, i+1);
+ chan_info++;
+ }
+
+ return ipc_sock_send(msg);
+}
+
+static int ipc_rx_greeting_req(const 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;
+}
+
+static int ipc_rx_info_req(const struct ipc_sk_if_info_req *info_req)
+{
+ ipc_tx_info_cnf();
+ return 0;
+}
+
+static int ipc_rx_open_req(const struct ipc_sk_if_open_req *open_req)
+{
+ /* calculate size needed */
+ unsigned int len;
+ len = ipc_shm_encode_region(NULL, open_req->num_chans, 4, 4096);
+ /* 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, 4096);
+ LOGP(DMAIN, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *) shm, 80));
+ ipc_tx_open_cnf(-rc, open_req->num_chans);
+ return 0;
+}
+
+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(DMAIN, LOGL_ERROR, "Received unknown PCU msg type %d\n",
+ msg_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int ipc_sock_send(struct msgb *msg)
+{
+ struct ipc_sock_state *state = global_ipc_sock_state;
+ struct osmo_fd *conn_bfd;
+ //struct ipc_sk_if *ipc_prim = (struct ipc_sk_if *) msg->data;
+
+ if (!state) {
+ LOGP(DMAIN, LOGL_INFO, "PCU 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, "PCU 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 void ipc_sock_close(struct ipc_sock_state *state)
+{
+ struct osmo_fd *bfd = &state->conn_bfd;
+
+ LOGP(DMAIN, LOGL_NOTICE, "PCU socket has LOST connection\n");
+
+ 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);
+ }
+}
+
+static 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 < sizeof(*ipc_prim)) {
+ LOGP(DMAIN, 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 pcu_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 = 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(DMAIN, 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 PCU */
+static 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(DMAIN, LOGL_ERROR, "Failed to accept a new connection\n");
+ return -1;
+ }
+
+ if (conn_bfd->fd >= 0) {
+ LOGP(DMAIN, LOGL_NOTICE, "osmo-trx connects but we already have "
+ "another active connection ?!?\n");
+ /* We already have one PCU 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(DMAIN, LOGL_ERROR, "Failed to register new connection "
+ "fd\n");
+ close(conn_bfd->fd);
+ conn_bfd->fd = -1;
+ return -1;
+ }
+
+ LOGP(DMAIN, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n");
+
+ /* send current info */
+ //pcu_tx_info_ind();
+
+ return 0;
+}
+
+int ipc_sock_init(const char *path)
+{
+ struct ipc_sock_state *state;
+ struct osmo_fd *bfd;
+ int rc;
+
+ state = talloc_zero(NULL, struct ipc_sock_state);
+ if (!state)
+ return -ENOMEM;
+ global_ipc_sock_state = 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 = ipc_sock_accept;
+ bfd->data = state;
+
+ 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;
+ }
+
+ //osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL);
+
+ LOGP(DMAIN, LOGL_INFO, "Started listening on IPC socket: %s\n", path);
+
+ return 0;
+}
+
+
+int main(int argc, char** argv) {
+ tall_ctx = talloc_named_const(NULL, 0, "OsmoTRX");
+ msgb_talloc_ctx_init(tall_ctx, 0);
+ osmo_init_logging2(tall_ctx, &log_info);
+ log_enable_multithread();
+ LOGP(DMAIN, LOGL_INFO, "Starting %s\n", argv[0]);
+ ipc_sock_init(IPC_SOCK_PATH);
+ while (true)
+ osmo_select_main(0);
+ //ipc_sock_close()
+ return 0;
+}
diff --git a/Transceiver52M/device/ipc/shm.c b/Transceiver52M/device/ipc/shm.c
new file mode 100644
index 0000000..cafb4f2
--- /dev/null
+++ b/Transceiver52M/device/ipc/shm.c
@@ -0,0 +1,121 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <osmocom/core/talloc.h>
+
+#include "shm.h"
+
+/* 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)
+{
+ 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++) {
+ fprintf(stderr, "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;
+ fprintf(stderr, "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)
+{
+ 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++) {
+ fprintf(stderr, "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)
+{
+ uint8_t* start = (uint8_t*)smpl_buf_raw;
+ unsigned int offset = sizeof(struct ipc_shm_raw_smpl_buf);
+
+ fprintf(stderr, "encode: smpl_buf at offset %lu\n", (start - (uint8_t*)root_raw));
+
+ offset += buffer_size * sizeof(uint16_t); /* 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)
+{
+ int i;
+ uint8_t* start = (uint8_t*)stream_raw;
+ unsigned int offset = sizeof(struct ipc_shm_raw_stream) + sizeof(uint32_t)*num_buffers;
+
+ fprintf(stderr, "encode: stream at offset %lu\n", (start - (uint8_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 - (uint8_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);
+
+ fprintf(stderr, "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)
+{
+ int i;
+ uint8_t* start = (uint8_t*)root_raw;
+ unsigned int offset = sizeof(struct ipc_shm_raw_region) + sizeof(uint32_t)*num_chans;
+
+ 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 - (uint8_t*)root_raw);
+ fprintf(stderr, "encode: channel %d chan_offset[i]=%u\n", i, root_raw->chan_offset[i]);
+ 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 0000000..8ee35d2
--- /dev/null
+++ b/Transceiver52M/device/ipc/shm.h
@@ -0,0 +1,207 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+* Author: Pau Espin Pedrol <pespin@sysmocom.de>
+*
+* SPDX-License-Identifier: AGPL-3.0+
+*
+* This software is distributed under multiple licenses; see the COPYING file in
+* the main directory for licensing information for this specific distribution.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ 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.
+
+*/
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* RAW structures */
+struct ipc_shm_raw_smpl_buf {
+ uint32_t timestamp;
+ uint32_t data_len; /* In samples */
+ uint16_t samples[0];
+};
+
+struct ipc_shm_raw_stream {
+ 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;
+ struct ipc_shm_raw_stream *raw;
+ 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 "/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];
+} __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 min_rx_gain;
+ double max_rx_gain;
+ double min_tx_gain;
+ double max_tx_gain;
+ uint32_t max_num_chans;
+ char dev_desc[200];
+ struct ipc_sk_if_info_chan chan_info[0];
+} __attribute__ ((packed));
+
+struct ipc_sk_if_open_req {
+ uint32_t num_chans;
+ uint32_t clockref; /* One of FEATUER_MASK_CLOCKREF_* */
+ uint32_t rx_sps;
+ uint32_t tx_sps;
+ uint32_t bandwidth;
+ struct ipc_sk_if_open_req_chan chan_info[0];
+} __attribute__ ((packed));
+
+struct ipc_sk_if_open_cnf {
+ uint8_t return_code;
+ char shm_name[SHM_NAME_MAX];
+ struct ipc_sk_if_open_cnf_chan chan_info[0];
+} __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_MSG_START_REQ 0x00
+#define IPC_IF_MSG_START_CNF 0x01
+#define IPC_IF_MSG_STOP_REQ 0x02
+#define IPC_IF_MSG_STOP_CNF 0x03
+#define IPC_IF_MSG_SETGAIN_REQ 0x04
+#define IPC_IF_MSG_SETGAIN_CNF 0x05
+#define IPC_IF_MSG_SETFREQ_REQ 0x04
+#define IPC_IF_MSG_SETFREQ_CNF 0x05
+
+struct ipc_sk_chan_if_op_void {
+} __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 {
+ 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;
+ } u;
+} __attribute__ ((packed));