Change in osmo-trx[master]: osmo-trx-ipc

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Hoernchen gerrit-no-reply at lists.osmocom.org
Fri Aug 14 02:09:29 UTC 2020


Hoernchen has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-trx/+/19641 )


Change subject: osmo-trx-ipc
......................................................................

osmo-trx-ipc

This adds a IPC backend that uses shared memory interface
to communicate with (proprietary) devices.

Requires config file option
dev-args ipc_msock=/path/to/socket
to specify the master socket the ipc backend should connect to.

If UHD is avaialble the ipc-driver-test tool can be used to test the
backend with a uhd device, this was so far only tested with a b2xx.

Change-Id: Ice63d3499026293ade8aad675ff7a883bcdd5756
---
M Transceiver52M/Makefile.am
M Transceiver52M/device/Makefile.am
A Transceiver52M/device/ipc/IPCDevice.cpp
A Transceiver52M/device/ipc/IPCDevice.h
A Transceiver52M/device/ipc/Makefile.am
A Transceiver52M/device/ipc/ipc-driver-test.c
A Transceiver52M/device/ipc/ipc-driver-test.h
A Transceiver52M/device/ipc/ipc_chan.c
A Transceiver52M/device/ipc/ipc_chan.h
A Transceiver52M/device/ipc/ipc_shm.c
A Transceiver52M/device/ipc/ipc_shm.h
A Transceiver52M/device/ipc/ipc_sock.c
A Transceiver52M/device/ipc/ipc_sock.h
A Transceiver52M/device/ipc/shm.c
A Transceiver52M/device/ipc/shm.h
A Transceiver52M/device/ipc/uhdwrap.cpp
A Transceiver52M/device/ipc/uhdwrap.h
M Transceiver52M/device/uhd/UHDDevice.cpp
M Transceiver52M/device/uhd/UHDDevice.h
M configure.ac
20 files changed, 3,660 insertions(+), 1 deletion(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-trx refs/changes/41/19641/1

diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index ade4e30..7dad159 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -104,3 +104,13 @@
 	$(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 369e877..29a8062 100644
--- a/Transceiver52M/device/Makefile.am
+++ b/Transceiver52M/device/Makefile.am
@@ -2,6 +2,10 @@
 
 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 0000000..75d80e8
--- /dev/null
+++ b/Transceiver52M/device/ipc/IPCDevice.cpp
@@ -0,0 +1,1271 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at 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 <cstdint>
+#include <cstring>
+#include <cstdlib>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "Logger.h"
+#include "Threads.h"
+#include "IPCDevice.h"
+#include "smpl_buf.h"
+
+extern "C" {
+#include <sys/mman.h>
+#include <sys/stat.h> /* For mode constants */
+#include <fcntl.h> /* For O_* constants */
+
+#include "osmo_signal.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 "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<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), tx_attenuation(),
+	  tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), 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);
+
+	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()
+{
+	//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];
+
+	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));
+		//		shm_io_tx_streams.push_back(ipc_shm_init_producer(shm_dec->channels[i]->dl_stream));
+	}
+
+	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;
+	//struct ipc_sk_if *ipc_prim = (struct ipc_sk_if *) msg->data;
+
+	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 deueue */
+		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<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;
+}
+
+static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+	IPCDevice *device = static_cast<IPCDevice *>(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=<optimized out>) at IPCDevice.cpp:789\n"
+>~"#1  in RadioInterface::start (this=0x614000001640) at radioInterface.cpp:187\n"
+>~"#2  in Transceiver::start (this=<optimized out>) 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=<optimized out>, flags=1) at Transceiver.cpp:168\n"
+>~"#5  in osmo_fd_disp_fds (_rset=<optimized out>, _wset=<optimized out>, _eset=<optimized out>) at select.c:227\n"
+>~"#6  _osmo_select_main (polling=<optimized out>) at select.c:265\n"
+>~"#7  in osmo_select_main (polling=128) at select.c:274\n"
+>~"#8  in main (argc=<optimized out>, argv=<optimized out>) 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<uint16_t> tmp(4096);
+	uint64_t tmps;
+	uint32_t read;
+
+	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<short *> &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 << ")";
+
+			expect_smpls = len - avail_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<short *> &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<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..8973471
--- /dev/null
+++ b/Transceiver52M/device/ipc/IPCDevice.h
@@ -0,0 +1,236 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at 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_
+
+#include <cstdint>
+#include <cstddef>
+#include <climits>
+#include <string>
+
+#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"
+
+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<struct ipc_per_trx_sock_state> 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<smpl_buf *> rx_buffers;
+	double actualSampleRate;
+
+	bool started;
+
+	TIMESTAMP ts_initial, ts_offset;
+
+	std::vector<double> 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<struct ipc_shm_io *> shm_io_rx_streams;
+	std::vector<struct ipc_shm_io *> 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<std::string> &tx_paths, const std::vector<std::string> &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<short *> &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<short *> &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 0000000..5dd1cc8
--- /dev/null
+++ b/Transceiver52M/device/ipc/Makefile.am
@@ -0,0 +1,29 @@
+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 ../uhd/UHDDevice.h uhdwrap.h
+
+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
+
+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 ../uhd/UHDDevice.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
+
diff --git a/Transceiver52M/device/ipc/ipc-driver-test.c b/Transceiver52M/device/ipc/ipc-driver-test.c
new file mode 100644
index 0000000..d71140d
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc-driver-test.c
@@ -0,0 +1,501 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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
+
+#define _GNU_SOURCE
+#include <pthread.h>
+
+
+#include <debug.h>
+
+#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 <getopt.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>
+
+#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;
+
+/* Debug Areas of the code */
+//enum { DMAIN,
+//};
+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),
+};
+#ifdef __cplusplus
+}
+#endif
+
+#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 int ul_running = 0;
+volatile int dl_running = 0;
+
+void *uplink_thread(void *x_void_ptr)
+{
+	uint32_t chann = decoded_region->num_chans;
+	ul_running = 1;
+	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 = 1;
+	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;
+	}
+
+	//osmo_signal_register_handler(SS_GLOBAL, IPC_if_signal_cb, NULL);
+
+	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);
+	}
+}
+
+#if defined(IPCMAGIC) && defined(__cplusplus)
+extern "C" int osmo_ctx_init(const char *id);
+
+extern "C" int magicmain(int argc, char **argv)
+{
+	osmo_ctx_init("main");
+	osmo_select_init();
+
+#else
+int main(int argc, char **argv)
+{
+#endif
+	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()
+	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 0000000..329c6ad
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc-driver-test.h
@@ -0,0 +1,44 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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 <osmocom/core/select.h>
+#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 to lcr */
+	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 0000000..78a3d51
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc_chan.c
@@ -0,0 +1,266 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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 <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 <debug.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>
+
+#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);
+		fprintf(stderr, "%s:%d: IPC_IF_MSG_START_REQ chan priv no %d\n", __FILE__, __LINE__, chan_nr);
+		break;
+	case IPC_IF_MSG_STOP_REQ:
+		rc = ipc_rx_chan_stop_req(&ipc_prim->u.stop_req, chan_nr);
+		fprintf(stderr, "%s:%d: IPC_IF_MSG_STOP_REQ chan priv no %d\n", __FILE__, __LINE__, chan_nr);
+		break;
+	case IPC_IF_MSG_SETGAIN_REQ:
+		rc = ipc_rx_chan_setgain_req(&ipc_prim->u.set_gain_req, chan_nr);
+		fprintf(stderr, "%s:%d: IPC_IF_MSG_SETGAIN_REQ chan priv no %d\n", __FILE__, __LINE__, chan_nr);
+		break;
+	case IPC_IF_MSG_SETFREQ_REQ:
+		rc = ipc_rx_chan_setfreq_req(&ipc_prim->u.set_freq_req, chan_nr);
+		fprintf(stderr, "%s:%d: IPC_IF_MSG_SETFREQ_REQ chan priv no %d\n", __FILE__, __LINE__, chan_nr);
+		break;
+	case IPC_IF_MSG_SETTXATTN_REQ:
+		rc = ipc_rx_chan_settxatten_req(&ipc_prim->u.txatten_req, chan_nr);
+		fprintf(stderr, "%s:%d: IPC_IF_MSG_SETTXATTN_REQ chan priv no %d\n", __FILE__, __LINE__, chan_nr);
+		break;
+	default:
+		fprintf(stderr, "Received unknown IPC msg type %d\n", msg_type);
+		rc = -EINVAL;
+	}
+	fflush(stderr);
+
+	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(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_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(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;
+}
+
+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(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_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(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 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(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 */
+	//IPC_tx_info_ind();
+
+	return 0;
+}
diff --git a/Transceiver52M/device/ipc/ipc_chan.h b/Transceiver52M/device/ipc/ipc_chan.h
new file mode 100644
index 0000000..ff1d5f4
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc_chan.h
@@ -0,0 +1,23 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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 0000000..d860ce7
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc_shm.c
@@ -0,0 +1,204 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Eric Wild <ewild at sysmocom.de>
+*
+* 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 <shm.h>
+#include "ipc_shm.h"
+#include <pthread.h>
+#include <semaphore.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#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) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	rv = pthread_mutexattr_setrobust(&att, PTHREAD_MUTEX_ROBUST);
+	if (rv != 0) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	rv = pthread_mutexattr_setpshared(&att, PTHREAD_PROCESS_SHARED);
+	if (rv != 0) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	rv = pthread_mutex_init((pthread_mutex_t *)&r->this_stream->lock, &att);
+	if (rv != 0) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	pthread_mutexattr_destroy(&att);
+
+	rv = pthread_condattr_setpshared(&t1, PTHREAD_PROCESS_SHARED);
+	if (rv != 0) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	rv = pthread_condattr_setpshared(&t2, PTHREAD_PROCESS_SHARED);
+	if (rv != 0) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->cf, &t1);
+	if (rv != 0) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	rv = pthread_cond_init((pthread_cond_t *)&r->this_stream->ce, &t2);
+	if (rv != 0) {
+		fprintf(stderr, "%s:%d rv:%d", __FILE__, __LINE__, rv);
+		exit(EXIT_FAILURE);
+	}
+
+	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) {
+		if (r->buf_ptrs)
+			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 0000000..66f59c9
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc_shm.h
@@ -0,0 +1,43 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Eric Wild <ewild at sysmocom.de>
+*
+* 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 <shm.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+struct ipc_shm_io {
+	volatile struct ipc_shm_raw_stream *this_stream; // plus num_buffers at end
+	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 0000000..1936674
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc_sock.c
@@ -0,0 +1,271 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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 <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 <debug.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>
+
+#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(DMAIN, LOGL_ERROR, "Received unknown IPC msg type %d\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;
+	//struct ipc_sk_if *ipc_prim = (struct ipc_sk_if *) msg->data;
+
+	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 ipc_sock_close(struct ipc_sock_state *state)
+{
+	struct osmo_fd *bfd = &state->conn_bfd;
+
+	LOGP(DMAIN, 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(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 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(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 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(DMAIN, LOGL_ERROR, "Failed to accept a new connection\n");
+		return -1;
+	}
+
+	if (conn_bfd->fd >= 0) {
+		LOGP(DMAIN, 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(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 */
+	//IPC_tx_info_ind();
+
+	return 0;
+}
diff --git a/Transceiver52M/device/ipc/ipc_sock.h b/Transceiver52M/device/ipc/ipc_sock.h
new file mode 100644
index 0000000..24c6756
--- /dev/null
+++ b/Transceiver52M/device/ipc/ipc_sock.h
@@ -0,0 +1,24 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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 0000000..beebb15
--- /dev/null
+++ b/Transceiver52M/device/ipc/shm.c
@@ -0,0 +1,161 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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 <stdint.h>
+#include <stddef.h>
+#include <osmocom/core/talloc.h>
+
+#include "shm.h"
+
+//#define ENCDECDEBUG
+
+/* 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++) {
+#ifdef ENCDECDEBUG
+		fprintf(stderr, "decode: smpl_buf %d at offset %u\n", i, stream_raw->buffer_offset[i]);
+#endif
+		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;
+#ifdef ENCDECDEBUG
+	fprintf(stderr, "decode: streams at offset %u and %u\n", chan_raw->dl_buf_offset, chan_raw->ul_buf_offset);
+#endif
+	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++) {
+#ifdef ENCDECDEBUG
+		fprintf(stderr, "decode: channel %d at offset %u\n", i, root_raw->chan_offset[i]);
+#endif
+		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);
+#ifdef ENCDECDEBUG
+	fprintf(stderr, "encode: smpl_buf at offset %lu\n", (start - (uint8_t *)root_raw));
+#endif
+	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);
+#ifdef ENCDECDEBUG
+	fprintf(stderr, "encode: stream at offset %lu\n", (start - (ptrdiff_t)root_raw));
+#endif
+	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);
+#ifdef ENCDECDEBUG
+	fprintf(stderr, "encode: channel at offset %lu\n", (start - (uint8_t *)root_raw));
+#endif
+	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);
+#ifdef ENCDECDEBUG
+		fprintf(stderr, "encode: channel %d chan_offset[i]=%u\n", i, ofs);
+#endif
+		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..c9dc58e
--- /dev/null
+++ b/Transceiver52M/device/ipc/shm.h
@@ -0,0 +1,234 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Pau Espin Pedrol <pespin at sysmocom.de>
+*
+* 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 <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+/* 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;
+	pthread_cond_t cf;
+	pthread_cond_t ce;
+	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 0000000..968ddb9
--- /dev/null
+++ b/Transceiver52M/device/ipc/uhdwrap.cpp
@@ -0,0 +1,254 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Eric Wild <ewild at sysmocom.de>
+*
+* 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 <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"
+#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<std::vector<short> >(channel_count, std::vector<short>(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<std::vector<short> >(channel_count, std::vector<short>(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<std::string> tx_paths;
+	std::vector<std::string> 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;
+	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, &timestamp, 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 0000000..1cc31e4
--- /dev/null
+++ b/Transceiver52M/device/ipc/uhdwrap.h
@@ -0,0 +1,81 @@
+/*
+* Copyright 2020 sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+* Author: Eric Wild <ewild at sysmocom.de>
+*
+* 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<std::vector<short> > wrap_rx_buffs;
+	std::vector<std::vector<short> > wrap_tx_buffs;
+	std::vector<short *> wrap_rx_buf_ptrs;
+	std::vector<short *> wrap_tx_buf_ptrs;
+
+	template <typename... Args> 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 b5dda28..037f3d9 100644
--- a/Transceiver52M/device/uhd/UHDDevice.cpp
+++ b/Transceiver52M/device/uhd/UHDDevice.cpp
@@ -1316,6 +1316,7 @@
 	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<std::string>& tx_paths,
@@ -1323,3 +1324,4 @@
 {
 	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 22a0948..c159e63 100644
--- a/Transceiver52M/device/uhd/UHDDevice.h
+++ b/Transceiver52M/device/uhd/UHDDevice.h
@@ -133,7 +133,7 @@
 		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 07d4bf4..d4722cf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -325,6 +325,7 @@
     Transceiver52M/device/uhd/Makefile \
     Transceiver52M/device/usrp1/Makefile \
     Transceiver52M/device/lms/Makefile \
+    Transceiver52M/device/ipc/Makefile \
     tests/Makefile \
     tests/CommonLibs/Makefile \
     tests/Transceiver52M/Makefile \

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-trx/+/19641
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-trx
Gerrit-Branch: master
Gerrit-Change-Id: Ice63d3499026293ade8aad675ff7a883bcdd5756
Gerrit-Change-Number: 19641
Gerrit-PatchSet: 1
Gerrit-Owner: Hoernchen <ewild at sysmocom.de>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200814/9415437d/attachment.htm>


More information about the gerrit-log mailing list